uppy-s3_multipart 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: 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