LP#1541559: ebook API handler for OverDrive
authorJeff Davis <jdavis@sitka.bclibraries.ca>
Tue, 7 Feb 2017 23:23:12 +0000 (15:23 -0800)
committerKathy Lussier <klussier@masslnc.org>
Mon, 20 Feb 2017 23:54:38 +0000 (18:54 -0500)
Signed-off-by: Jeff Davis <jdavis@sitka.bclibraries.ca>
Signed-off-by: Kathy Lussier <klussier@masslnc.org>
Open-ILS/src/perlmods/MANIFEST
Open-ILS/src/perlmods/lib/OpenILS/Application/EbookAPI/OverDrive.pm [new file with mode: 0644]
Open-ILS/src/perlmods/t/23-OpenILS-Application-EbookAPI.t
Open-ILS/src/sql/Pg/950.data.seed-values.sql
Open-ILS/src/sql/Pg/upgrade/XXXX.data.org-setting.ebook-api-overdrive.sql [new file with mode: 0644]

index 8de8df8..d3f1deb 100644 (file)
@@ -41,6 +41,7 @@ lib/OpenILS/Application/Circ/Transit.pm
 lib/OpenILS/Application/Collections.pm
 lib/OpenILS/Application/EbookAPI.pm
 lib/OpenILS/Application/EbookAPI/Test.pm
+lib/OpenILS/Application/EbookAPI/OverDrive.pm
 lib/OpenILS/Application/Fielder.pm
 lib/OpenILS/Application/PermaCrud.pm
 lib/OpenILS/Application/Proxy.pm
