scholarsphere-client 0.1.1 → 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: 84b082e1c474c137dd8202b5ae6666eb73938f9b1bd806f053308a715272c28f
4
- data.tar.gz: c94c0532b48c3b0f9f3b9918f01275a572f75a8ff3f7d5112837b7fd78e474ad
3
+ metadata.gz: 8ab004f36213538c0e74821396c0499a52f08a24c8a8d7f450b3c224a0b71a8d
4
+ data.tar.gz: 84788832717be95ff66b8def2433633fc41a82aa85e4b204d1750814b8fa4bb4
5
5
  SHA512:
6
- metadata.gz: 517ddf088ccad9da2fbdae7b0d674c3017c5085e850d568f23fa0eaff95899e4bfe728de6f144f2480d09d65025801c22e0929eeb60bf3548c7613e041b40cb8
7
- data.tar.gz: a7b2df6784f547a3ceb9b9de3e82497398e76b794dfe0fe22cdbbc6214e7a068940b8df7957195b61a417f9f8d4f4b1dd115693630467661b821907675b32b94
6
+ metadata.gz: 45c9c56b6f2db7ed715a595b9d5533ddba125b0a63d7b2d694669105c50b65e19c66b70814add3921b1d1fc83ef1ebc6592488641206c1eef1ef21c331d9649c
7
+ data.tar.gz: c7d927e3d027ecdf2b204f350b0758b353d0b161e285b795111174deca41951c2ca8f5642cc6461852b831cd6a1c890febb5083730b08b3360358fbbf902c574
@@ -0,0 +1,40 @@
1
+ version: 2.1
2
+ orbs:
3
+ ruby: circleci/ruby@0.1.2
4
+
5
+ jobs:
6
+ build:
7
+ docker:
8
+ - image: circleci/ruby:2.6.3-stretch-node
9
+ executor: ruby/default
10
+ steps:
11
+ - checkout
12
+ - run:
13
+ name: "Install Dependencides"
14
+ command: gem install bundler:2.1.4 && bundle install
15
+ - run:
16
+ name: "Rubocop"
17
+ command: bundle exec rubocop
18
+ - run:
19
+ name: "RSpec"
20
+ command: bundle exec rspec
21
+ environment:
22
+ SS4_ENDPOINT: "https://scholarsphere.test/api/v1"
23
+ - run:
24
+ name: "Copy VCR Logs"
25
+ when: on_fail
26
+ command: cp vcr.log /tmp/vcr.log
27
+ - run:
28
+ name: "Upload Coverage"
29
+ when: on_success
30
+ command: |
31
+ wget -q https://codeclimate.com/downloads/test-reporter/test-reporter-latest-linux-amd64 -O cc-test-reporter
32
+ chmod 755 cc-test-reporter
33
+ export TAG=${CIRCLE_SHA1}
34
+ export GIT_COMMIT_SHA=$CIRCLE_SHA1
35
+ export GIT_BRANCH=$CIRCLE_BRANCH
36
+ export GIT_COMMITED_AT=$(git log -1 --date=short --pretty=format:%ct)
37
+ ./cc-test-reporter after-build -d
38
+ - store_artifacts:
39
+ path: /tmp/vcr.log
40
+ destination: VCR
data/.gitignore CHANGED
@@ -12,4 +12,4 @@
12
12
 
13
13
  # Ignore application configuration
14
14
  /config/scholarsphere-client.yml
15
- Gemfile.lock
15
+ vcr.log
data/.rubocop.yml CHANGED
@@ -11,3 +11,6 @@ Metrics/BlockLength:
11
11
  Gemspec/RequiredRubyVersion:
12
12
  Exclude:
13
13
  - 'scholarsphere-client.gemspec'
