Skip to content

Commit

Permalink
Spike on a full redesign of the client
Browse files Browse the repository at this point in the history
Fixes #261

TODO: write a better commit message later when this is more fully baked.
  • Loading branch information
mjgiarlo committed Mar 12, 2024
1 parent b63b703 commit 9e425bb
Show file tree
Hide file tree
Showing 25 changed files with 1,124 additions and 25 deletions.
34 changes: 22 additions & 12 deletions .rubocop_todo.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# This configuration was generated by
# `rubocop --auto-gen-config --auto-gen-only-exclude`
# on 2023-09-11 17:08:41 UTC using RuboCop version 1.56.3.
# on 2024-01-12 23:32:44 UTC using RuboCop version 1.59.0.
# The point is for the user to remove these configuration records
# one by one as the offenses are removed from the code base.
# Note that changes in the inspected code, or installation of new
Expand All @@ -13,6 +13,12 @@ Gemspec/RequiredRubyVersion:
Exclude:
- 'sdr-client.gemspec'

# Offense count: 1
# Configuration parameters: AllowComments, AllowEmptyLambdas.
Lint/EmptyBlock:
Exclude:
- 'spec/sdr_client/redesigned_client/job_status_spec.rb'

# Offense count: 1
Lint/NoReturnInBeginEndBlocks:
Exclude:
Expand All @@ -26,11 +32,12 @@ Lint/UnusedMethodArgument:
- 'lib/sdr_client/deposit/file_type_file_set_strategy.rb'
- 'lib/sdr_client/deposit/image_file_set_strategy.rb'

# Offense count: 4
# Offense count: 5
# Configuration parameters: AllowedMethods, AllowedPatterns, CountRepeatedAttributes, Max.
Metrics/AbcSize:
Exclude:
- 'lib/sdr_client/cli.rb'
- 'lib/sdr_client/redesigned_client.rb'
- 'lib/sdr_client/update.rb'

# Offense count: 1
Expand All @@ -39,7 +46,7 @@ Metrics/CyclomaticComplexity:
Exclude:
- 'lib/sdr_client/update.rb'

# Offense count: 12
# Offense count: 14
# Configuration parameters: CountComments, Max, CountAsOne, AllowedMethods, AllowedPatterns.
Metrics/MethodLength:
Exclude:
Expand All @@ -49,14 +56,21 @@ Metrics/MethodLength:
- 'lib/sdr_client/deposit/process.rb'
- 'lib/sdr_client/deposit/request.rb'
- 'lib/sdr_client/login.rb'
- 'lib/sdr_client/redesigned_client.rb'
- 'lib/sdr_client/update.rb'

# Offense count: 1
# This cop supports unsafe autocorrection (--autocorrect-all).
RSpec/EmptyExampleGroup:
Exclude:
- 'spec/sdr_client/redesigned_client/job_status_spec.rb'

# Offense count: 11
# Configuration parameters: Max, CountAsOne.
RSpec/ExampleLength:
Exclude:
- 'spec/sdr_client/deposit_spec.rb'
- 'spec/sdr_client/deposit_model_spec.rb'
- 'spec/sdr_client/deposit_spec.rb'
- 'spec/sdr_client/update_spec.rb'

# Offense count: 1
Expand All @@ -72,10 +86,10 @@ RSpec/MultipleExpectations:
Exclude:
- 'spec/sdr_client/connection_spec.rb'
- 'spec/sdr_client/deposit/metadata_builder_spec.rb'
- 'spec/sdr_client/deposit_model_spec.rb'
- 'spec/sdr_client/deposit_spec.rb'
- 'spec/sdr_client/find_spec.rb'
- 'spec/sdr_client/login_spec.rb'
- 'spec/sdr_client/deposit_model_spec.rb'

# Offense count: 24
# Configuration parameters: EnforcedStyle, IgnoreSharedExamples.
Expand Down Expand Up @@ -105,14 +119,10 @@ RSpec/ReceiveMessages:
Exclude:
- 'spec/sdr_client/update_spec.rb'

# Offense count: 2
# Configuration parameters: Include, CustomTransform, IgnoreMethods, IgnoreMetadata.
# Include: **/*_spec.rb
RSpec/SpecFilePathFormat:
# Offense count: 4
RSpec/RepeatedExampleGroupBody:
Exclude:
- '**/spec/routing/**/*'
- 'spec/sdr_client/client_spec.rb'
- 'spec/sdr_client/model_deposit_spec.rb'
- 'spec/sdr_client/redesigned_client/job_status_spec.rb'

# Offense count: 21
# This cop supports safe autocorrection (--autocorrect).
Expand Down
3 changes: 0 additions & 3 deletions lib/sdr-client.rb

This file was deleted.

18 changes: 8 additions & 10 deletions lib/sdr_client.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,15 @@
require 'active_support'
require 'active_support/core_ext'
require 'cocina/models'
require 'zeitwerk'

require 'sdr_client/version'
require 'sdr_client/unexpected_response'
require 'sdr_client/deposit'
require 'sdr_client/update'
require 'sdr_client/credentials'
require 'sdr_client/find'
require 'sdr_client/login'
require 'sdr_client/login_prompt'
require 'sdr_client/connection'
require 'sdr_client/background_job_results'
loader = Zeitwerk::Loader.for_gem
loader.inflector.inflect(
'cli' => 'CLI',
'md5' => 'MD5',
'sha1' => 'SHA1'
)
loader.setup

module SdrClient
class Error < StandardError; end
Expand Down
158 changes: 158 additions & 0 deletions lib/sdr_client/redesigned_client.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
# frozen_string_literal: true

require 'logger'
require 'singleton'

module SdrClient
# The SDR client reimagined, built using patterns successfully used in other client gems we maintain
class RedesignedClient
include Singleton

