uppy-s3_multipart 0.3.2 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 701a2e46d55f76fb336473db225e298fad66daa9bd45fcef78aae4aedb928d3d
4
- data.tar.gz: c157522695c642ba85aa702ffc68d24a5ae8df88eebf6a45033adfc704ddbc71
3
+ metadata.gz: 7a87c64fe13425d997f2213f85c727195565eb095d84123401896e60c9d2caf8
4
+ data.tar.gz: 39b1d491f57bace5dbaf690545ac9fe0c9bd6bdb45c72b9915282d8670465a44
5
5
  SHA512:
6
- metadata.gz: bf9903c90a654d59c1bc0f442a4e4510cf4fe309d6775a12870b93253742dfa84e3f8c5854f5c60cab87048324c959abfd11679d010899554ca0ed61f8f0ab0f
7
- data.tar.gz: 4ffa1e3fdf4173754424e83d3e035b1cf6b71363198c6bfb29cf3507bd8599f16a724c6e9cee44f0942308cd345ca4973ff61eba9047e71516a7d849dc00c5b5
6
+ metadata.gz: 233728fa7a1d73c5863b7935ebda6edbec9c209871dd339a8955294c1edf942db9801e4b2c8bb7cec6238cf03bc7e6268cba628890672902de50b70e81e16e34
7
+ data.tar.gz: 55f2779209fc8b502574c5402b75d07cfc7d31e1afd8b290d5e5a5a075b71555ac5396909214b3035c462fb793fa9ecc381f0045f738c1e14307b1164fd773f3
data/README.md CHANGED
@@ -1,6 +1,6 @@
1
- # Uppy::S3Multipart
1
+ # uppy-s3_multipart
2
2
 
3
- Provides a Rack application that implements endpoints for the [AwsS3Multipart]
3
+ Provides a Rack application that implements endpoints for the [aws-s3-multipart]
4
4
  Uppy plugin. This enables multipart uploads directly to S3, which is
5
5
  recommended when dealing with large files, as it allows resuming interrupted
6
6
  uploads.
@@ -10,37 +10,49 @@ uploads.
10
10
  Add the gem to your Gemfile:
11
11
 
12
12
  ```rb
13
- gem "uppy-s3_multipart", "~> 0.2"
13
+ gem "uppy-s3_multipart", "~> 1.0"
14
14
  ```
15
15
 
16
16
  ## Setup
17
17
 
18
- In order to allow direct multipart uploads to your S3 bucket, we need to update
18
+ In order to allow direct multipart uploads to your S3 bucket, you need to update
19
19
  the bucket's CORS configuration. In the AWS S3 Console go to your bucket, click
20
20
  on "Permissions" tab and then on "CORS configuration". There paste in the
21
21
  following:
22
22
 
23
- ```xml
24
- <?xml version="1.0" encoding="UTF-8"?>
25
- <CORSConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
26
- <CORSRule>
27
- <AllowedOrigin>https://my-app.com</AllowedOrigin>
28
- <AllowedMethod>GET</AllowedMethod>
29
- <AllowedMethod>POST</AllowedMethod>
30
- <AllowedMethod>PUT</AllowedMethod>
31
- <MaxAgeSeconds>3000</MaxAgeSeconds>
32
- <AllowedHeader>Authorization</AllowedHeader>
33
- <AllowedHeader>x-amz-date</AllowedHeader>
34
- <AllowedHeader>x-amz-content-sha256</AllowedHeader>
35
- <AllowedHeader>content-type</AllowedHeader>
36
- <ExposeHeader>ETag</ExposeHeader>
37
- </CORSRule>
38
- <CORSRule>
39
- <AllowedOrigin>*</AllowedOrigin>
40
- <AllowedMethod>GET</AllowedMethod>
41
- <MaxAgeSeconds>3000</MaxAgeSeconds>
42
- </CORSRule>
43
- </CORSConfiguration>
23
+ ```json
24
+ [
25
+ {
26
+ "AllowedHeaders": [
27
+ "content-type",
28
+ "x-amz-content-sha256",
29
+ "x-amz-date"
30
+ ],
31
+ "AllowedMethods": [
32
+ "GET",
33
+ "POST",
34
+ "PUT"
35
+ ],
36
+ "AllowedOrigins": [
37
+ "https://my-app.com"
38
+ ],
39
+ "ExposeHeaders": [
40
+ "ETag"
41
+ ],
42
+ "MaxAgeSeconds": 3000
43
+ },
44
+ {
45
+ "AllowedHeaders": [],
46
+ "AllowedMethods": [
47
+ "GET"
48
+ ],
49
+ "AllowedOrigins": [
50
+ "*"
51
+ ],
52
+ "ExposeHeaders": [],
53
+ "MaxAgeSeconds": 3000
54
+ }
55
+ ]
44
56
  ```
45
57
 
46
58
  Replace `https://my-app.com` with the URL to your app (in development you can
