sdr-client 0.1.0 → 0.2.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 97ae47ce6b50c4fd12228990d25dc3fb63432ba1a1e0fff9a1e614dece546c69
4
- data.tar.gz: f64a044b2373cda941ec7fe593d2eacc1602a6178963cf80c0637415ecded9f4
3
+ metadata.gz: 3a995f725226eef2f793d8b0b45e487c37a6e34899b223a1f2c6864f32204325
4
+ data.tar.gz: dd947253f76fe8dcfd20565537e89fa86c4b6b13a4e6c68b2ecf285d30406c08
5
5
  SHA512:
6
- metadata.gz: c573233fcff72bb1c1402d170d048b03182ac7dd45c81a7531dd02529869f2354c9d73bd8522cbb1ba463c52bb933cf692a67e3b7932c77f3746bf2276253913
7
- data.tar.gz: '0848314bb1fd0b02ca2add9e09c92185fbcf8d9db642d683fcb116dae2cbd7ca6df97d5e9e3464f4b2ab71fa1027eda3d67d07723b50bf3ffc6f08274590f649'
6
+ metadata.gz: f3f0c8d2c9994c4a0408438199105dfdcccb0901e4905220cbf99fd69b3d5e9a735752c3de638264c1397986985ecb2a9f992ab1f1fad2e3245025e78eed1dd0
7
+ data.tar.gz: 300591272660ad09e74bc992742c3c26c14251bd107252b441a2e2856ed981749dfd5ff298704b13c360a0b3dab0cc89fceb2282a9f301fe24909eaac0b8de0d
@@ -1,22 +1,21 @@
1
1
  # This configuration was generated by
2
2
  # `rubocop --auto-gen-config`
3
- # on 2020-01-07 23:33:21 -0800 using RuboCop version 0.79.0.
3
+ # on 2020-01-10 17:16:01 -0600 using RuboCop version 0.79.0.
4
4
  # The point is for the user to remove these configuration records
5
5
  # one by one as the offenses are removed from the code base.
6
6
  # Note that changes in the inspected code, or installation of new
7
7
  # versions of RuboCop, may require this file to be generated again.
8
8
 
9
9
  # Offense count: 1
10
- # Cop supports --auto-correct.
11
- # Configuration parameters: AllowForAlignment, AllowBeforeTrailingComments, ForceEqualSignAlignment.
12
- Layout/ExtraSpacing:
13
- Exclude:
14
- - 'sdr-client.gemspec'
10
+ Metrics/AbcSize:
11
+ Max: 16
15
12
 
16
13
  # Offense count: 1
17
- # Cop supports --auto-correct.
18
- # Configuration parameters: AllowForAlignment, EnforcedStyleForExponentOperator.
19
- # SupportedStylesForExponentOperator: space, no_space
20
- Layout/SpaceAroundOperators:
21
- Exclude:
22
- - 'sdr-client.gemspec'
14
+ # Configuration parameters: CountComments, ExcludedMethods.
15
+ Metrics/MethodLength:
16
+ Max: 13
17
+
18
+ # Offense count: 2
19
+ # Configuration parameters: CountKeywordArgs.
20
+ Metrics/ParameterLists:
21
+ Max: 8
@@ -13,5 +13,8 @@ before_script:
13
13
  after_script:
14
14
  - ./cc-test-reporter after-build --exit-code $TRAVIS_TEST_RESULT
15
15
 
16
+ env:
17
+ global:
18
+ - CC_TEST_REPORTER_ID=859fcfe88b00c026d15dce30e838e2299face8088b49fe62bc3a02d1507ce3d5
16
19
  notifications:
17
20
  email: false
data/README.md CHANGED
@@ -10,7 +10,16 @@ The code for the SDR API server is at https://github.com/sul-dlss/sdr-api
10
10
 
11
11
  ## Usage
12
12
 
13
+ Log in:
14
+ ```
15
+ sdr --service-url http://sdr-api-server:3000 login
16
+ ```
17
+
18
+
13
19
  Deposit a new object:
14
20
  ```
15
- sdr --service-url http://sdr-api-server:3000 deposit --label 'hey there' file1.png file2.png
21
+ sdr --service-url https://sdr-api-server:3000 deposit --label 'hey there' \
22
+ --admin-policy 'druid:bk123gh4567' \
23
+ --collection 'druid:gh456kw9876' \
24
+ --source-id 'googlebooks:stanford_12345' file1.png file2.png
16
25
  ```
data/exe/sdr CHANGED
@@ -10,7 +10,28 @@ global = OptionParser.new do |opts|
10
10
  options[:url] = url
11
11
  end
12
12
  opts.on('-h', '--help', 'Display this screen') do
13
- puts opts
13
+ puts <<~HELP
14
+ DESCRIPTION:
15
+ The SDR Command Line Interface is a tool to interact with the Stanford Digital Repository.
16
+
17
+ SYNOPSIS:
18
+ sdr [options] <command>
19
+
20
+ OPTIONS:
21
+ --service-url (string)
22
+ Override the command's default URL with the given URL.
23
+
24
+ -h, --help
25
+ Displays this screen
26
+
27
+ COMMANDS
28
+ deposit
29
+ deposit files to the SDR
30
+
31
+ login
32
+ Will prompt for email & password and exchange it for an login token, which it saves in ~/.sdr/token
33
+
34
+ HELP
14
35
  exit
15
36
  end
16
37
  end
@@ -23,11 +44,28 @@ subcommands = {
23
44
  opts.on('--label LABEL', 'The object label') do |label|
24
45
  options[:label] = label
25
46
  end
47
+
48
+ opts.on('--admin-policy ADMIN_POLICY', 'The druid identifier of the admin policy object') do |apo|
49
+ options[:apo] = apo
50
+ end
51
+ opts.on('--collection COLLECTION', 'The druid identifier of the collection object') do |collection|
52
+ options[:collection] = collection
53
+ end
54
+
55
+ opts.on('--catkey CATKEY', 'The catkey for this item') do |catkey|
56
+ options[:catkey] = catkey
57
+ end
58
+
59
+ opts.on('--source-id SOURCE_ID', 'The source id for this object') do |source_id|
60
+ options[:source_id] = source_id
61
+ end
62
+
26
63
  opts.on('-h', '--help', 'Display this screen') do
27
64
  puts opts
28
65
  exit
29
66
  end
30
- end
67
+ end,
68
+ 'login' => OptionParser.new
31
69
  }
32
70
 
33
71
  unless subcommands.key?(command)
@@ -2,6 +2,9 @@
2
2
 
3
3
  require 'sdr_client/version'
4
4
  require 'sdr_client/deposit'
5
+ require 'sdr_client/credentials'
6
+ require 'sdr_client/login'
7
+ require 'sdr_client/login_prompt'
5
8
  require 'sdr_client/cli'
6
9
  require 'faraday'
7
10
 
@@ -7,6 +7,10 @@ module SdrClient
7
7
  case command
8
8
  when 'deposit'
9
9
  SdrClient::Deposit.run(options)
10
+ when 'login'
11
+ SdrClient::Login.run(options)
12
+ else
13
+ raise "Unknown command #{command}"
10
14
  end
11
15
  end
12
16
  end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SdrClient
4
+ # The stored credentials
5
+ class Credentials
6
+ # @param [String] a json string that contains a field 'token'
7
+ def self.write(body)
8
+ json = JSON.parse(body)
9
+ Dir.mkdir(credentials_path, 0o700) unless Dir.exist?(credentials_path)
10
+ File.open(credentials_file, 'w', 0o600) do |file|
11
+ file.write(json.fetch('token'))
12
+ end
13
+ puts 'Signed in.'
14
+ end
15
+
16
+ def self.read
17
+ return IO.readlines(credentials_file, chomp: true).first if File.exist?(credentials_file)
18
+
19
+ puts 'Log in first'
20
+ exit(1)
21
+ end
22
+
23
+ def self.credentials_path
24
+ @credentials_path ||= File.join(Dir.home, '.sdr')
25
+ end
26
+
27
+ def self.credentials_file
28
+ File.join(credentials_path, 'credentials')
29
+ end
30
+ end
31
+ end
@@ -3,10 +3,22 @@
3
3
  module SdrClient
4
4
  # The namespace for the "deposit" command
5
5
  module Deposit
6
- def self.run(label:,
6
+ def self.run(label: nil,
7
7
  type: 'http://cocina.sul.stanford.edu/models/book.jsonld',
8
+ apo:,
9
+ collection:,
10
+ catkey: nil,
11
+ source_id:,
8
12
  url:, files: [])
9
- Process.new(label: label, type: type, url: url, files: files).run
13
+ token = Credentials.read
14
+
15
+ metadata = Request.new(label: label,
16
+ type: type,
17
+ apo: apo,
18
+ collection: collection,
19
+ source_id: source_id,
20
+ catkey: catkey)
21
+ Process.new(metadata: metadata, url: url, token: token, files: files).run
10
22
  end
11
23
  end
12
24
  end
@@ -6,13 +6,18 @@ module SdrClient
6
6
  module Deposit
7
7
  # The process for doing a deposit
8
8
  class Process
9
- BLOB_PATH = '/rails/active_storage/direct_uploads'
9
+ BLOB_PATH = '/v1/direct_uploads'
10
10
  DRO_PATH = '/v1/resources'
11
- def initialize(label:, type:, url:, files: [], logger: Logger.new(STDOUT))
12
- @label = label
13
- @type = type
11
+ # @param [Request] metadata information about the object
12
+ # @param [String] url the server to send to
13
+ # @param [String] token the bearer auth token for the server
14
+ # @param [Array<String>] files a list of file names to upload
15
+ # @param [Logger] logger the logger to use
16
+ def initialize(metadata:, url:, token:, files: [], logger: Logger.new(STDOUT))
14
17
  @files = files
15
18
  @url = url
19
+ @token = token
20
+ @metadata = metadata
16
21
  @logger = logger
17
22
  end
18
23
 
@@ -21,15 +26,13 @@ module SdrClient
21
26
  file_metadata = collect_file_metadata
22
27
  upload_responses = upload_file_metadata(file_metadata)
23
28
  upload_files(upload_responses)
24
- metadata = Request.new(label: label,
25
- type: type,
26
- uploads: upload_responses.values)
27
- upload_metadata(metadata.as_json)
29
+ request = metadata.with_uploads(upload_responses.values)
30
+ upload_metadata(request.as_json)
28
31
  end
29
32
 
30
33
  private
31
34
 
32
- attr_reader :label, :type, :files, :url, :logger
35
+ attr_reader :metadata, :files, :url, :token, :logger
33
36
 
34
37
  def check_files_exist
35
38
  logger.info('checking to see if files exist')
@@ -51,7 +54,7 @@ module SdrClient
51
54
 
52
55
  def direct_upload(metadata_json)
53
56
  logger.info("Starting an upload request: #{metadata_json}")
54
- response = Faraday.post(url + BLOB_PATH, metadata_json, 'Content-Type' => 'application/json')
57
+ response = connection.post(BLOB_PATH, metadata_json, 'Content-Type' => 'application/json')
55
58
  raise "unexpected response: #{response.inspect}" unless response.status == 200
56
59
 
57
60
  logger.info("Response from server: #{response.body}")
@@ -73,10 +76,8 @@ module SdrClient
73
76
 
74
77
  def upload_file(filename:, url:, content_type:, content_length:)
75
78
  logger.info("Uploading `#{filename}' to #{url}")
76
- conn = Faraday.new(url) do |builder|
77
- builder.adapter :net_http
78
- end
79
- upload_response = conn.put(url) do |req|
79
+
80
+ upload_response = connection.put(url) do |req|
80
81
  req.body = File.open(filename)
81
82
  req.headers['Content-Type'] = content_type
82
83
  req.headers['Content-Length'] = content_length.to_s
@@ -85,15 +86,30 @@ module SdrClient
85
86
  raise "unexpected response: #{upload_response.inspect}" unless upload_response.status == 204
86
87
  end
87
88
 
89
+ # @return [Hash<Symbol,String>] the result of the metadata call
88
90
  def upload_metadata(metadata)
89
91
  logger.info("Starting upload metadata: #{metadata}")
90
92
  request_json = JSON.generate(metadata)
91
- response = Faraday.post(url + DRO_PATH, request_json, 'Content-Type' => 'application/json')
92
- raise "unexpected response: #{response.inspect}" unless response.status == 200
93
+ response = connection.post(DRO_PATH, request_json, 'Content-Type' => 'application/json')
94
+ unexpected_response(response) unless response.status == 201
93
95
 
94
96
  logger.info("Response from server: #{response.body}")
95
97
 
96
- JSON.parse(response.body)
98
+ { druid: JSON.parse(response.body)['druid'], background_job: response.headers['Location'] }
99
+ end
100
+
101
+ def unexpected_response(response)
102
+ raise "unexpected response: #{response.inspect}" unless response.status == 400
103
+
104
+ puts "\nThere was an error with your request: #{response.body}"
105
+ exit(1)
106
+ end
107
+
108
+ def connection
109
+ @connection ||= Faraday.new(url: url) do |conn|
110
+ conn.authorization :Bearer, token
111
+ conn.adapter :net_http
112
+ end
97
113
  end
98
114
  end
99
115
  end
@@ -4,33 +4,70 @@ module SdrClient
4
4
  module Deposit
5
5
  # This represents the metadata that we send to the server for doing a deposit
6
6
  class Request
7
- CONTEXT = 'http://cocina.sul.stanford.edu/contexts/cocina-base.jsonld'
8
-
9
7
  # @param [String] label the required object label
10
8
  # @param [String] type (http://cocina.sul.stanford.edu/models/object.jsonld) the required object type.
11
9
  # @param [Array<SdrClient::Deposit::Files::DirectUploadResponse>] uploads the uploaded files to attach.
12
- def initialize(label:,
10
+ def initialize(label: nil,
11
+ apo:,
12
+ collection:,
13
+ source_id:,
14
+ catkey: nil,
13
15
  type: 'http://cocina.sul.stanford.edu/models/object.jsonld',
14
16
  uploads: [])
15
17
  @label = label
16
18
  @type = type
19
+ @source_id = source_id
20
+ @collection = collection
21
+ @catkey = catkey
22
+ @apo = apo
17
23
  @uploads = uploads
18
24
  end
19
25
 
20
26
  def as_json
21
27
  {
22
- "@context": CONTEXT,
23
- "@type": type,
24
- label: label,
25
- structural: {
26
- hasMember: file_sets_as_json
27
- }
28
- }
28
+ access: {},
29
+ type: type,
30
+ administrative: administrative,
31
+ identification: identification,
32
+ structural: structural
33
+ }.tap do |json|
34
+ json[:label] = label if label
35
+ end
36
+ end
37
+
38
+ # @return [Request] a clone of this request with the uploads added
39
+ def with_uploads(uploads)
40
+ Request.new(label: label,
41
+ apo: apo,
42
+ collection: collection,
43
+ source_id: source_id,
44
+ catkey: catkey,
45
+ type: type,
46
+ uploads: uploads)
29
47
  end
30
48
 
31
49
  private
32
50
 
33
- attr_reader :label, :uploads, :type
51
+ attr_reader :label, :uploads, :source_id, :catkey, :apo, :collection, :type
52
+
53
+ def administrative
54
+ {
55
+ hasAdminPolicy: apo
56
+ }
57
+ end
58
+
59
+ def identification
60
+ { sourceId: source_id }.tap do |json|
61
+ json[:catkey] = catkey if catkey
62
+ end
63
+ end
64
+
65
+ def structural
66
+ {
67
+ isMemberOf: collection,
68
+ hasMember: file_sets_as_json
69
+ }
70
+ end
34
71
 
35
72
  # In this case there is a 1-1 mapping between Files and FileSets,
36
73
  # but this doesn't always have to be the case. We could change this in the
@@ -38,8 +75,7 @@ module SdrClient
38
75
  def file_sets_as_json
39
76
  uploads.map do |upload|
40
77
  {
41
- "@context": CONTEXT,
42
- "@type": 'http://cocina.sul.stanford.edu/models/fileset.jsonld',
78
+ "type": 'http://cocina.sul.stanford.edu/models/fileset.jsonld',
43
79
  label: upload.filename,
44
80
  structural: {
45
81
  hasMember: [upload.signed_id]
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SdrClient
4
+ # The namespace for the "login" command
5
+ module Login
6
+ LOGIN_PATH = '/v1/auth/login'
7
+ def self.run(url:, login_service: LoginPrompt)
8
+ request_json = JSON.generate(login_service.run)
9
+ response = Faraday.post(url + LOGIN_PATH, request_json, 'Content-Type' => 'application/json')
10
+ case response.status
11
+ when 200
12
+ Credentials.write(response.body)
13
+ when 400
14
+ puts 'Email address is not a valid email'
15
+ when 401
16
+ puts 'Invalid username or password'
17
+ else
18
+ puts "Status: #{response.status}"
19
+ puts response.body
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'io/console'
4
+
5
+ module SdrClient
6
+ # The namespace for the "login" command
7
+ module LoginPrompt
8
+ def self.run
9
+ print 'Email: '
10
+ email = gets
11
+ email.strip!
12
+ print 'Password: '
13
+ password = $stdin.noecho(&:gets)
14
+ password.strip!
15
+ puts
16
+ { email: email, password: password }
17
+ end
18
+ end
19
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module SdrClient
4
- VERSION = '0.1.0'
4
+ VERSION = '0.2.0'
5
5
  end
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: 0.1.0
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Justin Coyne
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2020-01-09 00:00:00.000000000 Z
11
+ date: 2020-01-13 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: faraday
@@ -146,11 +146,14 @@ files:
146
146
  - exe/sdr
147
147
  - lib/sdr_client.rb
148
148
  - lib/sdr_client/cli.rb
149
+ - lib/sdr_client/credentials.rb
149
150
  - lib/sdr_client/deposit.rb
150
151
  - lib/sdr_client/deposit/files/direct_upload_request.rb
151
152
  - lib/sdr_client/deposit/files/direct_upload_response.rb
152
153
  - lib/sdr_client/deposit/process.rb
153
154
  - lib/sdr_client/deposit/request.rb
155
+ - lib/sdr_client/login.rb
156
+ - lib/sdr_client/login_prompt.rb
154
157
  - lib/sdr_client/version.rb
155
158
  - sdr-client.gemspec
156
159
  homepage: https://github.com/sul-dlss/sdr-client
@@ -174,7 +177,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
174
177
  - !ruby/object:Gem::Version
175
178
  version: '0'
176
179
  requirements: []
177
- rubygems_version: 3.0.6
180
+ rubygems_version: 3.0.3
178
181
  signing_key:
179
182
  specification_version: 4
180
183
  summary: The CLI for https://github.com/sul-dlss/sdr-api