qa 5.14.0 → 5.16.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/config/authorities/assign_fast/oclc_assign_fast.json +6 -0
- data/lib/qa/authorities/assign_fast/generic_authority.rb +16 -1
- data/lib/qa/authorities/discogs/generic_authority.rb +26 -4
- data/lib/qa/configuration.rb +2 -2
- data/lib/qa/linked_data/authority_service.rb +34 -14
- data/lib/qa/version.rb +1 -1
- data/spec/controllers/terms_controller_spec.rb +1 -1
- data/spec/lib/authorities/assign_fast_spec.rb +3 -3
- data/spec/lib/authorities/discogs/generic_authority_spec.rb +42 -1
- data/spec/spec_helper.rb +5 -1
- data/spec/test_app_templates/Gemfile.extra +5 -0
- metadata +7 -6
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 72045748a59fdac5d424b0f6f0703e55282b3971380bc8c8288ccc66677d38d8
|
|
4
|
+
data.tar.gz: b03d37809c9d45a4c20b2b3dce9ce3a650fbb187580b21f6fdc956f549fef710
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: be2bdd94bae283c15a285c3213be0d780053948da33da5e933cc790cdf3ba2fac4c343380b68b80853d2c0fd83aee7c15ed059372fe314af3094376df855b8d7
|
|
7
|
+
data.tar.gz: 85500cf3c3b1d1a86b002ec6ef748d70b91a0f021caec6ef6ba94861766b67035bfb926fcd59c9e6a6d67abf107c99bf5d0a475b64f122604a006d1386753b29
|
|
@@ -19,6 +19,7 @@ module Qa::Authorities
|
|
|
19
19
|
Faraday.get(url) do |req|
|
|
20
20
|
req.options.params_encoder = space_fix_encoder
|
|
21
21
|
req.headers['Accept'] = 'application/json'
|
|
22
|
+
req.options.timeout = connection_timeout_in_seconds unless connection_timeout_in_seconds.nil?
|
|
22
23
|
end
|
|
23
24
|
end
|
|
24
25
|
|
|
@@ -49,11 +50,25 @@ module Qa::Authorities
|
|
|
49
50
|
|
|
50
51
|
# sort=usage+desc is not documented by OCLC but seems necessary to get the sort
|
|
51
52
|
# we formerly got without specifying, that is most useful in our use case.
|
|
52
|
-
"
|
|
53
|
+
"#{assign_fast_url}?&query=#{escaped_query}&queryIndex=#{index}&queryReturn=#{return_data}&suggest=autoSubject&rows=#{num_rows}&sort=usage+desc"
|
|
53
54
|
end
|
|
54
55
|
|
|
55
56
|
private
|
|
56
57
|
|
|
58
|
+
# See https://github.com/samvera/questioning_authority/wiki/Connecting-to-OCLC-FAST
|
|
59
|
+
# for more info about config settings for this authority.
|
|
60
|
+
def assign_fast_config
|
|
61
|
+
Qa.config.assign_fast_authority_configs
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def connection_timeout_in_seconds
|
|
65
|
+
assign_fast_config.dig(:OCLC_ASSIGN_FAST, :search, :connection, :timeout)&.to_i
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def assign_fast_url
|
|
69
|
+
assign_fast_config.dig(:OCLC_ASSIGN_FAST, :search, :urls, :fastsuggest) || 'https://fast.oclc.org/searchfast/fastsuggest'
|
|
70
|
+
end
|
|
71
|
+
|
|
57
72
|
# Removes characters from the query string that are not tolerated by the API
|
|
58
73
|
# See oclc sample code at
|
|
59
74
|
# http://experimental.worldcat.org/fast/assignfast/js/assignFASTComplete.js
|
|
@@ -1,12 +1,15 @@
|
|
|
1
1
|
require 'rdf'
|
|
2
2
|
require 'rdf/ntriples'
|
|
3
|
+
require 'net/http'
|
|
4
|
+
require 'json'
|
|
5
|
+
|
|
3
6
|
module Qa::Authorities
|
|
4
7
|
class Discogs::GenericAuthority < Base
|
|
5
8
|
include WebServiceBase
|
|
6
9
|
include Discogs::DiscogsTranslation
|
|
7
10
|
include Discogs::DiscogsUtils
|
|
8
11
|
|
|
9
|
-
class_attribute :discogs_secret, :discogs_key
|
|
12
|
+
class_attribute :discogs_secret, :discogs_key, :discogs_user_token
|
|
10
13
|
attr_accessor :primary_artists, :selected_format, :work_uri, :instance_uri
|
|
11
14
|
|
|
12
15
|
# @param [String] subauthority to use
|
|
@@ -26,8 +29,8 @@ module Qa::Authorities
|
|
|
26
29
|
# physical or digital object and a master represents a set of similar releases. Use of a
|
|
27
30
|
# subauthority (e.g., /qa/search/discogs/master) will target a specific type. Using the "all"
|
|
28
31
|
# subauthority will search for both types.
|
|
29
|
-
unless discogs_key && discogs_secret
|
|
30
|
-
Rails.logger.error "Questioning Authority tried to call Discogs, but no secret and/or key were set."
|
|
32
|
+
unless discogs_user_token.present? || (discogs_key && discogs_secret)
|
|
33
|
+
Rails.logger.error "Questioning Authority tried to call Discogs, but no user token, secret and/or key were set."
|
|
31
34
|
return []
|
|
32
35
|
end
|
|
33
36
|
response = json(build_query_url(q, tc))
|
|
@@ -72,7 +75,9 @@ module Qa::Authorities
|
|
|
72
75
|
per_page = tc.params["per_page"]
|
|
73
76
|
end
|
|
74
77
|
escaped_q = ERB::Util.url_encode(q)
|
|
75
|
-
"https://api.discogs.com/database/search?q=#{escaped_q}&type=#{tc.params['subauthority']}&page=#{page}&per_page=#{per_page}
|
|
78
|
+
url = "https://api.discogs.com/database/search?q=#{escaped_q}&type=#{tc.params['subauthority']}&page=#{page}&per_page=#{per_page}"
|
|
79
|
+
url += "&key=#{discogs_key}&secret=#{discogs_secret}" if discogs_user_token.blank?
|
|
80
|
+
url
|
|
76
81
|
end
|
|
77
82
|
|
|
78
83
|
# @param [String] the id of the selected item
|
|
@@ -82,6 +87,23 @@ module Qa::Authorities
|
|
|
82
87
|
"https://api.discogs.com/#{subauthority}s/#{id}"
|
|
83
88
|
end
|
|
84
89
|
|
|
90
|
+
def json(url)
|
|
91
|
+
if discogs_user_token.present?
|
|
92
|
+
uri = URI(url)
|
|
93
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
|
94
|
+
http.use_ssl = true
|
|
95
|
+
|
|
96
|
+
request = Net::HTTP::Get.new(uri)
|
|
97
|
+
request['Authorization'] = "Discogs token=#{discogs_user_token}"
|
|
98
|
+
request['User-Agent'] = 'HykuApp/1.0'
|
|
99
|
+
|
|
100
|
+
response = http.request(request)
|
|
101
|
+
JSON.parse(response.body)
|
|
102
|
+
else
|
|
103
|
+
super
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
|
|
85
107
|
private
|
|
86
108
|
|
|
87
109
|
# In the unusual case that we have an id and the subauthority is "all", we don't know which Discogs url to
|
data/lib/qa/configuration.rb
CHANGED
|
@@ -28,8 +28,8 @@ module Qa
|
|
|
28
28
|
token == authorized_reload_token
|
|
29
29
|
end
|
|
30
30
|
|
|
31
|
-
# Hold
|
|
32
|
-
attr_accessor :linked_data_authority_configs
|
|
31
|
+
# Hold authority configs
|
|
32
|
+
attr_accessor :linked_data_authority_configs, :assign_fast_authority_configs
|
|
33
33
|
|
|
34
34
|
# For linked data access, specify default language for sorting and selection. The default is only used if a language is not
|
|
35
35
|
# specified in the authority's configuration file and not passed in as a parameter. (e.g. :en, [:en], or [:en, :fr])
|
|
@@ -2,25 +2,45 @@
|
|
|
2
2
|
module Qa
|
|
3
3
|
module LinkedData
|
|
4
4
|
class AuthorityService
|
|
5
|
-
# Load or reload the linked data configuration files
|
|
6
5
|
def self.load_authorities
|
|
7
|
-
|
|
8
|
-
|
|
6
|
+
load_linked_data_config
|
|
7
|
+
load_assign_fast_config
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
# Load or reload the linked data configuration files
|
|
11
|
+
def self.load_linked_data_config
|
|
12
|
+
ld_auth_cfg = {}
|
|
13
|
+
# Linked data settings
|
|
9
14
|
Dir[File.join(Qa::Engine.root, 'config', 'authorities', 'linked_data', '*.json')].each do |fn|
|
|
10
|
-
|
|
11
|
-
json = File.read(File.expand_path(fn, __FILE__))
|
|
12
|
-
cfg = JSON.parse(json).deep_symbolize_keys
|
|
13
|
-
auth_cfg[auth] = cfg
|
|
15
|
+
process_config_file(file_path: fn, config_hash: ld_auth_cfg)
|
|
14
16
|
end
|
|
15
|
-
|
|
16
|
-
# load app configured linked data authorities and overrides
|
|
17
|
+
# Optional local (app) linked data settings overrides
|
|
17
18
|
Dir[Rails.root.join('config', 'authorities', 'linked_data', '*.json')].each do |fn|
|
|
18
|
-
|
|
19
|
-
json = File.read(File.expand_path(fn, __FILE__))
|
|
20
|
-
cfg = JSON.parse(json).deep_symbolize_keys
|
|
21
|
-
auth_cfg[auth] = cfg
|
|
19
|
+
process_config_file(file_path: fn, config_hash: ld_auth_cfg)
|
|
22
20
|
end
|
|
23
|
-
Qa.config.linked_data_authority_configs =
|
|
21
|
+
Qa.config.linked_data_authority_configs = ld_auth_cfg
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# similar to the above; these settings are for getting (non-linked-data) FAST subject headings from OCLC.
|
|
25
|
+
def self.load_assign_fast_config
|
|
26
|
+
assign_fast_auth_cfg = {}
|
|
27
|
+
# assign_fast settings
|
|
28
|
+
Dir[File.join(Qa::Engine.root, 'config', 'authorities', 'assign_fast', '*.json')].each do |fn|
|
|
29
|
+
process_config_file(file_path: fn, config_hash: assign_fast_auth_cfg)
|
|
30
|
+
end
|
|
31
|
+
# Optional local (app) assign_fast settings overrides
|
|
32
|
+
Dir[Rails.root.join('config', 'authorities', 'assign_fast', '*.json')].each do |fn|
|
|
33
|
+
process_config_file(file_path: fn, config_hash: assign_fast_auth_cfg)
|
|
34
|
+
end
|
|
35
|
+
Qa.config.assign_fast_authority_configs = assign_fast_auth_cfg
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# load settings into a configuration hash:
|
|
39
|
+
def self.process_config_file(file_path:, config_hash:)
|
|
40
|
+
file_key = File.basename(file_path, '.json').upcase.to_sym
|
|
41
|
+
json = File.read(File.expand_path(file_path, __FILE__))
|
|
42
|
+
cfg = JSON.parse(json).deep_symbolize_keys
|
|
43
|
+
config_hash[file_key] = cfg
|
|
24
44
|
end
|
|
25
45
|
|
|
26
46
|
# Get the list of names of the loaded authorities
|
data/lib/qa/version.rb
CHANGED
|
@@ -199,7 +199,7 @@ describe Qa::TermsController, type: :controller do
|
|
|
199
199
|
|
|
200
200
|
context "assign_fast" do
|
|
201
201
|
before do
|
|
202
|
-
stub_request(:get, "
|
|
202
|
+
stub_request(:get, "https://fast.oclc.org/searchfast/fastsuggest?query=word&queryIndex=suggest50&queryReturn=suggest50,idroot,auth,type&rows=20&suggest=autoSubject&sort=usage+desc")
|
|
203
203
|
.with(headers: { 'Accept' => 'application/json' })
|
|
204
204
|
.to_return(body: webmock_fixture("assign-fast-topical-result.json"), status: 200, headers: {})
|
|
205
205
|
end
|
|
@@ -2,7 +2,7 @@ require 'spec_helper'
|
|
|
2
2
|
|
|
3
3
|
describe Qa::Authorities::AssignFast do
|
|
4
4
|
let(:query) { "word (ling" }
|
|
5
|
-
let(:expected_url) { "
|
|
5
|
+
let(:expected_url) { "https://fast.oclc.org/searchfast/fastsuggest?&query=word%20ling&queryIndex=suggestall&queryReturn=suggestall%2Cidroot%2Cauth%2Ctype&suggest=autoSubject&rows=20&sort=usage+desc" }
|
|
6
6
|
|
|
7
7
|
# subauthority infrastructure
|
|
8
8
|
describe "#new" do
|
|
@@ -60,7 +60,7 @@ describe Qa::Authorities::AssignFast do
|
|
|
60
60
|
|
|
61
61
|
context "when query is blank" do
|
|
62
62
|
let(:query) { "" }
|
|
63
|
-
let(:expected_url) { "
|
|
63
|
+
let(:expected_url) { "https://fast.oclc.org/searchfast/fastsuggest?&query=&queryIndex=suggestall&queryReturn=suggestall%2Cidroot%2Cauth%2Ctype&suggest=autoSubject&rows=20&sort=usage+desc" }
|
|
64
64
|
|
|
65
65
|
# server returns results but no results header
|
|
66
66
|
let :results do
|
|
@@ -104,7 +104,7 @@ describe Qa::Authorities::AssignFast do
|
|
|
104
104
|
|
|
105
105
|
context "with topical results" do
|
|
106
106
|
let(:query) { "word" }
|
|
107
|
-
let(:expected_url) { "
|
|
107
|
+
let(:expected_url) { "https://fast.oclc.org/searchfast/fastsuggest?query=word&queryIndex=suggest50&queryReturn=suggest50,idroot,auth,type&rows=20&suggest=autoSubject&sort=usage+desc" }
|
|
108
108
|
|
|
109
109
|
let :results do
|
|
110
110
|
stub_request(:get, expected_url)
|
|
@@ -4,6 +4,7 @@ describe Qa::Authorities::Discogs::GenericAuthority do
|
|
|
4
4
|
before do
|
|
5
5
|
described_class.discogs_key = 'dummy_key'
|
|
6
6
|
described_class.discogs_secret = 'dummy_secret'
|
|
7
|
+
described_class.discogs_user_token = nil
|
|
7
8
|
end
|
|
8
9
|
|
|
9
10
|
let(:authority) { described_class.new "all" }
|
|
@@ -29,6 +30,18 @@ describe Qa::Authorities::Discogs::GenericAuthority do
|
|
|
29
30
|
|
|
30
31
|
it { is_expected.to eq 'https://api.discogs.com/database/search?q=foo&type=master&page=1&per_page=10&key=dummy_key&secret=dummy_secret' }
|
|
31
32
|
end
|
|
33
|
+
|
|
34
|
+
context "with a user token" do
|
|
35
|
+
subject { authority.build_query_url("foo", tc) }
|
|
36
|
+
let(:tc) { instance_double(Qa::TermsController) }
|
|
37
|
+
before do
|
|
38
|
+
described_class.discogs_user_token = 'dummy_token'
|
|
39
|
+
allow(Qa::TermsController).to receive(:new).and_return(tc)
|
|
40
|
+
allow(tc).to receive(:params).and_return('page' => "1", 'per_page' => "10", 'subauthority' => "master")
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
it { is_expected.to eq 'https://api.discogs.com/database/search?q=foo&type=master&page=1&per_page=10' }
|
|
44
|
+
end
|
|
32
45
|
end
|
|
33
46
|
|
|
34
47
|
describe "#find_url" do
|
|
@@ -363,6 +376,33 @@ describe Qa::Authorities::Discogs::GenericAuthority do
|
|
|
363
376
|
end
|
|
364
377
|
end
|
|
365
378
|
|
|
379
|
+
context "with user token authentication" do
|
|
380
|
+
let(:tc) { instance_double(Qa::TermsController) }
|
|
381
|
+
let :results do
|
|
382
|
+
described_class.discogs_user_token = 'dummy_token'
|
|
383
|
+
described_class.discogs_key = nil
|
|
384
|
+
described_class.discogs_secret = nil
|
|
385
|
+
stub_request(:get, "https://api.discogs.com/database/search?q=melody+gardot&type=all&page=&per_page=")
|
|
386
|
+
.with(
|
|
387
|
+
headers: {
|
|
388
|
+
'Authorization' => 'Discogs token=dummy_token',
|
|
389
|
+
'User-Agent' => 'HykuApp/1.0'
|
|
390
|
+
}
|
|
391
|
+
)
|
|
392
|
+
.to_return(status: 200, body: webmock_fixture("discogs-search-response-no-subauth.json"))
|
|
393
|
+
authority.search("melody gardot", tc)
|
|
394
|
+
end
|
|
395
|
+
before do
|
|
396
|
+
allow(Qa::TermsController).to receive(:new).and_return(tc)
|
|
397
|
+
allow(tc).to receive(:params).and_return('subauthority' => "all")
|
|
398
|
+
end
|
|
399
|
+
|
|
400
|
+
it "has id and label keys" do
|
|
401
|
+
expect(results.first["uri"]).to eq("https://www.discogs.com/Melody-Gardot-Who-Will-Comfort-Me-Over-The-Rainbow/release/1750352")
|
|
402
|
+
expect(results.first["id"]).to eq "1750352"
|
|
403
|
+
end
|
|
404
|
+
end
|
|
405
|
+
|
|
366
406
|
context "when authentication isn't set" do
|
|
367
407
|
let(:tc) { instance_double(Qa::TermsController) }
|
|
368
408
|
let :results do
|
|
@@ -373,12 +413,13 @@ describe Qa::Authorities::Discogs::GenericAuthority do
|
|
|
373
413
|
before do
|
|
374
414
|
described_class.discogs_secret = nil
|
|
375
415
|
described_class.discogs_key = nil
|
|
416
|
+
described_class.discogs_user_token = nil
|
|
376
417
|
allow(Qa::TermsController).to receive(:new).and_return(tc)
|
|
377
418
|
allow(tc).to receive(:params).and_return('subauthority' => "master")
|
|
378
419
|
end
|
|
379
420
|
|
|
380
421
|
it "logs an error" do
|
|
381
|
-
expect(Rails.logger).to receive(:error).with('Questioning Authority tried to call Discogs, but no secret and/or key were set.')
|
|
422
|
+
expect(Rails.logger).to receive(:error).with('Questioning Authority tried to call Discogs, but no user token, secret and/or key were set.')
|
|
382
423
|
expect(results).to be_empty
|
|
383
424
|
end
|
|
384
425
|
end
|
data/spec/spec_helper.rb
CHANGED
|
@@ -22,7 +22,11 @@ require 'pry'
|
|
|
22
22
|
Dir[File.join(File.dirname(__FILE__), "support/**/*.rb")].each { |f| require f }
|
|
23
23
|
|
|
24
24
|
RSpec.configure do |config|
|
|
25
|
-
config.
|
|
25
|
+
if config.respond_to?(:fixture_paths=)
|
|
26
|
+
config.fixture_paths = [File.expand_path("../fixtures", __FILE__)]
|
|
27
|
+
else
|
|
28
|
+
config.fixture_path = File.expand_path("../fixtures", __FILE__)
|
|
29
|
+
end
|
|
26
30
|
|
|
27
31
|
config.use_transactional_fixtures = true
|
|
28
32
|
|
|
@@ -21,4 +21,9 @@ group :development do
|
|
|
21
21
|
# See https://stackoverflow.com/questions/70500220/rails-7-ruby-3-1-loaderror-cannot-load-such-file-net-smtp
|
|
22
22
|
gem 'net-smtp', require: false
|
|
23
23
|
end
|
|
24
|
+
|
|
25
|
+
if Gem::Version.new(ENV['RAILS_VERSION']) < Gem::Version.new('7.1')
|
|
26
|
+
gem 'concurrent-ruby', '1.3.4'
|
|
27
|
+
end
|
|
28
|
+
|
|
24
29
|
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: qa
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 5.
|
|
4
|
+
version: 5.16.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Stephen Anderson
|
|
@@ -16,7 +16,7 @@ authors:
|
|
|
16
16
|
autorequire:
|
|
17
17
|
bindir: bin
|
|
18
18
|
cert_chain: []
|
|
19
|
-
date:
|
|
19
|
+
date: 2025-12-10 00:00:00.000000000 Z
|
|
20
20
|
dependencies:
|
|
21
21
|
- !ruby/object:Gem::Dependency
|
|
22
22
|
name: activerecord-import
|
|
@@ -114,20 +114,20 @@ dependencies:
|
|
|
114
114
|
requirements:
|
|
115
115
|
- - ">="
|
|
116
116
|
- !ruby/object:Gem::Version
|
|
117
|
-
version: '
|
|
117
|
+
version: '6.0'
|
|
118
118
|
- - "<"
|
|
119
119
|
- !ruby/object:Gem::Version
|
|
120
|
-
version: '8.
|
|
120
|
+
version: '8.2'
|
|
121
121
|
type: :runtime
|
|
122
122
|
prerelease: false
|
|
123
123
|
version_requirements: !ruby/object:Gem::Requirement
|
|
124
124
|
requirements:
|
|
125
125
|
- - ">="
|
|
126
126
|
- !ruby/object:Gem::Version
|
|
127
|
-
version: '
|
|
127
|
+
version: '6.0'
|
|
128
128
|
- - "<"
|
|
129
129
|
- !ruby/object:Gem::Version
|
|
130
|
-
version: '8.
|
|
130
|
+
version: '8.2'
|
|
131
131
|
- !ruby/object:Gem::Dependency
|
|
132
132
|
name: rdf
|
|
133
133
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -413,6 +413,7 @@ files:
|
|
|
413
413
|
- app/services/qa/pagination_service.rb
|
|
414
414
|
- app/views/layouts/qa/application.html.erb
|
|
415
415
|
- config/authorities.yml
|
|
416
|
+
- config/authorities/assign_fast/oclc_assign_fast.json
|
|
416
417
|
- config/authorities/linked_data/loc.json
|
|
417
418
|
- config/authorities/linked_data/oclc_fast.json
|
|
418
419
|
- config/authorities/states.yml
|