uc3-dmp-id 0.0.26 → 0.0.27
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/lib/uc3-dmp-id/creator.rb +5 -2
- data/lib/uc3-dmp-id/deleter.rb +59 -1
- data/lib/uc3-dmp-id/finder.rb +1 -39
- data/lib/uc3-dmp-id/helper.rb +47 -0
- data/lib/uc3-dmp-id/splicer.rb +150 -0
- data/lib/uc3-dmp-id/updater.rb +65 -1
- data/lib/uc3-dmp-id/validator.rb +6 -3
- data/lib/uc3-dmp-id/version.rb +1 -1
- data/lib/uc3-dmp-id/versioner.rb +106 -1
- data/lib/uc3-dmp-id.rb +8 -3
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 4bb37c3774e0bfdc3bde166671d245f5928fd1a15a3320e3f6736d9100cc667c
|
4
|
+
data.tar.gz: 842260600fdc60be2fef3beebbc9d5b8418ba0f90c0f5d4bd97b7d2c3d760255
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 8be8f0d5ec703707d5c7fc3ac2a836a91cfd4704d837a61aaa99191f033521cdb99a9268b37384e35b8300bd0cdf0f7f2b6b3de65a4f8d88ddcb409133d574c8
|
7
|
+
data.tar.gz: 0160566bfeb48fb811e6bb7d9dda0065c88a09040b13f35423ca4020296c7247e5cf70aead0d7e3dd1e482205051c831d21247a15b04e4657aecffd3a8ca9d45
|
data/lib/uc3-dmp-id/creator.rb
CHANGED
@@ -7,7 +7,6 @@ module Uc3DmpId
|
|
7
7
|
|
8
8
|
class Creator
|
9
9
|
MSG_NO_BASE_URL = 'No base URL found for DMP ID (e.g. `doi.org`)'
|
10
|
-
MSG_NO_PROVENANCE_OWNER = 'No provenance system and/or owner defined.'
|
11
10
|
MSG_NO_SHOULDER = 'No DOI shoulder found. (e.g. `10.12345/`)'
|
12
11
|
MSG_UNABLE_TO_MINT = 'Unable to mint a unique DMP ID.'
|
13
12
|
|
@@ -16,12 +15,16 @@ module Uc3DmpId
|
|
16
15
|
raise CreatorError, MSG_NO_SHOULDER if ENV['DMP_ID_SHOULDER'].nil?
|
17
16
|
raise CreatorError, MSG_NO_BASE_URL if ENV['DMP_ID_BASE_URL'].nil?
|
18
17
|
|
18
|
+
# Fail if the provenance is not defined
|
19
|
+
raise DeleterError, MSG_DMP_FORBIDDEN unless provenance.is_a?(Hash) && !provenance['PK'].nil?
|
20
|
+
|
19
21
|
# Validate the incoming JSON first
|
20
22
|
errs = Validator.validate(mode: 'author', json: Helper.parse_json(json: json))&.fetch('dmp', {})
|
21
23
|
raise CreatorError, errs.join(', ') if errs.is_a?(Array) && errs.any?
|
22
24
|
|
23
25
|
# Fail if the provenance or owner affiliation are not defined
|
24
26
|
raise CreatorError, MSG_NO_PROVENANCE_OWNER if provenance.nil? || owner_org.nil?
|
27
|
+
raise CreatorError, MSG_NO_OWNER_ORG unless owner_org.is_a?(String) && !owner_org.strip.empty?
|
25
28
|
|
26
29
|
# Try to find it first and Fail if found
|
27
30
|
result = Finder.by_json(json: json, debug: debug)
|
@@ -78,7 +81,7 @@ module Uc3DmpId
|
|
78
81
|
# We are creating, so this is always true
|
79
82
|
json['dmphub_updater_is_provenance'] = true
|
80
83
|
# Publish the change to the EventBridge
|
81
|
-
EventPublisher.publish(source: 'DmpCreator', dmp: json, debug: @debug)
|
84
|
+
# EventPublisher.publish(source: 'DmpCreator', dmp: json, debug: @debug)
|
82
85
|
true
|
83
86
|
end
|
84
87
|
end
|
data/lib/uc3-dmp-id/deleter.rb
CHANGED
@@ -1,11 +1,69 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module Uc3DmpId
|
4
|
-
class
|
4
|
+
class DeleterError < StandardError; end
|
5
5
|
|
6
|
+
# Utility to Tombstone DMP ID'a
|
6
7
|
class Deleter
|
7
8
|
class << self
|
9
|
+
# Delete/Tombstone a record in the table
|
10
|
+
# rubocop:disable Metrics/MethodLength, Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
11
|
+
# -------------------------------------------------------------------------
|
12
|
+
def tombstone(provenance:, p_key:, debug: false)
|
13
|
+
raise DeleterError, MSG_DMP_INVALID_DMP_ID unless p_key.is_a?(String) && !p_key.strip.empty?
|
8
14
|
|
15
|
+
# Fail if the provenance is not defined
|
16
|
+
raise DeleterError, MSG_DMP_FORBIDDEN unless provenance.is_a?(Hash) && !provenance['PK'].nil?
|
17
|
+
|
18
|
+
# Fetch the latest version of the DMP ID by it's PK
|
19
|
+
client = Uc3DmpDynamo::Client.new(debug: debug)
|
20
|
+
dmp = Finder.by_pk(p_key: p_key, client: client, debug: debug)
|
21
|
+
raise DeleterError, MSG_DMP_NOT_FOUND unless dmp.is_a?(Hash) && !dmp['dmp'].nil?
|
22
|
+
|
23
|
+
# Only allow this if the provenance is the owner of the DMP!
|
24
|
+
raise DeleterError, MSG_DMP_FORBIDDEN if dmp['dmp']['dmphub_provenance_id'] != provenance['PK']
|
25
|
+
# Make sure they're not trying to update a historical copy of the DMP
|
26
|
+
raise DeleterError, MSG_DMP_NO_HISTORICALS if dmp['dmp']['SK'] != Helper::DMP_LATEST_VERSION
|
27
|
+
|
28
|
+
# Annotate the DMP ID
|
29
|
+
dmp['dmp']['SK'] = Helper::DMP_TOMBSTONE_VERSION
|
30
|
+
dmp['dmp']['dmphub_tombstoned_at'] = Time.now.iso8601
|
31
|
+
dmp['dmp']['title'] = "OBSOLETE: #{dmp['title']}"
|
32
|
+
puts "Tombstoning DMP #{p_key}" if debug
|
33
|
+
|
34
|
+
# Create the Tombstone version
|
35
|
+
resp = client.put_item(json: dmp, debug: debug)
|
36
|
+
raise DeleterError, MSG_DMP_NO_TOMBSTONE if resp.nil?
|
37
|
+
|
38
|
+
# Delete the Latest version
|
39
|
+
resp = client.delete_item(p_key: p_key, s_key: Helper::SK_DMP_PREFIX, debug: debug)
|
40
|
+
|
41
|
+
# TODO: We should do a check here to see if it was successful!
|
42
|
+
puts resp.inspect
|
43
|
+
|
44
|
+
# Notify EZID about the removal
|
45
|
+
_post_process(json: dmp)
|
46
|
+
dmp
|
47
|
+
rescue Aws::Errors::ServiceError => e
|
48
|
+
Responder.log_error(source: source, message: e.message,
|
49
|
+
details: ([@provenance] << e.backtrace).flatten)
|
50
|
+
{ status: 500, error: Messages::MSG_SERVER_ERROR }
|
51
|
+
end
|
52
|
+
# rubocop:enable Metrics/MethodLength, Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
53
|
+
|
54
|
+
private
|
55
|
+
|
56
|
+
# Once the DMP has been tombstoned, we need to notify EZID
|
57
|
+
# -------------------------------------------------------------------------
|
58
|
+
def _post_process(json:)
|
59
|
+
return false unless json.is_a?(Hash)
|
60
|
+
|
61
|
+
# Indicate whether or not the updater is the provenance system
|
62
|
+
json['dmphub_updater_is_provenance'] = true
|
63
|
+
# Publish the change to the EventBridge
|
64
|
+
# EventPublisher.publish(source: 'DmpDeleter', dmp: json, debug: @debug)
|
65
|
+
true
|
66
|
+
end
|
9
67
|
end
|
10
68
|
end
|
11
69
|
end
|
data/lib/uc3-dmp-id/finder.rb
CHANGED
@@ -21,22 +21,6 @@ module Uc3DmpId
|
|
21
21
|
end
|
22
22
|
# rubocop:enable Metrics/MethodLength
|
23
23
|
|
24
|
-
# Find the DMP's versions
|
25
|
-
# -------------------------------------------------------------------------
|
26
|
-
def versions(p_key:, client: nil, debug: false)
|
27
|
-
raise FinderError, MSG_MISSING_PK if p_key.nil?
|
28
|
-
|
29
|
-
args = {
|
30
|
-
key_conditions: {
|
31
|
-
PK: { attribute_value_list: [Helper.append_pk_prefix(p_key: p_key)], comparison_operator: 'EQ' }
|
32
|
-
},
|
33
|
-
projection_expression: 'modified',
|
34
|
-
scan_index_forward: false
|
35
|
-
}
|
36
|
-
client = client.nil? ? Uc3DmpDynamo::Client.new(debug: debug) : client
|
37
|
-
client.query(args: args, debug: debug)
|
38
|
-
end
|
39
|
-
|
40
24
|
# Find a DMP based on the contents of the incoming JSON
|
41
25
|
# -------------------------------------------------------------------------
|
42
26
|
def by_json(json:, debug: false)
|
@@ -73,7 +57,7 @@ module Uc3DmpId
|
|
73
57
|
dmp = resp['dmp'].nil? ? JSON.parse({ dmp: resp }.to_json) : resp
|
74
58
|
return nil if dmp['dmp']['PK'].nil?
|
75
59
|
|
76
|
-
dmp =
|
60
|
+
dmp = Versioner.append_versions(p_key: dmp['dmp']['PK'], dmp: dmp, client: client, debug: debug)
|
77
61
|
Helper.cleanse_dmp_json(json: dmp)
|
78
62
|
end
|
79
63
|
|
@@ -119,28 +103,6 @@ module Uc3DmpId
|
|
119
103
|
by_pk(p_key: dmp['dmp']['PK'], s_key: dmp['dmp']['SK'])
|
120
104
|
end
|
121
105
|
# rubocop:enable Metrics/AbcSize
|
122
|
-
|
123
|
-
private
|
124
|
-
|
125
|
-
# Build the dmphub_versions array and attach it to the dmp
|
126
|
-
# rubocop:disable Metrics/AbcSize
|
127
|
-
def _append_versions(p_key:, dmp:, client: nil, debug: false)
|
128
|
-
return dmp if p_key.nil? || !dmp.is_a?(Hash) || dmp['dmp'].nil?
|
129
|
-
|
130
|
-
results = versions(p_key: p_key, client: client, debug: debug)
|
131
|
-
return dmp unless results.length > 1
|
132
|
-
|
133
|
-
versions = results.map do |ver|
|
134
|
-
next if ver['modified'].nil?
|
135
|
-
{
|
136
|
-
timestamp: ver['modified'],
|
137
|
-
url: "#{Helper.api_base_url}dmps/#{Helper.remove_pk_prefix(p_key: p_key)}?version=#{ver['modified']}"
|
138
|
-
}
|
139
|
-
end
|
140
|
-
dmp['dmp']['dmphub_versions'] = JSON.parse(versions.to_json)
|
141
|
-
dmp
|
142
|
-
end
|
143
|
-
# rubocop:enable Metrics/AbcSize
|
144
106
|
end
|
145
107
|
end
|
146
108
|
end
|
data/lib/uc3-dmp-id/helper.rb
CHANGED
@@ -110,6 +110,29 @@ module Uc3DmpId
|
|
110
110
|
json.is_a?(String) ? JSON.parse(json) : nil
|
111
111
|
end
|
112
112
|
|
113
|
+
# Compare the DMP IDs to see if they are the same
|
114
|
+
def eql?(dmp_a:, dmp_b:)
|
115
|
+
return dmp_a == dmp_b unless dmp_a.is_a?(Hash) && !dmp_a['dmp'].nil? && dmp_b.is_a?(Hash) && !dmp_b['dmp'].nil?
|
116
|
+
|
117
|
+
# return true if they're identical
|
118
|
+
return true if dmp_a == dmp_b
|
119
|
+
|
120
|
+
# If the PK do not match, then they are not equivalent!
|
121
|
+
return false unless dmp_a.is_a?(Hash) && dmp_a['dmp'].fetch('PK', '').start_with?(Helper::PK_DMP_PREFIX) &&
|
122
|
+
dmp_b.is_a?(Hash) && dmp_b['dmp'].fetch('PK', '').start_with?(Helper::PK_DMP_PREFIX) &&
|
123
|
+
dmp_a['dmp']['PK'] == dmp_b['dmp']['PK']
|
124
|
+
|
125
|
+
a = deep_copy_dmp(obj: dmp_a)
|
126
|
+
b = deep_copy_dmp(obj: dmp_b)
|
127
|
+
|
128
|
+
# ignore some of the attributes before comparing
|
129
|
+
%w[SK dmphub_modification_day dmphub_updated_at dmphub_created_at].each do |key|
|
130
|
+
a['dmp'].delete(key) unless a['dmp'][key].nil?
|
131
|
+
b['dmp'].delete(key) unless b['dmp'][key].nil?
|
132
|
+
end
|
133
|
+
a == b
|
134
|
+
end
|
135
|
+
|
113
136
|
# Add DMPHub specific fields to the DMP ID JSON
|
114
137
|
def annotate_dmp_json(provenance:, owner_org:, p_key:, json:)
|
115
138
|
json = parse_json(json: json)
|
@@ -166,6 +189,30 @@ module Uc3DmpId
|
|
166
189
|
cleansed.keys.any? ? cleansed : nil
|
167
190
|
end
|
168
191
|
# rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
192
|
+
|
193
|
+
# Ruby's clone/dup methods do not clone/dup the children, so we need to do it here
|
194
|
+
# --------------------------------------------------------------
|
195
|
+
# rubocop:disable Metrics/AbcSize
|
196
|
+
def deep_copy_dmp(obj:)
|
197
|
+
case obj.class.name
|
198
|
+
when 'Array'
|
199
|
+
obj.map { |item| deep_copy_dmp(obj: item) }
|
200
|
+
when 'Hash'
|
201
|
+
hash = obj.dup
|
202
|
+
hash.each_pair do |key, value|
|
203
|
+
if key.is_a?(::String) || key.is_a?(::Symbol)
|
204
|
+
hash[key] = deep_copy_dmp(obj: value)
|
205
|
+
else
|
206
|
+
hash.delete(key)
|
207
|
+
hash[deep_copy_dmp(obj: key)] = deep_copy_dmp(obj: value)
|
208
|
+
end
|
209
|
+
end
|
210
|
+
hash
|
211
|
+
else
|
212
|
+
obj.dup
|
213
|
+
end
|
214
|
+
end
|
215
|
+
# rubocop:enable Metrics/AbcSize
|
169
216
|
end
|
170
217
|
end
|
171
218
|
end
|
@@ -0,0 +1,150 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Uc3DmpId
|
4
|
+
class SplicerError < StandardError; end
|
5
|
+
|
6
|
+
class Splicer
|
7
|
+
class << self
|
8
|
+
# Splice changes from other systems onto the system of provenance's updated record
|
9
|
+
# --------------------------------------------------------------
|
10
|
+
# rubocop:disable Metrics/AbcSize, Metrics/MethodLength
|
11
|
+
# rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
12
|
+
def splice_for_owner(owner:, updater:, base:, mods:, debug: false)
|
13
|
+
return base if owner.nil? || updater.nil? || mods.nil?
|
14
|
+
return mods if base.nil?
|
15
|
+
|
16
|
+
provenance_regex = /"dmphub_provenance_id":"#{Helper::PK_PROVENANCE_PREFIX}[a-zA-Z\-_]+"/
|
17
|
+
others = base.to_json.match(provenance_regex)
|
18
|
+
# Just return it as is if there are no mods by other systems
|
19
|
+
return mods if others.nil?
|
20
|
+
|
21
|
+
spliced = Helper.deep_copy_dmp(obj: base)
|
22
|
+
cloned_mods = Helper.deep_copy_dmp(obj: mods)
|
23
|
+
|
24
|
+
# ensure that the :project and :funding are defined
|
25
|
+
spliced['project'] = [{}] if spliced['project'].nil? || spliced['project'].empty?
|
26
|
+
spliced['project'].first['funding'] = [] if spliced['project'].first['funding'].nil?
|
27
|
+
# get all the new funding and retain other system's funding metadata
|
28
|
+
mod_fundings = cloned_mods.fetch('project', [{}]).first.fetch('funding', [])
|
29
|
+
other_fundings = spliced['project'].first['funding'].reject { |fund| fund['dmphub_provenance_id'].nil? }
|
30
|
+
# process funding (just attach all funding not owned by the system of provenance)
|
31
|
+
spliced['project'].first['funding'] = mod_fundings
|
32
|
+
spliced['project'].first['funding'] << other_fundings if other_fundings.any?
|
33
|
+
return spliced if cloned_mods['dmproadmap_related_identifiers'].nil?
|
34
|
+
|
35
|
+
# process related_identifiers (just attach all related identifiers not owned by the system of provenance)
|
36
|
+
spliced['dmproadmap_related_identifiers'] = [] if spliced['dmproadmap_related_identifiers'].nil?
|
37
|
+
mod_relateds = cloned_mods.fetch('dmproadmap_related_identifiers', [])
|
38
|
+
other_relateds = spliced['dmproadmap_related_identifiers'].reject { |id| id['dmphub_provenance_id'].nil? }
|
39
|
+
spliced['dmproadmap_related_identifiers'] = mod_relateds
|
40
|
+
spliced['dmproadmap_related_identifiers'] << other_relateds if other_relateds.any?
|
41
|
+
|
42
|
+
puts 'JSON after splicing in changes from provenance' if debug
|
43
|
+
puts spliced if debug
|
44
|
+
|
45
|
+
spliced
|
46
|
+
end
|
47
|
+
# rubocop:enable Metrics/AbcSize, Metrics/MethodLength
|
48
|
+
# rubocop:enable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
49
|
+
|
50
|
+
# Splice changes from the other system onto the system of provenance and other system's changes
|
51
|
+
# --------------------------------------------------------------
|
52
|
+
# rubocop:disable Metrics/AbcSize
|
53
|
+
def splice_for_others(owner:, updater:, base:, mods:, debug: false)
|
54
|
+
return base if owner.nil? || updater.nil? || base.nil? || mods.nil?
|
55
|
+
|
56
|
+
spliced = Helper.deep_copy_dmp(obj: base)
|
57
|
+
base_funds = spliced.fetch('project', [{}]).first.fetch('funding', [])
|
58
|
+
base_relateds = spliced.fetch('dmproadmap_related_identifiers', [])
|
59
|
+
|
60
|
+
mod_funds = mods.fetch('project', [{}]).first.fetch('funding', [])
|
61
|
+
mod_relateds = mods.fetch('dmproadmap_related_identifiers', [])
|
62
|
+
|
63
|
+
# process funding
|
64
|
+
spliced['project'].first['funding'] = _update_funding(
|
65
|
+
updater: updater, base: base_funds, mods: mod_funds
|
66
|
+
)
|
67
|
+
return spliced if mod_relateds.empty?
|
68
|
+
|
69
|
+
# process related_identifiers
|
70
|
+
spliced['dmproadmap_related_identifiers'] = _update_related_identifiers(
|
71
|
+
updater: updater, base: base_relateds, mods: mod_relateds
|
72
|
+
)
|
73
|
+
|
74
|
+
puts 'JSON after splicing in changes from non-provenance' if debug
|
75
|
+
puts spliced if debug
|
76
|
+
|
77
|
+
spliced
|
78
|
+
end
|
79
|
+
# rubocop:enable Metrics/AbcSize
|
80
|
+
|
81
|
+
private
|
82
|
+
|
83
|
+
# These Splicing operations could probably be refined or genericized to traverse the Hash
|
84
|
+
# and apply to each object
|
85
|
+
|
86
|
+
# Splice funding changes
|
87
|
+
# --------------------------------------------------------------
|
88
|
+
# rubocop:disable Metrics/AbcSize, Metrics/MethodLength
|
89
|
+
# rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
90
|
+
def _update_funding(updater:, base:, mods:)
|
91
|
+
return base if updater.nil? || mods.nil? || mods.empty?
|
92
|
+
|
93
|
+
spliced = Helper.deep_copy_dmp(obj: base)
|
94
|
+
mods.each do |funding|
|
95
|
+
# Ignore it if it has no status or grant id
|
96
|
+
next if funding['funding_status'].nil? && funding['grant_id'].nil?
|
97
|
+
|
98
|
+
# See if there is an existing funding record for the funder that's waiting on an update
|
99
|
+
spliced = [] if spliced.nil?
|
100
|
+
items = spliced.select do |orig|
|
101
|
+
!orig['funder_id'].nil? &&
|
102
|
+
orig['funder_id'] == funding['funder_id'] &&
|
103
|
+
%w[applied planned].include?(orig['funding_status'])
|
104
|
+
end
|
105
|
+
# Always grab the most current
|
106
|
+
items = items.sort { |a, b| b.fetch('dmphub_created_at', '') <=> a.fetch('dmphub_created_at', '') }
|
107
|
+
item = items.first
|
108
|
+
|
109
|
+
# Out with the old and in with the new
|
110
|
+
spliced.delete(item) unless item.nil?
|
111
|
+
# retain the original name
|
112
|
+
funding['name'] = item['name'] unless item.nil?
|
113
|
+
item = Helper.deep_copy_dmp(obj: funding)
|
114
|
+
|
115
|
+
item['funding_status'] == funding['funding_status'] unless funding['funding_status'].nil?
|
116
|
+
spliced << item if funding['grant_id'].nil?
|
117
|
+
next if funding['grant_id'].nil?
|
118
|
+
|
119
|
+
item['grant_id'] = funding['grant_id']
|
120
|
+
item['funding_status'] = funding['grant_id'].nil? ? 'rejected' : 'granted'
|
121
|
+
|
122
|
+
# Add the provenance to the entry
|
123
|
+
item['grant_id']['dmphub_provenance_id'] = updater
|
124
|
+
item['grant_id']['dmphub_created_at'] = Time.now.iso8601
|
125
|
+
spliced << item
|
126
|
+
end
|
127
|
+
spliced
|
128
|
+
end
|
129
|
+
# rubocop:enable Metrics/AbcSize, Metrics/MethodLength
|
130
|
+
# rubocop:enable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
131
|
+
|
132
|
+
# Splice related identifier changes
|
133
|
+
# --------------------------------------------------------------
|
134
|
+
def _update_related_identifiers(updater:, base:, mods:)
|
135
|
+
return base if updater.nil? || mods.nil? || mods.empty?
|
136
|
+
|
137
|
+
# Remove the updater's existing related identifiers and replace with the new set
|
138
|
+
spliced = base.nil? ? [] : Helper.deep_copy_dmp(obj: base)
|
139
|
+
spliced = spliced.reject { |related| related['dmphub_provenance_id'] == updater }
|
140
|
+
# Add the provenance to the entry
|
141
|
+
updates = mods.nil? ? [] : Helper.deep_copy_dmp(obj: mods)
|
142
|
+
updates = updates.map do |related|
|
143
|
+
related['dmphub_provenance_id'] = updater
|
144
|
+
related
|
145
|
+
end
|
146
|
+
spliced + updates
|
147
|
+
end
|
148
|
+
end
|
149
|
+
end
|
150
|
+
end
|
data/lib/uc3-dmp-id/updater.rb
CHANGED
@@ -1,11 +1,75 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module Uc3DmpId
|
4
|
-
class
|
4
|
+
class UpdaterError < StandardError; end
|
5
5
|
|
6
6
|
class Updater
|
7
7
|
class << self
|
8
|
+
# Update a record in the table
|
9
|
+
# rubocop:disable Metrics/AbcSize, Metrics/MethodLength
|
10
|
+
# rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
11
|
+
# -------------------------------------------------------------------------
|
12
|
+
def update(provenance:, p_key:, json: {})
|
13
|
+
raise DeleterError, MSG_DMP_INVALID_DMP_ID unless p_key.is_a?(String) && !p_key.strip.empty?
|
8
14
|
|
15
|
+
dmp = Helper.parse_json(json: json)
|
16
|
+
errs = _updateable?(provenance: provenance, p_key: p_key, dmp: dmp)
|
17
|
+
raise DeleterError, errs if errs.is_a?(Array) && errs.any?
|
18
|
+
|
19
|
+
# Add the DMPHub specific attributes
|
20
|
+
annotated = Helper.annotate_dmp(provenance: provenance, json: dmp, p_key: p_key)
|
21
|
+
|
22
|
+
# fetch the existing latest version of the DMP ID
|
23
|
+
client = Uc3DmpDynamo::Client.new(debug: debug)
|
24
|
+
existing = Finder.by_pk(p_key: p_key, client: client, debug: debug)
|
25
|
+
# Don't continue if nothing has changed!
|
26
|
+
raise DeleterError, MSG_NO_CHANGE if Helper.eql?(dmp_a: existing, dmp_b: annotated)
|
27
|
+
|
28
|
+
# Generate a new version of the DMP. This involves versioning the current latest version
|
29
|
+
new_version = versioner.new_version(p_key: p_key, dmp: json)
|
30
|
+
raise DeleterError, MSG_DMP_UNABLE_TO_VERSION if new_version.nil?
|
31
|
+
|
32
|
+
# Save the changes as the new latest version
|
33
|
+
resp = client.put_item(json: new_version, debug: debug)
|
34
|
+
raise DeleterError, MSG_DMP_UNABLE_TO_VERSION if resp.nil?
|
35
|
+
|
36
|
+
# Send the updates to EZID, notify the provenance and download the PDF if applicable
|
37
|
+
_post_process(json: dmp)
|
38
|
+
|
39
|
+
resp
|
40
|
+
end
|
41
|
+
# rubocop:enable Metrics/AbcSize, Metrics/MethodLength
|
42
|
+
# rubocop:enable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
43
|
+
|
44
|
+
private
|
45
|
+
|
46
|
+
# Check if the DMP ID is updateable by the provenance
|
47
|
+
def _updateable?(provenance:, p_key:, json:)
|
48
|
+
# Validate the incoming JSON first
|
49
|
+
errs = Validator.validate(mode: 'author', json: json)&.fetch('dmp', {})
|
50
|
+
return errs.join(', ') if errs.is_a?(Array) && errs.any?
|
51
|
+
|
52
|
+
# Fail if the provenance is not defined
|
53
|
+
return [MSG_DMP_FORBIDDEN] unless provenance.is_a?(Hash) && !provenance['PK'].nil?
|
54
|
+
|
55
|
+
# Verify that the JSON is for the same DMP in the PK
|
56
|
+
dmp_id = json.fetch('dmp_id', {})
|
57
|
+
return [MSG_DMP_FORBIDDEN] unless Helper.dmp_id_to_pk(json: dmp_id) == p_key
|
58
|
+
# Make sure they're not trying to update a historical copy of the DMP
|
59
|
+
return [MSG_DMP_NO_HISTORICALS] if json['SK'] != Helper::DMP_LATEST_VERSION
|
60
|
+
end
|
61
|
+
|
62
|
+
# Once the DMP has been updated, we need to register it's DMP ID and download any PDF if applicable
|
63
|
+
# -------------------------------------------------------------------------
|
64
|
+
def _post_process(json:)
|
65
|
+
return false unless json.is_a?(Hash)
|
66
|
+
|
67
|
+
# Indicate whether or not the updater is the provenance system
|
68
|
+
json['dmphub_updater_is_provenance'] = @provenance['PK'] == json['dmphub_provenance_id']
|
69
|
+
# Publish the change to the EventBridge
|
70
|
+
# EventPublisher.publish(source: 'DmpUpdater', dmp: json, debug: @debug)
|
71
|
+
true
|
72
|
+
end
|
9
73
|
end
|
10
74
|
end
|
11
75
|
end
|
data/lib/uc3-dmp-id/validator.rb
CHANGED
@@ -34,7 +34,7 @@ module Uc3DmpId
|
|
34
34
|
errors = ([MSG_INVALID_JSON] << errors).flatten.compact.uniq unless errors.empty?
|
35
35
|
errors.map { |err| err.gsub(/in schema [a-z0-9-]+/, '').strip }
|
36
36
|
rescue JSON::Schema::ValidationError => e
|
37
|
-
|
37
|
+
["#{MSG_INVALID_JSON} - #{e.message}"]
|
38
38
|
end
|
39
39
|
|
40
40
|
# ------------------------------------------------------------------------------------
|
@@ -45,10 +45,13 @@ module Uc3DmpId
|
|
45
45
|
# ------------------------------------------------------------------------------------
|
46
46
|
def _load_schema(mode:)
|
47
47
|
|
48
|
-
puts "
|
48
|
+
puts "::Uc3DmpId::Schemas::Author defined? #{Object.const_defined?('::Uc3DmpId::Schemas::Author')}"
|
49
|
+
puts "Uc3DmpId::Schemas::Author defined? #{Object.const_defined?('Uc3DmpId::Schemas::Author')}"
|
50
|
+
puts "Schemas::Author defined? #{Object.const_defined?('Schemas::Author')}"
|
51
|
+
puts "Author defined? #{Object.const_defined?('Author')}"
|
49
52
|
|
50
53
|
# Instatiate the matching schema
|
51
|
-
schema = "
|
54
|
+
schema = "Schemas::#{mode.to_s.downcase.capitalize}".split('::').inject(Object) { |o,c| o.const_get c }
|
52
55
|
schema.respond_to?(:load) ? schema.load : nil
|
53
56
|
end
|
54
57
|
|
data/lib/uc3-dmp-id/version.rb
CHANGED
data/lib/uc3-dmp-id/versioner.rb
CHANGED
@@ -1,11 +1,116 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require 'uc3-dmp-dynamo'
|
4
|
+
|
3
5
|
module Uc3DmpId
|
4
|
-
class
|
6
|
+
class VersionerError < StandardError; end
|
5
7
|
|
6
8
|
class Versioner
|
7
9
|
class << self
|
8
10
|
|
11
|
+
# Find the DMP ID's versions
|
12
|
+
# -------------------------------------------------------------------------
|
13
|
+
def get_versions(p_key:, client: nil, debug: false)
|
14
|
+
return [] unless p_key.is_a?(String) && !p_key.strip.empty?
|
15
|
+
|
16
|
+
args = {
|
17
|
+
key_conditions: {
|
18
|
+
PK: { attribute_value_list: [Helper.append_pk_prefix(p_key: p_key)], comparison_operator: 'EQ' }
|
19
|
+
},
|
20
|
+
projection_expression: 'modified',
|
21
|
+
scan_index_forward: false
|
22
|
+
}
|
23
|
+
client = client.nil? ? Uc3DmpDynamo::Client.new(debug: debug) : client
|
24
|
+
client.query(args: args, debug: debug)
|
25
|
+
end
|
26
|
+
|
27
|
+
# Create a new version of the DMP. This involves:
|
28
|
+
# - Cloning the existing `VERSION#latest` and setting it's `SK` to `VERSION=yyyy-mm-ddThh:mm:ss+zz:zz`
|
29
|
+
# - Saving the new `VERSION=yyyy-mm-ddThh:mm:ss+zz:zz` item
|
30
|
+
# - Splicing in the current changes onto the existing `VERSION#latest` item
|
31
|
+
# - Returning the spliced `VERSION#latest` back to this method
|
32
|
+
def new_version(client:, provenance:, p_key:, client: nil, dmp:, latest_version: {})
|
33
|
+
return nil unless p_key.is_a?(String) && !p_key.strip.empty? && _versionable?(dmp: dmp)
|
34
|
+
|
35
|
+
client = Uc3DmpDynamo::Client.new(debug: debug) if client.nil?
|
36
|
+
latest_version = Finder.by_p_key(client: client, p_key: p_key) unless latest_version.is_a?(Hash) &&
|
37
|
+
!latest_version['PK'].nil?
|
38
|
+
|
39
|
+
# Only continue if there was an existing record and its the latest version
|
40
|
+
return nil unless latest['SK'] != Helper::DMP_LATEST_VERSION
|
41
|
+
|
42
|
+
owner = latest['dmphub_provenance_id']
|
43
|
+
updater = provenance['PK']
|
44
|
+
prior = _generate_version(client: client, latest_version: latest, owner: owner, updater: updater)
|
45
|
+
return nil if prior.nil?
|
46
|
+
|
47
|
+
args = { owner: owner, updater: updater, base: prior, mods: dmp, debug: debug }
|
48
|
+
puts 'DMP ID update prior to splicing changes' if debug
|
49
|
+
puts dmp
|
50
|
+
|
51
|
+
args = { owner: owner, updater: updater, base: prior, mods: dmp, debug: debug }
|
52
|
+
# If the system of provenance is making the change then just use the
|
53
|
+
# new version as the base and then splice in any mods made by others
|
54
|
+
# args = args.merge({ base: new_version, mods: original_version })
|
55
|
+
new_version = Splicer.splice_for_owner(args) if owner == updater
|
56
|
+
# Otherwise use the original version as the base and then update the
|
57
|
+
# metadata owned by the updater system
|
58
|
+
new_version = Splicer.splice_for_others(args) if new_version.nil?
|
59
|
+
new_version
|
60
|
+
end
|
61
|
+
|
62
|
+
# Build the :dmphub_versions array and attach it to the DMP JSON
|
63
|
+
# rubocop:disable Metrics/AbcSize
|
64
|
+
def append_versions(p_key:, dmp:, client: nil, debug: false)
|
65
|
+
json = Helper.parse_json(json: dmp)
|
66
|
+
return json unless p_key.is_a?(String) && !p_key.strip.empty? && json.is_a?(Hash) && !json['dmp'].nil?
|
67
|
+
|
68
|
+
results = get_versions(p_key: p_key, client: client, debug: debug)
|
69
|
+
return json unless results.length > 1
|
70
|
+
|
71
|
+
versions = results.map do |ver|
|
72
|
+
next if ver['modified'].nil?
|
73
|
+
{
|
74
|
+
timestamp: ver['modified'],
|
75
|
+
url: "#{Helper.api_base_url}dmps/#{Helper.remove_pk_prefix(p_key: p_key)}?version=#{ver['modified']}"
|
76
|
+
}
|
77
|
+
end
|
78
|
+
json['dmp']['dmphub_versions'] = JSON.parse(versions.to_json)
|
79
|
+
json
|
80
|
+
end
|
81
|
+
# rubocop:enable Metrics/AbcSize
|
82
|
+
|
83
|
+
private
|
84
|
+
|
85
|
+
# Determine whether the specified DMP metadata is versionable - returns boolean
|
86
|
+
def _versionable?(dmp:)
|
87
|
+
return false unless dmp.is_a?(Hash) && dmp['PK'].nil? && dmp['SK'].nil?
|
88
|
+
|
89
|
+
# It's versionable if it has a DMP ID
|
90
|
+
!dmp.fetch('dmp_id', {})['identifier'].nil?
|
91
|
+
end
|
92
|
+
|
93
|
+
# Generate a version
|
94
|
+
# rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
95
|
+
def _generate_version(client:, latest_version:, owner:, updater:, debug: false)
|
96
|
+
# Only create a version if the Updater is not the Owner OR the changes have happened on a different day
|
97
|
+
mod_time = Time.parse(latest_version.fetch('dmphub_updated_at', Time.now.iso8601))
|
98
|
+
now = Time.now
|
99
|
+
return latest_version if mod_time.nil? || !(now - mod_time).is_a?(Float)
|
100
|
+
|
101
|
+
same_hour = (now - mod_time).round <= 3600
|
102
|
+
return latest_version if owner != updater || (owner == updater && same_hour)
|
103
|
+
|
104
|
+
latest_version['SK'] = "#{Helper::SK_DMP_PREFIX}#{latest_version['dmphub_updated_at'] || Time.now.iso8601}"
|
105
|
+
|
106
|
+
# Create the prior version record
|
107
|
+
resp = client.put_item(json: latest_version, debug: debug)
|
108
|
+
return nil if resp.nil?
|
109
|
+
|
110
|
+
puts "Created new version #{latest_version['PK']} - #{latest_version['SK']}" if debug
|
111
|
+
latest_version
|
112
|
+
end
|
113
|
+
# rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
9
114
|
end
|
10
115
|
end
|
11
116
|
end
|
data/lib/uc3-dmp-id.rb
CHANGED
@@ -8,20 +8,25 @@ require 'uc3-dmp-id/creator'
|
|
8
8
|
require 'uc3-dmp-id/deleter'
|
9
9
|
require 'uc3-dmp-id/finder'
|
10
10
|
require 'uc3-dmp-id/helper'
|
11
|
+
require 'uc3-dmp-id/splicer'
|
11
12
|
require 'uc3-dmp-id/updater'
|
12
13
|
require 'uc3-dmp-id/validator'
|
13
14
|
require 'uc3-dmp-id/versioner'
|
15
|
+
|
14
16
|
require 'uc3-dmp-id/schemas/amend'
|
15
17
|
require 'uc3-dmp-id/schemas/author'
|
16
18
|
|
17
19
|
module Uc3DmpId
|
18
20
|
MSG_DMP_EXISTS = 'DMP already exists. Try :update instead.'
|
19
|
-
MSG_DMP_UNKNOWN = 'DMP does not exist. Try :create instead.'
|
20
|
-
MSG_DMP_NOT_FOUND = 'DMP does not exist.'
|
21
21
|
MSG_DMP_FORBIDDEN = 'You do not have permission.'
|
22
|
-
MSG_DMP_NO_DMP_ID = 'A DMP ID could not be registered at this time.'
|
23
22
|
MSG_DMP_INVALID_DMP_ID = 'Invalid DMP ID format.'
|
23
|
+
MSG_DMP_NO_DMP_ID = 'A DMP ID could not be registered at this time.'
|
24
24
|
MSG_DMP_NO_HISTORICALS = 'You cannot modify a historical version of the DMP.'
|
25
|
+
MSG_NO_OWNER_ORG = 'Could not determine ownership of the DMP ID.'
|
26
|
+
MSG_DMP_NO_TOMBSTONE = 'Unable to tombstone the DMP ID at this time.'
|
27
|
+
MSG_DMP_NO_UPDATE = 'Unable to update the DMP ID at this time.'
|
28
|
+
MSG_DMP_NOT_FOUND = 'DMP does not exist.'
|
25
29
|
MSG_DMP_UNABLE_TO_VERSION = 'Unable to version this DMP.'
|
30
|
+
MSG_DMP_UNKNOWN = 'DMP does not exist. Try :create instead.'
|
26
31
|
end
|
27
32
|
# rubocop:enable Naming/FileName
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: uc3-dmp-id
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.27
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Brian Riley
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2023-06-
|
11
|
+
date: 2023-06-07 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: json
|
@@ -123,6 +123,7 @@ files:
|
|
123
123
|
- lib/uc3-dmp-id/helper.rb
|
124
124
|
- lib/uc3-dmp-id/schemas/amend.rb
|
125
125
|
- lib/uc3-dmp-id/schemas/author.rb
|
126
|
+
- lib/uc3-dmp-id/splicer.rb
|
126
127
|
- lib/uc3-dmp-id/updater.rb
|
127
128
|
- lib/uc3-dmp-id/validator.rb
|
128
129
|
- lib/uc3-dmp-id/version.rb
|