sdr-client 2.12.0 → 2.13.0.beta1

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.
Files changed (38) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +3 -0
  3. data/.rubocop_todo.yml +24 -20
  4. data/Gemfile.lock +11 -8
  5. data/exe/sdr_redesigned +10 -0
  6. data/lib/sdr_client/redesigned_client/authenticator.rb +40 -0
  7. data/lib/sdr_client/redesigned_client/cli/config.rb +32 -0
  8. data/lib/sdr_client/redesigned_client/cli/credentials.rb +35 -0
  9. data/lib/sdr_client/redesigned_client/cli/update.rb +186 -0
  10. data/lib/sdr_client/redesigned_client/cli.rb +198 -0
  11. data/lib/sdr_client/redesigned_client/create_resource.rb +71 -0
  12. data/lib/sdr_client/redesigned_client/deposit.rb +115 -0
  13. data/lib/sdr_client/redesigned_client/direct_upload_request.rb +45 -0
  14. data/lib/sdr_client/redesigned_client/direct_upload_response.rb +9 -0
  15. data/lib/sdr_client/redesigned_client/file.rb +100 -0
  16. data/lib/sdr_client/redesigned_client/file_set.rb +53 -0
  17. data/lib/sdr_client/redesigned_client/file_type_file_set_strategy.rb +13 -0
  18. data/lib/sdr_client/redesigned_client/find.rb +42 -0
  19. data/lib/sdr_client/redesigned_client/image_file_set_strategy.rb +13 -0
  20. data/lib/sdr_client/redesigned_client/job_status.rb +74 -0
  21. data/lib/sdr_client/redesigned_client/matching_file_grouping_strategy.rb +19 -0
  22. data/lib/sdr_client/redesigned_client/metadata.rb +64 -0
  23. data/lib/sdr_client/redesigned_client/operations/md5.rb +16 -0
  24. data/lib/sdr_client/redesigned_client/operations/mime_type.rb +17 -0
  25. data/lib/sdr_client/redesigned_client/operations/sha1.rb +16 -0
  26. data/lib/sdr_client/redesigned_client/request_builder.rb +171 -0
  27. data/lib/sdr_client/redesigned_client/single_file_grouping_strategy.rb +14 -0
  28. data/lib/sdr_client/redesigned_client/structural_grouper.rb +72 -0
  29. data/lib/sdr_client/redesigned_client/structural_metadata_builder.rb +51 -0
  30. data/lib/sdr_client/redesigned_client/unexpected_response.rb +25 -0
  31. data/lib/sdr_client/redesigned_client/update_dro_with_file_identifiers.rb +35 -0
  32. data/lib/sdr_client/redesigned_client/update_resource.rb +61 -0
  33. data/lib/sdr_client/redesigned_client/upload_files.rb +71 -0
  34. data/lib/sdr_client/redesigned_client/upload_files_metadata_builder.rb +40 -0
  35. data/lib/sdr_client/redesigned_client.rb +192 -0
  36. data/lib/sdr_client/version.rb +1 -1
  37. data/lib/sdr_client.rb +3 -1
  38. metadata +35 -3
