Add :public option for public uploads and URLs

parent 618f6793
...@@ -134,6 +134,14 @@ files**, instead it will just copy metadata that was extracted on the client ...@@ -134,6 +134,14 @@ files**, instead it will just copy metadata that was extracted on the client
side. See [this section][metadata direct uploads] for the rationale and side. See [this section][metadata direct uploads] for the rationale and
instructions on how to opt in. instructions on how to opt in.
If you want to make uploads public and have public URLs without query
parameters returned, you can pass `public: true` to the Shrine storage (note
that this is supported starting from Shrine 2.13).
```rb
Shrine::Storage::S3.new(public: true, **options)
```
Both the plugin and method accept `:options` for specifying additional options Both the plugin and method accept `:options` for specifying additional options
to the S3 client operations (see the [Client](#client) section for list of to the S3 client operations (see the [Client](#client) section for list of
operations and options they accept): operations and options they accept):
...@@ -194,9 +202,16 @@ uppy.use(Uppy.AwsS3Multipart, { ...@@ -194,9 +202,16 @@ uppy.use(Uppy.AwsS3Multipart, {
}) })
``` ```
The `Uppy::S3Mutipart::App` initializer accepts `:options` for specifying If you want to make uploads public and have public URLs without query
additional options to the S3 client operations (see the [Client](#client) parameters returned, you can pass `public: true` to the app.
section for list of operations and options they accept):
```rb
Uppy::S3Multipart::App.new(bucket: bucket, public: true)
```
You can also pass `:options` for specifying additional options to the S3 client
operations (see the [Client](#client) section for list of operations and
options they accept):
```rb ```rb
Uppy::S3Multipart::App.new(bucket: bucket, options: { Uppy::S3Multipart::App.new(bucket: bucket, options: {
......
...@@ -15,12 +15,12 @@ class Shrine ...@@ -15,12 +15,12 @@ class Shrine
fail Error, "expected storage to be a Shrine::Storage::S3, but was #{s3.inspect}" fail Error, "expected storage to be a Shrine::Storage::S3, but was #{s3.inspect}"
end end
::Uppy::S3Multipart::App.new( options[:bucket] ||= s3.bucket
bucket: s3.bucket, options[:prefix] ||= s3.prefix
prefix: s3.prefix, options[:public] ||= s3.public if s3.respond_to?(:public)
options: opts[:uppy_s3_multipart_options], options[:options] ||= opts[:uppy_s3_multipart_options]
**options
) ::Uppy::S3Multipart::App.new(**options)
end end
end end
end end
......
...@@ -8,10 +8,11 @@ require "cgi" ...@@ -8,10 +8,11 @@ require "cgi"
module Uppy module Uppy
module S3Multipart module S3Multipart
class App class App
def initialize(bucket:, prefix: nil, options: {}) def initialize(bucket:, prefix: nil, public: nil, options: {})
@router = Class.new(Router) @router = Class.new(Router)
@router.opts[:client] = Client.new(bucket: bucket) @router.opts[:client] = Client.new(bucket: bucket)
@router.opts[:prefix] = prefix @router.opts[:prefix] = prefix
@router.opts[:public] = public
@router.opts[:options] = options @router.opts[:options] = options
end end
...@@ -42,7 +43,10 @@ module Uppy ...@@ -42,7 +43,10 @@ module Uppy
# CGI-escape the filename because aws-sdk's signature calculator trips on special characters # CGI-escape the filename because aws-sdk's signature calculator trips on special characters
content_disposition = "inline; filename=\"#{CGI.escape(filename)}\"" if filename content_disposition = "inline; filename=\"#{CGI.escape(filename)}\"" if filename
result = client_call(:create_multipart_upload, key: key, content_type: content_type, content_disposition: content_disposition) options = { content_type: content_type, content_disposition: content_disposition }
options[:acl] = "public-read" if opts[:public]
result = client_call(:create_multipart_upload, key: key, **options)
{ uploadId: result.fetch(:upload_id), key: result.fetch(:key) } { uploadId: result.fetch(:upload_id), key: result.fetch(:key) }
end end
...@@ -82,7 +86,7 @@ module Uppy ...@@ -82,7 +86,7 @@ module Uppy
client_call(:complete_multipart_upload, upload_id: upload_id, key: key, parts: parts) client_call(:complete_multipart_upload, upload_id: upload_id, key: key, parts: parts)
object_url = client_call(:object_url, key: key) object_url = client_call(:object_url, key: key, public: opts[:public])
{ location: object_url } { location: object_url }
end end
......
...@@ -60,9 +60,21 @@ describe Uppy::S3Multipart::App do ...@@ -60,9 +60,21 @@ describe Uppy::S3Multipart::App do
response = app.post "/s3/multipart" response = app.post "/s3/multipart"
assert_equal :create_multipart_upload, @s3.api_requests[0][:operation_name]
assert_match /^prefix\/\w{32}/, @s3.api_requests[0][:params][:key]
assert_match /^prefix\/\w{32}$/, response.body_json["key"] assert_match /^prefix\/\w{32}$/, response.body_json["key"]
end end
it "handles :public option" do
@endpoint = Uppy::S3Multipart::App.new(bucket: @bucket, public: true)
response = app.post "/s3/multipart"
assert_equal :create_multipart_upload, @s3.api_requests[0][:operation_name]
assert_equal "public-read", @s3.api_requests[0][:params][:acl]
end
it "handles :options as a hash" do it "handles :options as a hash" do
@endpoint = Uppy::S3Multipart::App.new(bucket: @bucket, options: { @endpoint = Uppy::S3Multipart::App.new(bucket: @bucket, options: {
create_multipart_upload: { acl: "public-read" } create_multipart_upload: { acl: "public-read" }
...@@ -223,6 +235,19 @@ describe Uppy::S3Multipart::App do ...@@ -223,6 +235,19 @@ describe Uppy::S3Multipart::App do
assert_includes response.body_json["location"], "response-content-disposition" assert_includes response.body_json["location"], "response-content-disposition"
end end
it "handles :public option" do
@endpoint = Uppy::S3Multipart::App.new(bucket: @bucket, public: true)
response = app.post "/s3/multipart/foo/complete", query: { key: "bar" }, json: { parts: [] }
assert_equal 200, response.status
assert_equal "application/json", response.headers["Content-Type"]
uri = URI.parse(response.body_json["location"])
assert_nil uri.query
end
it "returns error response when 'key' parameter is missing" do it "returns error response when 'key' parameter is missing" do
response = app.post "/s3/multipart/foo/complete", json: { parts: [] } response = app.post "/s3/multipart/foo/complete", json: { parts: [] }
......
...@@ -53,6 +53,16 @@ describe Shrine::Plugins::UppyS3Multipart do ...@@ -53,6 +53,16 @@ describe Shrine::Plugins::UppyS3Multipart do
assert_match /^prefix\/\w+$/, client.api_requests[0][:params][:key] assert_match /^prefix\/\w+$/, client.api_requests[0][:params][:key]
end end
it "passes the public" do
@shrine.storages[:s3] = s3(public: true)
app = test_app
app.post "/multipart"
assert_equal :create_multipart_upload, client.api_requests[0][:operation_name]
assert_match "public-read", client.api_requests[0][:params][:acl]
end
it "passes client options" do it "passes client options" do
@shrine.plugin :uppy_s3_multipart, options: { @shrine.plugin :uppy_s3_multipart, options: {
create_multipart_upload: { acl: "public-read" }, create_multipart_upload: { acl: "public-read" },
......
...@@ -19,7 +19,7 @@ Gem::Specification.new do |gem| ...@@ -19,7 +19,7 @@ Gem::Specification.new do |gem|
gem.add_development_dependency "rake" gem.add_development_dependency "rake"
gem.add_development_dependency "minitest" gem.add_development_dependency "minitest"
gem.add_development_dependency "rack-test_app" gem.add_development_dependency "rack-test_app"
gem.add_development_dependency "shrine", "~> 2.0" gem.add_development_dependency "shrine", "~> 2.13"
gem.add_development_dependency "shrine-memory" gem.add_development_dependency "shrine-memory"
gem.add_development_dependency "aws-sdk-core", "~> 3.23" gem.add_development_dependency "aws-sdk-core", "~> 3.23"
end end
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment