sdr-client 0.3.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 +7 -0
- data/.github/pull_request_template.md +5 -0
- data/.gitignore +14 -0
- data/.rspec +3 -0
- data/.rubocop.yml +15 -0
- data/.rubocop_todo.yml +21 -0
- data/.travis.yml +20 -0
- data/Gemfile +8 -0
- data/LICENSE +14 -0
- data/README.md +25 -0
- data/Rakefile +11 -0
- data/bin/console +15 -0
- data/bin/setup +8 -0
- data/exe/sdr +80 -0
- data/lib/sdr_client.rb +16 -0
- data/lib/sdr_client/cli.rb +18 -0
- data/lib/sdr_client/credentials.rb +31 -0
- data/lib/sdr_client/deposit.rb +31 -0
- data/lib/sdr_client/deposit/file.rb +33 -0
- data/lib/sdr_client/deposit/file_set.rb +31 -0
- data/lib/sdr_client/deposit/files/direct_upload_request.rb +29 -0
- data/lib/sdr_client/deposit/files/direct_upload_response.rb +11 -0
- data/lib/sdr_client/deposit/process.rb +116 -0
- data/lib/sdr_client/deposit/request.rb +81 -0
- data/lib/sdr_client/login.rb +26 -0
- data/lib/sdr_client/login_prompt.rb +19 -0
- data/lib/sdr_client/version.rb +5 -0
- data/sdr-client.gemspec +40 -0
- metadata +200 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 80a6d11a266d546eb1e0763b99b13b18d9f64c558ef21db50f3f509623a4f4bf
|
4
|
+
data.tar.gz: 5ec2344617583af3322f0426e39328aee355ee4c12d7338d8734f529110e8da6
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 3a145ef20a1c42abaed33ab79746778844e922b530a6c48d1b7f6d6f50a0e5a2c3f23e15a804fc5b75b3baeaac6baa4e67b35391965b5bb5da34892b345c9805
|
7
|
+
data.tar.gz: 2600c209c4f35f2ab7a8d7209189c690ec8f06de819666fd7bdd38f63290b997aa87355d54c7a5b5b2e4633ecfd02f75e908815923c2f44116373a999789bf68
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/.rubocop.yml
ADDED
data/.rubocop_todo.yml
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
# This configuration was generated by
|
2
|
+
# `rubocop --auto-gen-config`
|
3
|
+
# on 2020-01-10 17:16:01 -0600 using RuboCop version 0.79.0.
|
4
|
+
# The point is for the user to remove these configuration records
|
5
|
+
# one by one as the offenses are removed from the code base.
|
6
|
+
# Note that changes in the inspected code, or installation of new
|
7
|
+
# versions of RuboCop, may require this file to be generated again.
|
8
|
+
|
9
|
+
# Offense count: 1
|
10
|
+
Metrics/AbcSize:
|
11
|
+
Max: 16
|
12
|
+
|
13
|
+
# Offense count: 1
|
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
|
data/.travis.yml
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
---
|
2
|
+
sudo: false
|
3
|
+
language: ruby
|
4
|
+
cache: bundler
|
5
|
+
rvm:
|
6
|
+
- 2.6.4
|
7
|
+
before_install: gem install bundler -v 2.1.2
|
8
|
+
|
9
|
+
before_script:
|
10
|
+
- curl -L https://codeclimate.com/downloads/test-reporter/test-reporter-latest-linux-amd64 > ./cc-test-reporter
|
11
|
+
- chmod +x ./cc-test-reporter
|
12
|
+
- ./cc-test-reporter before-build
|
13
|
+
after_script:
|
14
|
+
- ./cc-test-reporter after-build --exit-code $TRAVIS_TEST_RESULT
|
15
|
+
|
16
|
+
env:
|
17
|
+
global:
|
18
|
+
- CC_TEST_REPORTER_ID=859fcfe88b00c026d15dce30e838e2299face8088b49fe62bc3a02d1507ce3d5
|
19
|
+
notifications:
|
20
|
+
email: false
|
data/Gemfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
Copyright (c) 2020 by The Board of Trustees of the Leland Stanford
|
2
|
+
Junior University. All rights reserved.
|
3
|
+
|
4
|
+
Licensed under the Apache License, Version 2.0 (the "License"); you
|
5
|
+
may not use this file except in compliance with the License. You
|
6
|
+
may obtain a copy of the License at
|
7
|
+
|
8
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
9
|
+
|
10
|
+
Unless required by applicable law or agreed to in writing, software
|
11
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
12
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
13
|
+
implied. See the License for the specific language governing
|
14
|
+
permissions and limitations under the License.
|
data/README.md
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
[](https://travis-ci.org/sul-dlss/sdr-client)
|
2
|
+
[](https://codeclimate.com/github/sul-dlss/sdr-client/maintainability)
|
3
|
+
[](https://codeclimate.com/github/sul-dlss/sdr-client/test_coverage)
|
4
|
+
[](https://badge.fury.io/rb/sdr-client)
|
5
|
+
|
6
|
+
# Sdr::Client
|
7
|
+
|
8
|
+
This is a CLI for interacting with the Stanford Digital Repository API.
|
9
|
+
The code for the SDR API server is at https://github.com/sul-dlss/sdr-api
|
10
|
+
|
11
|
+
## Usage
|
12
|
+
|
13
|
+
Log in:
|
14
|
+
```
|
15
|
+
sdr --service-url http://sdr-api-server:3000 login
|
16
|
+
```
|
17
|
+
|
18
|
+
|
19
|
+
Deposit a new object:
|
20
|
+
```
|
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
|
25
|
+
```
|
data/Rakefile
ADDED
data/bin/console
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require 'bundler/setup'
|
5
|
+
require 'sdr/client'
|
6
|
+
|
7
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
8
|
+
# with your gem easier. You can also use a different console, if you like.
|
9
|
+
|
10
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
11
|
+
# require "pry"
|
12
|
+
# Pry.start
|
13
|
+
|
14
|
+
require 'irb'
|
15
|
+
IRB.start(__FILE__)
|
data/bin/setup
ADDED
data/exe/sdr
ADDED
@@ -0,0 +1,80 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
$LOAD_PATH.unshift 'lib'
|
5
|
+
require 'optparse'
|
6
|
+
|
7
|
+
options = {}
|
8
|
+
global = OptionParser.new do |opts|
|
9
|
+
opts.on('--service-url URL', 'Connect to the host at this URL') do |url|
|
10
|
+
options[:url] = url
|
11
|
+
end
|
12
|
+
opts.on('-h', '--help', 'Display this screen') do
|
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
|
35
|
+
exit
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
global.order!
|
40
|
+
command = ARGV.shift
|
41
|
+
|
42
|
+
subcommands = {
|
43
|
+
'deposit' => OptionParser.new do |opts|
|
44
|
+
opts.on('--label LABEL', 'The object label') do |label|
|
45
|
+
options[:label] = label
|
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
|
+
|
63
|
+
opts.on('-h', '--help', 'Display this screen') do
|
64
|
+
puts opts
|
65
|
+
exit
|
66
|
+
end
|
67
|
+
end,
|
68
|
+
'login' => OptionParser.new
|
69
|
+
}
|
70
|
+
|
71
|
+
unless subcommands.key?(command)
|
72
|
+
puts "unknown command '#{command}'"
|
73
|
+
exit
|
74
|
+
end
|
75
|
+
|
76
|
+
subcommands[command].order!
|
77
|
+
|
78
|
+
require 'sdr_client'
|
79
|
+
options[:files] = ARGV unless ARGV.empty?
|
80
|
+
SdrClient::CLI.start(command, options)
|
data/lib/sdr_client.rb
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'dry/monads'
|
4
|
+
require 'faraday'
|
5
|
+
|
6
|
+
require 'sdr_client/version'
|
7
|
+
require 'sdr_client/deposit'
|
8
|
+
require 'sdr_client/credentials'
|
9
|
+
require 'sdr_client/login'
|
10
|
+
require 'sdr_client/login_prompt'
|
11
|
+
require 'sdr_client/cli'
|
12
|
+
|
13
|
+
module SdrClient
|
14
|
+
class Error < StandardError; end
|
15
|
+
# Your code goes here...
|
16
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module SdrClient
|
4
|
+
# The command line interface
|
5
|
+
module CLI
|
6
|
+
def self.start(command, options)
|
7
|
+
case command
|
8
|
+
when 'deposit'
|
9
|
+
SdrClient::Deposit.run(options)
|
10
|
+
when 'login'
|
11
|
+
status = SdrClient::Login.run(options)
|
12
|
+
puts status.value if status.failure?
|
13
|
+
else
|
14
|
+
raise "Unknown command #{command}"
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
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
|
@@ -0,0 +1,31 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module SdrClient
|
4
|
+
# The namespace for the "deposit" command
|
5
|
+
module Deposit
|
6
|
+
def self.run(label: nil,
|
7
|
+
type: 'http://cocina.sul.stanford.edu/models/book.jsonld',
|
8
|
+
apo:,
|
9
|
+
collection:,
|
10
|
+
catkey: nil,
|
11
|
+
source_id:,
|
12
|
+
url:, files: [])
|
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
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
require 'json'
|
26
|
+
require 'sdr_client/deposit/files/direct_upload_request'
|
27
|
+
require 'sdr_client/deposit/files/direct_upload_response'
|
28
|
+
require 'sdr_client/deposit/file'
|
29
|
+
require 'sdr_client/deposit/file_set'
|
30
|
+
require 'sdr_client/deposit/request'
|
31
|
+
require 'sdr_client/deposit/process'
|
@@ -0,0 +1,33 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module SdrClient
|
4
|
+
module Deposit
|
5
|
+
# This represents the File metadata that we send to the server for doing a deposit
|
6
|
+
class File
|
7
|
+
def initialize(external_identifier:, label:, filename:, access: 'dark', preserve: false, shelve: false)
|
8
|
+
@external_identifier = external_identifier
|
9
|
+
@label = label
|
10
|
+
@filename = filename
|
11
|
+
@access = access
|
12
|
+
@preserve = preserve
|
13
|
+
@shelve = shelve
|
14
|
+
end
|
15
|
+
|
16
|
+
def as_json
|
17
|
+
{
|
18
|
+
"type": 'http://cocina.sul.stanford.edu/models/file.jsonld',
|
19
|
+
label: @label,
|
20
|
+
filename: @filename,
|
21
|
+
externalIdentifier: @external_identifier,
|
22
|
+
access: {
|
23
|
+
access: @access
|
24
|
+
},
|
25
|
+
administrative: {
|
26
|
+
sdrPreserve: @preserve,
|
27
|
+
shelve: @shelve
|
28
|
+
}
|
29
|
+
}
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module SdrClient
|
4
|
+
module Deposit
|
5
|
+
# This represents the FileSet metadata that we send to the server for doing a deposit
|
6
|
+
class FileSet
|
7
|
+
def initialize(uploads: [], files: [])
|
8
|
+
@files = if !uploads.empty?
|
9
|
+
uploads.map do |upload|
|
10
|
+
File.new(external_identifier: upload.signed_id, label: upload.filename, filename: upload.filename)
|
11
|
+
end
|
12
|
+
else
|
13
|
+
files
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def as_json
|
18
|
+
{
|
19
|
+
"type": 'http://cocina.sul.stanford.edu/models/fileset.jsonld',
|
20
|
+
structural: {
|
21
|
+
hasMember: files.map(&:as_json)
|
22
|
+
}
|
23
|
+
}
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
attr_reader :files
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'digest'
|
4
|
+
|
5
|
+
module SdrClient
|
6
|
+
module Deposit
|
7
|
+
module Files
|
8
|
+
DirectUploadRequest = Struct.new(:checksum, :byte_size, :content_type, :filename, keyword_init: true) do
|
9
|
+
def self.from_file(filename)
|
10
|
+
checksum = Digest::MD5.file(filename).base64digest
|
11
|
+
new(checksum: checksum,
|
12
|
+
byte_size: ::File.size(filename),
|
13
|
+
content_type: 'text/html',
|
14
|
+
filename: ::File.basename(filename))
|
15
|
+
end
|
16
|
+
|
17
|
+
def as_json
|
18
|
+
{
|
19
|
+
blob: { filename: filename, byte_size: byte_size, checksum: checksum, content_type: content_type }
|
20
|
+
}
|
21
|
+
end
|
22
|
+
|
23
|
+
def to_json(*_args)
|
24
|
+
JSON.generate(as_json)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module SdrClient
|
4
|
+
module Deposit
|
5
|
+
module Files
|
6
|
+
DirectUploadResponse = Struct.new(:id, :key, :checksum, :byte_size, :content_type,
|
7
|
+
:filename, :metadata, :created_at, :direct_upload,
|
8
|
+
:signed_id, keyword_init: true)
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
@@ -0,0 +1,116 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'logger'
|
4
|
+
|
5
|
+
module SdrClient
|
6
|
+
module Deposit
|
7
|
+
# The process for doing a deposit
|
8
|
+
class Process
|
9
|
+
BLOB_PATH = '/v1/direct_uploads'
|
10
|
+
DRO_PATH = '/v1/resources'
|
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))
|
17
|
+
@files = files
|
18
|
+
@url = url
|
19
|
+
@token = token
|
20
|
+
@metadata = metadata
|
21
|
+
@logger = logger
|
22
|
+
end
|
23
|
+
|
24
|
+
def run
|
25
|
+
check_files_exist
|
26
|
+
file_metadata = collect_file_metadata
|
27
|
+
upload_responses = upload_file_metadata(file_metadata)
|
28
|
+
upload_files(upload_responses)
|
29
|
+
request = metadata.with_uploads(upload_responses.values)
|
30
|
+
upload_metadata(request.as_json)
|
31
|
+
end
|
32
|
+
|
33
|
+
private
|
34
|
+
|
35
|
+
attr_reader :metadata, :files, :url, :token, :logger
|
36
|
+
|
37
|
+
def check_files_exist
|
38
|
+
logger.info('checking to see if files exist')
|
39
|
+
files.each do |file_name|
|
40
|
+
raise Errno::ENOENT, file_name unless ::File.exist?(file_name)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def collect_file_metadata
|
45
|
+
files.each_with_object({}) do |filename, obj|
|
46
|
+
obj[filename] = Files::DirectUploadRequest.from_file(filename)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
# @param [Hash<String,Files::DirectUploadRequest>] file_metadata the filenames and their upload request
|
51
|
+
def upload_file_metadata(file_metadata)
|
52
|
+
Hash[file_metadata.map { |filename, metadata| [filename, direct_upload(metadata.to_json)] }]
|
53
|
+
end
|
54
|
+
|
55
|
+
def direct_upload(metadata_json)
|
56
|
+
logger.info("Starting an upload request: #{metadata_json}")
|
57
|
+
response = connection.post(BLOB_PATH, metadata_json, 'Content-Type' => 'application/json')
|
58
|
+
raise "unexpected response: #{response.inspect}" unless response.status == 200
|
59
|
+
|
60
|
+
logger.info("Response from server: #{response.body}")
|
61
|
+
|
62
|
+
Files::DirectUploadResponse.new(JSON.parse(response.body))
|
63
|
+
end
|
64
|
+
|
65
|
+
# @param [Hash<String,Files::DirectUploadResponse>] upload_responses the filenames and their upload response
|
66
|
+
def upload_files(upload_responses)
|
67
|
+
upload_responses.each do |filename, response|
|
68
|
+
upload_file(filename: filename,
|
69
|
+
url: response.direct_upload.fetch('url'),
|
70
|
+
content_type: response.content_type,
|
71
|
+
content_length: response.byte_size)
|
72
|
+
|
73
|
+
logger.info('Upload complete')
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
def upload_file(filename:, url:, content_type:, content_length:)
|
78
|
+
logger.info("Uploading `#{filename}' to #{url}")
|
79
|
+
|
80
|
+
upload_response = connection.put(url) do |req|
|
81
|
+
req.body = ::File.open(filename)
|
82
|
+
req.headers['Content-Type'] = content_type
|
83
|
+
req.headers['Content-Length'] = content_length.to_s
|
84
|
+
end
|
85
|
+
|
86
|
+
raise "unexpected response: #{upload_response.inspect}" unless upload_response.status == 204
|
87
|
+
end
|
88
|
+
|
89
|
+
# @return [Hash<Symbol,String>] the result of the metadata call
|
90
|
+
def upload_metadata(metadata)
|
91
|
+
logger.info("Starting upload metadata: #{metadata}")
|
92
|
+
request_json = JSON.generate(metadata)
|
93
|
+
response = connection.post(DRO_PATH, request_json, 'Content-Type' => 'application/json')
|
94
|
+
unexpected_response(response) unless response.status == 201
|
95
|
+
|
96
|
+
logger.info("Response from server: #{response.body}")
|
97
|
+
|
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
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
@@ -0,0 +1,81 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module SdrClient
|
4
|
+
module Deposit
|
5
|
+
# This represents the metadata that we send to the server for doing a deposit
|
6
|
+
class Request
|
7
|
+
# @param [String] label the required object label
|
8
|
+
# @param [String] type (http://cocina.sul.stanford.edu/models/object.jsonld) the required object type.
|
9
|
+
# @param [Array<FileSet>] file_sets the file sets to attach.
|
10
|
+
def initialize(label: nil,
|
11
|
+
apo:,
|
12
|
+
collection:,
|
13
|
+
source_id:,
|
14
|
+
catkey: nil,
|
15
|
+
type: 'http://cocina.sul.stanford.edu/models/object.jsonld',
|
16
|
+
file_sets: [])
|
17
|
+
@label = label
|
18
|
+
@type = type
|
19
|
+
@source_id = source_id
|
20
|
+
@collection = collection
|
21
|
+
@catkey = catkey
|
22
|
+
@apo = apo
|
23
|
+
@file_sets = file_sets
|
24
|
+
end
|
25
|
+
|
26
|
+
def as_json
|
27
|
+
{
|
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
|
+
# @param [Array<SdrClient::Deposit::Files::DirectUploadResponse>] uploads the uploaded files to attach.
|
39
|
+
# @return [Request] a clone of this request with the uploads added
|
40
|
+
def with_uploads(uploads)
|
41
|
+
file_sets = uploads.map { |upload| FileSet.new(uploads: [upload]) }
|
42
|
+
|
43
|
+
Request.new(label: label,
|
44
|
+
apo: apo,
|
45
|
+
collection: collection,
|
46
|
+
source_id: source_id,
|
47
|
+
catkey: catkey,
|
48
|
+
type: type,
|
49
|
+
file_sets: file_sets)
|
50
|
+
end
|
51
|
+
|
52
|
+
# In this case there is a 1-1 mapping between Files and FileSets,
|
53
|
+
# but this doesn't always have to be the case. We could change this in the
|
54
|
+
# future so that we have one FileSet that has an Image and its OCR file.
|
55
|
+
def add_uploads_each_as_resource(uploads); end
|
56
|
+
|
57
|
+
private
|
58
|
+
|
59
|
+
attr_reader :label, :file_sets, :source_id, :catkey, :apo, :collection, :type
|
60
|
+
|
61
|
+
def administrative
|
62
|
+
{
|
63
|
+
hasAdminPolicy: apo
|
64
|
+
}
|
65
|
+
end
|
66
|
+
|
67
|
+
def identification
|
68
|
+
{ sourceId: source_id }.tap do |json|
|
69
|
+
json[:catkey] = catkey if catkey
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
def structural
|
74
|
+
{
|
75
|
+
isMemberOf: collection,
|
76
|
+
hasMember: file_sets.map(&:as_json)
|
77
|
+
}
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
@@ -0,0 +1,26 @@
|
|
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
|
+
extend Dry::Monads[:result]
|
8
|
+
|
9
|
+
# @return [Result] the status of the call
|
10
|
+
def self.run(url:, login_service: LoginPrompt)
|
11
|
+
request_json = JSON.generate(login_service.run)
|
12
|
+
response = Faraday.post(url + LOGIN_PATH, request_json, 'Content-Type' => 'application/json')
|
13
|
+
case response.status
|
14
|
+
when 200
|
15
|
+
Credentials.write(response.body)
|
16
|
+
Success()
|
17
|
+
when 400
|
18
|
+
Failure('Email address is not a valid email')
|
19
|
+
when 401
|
20
|
+
Failure('Invalid username or password')
|
21
|
+
else
|
22
|
+
Failure("Status: #{response.status}\n#{response.body}")
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
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
|
data/sdr-client.gemspec
ADDED
@@ -0,0 +1,40 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
lib = File.expand_path('lib', __dir__)
|
4
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
5
|
+
require 'sdr_client/version'
|
6
|
+
|
7
|
+
Gem::Specification.new do |spec|
|
8
|
+
spec.name = 'sdr-client'
|
9
|
+
spec.version = SdrClient::VERSION
|
10
|
+
spec.authors = ['Justin Coyne']
|
11
|
+
spec.email = ['jcoyne@justincoyne.com']
|
12
|
+
|
13
|
+
spec.summary = 'The CLI for https://github.com/sul-dlss/sdr-api'
|
14
|
+
spec.description = 'This provides a way to deposit repository objects into the Stanford Digital Repository'
|
15
|
+
spec.homepage = 'https://github.com/sul-dlss/sdr-client'
|
16
|
+
|
17
|
+
spec.metadata['homepage_uri'] = spec.homepage
|
18
|
+
spec.metadata['source_code_uri'] = 'https://github.com/sul-dlss/sdr-client'
|
19
|
+
spec.metadata['changelog_uri'] = 'https://github.com/sul-dlss/sdr-client/releases'
|
20
|
+
|
21
|
+
# Specify which files should be added to the gem when it is released.
|
22
|
+
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
23
|
+
spec.files = Dir.chdir(File.expand_path(__dir__)) do
|
24
|
+
`git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
25
|
+
end
|
26
|
+
spec.bindir = 'exe'
|
27
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
28
|
+
spec.require_paths = ['lib']
|
29
|
+
|
30
|
+
spec.add_dependency 'dry-monads'
|
31
|
+
spec.add_dependency 'faraday', '>= 0.16'
|
32
|
+
|
33
|
+
spec.add_development_dependency 'bundler', '~> 2.0'
|
34
|
+
spec.add_development_dependency 'rake', '~> 13.0'
|
35
|
+
spec.add_development_dependency 'rspec', '~> 3.0'
|
36
|
+
spec.add_development_dependency 'rubocop', '~> 0.79.0'
|
37
|
+
spec.add_development_dependency 'rubocop-rspec', '~> 1.37.1'
|
38
|
+
spec.add_development_dependency 'simplecov'
|
39
|
+
spec.add_development_dependency 'webmock', '~> 3.7'
|
40
|
+
end
|
metadata
ADDED
@@ -0,0 +1,200 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: sdr-client
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.3.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Justin Coyne
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2020-01-23 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: dry-monads
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: faraday
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0.16'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0.16'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: bundler
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '2.0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '2.0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: rake
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '13.0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '13.0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: rspec
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - "~>"
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '3.0'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - "~>"
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '3.0'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: rubocop
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - "~>"
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: 0.79.0
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - "~>"
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: 0.79.0
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: rubocop-rspec
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - "~>"
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: 1.37.1
|
104
|
+
type: :development
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - "~>"
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: 1.37.1
|
111
|
+
- !ruby/object:Gem::Dependency
|
112
|
+
name: simplecov
|
113
|
+
requirement: !ruby/object:Gem::Requirement
|
114
|
+
requirements:
|
115
|
+
- - ">="
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: '0'
|
118
|
+
type: :development
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
requirements:
|
122
|
+
- - ">="
|
123
|
+
- !ruby/object:Gem::Version
|
124
|
+
version: '0'
|
125
|
+
- !ruby/object:Gem::Dependency
|
126
|
+
name: webmock
|
127
|
+
requirement: !ruby/object:Gem::Requirement
|
128
|
+
requirements:
|
129
|
+
- - "~>"
|
130
|
+
- !ruby/object:Gem::Version
|
131
|
+
version: '3.7'
|
132
|
+
type: :development
|
133
|
+
prerelease: false
|
134
|
+
version_requirements: !ruby/object:Gem::Requirement
|
135
|
+
requirements:
|
136
|
+
- - "~>"
|
137
|
+
- !ruby/object:Gem::Version
|
138
|
+
version: '3.7'
|
139
|
+
description: This provides a way to deposit repository objects into the Stanford Digital
|
140
|
+
Repository
|
141
|
+
email:
|
142
|
+
- jcoyne@justincoyne.com
|
143
|
+
executables:
|
144
|
+
- sdr
|
145
|
+
extensions: []
|
146
|
+
extra_rdoc_files: []
|
147
|
+
files:
|
148
|
+
- ".github/pull_request_template.md"
|
149
|
+
- ".gitignore"
|
150
|
+
- ".rspec"
|
151
|
+
- ".rubocop.yml"
|
152
|
+
- ".rubocop_todo.yml"
|
153
|
+
- ".travis.yml"
|
154
|
+
- Gemfile
|
155
|
+
- LICENSE
|
156
|
+
- README.md
|
157
|
+
- Rakefile
|
158
|
+
- bin/console
|
159
|
+
- bin/setup
|
160
|
+
- exe/sdr
|
161
|
+
- lib/sdr_client.rb
|
162
|
+
- lib/sdr_client/cli.rb
|
163
|
+
- lib/sdr_client/credentials.rb
|
164
|
+
- lib/sdr_client/deposit.rb
|
165
|
+
- lib/sdr_client/deposit/file.rb
|
166
|
+
- lib/sdr_client/deposit/file_set.rb
|
167
|
+
- lib/sdr_client/deposit/files/direct_upload_request.rb
|
168
|
+
- lib/sdr_client/deposit/files/direct_upload_response.rb
|
169
|
+
- lib/sdr_client/deposit/process.rb
|
170
|
+
- lib/sdr_client/deposit/request.rb
|
171
|
+
- lib/sdr_client/login.rb
|
172
|
+
- lib/sdr_client/login_prompt.rb
|
173
|
+
- lib/sdr_client/version.rb
|
174
|
+
- sdr-client.gemspec
|
175
|
+
homepage: https://github.com/sul-dlss/sdr-client
|
176
|
+
licenses: []
|
177
|
+
metadata:
|
178
|
+
homepage_uri: https://github.com/sul-dlss/sdr-client
|
179
|
+
source_code_uri: https://github.com/sul-dlss/sdr-client
|
180
|
+
changelog_uri: https://github.com/sul-dlss/sdr-client/releases
|
181
|
+
post_install_message:
|
182
|
+
rdoc_options: []
|
183
|
+
require_paths:
|
184
|
+
- lib
|
185
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
186
|
+
requirements:
|
187
|
+
- - ">="
|
188
|
+
- !ruby/object:Gem::Version
|
189
|
+
version: '0'
|
190
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
191
|
+
requirements:
|
192
|
+
- - ">="
|
193
|
+
- !ruby/object:Gem::Version
|
194
|
+
version: '0'
|
195
|
+
requirements: []
|
196
|
+
rubygems_version: 3.0.3
|
197
|
+
signing_key:
|
198
|
+
specification_version: 4
|
199
|
+
summary: The CLI for https://github.com/sul-dlss/sdr-api
|
200
|
+
test_files: []
|