@@ -51,11 +63,63 @@ CORS settings to be applied.
51
63
 
52
64
  This gem provides a Rack application that you can mount inside your main
53
65
  application. If you're using [Shrine], you can initialize the Rack application
54
- via the `uppy_s3_multipart` Shrine plugin, otherwise you can initialize it
55
- directly.
66
+ via the [Shrine plugin](#shrine).
67
+
68
+ ### App
69
+
70
+ At its core, you initialize an `Uppy::S3Multipart::App` with an
71
+ `Aws::S3::Bucket` object:
72
+
73
+ ```rb
74
+ require "uppy/s3_multipart"
75
+
76
+ bucket = Aws::S3::Bucket.new(
77
+ name: "my-bucket",
78
+ access_key_id: "...",
79
+ secret_access_key: "...",
80
+ region: "...",
81
+ )
82
+
83
+ UPPY_S3_MULTIPART_APP = Uppy::S3Multipart::App.new(bucket: bucket)
84
+ ```
85
+
86
+ The instance of `Uppy::S3Multipart::App` is a Rack application that can be
87
+ mounted in your router (`config/routes.rb` in Rails). It should be
88
+ mounted at `/s3/multipart`:
89
+
90
+ ```rb
91
+ # config/routes.rb (Rails)
92
+ Rails.application.routes.draw do
93
+ # ...
94
+ mount UPPY_S3_MULTIPART_APP => "/s3/multipart"
95
+ end
96
+ ```
97
+
98
+ This will add the routes that the `aws-s3-multipart` Uppy plugin expects:
99
+
100
+ ```
101
+ POST /s3/multipart
102
+ GET /s3/multipart/:uploadId
103
+ GET /s3/multipart/:uploadId/:partNumber
104
+ POST /s3/multipart/:uploadId/complete
105
+ DELETE /s3/multipart/:uploadId
106
+ ```
107
+
108
+ Since your app will now play the role of Uppy Companion, in your Uppy
109
+ configuration you can point `companionUrl` to your app's URL:
110
+
111
+ ```js
112
+ // ...
113
+ uppy.use(Uppy.AwsS3Multipart, {
114
+ companionUrl: '/',
115
+ })
116
+ ```
56
117
 
57
118
  ### Shrine
58
119
 
120
+ If you're using Shrine, you can use the `uppy_s3_multipart` Shrine plugin that
121
+ ships with this gem to simplify the setup.
122
+
59
123
  In your Shrine initializer load the `uppy_s3_multipart` plugin:
60
124
 
61
125
  ```rb
@@ -71,34 +135,29 @@ Shrine.storages = {
71
135
  Shrine.plugin :uppy_s3_multipart # load the plugin
72
136
  ```
73
137
 
74
- The plugin will provide a `Shrine.uppy_s3_multipart` method that creates a new
75
- `Uppy::S3Multipart::App` instance, which is a Rack app that you can mount
76
- inside your main application:
138
+ The plugin will provide a `Shrine.uppy_s3_multipart` method that creates the
139
+ `Uppy::S3Multipart::App` instance, which you can then mount inside your router:
77
140
 
78
141
  ```rb
79
- # Rails (config/routes.rb)
142
+ # config/routes.rb (Rails)
80
143
  Rails.application.routes.draw do
144
+ # ...
81
145
  mount Shrine.uppy_s3_multipart(:cache) => "/s3/multipart"
82
146
  end
83
-
84
- # Rack (config.ru)
85
- map "/s3/multipart" do
86
- run Shrine.uppy_s3_multipart(:cache)
87
- end
88
147
  ```
89
148
 
90
- Now in your Uppy configuration point `serverUrl` to your app's URL:
149
+ Now in your Uppy configuration point `companionUrl` to your app's URL:
91
150
 
92
151
  ```js
93
152
  // ...
94
153
  uppy.use(Uppy.AwsS3Multipart, {
95
- serverUrl: '/',
154
+ companionUrl: '/',
96
155
  })
97
156
  ```
98
157
 
99
- In the `upload-success` Uppy callback you can then construct the Shrine
100
- uploaded file data (this example assumes your temporary Shrine S3 storage has
101
- `prefix: "cache"` set):
158
+ In the `upload-success` Uppy callback, you can construct the Shrine uploaded
159
+ file data (this example assumes your temporary Shrine S3 storage has `prefix:
160
+ "cache"` set):
102
161
 
103
162
  ```js
104
163
  uppy.on('upload-success', function (file, response) {
@@ -115,108 +174,57 @@ uppy.on('upload-success', function (file, response) {
115
174
  })
116
175
  ```
117
176
 
118
- **See [Adding Direct S3 Uploads] for an example of a complete Uppy setup with
119
- Shrine. From there you can swap the `presign_endpoint` + `AwsS3` code with the
120
- `uppy_s3_multipart` + `AwsS3Multipart` setup.**
177
+ See [Adding Direct S3 Uploads] for an example of a complete Uppy setup with
178
+ Shrine. From there you can swap the `presign_endpoint` + `aws-s3` code with the
179
+ `uppy_s3_multipart` + `aws-s3-multipart` setup.
121
180
 
122
181
  Note that **Shrine won't extract metadata from directly upload files on
123
182
  assignment** by default. Instead, it will just copy metadata that was extracted
124
183
  on the client side. See [this section][metadata direct uploads] for the
125
184
  rationale and instructions on how to opt in.
126
185
 
127
- ### App
128
-
129
- You can also use `uppy-s3_multipart` without Shrine, by initializing the
130
- `Uppy::S3Multipart::App` directly:
131
-
132
- ```rb
133
- require "uppy/s3_multipart"
134
-
135
- resource = Aws::S3::Resource.new(
136
- access_key_id: "...",
137
- secret_access_key: "...",
138
- region: "...",
139
- )
140
-
141
- bucket = resource.bucket("my-bucket")
142
-
143
- UPPY_S3_MULTIPART_APP = Uppy::S3Multipart::App.new(bucket: bucket)
144
- ```
145
-
146
- You can mount it inside your main app in the same way:
147
-
148
- ```rb
149
- # Rails (config/routes.rb)
150
- Rails.application.routes.draw do
151
- mount UPPY_S3_MULTIPART_APP => "/s3/multipart"
152
- end
153
-
154
- # Rack (config.ru)
155
- map "/s3/multipart" do
156
- run UPPY_S3_MULTIPART_APP
157
- end
158
- ```
159
-
160
- This will add the routes that the `AwsS3Multipart` Uppy plugin expects:
161
-
162
- ```
163
- POST /s3/multipart
164
- GET /s3/multipart/:uploadId
165
- GET /s3/multipart/:uploadId/:partNumber
166
- POST /s3/multipart/:uploadId/complete
167
- DELETE /s3/multipart/:uploadId
168
- ```
169
-
170
- Now in your Uppy configuration point `serverUrl` to your app's URL:
171
-
172
- ```js
173
- // ...
174
- uppy.use(Uppy.AwsS3Multipart, {
175
- serverUrl: '/',
176
- })
177
- ```
178
-
179
- ### Configuration
186
+ ## Configuration
180
187
 
181
188
  This section describe various configuration options that you can pass to
182
189
  `Uppy::S3Multipart::App`.
183
190
 
184
191
  #### `:bucket`
185
192
 
186
- The `:bucket` option is mandatory and accepts an instance of `Aws::S3::Bucket`.
187
- It's easiest to create an `Aws::S3::Resource`, and call `#bucket` on it.
193
+ The `:bucket` option is mandatory and accepts an instance of `Aws::S3::Bucket`:
188
194
 
189
195
  ```rb
190
196
  require "uppy/s3_multipart"
191
197
 
192
- resource = Aws::S3::Resource.new(
198
+ bucket = Aws::S3::Bucket.new(
199
+ name: "<BUCKET>",
193
200
  access_key_id: "<ACCESS_KEY_ID>",
194
201
  secret_access_key: "<SECRET_ACCESS_KEY>",
195
202
  region: "<REGION>",
196
203
  )
197
204
 
198
- bucket = resource.bucket("<BUCKET>")
199
-
200
- Uppy::S3MUltipart::App.new(bucket: bucket)
205
+ Uppy::S3Multipart::App.new(bucket: bucket)
201
206
  ```
202
207
 
203
- If you want to use [Minio], you can easily configure your `Aws::S3::Bucket` to
204
- point to your Minio server:
208
+ If you want to use [Minio], you can easily configure the `Aws::S3::Bucket` to
209
+ use your Minio server:
205
210
 
206
211
  ```rb
207
- resource = Aws::S3::Resource.new(
212
+ bucket = Aws::S3::Bucket.new(
213
+ name: "<MINIO_BUCKET>",
208
214
  access_key_id: "<MINIO_ACCESS_KEY>", # "AccessKey" value
209
215
  secret_access_key: "<MINIO_SECRET_KEY>", # "SecretKey" value
210
216
  endpoint: "<MINIO_ENDPOINT>", # "Endpoint" value
211
217
  region: "us-east-1",
212
- force_path_style: true,
213
218
  )
214
219
 
215
- bucket = resource.bucket("<MINIO_BUCKET>") # name of the bucket you created
220
+ Uppy::S3Multipart::App.new(bucket: bucket)
216
221
  ```
217
222
 
218
- See the [`Aws::S3::Client#initialize`] docs for all supported configuration
219
- options. In the Shrine plugin this option is inferred from the S3 storage.
223
+ Except for `:name`, all options passed to [`Aws::S3::Bucket#initialize`] are
224
+ forwarded to [`Aws::S3::Client#initialize`], see its documentation for
225
+ additional options.
226
+
227
+ In the Shrine plugin this configuration is inferred from the S3 storage.
220
228
 
221
229
  #### `:prefix`
222
230
 
@@ -227,14 +235,7 @@ to be uploaded to.
227
235
  Uppy::S3Multipart::App.new(bucket: bucket, prefix: "cache")
228
236
  ```
229
237
 
230
- In the Shrine plugin this option is inferred from the S3 storage:
231
-
232
- ```rb
233
- Shrine.storages = {
234
- cache: Shrine::Storage::S3.new(prefix: "cache", **options),
235
- store: Shrine::Storage::S3.new(**options),
236
- }
237
- ```
238
+ In the Shrine plugin this option is inferred from the S3 storage.
238
239
 
239
240
  #### `:options`
240
241
 
@@ -326,7 +327,7 @@ Shrine.storages = {
326
327
  }
327
328
  ```
328
329
 
329
- ### Client
330
+ ## Client
330
331
 
331
332
  If you would rather implement the endpoints yourself, you can utilize the
332
333
  `Uppy::S3Multipart::Client` to make S3 requests.
@@ -475,13 +476,14 @@ Covenant](http://contributor-covenant.org) code of conduct.
475
476
  The gem is available as open source under the terms of the [MIT
476
477
  License](https://opensource.org/licenses/MIT).
477
478
 
478
- [AwsS3Multipart]: https://uppy.io/docs/aws-s3-multipart/
479
+ [aws-s3-multipart]: https://uppy.io/docs/aws-s3-multipart/
479
480
  [Shrine]: https://shrinerb.com
480
481
  [Adding Direct S3 Uploads]: https://github.com/shrinerb/shrine/wiki/Adding-Direct-S3-Uploads
481
482
  [Minio]: https://minio.io/
482
483
  [Client]: #client
483
484
  [content_disposition]: https://github.com/shrinerb/content_disposition
484
485
  [`Rack::Request`]: https://www.rubydoc.info/github/rack/rack/master/Rack/Request
486
+ [`Aws::S3::Bucket#initialize`]: https://docs.aws.amazon.com/sdk-for-ruby/v3/api/Aws/S3/Bucket.html#initialize-instance_method
485
487
  [`Aws::S3::Client#initialize`]: https://docs.aws.amazon.com/sdk-for-ruby/v3/api/Aws/S3/Client.html#initialize-instance_method
486
488
  [`Aws::S3::Client#create_multipart_upload`]: https://docs.aws.amazon.com/sdk-for-ruby/v3/api/Aws/S3/Client.html#create_multipart_upload-instance_method
487
489
  [`Aws::S3::Client#list_parts`]: https://docs.aws.amazon.com/sdk-for-ruby/v3/api/Aws/S3/Client.html#list_parts-instance_method
@@ -38,23 +38,32 @@ module Uppy
38
38
  route do |r|
39
39
  # POST /s3/multipart
40
40
  r.post ["", true] do
41
- content_type = r.params["type"]
42
- filename = r.params["filename"]
41
+ type = r.params["type"]
42
+ filename = r.params["filename"]
43
43
 
44
- extension = File.extname(filename.to_s)
45
- key = SecureRandom.hex + extension
46
- key = "#{opts[:prefix]}/#{key}" if opts[:prefix]
44
+ key = SecureRandom.hex + File.extname(filename.to_s)
45
+ key = [*opts[:prefix], key].join("/")
47
46
 
48
- content_disposition = ContentDisposition.inline(filename) if filename
49
-
50
- options = { content_type: content_type, content_disposition: content_disposition }
51
- options[:acl] = "public-read" if opts[:public]
47
+ options = {}
48
+ options[:content_type] = type if type
49
+ options[:content_disposition] = ContentDisposition.inline(filename) if filename
50
+ options[:acl] = "public-read" if opts[:public]
52
51
 
53
52
  result = client_call(:create_multipart_upload, key: key, **options)
54
53
 
55
54
  { uploadId: result.fetch(:upload_id), key: result.fetch(:key) }
56
55
  end
57
56
 
57
+ # OPTIONS /s3/multipart
58
+ r.options ["", true] do
59
+ r.halt 204
60
+ end
61
+
62
+ # OPTIONS /s3/multipart/:uploadId/:partNumber
63
+ r.options String, String do |upload_id, part_number|
64
+ r.halt 204
65
+ end
66
+
58
67
  # GET /s3/multipart/:uploadId
59
68
  r.get String do |upload_id|
60
69
  key = param!("key")
@@ -99,7 +108,14 @@ module Uppy
99
108
  r.delete String do |upload_id|
100
109
  key = param!("key")
101
110
 
102
- client_call(:abort_multipart_upload, upload_id: upload_id, key: key)
111
+ begin
112
+ client_call(:abort_multipart_upload, upload_id: upload_id, key: key)
113
+ rescue Aws::S3::Errors::NoSuchUpload
114
+ error!(
115
+ "Upload doesn't exist for \"key\" parameter",
116
+ status: 404
117
+ )
118
+ end
103
119
 
104
120
  {}
105
121
  end
@@ -124,8 +140,8 @@ module Uppy
124
140
  value
125
141
  end
126
142
 
127
- def error!(message)
128
- request.halt 400, { error: message }
143
+ def error!(message, status: 400)
144
+ request.halt status, { error: message }
129
145
  end
130
146
  end
131
147
  end
@@ -56,13 +56,7 @@ module Uppy
56
56
 
57
57
  def abort_multipart_upload(upload_id:, key:, **options)
58
58
  multipart_upload = multipart_upload(upload_id, key)
59
-
60
- # aws-sdk-s3 docs recommend retrying the abort in case the multipart
61
- # upload still has parts
62
- loop do
63
- multipart_upload.abort(**options)
64
- break unless multipart_upload.parts.any?
65
- end
59
+ multipart_upload.abort(**options)
66
60
 
67
61
  {}
68
62
  end
@@ -1,8 +1,8 @@
1
1
  Gem::Specification.new do |gem|
2
2
  gem.name = "uppy-s3_multipart"
3
- gem.version = "0.3.2"
3
+ gem.version = "1.1.0"
4
4
 
5
- gem.required_ruby_version = ">= 2.2"
5
+ gem.required_ruby_version = ">= 2.3"
6
6
 
7
7
  gem.summary = "Provides a Rack application that implements endpoints for the AwsS3Multipart Uppy plugin."
8
8
  gem.homepage = "https://github.com/janko/uppy-s3_multipart"
@@ -23,4 +23,5 @@ Gem::Specification.new do |gem|
23
23
  gem.add_development_dependency "shrine", "~> 2.13"
24
24
  gem.add_development_dependency "shrine-memory"
25
25
  gem.add_development_dependency "aws-sdk-core", "~> 3.23"
26
+ gem.add_development_dependency "rexml"
26
27
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: uppy-s3_multipart
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.2
4
+ version: 1.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Janko Marohnić
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2019-05-04 00:00:00.000000000 Z
11
+ date: 2021-08-27 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: roda
@@ -142,7 +142,21 @@ dependencies:
142
142
  - - "~>"
143
143
  - !ruby/object:Gem::Version
144
144
  version: '3.23'
145
- description:
145
+ - !ruby/object:Gem::Dependency
146
+ name: rexml
147
+ requirement: !ruby/object:Gem::Requirement
148
+ requirements:
149
+ - - ">="
150
+ - !ruby/object:Gem::Version
151
+ version: '0'
152
+ type: :development
153
+ prerelease: false
154
+ version_requirements: !ruby/object:Gem::Requirement
155
+ requirements:
156
+ - - ">="
157
+ - !ruby/object:Gem::Version
158
+ version: '0'
159
+ description:
146
160
  email:
147
161
  - janko.marohnic@gmail.com
148
162
  executables: []
@@ -161,7 +175,7 @@ homepage: https://github.com/janko/uppy-s3_multipart
161
175
  licenses:
162
176
  - MIT
163
177
  metadata: {}
164
- post_install_message:
178
+ post_install_message:
165
179
  rdoc_options: []
166
180
  require_paths:
167
181
  - lib
@@ -169,15 +183,15 @@ required_ruby_version: !ruby/object:Gem::Requirement
169
183
  requirements:
170
184
  - - ">="
171
185
  - !ruby/object:Gem::Version
172
- version: '2.2'
186
+ version: '2.3'
173
187
  required_rubygems_version: !ruby/object:Gem::Requirement
174
188
  requirements:
175
189
  - - ">="
176
190
  - !ruby/object:Gem::Version
177
191
  version: '0'
178
192
  requirements: []
179
- rubygems_version: 3.0.3
180
- signing_key:
193
+ rubygems_version: 3.2.15
194
+ signing_key:
181
195
  specification_version: 4
182
196
  summary: Provides a Rack application that implements endpoints for the AwsS3Multipart
183
197
  Uppy plugin.