DEFAULT_HEADERS = {
accept: 'application/json',
content_type: 'application/json'
}.freeze

class << self
def configure(url:, email:, password:, request_options: default_request_options, logger: default_logger)
instance.config = Config.new(
url: url,
email: email,
password: password,
request_options: request_options,
logger: logger
)

instance
end

def default_logger
Logger.new($stdout)
end

def default_request_options
{
read_timeout: default_timeout,
timeout: default_timeout
}
end

# NOTE: This is the number of seconds it roughly takes for H2 to
# successfully shunt ~10GB files over to SDR API
def default_timeout
900
end

delegate :config, :connection, :deposit_model, :job_status, to: :instance
end

attr_accessor :config

def deposit_model(...)
Deposit.deposit_model(...)
end

def job_status(...)
JobStatus.new(...)
end

# def token_for(user_id:)
# end

# def checksum_for(file_path:, algorithm: :md5|:sha1)
# end

# def find(object_id:)
# end

# def update_model(model:, version_description:)
# end

# # NOTE: this method is used only the CLI & the infrastructure-integration-tests deposit helpers
# def deposit_metadata(apo:, collection:, type:, strategies:, files:, path:)
# end

# Send an authenticated GET request
# @param path [String] the path to the SDR API request
def get(path:)
response = with_token_refresh_when_unauthorized do
connection.get(path)
end

UnexpectedResponse.call(response) unless response.success?

return nil if response.body.blank?

JSON.parse(response.body).with_indifferent_access
end

# Send an authenticated POST request
# @param path [String] the path to the SDR API request
# @param body [String] the body of the SDR API request
# @param headers [Hash] extra headers to add to the SDR API request
def post(path:, body:, headers: {})
response = with_token_refresh_when_unauthorized do
connection.post(path) do |conn|
conn.body = body.to_json
conn.headers = headers
end
end

UnexpectedResponse.call(response) unless response.success?

return nil if response.body.blank?

JSON.parse(response.body).with_indifferent_access
end

# Send an authenticated PUT request
# @param path [String] the path to the SDR API request
# @param body [String] the body of the SDR API request
def put(path:, body:, content_type:, content_length:)
request_body = content_type == 'application/json' ? body.to_json : body
request_headers = {
'content-type': content_type,
'content-length': content_length
}
response = with_token_refresh_when_unauthorized do
connection.put(path) do |conn|
conn.body = request_body
conn.headers = request_headers
end
end

UnexpectedResponse.call(response) unless response.success?

return nil if response.body.blank?

JSON.parse(response.body).with_indifferent_access
end

private

Config = Struct.new(:url, :email, :password, :logger, :request_options, keyword_init: true)

def connection
@connection ||= Faraday.new(
url: config.url,
headers: DEFAULT_HEADERS,
request: config.request_options
) do |conn|
conn.request :authorization, :Bearer, SdrClient::Credentials.read
conn.adapter :net_http
end
end

def with_token_refresh_when_unauthorized
response = yield

# if unauthorized, token has likely expired. try to get a new token and then retry the same request(s).
if response.status == 401
config.token = Authenticator.token
response = yield
end

response
end
end
end
39 changes: 39 additions & 0 deletions lib/sdr_client/redesigned_client/authenticator.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# frozen_string_literal: true

module SdrClient
class RedesignedClient
# Fetch a token from the Folio API using login_params
class Authenticator
include Dry::Monads[:result]

def self.token
new.token
end

# Request an access_token
def token
response = SdrClient::RedesignedClient.connection.post(path, request_body)

UnexpectedResponse.call(response) unless response.success?

credential_store.write(response.body)
Success()
end

private

def request_body
JSON.generate(
{
email: SdrClient::RedesignedClient.config.email,
password: SdrClient::RedesignedClient.config.password
}
)
end

def path
'/v1/auth/login'
end
end
end
end
71 changes: 71 additions & 0 deletions lib/sdr_client/redesigned_client/create_resource.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
# frozen_string_literal: true

module SdrClient
class RedesignedClient
# Creates a resource (metadata) in SDR
class CreateResource
def self.run(...)
new(...).run
end

# @param [Boolean] accession should the accessionWF be started
# @param [Boolean] assign_doi should a DOI be assigned to this item
# @param [Cocina::Models::RequestDRO, Cocina::Models::RequestCollection] metadata
# @param [Hash<Symbol,String>] the result of the metadata call
# @param [String] priority what processing priority should be used
# either 'low' or 'default'
def initialize(accession:, metadata:, assign_doi: false, priority: nil)
@accession = accession
@priority = priority
@assign_doi = assign_doi
@metadata = metadata
end

# @param [Hash<Symbol,String>] the result of the metadata call
# @return [String] job id for the background job result
def run
response = metadata_request
UnexpectedResponse.call(response) unless response.status == 201

logger.info("Response from server: #{response.body}")

JSON.parse(response.body)['jobId']
end

private

attr_reader :metadata, :priority

def logger
SdrClient::RedesignedClient.config.logger
end

def client
SdrClient::RedesignedClient.instance
end

def metadata_request
json = metadata.to_json
logger.debug("Starting upload metadata: #{json}")

connection.post(path: path, body: json, headers: { 'X-Cocina-Models-Version' => Cocina::Models::VERSION })
end

def accession?
@accession
end

def assign_doi?
@assign_doi
end

def path
params = { accession: accession? }
params[:priority] = priority if priority
params[:assign_doi] = true if assign_doi? # false is default
query_string = params.map { |k, v| "#{k}=#{v}" }.join('&')
"/v1/resources?#{query_string}"
end
end
end
end
Loading

0 comments on commit 9e425bb

Please sign in to comment.