@@ -0,0 +1,71 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SdrClient
4
+ class RedesignedClient
5
+ # The file uploading part of a deposit
6
+ class UploadFiles
7
+ # @param [Hash<String,DirectUploadRequest>] file_metadata map of relative filepaths to file metadata
8
+ # @param [Hash<String,String>] filepath_map map of relative filepaths to absolute filepaths
9
+ def self.upload(file_metadata:, filepath_map:)
10
+ new(file_metadata: file_metadata, filepath_map: filepath_map).upload
11
+ end
12
+
13
+ # @param [Hash<String,DirectUploadRequest>] file_metadata map of relative filepaths to file metadata
14
+ # @param [Hash<String,String>] filepath_map map of relative filepaths to absolute filepaths
15
+ def initialize(file_metadata:, filepath_map:)
16
+ @file_metadata = file_metadata
17
+ @filepath_map = filepath_map
18
+ end
19
+
20
+ # @return [Array<DirectUploadResponse>] the responses from the server for the uploads
21
+ def upload
22
+ file_metadata.map do |filepath, metadata|
23
+ direct_upload(metadata.to_json).tap do |response|
24
+ # ActiveStorage modifies the filename provided in response, so setting here with the relative filename
25
+ response.filename = filepath
26
+ upload_file(response)
27
+ logger.info("Upload of #{filepath} complete")
28
+ end
29
+ end
30
+ end
31
+
32
+ private
33
+
34
+ attr_reader :file_metadata, :filepath_map
35
+
36
+ def logger
37
+ SdrClient::RedesignedClient.config.logger
38
+ end
39
+
40
+ def client
41
+ SdrClient::RedesignedClient.instance
42
+ end
43
+
44
+ def path
45
+ '/v1/direct_uploads'
46
+ end
47
+
48
+ def direct_upload(metadata_json)
49
+ logger.info("Starting an upload request: #{metadata_json}")
50
+ response = client.post(path: path, body: metadata_json)
51
+
52
+ logger.info("Response from server: #{response}")
53
+ DirectUploadResponse.new(response)
54
+ end
55
+
56
+ def upload_file(response)
57
+ logger.info("Uploading `#{response.filename}' to #{response.direct_upload.fetch('url')}")
58
+
59
+ client.put(
60
+ path: response.direct_upload.fetch('url'),
61
+ body: ::File.open(filepath_map[response.filename]),
62
+ headers: {
63
+ 'content-type' => response.content_type,
64
+ 'content-length' => response.byte_size.to_s
65
+ },
66
+ expected_status: 204
67
+ )
68
+ end
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SdrClient
4
+ class RedesignedClient
5
+ # Collecting all the metadata about the files for a deposit
6
+ class UploadFilesMetadataBuilder
7
+ # @param [Array<String>] files a list of relative filepaths to upload
8
+ # @param [Hash<String,String>] mime_types a map of filenames to mime types
9
+ # @param [String] basepath path to which files are relative
10
+ # @return [Hash<String, DirectUploadRequest>] the metadata for uploading the files
11
+ def self.build(files:, mime_types:, basepath:)
12
+ new(files: files, mime_types: mime_types, basepath: basepath).build
13
+ end
14
+
15
+ # @param [Array<String>] files a list of absolute filepaths to upload
16
+ # @param [Hash<String,String>] mime_types a map of filenames to mime types
17
+ # @param [String] basepath path to which files are relative
18
+ def initialize(files:, mime_types:, basepath:)
19
+ @files = files
20
+ @mime_types = mime_types
21
+ @basepath = basepath
22
+ end
23
+
24
+ attr_reader :files, :mime_types, :basepath
25
+
26
+ # @return [Hash<String, DirectUploadRequest>] the metadata for uploading the files
27
+ def build
28
+ files.each_with_object({}) do |filepath, obj|
29
+ obj[filepath] = DirectUploadRequest.from_file(absolute_filepath_for(filepath),
30
+ file_name: filepath,
31
+ content_type: mime_types[filepath])
32
+ end
33
+ end
34
+
35
+ def absolute_filepath_for(filepath)
36
+ ::File.join(basepath, filepath)
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,192 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'digest'
4
+ require 'logger'
5
+ require 'shellwords'
6
+ require 'singleton'
7
+ require 'timeout'
8
+
9
+ module SdrClient
10
+ # The SDR client reimagined, built using patterns successfully used in other client gems we maintain
11
+ class RedesignedClient
12
+ include Singleton
13
+
14
+ class << self
15
+ # rubocop:disable Metrics/MethodLength, Metrics/ParameterLists
16
+ def configure(url:, email: nil, password: nil, token_refresher: nil, token: default_token,
17
+ request_options: default_request_options, logger: default_logger)
18
+ if email.blank? && password.blank? && !token_refresher.respond_to?(:call)
19
+ raise ArgumentError, 'email and password cannot be blank without a custom token refresher callable'
20
+ end
21
+
22
+ instance.config = Config.new(
23
+ token: token,
24
+ url: url,
25
+ email: email,
26
+ password: password,
27
+ request_options: request_options,
28
+ logger: logger,
29
+ token_refresher: token_refresher
30
+ )
31
+
32
+ instance
33
+ end
34
+ # rubocop:enable Metrics/MethodLength, Metrics/ParameterLists
35
+
36
+ # For the initial token, use a dummy value to avoid hitting any APIs
37
+ # during configuration, allowing `with_token_refresh_when_unauthorized` to handle
38
+ # auto-magic token refreshing. Why not immediately get a valid token? Our apps
39
+ # commonly invoke client `.configure` methods in the initializer in all
40
+ # application environments, even those that are never expected to
41
+ # connect to production APIs, such as local development machines.
42
+ #
43
+ # NOTE: `nil` and blank string cannot be used as dummy values here as
44
+ # they lead to a malformed request to be sent, which triggers an
45
+ # exception not rescued by `with_token_refresh_when_unauthorized`
46
+ def default_token
47
+ 'a temporary dummy token to avoid hitting the API before it is needed'
48
+ end
49
+
50
+ def default_logger
51
+ Logger.new($stdout)
52
+ end
53
+
54
+ def default_request_options
55
+ {
56
+ read_timeout: default_timeout,
57
+ timeout: default_timeout
58
+ }
59
+ end
60
+
61
+ # NOTE: This is the number of seconds it roughly takes for H2 to
62
+ # successfully shunt ~10GB files over to SDR API
63
+ def default_timeout
64
+ 900
65
+ end
66
+
67
+ delegate :config, :connection, :deposit_model, :job_status, :find, :update_model, :build_and_deposit,
68
+ to: :instance
69
+ end
70
+
71
+ attr_accessor :config
72
+
73
+ def deposit_model(...)
74
+ Deposit.deposit_model(...)
75
+ end
76
+
77
+ def job_status(...)
78
+ JobStatus.new(...)
79
+ end
80
+
81
+ def find(...)
82
+ Find.run(...)
83
+ end
84
+
85
+ def update_model(...)
86
+ UpdateResource.run(...)
87
+ end
88
+
89
+ def build_and_deposit(...)
90
+ Metadata.deposit(...)
91
+ end
92
+
93
+ # Send an authenticated GET request
94
+ # @param path [String] the path to the SDR API request
95
+ def get(path:)
96
+ response = with_token_refresh_when_unauthorized do
97
+ connection.get(path)
98
+ end
99
+
100
+ UnexpectedResponse.call(response) unless response.success?
101
+
102
+ return nil if response.body.blank?
103
+
104
+ JSON.parse(response.body).with_indifferent_access
105
+ end
106
+
107
+ # Send an authenticated POST request
108
+ # @param path [String] the path to the SDR API request
109
+ # @param body [String] the body of the SDR API request
110
+ # @param headers [Hash] extra headers to add to the SDR API request
111
+ # @param expected_status [Integer] override if all 2xx statuses aren't success conditions
112
+ def post(path:, body:, headers: {}, expected_status: nil) # rubocop:disable Metrics/MethodLength
113
+ response = with_token_refresh_when_unauthorized do
114
+ connection.post(path) do |request|
115
+ request.body = body
116
+ request.headers = default_headers.merge(headers)
117
+ end
118
+ end
119
+
120
+ if expected_status
121
+ UnexpectedResponse.call(response) if response.status != expected_status
122
+ elsif !response.success?
123
+ UnexpectedResponse.call(response)
124
+ end
125
+
126
+ return nil if response.body.blank?
127
+
128
+ JSON.parse(response.body).with_indifferent_access
129
+ end
130
+
131
+ # Send an authenticated PUT request
132
+ # @param path [String] the path to the SDR API request
133
+ # @param body [String] the body of the SDR API request
134
+ # @param headers [Hash] extra headers to add to the SDR API request
135
+ # @param params [Hash] query parameters to add to the SDR API request
136
+ # @param expected_status [Integer] override if all 2xx statuses aren't success conditions
137
+ def put(path:, body:, headers: {}, params: {}, expected_status: nil) # rubocop:disable Metrics/MethodLength
138
+ response = with_token_refresh_when_unauthorized do
139
+ connection.put(path) do |request|
140
+ request.body = body
141
+ request.headers = default_headers.merge(headers)
142
+ request.params = params if params.present?
143
+ end
144
+ end
145
+
146
+ if expected_status
147
+ UnexpectedResponse.call(response) if response.status != expected_status
148
+ elsif !response.success?
149
+ UnexpectedResponse.call(response)
150
+ end
151
+
152
+ return nil if response.body.blank?
153
+
154
+ JSON.parse(response.body).with_indifferent_access
155
+ end
156
+
157
+ private
158
+
159
+ Config = Struct.new(:url, :email, :password, :token, :logger,
160
+ :request_options, :token_refresher, keyword_init: true)
161
+
162
+ def connection
163
+ Faraday.new(
164
+ url: SdrClient::RedesignedClient.config.url,
165
+ headers: default_headers,
166
+ request: SdrClient::RedesignedClient.config.request_options
167
+ ) do |conn|
168
+ conn.adapter :net_http
169
+ end
170
+ end
171
+
172
+ def default_headers
173
+ {
174
+ accept: 'application/json',
175
+ content_type: 'application/json',
176
+ Authorization: "Bearer #{config.token}"
177
+ }
178
+ end
179
+
180
+ def with_token_refresh_when_unauthorized
181
+ response = yield
182
+
183
+ # if unauthorized, token has likely expired. try to get a new token and then retry the same request(s).
184
+ if response.status == 401
185
+ config.token = config.token_refresher ? config.token_refresher.call : Authenticator.token
186
+ response = yield
187
+ end
188
+
189
+ response
190
+ end
191
+ end
192
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module SdrClient
4
- VERSION = '2.12.0'
4
+ VERSION = '2.13.0.beta1'
5
5
  end
