uppy-s3_multipart 0.1.1 → 0.2.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: dfe2ae799d5586b474000ff740cfc72d5e30ab0186098665ff50060693d3fc79
4
- data.tar.gz: 4748896f3bcaae2f5de3b739e2cf1fe4d99754241165feff92cb4c6a2c92f486
3
+ metadata.gz: e799b2dfe2ad03929e1e6477382b85c4d721313708abe699673c2c3e496e98e8
4
+ data.tar.gz: 05d354cff6f1f5a9ae807faffdd91b29d5f16c62fba9abbec82038b7713a1a3a
5
5
  SHA512:
6
- metadata.gz: 9b4fc57d8a84a1ce4b7444b20d99fc824ce7afe89dc4bb1da81765b24667ca5cacf53b289d8ee336c99530210f96d58ee839b10a425232505b19096007e385fd
7
- data.tar.gz: 801e7e0f455a23d4db546898ed20b118a642279a7270988ace5cb39e5c15505a57eab6094c8eeb79f4b689ff88854cdfe5315655e105940cfb80ae3164487e6a
6
+ metadata.gz: 336d352eeeb31ac132c074ddb4febd73d75f47b5fe41ed69caafb90eecf1d43494f37542d06a1df2859210fe9907207456695c0a3c965131384564f93b0abaa9
7
+ data.tar.gz: 2ae94a045d3b19e1eabc99dce49ffcaadc1d806caf77cbc87b6edf5b771e1509d58c56a3510f5e1a44b15adfdbe5d93f19fc6acf5322d66bb22791aa5d80d043
data/README.md CHANGED
@@ -10,7 +10,7 @@ uploads.
10
10
  Add the gem to your Gemfile:
11
11
 
12
12
  ```rb
13
- gem "uppy-s3_multipart"
13
+ gem "uppy-s3_multipart", "~> 0.2"
14
14
  ```
15
15
 
16
16
  ## Setup
@@ -56,14 +56,14 @@ directly.
56
56
 
57
57
  ### Shrine
58
58
 
59
- In the initializer load the `uppy_s3_multipart` plugin:
59
+ In your Shrine initializer load the `uppy_s3_multipart` plugin:
60
60
 
61
61
  ```rb
62
62
  require "shrine"
63
63
  require "shrine/storage/s3"
64
64
 
65
65
  Shrine.storages = {
66
- cache: Shrine::Storage::S3.new(...),
66
+ cache: Shrine::Storage::S3.new(prefix: "cache", ...),
67
67
  store: Shrine::Storage::S3.new(...),
68
68
  }
69
69
 
@@ -78,11 +78,11 @@ inside your main application:
78
78
  ```rb
79
79
  # Rails (config/routes.rb)
80
80
  Rails.application.routes.draw do
81
- mount Shrine.uppy_s3_multipart(:cache) => "/s3"
81
+ mount Shrine.uppy_s3_multipart(:cache) => "/s3/multipart"
82
82
  end
83
83
 
84
84
  # Rack (config.ru)
85
- map "/s3" do
85
+ map "/s3/multipart" do
86
86
  run Shrine.uppy_s3_multipart(:cache)
87
87
  end
88
88
  ```
@@ -97,14 +97,20 @@ POST /s3/multipart/:uploadId/complete
97
97
  DELETE /s3/multipart/:uploadId
98
98
  ```
99
99
 
100
- Finally, in your Uppy configuration point `serverUrl` to your app's URL:
100
+ Now in your Uppy configuration point `serverUrl` to your app's URL:
101
101
 
102
102
  ```js
103
103
  // ...
104
104
  uppy.use(Uppy.AwsS3Multipart, {
105
105
  serverUrl: '/',
106
106
  })
107
+ ```
108
+
109
+ In the `upload-success` Uppy callback you can then construct the Shrine
110
+ uploaded file data (this example assumes your temporary Shrine S3 storage has
111
+ `prefix: "cache"` set):
107
112
 