diff --git a/Open-ILS/src/perlmods/lib/OpenILS/Application/EbookAPI/OverDrive.pm b/Open-ILS/src/perlmods/lib/OpenILS/Application/EbookAPI/OverDrive.pm
new file mode 100644 (file)
index 0000000..6bb5be5
--- /dev/null
@@ -0,0 +1,561 @@
+#!/usr/bin/perl
+
+# Copyright (C) 2015 BC Libraries Cooperative
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+# 
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+# 
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+
+package OpenILS::Application::EbookAPI::OverDrive;
+
+use strict;
+use warnings;
+
+use OpenILS::Application;
+use OpenILS::Application::EbookAPI;
+use base qw/OpenILS::Application::EbookAPI/;
+use OpenSRF::AppSession;
+use OpenSRF::EX qw(:try);
+use OpenSRF::Utils::SettingsClient;
+use OpenSRF::Utils::Logger qw($logger);
+use OpenSRF::Utils::Cache;
+use OpenILS::Application::AppUtils;
+use Data::Dumper;
+
+sub new {
+    my( $class, $args ) = @_;
+    $class = ref $class || $class;
+    return bless $args, $class;
+}
+
+sub ou {
+    my $self = shift;
+    return $self->{ou};
+}
+
+sub vendor {
+    my $self = shift;
+    return $self->{vendor};
+}
+
+sub session_id {
+    my $self = shift;
+    return $self->{session_id};
+}
+
+sub account_id {
+    my $self = shift;
+    return $self->{account_id};
+}
+
+sub websiteid {
+    my $self = shift;
+    return $self->{websiteid};
+}
+
+sub authorizationname {
+    my $self = shift;
+    return $self->{authorizationname};
+}
+
+sub basic_token {
+    my $self = shift;
+    return $self->{basic_token};
+}
+
+sub bearer_token {
+    my $self = shift;
+    return $self->{bearer_token};
+}
+
+sub collection_token {
+    my $self = shift;
+    return $self->{collection_token};
+}
+
+sub granted_auth_uri {
+    my $self = shift;
+    return $self->{granted_auth_uri};
+}
+
+sub password_required {
+    my $self = shift;
+    return $self->{password_required};
+}
+
+sub patron_token {
+    my $self = shift;
+    return $self->{patron_token};
+}
+
+sub initialize {
+    my $self = shift;
+    my $ou = $self->{ou};
+
+    my $discovery_base_uri = OpenILS::Application::AppUtils->ou_ancestor_setting_value($ou, 'ebook_api.overdrive.discovery_base_uri');
+    $self->{discovery_base_uri} = $discovery_base_uri || 'http://api.overdrive.com/v1';
+    my $circulation_base_uri = OpenILS::Application::AppUtils->ou_ancestor_setting_value($ou, 'ebook_api.overdrive.circulation_base_uri');
+    $self->{circulation_base_uri} = $circulation_base_uri || 'http://patron.api.overdrive.com/v1';
+
+    my $account_id = OpenILS::Application::AppUtils->ou_ancestor_setting_value($ou, 'ebook_api.overdrive.account_id');
+    if ($account_id) {
+        $self->{account_id} = $account_id;
+    } else {
+        $logger->error("EbookAPI: no OverDrive account ID found for org unit $ou");
+        return;
+    }
+
+    my $websiteid = OpenILS::Application::AppUtils->ou_ancestor_setting_value($ou, 'ebook_api.overdrive.websiteid');
+    if ($websiteid) {
+        $self->{websiteid} = $websiteid;
+    } else {
+        $logger->error("EbookAPI: no OverDrive website ID found for org unit $ou");
+        return;
+    }
+
+    my $authorizationname = OpenILS::Application::AppUtils->ou_ancestor_setting_value($ou, 'ebook_api.overdrive.authorizationname');
+    if ($authorizationname) {
+        $self->{authorizationname} = $authorizationname;
+    } else {
+        $logger->error("EbookAPI: no OverDrive authorization name found for org unit $ou");
+        return;
+    }
+
+    my $basic_token = OpenILS::Application::AppUtils->ou_ancestor_setting_value($ou, 'ebook_api.overdrive.basic_token');
+    if ($basic_token) {
+        $self->{basic_token} = $basic_token;
+    } else {
+        $logger->error("EbookAPI: no OverDrive basic token found for org unit $ou");
+        return;
+    }
+
+    my $granted_auth_uri = OpenILS::Application::AppUtils->ou_ancestor_setting_value($ou, 'ebook_api.overdrive.granted_auth_redirect_uri');
+    if ($granted_auth_uri) {
+        $self->{granted_auth_uri} = $granted_auth_uri;
+    }
+
+    my $password_required = OpenILS::Application::AppUtils->ou_ancestor_setting_value($ou, 'ebook_api.overdrive.password_required') || 0;
+    $self->{password_required} = $password_required;
+
+    return $self;
+
+}
+
+# Wrapper method for HTTP requests.
+sub handle_http_request {
+    my $self = shift;
+    my $req = shift;
+
+    # Prep our request using defaults.
+    $req->{method} = 'GET' if (!$req->{method});
+    $req = $self->set_http_headers($req);
+
+    # Send the request.
+    my $res = $self->request($req, $self->{session_id});
+
+    $logger->info("EbookAPI: raw OverDrive HTTP response: " . Dumper $res);
+
+    # A "401 Unauthorized" response means we need to re-auth our client or patron.
+    if (defined ($res) && $res->{status} =~ /^401/) {
+        $logger->info("EbookAPI: 401 response received from OverDrive, re-authorizing...");
+
+        # Always re-auth client to ensure we have an up-to-date client token.
+        $self->do_client_auth();
+
+        # If we're using a Circulation API, redo patron auth too.
+        my $circulation_base_uri = $self->{circulation_base_uri};
+        if ($req->{uri} =~ /^$circulation_base_uri/) {
+            $self->do_patron_auth();
+        }
+
+        # Now we can update our headers with our fresh client/patron tokens
+        # and re-send our request.
+        $req = $self->set_http_headers($req);
+        return $self->request($req, $self->{session_id});
+    }
+
+    # For any non-401 response (including no response at all),
+    # just return whatever response we got (if any).
+    return $res;
+}
+
+# Set the correct headers for our request.
+# Authorization headers are determined by which API we're using:
+# - Circulation APIs use a patron access token.
+# - Discovery APIs use a regular access token.
+# - For other APIs, fallback to our basic token.
+sub set_http_headers {
+    my $self = shift;
+    my $req = shift;
+    $req->{headers} = {} if (!$req->{headers});
+    if (!$req->{headers}->{Authorization}) {
+        my $auth_type;
+        my $token;
+        my $circulation_base_uri = $self->{circulation_base_uri};
+        my $discovery_base_uri = $self->{discovery_base_uri};
+        if ($req->{uri} =~ /^$circulation_base_uri/) {
+            $auth_type = 'Bearer';
+            $token = $self->{patron_token};
+        } elsif ($req->{uri} =~ /^$discovery_base_uri/) {
+            $auth_type = 'Bearer';
+            $token = $self->{bearer_token};
+        } else {
+            $auth_type = 'Basic';
+            $token = $self->{basic_token};
+        }
+        if (!$token) {
+            $logger->error("EbookAPI: unable to set HTTP Authorization header without token");
+            $logger->error("EbookAPI: failed request: " . Dumper $req);
+            return;
+        } else {
+            $req->{headers}->{Authorization} = "$auth_type $token";
+        }
+    }
+    return $req;
+}
+
+# POST /token HTTP/1.1
+# Host: oauth.overdrive.com
+# Authorization: Basic czZCaGRSa3F0MzpnWDFmQmF0M2JW
+# 
+# grant_type=client_credentials
+sub do_client_auth {
+    my $self = shift;
+    my $req = {
+        method  => 'POST',
+        uri     => 'https://oauth.overdrive.com/token',
+        headers => {
+            'Authorization' => 'Basic ' . $self->{basic_token},
+            'Content-Type'  => 'application/x-www-form-urlencoded;charset=UTF-8'
+        },
+        content => 'grant_type=client_credentials'
+    };
+    my $res = $self->request($req, $self->{session_id});
+
+    if (defined ($res)) {
+        if ($res->{content}->{access_token}) {
+            # save our access token for future use
+            $self->{bearer_token} = $res->{content}->{access_token};
+            # use access token to grab other library info (e.g. collection token)
+            $self->get_library_info();
+            return $res;
+        } else {
+            $logger->error("EbookAPI: bearer token not received from OverDrive API");
+            $logger->error("EbookAPI: bad response: " . Dumper $res);
+        }
+    } else {
+        $logger->error("EbookAPI: no client authentication response from OverDrive API");
+    }
+    return;
+}
+
+sub do_patron_auth {
+    my $self = shift;
+    my @args = @_;
+    if ($self->{granted_auth_uri}) {
+        return $self->do_granted_patron_auth(@args);
+    } else {
+        return $self->do_basic_patron_auth(@args);
+    }
+}
+
+# TODO
+sub do_granted_patron_auth {
+}
+
+# POST /patrontoken HTTP/1.1
+# Host: oauth-patron.overdrive.com
+# Authorization: Basic {Base64-encoded string}
+# Content-Type: application/x-www-form-urlencoded;charset=UTF-8
+# 
+# grant_type=password&username=1234567890&password=1234&scope=websiteid:12345 authorizationname:default
+# OR:
+# grant_type=password&username=1234567890&password=[ignore]&password_required=false&scope=websiteid:12345 authorizationname:default
+sub do_basic_patron_auth {
+    my $self = shift;
+    my $barcode = shift;
+
+    if ($barcode) {
+        if (!$self->{patron_barcode}) {
+            $self->{patron_barcode} = $barcode;
+        } elsif ($barcode ne $self->{patron_barcode}) {
+            $logger->error("EbookAPI: patron barcode in auth request does not match patron barcode for this session");
+            return;
+        }
+    } else {
+        if (!$self->{patron_barcode}) {
+            $logger->error("EbookAPI: Cannot authenticate patron with unknown barcode");
+        } else {
+            $barcode = $self->{patron_barcode};
+        }
+    }
+
+    # TODO handle cached/expired tokens?
+    # Making a request using an expired token will give a 401 Unauthorized error.
+    # Handle this appropriately.
+
+    # request content is an ugly url-encoded string
+    my $pw = (defined $self->{patron_password}) ? $self->{patron_password} : '';
+    my $content = 'grant_type=password';
+    $content .= "&username=$barcode";
+    if ($self->{password_required}) {
+        $content .= "&password=$pw";
+    } else {
+        $content .= '&password=xxx&password_required=false'
+    }
+    $content .= '&scope=websiteid:' . $self->{websiteid} . ' authorizationname:' . $self->{authorizationname};
+
+    my $req = {
+        method  => 'POST',
+        uri     => 'https://oauth-patron.overdrive.com/patrontoken',
+        headers => {
+            'Authorization' => 'Basic ' . $self->{basic_token},
+            'Content-Type'  => 'application/x-www-form-urlencoded;charset=UTF-8'
+        },
+        content => $content
+    };
+    my $res = $self->request($req, $self->{session_id});
+
+    if (defined ($res)) {
+        if ($res->{content}->{access_token}) {
+            $self->{patron_token} = $res->{content}->{access_token};
+            return $self->{patron_token};
+        } else {
+            $logger->error("EbookAPI: patron access token not received from OverDrive API");
+        }
+    } else {
+        $logger->error("EbookAPI: no patron authentication response from OverDrive API");
+    }
+    return;
+}
+
+# GET http://api.overdrive.com/v1/libraries/1225
+# User-Agent: {Your application}
+# Authorization: Bearer {OAuth access token}
+# Host: api.overdrive.com
+sub get_library_info {
+    my $self = shift;
+    my $library_id = $self->{account_id};
+    my $req = {
+        method  => 'GET',
+        uri     => $self->{discovery_base_uri} . "/libraries/$library_id"
+    };
+    if (my $res = $self->handle_http_request($req, $self->{session_id})) {
+        $self->{collection_token} = $res->{content}->{collectionToken};
+        return $self->{collection_token};
+    } else {
+        $logger->error("EbookAPI: OverDrive Library Account API request failed");
+        return;
+    }
+}
+
+# GET http://api.overdrive.com/v1/collections/v1L1BYwAAAA2Q/products/76c1b7d0-17f4-4c05-8397-c66c17411584/metadata
+# User-Agent: {Your application}
+# Authorization: Bearer {OAuth access token}
+# Host: api.overdrive.com
+sub get_title_info {
+    my $self = shift;
+    my $title_id = shift;
+    $self->do_client_auth() if (!$self->{bearer_token});
+    $self->get_library_info() if (!$self->{collection_token});
+    my $collection_token = $self->{collection_token};
+    my $req = {
+        method  => 'GET',
+        uri     => $self->{discovery_base_uri} . "/collections/$collection_token/products/$title_id/metadata"
+    };
+    if (my $res = $self->handle_http_request($req, $self->{session_id})) {
+        if ($res->{content}->{title}) {
+            return {
+                title  => $res->{content}->{title},
+                author => $res->{content}->{creators}[0]{name}
+            };
+        } else {
+            $logger->error("EbookAPI: OverDrive metadata lookup failed for $title_id");
+        }
+    } else {
+        $logger->error("EbookAPI: no metadata response from OverDrive API");
+    }
+    return;
+}
+
+# GET http://api.overdrive.com/v1/collections/L1BAAEAAA2i/products/76C1B7D0-17F4-4C05-8397-C66C17411584/availability
+# User-Agent: {Your application}
+# Authorization: Bearer {OAuth access token}
+# Host: api.overdrive.com
+sub do_availability_lookup {
+    my $self = shift;
+    my $title_id = shift;
+    $self->do_client_auth() if (!$self->{bearer_token});
+    $self->get_library_info() if (!$self->{collection_token});
+    my $req = {
+        method  => 'GET',
+        uri     => $self->{discovery_base_uri} . "/collections/" . $self->{collection_token} . "/products/$title_id/availability"
+    };
+    if (my $res = $self->handle_http_request($req, $self->{session_id})) {
+        return $res->{content}->{available};
+    } else {
+        $logger->error("EbookAPI: could not retrieve OverDrive availability for title $title_id");
+        return;
+    }
+}
+
+# Holdings lookup has two parts:
+#
+# 1. Copy availability: as above, but grab more details.
+#
+# 2. Formats:
+#     GET https://api.overdrive.com/v1/collections/v1L1BYwAAAA2Q/products/76c1b7d0-17f4-4c05-8397-c66c17411584/metadata
+#     User-Agent: {Your application}
+#     Authorization: Bearer {OAuth access token}
+#     Host: api.overdrive.com
+#
+sub do_holdings_lookup {
+    my ($self, $title_id) = @_;
+    $self->do_client_auth() if (!$self->{bearer_token});
+    $self->get_library_info() if (!$self->{collection_token});
+    my $collection_token = $self->{collection_token};
+
+    # prepare data structure to be used as return value
+    my $holdings = {
+        copies_owned => 0,
+        copies_available => 0,
+        formats => []
+    };
+
+    # request copy availability totals
+    my $avail_req = {
+        method  => 'GET',
+        uri     => $self->{discovery_base_uri} . "/collections/$collection_token/products/$title_id/availability"
+    };
+    if (my $avail_res = $self->handle_http_request($avail_req, $self->{session_id})) {
+        $holdings->{copies_owned} = $avail_res->{content}->{copiesOwned};
+        $holdings->{copies_available} = $avail_res->{content}->{copiesAvailable};
+    } else {
+        $logger->error("EbookAPI: failed to retrieve OverDrive holdings counts for title $title_id");
+    }
+
+    # request available formats
+    my $format_req = {
+        method  => 'GET',
+        uri     => $self->{discovery_base_uri} . "/collections/$collection_token/products/$title_id/metadata"
+    };
+    if (my $format_res = $self->handle_http_request($format_req, $self->{session_id})) {
+        if ($format_res->{content}->{formats}) {
+            foreach my $f (@{$format_res->{content}->{formats}}) {
+                push @{$holdings->{formats}}, $f->{name};
+            }
+        } else {
+            $logger->info("EbookAPI: OverDrive holdings format request for title $title_id contained no format information");
+        }
+    } else {
+        $logger->error("EbookAPI: failed to retrieve OverDrive holdings formats for title $title_id");
+    }
+
+    return $holdings;
+}
+
+# List of patron checkouts:
+# GET http://patron.api.overdrive.com/v1/patrons/me/checkouts
+# User-Agent: {Your application}
+# Authorization: Bearer {OAuth patron access token}
+# Host: patron.api.overdrive.com
+#
+# Response looks like this:
+# {
+#     "totalItems": 4,
+#     "totalCheckouts": 2,
+#     "checkouts": [
+#         {
+#             "reserveId": "A03EAC2C-C088-46C6-B9E9-59D6C11A3596",
+#             "expires": "2015-08-11T18:53:00Z",
+#             ...
+#         }
+#     ],
+#     ...
+# }
+#
+# To get title metadata (e.g. title/author), do get_title_info(reserveId).
+sub get_patron_checkouts {
+    my $self = shift;
+    my $patron_token = shift;
+    if (my $res = $self->do_get_patron_xacts('checkouts', $patron_token)) {
+        my $checkouts = [];
+        foreach my $checkout (@{$res->{content}->{checkouts}}) {
+            my $title_id = $checkout->{reserveId};
+            my $title_info = $self->get_title_info($title_id);
+            # TODO get download URL - need to "lock in" a format first, see OD Checkouts API docs
+            push @$checkouts, {
+                title_id => $title_id,
+                due_date => $checkout->{expires},
+                title => $title_info->{title},
+                author => $title_info->{author}
+            }
+        };
+        $self->{checkouts} = $checkouts;
+        return $self->{checkouts};
+    } else {
+        $logger->error("EbookAPI: unable to retrieve OverDrive checkouts for patron " . $self->{patron_barcode});
+        return;
+    }
+}
+
+sub get_patron_holds {
+    my $self = shift;
+    my $patron_token = shift;
+    if (my $res = $self->do_get_patron_xacts('holds', $patron_token)) {
+        my $holds = [];
+        foreach my $hold (@{$res->{content}->{holds}}) {
+            my $title_id = $hold->{reserveId};
+            my $title_info = $self->get_title_info($title_id);
+            my $this_hold = {
+                title_id => $title_id,
+                queue_position => $hold->{holdListPosition},
+                queue_size => $hold->{numberOfHolds},
+                # TODO: special handling for ready-to-checkout holds
+                is_ready => ( $hold->{actions}->{checkout} ) ? 1 : 0,
+                create_date => $hold->{holdPlacedDate},
+                expire_date => ( $hold->{holdExpires} ) ? $hold->{holdExpires} : '-',
+                title => $title_info->{title},
+                author => $title_info->{author}
+            };
+            # TODO: hold suspensions
+            push @$holds, $this_hold;
+        }
+        $self->{holds} = $holds;
+        return $self->{holds};
+    } else {
+        $logger->error("EbookAPI: unable to retrieve OverDrive holds for patron " . $self->{patron_barcode});
+        return;
+    }
+}
+
+# generic function for retrieving patron transactions
+sub do_get_patron_xacts {
+    my $self = shift;
+    my $xact_type = shift;
+    my $patron_token = shift;
+    if (!$patron_token) {
+        if ($self->{patron_barcode}) {
+            $self->do_client_auth() if (!$self->{bearer_token});
+            $self->do_patron_auth();
+        } else {
+            $logger->error("EbookAPI: Cannot retrieve OverDrive $xact_type with no patron information");
+        }
+    }
+    my $req = {
+        method  => 'GET',
+        uri     => $self->{circulation_base_uri} . "/patrons/me/$xact_type"
+    };
+    return $self->handle_http_request($req, $self->{session_id});
+}
+
index 338f043..44a1d42 100644 (file)
@@ -1,9 +1,10 @@
 #!perl -T
 
