talis 0.12.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 +7 -0
- data/.gitignore +12 -0
- data/.rspec +2 -0
- data/.rubocop.yml +2 -0
- data/.travis.yml +24 -0
- data/.yardopts +1 -0
- data/CONTRIBUTING.md +28 -0
- data/Gemfile +4 -0
- data/Guardfile +5 -0
- data/README.md +76 -0
- data/Rakefile +8 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/lib/talis.rb +25 -0
- data/lib/talis/analytics.rb +31 -0
- data/lib/talis/analytics/event.rb +67 -0
- data/lib/talis/authentication.rb +14 -0
- data/lib/talis/authentication/client.rb +82 -0
- data/lib/talis/authentication/login.rb +169 -0
- data/lib/talis/authentication/public_key.rb +53 -0
- data/lib/talis/authentication/token.rb +172 -0
- data/lib/talis/bibliography.rb +52 -0
- data/lib/talis/bibliography/ebook.rb +50 -0
- data/lib/talis/bibliography/manifestation.rb +141 -0
- data/lib/talis/bibliography/result_set.rb +34 -0
- data/lib/talis/bibliography/work.rb +164 -0
- data/lib/talis/constants.rb +9 -0
- data/lib/talis/errors.rb +10 -0
- data/lib/talis/errors/authentication_failed_error.rb +4 -0
- data/lib/talis/errors/client_errors.rb +19 -0
- data/lib/talis/errors/server_communication_error.rb +4 -0
- data/lib/talis/errors/server_error.rb +4 -0
- data/lib/talis/extensions/object.rb +11 -0
- data/lib/talis/feeds.rb +8 -0
- data/lib/talis/feeds/annotation.rb +129 -0
- data/lib/talis/feeds/feed.rb +58 -0
- data/lib/talis/hierarchy.rb +9 -0
- data/lib/talis/hierarchy/asset.rb +265 -0
- data/lib/talis/hierarchy/node.rb +200 -0
- data/lib/talis/hierarchy/resource.rb +159 -0
- data/lib/talis/oauth_service.rb +18 -0
- data/lib/talis/resource.rb +68 -0
- data/lib/talis/user.rb +112 -0
- data/lib/talis/version.rb +3 -0
- data/talis.gemspec +39 -0
- metadata +327 -0
@@ -0,0 +1,53 @@
|
|
1
|
+
require 'active_support'
|
2
|
+
require 'talis/authentication/token'
|
3
|
+
|
4
|
+
module Talis
|
5
|
+
module Authentication
|
6
|
+
# Provides the ability to fetch a public key to verify tokens.
|
7
|
+
# There is no need to use this class directly as it is used by the Token
|
8
|
+
# class to verify tokens.
|
9
|
+
class PublicKey < Talis::Resource
|
10
|
+
base_uri Token.base_uri
|
11
|
+
|
12
|
+
# Construct an empty public key object.
|
13
|
+
# @param cache_store [ActiveSupport::Cache::MemoryStore] A cache
|
14
|
+
# store to use to fetch locally cached keys before trying remotely.
|
15
|
+
def initialize(cache_store)
|
16
|
+
@cache_store = cache_store
|
17
|
+
end
|
18
|
+
|
19
|
+
# Fetch a public key for use with token verification, either from
|
20
|
+
# the provided cache or remotely.
|
21
|
+
# @param request_id [String] (uuid) unique ID for the remote request.
|
22
|
+
# @return [String] the public key.
|
23
|
+
def fetch(request_id: self.class.new_req_id)
|
24
|
+
# Token base URI may have changed after the class was loaded.
|
25
|
+
self.class.base_uri(Token.base_uri)
|
26
|
+
public_key = @cache_store.fetch(cache_key, cache_options) do
|
27
|
+
opts = { format: :plain, headers: { 'X-Request-Id' => request_id } }
|
28
|
+
response = self.class.get('/oauth/keys', opts)
|
29
|
+
self.class.handle_response(response)
|
30
|
+
end
|
31
|
+
OpenSSL::PKey.read(public_key)
|
32
|
+
end
|
33
|
+
|
34
|
+
private
|
35
|
+
|
36
|
+
def digest_data(data)
|
37
|
+
md4 = OpenSSL::Digest::MD4.new
|
38
|
+
Base64.encode64(md4.digest(data))
|
39
|
+
end
|
40
|
+
|
41
|
+
def cache_key
|
42
|
+
"public_key:#{digest_data(self.class.base_uri)}"
|
43
|
+
end
|
44
|
+
|
45
|
+
def cache_options
|
46
|
+
{
|
47
|
+
expires_in: ENV.fetch('PUBLIC_KEY_EXPIRY_SECONDS', 7.minutes),
|
48
|
+
race_condition_ttl: 10.seconds
|
49
|
+
}
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,172 @@
|
|
1
|
+
require 'active_support'
|
2
|
+
require 'jwt'
|
3
|
+
require 'openssl'
|
4
|
+
|
5
|
+
module Talis
|
6
|
+
module Authentication
|
7
|
+
# Represents a JWT-based OAuth access token.
|
8
|
+
#
|
9
|
+
# Optionally configure an ActiveSupport-based cache store for caching the
|
10
|
+
# public key and tokens. The default cache used is an in-memory one.
|
11
|
+
# See http://api.rubyonrails.org/classes/ActiveSupport/Cache.html for
|
12
|
+
# supported cache types.
|
13
|
+
# @example Using an in-memory cache.
|
14
|
+
# store = ActiveSupport::Cache::MemoryStore.new
|
15
|
+
# Talis::Authentication::Token.cache_store = store
|
16
|
+
class Token < Talis::Resource
|
17
|
+
base_uri Talis::PERSONA_HOST
|
18
|
+
cattr_accessor :cache_store
|
19
|
+
Token.cache_store = ActiveSupport::Cache::MemoryStore.new
|
20
|
+
|
21
|
+
# Create a new token object from an existing JWT.
|
22
|
+
# @param jwt [String] the encoded JWT.
|
23
|
+
# @param public_key [PublicKey] (nil) Only used in unit tests.
|
24
|
+
def initialize(jwt:, public_key: nil)
|
25
|
+
@jwt = jwt
|
26
|
+
@public_key = public_key || PublicKey.new(Token.cache_store)
|
27
|
+
end
|
28
|
+
|
29
|
+
# Validate the token, optionally against one or more required scopes.
|
30
|
+
#
|
31
|
+
# Scope validation is performed locally unless there are too many tokens
|
32
|
+
# to list inside the token payload. When this is the case, a remote
|
33
|
+
# request is performed to validate the token against the scopes.
|
34
|
+
#
|
35
|
+
# The validation error returned can be one of the following:
|
36
|
+
# - `:expired_token` if the token has expired.
|
37
|
+
# - `:insufficient_scope` if the provided scopes are not in the token.
|
38
|
+
# - `:invalid_token` if the token could not be verified by the public
|
39
|
+
# key.
|
40
|
+
# - `:invalid_token` if the token could not be decoded.
|
41
|
+
# - `:invalid_key` if the public key is corrupt.
|
42
|
+
# @param request_id [String] (uuid) unique ID for the remote request.
|
43
|
+
# @param scopes [Array] Scope(s) that the token needs in order to be
|
44
|
+
# valid.
|
45
|
+
# @param all [Boolean] (true) Whether or not all scopes must be present
|
46
|
+
# within the token for validation to pass. If false, only one matching
|
47
|
+
# scope is required.
|
48
|
+
# @return [Symbol, Nil] nil iff the token is valid else a symbol error.
|
49
|
+
# @raise [Talis::ServerError] if the sever cannot validate the
|
50
|
+
# scope.
|
51
|
+
# @raise [Talis::ServerCommunicationError] for network issues.
|
52
|
+
def validate(request_id: self.class.new_req_id, scopes: [], all: true)
|
53
|
+
decoded = JWT.decode(@jwt, p_key(request_id), true, algorithm: 'RS256')
|
54
|
+
validate_scopes(request_id, scopes, decoded[0], all)
|
55
|
+
rescue JWT::ExpiredSignature
|
56
|
+
return :expired_token
|
57
|
+
rescue JWT::VerificationError, JWT::DecodeError
|
58
|
+
return :invalid_token
|
59
|
+
rescue NoMethodError
|
60
|
+
return :invalid_key
|
61
|
+
rescue Talis::ClientError
|
62
|
+
:insufficient_scope
|
63
|
+
end
|
64
|
+
|
65
|
+
# @return [String] the encoded version of the token - a JWT string.
|
66
|
+
def to_s
|
67
|
+
@jwt
|
68
|
+
end
|
69
|
+
|
70
|
+
private
|
71
|
+
|
72
|
+
def p_key(req_id)
|
73
|
+
@public_key.fetch(request_id: req_id)
|
74
|
+
end
|
75
|
+
|
76
|
+
def validate_scopes(request_id, wanted_scopes, token, all_must_match)
|
77
|
+
# The existence of this key means there are too many scopes to fit
|
78
|
+
# into an encoded token, it must be fetched from the server
|
79
|
+
token = fetch_token(request_id) if token.key? 'scopeCount'
|
80
|
+
|
81
|
+
return :invalid_token unless token.key? 'scopes'
|
82
|
+
provided_scopes = token['scopes']
|
83
|
+
return nil if wanted_scopes.empty?
|
84
|
+
return nil if provided_scopes.include? 'su'
|
85
|
+
compare_scope_intersect(wanted_scopes, provided_scopes, all_must_match)
|
86
|
+
end
|
87
|
+
|
88
|
+
def compare_scope_intersect(wanted_scope, provided_scope, all_must_match)
|
89
|
+
intersect_scope = (wanted_scope & provided_scope)
|
90
|
+
if (all_must_match && intersect_scope != wanted_scope) ||
|
91
|
+
intersect_scope.empty?
|
92
|
+
:insufficient_scope
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
def fetch_token(request_id)
|
97
|
+
token_url = "/oauth/tokens/#{@jwt}"
|
98
|
+
headers = { headers: { 'X-Request-Id' => request_id } }
|
99
|
+
self.class.handle_response(self.class.get(token_url, headers))
|
100
|
+
end
|
101
|
+
|
102
|
+
class << self
|
103
|
+
# Generate a new token for the given client.
|
104
|
+
# If a previous token has been generated for the client and has not
|
105
|
+
# expired then this will be returned from the cache.
|
106
|
+
# @param request_id [String] ('uuid') unique ID for the remote request.
|
107
|
+
# @param client_id [String] the client for whom this token is for.
|
108
|
+
# @param client_secret [String] secret belonging to the client.
|
109
|
+
# @param host [String] Optional persona host override for service
|
110
|
+
# @return [Talis::Authentication::Token] the generated or cached token.
|
111
|
+
# @raise [Talis::ClientError] if the client ID/secret are
|
112
|
+
# invalid.
|
113
|
+
# @raise [Talis::ServerError] if the generation failed on the
|
114
|
+
# server.
|
115
|
+
# @raise [Talis::ServerCommunicationError] for network issues.
|
116
|
+
def generate(request_id: new_req_id, client_id:, client_secret:,
|
117
|
+
host: base_uri)
|
118
|
+
token = cached_token(client_id, host)
|
119
|
+
if token
|
120
|
+
new(jwt: token)
|
121
|
+
else
|
122
|
+
generate_remote_token(request_id, client_id, client_secret, host)
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
private
|
127
|
+
|
128
|
+
def generate_remote_token(request_id, client_id, client_secret, host)
|
129
|
+
response = create_token(request_id, client_id, client_secret, host)
|
130
|
+
parsed_response = handle_response(response)
|
131
|
+
cache_token(parsed_response, client_id, host)
|
132
|
+
new(jwt: parsed_response['access_token'])
|
133
|
+
end
|
134
|
+
|
135
|
+
def digest_data(data)
|
136
|
+
md4 = OpenSSL::Digest::MD4.new
|
137
|
+
Base64.encode64(md4.digest(data))
|
138
|
+
end
|
139
|
+
|
140
|
+
def cache_key(client_id, host)
|
141
|
+
"token:#{digest_data(client_id)}_#{digest_data(host)}"
|
142
|
+
end
|
143
|
+
|
144
|
+
def cache_token(data, client_id, host)
|
145
|
+
access_token = data['access_token']
|
146
|
+
# Expire the cache slightly before the token expires to cater for
|
147
|
+
# communication delay between server issuing and client receiving.
|
148
|
+
expiry_time = data['expires_in'].to_i - 5.seconds
|
149
|
+
Token.cache_store.write(cache_key(client_id, host), access_token,
|
150
|
+
expires_in: expiry_time)
|
151
|
+
end
|
152
|
+
|
153
|
+
def cached_token(client_id, host)
|
154
|
+
key = cache_key(client_id, host)
|
155
|
+
Token.cache_store.fetch(key) if Token.cache_store.exist?(key)
|
156
|
+
end
|
157
|
+
|
158
|
+
def create_token(request_id, client_id, client_secret, host)
|
159
|
+
post(host + '/oauth/tokens',
|
160
|
+
headers: { 'X-Request-Id' => request_id },
|
161
|
+
body: {
|
162
|
+
client_id: client_id,
|
163
|
+
client_secret: client_secret,
|
164
|
+
grant_type: 'client_credentials'
|
165
|
+
})
|
166
|
+
rescue
|
167
|
+
raise Talis::ServerCommunicationError
|
168
|
+
end
|
169
|
+
end
|
170
|
+
end
|
171
|
+
end
|
172
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
require_relative 'bibliography/result_set'
|
2
|
+
require_relative 'bibliography/work'
|
3
|
+
require_relative 'bibliography/manifestation'
|
4
|
+
require_relative 'bibliography/ebook'
|
5
|
+
|
6
|
+
module Talis
|
7
|
+
# Encompasses all classes associated with bibliographic resources
|
8
|
+
module Bibliography
|
9
|
+
# Exposes the underlying Metatron API client.
|
10
|
+
# @param request_id [String] ('uuid') unique ID for remote requests.
|
11
|
+
# @return MetatronClient::DefaultApi
|
12
|
+
def api_client(request_id = new_req_id)
|
13
|
+
configure_metatron
|
14
|
+
|
15
|
+
api_client = MetatronClient::ApiClient.new
|
16
|
+
api_client.default_headers = {
|
17
|
+
'X-Request-Id' => request_id,
|
18
|
+
'User-Agent' => "talis-ruby-client/#{Talis::VERSION} "\
|
19
|
+
"ruby/#{RUBY_VERSION}"
|
20
|
+
}
|
21
|
+
|
22
|
+
MetatronClient::DefaultApi.new(api_client)
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
def configure_metatron
|
28
|
+
MetatronClient.configure do |config|
|
29
|
+
config.scheme = base_uri[/https?/]
|
30
|
+
config.host = base_uri
|
31
|
+
# Non-production environments have a base path
|
32
|
+
if ENV['METATRON_BASE_PATH']
|
33
|
+
config.base_path = ENV['METATRON_BASE_PATH']
|
34
|
+
end
|
35
|
+
config.api_key_prefix['Authorization'] = 'Bearer'
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def empty_result_set(klass, meta_properties)
|
40
|
+
meta = OpenStruct.new(meta_properties)
|
41
|
+
klass.new(data: [], meta: meta).extend(ResultSet)
|
42
|
+
end
|
43
|
+
|
44
|
+
def escape_query(query_string)
|
45
|
+
# TODO: are all of these necessary?
|
46
|
+
pattern = %r{
|
47
|
+
(\+|\-|\=|\&\&|\|\||\>|\<|\!|\(|\)|\{|\}|\[|\]|\^|\"|\~|\*|\?|\:|\\|\/)
|
48
|
+
}x
|
49
|
+
query_string.gsub(pattern) { |match| "\\#{match}" }
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
require 'metatron_ruby_client'
|
2
|
+
require 'forwardable'
|
3
|
+
|
4
|
+
module Talis
|
5
|
+
module Bibliography
|
6
|
+
# Represents an eBook which is a type of asset associated with
|
7
|
+
# works and their manifestations.
|
8
|
+
#
|
9
|
+
# In order to perform remote operations, the client must be configured
|
10
|
+
# with a valid OAuth client that is allowed to query nodes:
|
11
|
+
#
|
12
|
+
# Talis::Authentication.client_id = 'client_id'
|
13
|
+
# Talis::Authentication.client_secret = 'client_secret'
|
14
|
+
#
|
15
|
+
class EBook < Talis::Resource
|
16
|
+
extend Forwardable, Talis::OAuthService, Talis::Bibliography
|
17
|
+
base_uri Talis::METATRON_HOST
|
18
|
+
attr_accessor :id, :vbid, :title, :author, :format, :digital_list_price,
|
19
|
+
:publisher_list_price, :store_price
|
20
|
+
private_class_method :new
|
21
|
+
|
22
|
+
def initialize(asset_data)
|
23
|
+
attrs = asset_data.try(:attributes) || {}
|
24
|
+
@id = asset_data.id
|
25
|
+
@vbid = attrs[:vbid]
|
26
|
+
@title = attrs[:title]
|
27
|
+
@author = attrs[:author]
|
28
|
+
@format = attrs[:'book-format']
|
29
|
+
price_list = attrs.fetch(:pricelist, {})
|
30
|
+
@digital_list_price = price_list[:'digital-list-price']
|
31
|
+
@publisher_list_price = price_list[:'publisher-list-price']
|
32
|
+
@store_price = price_list.fetch(:'store-price', {})[:value]
|
33
|
+
end
|
34
|
+
|
35
|
+
class << self
|
36
|
+
def find_by_manifestation_id(manifestation_id, request_id = new_req_id)
|
37
|
+
id = manifestation_id.gsub('nbd:', '')
|
38
|
+
begin
|
39
|
+
set = api_client(request_id).get_manifestation_assets(token, id)
|
40
|
+
rescue MetatronClient::ApiError => error
|
41
|
+
return [] if error.code == 404
|
42
|
+
handle_response(error)
|
43
|
+
end
|
44
|
+
set.data.select { |asset| asset.type == Talis::EBOOK_TYPE }
|
45
|
+
.map { |asset| new(asset) }
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,141 @@
|
|
1
|
+
require 'metatron_ruby_client'
|
2
|
+
require 'forwardable'
|
3
|
+
|
4
|
+
module Talis
|
5
|
+
module Bibliography
|
6
|
+
# Represents bibliographic manifestations API operations provided by the
|
7
|
+
# Metatron gem
|
8
|
+
# {https://github.com/talis/metatron_rb}
|
9
|
+
#
|
10
|
+
# In order to perform remote operations, the client must be configured
|
11
|
+
# with a valid OAuth client that is allowed to query nodes:
|
12
|
+
#
|
13
|
+
# Talis::Authentication.client_id = 'client_id'
|
14
|
+
# Talis::Authentication.client_secret = 'client_secret'
|
15
|
+
#
|
16
|
+
class Manifestation < Talis::Resource
|
17
|
+
extend Forwardable, Talis::OAuthService, Talis::Bibliography
|
18
|
+
base_uri Talis::METATRON_HOST
|
19
|
+
attr_reader :contributors, :assets, :manifestation_data, :work
|
20
|
+
attr_accessor :id, :type, :title
|
21
|
+
def_delegators :@manifestation_data, :id, :type
|
22
|
+
|
23
|
+
# rubocop:disable Metrics/LineLength
|
24
|
+
class << self
|
25
|
+
# Search for bibliographic manifestations
|
26
|
+
# @param request_id [String] ('uuid') unique ID for the remote request.
|
27
|
+
# @param opts [Hash] the query parameters: currently supported: work_id and isbn
|
28
|
+
# see {https://github.com/talis/metatron_rb/blob/metatron-swagger-updates/docs/DefaultApi.md#manifestation}
|
29
|
+
# @return [MetatronClient::ManifestationResultSet] containing data and meta attributes.
|
30
|
+
# The structure is as follows:
|
31
|
+
# {
|
32
|
+
# data: [manifestation1, manifestation2, manifestation3],
|
33
|
+
# meta: { count: 3 }
|
34
|
+
# included: [contributor1]
|
35
|
+
# }
|
36
|
+
# where manifestations are of type Talis::Bibliography::Manifestation, which are also available
|
37
|
+
# directly via the Enumerable methods: each, find, find_all, first, last
|
38
|
+
# @raise [Talis::ClientError] if the request was invalid.
|
39
|
+
# @raise [Talis::ServerError] if the search failed on the
|
40
|
+
# server.
|
41
|
+
# @raise [Talis::ServerCommunicationError] for network issues.
|
42
|
+
def find(request_id: new_req_id, opts: {})
|
43
|
+
api_client(request_id).manifestation(token, opts)
|
44
|
+
.extend(ResultSet).hydrate
|
45
|
+
rescue MetatronClient::ApiError => error
|
46
|
+
begin
|
47
|
+
handle_response(error)
|
48
|
+
rescue Talis::NotFoundError
|
49
|
+
empty_result_set(MetatronClient::ManifestationResultSet, count: 0)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
# Fetch a single work by id
|
54
|
+
# @param request_id [String] ('uuid') unique ID for the remote request.
|
55
|
+
# @param id [String] the ID of the work to fetch.
|
56
|
+
# @return Talis::Bibliography::Work or nil if the work cannot be found.
|
57
|
+
# @raise [Talis::ClientError] if the request was invalid.
|
58
|
+
# @raise [Talis::ServerError] if the fetch failed on the
|
59
|
+
# server.
|
60
|
+
# @raise [Talis::ServerCommunicationError] for network issues.
|
61
|
+
def get(request_id: new_req_id, id:)
|
62
|
+
new api_client(request_id).get_manifestation(token, id).data
|
63
|
+
rescue MetatronClient::ApiError => error
|
64
|
+
begin
|
65
|
+
handle_response(error)
|
66
|
+
rescue Talis::NotFoundError
|
67
|
+
nil
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def initialize(manifestation_data = nil)
|
73
|
+
if manifestation_data.is_a? MetatronClient::ManifestationData
|
74
|
+
parse_manifestation_data manifestation_data
|
75
|
+
else
|
76
|
+
@manifestation_data = MetatronClient::ManifestationData.new
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
def contributors
|
81
|
+
@contributors ||= []
|
82
|
+
end
|
83
|
+
|
84
|
+
# TODO: call assets route if not set
|
85
|
+
def assets
|
86
|
+
@assets ||= []
|
87
|
+
end
|
88
|
+
|
89
|
+
# By default, the metatron client returns generic ResourceLink objects
|
90
|
+
# as the related resources. When passed an array of Metatron::ResourceData
|
91
|
+
# objects, it will replace the ResourceLink objects with more appropriately
|
92
|
+
# typed objects
|
93
|
+
# @param resources [Array] an array of Metatron::ResourceData objects
|
94
|
+
def hydrate_relationships(included_resources)
|
95
|
+
contributors.map! do |contributor|
|
96
|
+
find_relationship_in_included(contributor,
|
97
|
+
included_resources)
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
private
|
102
|
+
|
103
|
+
def find_relationship_in_included(resource_data, included)
|
104
|
+
included.find do |resource|
|
105
|
+
resource.id == resource_data.id && resource.type == resource_data.type
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
def parse_manifestation_data(manifestation_data)
|
110
|
+
@manifestation_data = manifestation_data
|
111
|
+
@title = manifestation_data.try(:attributes).try(:title)
|
112
|
+
|
113
|
+
unless manifestation_data.relationships.nil?
|
114
|
+
add_relationships(manifestation_data)
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
def add_relationships(manifestation_data)
|
119
|
+
[:contributors, :work].each do |rel|
|
120
|
+
next unless manifestation_data.relationships.try(rel).try(:data)
|
121
|
+
if rel == :contributors
|
122
|
+
add_related_contributors(manifestation_data)
|
123
|
+
else
|
124
|
+
add_related_work(manifestation_data)
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
def add_related_contributors(manifestation_data)
|
130
|
+
@contributors ||= []
|
131
|
+
@contributors += manifestation_data.relationships.contributors.data
|
132
|
+
end
|
133
|
+
|
134
|
+
def add_related_work(manifestation_data)
|
135
|
+
@work = MetatronClient::WorkData.new(
|
136
|
+
manifestation_data.relationships.work.data.to_hash
|
137
|
+
)
|
138
|
+
end
|
139
|
+
end
|
140
|
+
end
|
141
|
+
end
|