113
+ ```js
108
114
  uppy.on('upload-success', function (file, data, uploadURL) {
109
115
  var uploadedFileData = JSON.stringify({
110
116
  id: uploadURL.match(/\/cache\/([^\?]+)/)[1], // extract key without prefix
@@ -123,8 +129,22 @@ uppy.on('upload-success', function (file, data, uploadURL) {
123
129
  Shrine. From there you can swap the `presign_endpoint` + `AwsS3` code with the
124
130
  `uppy_s3_multipart` + `AwsS3Multipart` setup.**
125
131
 
126
- Both the plugin and method accepts `:options` for specifying additional options
127
- to the aws-sdk calls (read further for more details on these options):
132
+ Note that by default **Shrine won't extract metadata from directly upload
133
+ files**, instead it will just copy metadata that was extracted on the client
134
+ side. See [this section][metadata direct uploads] for the rationale and
135
+ instructions on how to opt in.
136
+
137
+ If you want to make uploads public and have public URLs without query
138
+ parameters returned, you can pass `public: true` to the Shrine storage (note
139
+ that this is supported starting from Shrine 2.13).
140
+
141
+ ```rb
142
+ Shrine::Storage::S3.new(public: true, **options)
143
+ ```
144
+
145
+ Both the plugin and method accept `:options` for specifying additional options
146
+ to the S3 client operations (see the [Client](#client) section for list of
147
+ operations and options they accept):
128
148
 
129
149
  ```rb
