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.
@@ -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
@@ -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.
@@ -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,2 @@
1
+ require "uppy/s3_multipart/app"
2
+ require "uppy/s3_multipart/client"
@@ -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: []