Class: Dmp::DynamoAdapter

Inherits:
Object
  • Object
show all
Defined in:
lib/dmp/dynamo_adapter.rb

Overview

DMP adapter for an AWS DynamoDB Table rubocop:disable Metrics/ClassLength

Constant Summary collapse

MSG_DEFAULT =
'Unable to process your request.'
MSG_EXISTS =
'DMP already exists. Try :update instead.'
MSG_NOT_FOUND =
'DMP does not exist.'
MSG_FORBIDDEN =
'You cannot update the DMP.'
MSG_NO_DMP_ID =
'A DMP ID could not be registered at this time.'
MSG_UNKNOWN =
'DMP does not exist. Try :create instead.'
MSG_NO_HISTORICALS =
'You cannot modify a historical version of the DMP.'

Instance Method Summary collapse

Constructor Details

#initialize(provenance:, debug: false) ⇒ DynamoAdapter

Initialize an instance by setting the provenance and connecting to the DB



22
23
24
25
26
27
28
29
# File 'lib/dmp/dynamo_adapter.rb', line 22

def initialize(provenance:, debug: false)
  @provenance = Dmp::MetadataHandler.append_pk_prefix(provenance: provenance)
  @debug_mode = debug

  @client = Aws::DynamoDB::Client.new(
    region: ENV['AWS_REGION']
  )
end

Instance Method Details

#create(json: {}) ⇒ Object

Add a record to the table rubocop:disable Metrics/MethodLength, Metrics/CyclomaticComplexity



101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
# File 'lib/dmp/dynamo_adapter.rb', line 101

def create(json: {})
  json = prepare_json(json: json)
  return { status: 400, error: MSG_DEFAULT } if json.nil? || @provenance.nil?

  # Try to find it first
  result = find_by_json(json: json)
  return { status: 500, error: result.fetch(:error, MSG_DEFAULT) } if result[:status] == 500
  # Abort if found
  return { status: 400, error: MSG_EXISTS } if result.fetch(:items, []).any?

  # allocate a DMP ID
  p_key = preregister_dmp_id

p "PRE REGISTERED DMP ID: #{p_key.inspect}"

  return { status: 500, error: MSG_NO_DMP_ID } if p_key.nil?

  # Add the DMPHub specific attributes and then save
  json = Dmp::MetadataHandler.annotate_json(provenance: @provenance, json: json, p_key: p_key)

p "ANNOTATED:"
pp json

p "PUTTING ITEM:"
  response = @client.put_item(
    {
      table_name: ENV['AWS_DYNAMO_TABLE_NAME'],
      item: json,
      return_consumed_capacity: @debug_mode ? 'TOTAL' : 'NONE'
    }
  )
  # Fetch the newly created record
  response = find_by_pk(p_key: p_key, s_key: Dmp::MetadataHandler::LATEST_VERSION)
  return { status: 500, error: MSG_DEFAULT } unless response[:status] == 200

p 'CREATED!?'
pp response[:items]

  { status: 201, items: response[:items] }
rescue Aws::DynamoDB::Errors::DuplicateItemException
  { status: 405, error: MSG_EXISTS }
rescue Aws::Errors::ServiceError => e
  { status: 500, error: "#{MSG_DEFAULT} - #{e.message}" }
end

#delete(p_key:, json: {}) ⇒ Object

Delete/Tombstone a record in the table rubocop:disable Metrics/MethodLength, Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity



207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
# File 'lib/dmp/dynamo_adapter.rb', line 207

def delete(p_key:, json: {})
  json = prepare_json(json: json)
  return { status: 400, error: MSG_DEFAULT } if json.nil? || p_key.nil? || @provenance.nil?

  # Verify that the JSON is for the same DMP in the PK
  dmp_id = json.fetch('dmp_id', {})
  return { status: 403, error: MSG_FORBIDDEN } unless Dmp::DmpIdHandler.dmp_id_to_pk(json: dmp_id) == p_key

  # Try to find it first
  result = find_by_json(json: json)
  return { status: 500, error: result.fetch(:error, MSG_DEFAULT) } if result[:status] == 500
  # Abort if NOT found
  return { status: 404, error: MSG_NOT_FOUND } unless result[:status] == 200 && result.fetch(:items, []).any?

  dmp = result[:items].first&.item
  return { status: 404, error: MSG_NOT_FOUND } if dmp.nil?
  # Only allow this if the provenance is the owner of the DMP!
  return { status: 403, error: MSG_FORBIDDEN } unless dmp['dmphub_provenance_id'] == @provenance
  # Make sure they're not trying to update a historical copy of the DMP
  return { status: 405, error: MSG_NO_HISTORICALS } if dmp['SK'] != Dmp::MetadataHandler::LATEST_VERSION

  response = @client.update_item(
    {
      table_name: ENV['AWS_DYNAMO_TABLE_NAME'],
      key: {
        PK: dmp['PK'],
        SK: Dmp::MetadataHandler::LATEST_VERSION
      },
      update_expression: 'SET SK = :sk, dmphub_deleted_at = :deletion_date',
      expression_attribute_values: {
        sk: Dmp::MetadataHandler::TOMBSTONE_VERSION,
        deletion_date: Time.now.iso8601
      },
      return_consumed_capacity: @debug_mode ? 'TOTAL' : 'NONE',
      return_values: 'ALL_NEW'
    }
  )
  { status: 200, items: response.items.map(&:item).compact.uniq }