14
+
15
+ RSpec/MultipleMemoizedHelpers:
16
+ Enabled: false
data/Gemfile.lock ADDED
@@ -0,0 +1,204 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ scholarsphere-client (0.2.0)
5
+ aws-sdk-s3 (~> 1.49)
6
+ faraday (> 0.12)
7
+ marcel (~> 0.3)
8
+
9
+ GEM
10
+ remote: https://rubygems.org/
11
+ specs:
12
+ actionview (6.1.1)
13
+ activesupport (= 6.1.1)
14
+ builder (~> 3.1)
15
+ erubi (~> 1.4)
16
+ rails-dom-testing (~> 2.0)
17
+ rails-html-sanitizer (~> 1.1, >= 1.2.0)
18
+ activesupport (6.1.1)
19
+ concurrent-ruby (~> 1.0, >= 1.0.2)
20
+ i18n (>= 1.6, < 2)
21
+ minitest (>= 5.1)
22
+ tzinfo (~> 2.0)
23
+ zeitwerk (~> 2.3)
24
+ addressable (2.7.0)
25
+ public_suffix (>= 2.0.2, < 5.0)
26
+ ast (2.4.1)
27
+ aws-eventstream (1.1.0)
28
+ aws-partitions (1.322.0)
29
+ aws-sdk-core (3.97.0)
30
+ aws-eventstream (~> 1, >= 1.0.2)
31
+ aws-partitions (~> 1, >= 1.239.0)
32
+ aws-sigv4 (~> 1.1)
33
+ jmespath (~> 1.0)
34
+ aws-sdk-kms (1.32.0)
35
+ aws-sdk-core (~> 3, >= 3.71.0)
36
+ aws-sigv4 (~> 1.1)
37
+ aws-sdk-s3 (1.67.0)
38
+ aws-sdk-core (~> 3, >= 3.96.1)
39
+ aws-sdk-kms (~> 1)
40
+ aws-sigv4 (~> 1.1)
41
+ aws-sigv4 (1.1.4)
42
+ aws-eventstream (~> 1.0, >= 1.0.2)
43
+ better_html (1.0.15)
44
+ actionview (>= 4.0)
45
+ activesupport (>= 4.0)
46
+ ast (~> 2.0)
47
+ erubi (~> 1.4)
48
+ html_tokenizer (~> 0.0.6)
49
+ parser (>= 2.4)
50
+ smart_properties
51
+ builder (3.2.4)
52
+ byebug (11.1.3)
53
+ coderay (1.1.3)
54
+ colorize (0.8.1)
55
+ concurrent-ruby (1.1.7)
56
+ crack (0.4.5)
57
+ rexml
58
+ crass (1.0.6)
59
+ diff-lcs (1.3)
60
+ docile (1.3.5)
61
+ erb_lint (0.0.37)
62
+ activesupport
63
+ better_html (~> 1.0.7)
64
+ html_tokenizer
65
+ parser (>= 2.7.1.4)
66
+ rainbow
67
+ rubocop
68
+ smart_properties
69
+ erubi (1.10.0)
70
+ faraday (1.0.1)
71
+ multipart-post (>= 1.2, < 3)
72
+ ffi (1.14.2)
73
+ hashdiff (1.0.1)
74
+ html_tokenizer (0.0.7)
75
+ i18n (1.8.7)
76
+ concurrent-ruby (~> 1.0)
77
+ jmespath (1.4.0)
78
+ json (2.5.1)
79
+ loofah (2.8.0)
80
+ crass (~> 1.0.2)
81
+ nokogiri (>= 1.5.9)
82
+ marcel (0.3.3)
83
+ mimemagic (~> 0.3.2)
84
+ method_source (1.0.0)
85
+ mimemagic (0.3.5)
86
+ mini_portile2 (2.5.0)
87
+ minitest (5.14.3)
88
+ multipart-post (2.1.1)
89
+ niftany (0.8.0)
90
+ colorize (~> 0.8.1)
91
+ erb_lint (~> 0.0.22)
92
+ rubocop (~> 0.79)
93
+ rubocop-performance (~> 1.1)
94
+ rubocop-rails (~> 2.3)
95
+ rubocop-rspec (~> 1.3)
96
+ scss_lint (~> 0.55)
97
+ nokogiri (1.11.1)
98
+ mini_portile2 (~> 2.5.0)
99
+ racc (~> 1.4)
100
+ parallel (1.20.1)
101
+ parser (3.0.0.0)
102
+ ast (~> 2.4.1)
103
+ pry (0.13.1)
104
+ coderay (~> 1.1)
105
+ method_source (~> 1.0)
106
+ pry-byebug (3.9.0)
107
+ byebug (~> 11.0)
108
+ pry (~> 0.13.0)
109
+ public_suffix (4.0.6)
110
+ racc (1.5.2)
111
+ rack (2.2.3)
112
+ rails-dom-testing (2.0.3)
113
+ activesupport (>= 4.2.0)
114
+ nokogiri (>= 1.6)
115
+ rails-html-sanitizer (1.3.0)
116
+ loofah (~> 2.3)
117
+ rainbow (3.0.0)
118
+ rake (13.0.1)
119
+ rb-fsevent (0.10.4)
120
+ rb-inotify (0.10.1)
121
+ ffi (~> 1.0)
122
+ regexp_parser (2.0.3)
123
+ rexml (3.2.4)
124
+ rspec (3.9.0)
125
+ rspec-core (~> 3.9.0)
126
+ rspec-expectations (~> 3.9.0)
127
+ rspec-mocks (~> 3.9.0)
128
+ rspec-core (3.9.2)
129
+ rspec-support (~> 3.9.3)
130
+ rspec-expectations (3.9.2)
131
+ diff-lcs (>= 1.2.0, < 2.0)
132
+ rspec-support (~> 3.9.0)
133
+ rspec-its (1.3.0)
134
+ rspec-core (>= 3.0.0)
135
+ rspec-expectations (>= 3.0.0)
136
+ rspec-mocks (3.9.1)
137
+ diff-lcs (>= 1.2.0, < 2.0)
138
+ rspec-support (~> 3.9.0)
139
+ rspec-support (3.9.3)
140
+ rubocop (0.93.1)
141
+ parallel (~> 1.10)
142
+ parser (>= 2.7.1.5)
143
+ rainbow (>= 2.2.2, < 4.0)
144
+ regexp_parser (>= 1.8)
145
+ rexml
146
+ rubocop-ast (>= 0.6.0)
147
+ ruby-progressbar (~> 1.7)
148
+ unicode-display_width (>= 1.4.0, < 2.0)
149
+ rubocop-ast (1.4.0)
150
+ parser (>= 2.7.1.5)
151
+ rubocop-performance (1.9.2)
152
+ rubocop (>= 0.90.0, < 2.0)
153
+ rubocop-ast (>= 0.4.0)
154
+ rubocop-rails (2.9.1)
155
+ activesupport (>= 4.2.0)
156
+ rack (>= 1.1)
157
+ rubocop (>= 0.90.0, < 2.0)
158
+ rubocop-rspec (1.44.1)
159
+ rubocop (~> 0.87)
160
+ rubocop-ast (>= 0.7.1)
161
+ ruby-progressbar (1.11.0)
162
+ sass (3.7.4)
163
+ sass-listen (~> 4.0.0)
164
+ sass-listen (4.0.0)
165
+ rb-fsevent (~> 0.9, >= 0.9.4)
166
+ rb-inotify (~> 0.9, >= 0.9.7)
167
+ scss_lint (0.59.0)
168
+ sass (~> 3.5, >= 3.5.5)
169
+ simplecov (0.17.1)
170
+ docile (~> 1.1)
171
+ json (>= 1.8, < 3)
172
+ simplecov-html (~> 0.10.0)
173
+ simplecov-html (0.10.2)
174
+ smart_properties (1.15.0)
175
+ tzinfo (2.0.4)
176
+ concurrent-ruby (~> 1.0)
177
+ unicode-display_width (1.7.0)
178
+ vcr (6.0.0)
179
+ webmock (3.11.1)
180
+ addressable (>= 2.3.6)
181
+ crack (>= 0.3.2)
182
+ hashdiff (>= 0.4.0, < 2.0.0)
183
+ yard (0.9.26)
184
+ zeitwerk (2.4.2)
185
+
186
+ PLATFORMS
187
+ ruby
188
+
189
+ DEPENDENCIES
190
+ bundler (~> 2.0)
191
+ niftany (~> 0.6)
192
+ pry (~> 0.12)
193
+ pry-byebug (~> 3.9)
194
+ rake (>= 12.3.3)
195
+ rspec (~> 3.0)
196
+ rspec-its (~> 1.3)
197
+ scholarsphere-client!
198
+ simplecov (< 0.18)
199
+ vcr (~> 6.0)
200
+ webmock (~> 3.11)
201
+ yard (< 1.0)
202
+
203
+ BUNDLED WITH
204
+ 2.1.4
data/README.md CHANGED
@@ -20,8 +20,60 @@ Or install it yourself as:
20
20
 
