3 # Copyright (C) 2015 BC Libraries Cooperative
5 # This program is free software; you can redistribute it and/or
6 # modify it under the terms of the GNU General Public License
7 # as published by the Free Software Foundation; either version 2
8 # of the License, or (at your option) any later version.
10 # This program is distributed in the hope that it will be useful,
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 # GNU General Public License for more details.
15 # You should have received a copy of the GNU General Public License
16 # along with this program; if not, write to the Free Software
17 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
19 package OpenILS::Application::EbookAPI::OneClickdigital;
24 use OpenILS::Application;
25 use OpenILS::Application::EbookAPI;
26 use base qw/OpenILS::Application::EbookAPI/;
27 use OpenSRF::AppSession;
28 use OpenSRF::EX qw(:try);
29 use OpenSRF::Utils::SettingsClient;
30 use OpenSRF::Utils::Logger qw($logger);
31 use OpenSRF::Utils::Cache;
32 use OpenILS::Application::AppUtils;
36 my( $class, $args ) = @_;
37 $class = ref $class || $class;
38 return bless $args, $class;
48 return $self->{vendor};
53 return $self->{session_id};
58 return $self->{base_uri};
63 return $self->{library_id};
68 return $self->{basic_token};
73 return $self->{patron_id};
80 $self->{base_uri} = 'https://api.oneclickdigital.us/v1'; # TODO pull from org setting?
82 my $library_id = OpenILS::Application::AppUtils->ou_ancestor_setting_value($ou, 'ebook_api.oneclickdigital.library_id');
84 $self->{library_id} = $library_id;
86 $logger->error("EbookAPI: no OneClickdigital library ID found for org unit $ou");
90 my $basic_token = OpenILS::Application::AppUtils->ou_ancestor_setting_value($ou, 'ebook_api.oneclickdigital.basic_token');
92 $self->{basic_token} = $basic_token;
94 $logger->error("EbookAPI: no OneClickdigital basic token found for org unit $ou");
102 # OneClickdigital API does not require separate client auth;
103 # we just need to include our basic auth token in requests
109 # retrieve OneClickdigital patron ID (if any) based on patron barcode
110 # GET http://api.oneclickdigital.us/v1/rpc/libraries/{libraryID}/patrons/{barcode}
112 my ($self, $barcode) = @_;
113 my $base_uri = $self->{base_uri};
114 my $library_id = $self->{library_id};
115 my $session_id = $self->{session_id};
118 uri => "$base_uri/rpc/libraries/$library_id/patrons/$barcode"
120 my $res = $self->request($req, $session_id);
121 # TODO distinguish between unregistered patrons and patron auth failure
122 if (defined ($res) && $res->{content}->{patronId}) {
123 return $res->{content}->{patronId};
125 $logger->error("EbookAPI: no OneClickdigital patron ID found for barcode $barcode");
129 # does this title have available "copies"? y/n
130 # GET http://api.oneclickdigital.us/v1/libraries/{libraryID}/media/{isbn}/availability
131 sub do_availability_lookup {
132 my ($self, $isbn) = @_;
133 my $base_uri = $self->{base_uri};
134 my $library_id = $self->{library_id};
135 my $session_id = $self->{session_id};
138 uri => "$base_uri/libraries/$library_id/media/$isbn/availability"
140 my $res = $self->request($req, $session_id);
141 if (defined ($res)) {
142 $logger->info("EbookAPI: received availability response for ISBN $isbn: " . Dumper $res);
143 return $res->{content}->{availability};
145 $logger->error("EbookAPI: could not retrieve OneClickdigital availability for ISBN $isbn");
150 # OneClickdigital API does not support detailed holdings lookup,
151 # so we return basic availability information.
152 sub do_holdings_lookup {
153 my ($self, $isbn) = @_;
154 my $avail = $self->do_availability_lookup($isbn);
155 return { available => $avail };
158 # checkout an item to a patron
159 # item is identified by ISBN, patron ID is their barcode
160 # POST //api.{domain}/v1/libraries/{libraryId}/patrons/{patronId}/checkouts/{isbn}
162 my ($self, $isbn, $patron_id) = @_;
163 my $base_uri = $self->{base_uri};
164 my $library_id = $self->{library_id};
165 my $session_id = $self->{session_id};
168 uri => "$base_uri/libraries/$library_id/patrons/$patron_id/checkouts/$isbn"
170 my $res = $self->request($req, $session_id);
172 # TODO: more sophisticated response handling
173 # HTTP 200 response indicates success, HTTP 409 indicates checkout limit reached
174 if (defined ($res)) {
175 if ($res->{is_success}) {
177 xact_id => $res->{content}->{transactionId},
178 due_date => $res->{content}->{expiration}
181 $logger->error("EbookAPI: checkout failed for OneClickdigital title $isbn");
182 return { error_msg => $res->{content} };
185 $logger->error("EbookAPI: no response received from OneClickdigital server");
190 # renew a checked-out item
191 # item id = ISBN, patron id = barcode
192 # PUT //api.{domain}/v1/libraries/{libraryId}/patrons/{patronId}/checkouts/{isbn}
194 my ($self, $isbn, $patron_id) = @_;
195 my $base_uri = $self->{base_uri};
196 my $library_id = $self->{library_id};
197 my $session_id = $self->{session_id};
200 uri => "$base_uri/libraries/$library_id/patrons/$patron_id/checkouts/$isbn"
202 my $res = $self->request($req, $session_id);
204 # TODO: more sophisticated response handling
205 # HTTP 200 response indicates success
206 if (defined ($res)) {
207 if ($res->{is_success}) {
209 xact_id => $res->{content}->{transactionId},
210 due_date => $res->{content}->{expiration}
213 $logger->error("EbookAPI: renewal failed for OneClickdigital title $isbn");
214 return { error_msg => $res->{content} };
217 $logger->error("EbookAPI: no response received from OneClickdigital server");
222 # checkin a checked-out item
223 # item id = ISBN, patron id = barcode
224 # XXX API docs indicate that a bearer token is required!
225 # DELETE //api.{domain}/v1/libraries/{libraryId}/patrons/{patronId}/checkouts/{isbn}
235 # GET //api.{domain}/v1/libraries/{libraryId}/patrons/{patronId}/checkouts/all
236 sub get_patron_checkouts {
237 my ($self, $patron_id) = @_;
238 my $base_uri = $self->{base_uri};
239 my $library_id = $self->{library_id};
240 my $session_id = $self->{session_id};
243 uri => "$base_uri/libraries/$library_id/patrons/$patron_id/checkouts/all"
245 my $res = $self->request($req, $session_id);
248 if (defined ($res)) {
249 $logger->info("EbookAPI: received response for OneClickdigital checkouts: " . Dumper $res);
250 foreach my $checkout (@{$res->{content}}) {
252 xact_id => $checkout->{transactionID},
253 title_id => $checkout->{isbn},
254 due_date => $checkout->{expiration},
255 download_url => $checkout->{downloadURL},
256 title => $checkout->{title},
257 author => $checkout->{authors}
260 $logger->info("EbookAPI: retrieved " . scalar(@$checkouts) . " OneClickdigital checkouts for patron $patron_id");
261 $self->{checkouts} = $checkouts;
262 return $self->{checkouts};
264 $logger->error("EbookAPI: failed to retrieve OneClickdigital checkouts for patron $patron_id");
269 # GET //api.{domain}/v1/libraries/{libraryId}/patrons/{patronId}/holds/all
270 sub get_patron_holds {
271 my ($self, $patron_id) = @_;
272 my $base_uri = $self->{base_uri};
273 my $library_id = $self->{library_id};
274 my $session_id = $self->{session_id};
277 uri => "$base_uri/libraries/$library_id/patrons/$patron_id/holds/all"
279 my $res = $self->request($req, $session_id);
282 if (defined ($res)) {
283 $logger->info("EbookAPI: received response for OneClickdigital holds: " . Dumper $res);
284 foreach my $hold (@{$res->{content}}) {
286 xact_id => $hold->{transactionID},
287 title_id => $hold->{isbn},
288 expire_date => $hold->{expiration},
289 title => $hold->{title},
290 author => $hold->{authors},
291 # XXX queue position/size and pending vs ready info not available via API
292 queue_position => '-',
297 $logger->info("EbookAPI: retrieved " . scalar(@$holds) . " OneClickdigital holds for patron $patron_id");
298 $self->{holds} = $holds;
299 return $self->{holds};
301 $logger->error("EbookAPI: failed to retrieve OneClickdigital holds for patron $patron_id");