uppy-s3_multipart 0.1.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/LICENSE.txt +21 -0
- data/README.md +309 -0
- data/lib/shrine/plugins/uppy_s3_multipart.rb +28 -0
- data/lib/uppy-s3_multipart.rb +1 -0
- data/lib/uppy/s3_multipart.rb +2 -0
- data/lib/uppy/s3_multipart/app.rb +115 -0
- data/lib/uppy/s3_multipart/client.rb +81 -0
- data/uppy-s3_multipart.gemspec +24 -0
- metadata +157 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: c747885d4a3c5e57af416694daa5ad809831f42a73e03ea0f72c6be2106bca4b
|
4
|
+
data.tar.gz: 3dfdb337a8d1ff8f9096018233435d4e3a8b1cc71092ca10fae0582b660e1d1a
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 8366713f060f860ecaaf98177751471a3cbab32bcb4e29414fea742a13bd90ffa6b46845371a10c4deb90273bdfe0a5c5c6db68f579ad6d502f479dcd2fbc4bc
|
7
|
+
data.tar.gz: e2aa4485b564ba814ddea95b0d77d8872b9afde9baedbd2c0c298cd5434054c2bc56ce6eac48722cddd9719a7bc3f44ccecba696a8f34a1bd86297c72fbec47d
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2018 Janko Marohnić
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
13
|
+
all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,309 @@
|
|
1
|
+
# Uppy::S3Multipart
|
2
|
+
|
3
|
+
Provides a Rack application that implements endpoints for the [AwsS3Multipart]
|
4
|
+
Uppy plugin. This enables multipart uploads directly to S3, which is
|
5
|
+
recommended when dealing with large files, as it allows resuming interrupted
|
6
|
+
uploads.
|
7
|
+
|
8
|
+
## Installation
|
9
|
+
|
10
|
+
Add the gem to your Gemfile:
|
11
|
+
|
12
|
+
```rb
|
13
|
+
gem "uppy-s3_multipart"
|
14
|
+
```
|
15
|
+
|
16
|
+
## Setup
|
17
|
+
|
18
|
+
Once you've created your S3 bucket, you need to set up CORS for it. The
|
19
|
+
following script sets up minimal CORS configuration needed for multipart
|
20
|
+
uploads on your bucket using the `aws-sdk-s3` gem:
|
21
|
+
|
22
|
+
```rb
|
23
|
+
require "aws-sdk-s3"
|
24
|
+
|
25
|
+
client = Aws::S3::Client.new(
|
26
|
+
access_key_id: "<YOUR KEY>",
|
27
|
+
secret_access_key: "<YOUR SECRET>",
|
28
|
+
region: "<REGION>",
|
29
|
+
)
|
30
|
+
|
31
|
+
client.put_bucket_cors(
|
32
|
+
bucket: "<YOUR BUCKET>",
|
33
|
+
cors_configuration: {
|
34
|
+
cors_rules: [{
|
35
|
+
allowed_headers: ["Authorization", "Content-Type", "Origin", "ETag"],
|
36
|
+
allowed_methods: ["GET", "POST", "PUT", "DELETE"],
|
37
|
+
allowed_origins: ["*"],
|
38
|
+
max_age_seconds: 3000,
|
39
|
+
}]
|
40
|
+
}
|
41
|
+
)
|
42
|
+
```
|
43
|
+
|
44
|
+
## Usage
|
45
|
+
|
46
|
+
This gem provides a Rack application that you can mount inside your main
|
47
|
+
application. If you're using [Shrine], you can initialize the Rack application
|
48
|
+
via the `uppy_s3_multipart` Shrine plugin, otherwise you can initialize it
|
49
|
+
directly.
|
50
|
+
|
51
|
+
### Shrine
|
52
|
+
|
53
|
+
In the initializer load the `uppy_s3_multipart` plugin:
|
54
|
+
|
55
|
+
```rb
|
56
|
+
require "shrine"
|
57
|
+
require "shrine/storage/s3"
|
58
|
+
|
59
|
+
Shrine.storages = {
|
60
|
+
cache: Shrine::Storage::S3.new(...),
|
61
|
+
store: Shrine::Storage::S3.new(...),
|
62
|
+
}
|
63
|
+
|
64
|
+
# ...
|
65
|
+
Shrine.plugin :uppy_s3_multipart # load the plugin
|
66
|
+
```
|
67
|
+
|
68
|
+
The plugin will provide a `Shrine.uppy_s3_multipart` method, which returns an
|
69
|
+
instance of `Uppy::S3Multipart::App`, which is a Rack app that you can mount
|
70
|
+
inside your main application:
|
71
|
+
|
72
|
+
```rb
|
73
|
+
# Rails (config/routes.rb)
|
74
|
+
Rails.application.routes.draw do
|
75
|
+
mount Shrine.uppy_s3_multipart(:cache) => "/s3"
|
76
|
+
end
|
77
|
+
|
78
|
+
# Rack (config.ru)
|
79
|
+
map "/s3" do
|
80
|
+
run Shrine.uppy_s3_multipart(:cache)
|
81
|
+
end
|
82
|
+
```
|
83
|
+
|
84
|
+
This will add the routes that the `AwsS3Multipart` Uppy plugin expects:
|
85
|
+
|
86
|
+
```
|
87
|
+
POST /s3/multipart
|
88
|
+
GET /s3/multipart/:uploadId
|
89
|
+
GET /s3/multipart/:uploadId/:partNumber
|
90
|
+
POST /s3/multipart/:uploadId/complete
|
91
|
+
DELETE /s3/multipart/:uploadId
|
92
|
+
```
|
93
|
+
|
94
|
+
Finally, in your Uppy configuration pass your app's URL as the `serverUrl`:
|
95
|
+
|
96
|
+
```js
|
97
|
+
// ...
|
98
|
+
uppy.use(Uppy.AwsS3Multipart, {
|
99
|
+
serverUrl: "https://your-app.com/",
|
100
|
+
})
|
101
|
+
```
|
102
|
+
|
103
|
+
Both the plugin and method accepts `:options` for specifying additional options
|
104
|
+
to the aws-sdk calls (read further for more details on these options):
|
105
|
+
|
106
|
+
```rb
|
107
|
+
Shrine.plugin :uppy_s3_multipart, options: {
|
108
|
+
create_multipart_upload: { acl: "public-read" } # static
|
109
|
+
}
|
110
|
+
|
111
|
+
# OR
|
112
|
+
|
113
|
+
Shrine.uppy_s3_multipart(:cache, options: {
|
114
|
+
create_multipart_upload: -> (request) { { acl: "public-read" } } # dynamic
|
115
|
+
})
|
116
|
+
```
|
117
|
+
|
118
|
+
### Standalone
|
119
|
+
|
120
|
+
You can also initialize `Uppy::S3Multipart::App` directly:
|
121
|
+
|
122
|
+
```rb
|
123
|
+
require "uppy/s3_multipart"
|
124
|
+
|
125
|
+
resource = Aws::S3::Resource.new(
|
126
|
+
access_key_id: "...",
|
127
|
+
secret_access_key: "...",
|
128
|
+
region: "...",
|
129
|
+
)
|
130
|
+
|
131
|
+
bucket = resource.bucket("my-bucket")
|
132
|
+
|
133
|
+
UPPY_S3_MULTIPART_APP = Uppy::S3Multipart::App.new(bucket: bucket)
|
134
|
+
```
|
135
|
+
|
136
|
+
and mount it in your app in the same way:
|
137
|
+
|
138
|
+
```rb
|
139
|
+
# Rails (config/routes.rb)
|
140
|
+
Rails.application.routes.draw do
|
141
|
+
mount UPPY_S3_MULTIPART_APP => "/s3"
|
142
|
+
end
|
143
|
+
|
144
|
+
# Rack (config.ru)
|
145
|
+
map "/s3" do
|
146
|
+
run UPPY_S3_MULTIPART_APP
|
147
|
+
end
|
148
|
+
```
|
149
|
+
|
150
|
+
In your Uppy configuration point the `serverUrl` to your application:
|
151
|
+
|
152
|
+
```js
|
153
|
+
// ...
|
154
|
+
uppy.use(Uppy.AwsS3Multipart, {
|
155
|
+
serverUrl: "https://your-app.com/",
|
156
|
+
})
|
157
|
+
```
|
158
|
+
|
159
|
+
The `Uppy::S3Mutipart::App` initializer accepts `:options` for specifying
|
160
|
+
additional options to the aws-sdk calls (read further for more details on these
|
161
|
+
options):
|
162
|
+
|
163
|
+
```rb
|
164
|
+
Uppy::S3Multipart::App.new(bucket: bucket, options: {
|
165
|
+
create_multipart_upload: { acl: "public-read" }
|
166
|
+
})
|
167
|
+
|
168
|
+
# OR
|
169
|
+
|
170
|
+
Uppy::S3Multipart::App.new(bucket: bucket, options: {
|
171
|
+
create_multipart_upload: -> (request) { { acl: "public-read" } }
|
172
|
+
})
|
173
|
+
```
|
174
|
+
|
175
|
+
### Custom implementation
|
176
|
+
|
177
|
+
If you would rather implement the endpoints yourself, you can utilize
|
178
|
+
`Uppy::S3Multipart::Client` to make S3 requests.
|
179
|
+
|
180
|
+
```rb
|
181
|
+
require "uppy/s3_multipart/client"
|
182
|
+
|
183
|
+
client = Uppy::S3Multipart::Client.new(bucket: bucket)
|
184
|
+
```
|
185
|
+
|
186
|
+
#### `create_multipart_upload`
|
187
|
+
|
188
|
+
Initiates a new multipart upload.
|
189
|
+
|
190
|
+
```rb
|
191
|
+
client.create_multipart_upload(key: "foo", **options)
|
192
|
+
#=> { upload_id: "MultipartUploadId", key: "foo" }
|
193
|
+
```
|
194
|
+
|
195
|
+
Accepts:
|
196
|
+
|
197
|
+
* `:key` -- object key
|
198
|
+
* additional options for [`Aws::S3::Client#create_multipart_upload`]
|
199
|
+
|
200
|
+
Returns:
|
201
|
+
|
202
|
+
* `:upload_id` -- id of the created multipart upload
|
203
|
+
* `:key` -- object key
|
204
|
+
|
205
|
+
#### `#list_parts`
|
206
|
+
|
207
|
+
Retrieves currently uploaded parts of a multipart upload.
|
208
|
+
|
209
|
+
```rb
|
210
|
+
client.list_parts(upload_id: "MultipartUploadId", key: "foo", **options)
|
211
|
+
#=> [ { part_number: 1, size: 5402383, etag: "etag1" },
|
212
|
+
# { part_number: 2, size: 5982742, etag: "etag2" },
|
213
|
+
# ... ]
|
214
|
+
```
|
215
|
+
|
216
|
+
Accepts:
|
217
|
+
|
218
|
+
* `:upload_id` -- multipart upload id
|
219
|
+
* `:key` -- object key
|
220
|
+
* additional options for [`Aws::S3::Client#list_parts`]
|
221
|
+
|
222
|
+
Returns:
|
223
|
+
|
224
|
+
* array of parts
|
225
|
+
|
226
|
+
- `:part_number` -- position of the part
|
227
|
+
- `:size` -- filesize of the part
|
228
|
+
- `:etag` -- etag of the part
|
229
|
+
|
230
|
+
#### `#prepare_upload_part`
|
231
|
+
|
232
|
+
Returns the endpoint that should be used for uploading a new multipart part.
|
233
|
+
|
234
|
+
```rb
|
235
|
+
client.prepare_upload_part(upload_id: "MultipartUploadId", key: "foo", part_number: 1, **options)
|
236
|
+
#=> { url: "https://my-bucket.s3.amazonaws.com/foo?partNumber=1&uploadId=MultipartUploadId&..." }
|
237
|
+
```
|
238
|
+
|
239
|
+
Accepts:
|
240
|
+
|
241
|
+
* `:upload_id` -- multipart upload id
|
242
|
+
* `:key` -- object key
|
243
|
+
* `:part_number` -- number of the next part
|
244
|
+
* additional options for [`Aws::S3::Client#upload_part`] and [`Aws::S3::Presigner#presigned_url`]
|
245
|
+
|
246
|
+
Returns:
|
247
|
+
|
248
|
+
* `:url` -- endpoint that should be used for uploading a new multipart part via a `PUT` request
|
249
|
+
|
250
|
+
#### `#complete_multipart_upload`
|
251
|
+
|
252
|
+
Finalizes the multipart upload and returns URL to the object.
|
253
|
+
|
254
|
+
```rb
|
255
|
+
client.complete_multipart_upload(upload_id: upload_id, key: key, parts: [{ part_number: 1, etag: "etag1" }], **options)
|
256
|
+
#=> { location: "https://my-bucket.s3.amazonaws.com/foo?..." }
|
257
|
+
```
|
258
|
+
|
259
|
+
Accepts:
|
260
|
+
|
261
|
+
* `:upload_id` -- multipart upload id
|
262
|
+
* `:key` -- object key
|
263
|
+
* `:parts` -- list of all uploaded parts, consisting of `:part_number` and `:etag`
|
264
|
+
* additional options for [`Aws::S3::Client#complete_multipart_upload`]
|
265
|
+
|
266
|
+
Returns:
|
267
|
+
|
268
|
+
* `:location` -- URL to the uploaded object
|
269
|
+
|
270
|
+
#### `#abort_multipart_upload`
|
271
|
+
|
272
|
+
Aborts the multipart upload, removing all parts uploaded so far.
|
273
|
+
|
274
|
+
```rb
|
275
|
+
client.abort_multipart_upload(upload_id: upload_id, key: key, **options)
|
276
|
+
#=> {}
|
277
|
+
```
|
278
|
+
|
279
|
+
Accepts:
|
280
|
+
|
281
|
+
* `:upload_id` -- multipart upload id
|
282
|
+
* `:key` -- object key
|
283
|
+
* additional options for [`Aws::S3::Client#abort_multipart_upload`]
|
284
|
+
|
285
|
+
## Contributing
|
286
|
+
|
287
|
+
You can run the test suite with
|
288
|
+
|
289
|
+
```
|
290
|
+
$ bundle exec rake test
|
291
|
+
```
|
292
|
+
|
293
|
+
This project is intended to be a safe, welcoming space for collaboration, and
|
294
|
+
contributors are expected to adhere to the [Contributor
|
295
|
+
Covenant](http://contributor-covenant.org) code of conduct.
|
296
|
+
|
297
|
+
## License
|
298
|
+
|
299
|
+
The gem is available as open source under the terms of the [MIT
|
300
|
+
License](https://opensource.org/licenses/MIT).
|
301
|
+
|
302
|
+
[AwsS3Multipart]: https://uppy.io/docs/aws-s3-multipart/
|
303
|
+
[Shrine]: https://shrinerb.com
|
304
|
+
[`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
|
305
|
+
[`Aws::S3::Client#list_parts`]: https://docs.aws.amazon.com/sdk-for-ruby/v3/api/Aws/S3/Client.html#list_parts-instance_method
|
306
|
+
[`Aws::S3::Client#upload_part`]: https://docs.aws.amazon.com/sdk-for-ruby/v3/api/Aws/S3/Client.html#upload_part-instance_method
|
307
|
+
[`Aws::S3::Presigner#presigned_url`]: https://docs.aws.amazon.com/sdk-for-ruby/v3/api/Aws/S3/Presigner.html#presigned_url-instance_method
|
308
|
+
[`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
|
309
|
+
[`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
|
@@ -0,0 +1,28 @@
|
|
1
|
+
require "uppy/s3_multipart"
|
2
|
+
|
3
|
+
require "securerandom"
|
4
|
+
|
5
|
+
class Shrine
|
6
|
+
module Plugins
|
7
|
+
module UppyS3Multipart
|
8
|
+
def self.configure(uploader, options = {})
|
9
|
+
uploader.opts[:uppy_s3_multipart_options] = (uploader.opts[:uppy_s3_multipart_options] || {}).merge(options[:options] || {})
|
10
|
+
end
|
11
|
+
|
12
|
+
module ClassMethods
|
13
|
+
def uppy_s3_multipart(storage_key, **options)
|
14
|
+
s3 = find_storage(storage_key)
|
15
|
+
|
16
|
+
::Uppy::S3Multipart::App.new(
|
17
|
+
bucket: s3.bucket,
|
18
|
+
prefix: s3.prefix,
|
19
|
+
options: opts[:uppy_s3_multipart_options],
|
20
|
+
**options
|
21
|
+
)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
register_plugin(:uppy_s3_multipart, UppyS3Multipart)
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
require "uppy/s3_multipart"
|
@@ -0,0 +1,115 @@
|
|
1
|
+
require "uppy/s3_multipart/client"
|
2
|
+
|
3
|
+
require "roda"
|
4
|
+
|
5
|
+
require "securerandom"
|
6
|
+
require "cgi"
|
7
|
+
|
8
|
+
module Uppy
|
9
|
+
module S3Multipart
|
10
|
+
class App
|
11
|
+
def initialize(bucket:, prefix: nil, options: {})
|
12
|
+
@router = Class.new(Router)
|
13
|
+
@router.opts[:client] = Client.new(bucket: bucket)
|
14
|
+
@router.opts[:prefix] = prefix
|
15
|
+
@router.opts[:options] = options
|
16
|
+
end
|
17
|
+
|
18
|
+
def call(env)
|
19
|
+
@router.call(env)
|
20
|
+
end
|
21
|
+
|
22
|
+
class Router < Roda
|
23
|
+
plugin :all_verbs
|
24
|
+
plugin :json
|
25
|
+
plugin :json_parser
|
26
|
+
plugin :halt
|
27
|
+
|
28
|
+
route do |r|
|
29
|
+
# POST /multipart
|
30
|
+
r.post "multipart" do
|
31
|
+
content_type = r.params["type"]
|
32
|
+
filename = r.params["filename"]
|
33
|
+
|
34
|
+
extension = File.extname(filename.to_s)
|
35
|
+
key = SecureRandom.hex + extension
|
36
|
+
key = "#{opts[:prefix]}/#{key}" if opts[:prefix]
|
37
|
+
|
38
|
+
# CGI-escape the filename because aws-sdk's signature calculator trips on special characters
|
39
|
+
content_disposition = "inline; filename=\"#{CGI.escape(filename)}\"" if filename
|
40
|
+
|
41
|
+
result = client_call(:create_multipart_upload, key: key, content_type: content_type, content_disposition: content_disposition)
|
42
|
+
|
43
|
+
{ uploadId: result.fetch(:upload_id), key: result.fetch(:key) }
|
44
|
+
end
|
45
|
+
|
46
|
+
# GET /multipart/:uploadId
|
47
|
+
r.get "multipart", String do |upload_id|
|
48
|
+
key = param!("key")
|
49
|
+
|
50
|
+
result = client_call(:list_parts, upload_id: upload_id, key: key)
|
51
|
+
|
52
|
+
result.map do |part|
|
53
|
+
{ PartNumber: part.fetch(:part_number), Size: part.fetch(:size), ETag: part.fetch(:etag) }
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
# GET /multipart/:uploadId/:partNumber
|
58
|
+
r.get "multipart", String, String do |upload_id, part_number|
|
59
|
+
key = param!("key")
|
60
|
+
|
61
|
+
result = client_call(:prepare_upload_part, upload_id: upload_id, key: key, part_number: part_number)
|
62
|
+
|
63
|
+
{ url: result.fetch(:url) }
|
64
|
+
end
|
65
|
+
|
66
|
+
# POST /multipart/:uploadId/complete
|
67
|
+
r.post "multipart", String, "complete" do |upload_id|
|
68
|
+
key = param!("key")
|
69
|
+
parts = param!("parts")
|
70
|
+
|
71
|
+
parts = parts.map do |part|
|
72
|
+
begin
|
73
|
+
{ part_number: part.fetch("PartNumber"), etag: part.fetch("ETag") }
|
74
|
+
rescue KeyError
|
75
|
+
r.halt 400, { error: "At least one part is missing \"PartNumber\" or \"ETag\" field" }
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
result = client_call(:complete_multipart_upload, upload_id: upload_id, key: key, parts: parts)
|
80
|
+
|
81
|
+
{ location: result.fetch(:location) }
|
82
|
+
end
|
83
|
+
|
84
|
+
# DELETE /multipart/:uploadId
|
85
|
+
r.delete "multipart", String do |upload_id|
|
86
|
+
key = param!("key")
|
87
|
+
|
88
|
+
client_call(:abort_multipart_upload, upload_id: upload_id, key: key)
|
89
|
+
|
90
|
+
{}
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
def client_call(operation, **options)
|
95
|
+
client = opts[:client]
|
96
|
+
|
97
|
+
overrides = opts[:options][operation] || {}
|
98
|
+
overrides = overrides.call(request) if overrides.respond_to?(:call)
|
99
|
+
|
100
|
+
options = options.merge(overrides)
|
101
|
+
|
102
|
+
client.send(operation, **options)
|
103
|
+
end
|
104
|
+
|
105
|
+
def param!(name)
|
106
|
+
value = request.params[name]
|
107
|
+
|
108
|
+
request.halt 400, { error: "Missing \"#{name}\" parameter" } if value.nil?
|
109
|
+
|
110
|
+
value
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
@@ -0,0 +1,81 @@
|
|
1
|
+
require "aws-sdk-s3"
|
2
|
+
|
3
|
+
module Uppy
|
4
|
+
module S3Multipart
|
5
|
+
class Client
|
6
|
+
attr_reader :bucket
|
7
|
+
|
8
|
+
def initialize(bucket:)
|
9
|
+
@bucket = bucket
|
10
|
+
end
|
11
|
+
|
12
|
+
def create_multipart_upload(key:, **options)
|
13
|
+
multipart_upload = object(key).initiate_multipart_upload(**options)
|
14
|
+
|
15
|
+
{ upload_id: multipart_upload.id, key: multipart_upload.object_key }
|
16
|
+
end
|
17
|
+
|
18
|
+
def list_parts(upload_id:, key:, **options)
|
19
|
+
multipart_upload = multipart_upload(upload_id, key)
|
20
|
+
multipart_parts = multipart_upload.parts(**options).to_a
|
21
|
+
|
22
|
+
multipart_parts.map do |part|
|
23
|
+
{ part_number: part.part_number, size: part.size, etag: part.etag }
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def prepare_upload_part(upload_id:, key:, part_number:, **options)
|
28
|
+
presigned_url = presigner.presigned_url "upload_part",
|
29
|
+
bucket: bucket.name,
|
30
|
+
key: object(key).key,
|
31
|
+
upload_id: upload_id,
|
32
|
+
part_number: part_number,
|
33
|
+
body: "",
|
34
|
+
**options
|
35
|
+
|
36
|
+
{ url: presigned_url }
|
37
|
+
end
|
38
|
+
|
39
|
+
def complete_multipart_upload(upload_id:, key:, parts:, **options)
|
40
|
+
multipart_upload = multipart_upload(upload_id, key)
|
41
|
+
multipart_upload.complete(
|
42
|
+
multipart_upload: { parts: parts },
|
43
|
+
**options
|
44
|
+
)
|
45
|
+
|
46
|
+
{ location: object(key).presigned_url(:get) }
|
47
|
+
end
|
48
|
+
|
49
|
+
def abort_multipart_upload(upload_id:, key:, **options)
|
50
|
+
multipart_upload = multipart_upload(upload_id, key)
|
51
|
+
|
52
|
+
# aws-sdk-s3 docs recommend retrying the abort in case the multipart
|
53
|
+
# upload still has parts
|
54
|
+
loop do
|
55
|
+
multipart_upload.abort(**options)
|
56
|
+
break unless multipart_upload.parts.any?
|
57
|
+
end
|
58
|
+
|
59
|
+
{}
|
60
|
+
end
|
61
|
+
|
62
|
+
private
|
63
|
+
|
64
|
+
def multipart_upload(upload_id, key)
|
65
|
+
object(key).multipart_upload(upload_id)
|
66
|
+
end
|
67
|
+
|
68
|
+
def object(key)
|
69
|
+
bucket.object(key)
|
70
|
+
end
|
71
|
+
|
72
|
+
def presigner
|
73
|
+
Aws::S3::Presigner.new(client: client)
|
74
|
+
end
|
75
|
+
|
76
|
+
def client
|
77
|
+
bucket.client
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
Gem::Specification.new do |gem|
|
2
|
+
gem.name = "uppy-s3_multipart"
|
3
|
+
gem.version = "0.1.0"
|
4
|
+
|
5
|
+
gem.required_ruby_version = ">= 2.2"
|
6
|
+
|
7
|
+
gem.summary = "Provides a Rack application that implements endpoints for the AwsS3Multipart Uppy plugin."
|
8
|
+
gem.homepage = "https://github.com/janko-m/uppy-s3_multipart"
|
9
|
+
gem.authors = ["Janko Marohnić"]
|
10
|
+
gem.email = ["janko.marohnic@gmail.com"]
|
11
|
+
gem.license = "MIT"
|
12
|
+
|
13
|
+
gem.files = Dir["README.md", "LICENSE.txt", "lib/**/*.rb", "*.gemspec"]
|
14
|
+
gem.require_path = "lib"
|
15
|
+
|
16
|
+
gem.add_dependency "roda", ">= 2.27", "< 4"
|
17
|
+
gem.add_dependency "aws-sdk-s3", "~> 1.0"
|
18
|
+
|
19
|
+
gem.add_development_dependency "rake"
|
20
|
+
gem.add_development_dependency "minitest"
|
21
|
+
gem.add_development_dependency "rack-test_app"
|
22
|
+
gem.add_development_dependency "shrine", "~> 2.0"
|
23
|
+
gem.add_development_dependency "aws-sdk-core", "~> 3.23"
|
24
|
+
end
|
metadata
ADDED
@@ -0,0 +1,157 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: uppy-s3_multipart
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Janko Marohnić
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2018-08-27 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: roda
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '2.27'
|
20
|
+
- - "<"
|
21
|
+
- !ruby/object:Gem::Version
|
22
|
+
version: '4'
|
23
|
+
type: :runtime
|
24
|
+
prerelease: false
|
25
|
+
version_requirements: !ruby/object:Gem::Requirement
|
26
|
+
requirements:
|
27
|
+
- - ">="
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '2.27'
|
30
|
+
- - "<"
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: '4'
|
33
|
+
- !ruby/object:Gem::Dependency
|
34
|
+
name: aws-sdk-s3
|
35
|
+
requirement: !ruby/object:Gem::Requirement
|
36
|
+
requirements:
|
37
|
+
- - "~>"
|
38
|
+
- !ruby/object:Gem::Version
|
39
|
+
version: '1.0'
|
40
|
+
type: :runtime
|
41
|
+
prerelease: false
|
42
|
+
version_requirements: !ruby/object:Gem::Requirement
|
43
|
+
requirements:
|
44
|
+
- - "~>"
|
45
|
+
- !ruby/object:Gem::Version
|
46
|
+
version: '1.0'
|
47
|
+
- !ruby/object:Gem::Dependency
|
48
|
+
name: rake
|
49
|
+
requirement: !ruby/object:Gem::Requirement
|
50
|
+
requirements:
|
51
|
+
- - ">="
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '0'
|
54
|
+
type: :development
|
55
|
+
prerelease: false
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
57
|
+
requirements:
|
58
|
+
- - ">="
|
59
|
+
- !ruby/object:Gem::Version
|
60
|
+
version: '0'
|
61
|
+
- !ruby/object:Gem::Dependency
|
62
|
+
name: minitest
|
63
|
+
requirement: !ruby/object:Gem::Requirement
|
64
|
+
requirements:
|
65
|
+
- - ">="
|
66
|
+
- !ruby/object:Gem::Version
|
67
|
+
version: '0'
|
68
|
+
type: :development
|
69
|
+
prerelease: false
|
70
|
+
version_requirements: !ruby/object:Gem::Requirement
|
71
|
+
requirements:
|
72
|
+
- - ">="
|
73
|
+
- !ruby/object:Gem::Version
|
74
|
+
version: '0'
|
75
|
+
- !ruby/object:Gem::Dependency
|
76
|
+
name: rack-test_app
|
77
|
+
requirement: !ruby/object:Gem::Requirement
|
78
|
+
requirements:
|
79
|
+
- - ">="
|
80
|
+
- !ruby/object:Gem::Version
|
81
|
+
version: '0'
|
82
|
+
type: :development
|
83
|
+
prerelease: false
|
84
|
+
version_requirements: !ruby/object:Gem::Requirement
|
85
|
+
requirements:
|
86
|
+
- - ">="
|
87
|
+
- !ruby/object:Gem::Version
|
88
|
+
version: '0'
|
89
|
+
- !ruby/object:Gem::Dependency
|
90
|
+
name: shrine
|
91
|
+
requirement: !ruby/object:Gem::Requirement
|
92
|
+
requirements:
|
93
|
+
- - "~>"
|
94
|
+
- !ruby/object:Gem::Version
|
95
|
+
version: '2.0'
|
96
|
+
type: :development
|
97
|
+
prerelease: false
|
98
|
+
version_requirements: !ruby/object:Gem::Requirement
|
99
|
+
requirements:
|
100
|
+
- - "~>"
|
101
|
+
- !ruby/object:Gem::Version
|
102
|
+
version: '2.0'
|
103
|
+
- !ruby/object:Gem::Dependency
|
104
|
+
name: aws-sdk-core
|
105
|
+
requirement: !ruby/object:Gem::Requirement
|
106
|
+
requirements:
|
107
|
+
- - "~>"
|
108
|
+
- !ruby/object:Gem::Version
|
109
|
+
version: '3.23'
|
110
|
+
type: :development
|
111
|
+
prerelease: false
|
112
|
+
version_requirements: !ruby/object:Gem::Requirement
|
113
|
+
requirements:
|
114
|
+
- - "~>"
|
115
|
+
- !ruby/object:Gem::Version
|
116
|
+
version: '3.23'
|
117
|
+
description:
|
118
|
+
email:
|
119
|
+
- janko.marohnic@gmail.com
|
120
|
+
executables: []
|
121
|
+
extensions: []
|
122
|
+
extra_rdoc_files: []
|
123
|
+
files:
|
124
|
+
- LICENSE.txt
|
125
|
+
- README.md
|
126
|
+
- lib/shrine/plugins/uppy_s3_multipart.rb
|
127
|
+
- lib/uppy-s3_multipart.rb
|
128
|
+
- lib/uppy/s3_multipart.rb
|
129
|
+
- lib/uppy/s3_multipart/app.rb
|
130
|
+
- lib/uppy/s3_multipart/client.rb
|
131
|
+
- uppy-s3_multipart.gemspec
|
132
|
+
homepage: https://github.com/janko-m/uppy-s3_multipart
|
133
|
+
licenses:
|
134
|
+
- MIT
|
135
|
+
metadata: {}
|
136
|
+
post_install_message:
|
137
|
+
rdoc_options: []
|
138
|
+
require_paths:
|
139
|
+
- lib
|
140
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
141
|
+
requirements:
|
142
|
+
- - ">="
|
143
|
+
- !ruby/object:Gem::Version
|
144
|
+
version: '2.2'
|
145
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
146
|
+
requirements:
|
147
|
+
- - ">="
|
148
|
+
- !ruby/object:Gem::Version
|
149
|
+
version: '0'
|
150
|
+
requirements: []
|
151
|
+
rubyforge_project:
|
152
|
+
rubygems_version: 2.7.6
|
153
|
+
signing_key:
|
154
|
+
specification_version: 4
|
155
|
+
summary: Provides a Rack application that implements endpoints for the AwsS3Multipart
|
156
|
+
Uppy plugin.
|
157
|
+
test_files: []
|