21
21
  ## Usage
22
22
 
23
- You can read the [ruby docs](https://www.rubydoc.info/gems/scholarsphere-client) for the latest features.
23
+ ### Authentication
24
24
 
25
- **Warning!**
25
+ Obtain an api key, and save it to `config/scholarsphere-client.yml`
26
26
 
27
- This is not yet in 1.0 status and features will change without the customary deprecation warnings.
27
+ SS4_ENDPOINT: "http://scholarsphere/api/v1"
28
+ SS_CLIENT_KEY: "[key]"
29
+
30
+ If you are using a testing instance, you'll need to disable ssl verification:
31
+
32
+ SS_CLIENT_SSL: "false"
33
+
34
+ ### Ingesting
35
+
36
+ To publish a work:
37
+
38
+ metadata = {
39
+ title: "My Awesome Work",
40
+ creators_attributes: [
41
+ {
42
+ display_name: 'Dr. Pat Researcher',
43
+ actor_attributes: {
44
+ psu_id: 'pxr123',
45
+ surname: 'Researcher',
46
+ given_name: 'Pat',
47
+ email: 'pxr123@psu.edu'
48
+ }
49
+ }
50
+ ]
51
+ }
52
+
53
+ files = [ File.new('path/to/file') ]
54
+
55
+ depositor = {
56
+ psu_id: 'pxr123',
57
+ surname: 'Researcher',
58
+ given_name: 'Pat',
59
+ email: 'pxr123@psu.edu'
60
+ }
61
+
62
+ ingest = Scholarsphere::Client::Ingest.new(
63
+ metadata: metadata,
64
+ files: files,
65
+ depositor: depositor
66
+ )
67
+
68
+ response = ingest.publish
69
+
70
+ puts response.body
71
+
72
+ {
73
+ "message": "Work was successfully created",
74
+ "url": "/resources/0797e99c-7d4f-4e05-8bf6-86aea1029a6a"
75
+ }
76
+
77
+ ## Documentation
78
+
79
+ You can read the [ruby docs](https://www.rubydoc.info/github/psu-stewardship/scholarsphere-client/main) for the latest features.
@@ -7,6 +7,7 @@ require 'scholarsphere/s3'
7
7
  require 'scholarsphere/client/config'
8
8
  require 'scholarsphere/client/ingest'
9
9
  require 'scholarsphere/client/collection'
10
+ require 'scholarsphere/client/upload'
10
11
  require 'scholarsphere/client/version'
11
12
 
12
13
  module Scholarsphere
@@ -16,6 +17,7 @@ module Scholarsphere
16
17
  Config.load_defaults
17
18
 
18
19
  class << self
20
+ # @return [Faraday::Connection] A cached connection to the Scholarsphere API with the provided credentials.
19
21
  def connection
20
22
  @connection ||= Faraday::Connection.new(
21
23
  url: ENV['SS4_ENDPOINT'],
@@ -27,10 +29,18 @@ module Scholarsphere
27
29
  )
28
30
  end
29
31
 
32
+ # @return [nil] Resets the client connection when needed.
33
+ def reset
34
+ @connection = nil
35
+ end
36
+
37
+ # @return [TrueClass, FalseClass] If set to 'false', Faraday will not verify the SSL certificate. This is mostly
38
+ # used for testing. Default is 'true'.
30
39
  def verify_ssl?
31
40
  ENV['SS_CLIENT_SSL'] != 'false'
32
41
  end
33
42
 
43
+ # @return [String] Alphanumeric API key that grants access to the API.
34
44
  def api_key
35
45
  ENV['SS_CLIENT_KEY']
36
46
  end
@@ -7,11 +7,16 @@ module Scholarsphere
7
7
  # Loads the yaml configuration file for the client. The default location is `config/scholarsphere-client.yml` and
8
8
  # the client will load this file automatically whenever it is invoked.
9
9
  #
10
- # The configuration file should contain the endpoint of the Scholarsphere API.
10
+ # The configuration file should contain the endpoint of the Scholarsphere API and the API key.
11
11
  #
12
- # # Example
12
+ # ## Required
13
13
  #
14
- # SS4_ENDPOINT: "http://scholarsphere.psu.edu/api/v1"
14
+ # SS4_ENDPOINT: "https://scholarsphere.psu.edu/api/v1"
15
+ # SS_CLIENT_KEY: "[key]"
16
+ #
17
+ # ## Optional
18
+ #
19
+ # SS_CLIENT_SSL: "false"
15
20
  #
16
21
  class Config
17
22
  # @private
@@ -2,12 +2,98 @@
2
2
 
3
3
  module Scholarsphere
4
4
  module Client
5
+ ##
6
+ #
7
+ # Uploads a complete work into Scholarsphere. If successful, the work will be published and made
8
+ # publicly available.
9
+ #
10
+ # ## Publishing a New Work
11
+ #
12
+ # The most common use case is uploading a single file with the required metadata:
13
+ #
14
+ # ingest = Scholarsphere::Client::Ingest.new(
15
+ # metadata: metadata,
16
+ # files: files,
17
+ # depositor: depositor
18
+ # )
19
+ # response = ingest.publish
20
+ #
21
+ # If the response is successful, the application returns 200 OK with JSON:
22
+ #
23
+ # puts response.body
24
+ # {
25
+ # "message": "Work was successfully created",
26
+ # "url": "/resources/0797e99c-7d4f-4e05-8bf6-86aea1029a6a"
27
+ # }
28
+ #
29
+ # Other possible outcomes include:
30
+ #
31
+ # * work was created, but not successfully published because of missing attributes (201 Created)
32
+ # * work could not be created due to insufficient parameters (422 Unprocessable Entity)
33
+ # * there was an error with the application (500 Internal Server Error)
34
+ #
35
+ # If possible, the response will include additional information about which errors occurred and which attributes are
36
+ # required or are incorrect.
37
+ #
38
+ # ## Metadata
39
+ #
40
+ # A hash of descriptive metadata about the work. The minimal amount required in order to publish would be:
41
+ #
42
+ # {
43
+ # title: "[descriptive title of the work]",
44
+ # creators_attributes: [
45
+ # {
46
+ # display_name: '[Penn State Person]',
47
+ # actor_attributes: {
48
+ # psu_id: 'abc123',
49
+ # surname: '[family name]',
50
+ # given_name: '[given name]',
51
+ # email: 'abc123@psu.edu'
52
+ # }
53
+ # }
54
+ # ]
55
+ # }
56
+ #
57
+ # For a complete listing of all the possible metadata values, see the OpenAPI docs for
58
+ # Scholarsphere.
59
+ #
60
+ # ## Files
61
+ #
62
+ # [
63
+ # Pathname.new('MyPaper.pdf'),
64
+ # Pathname.new('dataset.dat')
65
+ # ]
66
+ #
67
+ # One or more files are required in order for the work be published. The simplest method is an array of `IO`
68
+ # objects. The client then uploads them into S3.
69
+ #
70
+ # *Note: All filenames must have an extension!*
71
+ #
72
+ # ## Depositor
73
+ #
74
+ # {
75
+ # psu_id: '[Penn State Person]',
76
+ # surname: '[family name]',
77
+ # given_name: '[given name]',
78
+ # email: 'abc123@psu.edu'
79
+ # }
80
+ #
81
+ # Currently, the person identified as the depositor must have an active access account at Penn State. In most cases,
82
+ # the depositor will be the same person as the creator specified in the metadata; although, this is not always the
83
+ # case. The depositor may be someone who is uploading on behalf of the creator and is not affiliated with the
84
+ # creation of the work. The depositor is *not* the client itself, either. The client is identified separately via
85
+ # the API token.
86
+ #
87
+ # Note that the fields are the same for both `creators_attributes` and `depositor`, so if the depositor is the same
88
+ # as the creator, all the values will need to be duplicated.
89
+ #
90
+ #
5
91
  class Ingest
6
92
  attr_reader :content, :metadata, :depositor, :permissions
7
93
 
8
94
  # @param metadata [Hash] Metadata attributes
9
95
  # @param files [Array<File,IO,Pathnme>,Hash] An array of File or IO objects, or a hash with a :file param
10
- # @param depositor [String] The access ID of the depositor
96
+ # @param depositor [Hash] The name and access id of the depositor
11
97
  # @param permissions [Hash] (optional) Additional permissions to apply to the resource
12
98
  def initialize(metadata:, files:, depositor:, permissions: {})
13
99
  @content = build_content_hash(files)
@@ -16,6 +102,7 @@ module Scholarsphere
16
102
  @permissions = permissions
17
103
  end
18
104
 
105
+ # @return [Faraday::Response] The response from the Scholarsphere application.
19
106
  def publish
20
107
  upload_files
21
108
  connection.post do |req|
@@ -29,24 +116,20 @@ module Scholarsphere
29
116
  def build_content_hash(files)
30
117
  files.map do |file|
31
118
  if file.is_a?(Hash)
32
- file.merge(file: S3::UploadedFile.new(file.fetch(:file)))
119
+ file.merge(file: S3::UploadedFile.new(source: file.fetch(:file)))
33
120
  else
34
- { file: S3::UploadedFile.new(file) }
121
+ { file: S3::UploadedFile.new(source: file) }
35
122
  end
36
123
  end
37
124
  end
38
125
 
39
126
  def upload_files
40
127
  content.map do |file_parameters|
41
- uploader.upload(file_parameters.fetch(:file))
42
- file_parameters[:file] = file_parameters[:file].to_shrine.to_json
128
+ S3::Uploader.new(file: file_parameters.fetch(:file)).upload
129
+ file_parameters[:file] = file_parameters[:file].to_param.to_json
43
130
  end
44
131
  end
45
132
 
46
- def uploader
47
- @uploader ||= S3::Uploader.new
48
- end
49
-
50
133
  def connection
51
134
  Scholarsphere::Client.connection
52
135
  end
@@ -0,0 +1,52 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Scholarsphere
4
+ module Client
5
+ ##
6
+ #
7
+ # Requests a pre-signed url, id, and prefix from Scholarsphere for uploading a given file. In order to generate a
8
+ # correct path, the file's extension name is required for the request. The url is used to upload the file's binary
9
+ # content into Scholarsphere, while the id and key are used when adding the file to a work.
10
+ #
11
+ class Upload
12
+ # @param extname [String] Extension of the file to be uploaded, without the period, such as 'pdf'
13
+ def initialize(extname:)
14
+ @extname = extname
15
+ end
16
+
17
+ # @return [String] Prefix where the file is stored in the S3 bucket.
18
+ def prefix
19
+ data['prefix']
20
+ end
21
+
22
+ # @return [String] URL for uploading the file into AWS.
23
+ def url
24
+ data['url']
25
+ end
26
+
27
+ # @return [String] A unique identifier for the file which will serve as its name in S3.
28
+ def id
29
+ data['id']
30
+ end
31
+
32
+ private
33
+
34
+ attr_reader :extname
35
+
36
+ def request
37
+ @request ||= Scholarsphere::Client.connection.post do |req|
38
+ req.url 'uploads'
39
+ req.body = { extension: extname }.to_json
40
+ end
41
+ end
42
+
43
+ def data
44
+ @data ||= begin
45
+ raise Client::Error unless request.success?
46
+
47
+ JSON.parse(request.body)
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Scholarsphere
4
4
  module Client
5
- VERSION = '0.1.1'
5
+ VERSION = '0.2.0'
6
6
  end
7
7
  end
@@ -4,10 +4,10 @@ module Scholarsphere
4
4
  module S3
5
5
  ##
6
6
  #
7
- # Represents a file on the client's file system that will be uploaded, but hasn't been yet. The object is
8
- # constructed using a pathname for the file, and if desired, a checksum. The checksum is not required, and if it is
9
- # not provided, the client will calculate one. Once initialized, the uploaded file object can be used by the client
10
- # to make additional calls to the application.
7
+ # Represents a file on the client's file system to be uploaded into Scholarsphere. The object is constructed using a
8
+ # pathname for the file, and an optional checksum. Once initialized, the object is used by the client to make
9
+ # additional calls to the application, including uploading the file into S3, and adding the resulting uploaded file
10
+ # to a work in Scholarsphere.
11
11
  #
12
12
  # ## Examples
13
13
  #
@@ -16,46 +16,38 @@ module Scholarsphere
16
16
  # pathname = Pathname.new('path/to/your/file')
17
17
  # uploaded_file = UploadedFile.new(pathname)
18
18
  #
19
- # If the file is large, then calculating a checksum could be time-intensive. Providing one can avoid that. Or, if
19
+ # If the file is large, then calculating a checksum could be time-intensive. Providing one can avoid that, or, if
20
20
  # you already have a checksum from a trusted source, you can pass that along for the client to use:
21
21
  #
22
22
  # uploaded_file = UploadedFile.new(pathname, checksum: '[md5 checksum hash]')
23
23
  #
24
+ # ## Checksum Verification
25
+ #
26
+ # Checksums are always used. If you don't provide one, the client will calculate one for you and use it when
27
+ # uploading the file, such as with S3::Uploader. AWS uses the provided checksum to verify the file's integrity, and
28
+ # if that check fails, an exception is raised. This avoids the prospect that the file could be corrupted during its
29
+ # transfer from the local client's filesystem into S3.
30
+ #
24
31
  class UploadedFile
25
- # @return [Pathname] The location of the file to be uploaded
32
+ # @return [Pathname] The file to be uploaded on the client's filesystem
26
33
  attr_reader :source
27
34
 
28
- # @param source [Pathname] The file to be uploaded
29
- # @param options [Hash]
30
- # @option options [String] :checksum The file's md5 checksum. If one is not provided, it will be calculated at
31
- # upload
32
- def initialize(source, options = {})
33
- @source = source
34
- @checksum = options[:checksum]
35
+ # @param source [Pathname, File, IO, String] Object or path to the file
36
+ # @param checksum [String] Optional md5 checksum of the file
37
+ def initialize(source:, checksum: nil)
38
+ @source = Pathname.new(source)
39
+ @checksum = checksum
35
40
  end
36
41
 
37
- # @return [Hash] Set of metadata needed by Shrine to upload the file
38
- # @note this can be passed to a controller for uploading the file to Shrine
39
- def to_shrine
42
+ # @return [Hash] Parameters required to add the file to a work in Scholarsphere
43
+ def to_param
40
44
  {
41
- id: id,
42
- storage: 'cache',
45
+ id: upload.id,
46
+ storage: upload.prefix,
43
47
  metadata: metadata
44
48
  }
45
49
  end
46
50
 
47
- # @return [String] A unique, randomly-generated UUID
48
- # @note This serves as the name of the file in the S3 bucket. However, this original name of the file is kept as
49
- # metadata within the application so that it can be downloaded.
50
- def id
51
- @id ||= "#{SecureRandom.uuid}#{source.extname}"
52
- end
53
-
54
- # @return [String] Path of the file relative to the bucket in S3
55
- def key
56
- "#{prefix}/#{id}"
57
- end
58
-
59
51
  # @return [String] The md5 checksum encoded in base64. If you provided a checksum at initialization, that one will
60
52
  # be encoded, if not, a checksum will be calculated and then encoded.
61
53
  # @note When sending the checksum to verify the file's integrity, Amazon requires that the value be base64
@@ -70,9 +62,9 @@ module Scholarsphere
70
62
  end
71
63
  end
72
64
 
73
- # @return [String] Size of the file in bytes
74
- def size
75
- source.size
65
+ # @return [String] Pre-signed url used to upload the file into Scholarsphere's S3 instance.
66
+ def presigned_url
67
+ upload.url
76
68
  end
77
69
 
78
70
  private
@@ -87,8 +79,8 @@ module Scholarsphere
87
79
  }
88
80
  end
89
81
 
90
- def prefix
91
- ENV['SHRINE_CACHE_PREFIX'] || 'cache'
82
+ def upload
83
+ @upload ||= Client::Upload.new(extname: source.extname)
92
84
  end
93
85
  end
94
86
  end
@@ -2,43 +2,43 @@
2
2
 
3
3
  module Scholarsphere
4
4
  module S3
5
- class Uploader < ::Aws::S3::FileUploader
6
- FIFTEEN_MEGABYTES = 15 * 1024 * 1024
7
-
8
- # @param [Hash] options
9
- # @option options [Client] :client
10
- # @option options [Integer] :multipart_threshold (15728640)
11
- def initialize(options = {})
12
- @options = options
13
- @options[:client] ||= ::Aws::S3::Client.new(client_defaults)
14
- @client = options[:client]
15
- @multipart_threshold = options[:multipart_threshold] || FIFTEEN_MEGABYTES
5
+ ##
6
+ #
7
+ # Uploads a file to Scholarsphere's AWS S3 instance. When upload is invoked, a pre-signed URL is requested from
8
+ # Scholarsphere, and if successful, the file is then uploaded to Scholarsphere using the url.
9
+ #
10
+ # An md5 hash is calculated for the file at initialization to ensure the file is transferred successfully.
11
+ #
12
+ # # Example
13
+ #
14
+ # uploaded_file = Scholarsphere::S3::UploadedFile.new('path/to/file')
15
+ # uploader = Scholarsphere::S3::Uploader(file: uploaded_file)
16
+ # response = uploader.upload
17
+ #
18
+ class Uploader
19
+ # @param file [UploadedFile]
20
+ def initialize(file:)
21
+ @file = file
22
+ @content_md5 = file.content_md5
16
23
  end
17
24
 
18
- # @param [UploadedFile] uploaded_file
19
- # @param [Hash] options of additional options
20
- # @option options [String] content_md5 a base64-encoded string representating the md5 checksum
21
- # @return [void]
22
- # @note The content_md5 hash cannot be used when doing a multipart upload.
23
- def upload(uploaded_file, options = {})
24
- options[:bucket] = ENV['AWS_BUCKET']
25
- options[:key] = uploaded_file.key
26
- if uploaded_file.size < multipart_threshold
27
- options[:content_md5] = uploaded_file.content_md5
25
+ # @return [Faraday::Response] The response from Scholarsphere to the upload request.
26
+ def upload
27
+ connection(file.presigned_url).put do |req|
28
+ req.body = file.source.read
29
+ req.headers['Content-MD5'] = file.content_md5
28
30
  end
29
- super(uploaded_file.source, options)
30
31
  end
31
32
 
32
33
  private
33
34
 
34
- def client_defaults
35
- {
36
- endpoint: ENV['S3_ENDPOINT'],
37
- access_key_id: ENV['AWS_ACCESS_KEY_ID'],
38
- secret_access_key: ENV['AWS_SECRET_ACCESS_KEY'],
39
- force_path_style: true,
40
- region: ENV['AWS_REGION']
41
- }
35
+ attr_reader :file, :content_md5
36
+
37
+ def connection(url)
38
+ Faraday::Connection.new(
39
+ url: url,
40
+ ssl: { verify: Scholarsphere::Client.verify_ssl? }
41
+ )
42
42
  end
43
43
  end
44
44
  end
@@ -17,7 +17,7 @@ Gem::Specification.new do |spec|
17
17
  spec.metadata = {
18
18
  'homepage_uri' => spec.homepage,
19
19
  'source_code_uri' => spec.homepage,
20
- 'documentation' => 'https://www.rubydoc.info/gems/scholarsphere-client',
20
+ 'documentation_uri' => 'https://www.rubydoc.info/github/psu-stewardship/scholarsphere-client/main',
21
21
  'allowed_push_host' => 'https://rubygems.org'
22
22
  }
23
23
 
@@ -41,6 +41,10 @@ Gem::Specification.new do |spec|
41
41
  spec.add_development_dependency 'rake', '>= 12.3.3'
42
42
  spec.add_development_dependency 'rspec', '~> 3.0'
43
43
  spec.add_development_dependency 'rspec-its', '~> 1.3'
44
- spec.add_development_dependency 'simplecov', '~> 0.18'
44
+ spec.add_development_dependency 'vcr', '~> 6.0'
45
+ spec.add_development_dependency 'webmock', '~> 3.11'
45
46
  spec.add_development_dependency 'yard', '< 1.0'
47
+
48
+ # Latest version of simplecov is not compatible with Code Climate
49
+ spec.add_development_dependency 'simplecov', '< 0.18'
46
50
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: scholarsphere-client
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.1
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Adam Wead
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2021-01-14 00:00:00.000000000 Z
11
+ date: 2021-03-04 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: aws-sdk-s3
@@ -151,19 +151,33 @@ dependencies:
151
151
  - !ruby/object:Gem::Version
152
152
  version: '1.3'
153
153
  - !ruby/object:Gem::Dependency
154
- name: simplecov
154
+ name: vcr
155
155
  requirement: !ruby/object:Gem::Requirement
156
156
  requirements:
157
157
  - - "~>"
158
158
  - !ruby/object:Gem::Version
159
- version: '0.18'
159
+ version: '6.0'
160
160
  type: :development
161
161
  prerelease: false
162
162
  version_requirements: !ruby/object:Gem::Requirement
163
163
  requirements:
164
164
  - - "~>"
165
165
  - !ruby/object:Gem::Version
166
- version: '0.18'
166
+ version: '6.0'
167
+ - !ruby/object:Gem::Dependency
168
+ name: webmock
169
+ requirement: !ruby/object:Gem::Requirement
170
+ requirements:
171
+ - - "~>"
172
+ - !ruby/object:Gem::Version
173
+ version: '3.11'
174
+ type: :development
175
+ prerelease: false
176
+ version_requirements: !ruby/object:Gem::Requirement
177
+ requirements:
178
+ - - "~>"
179
+ - !ruby/object:Gem::Version
180
+ version: '3.11'
167
181
  - !ruby/object:Gem::Dependency
168
182
  name: yard
169
183
  requirement: !ruby/object:Gem::Requirement
@@ -178,6 +192,20 @@ dependencies:
178
192
  - - "<"
179
193
  - !ruby/object:Gem::Version
180
194
  version: '1.0'
195
+ - !ruby/object:Gem::Dependency
196
+ name: simplecov
197
+ requirement: !ruby/object:Gem::Requirement
198
+ requirements:
199
+ - - "<"
200
+ - !ruby/object:Gem::Version
201
+ version: '0.18'
202
+ type: :development
203
+ prerelease: false
204
+ version_requirements: !ruby/object:Gem::Requirement
205
+ requirements:
206
+ - - "<"
207
+ - !ruby/object:Gem::Version
208
+ version: '0.18'
181
209
  description: Client software to create new content for the Scholarsphere repository
182
210
  at Penn State.
183
211
  email:
@@ -186,11 +214,13 @@ executables: []
186
214
  extensions: []
187
215
  extra_rdoc_files: []
188
216
  files:
217
+ - ".circleci/config.yml"
189
218
  - ".gitignore"
190
219
  - ".rspec"
191
220
  - ".rubocop.yml"
192
221
  - ".yardopts"
193
222
  - Gemfile
223
+ - Gemfile.lock
194
224
  - README.md
195
225
  - Rakefile
196
226
  - bin/console
@@ -200,6 +230,7 @@ files:
200
230
  - lib/scholarsphere/client/collection.rb
201
231
  - lib/scholarsphere/client/config.rb
202
232
  - lib/scholarsphere/client/ingest.rb
233
+ - lib/scholarsphere/client/upload.rb
203
234
  - lib/scholarsphere/client/version.rb
204
235
  - lib/scholarsphere/s3.rb
205
236
  - lib/scholarsphere/s3/uploaded_file.rb
@@ -210,7 +241,7 @@ licenses: []
210
241
  metadata:
211
242
  homepage_uri: https://github.com/psu-stewardship/scholarsphere-client
212
243
  source_code_uri: https://github.com/psu-stewardship/scholarsphere-client
213
- documentation: https://www.rubydoc.info/gems/scholarsphere-client
244
+ documentation_uri: https://www.rubydoc.info/github/psu-stewardship/scholarsphere-client/main
214
245
  allowed_push_host: https://rubygems.org
215
246
  post_install_message:
216
247
  rdoc_options: []