data/lib/sdr_client.rb CHANGED
@@ -12,7 +12,9 @@ loader = Zeitwerk::Loader.for_gem
12
12
  loader.ignore(
13
13
  "#{__dir__}/sdr-client.rb",
14
14
  "#{__dir__}/sdr_client/cli.rb",
15
- "#{__dir__}/sdr_client/cli/config.rb"
15
+ "#{__dir__}/sdr_client/cli/config.rb",
16
+ "#{__dir__}/sdr_client/redesigned_client/cli.rb",
17
+ "#{__dir__}/sdr_client/redesigned_client/cli/config.rb"
16
18
  )
17
19
  loader.inflector.inflect(
18
20
  'md5' => 'MD5',
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: sdr-client
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.12.0
4
+ version: 2.13.0.beta1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Justin Coyne
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2024-03-29 00:00:00.000000000 Z
11
+ date: 2024-04-22 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -115,6 +115,7 @@ email:
115
115
  executables:
116
116
  - remove_w3cdtf_encoding_from_event_dates
117
117
  - sdr
118
+ - sdr_redesigned
118
119
  extensions: []
119
120
  extra_rdoc_files: []
120
121
  files:
@@ -134,6 +135,7 @@ files:
134
135
  - config/settings.yml
135
136
  - exe/remove_w3cdtf_encoding_from_event_dates
136
137
  - exe/sdr
138
+ - exe/sdr_redesigned
137
139
  - lib/sdr-client.rb
138
140
  - lib/sdr_client.rb
139
141
  - lib/sdr_client/background_job_results.rb
@@ -166,6 +168,36 @@ files:
166
168
  - lib/sdr_client/find.rb
167
169
  - lib/sdr_client/login.rb
168
170
  - lib/sdr_client/login_prompt.rb
171
+ - lib/sdr_client/redesigned_client.rb
172
+ - lib/sdr_client/redesigned_client/authenticator.rb
173
+ - lib/sdr_client/redesigned_client/cli.rb
174
+ - lib/sdr_client/redesigned_client/cli/config.rb
175
+ - lib/sdr_client/redesigned_client/cli/credentials.rb
176
+ - lib/sdr_client/redesigned_client/cli/update.rb
177
+ - lib/sdr_client/redesigned_client/create_resource.rb
178
+ - lib/sdr_client/redesigned_client/deposit.rb
179
+ - lib/sdr_client/redesigned_client/direct_upload_request.rb
180
+ - lib/sdr_client/redesigned_client/direct_upload_response.rb
181
+ - lib/sdr_client/redesigned_client/file.rb
182
+ - lib/sdr_client/redesigned_client/file_set.rb
183
+ - lib/sdr_client/redesigned_client/file_type_file_set_strategy.rb
184
+ - lib/sdr_client/redesigned_client/find.rb
185
+ - lib/sdr_client/redesigned_client/image_file_set_strategy.rb
186
+ - lib/sdr_client/redesigned_client/job_status.rb
187
+ - lib/sdr_client/redesigned_client/matching_file_grouping_strategy.rb
188
+ - lib/sdr_client/redesigned_client/metadata.rb
189
+ - lib/sdr_client/redesigned_client/operations/md5.rb
190
+ - lib/sdr_client/redesigned_client/operations/mime_type.rb
191
+ - lib/sdr_client/redesigned_client/operations/sha1.rb
192
+ - lib/sdr_client/redesigned_client/request_builder.rb
193
+ - lib/sdr_client/redesigned_client/single_file_grouping_strategy.rb
194
+ - lib/sdr_client/redesigned_client/structural_grouper.rb
195
+ - lib/sdr_client/redesigned_client/structural_metadata_builder.rb
196
+ - lib/sdr_client/redesigned_client/unexpected_response.rb
197
+ - lib/sdr_client/redesigned_client/update_dro_with_file_identifiers.rb
198
+ - lib/sdr_client/redesigned_client/update_resource.rb
199
+ - lib/sdr_client/redesigned_client/upload_files.rb
200
+ - lib/sdr_client/redesigned_client/upload_files_metadata_builder.rb
169
201
  - lib/sdr_client/unexpected_response.rb
170
202
  - lib/sdr_client/update.rb
171
203
  - lib/sdr_client/version.rb
@@ -192,7 +224,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
192
224
  - !ruby/object:Gem::Version
193
225
  version: '0'
194
226
  requirements: []
195
- rubygems_version: 3.5.6
227
+ rubygems_version: 3.5.7
196
228
  signing_key:
197
229
  specification_version: 4
198
230
  summary: The CLI for https://github.com/sul-dlss/sdr-api