rescue Aws::Errors::ServiceError => e
  { status: 500, error: "#{MSG_DEFAULT} - #{e.message}" }
end

#dmps_for_provenanceObject

Fetch the DMPs for the provenance rubocop:disable Metrics/MethodLength



33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
# File 'lib/dmp/dynamo_adapter.rb', line 33

def dmps_for_provenance
  return { status: 404, error: MSG_NOT_FOUND } if @provenance.nil?

  response = @client.query(
    {
      table_name: ENV['AWS_DYNAMO_TABLE_NAME'],
      key_conditions: {
        PK: {
          attribute_value_list: ["PROVENANCE##{@provenance}"],
          comparison_operator: 'EQ'
        },
        SK: {
          attribute_value_list: ['DMPS'],
          comparison_operator: 'EQ'
        }
      }
    }
  )
  { status: 200, items: response.items.map(&:item).compact.uniq }
rescue Aws::Errors::ServiceError => e
  { status: 500, error: "#{MSG_DEFAULT} - #{e.message}" }
end

#find_by_json(json:) ⇒ Object

Find a DMP based on the contents of the incoming JSON rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity



82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
# File 'lib/dmp/dynamo_adapter.rb', line 82

def find_by_json(json:)
  return { status: 404, error: MSG_NOT_FOUND } if json.nil? ||
                                                  (json['PK'].nil? && json['dmp_id'].nil?)

  pk = json['PK']
  # Translate the incoming :dmp_id into a PK
  pk = Dmp::DmpIdHandler.dmp_id_to_pk(json: json.fetch('dmp_id', {})) if pk.nil?

  # find_by_PK
  response = find_by_pk(p_key: pk, s_key: json['SK']) unless pk.nil?
  return response unless response[:status] != 404

  # find_by_dmphub_provenance_id -> if no PK and no dmp_id result
  find_by_dmphub_provenance_identifier(json: json)
end

#find_by_pk(p_key:, s_key: Dmp::MetadataHandler::LATEST_VERSION) ⇒ Object

Find the DMP by its PK and SK



58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
# File 'lib/dmp/dynamo_adapter.rb', line 58

def find_by_pk(p_key:, s_key: Dmp::MetadataHandler::LATEST_VERSION)
  return { status: 404, error: MSG_NOT_FOUND } if p_key.nil?

  s_key = Dmp::MetadataHandler::LATEST_VERSION if s_key.nil? || s_key.strip == ''

p "FINDING BY PK: PK - #{p_key.inspect} / SK - #{s_key.inspect}"

  response = @client.get_item(
    {
      table_name: ENV['AWS_DYNAMO_TABLE_NAME'],
      key: { PK: p_key, SK: s_key },
      consistent_read: false,
      return_consumed_capacity: @debug_mode ? 'TOTAL' : 'NONE'
    }
  )
  return { status: 404, error: MSG_NOT_FOUND } if response[:item].nil? || response[:item].empty?

  { status: 200, items: [response[:item]] }
rescue Aws::Errors::ServiceError => e
  { status: 500, error: "#{MSG_DEFAULT} - #{e.message}" }
end

#update(p_key:, json: {}) ⇒ Object

Update a record in the table rubocop:disable Metrics/AbcSize, Metrics/MethodLength, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity



149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
# File 'lib/dmp/dynamo_adapter.rb', line 149

def update(p_key:, json: {})
  json = prepare_json(json: json)
  return { status: 400, error: MSG_DEFAULT } if json.nil? || p_key.nil? || @provenance.nil?

  # Verify that the JSON is for the same DMP in the PK
  dmp_id = json.fetch('dmp_id', {})
  return { status: 403, error: MSG_FORBIDDEN } unless Dmp::DmpIdHandler.dmp_id_to_pk(json: dmp_id) == p_key

  # Try to find it first
  result = find_by_json(json: json)
  return { status: 500, error: result.fetch(:error, MSG_DEFAULT) } if result[:status] == 500

  dmp = result[:items].first&.item
  return { status: 404, error: MSG_NOT_FOUND } if dmp.nil?
  # Only allow this if the provenance is the owner of the DMP!
  return { status: 403, error: MSG_FORBIDDEN } unless dmp['dmphub_provenance_id'] == @provenance
  # Make sure they're not trying to update a historical copy of the DMP
  return { status: 405, error: MSG_NO_HISTORICALS } if dmp['SK'] != Dmp::MetadataHandler::LATEST_VERSION

  # version the old :latest
  version_result = version_it(dmp: dmp)
  return version_result if version_result[:status] != 200

  # Add the DMPHub specific attributes and then save it
  json = Dmp::MetadataHandler.annotate_json(provenance: @provenance, json: json, p_key: p_key)

p "BEFORE:"
pp json
p '==================================='
p ''

  json = splice_json(original_version: version_result[:items].first&.item, new_version: json)

# p ''
p "AFTER:"
pp json

  response = @client.put_item(
    {
      table_name: ENV['AWS_DYNAMO_TABLE_NAME'],
      item: json,
      return_consumed_capacity: @debug_mode ? 'TOTAL' : 'NONE'
    }
  )

  # Update the provenance keys!
  # Update the ancillary keys for orcids, affiliations, provenance

  { status: 200, items: response.items.map(&:item).compact.uniq }
rescue Aws::DynamoDB::Errors::DuplicateItemException
  { status: 405, error: MSG_EXISTS }
rescue Aws::Errors::ServiceError => e
  { status: 500, error: "#{MSG_DEFAULT} - #{e.message}" }
end