-use Test::More tests => 2;
+use Test::More tests => 3;
 
 BEGIN {
     use_ok( 'OpenILS::Application::EbookAPI' );
     use_ok( 'OpenILS::Application::EbookAPI::Test' );
+    use_ok( 'OpenILS::Application::EbookAPI::OverDrive' );
 }
 
index d294365..08f8877 100644 (file)
@@ -16666,3 +16666,137 @@ INSERT INTO config.global_flag (name, label, value, enabled) VALUES (
     TRUE
 );
 
+INSERT INTO config.settings_group (name, label)
+    VALUES ('ebook_api', 'Ebook API Integration');
+
+INSERT INTO config.org_unit_setting_type
+    (name, label, description, grp, datatype) 
+VALUES (
+    'ebook_api.overdrive.discovery_base_uri',
+    oils_i18n_gettext(
+        'ebook_api.overdrive.discovery_base_uri',
+        'OverDrive Discovery API Base URI',
+        'coust',
+        'label'
+    ),
+    oils_i18n_gettext(
+        'ebook_api.overdrive.discovery_base_uri',
+        'Base URI for OverDrive Discovery API (defaults to http://api.overdrive.com/v1)',
+        'coust',
+        'description'
+    ),
+    'ebook_api',
+    'string'
+),(
+    'ebook_api.overdrive.circulation_base_uri',
+    oils_i18n_gettext(
+        'ebook_api.overdrive.circulation_base_uri',
+        'OverDrive Circulation API Base URI',
+        'coust',
+        'label'
+    ),
+    oils_i18n_gettext(
+        'ebook_api.overdrive.circulation_base_uri',
+        'Base URI for OverDrive Circulation API (defaults to http://patron.api.overdrive.com/v1)',
+        'coust',
+        'description'
+    ),
+    'ebook_api',
+    'string'
+),(
+    'ebook_api.overdrive.account_id',
+    oils_i18n_gettext(
+        'ebook_api.overdrive.account_id',
+        'OverDrive Account ID',
+        'coust',
+        'label'
+    ),
+    oils_i18n_gettext(
+        'ebook_api.overdrive.account_id',
+        'Account ID (a.k.a. Library ID) for this library, as assigned by OverDrive',
+        'coust',
+        'description'
+    ),
+    'ebook_api',
+    'string'
+),(
+    'ebook_api.overdrive.websiteid',
+    oils_i18n_gettext(
+        'ebook_api.overdrive.websiteid',
+        'OverDrive Website ID',
+        'coust',
+        'label'
+    ),
+    oils_i18n_gettext(
+        'ebook_api.overdrive.websiteid',
+        'Website ID for this library, as assigned by OverDrive',
+        'coust',
+        'description'
+    ),
+    'ebook_api',
+    'string'
+),(
+    'ebook_api.overdrive.authorizationname',
+    oils_i18n_gettext(
+        'ebook_api.overdrive.authorizationname',
+        'OverDrive Authorization Name',
+        'coust',
+        'label'
+    ),
+    oils_i18n_gettext(
+        'ebook_api.overdrive.authorizationname',
+        'Authorization name for this library, as assigned by OverDrive',
+        'coust',
+        'description'
+    ),
+    'ebook_api',
+    'string'
+),(
+    'ebook_api.overdrive.basic_token',
+    oils_i18n_gettext(
+        'ebook_api.overdrive.basic_token',
+        'OverDrive Basic Token',
+        'coust',
+        'label'
+    ),
+    oils_i18n_gettext(
+        'ebook_api.overdrive.basic_token',
+        'Basic token for client authentication with OverDrive API (supplied by OverDrive)',
+        'coust',
+        'description'
+    ),
+    'ebook_api',
+    'string'
+),(
+    'ebook_api.overdrive.granted_auth_redirect_uri',
+    oils_i18n_gettext(
+        'ebook_api.overdrive.granted_auth_redirect_uri',
+        'OverDrive Granted Authorization Redirect URI',
+        'coust',
+        'label'
+    ),
+    oils_i18n_gettext(
+        'ebook_api.overdrive.granted_auth_redirect_uri',
+        'URI provided to OverDrive for use with granted authorization',
+        'coust',
+        'description'
+    ),
+    'ebook_api',
+    'string'
+),(
+    'ebook_api.overdrive.password_required',
+    oils_i18n_gettext(
+        'ebook_api.overdrive.password_required',
+        'OverDrive Password Required',
+        'coust',
+        'label'
+    ),
+    oils_i18n_gettext(
+        'ebook_api.overdrive.password_required',
+        'Does this library require a password when authenticating patrons with the OverDrive API?',
+        'coust',
+        'description'
+    ),
+    'ebook_api',
+    'bool'
+);
diff --git a/Open-ILS/src/sql/Pg/upgrade/XXXX.data.org-setting.ebook-api-overdrive.sql b/Open-ILS/src/sql/Pg/upgrade/XXXX.data.org-setting.ebook-api-overdrive.sql
new file mode 100644 (file)
index 0000000..7e10262
--- /dev/null
@@ -0,0 +1,141 @@
+BEGIN;
+
+SELECT evergreen.upgrade_deps_block_check('XXXX', :eg_version);
+
+INSERT INTO config.settings_group (name, label)
+    VALUES ('ebook_api', 'Ebook API Integration');
+
+INSERT INTO config.org_unit_setting_type
+    (name, label, description, grp, datatype) 
+VALUES (
+    'ebook_api.overdrive.discovery_base_uri',
+    oils_i18n_gettext(
+        'ebook_api.overdrive.discovery_base_uri',
+        'OverDrive Discovery API Base URI',
+        'coust',
+        'label'
+    ),
+    oils_i18n_gettext(
+        'ebook_api.overdrive.discovery_base_uri',
+        'Base URI for OverDrive Discovery API (defaults to http://api.overdrive.com/v1)',
+        'coust',
+        'description'
+    ),
+    'ebook_api',
+    'string'
+),(
+    'ebook_api.overdrive.circulation_base_uri',
+    oils_i18n_gettext(
+        'ebook_api.overdrive.circulation_base_uri',
+        'OverDrive Circulation API Base URI',
+        'coust',
+        'label'
+    ),
+    oils_i18n_gettext(
+        'ebook_api.overdrive.circulation_base_uri',
+        'Base URI for OverDrive Circulation API (defaults to http://patron.api.overdrive.com/v1)',
+        'coust',
+        'description'
+    ),
+    'ebook_api',
+    'string'
+),(
+    'ebook_api.overdrive.account_id',
+    oils_i18n_gettext(
+        'ebook_api.overdrive.account_id',
+        'OverDrive Account ID',
+        'coust',
+        'label'
+    ),
+    oils_i18n_gettext(
+        'ebook_api.overdrive.account_id',
+        'Account ID (a.k.a. Library ID) for this library, as assigned by OverDrive',
+        'coust',
+        'description'
+    ),
+    'ebook_api',
+    'string'
+),(
+    'ebook_api.overdrive.websiteid',
+    oils_i18n_gettext(
+        'ebook_api.overdrive.websiteid',
+        'OverDrive Website ID',
+        'coust',
+        'label'
+    ),
+    oils_i18n_gettext(
+        'ebook_api.overdrive.websiteid',
+        'Website ID for this library, as assigned by OverDrive',
+        'coust',
+        'description'
+    ),
+    'ebook_api',
+    'string'
+),(
+    'ebook_api.overdrive.authorizationname',
+    oils_i18n_gettext(
+        'ebook_api.overdrive.authorizationname',
+        'OverDrive Authorization Name',
+        'coust',
+        'label'
+    ),
+    oils_i18n_gettext(
+        'ebook_api.overdrive.authorizationname',
+        'Authorization name for this library, as assigned by OverDrive',
+        'coust',
+        'description'
+    ),
+    'ebook_api',
+    'string'
+),(
+    'ebook_api.overdrive.basic_token',
+    oils_i18n_gettext(
+        'ebook_api.overdrive.basic_token',
+        'OverDrive Basic Token',
+        'coust',
+        'label'
+    ),
+    oils_i18n_gettext(
+        'ebook_api.overdrive.basic_token',
+        'Basic token for client authentication with OverDrive API (supplied by OverDrive)',
+        'coust',
+        'description'
+    ),
+    'ebook_api',
+    'string'
+),(
+    'ebook_api.overdrive.granted_auth_redirect_uri',
+    oils_i18n_gettext(
+        'ebook_api.overdrive.granted_auth_redirect_uri',
+        'OverDrive Granted Authorization Redirect URI',
+        'coust',
+        'label'
+    ),
+    oils_i18n_gettext(
+        'ebook_api.overdrive.granted_auth_redirect_uri',
+        'URI provided to OverDrive for use with granted authorization',
+        'coust',
+        'description'
+    ),
+    'ebook_api',
+    'string'
+),(
+    'ebook_api.overdrive.password_required',
+    oils_i18n_gettext(
+        'ebook_api.overdrive.password_required',
+        'OverDrive Password Required',
+        'coust',
+        'label'
+    ),
+    oils_i18n_gettext(
+        'ebook_api.overdrive.password_required',
+        'Does this library require a password when authenticating patrons with the OverDrive API?',
+        'coust',
+        'description'
+    ),
+    'ebook_api',
+    'bool'
+);
+
+COMMIT;
+