130
150
  Shrine.plugin :uppy_s3_multipart, options: {
@@ -138,14 +158,11 @@ Shrine.uppy_s3_multipart(:cache, options: {
138
158
  })
139
159
  ```
140
160
 
141
- Note that by default **Shrine won't extract metadata from directly upload
142
- files**, instead it will just copy metadata that was extracted on the client
143
- side. See [this section][metadata direct uploads] for the rationale and
144
- instructions on how to opt in.
161
+ In the dynamic version the yielded object is an instance of [`Rack::Request`].
145
162
 
146
- ### Standalone
163
+ ### App
147
164
 
148
- You can also use `uppy-s3_multipart` without Shrine. Start by initializing the
165
+ You can also use `uppy-s3_multipart` without Shrine, by initializing the
149
166
  `Uppy::S3Multipart::App` directly:
150
167
 
151
168
  ```rb
@@ -162,16 +179,16 @@ bucket = resource.bucket("my-bucket")
162
179
  UPPY_S3_MULTIPART_APP = Uppy::S3Multipart::App.new(bucket: bucket)
163
180
  ```
164
181
 
165
- and mount it in your app in the same way:
182
+ You can mount it inside your main app in the same way:
166
183
 
167
184
  ```rb
168
185
  # Rails (config/routes.rb)
169
186
  Rails.application.routes.draw do
170
- mount UPPY_S3_MULTIPART_APP => "/s3"
187
+ mount UPPY_S3_MULTIPART_APP => "/s3/multipart"
171
188
  end
172
189
 
173
190
  # Rack (config.ru)
174
- map "/s3" do
191
+ map "/s3/multipart" do
175
192
  run UPPY_S3_MULTIPART_APP
176
193
  end
177
194
  ```
@@ -185,25 +202,34 @@ uppy.use(Uppy.AwsS3Multipart, {
185
202
  })
186
203
  ```
187
204
 
188
- The `Uppy::S3Mutipart::App` initializer accepts `:options` for specifying
189
- additional options to the aws-sdk calls (read further for more details on these
190
- options):
205
+ If you want to make uploads public and have public URLs without query
206
+ parameters returned, you can pass `public: true` to the app.
207
+
208
+ ```rb
209
+ Uppy::S3Multipart::App.new(bucket: bucket, public: true)
210
+ ```
211
+
212
+ You can also pass `:options` for specifying additional options to the S3 client
213
+ operations (see the [Client](#client) section for list of operations and
214
+ options they accept):
191
215
 
192
216
  ```rb
193
217
  Uppy::S3Multipart::App.new(bucket: bucket, options: {
194
- create_multipart_upload: { acl: "public-read" }
218
+ create_multipart_upload: { acl: "public-read" } # static
195
219
  })
196
220
 
197
221
  # OR
198
222
 
199
223
  Uppy::S3Multipart::App.new(bucket: bucket, options: {
200
- create_multipart_upload: -> (request) { { acl: "public-read" } }
224
+ create_multipart_upload: -> (request) { { acl: "public-read" } } # dynamic
201
225
  })
202
226
  ```
203
227
 
204
- ### Custom implementation
228
+ In the dynamic version the yielded object is an instance of [`Rack::Request`].
229
+
230
+ ### Client
205
231
 
206
- If you would rather implement the endpoints yourself, you can utilize
232
+ If you would rather implement the endpoints yourself, you can utilize the
207
233
  `Uppy::S3Multipart::Client` to make S3 requests.
208
234
 
209
235
  ```rb
@@ -223,13 +249,13 @@ client.create_multipart_upload(key: "foo", **options)
223
249
 
224
250
  Accepts:
225
251
 
226
- * `:key` -- object key
252
+ * `:key` object key
227
253
  * additional options for [`Aws::S3::Client#create_multipart_upload`]
228
254
 
229
255
  Returns:
230
256
 
231
- * `:upload_id` -- id of the created multipart upload
232
- * `:key` -- object key
257
+ * `:upload_id` id of the created multipart upload
258
+ * `:key` object key
233
259
 
234
260
  #### `#list_parts`
235
261
 
@@ -244,17 +270,17 @@ client.list_parts(upload_id: "MultipartUploadId", key: "foo", **options)
244
270
 
245
271
  Accepts:
246
272
 
247
- * `:upload_id` -- multipart upload id
248
- * `:key` -- object key
273
+ * `:upload_id` multipart upload id
274
+ * `:key` object key
249
275
  * additional options for [`Aws::S3::Client#list_parts`]
250
276
 
251
277
  Returns:
252
278
 
253
279
  * array of parts
254
280
 
255
- - `:part_number` -- position of the part
256
- - `:size` -- filesize of the part
257
- - `:etag` -- etag of the part
281
+ - `:part_number` position of the part
282
+ - `:size` filesize of the part
283
+ - `:etag` etag of the part
258
284
 
259
285
  #### `#prepare_upload_part`
260
286
 
@@ -267,14 +293,14 @@ client.prepare_upload_part(upload_id: "MultipartUploadId", key: "foo", part_numb
267
293
 
268
294
  Accepts:
269
295
 
270
- * `:upload_id` -- multipart upload id
271
- * `:key` -- object key
272
- * `:part_number` -- number of the next part
296
+ * `:upload_id` multipart upload id
297
+ * `:key` object key
298
+ * `:part_number` number of the next part
273
299
  * additional options for [`Aws::S3::Client#upload_part`] and [`Aws::S3::Presigner#presigned_url`]
274
300
 
275
301
  Returns:
276
302
 
277
- * `:url` -- endpoint that should be used for uploading a new multipart part via a `PUT` request
303
+ * `:url` endpoint that should be used for uploading a new multipart part via a `PUT` request
278
304
 
279
305
  #### `#complete_multipart_upload`
280
306
 
@@ -287,14 +313,36 @@ client.complete_multipart_upload(upload_id: upload_id, key: key, parts: [{ part_
287
313
 
288
314
  Accepts:
289
315
 
290
- * `:upload_id` -- multipart upload id
291
- * `:key` -- object key
292
- * `:parts` -- list of all uploaded parts, consisting of `:part_number` and `:etag`
316
+ * `:upload_id` multipart upload id
317
+ * `:key` object key
318
+ * `:parts` list of all uploaded parts, consisting of `:part_number` and `:etag`
293
319
  * additional options for [`Aws::S3::Client#complete_multipart_upload`]
294
320
 
295
321
  Returns:
296
322
 
297
- * `:location` -- URL to the uploaded object
323
+ * `:location` URL to the uploaded object
324
+
325
+ #### `#object_url`
326
+
327
+ Generates URL to the object.
328
+
329
+ ```rb
330
+ client.object_url(key: key, **options)
331
+ # => "https://my-bucket.s3.amazonaws.com/foo?..."
332
+ ```
333
+
334
+ This is called after `#complete_multipart_upload` in the app and returned in
335
+ the response.
336
+
337
+ Accepts:
338
+
339
+ * `:key` – object key
340
+ * `:public` – for generating a public URL (default is presigned expiring URL)
341
+ * additional options for [`Aws::S3::Object#presigned_url`] and [`Aws::S3::Client#get_object`]
342
+
343
+ Returns:
344
+
345
+ * URL to the object
298
346
 
299
347
  #### `#abort_multipart_upload`
300
348
 
@@ -307,8 +355,8 @@ client.abort_multipart_upload(upload_id: upload_id, key: key, **options)
307
355
 
308
356
  Accepts:
309
357
 
310
- * `:upload_id` -- multipart upload id
311
- * `:key` -- object key
358
+ * `:upload_id` multipart upload id
359
+ * `:key` object key
312
360
  * additional options for [`Aws::S3::Client#abort_multipart_upload`]
313
361
 
314
362
  ## Contributing
@@ -331,10 +379,13 @@ License](https://opensource.org/licenses/MIT).
331
379
  [AwsS3Multipart]: https://uppy.io/docs/aws-s3-multipart/
332
380
  [Shrine]: https://shrinerb.com
333
381
  [Adding Direct S3 Uploads]: https://github.com/shrinerb/shrine/wiki/Adding-Direct-S3-Uploads
382
+ [`Rack::Request`]: https://www.rubydoc.info/github/rack/rack/master/Rack/Request
334
383
  [`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
335
384
  [`Aws::S3::Client#list_parts`]: https://docs.aws.amazon.com/sdk-for-ruby/v3/api/Aws/S3/Client.html#list_parts-instance_method
336
385
  [`Aws::S3::Client#upload_part`]: https://docs.aws.amazon.com/sdk-for-ruby/v3/api/Aws/S3/Client.html#upload_part-instance_method
337
386
  [`Aws::S3::Presigner#presigned_url`]: https://docs.aws.amazon.com/sdk-for-ruby/v3/api/Aws/S3/Presigner.html#presigned_url-instance_method
338
387
  [`Aws::S3::Client#complete_multipart_upload`]: https://docs.aws.amazon.com/sdk-for-ruby/v3/api/Aws/S3/Client.html#complete_multipart_upload-instance_method
388
+ [`Aws::S3::Object#presigned_url`]: https://docs.aws.amazon.com/sdk-for-ruby/v3/api/Aws/S3/Object.html#presigned_url-instance_method
389
+ [`Aws::S3::Client#get_object`]: https://docs.aws.amazon.com/sdk-for-ruby/v3/api/Aws/S3/Client.html#get_object-instance_method
339
390
  [`Aws::S3::Client#abort_multipart_upload`]: https://docs.aws.amazon.com/sdk-for-ruby/v3/api/Aws/S3/Client.html#abort_multipart_upload-instance_method
340
391
  [metadata direct uploads]: https://github.com/shrinerb/shrine/blob/master/doc/metadata.md#direct-uploads
@@ -15,12 +15,12 @@ class Shrine
15
15
  fail Error, "expected storage to be a Shrine::Storage::S3, but was #{s3.inspect}"
16
16
  end
17
17
 
18
- ::Uppy::S3Multipart::App.new(
19
- bucket: s3.bucket,
20
- prefix: s3.prefix,
21
- options: opts[:uppy_s3_multipart_options],
22
- **options
23
- )
18
+ options[:bucket] ||= s3.bucket
19
+ options[:prefix] ||= s3.prefix
20
+ options[:public] ||= s3.public if s3.respond_to?(:public)
21
+ options[:options] ||= opts[:uppy_s3_multipart_options]
22
+
23
+ ::Uppy::S3Multipart::App.new(**options)
24
24
  end
25
25
  end
26
26
  end
@@ -8,10 +8,11 @@ require "cgi"
8
8
  module Uppy
9
9
  module S3Multipart
10
10
  class App
11
- def initialize(bucket:, prefix: nil, options: {})
11
+ def initialize(bucket:, prefix: nil, public: nil, options: {})
12
12
  @router = Class.new(Router)
13
13
  @router.opts[:client] = Client.new(bucket: bucket)
14
14
  @router.opts[:prefix] = prefix
15
+ @router.opts[:public] = public
15
16
  @router.opts[:options] = options
16
17
  end
17
18
 
@@ -24,10 +25,14 @@ module Uppy
24
25
  plugin :json
25
26
  plugin :json_parser
26
27
  plugin :halt
28
+ plugin :path_rewriter
29
+
30
+ # allow mounting on "/s3" for backwards compatibility
31
+ rewrite_path "/multipart", ""
27
32
 
28
33
  route do |r|
29
- # POST /multipart
30
- r.post "multipart" do
34
+ # POST /s3/multipart
35
+ r.post true do
31
36
  content_type = r.params["type"]
32
37
  filename = r.params["filename"]
33
38
 
@@ -38,13 +43,16 @@ module Uppy
38
43
  # CGI-escape the filename because aws-sdk's signature calculator trips on special characters
39
44
  content_disposition = "inline; filename=\"#{CGI.escape(filename)}\"" if filename
40
45
 
41
- result = client_call(:create_multipart_upload, key: key, content_type: content_type, content_disposition: content_disposition)
46
+ options = { content_type: content_type, content_disposition: content_disposition }
47
+ options[:acl] = "public-read" if opts[:public]
48
+
49
+ result = client_call(:create_multipart_upload, key: key, **options)
42
50
 
43
51
  { uploadId: result.fetch(:upload_id), key: result.fetch(:key) }
44
52
  end
45
53
 
46
- # GET /multipart/:uploadId
47
- r.get "multipart", String do |upload_id|
54
+ # GET /s3/multipart/:uploadId
55
+ r.get String do |upload_id|
48
56
  key = param!("key")
49
57
 
50
58
  result = client_call(:list_parts, upload_id: upload_id, key: key)
@@ -54,8 +62,8 @@ module Uppy
54
62
  end
55
63
  end
56
64
 
57
- # GET /multipart/:uploadId/:partNumber
58
- r.get "multipart", String, String do |upload_id, part_number|
65
+ # GET /s3/multipart/:uploadId/:partNumber
66
+ r.get String, String do |upload_id, part_number|
59
67
  key = param!("key")
60
68
 
61
69
  result = client_call(:prepare_upload_part, upload_id: upload_id, key: key, part_number: part_number)
@@ -63,8 +71,8 @@ module Uppy
63
71
  { url: result.fetch(:url) }
64
72
  end
65
73
 
66
- # POST /multipart/:uploadId/complete
67
- r.post "multipart", String, "complete" do |upload_id|
74
+ # POST /s3/multipart/:uploadId/complete
75
+ r.post String, "complete" do |upload_id|
68
76
  key = param!("key")
69
77
  parts = param!("parts")
70
78
 
@@ -72,17 +80,19 @@ module Uppy
72
80
  begin
73
81
  { part_number: part.fetch("PartNumber"), etag: part.fetch("ETag") }
74
82
  rescue KeyError
75
- r.halt 400, { error: "At least one part is missing \"PartNumber\" or \"ETag\" field" }
83
+ error! "At least one part is missing \"PartNumber\" or \"ETag\" field"
76
84
  end
77
85
  end
78
86
 
79
- result = client_call(:complete_multipart_upload, upload_id: upload_id, key: key, parts: parts)
87
+ client_call(:complete_multipart_upload, upload_id: upload_id, key: key, parts: parts)
88
+
89
+ object_url = client_call(:object_url, key: key, public: opts[:public])
80
90
 
81
- { location: result.fetch(:location) }
91
+ { location: object_url }
82
92
  end
83
93
 
84
- # DELETE /multipart/:uploadId
85
- r.delete "multipart", String do |upload_id|
94
+ # DELETE /s3/multipart/:uploadId
95
+ r.delete String do |upload_id|
86
96
  key = param!("key")
87
97
 
88
98
  client_call(:abort_multipart_upload, upload_id: upload_id, key: key)
@@ -105,10 +115,14 @@ module Uppy
105
115
  def param!(name)
106
116
  value = request.params[name]
107
117
 
108
- request.halt 400, { error: "Missing \"#{name}\" parameter" } if value.nil?
118
+ error! "Missing \"#{name}\" parameter" if value.nil?
109
119
 
110
120
  value
111
121
  end
122
+
123
+ def error!(message)
124
+ request.halt 400, { error: message }
125
+ end
112
126
  end
113
127
  end
114
128
  end
@@ -43,7 +43,15 @@ module Uppy
43
43
  **options
44
44
  )
45
45
 
46
- { location: object(key).presigned_url(:get) }
46
+ { location: object_url(key: key) }
47
+ end
48
+
49
+ def object_url(key:, public: nil, **options)
50
+ if public
51
+ object(key).public_url(**options)
52
+ else
53
+ object(key).presigned_url(:get, **options)
54
+ end
47
55
  end
48
56
 
49
57
  def abort_multipart_upload(upload_id:, key:, **options)
@@ -1,6 +1,6 @@
1
1
  Gem::Specification.new do |gem|
2
2
  gem.name = "uppy-s3_multipart"
3
- gem.version = "0.1.1"
3
+ gem.version = "0.2.0"
4
4
 
5
5
  gem.required_ruby_version = ">= 2.2"
6
6
 
@@ -19,7 +19,7 @@ Gem::Specification.new do |gem|
19
19
  gem.add_development_dependency "rake"
20
20
  gem.add_development_dependency "minitest"
21
21
  gem.add_development_dependency "rack-test_app"
22
- gem.add_development_dependency "shrine", "~> 2.0"
22
+ gem.add_development_dependency "shrine", "~> 2.13"
23
23
  gem.add_development_dependency "shrine-memory"
24
24
  gem.add_development_dependency "aws-sdk-core", "~> 3.23"
25
25
  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.1.1
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Janko Marohnić
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2018-11-20 00:00:00.000000000 Z
11
+ date: 2018-12-08 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: roda
@@ -92,14 +92,14 @@ dependencies:
92
92
  requirements:
93
93
  - - "~>"
94
94
  - !ruby/object:Gem::Version
95
- version: '2.0'
95
+ version: '2.13'
96
96
  type: :development
97
97
  prerelease: false
98
98
  version_requirements: !ruby/object:Gem::Requirement
99
99
  requirements:
100
100
  - - "~>"
101
101
  - !ruby/object:Gem::Version
102
- version: '2.0'
102
+ version: '2.13'
103
103
  - !ruby/object:Gem::Dependency
104
104
  name: shrine-memory
105
105
  requirement: !ruby/object:Gem::Requirement