# frozen_string_literal: true require "httparty" require "active_support" require "active_support/core_ext" require "alma/config" module Alma # Alma::Electronic APIs wrapper. class Electronic class ElectronicError < ArgumentError end def self.get(params = {}) retries_count = 0 response = nil while retries_count < http_retries do begin response = get_api(params) break; rescue Net::ReadTimeout retries_count += 1 log.error("Retrying http after timeout with : #{params}") no_more_retries_left = retries_count == http_retries raise Net::ReadTimeout.new("Failed due to net timeout after #{http_retries}: #{params}") if no_more_retries_left end end return response end def self.get_totals @totals ||= get(limit: "0").data["total_record_count"] end def self.log Alma.configuration.logger end def self.get_ids total = get_totals() limit = 100 offset = 0 log.info("Retrieving #{total} collection ids.") groups = Array.new(total / limit + 1, limit) @ids ||= groups.map { |limit| prev_offset = offset offset += limit { offset: prev_offset, limit: limit } } .map { |params| Thread.new { self.get(params) } } .map(&:value).map(&:data) .map { |data| data["electronic_collection"].map { |coll| coll["id"] } } .flatten.uniq end def self.http_retries Alma.configuration.http_retries end private class ElectronicAPI include ::HTTParty include ::Enumerable extend ::Forwardable REQUIRED_PARAMS = [] RESOURCE = "/almaws/v1/electronic" attr_reader :params, :data def_delegators :@data, :each, :each_pair, :fetch, :values, :keys, :dig, :slice, :except, :to_h, :to_hash, :[], :with_indifferent_access def initialize(params = {}) @params = params headers = self.class::headers log.info(url: url, query: params) response = self.class::get(url, headers: headers, query: params, timeout: timeout) @data = JSON.parse(response.body) rescue {} end def url "#{Alma.configuration.region}#{resource}" end def timeout Alma.configuration.timeout end def log Alma::Electronic.log end def resource @params.inject(self.class::RESOURCE) { |path, param| key = param.first value = param.last if key && value path.gsub(/:#{key}/, value.to_s) else path end } end def self.can_process?(params = {}) type = self.to_s.split("::").last.parameterize self::REQUIRED_PARAMS.all? { |param| params.include? param } && params[:type].blank? || params[:type] == type end private def self.headers { "Authorization": "apikey #{apikey}", "Accept": "application/json", "Content-Type": "application/json" } end def self.apikey Alma.configuration.apikey end end class Portfolio < ElectronicAPI REQUIRED_PARAMS = [ :collection_id, :service_id, :portfolio_id ] RESOURCE = "/almaws/v1/electronic/e-collections/:collection_id/e-services/:service_id/portfolios/:portfolio_id" end class Service < ElectronicAPI REQUIRED_PARAMS = [ :collection_id, :service_id ] RESOURCE = "/almaws/v1/electronic/e-collections/:collection_id/e-services/:service_id" end class Services < ElectronicAPI REQUIRED_PARAMS = [ :collection_id, :type ] RESOURCE = "/almaws/v1/electronic/e-collections/:collection_id/e-services" end class Collection < ElectronicAPI REQUIRED_PARAMS = [ :collection_id ] RESOURCE = "/almaws/v1/electronic/e-collections/:collection_id" end # Catch all Electronic API. # By default returns all collections class Collections < ElectronicAPI REQUIRED_PARAMS = [] RESOURCE = "/almaws/v1/electronic/e-collections" def self.can_process?(params = {}) true end end # Order matters because parameters can repeat. REGISTERED_APIs = [Portfolio, Service, Services, Collection, Collections] def self.get_api(params) REGISTERED_APIs .find { |m| m.can_process? params } .new(params) end end end