mirror of
https://github.com/rails/rails.git
synced 2022-11-09 12:12:34 -05:00
Support direct uploads to multiple services
This commit is contained in:
parent
a92db0d533
commit
193289dbbe
18 changed files with 434 additions and 152 deletions
|
@ -506,14 +506,16 @@ var activestorage = {exports: {}};
|
|||
}
|
||||
}
|
||||
class BlobRecord {
|
||||
constructor(file, checksum, url) {
|
||||
constructor(file, checksum, url, directUploadToken, attachmentName) {
|
||||
this.file = file;
|
||||
this.attributes = {
|
||||
filename: file.name,
|
||||
content_type: file.type || "application/octet-stream",
|
||||
byte_size: file.size,
|
||||
checksum: checksum
|
||||
checksum: checksum,
|
||||
};
|
||||
this.directUploadToken = directUploadToken;
|
||||
this.attachmentName = attachmentName;
|
||||
this.xhr = new XMLHttpRequest;
|
||||
this.xhr.open("POST", url, true);
|
||||
this.xhr.responseType = "json";
|
||||
|
@ -541,7 +543,9 @@ var activestorage = {exports: {}};
|
|||
create(callback) {
|
||||
this.callback = callback;
|
||||
this.xhr.send(JSON.stringify({
|
||||
blob: this.attributes
|
||||
blob: this.attributes,
|
||||
direct_upload_token: this.directUploadToken,
|
||||
attachment_name: this.attachmentName
|
||||
}));
|
||||
}
|
||||
requestDidLoad(event) {
|
||||
|
@ -599,10 +603,12 @@ var activestorage = {exports: {}};
|
|||
}
|
||||
let id = 0;
|
||||
class DirectUpload {
|
||||
constructor(file, url, delegate) {
|
||||
constructor(file, url, directUploadToken, attachmentName, delegate) {
|
||||
this.id = ++id;
|
||||
this.file = file;
|
||||
this.url = url;
|
||||
this.directUploadToken = directUploadToken;
|
||||
this.attachmentName = attachmentName;
|
||||
this.delegate = delegate;
|
||||
}
|
||||
create(callback) {
|
||||
|
@ -611,7 +617,7 @@ var activestorage = {exports: {}};
|
|||
callback(error);
|
||||
return;
|
||||
}
|
||||
const blob = new BlobRecord(this.file, checksum, this.url);
|
||||
const blob = new BlobRecord(this.file, checksum, this.url, this.directUploadToken, this.attachmentName);
|
||||
notify(this.delegate, "directUploadWillCreateBlobWithXHR", blob.xhr);
|
||||
blob.create((error => {
|
||||
if (error) {
|
||||
|
@ -640,7 +646,7 @@ var activestorage = {exports: {}};
|
|||
constructor(input, file) {
|
||||
this.input = input;
|
||||
this.file = file;
|
||||
this.directUpload = new DirectUpload(this.file, this.url, this);
|
||||
this.directUpload = new DirectUpload(this.file, this.url, this.directUploadToken, this.attachmentName, this);
|
||||
this.dispatch("initialize");
|
||||
}
|
||||
start(callback) {
|
||||
|
@ -671,6 +677,12 @@ var activestorage = {exports: {}};
|
|||
get url() {
|
||||
return this.input.getAttribute("data-direct-upload-url");
|
||||
}
|
||||
get directUploadToken() {
|
||||
return this.input.getAttribute("data-direct-upload-token");
|
||||
}
|
||||
get attachmentName() {
|
||||
return this.input.getAttribute("data-direct-upload-attachment-name");
|
||||
}
|
||||
dispatch(name, detail = {}) {
|
||||
detail.file = this.file;
|
||||
detail.id = this.directUpload.id;
|
||||
|
@ -830,7 +842,7 @@ class AttachmentUpload {
|
|||
constructor(attachment, element) {
|
||||
this.attachment = attachment;
|
||||
this.element = element;
|
||||
this.directUpload = new activestorage.exports.DirectUpload(attachment.file, this.directUploadUrl, this);
|
||||
this.directUpload = new activestorage.exports.DirectUpload(attachment.file, this.directUploadUrl, this.directUploadToken, this.directUploadAttachmentName, this);
|
||||
}
|
||||
|
||||
start() {
|
||||
|
@ -865,6 +877,14 @@ class AttachmentUpload {
|
|||
return this.element.dataset.directUploadUrl
|
||||
}
|
||||
|
||||
get directUploadToken() {
|
||||
return this.element.dataset.directUploadToken
|
||||
}
|
||||
|
||||
get directUploadAttachmentName() {
|
||||
return this.element.dataset.directUploadAttachmentName
|
||||
}
|
||||
|
||||
get blobUrlTemplate() {
|
||||
return this.element.dataset.blobUrlTemplate
|
||||
}
|
||||
|
|
|
@ -32,6 +32,14 @@ module ActionText
|
|||
options[:data][:direct_upload_url] ||= main_app.rails_direct_uploads_url
|
||||
options[:data][:blob_url_template] ||= main_app.rails_service_blob_url(":signed_id", ":filename")
|
||||
|
||||
class_with_attachment = "ActionText::RichText#embeds"
|
||||
options[:data][:direct_upload_attachment_name] ||= class_with_attachment
|
||||
options[:data][:direct_upload_token] = ActiveStorage::DirectUploadToken.generate_direct_upload_token(
|
||||
class_with_attachment,
|
||||
ActiveStorage::Blob.service.name,
|
||||
session
|
||||
)
|
||||
|
||||
editor_tag = content_tag("trix-editor", "", options)
|
||||
input_tag = hidden_field_tag(name, value.try(:to_trix_html) || value, id: options[:input], form: form)
|
||||
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require "test_helper"
|
||||
require "minitest/mock"
|
||||
|
||||
class ActionText::FormHelperTest < ActionView::TestCase
|
||||
tests ActionText::TagHelper
|
||||
|
@ -28,158 +29,184 @@ class ActionText::FormHelperTest < ActionView::TestCase
|
|||
test "rich text area tag" do
|
||||
message = Message.new
|
||||
|
||||
form_with model: message, scope: :message do |form|
|
||||
rich_text_area_tag :content, message.content, { input: "trix_input_1" }
|
||||
end
|
||||
with_stub_token do
|
||||
form_with model: message, scope: :message do |form|
|
||||
rich_text_area_tag :content, message.content, { input: "trix_input_1" }
|
||||
end
|
||||
|
||||
assert_dom_equal \
|
||||
'<form action="/messages" accept-charset="UTF-8" method="post">' \
|
||||
'<input type="hidden" name="content" id="trix_input_1" autocomplete="off" />' \
|
||||
'<trix-editor input="trix_input_1" class="trix-content" data-direct-upload-url="http://test.host/rails/active_storage/direct_uploads" data-blob-url-template="http://test.host/rails/active_storage/blobs/redirect/:signed_id/:filename">' \
|
||||
"</trix-editor>" \
|
||||
"</form>",
|
||||
output_buffer
|
||||
assert_dom_equal \
|
||||
'<form action="/messages" accept-charset="UTF-8" method="post">' \
|
||||
'<input type="hidden" name="content" id="trix_input_1" autocomplete="off" />' \
|
||||
'<trix-editor input="trix_input_1" class="trix-content" data-direct-upload-url="http://test.host/rails/active_storage/direct_uploads" data-blob-url-template="http://test.host/rails/active_storage/blobs/redirect/:signed_id/:filename" data-direct-upload-attachment-name="ActionText::RichText#embeds" data-direct-upload-token="token">' \
|
||||
"</trix-editor>" \
|
||||
"</form>",
|
||||
output_buffer
|
||||
end
|
||||
end
|
||||
|
||||
test "form with rich text area" do
|
||||
form_with model: Message.new, scope: :message do |form|
|
||||
form.rich_text_area :content
|
||||
end
|
||||
with_stub_token do
|
||||
form_with model: Message.new, scope: :message do |form|
|
||||
form.rich_text_area :content
|
||||
end
|
||||
|
||||
assert_dom_equal \
|
||||
'<form action="/messages" accept-charset="UTF-8" method="post">' \
|
||||
'<input type="hidden" name="message[content]" id="message_content_trix_input_message" autocomplete="off" />' \
|
||||
'<trix-editor id="message_content" input="message_content_trix_input_message" class="trix-content" data-direct-upload-url="http://test.host/rails/active_storage/direct_uploads" data-blob-url-template="http://test.host/rails/active_storage/blobs/redirect/:signed_id/:filename">' \
|
||||
"</trix-editor>" \
|
||||
"</form>",
|
||||
output_buffer
|
||||
assert_dom_equal \
|
||||
'<form action="/messages" accept-charset="UTF-8" method="post">' \
|
||||
'<input type="hidden" name="message[content]" id="message_content_trix_input_message" autocomplete="off" />' \
|
||||
'<trix-editor id="message_content" input="message_content_trix_input_message" class="trix-content" data-direct-upload-url="http://test.host/rails/active_storage/direct_uploads" data-blob-url-template="http://test.host/rails/active_storage/blobs/redirect/:signed_id/:filename" data-direct-upload-attachment-name="ActionText::RichText#embeds" data-direct-upload-token="token">' \
|
||||
"</trix-editor>" \
|
||||
"</form>",
|
||||
output_buffer
|
||||
end
|
||||
end
|
||||
|
||||
test "form with rich text area having class" do
|
||||
form_with model: Message.new, scope: :message do |form|
|
||||
form.rich_text_area :content, class: "custom-class"
|
||||
end
|
||||
with_stub_token do
|
||||
form_with model: Message.new, scope: :message do |form|
|
||||
form.rich_text_area :content, class: "custom-class"
|
||||
end
|
||||
|
||||
assert_dom_equal \
|
||||
'<form action="/messages" accept-charset="UTF-8" method="post">' \
|
||||
'<input type="hidden" name="message[content]" id="message_content_trix_input_message" autocomplete="off" />' \
|
||||
'<trix-editor id="message_content" input="message_content_trix_input_message" class="custom-class" data-direct-upload-url="http://test.host/rails/active_storage/direct_uploads" data-blob-url-template="http://test.host/rails/active_storage/blobs/redirect/:signed_id/:filename">' \
|
||||
"</trix-editor>" \
|
||||
"</form>",
|
||||
output_buffer
|
||||
assert_dom_equal \
|
||||
'<form action="/messages" accept-charset="UTF-8" method="post">' \
|
||||
'<input type="hidden" name="message[content]" id="message_content_trix_input_message" autocomplete="off" />' \
|
||||
'<trix-editor id="message_content" input="message_content_trix_input_message" class="custom-class" data-direct-upload-url="http://test.host/rails/active_storage/direct_uploads" data-blob-url-template="http://test.host/rails/active_storage/blobs/redirect/:signed_id/:filename" data-direct-upload-attachment-name="ActionText::RichText#embeds" data-direct-upload-token="token">' \
|
||||
"</trix-editor>" \
|
||||
"</form>",
|
||||
output_buffer
|
||||
end
|
||||
end
|
||||
|
||||
test "form with rich text area for non-attribute" do
|
||||
form_with model: Message.new, scope: :message do |form|
|
||||
form.rich_text_area :not_an_attribute
|
||||
end
|
||||
with_stub_token do
|
||||
form_with model: Message.new, scope: :message do |form|
|
||||
form.rich_text_area :not_an_attribute
|
||||
end
|
||||
|
||||
assert_dom_equal \
|
||||
'<form action="/messages" accept-charset="UTF-8" method="post">' \
|
||||
'<input type="hidden" name="message[not_an_attribute]" id="message_not_an_attribute_trix_input_message" autocomplete="off" />' \
|
||||
'<trix-editor id="message_not_an_attribute" input="message_not_an_attribute_trix_input_message" class="trix-content" data-direct-upload-url="http://test.host/rails/active_storage/direct_uploads" data-blob-url-template="http://test.host/rails/active_storage/blobs/redirect/:signed_id/:filename">' \
|
||||
"</trix-editor>" \
|
||||
"</form>",
|
||||
output_buffer
|
||||
assert_dom_equal \
|
||||
'<form action="/messages" accept-charset="UTF-8" method="post">' \
|
||||
'<input type="hidden" name="message[not_an_attribute]" id="message_not_an_attribute_trix_input_message" autocomplete="off" />' \
|
||||
'<trix-editor id="message_not_an_attribute" input="message_not_an_attribute_trix_input_message" class="trix-content" data-direct-upload-url="http://test.host/rails/active_storage/direct_uploads" data-blob-url-template="http://test.host/rails/active_storage/blobs/redirect/:signed_id/:filename" data-direct-upload-attachment-name="ActionText::RichText#embeds" data-direct-upload-token="token">' \
|
||||
"</trix-editor>" \
|
||||
"</form>",
|
||||
output_buffer
|
||||
end
|
||||
end
|
||||
|
||||
test "modelless form with rich text area" do
|
||||
form_with url: "/messages", scope: :message do |form|
|
||||
form.rich_text_area :content, { input: "trix_input_2" }
|
||||
end
|
||||
with_stub_token do
|
||||
form_with url: "/messages", scope: :message do |form|
|
||||
form.rich_text_area :content, { input: "trix_input_2" }
|
||||
end
|
||||
|
||||
assert_dom_equal \
|
||||
'<form action="/messages" accept-charset="UTF-8" method="post">' \
|
||||
'<input type="hidden" name="message[content]" id="trix_input_2" autocomplete="off" />' \
|
||||
'<trix-editor id="message_content" input="trix_input_2" class="trix-content" data-direct-upload-url="http://test.host/rails/active_storage/direct_uploads" data-blob-url-template="http://test.host/rails/active_storage/blobs/redirect/:signed_id/:filename">' \
|
||||
"</trix-editor>" \
|
||||
"</form>",
|
||||
output_buffer
|
||||
assert_dom_equal \
|
||||
'<form action="/messages" accept-charset="UTF-8" method="post">' \
|
||||
'<input type="hidden" name="message[content]" id="trix_input_2" autocomplete="off" />' \
|
||||
'<trix-editor id="message_content" input="trix_input_2" class="trix-content" data-direct-upload-url="http://test.host/rails/active_storage/direct_uploads" data-blob-url-template="http://test.host/rails/active_storage/blobs/redirect/:signed_id/:filename" data-direct-upload-attachment-name="ActionText::RichText#embeds" data-direct-upload-token="token">' \
|
||||
"</trix-editor>" \
|
||||
"</form>",
|
||||
output_buffer
|
||||
end
|
||||
end
|
||||
|
||||
test "form with rich text area having placeholder without locale" do
|
||||
form_with model: Message.new, scope: :message do |form|
|
||||
form.rich_text_area :content, placeholder: true
|
||||
end
|
||||
with_stub_token do
|
||||
form_with model: Message.new, scope: :message do |form|
|
||||
form.rich_text_area :content, placeholder: true
|
||||
end
|
||||
|
||||
assert_dom_equal \
|
||||
'<form action="/messages" accept-charset="UTF-8" method="post">' \
|
||||
'<input type="hidden" name="message[content]" id="message_content_trix_input_message" autocomplete="off" />' \
|
||||
'<trix-editor placeholder="Content" id="message_content" input="message_content_trix_input_message" class="trix-content" data-direct-upload-url="http://test.host/rails/active_storage/direct_uploads" data-blob-url-template="http://test.host/rails/active_storage/blobs/redirect/:signed_id/:filename">' \
|
||||
"</trix-editor>" \
|
||||
"</form>",
|
||||
output_buffer
|
||||
assert_dom_equal \
|
||||
'<form action="/messages" accept-charset="UTF-8" method="post">' \
|
||||
'<input type="hidden" name="message[content]" id="message_content_trix_input_message" autocomplete="off" />' \
|
||||
'<trix-editor placeholder="Content" id="message_content" input="message_content_trix_input_message" class="trix-content" data-direct-upload-url="http://test.host/rails/active_storage/direct_uploads" data-blob-url-template="http://test.host/rails/active_storage/blobs/redirect/:signed_id/:filename" data-direct-upload-attachment-name="ActionText::RichText#embeds" data-direct-upload-token="token">' \
|
||||
"</trix-editor>" \
|
||||
"</form>",
|
||||
output_buffer
|
||||
end
|
||||
end
|
||||
|
||||
test "form with rich text area having placeholder with locale" do
|
||||
I18n.with_locale :placeholder do
|
||||
form_with model: Message.new, scope: :message do |form|
|
||||
form.rich_text_area :title, placeholder: true
|
||||
with_stub_token do
|
||||
form.rich_text_area :title, placeholder: true
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
assert_dom_equal \
|
||||
'<form action="/messages" accept-charset="UTF-8" method="post">' \
|
||||
'<input type="hidden" name="message[title]" id="message_title_trix_input_message" autocomplete="off" />' \
|
||||
'<trix-editor placeholder="Story title" id="message_title" input="message_title_trix_input_message" class="trix-content" data-direct-upload-url="http://test.host/rails/active_storage/direct_uploads" data-blob-url-template="http://test.host/rails/active_storage/blobs/redirect/:signed_id/:filename">' \
|
||||
"</trix-editor>" \
|
||||
"</form>",
|
||||
output_buffer
|
||||
assert_dom_equal \
|
||||
'<form action="/messages" accept-charset="UTF-8" method="post">' \
|
||||
'<input type="hidden" name="message[title]" id="message_title_trix_input_message" autocomplete="off" />' \
|
||||
'<trix-editor placeholder="Story title" id="message_title" input="message_title_trix_input_message" class="trix-content" data-direct-upload-url="http://test.host/rails/active_storage/direct_uploads" data-blob-url-template="http://test.host/rails/active_storage/blobs/redirect/:signed_id/:filename" data-direct-upload-attachment-name="ActionText::RichText#embeds" data-direct-upload-token="token">' \
|
||||
"</trix-editor>" \
|
||||
"</form>",
|
||||
output_buffer
|
||||
end
|
||||
end
|
||||
|
||||
test "form with rich text area with value" do
|
||||
form_with model: Message.new, scope: :message do |form|
|
||||
form.rich_text_area :title, value: "<h1>hello world</h1>"
|
||||
end
|
||||
with_stub_token do
|
||||
form_with model: Message.new, scope: :message do |form|
|
||||
form.rich_text_area :title, value: "<h1>hello world</h1>"
|
||||
end
|
||||
|
||||
assert_dom_equal \
|
||||
'<form action="/messages" accept-charset="UTF-8" method="post">' \
|
||||
'<input type="hidden" name="message[title]" id="message_title_trix_input_message" value="<h1>hello world</h1>" autocomplete="off" />' \
|
||||
'<trix-editor id="message_title" input="message_title_trix_input_message" class="trix-content" data-direct-upload-url="http://test.host/rails/active_storage/direct_uploads" data-blob-url-template="http://test.host/rails/active_storage/blobs/redirect/:signed_id/:filename">' \
|
||||
"</trix-editor>" \
|
||||
"</form>",
|
||||
output_buffer
|
||||
assert_dom_equal \
|
||||
'<form action="/messages" accept-charset="UTF-8" method="post">' \
|
||||
'<input type="hidden" name="message[title]" id="message_title_trix_input_message" value="<h1>hello world</h1>" autocomplete="off" />' \
|
||||
'<trix-editor id="message_title" input="message_title_trix_input_message" class="trix-content" data-direct-upload-url="http://test.host/rails/active_storage/direct_uploads" data-blob-url-template="http://test.host/rails/active_storage/blobs/redirect/:signed_id/:filename" data-direct-upload-attachment-name="ActionText::RichText#embeds" data-direct-upload-token="token">' \
|
||||
"</trix-editor>" \
|
||||
"</form>",
|
||||
output_buffer
|
||||
end
|
||||
end
|
||||
|
||||
test "form with rich text area with form attribute" do
|
||||
form_with model: Message.new, scope: :message do |form|
|
||||
form.rich_text_area :title, form: "other_form"
|
||||
end
|
||||
with_stub_token do
|
||||
form_with model: Message.new, scope: :message do |form|
|
||||
form.rich_text_area :title, form: "other_form"
|
||||
end
|
||||
|
||||
assert_dom_equal \
|
||||
'<form action="/messages" accept-charset="UTF-8" method="post">' \
|
||||
'<input type="hidden" name="message[title]" id="message_title_trix_input_message" form="other_form" autocomplete="off" />' \
|
||||
'<trix-editor id="message_title" input="message_title_trix_input_message" class="trix-content" data-direct-upload-url="http://test.host/rails/active_storage/direct_uploads" data-blob-url-template="http://test.host/rails/active_storage/blobs/redirect/:signed_id/:filename">' \
|
||||
"</trix-editor>" \
|
||||
"</form>",
|
||||
output_buffer
|
||||
assert_dom_equal \
|
||||
'<form action="/messages" accept-charset="UTF-8" method="post">' \
|
||||
'<input type="hidden" name="message[title]" id="message_title_trix_input_message" form="other_form" autocomplete="off" />' \
|
||||
'<trix-editor id="message_title" input="message_title_trix_input_message" class="trix-content" data-direct-upload-url="http://test.host/rails/active_storage/direct_uploads" data-blob-url-template="http://test.host/rails/active_storage/blobs/redirect/:signed_id/:filename" data-direct-upload-attachment-name="ActionText::RichText#embeds" data-direct-upload-token="token">' \
|
||||
"</trix-editor>" \
|
||||
"</form>",
|
||||
output_buffer
|
||||
end
|
||||
end
|
||||
|
||||
test "form with rich text area with data[direct_upload_url]" do
|
||||
form_with model: Message.new, scope: :message do |form|
|
||||
form.rich_text_area :content, data: { direct_upload_url: "http://test.host/direct_uploads" }
|
||||
end
|
||||
with_stub_token do
|
||||
form_with model: Message.new, scope: :message do |form|
|
||||
form.rich_text_area :content, data: { direct_upload_url: "http://test.host/direct_uploads" }
|
||||
end
|
||||
|
||||
assert_dom_equal \
|
||||
'<form action="/messages" accept-charset="UTF-8" method="post">' \
|
||||
'<input type="hidden" name="message[content]" id="message_content_trix_input_message" autocomplete="off" />' \
|
||||
'<trix-editor id="message_content" input="message_content_trix_input_message" class="trix-content" data-direct-upload-url="http://test.host/direct_uploads" data-blob-url-template="http://test.host/rails/active_storage/blobs/redirect/:signed_id/:filename">' \
|
||||
"</trix-editor>" \
|
||||
"</form>",
|
||||
output_buffer
|
||||
assert_dom_equal \
|
||||
'<form action="/messages" accept-charset="UTF-8" method="post">' \
|
||||
'<input type="hidden" name="message[content]" id="message_content_trix_input_message" autocomplete="off" />' \
|
||||
'<trix-editor id="message_content" input="message_content_trix_input_message" class="trix-content" data-direct-upload-url="http://test.host/direct_uploads" data-blob-url-template="http://test.host/rails/active_storage/blobs/redirect/:signed_id/:filename" data-direct-upload-attachment-name="ActionText::RichText#embeds" data-direct-upload-token="token">' \
|
||||
"</trix-editor>" \
|
||||
"</form>",
|
||||
output_buffer
|
||||
end
|
||||
end
|
||||
|
||||
test "form with rich text area with data[blob_url_template]" do
|
||||
form_with model: Message.new, scope: :message do |form|
|
||||
form.rich_text_area :content, data: { blob_url_template: "http://test.host/blobs/:signed_id/:filename" }
|
||||
end
|
||||
with_stub_token do
|
||||
form_with model: Message.new, scope: :message do |form|
|
||||
form.rich_text_area :content, data: { blob_url_template: "http://test.host/blobs/:signed_id/:filename" }
|
||||
end
|
||||
|
||||
assert_dom_equal \
|
||||
'<form action="/messages" accept-charset="UTF-8" method="post">' \
|
||||
'<input type="hidden" name="message[content]" id="message_content_trix_input_message" autocomplete="off" />' \
|
||||
'<trix-editor id="message_content" input="message_content_trix_input_message" class="trix-content" data-direct-upload-url="http://test.host/rails/active_storage/direct_uploads" data-blob-url-template="http://test.host/blobs/:signed_id/:filename">' \
|
||||
"</trix-editor>" \
|
||||
"</form>",
|
||||
output_buffer
|
||||
assert_dom_equal \
|
||||
'<form action="/messages" accept-charset="UTF-8" method="post">' \
|
||||
'<input type="hidden" name="message[content]" id="message_content_trix_input_message" autocomplete="off" />' \
|
||||
'<trix-editor id="message_content" input="message_content_trix_input_message" class="trix-content" data-direct-upload-url="http://test.host/rails/active_storage/direct_uploads" data-blob-url-template="http://test.host/blobs/:signed_id/:filename" data-direct-upload-attachment-name="ActionText::RichText#embeds" data-direct-upload-token="token">' \
|
||||
"</trix-editor>" \
|
||||
"</form>",
|
||||
output_buffer
|
||||
end
|
||||
end
|
||||
|
||||
def with_stub_token(&block)
|
||||
ActiveStorage::DirectUploadToken.stub(:generate_direct_upload_token, "token", &block)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1220,7 +1220,7 @@ module ActionView
|
|||
# file_field(:attachment, :file, class: 'file_input')
|
||||
# # => <input type="file" id="attachment_file" name="attachment[file]" class="file_input" />
|
||||
def file_field(object_name, method, options = {})
|
||||
Tags::FileField.new(object_name, method, self, convert_direct_upload_option_to_url(options.dup)).render
|
||||
Tags::FileField.new(object_name, method, self, convert_direct_upload_option_to_url(method, options.dup)).render
|
||||
end
|
||||
|
||||
# Returns a textarea opening and closing tag set tailored for accessing a specified attribute (identified by +method+)
|
||||
|
|
|
@ -316,7 +316,7 @@ module ActionView
|
|||
# file_field_tag 'file', accept: 'text/html', class: 'upload', value: 'index.html'
|
||||
# # => <input accept="text/html" class="upload" id="file" name="file" type="file" value="index.html" />
|
||||
def file_field_tag(name, options = {})
|
||||
text_field_tag(name, nil, convert_direct_upload_option_to_url(options.merge(type: :file)))
|
||||
text_field_tag(name, nil, convert_direct_upload_option_to_url(name, options.merge(type: :file)))
|
||||
end
|
||||
|
||||
# Creates a password field, a masked text field that will hide the users input behind a mask character.
|
||||
|
@ -954,9 +954,23 @@ module ActionView
|
|||
tag_options.delete("data-disable-with")
|
||||
end
|
||||
|
||||
def convert_direct_upload_option_to_url(options)
|
||||
def convert_direct_upload_option_to_url(name, options)
|
||||
if options.delete(:direct_upload) && respond_to?(:rails_direct_uploads_url)
|
||||
options["data-direct-upload-url"] = rails_direct_uploads_url
|
||||
|
||||
if options[:object] && options[:object].class.respond_to?(:reflect_on_attachment)
|
||||
attachment_reflection = options[:object].class.reflect_on_attachment(name)
|
||||
|
||||
class_with_attachment = "#{options[:object].class.name.underscore}##{name}"
|
||||
options["data-direct-upload-attachment-name"] = class_with_attachment
|
||||
|
||||
service_name = attachment_reflection.options[:service_name] || ActiveStorage::Blob.service.name
|
||||
options["data-direct-upload-token"] = ActiveStorage::DirectUploadToken.generate_direct_upload_token(
|
||||
class_with_attachment,
|
||||
service_name,
|
||||
session
|
||||
)
|
||||
end
|
||||
end
|
||||
options
|
||||
end
|
||||
|
|
|
@ -1,3 +1,7 @@
|
|||
* Support direct uploads to multiple services.
|
||||
|
||||
*Dmitry Tsepelev*
|
||||
|
||||
* Invalid default content types are deprecated
|
||||
|
||||
Blobs created with content_type `image/jpg`, `image/pjpeg`, `image/bmp`, `text/javascript` will now produce
|
||||
|
|
|
@ -508,7 +508,7 @@ function toArray(value) {
|
|||
}
|
||||
|
||||
class BlobRecord {
|
||||
constructor(file, checksum, url) {
|
||||
constructor(file, checksum, url, directUploadToken, attachmentName) {
|
||||
this.file = file;
|
||||
this.attributes = {
|
||||
filename: file.name,
|
||||
|
@ -516,6 +516,8 @@ class BlobRecord {
|
|||
byte_size: file.size,
|
||||
checksum: checksum
|
||||
};
|
||||
this.directUploadToken = directUploadToken;
|
||||
this.attachmentName = attachmentName;
|
||||
this.xhr = new XMLHttpRequest;
|
||||
this.xhr.open("POST", url, true);
|
||||
this.xhr.responseType = "json";
|
||||
|
@ -543,7 +545,9 @@ class BlobRecord {
|
|||
create(callback) {
|
||||
this.callback = callback;
|
||||
this.xhr.send(JSON.stringify({
|
||||
blob: this.attributes
|
||||
blob: this.attributes,
|
||||
direct_upload_token: this.directUploadToken,
|
||||
attachment_name: this.attachmentName
|
||||
}));
|
||||
}
|
||||
requestDidLoad(event) {
|
||||
|
@ -604,10 +608,12 @@ class BlobUpload {
|
|||
let id = 0;
|
||||
|
||||
class DirectUpload {
|
||||
constructor(file, url, delegate) {
|
||||
constructor(file, url, serviceName, attachmentName, delegate) {
|
||||
this.id = ++id;
|
||||
this.file = file;
|
||||
this.url = url;
|
||||
this.serviceName = serviceName;
|
||||
this.attachmentName = attachmentName;
|
||||
this.delegate = delegate;
|
||||
}
|
||||
create(callback) {
|
||||
|
@ -616,7 +622,7 @@ class DirectUpload {
|
|||
callback(error);
|
||||
return;
|
||||
}
|
||||
const blob = new BlobRecord(this.file, checksum, this.url);
|
||||
const blob = new BlobRecord(this.file, checksum, this.url, this.serviceName, this.attachmentName);
|
||||
notify(this.delegate, "directUploadWillCreateBlobWithXHR", blob.xhr);
|
||||
blob.create((error => {
|
||||
if (error) {
|
||||
|
@ -647,7 +653,7 @@ class DirectUploadController {
|
|||
constructor(input, file) {
|
||||
this.input = input;
|
||||
this.file = file;
|
||||
this.directUpload = new DirectUpload(this.file, this.url, this);
|
||||
this.directUpload = new DirectUpload(this.file, this.url, this.directUploadToken, this.attachmentName, this);
|
||||
this.dispatch("initialize");
|
||||
}
|
||||
start(callback) {
|
||||
|
@ -678,6 +684,12 @@ class DirectUploadController {
|
|||
get url() {
|
||||
return this.input.getAttribute("data-direct-upload-url");
|
||||
}
|
||||
get directUploadToken() {
|
||||
return this.input.getAttribute("data-direct-upload-token");
|
||||
}
|
||||
get attachmentName() {
|
||||
return this.input.getAttribute("data-direct-upload-attachment-name");
|
||||
}
|
||||
dispatch(name, detail = {}) {
|
||||
detail.file = this.file;
|
||||
detail.id = this.directUpload.id;
|
||||
|
|
|
@ -503,7 +503,7 @@
|
|||
}
|
||||
}
|
||||
class BlobRecord {
|
||||
constructor(file, checksum, url) {
|
||||
constructor(file, checksum, url, directUploadToken, attachmentName) {
|
||||
this.file = file;
|
||||
this.attributes = {
|
||||
filename: file.name,
|
||||
|
@ -511,6 +511,8 @@
|
|||
byte_size: file.size,
|
||||
checksum: checksum
|
||||
};
|
||||
this.directUploadToken = directUploadToken;
|
||||
this.attachmentName = attachmentName;
|
||||
this.xhr = new XMLHttpRequest;
|
||||
this.xhr.open("POST", url, true);
|
||||
this.xhr.responseType = "json";
|
||||
|
@ -538,7 +540,9 @@
|
|||
create(callback) {
|
||||
this.callback = callback;
|
||||
this.xhr.send(JSON.stringify({
|
||||
blob: this.attributes
|
||||
blob: this.attributes,
|
||||
direct_upload_token: this.directUploadToken,
|
||||
attachment_name: this.attachmentName
|
||||
}));
|
||||
}
|
||||
requestDidLoad(event) {
|
||||
|
@ -596,10 +600,12 @@
|
|||
}
|
||||
let id = 0;
|
||||
class DirectUpload {
|
||||
constructor(file, url, delegate) {
|
||||
constructor(file, url, serviceName, attachmentName, delegate) {
|
||||
this.id = ++id;
|
||||
this.file = file;
|
||||
this.url = url;
|
||||
this.serviceName = serviceName;
|
||||
this.attachmentName = attachmentName;
|
||||
this.delegate = delegate;
|
||||
}
|
||||
create(callback) {
|
||||
|
@ -608,7 +614,7 @@
|
|||
callback(error);
|
||||
return;
|
||||
}
|
||||
const blob = new BlobRecord(this.file, checksum, this.url);
|
||||
const blob = new BlobRecord(this.file, checksum, this.url, this.serviceName, this.attachmentName);
|
||||
notify(this.delegate, "directUploadWillCreateBlobWithXHR", blob.xhr);
|
||||
blob.create((error => {
|
||||
if (error) {
|
||||
|
@ -637,7 +643,7 @@
|
|||
constructor(input, file) {
|
||||
this.input = input;
|
||||
this.file = file;
|
||||
this.directUpload = new DirectUpload(this.file, this.url, this);
|
||||
this.directUpload = new DirectUpload(this.file, this.url, this.directUploadToken, this.attachmentName, this);
|
||||
this.dispatch("initialize");
|
||||
}
|
||||
start(callback) {
|
||||
|
@ -668,6 +674,12 @@
|
|||
get url() {
|
||||
return this.input.getAttribute("data-direct-upload-url");
|
||||
}
|
||||
get directUploadToken() {
|
||||
return this.input.getAttribute("data-direct-upload-token");
|
||||
}
|
||||
get attachmentName() {
|
||||
return this.input.getAttribute("data-direct-upload-attachment-name");
|
||||
}
|
||||
dispatch(name, detail = {}) {
|
||||
detail.file = this.file;
|
||||
detail.id = this.directUpload.id;
|
||||
|
|
|
@ -4,8 +4,10 @@
|
|||
# When the client-side upload is completed, the signed_blob_id can be submitted as part of the form to reference
|
||||
# the blob that was created up front.
|
||||
class ActiveStorage::DirectUploadsController < ActiveStorage::BaseController
|
||||
include ActiveStorage::DirectUploadToken
|
||||
|
||||
def create
|
||||
blob = ActiveStorage::Blob.create_before_direct_upload!(**blob_args)
|
||||
blob = ActiveStorage::Blob.create_before_direct_upload!(**blob_args.merge(service_name: verified_service_name))
|
||||
render json: direct_upload_json(blob)
|
||||
end
|
||||
|
||||
|
@ -14,6 +16,10 @@ class ActiveStorage::DirectUploadsController < ActiveStorage::BaseController
|
|||
params.require(:blob).permit(:filename, :byte_size, :checksum, :content_type, metadata: {}).to_h.symbolize_keys
|
||||
end
|
||||
|
||||
def verified_service_name
|
||||
ActiveStorage::DirectUploadToken.verify_direct_upload_token(params[:direct_upload_token], params[:attachment_name], session)
|
||||
end
|
||||
|
||||
def direct_upload_json(blob)
|
||||
blob.as_json(root: false, methods: :signed_id).merge(direct_upload: {
|
||||
url: blob.service_url_for_direct_upload,
|
||||
|
|
|
@ -1,16 +1,19 @@
|
|||
import { getMetaValue } from "./helpers"
|
||||
|
||||
export class BlobRecord {
|
||||
constructor(file, checksum, url) {
|
||||
constructor(file, checksum, url, directUploadToken, attachmentName) {
|
||||
this.file = file
|
||||
|
||||
this.attributes = {
|
||||
filename: file.name,
|
||||
content_type: file.type || "application/octet-stream",
|
||||
byte_size: file.size,
|
||||
checksum: checksum
|
||||
checksum: checksum,
|
||||
}
|
||||
|
||||
this.directUploadToken = directUploadToken
|
||||
this.attachmentName = attachmentName
|
||||
|
||||
this.xhr = new XMLHttpRequest
|
||||
this.xhr.open("POST", url, true)
|
||||
this.xhr.responseType = "json"
|
||||
|
@ -43,7 +46,11 @@ export class BlobRecord {
|
|||
|
||||
create(callback) {
|
||||
this.callback = callback
|
||||
this.xhr.send(JSON.stringify({ blob: this.attributes }))
|
||||
this.xhr.send(JSON.stringify({
|
||||
blob: this.attributes,
|
||||
direct_upload_token: this.directUploadToken,
|
||||
attachment_name: this.attachmentName
|
||||
}))
|
||||
}
|
||||
|
||||
requestDidLoad(event) {
|
||||
|
|
|
@ -5,10 +5,12 @@ import { BlobUpload } from "./blob_upload"
|
|||
let id = 0
|
||||
|
||||
export class DirectUpload {
|
||||
constructor(file, url, delegate) {
|
||||
constructor(file, url, serviceName, attachmentName, delegate) {
|
||||
this.id = ++id
|
||||
this.file = file
|
||||
this.url = url
|
||||
this.serviceName = serviceName
|
||||
this.attachmentName = attachmentName
|
||||
this.delegate = delegate
|
||||
}
|
||||
|
||||
|
@ -19,7 +21,7 @@ export class DirectUpload {
|
|||
return
|
||||
}
|
||||
|
||||
const blob = new BlobRecord(this.file, checksum, this.url)
|
||||
const blob = new BlobRecord(this.file, checksum, this.url, this.serviceName, this.attachmentName)
|
||||
notify(this.delegate, "directUploadWillCreateBlobWithXHR", blob.xhr)
|
||||
|
||||
blob.create(error => {
|
||||
|
|
|
@ -5,7 +5,7 @@ export class DirectUploadController {
|
|||
constructor(input, file) {
|
||||
this.input = input
|
||||
this.file = file
|
||||
this.directUpload = new DirectUpload(this.file, this.url, this)
|
||||
this.directUpload = new DirectUpload(this.file, this.url, this.directUploadToken, this.attachmentName, this)
|
||||
this.dispatch("initialize")
|
||||
}
|
||||
|
||||
|
@ -41,6 +41,14 @@ export class DirectUploadController {
|
|||
return this.input.getAttribute("data-direct-upload-url")
|
||||
}
|
||||
|
||||
get directUploadToken() {
|
||||
return this.input.getAttribute("data-direct-upload-token")
|
||||
}
|
||||
|
||||
get attachmentName() {
|
||||
return this.input.getAttribute("data-direct-upload-attachment-name")
|
||||
}
|
||||
|
||||
dispatch(name, detail = {}) {
|
||||
detail.file = this.file
|
||||
detail.id = this.directUpload.id
|
||||
|
|
|
@ -41,6 +41,7 @@ module ActiveStorage
|
|||
autoload :Service
|
||||
autoload :Previewer
|
||||
autoload :Analyzer
|
||||
autoload :DirectUploadToken
|
||||
|
||||
mattr_accessor :logger
|
||||
mattr_accessor :verifier
|
||||
|
|
59
activestorage/lib/active_storage/direct_upload_token.rb
Normal file
59
activestorage/lib/active_storage/direct_upload_token.rb
Normal file
|
@ -0,0 +1,59 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module ActiveStorage
|
||||
module DirectUploadToken
|
||||
extend self
|
||||
|
||||
SEPARATOR = "."
|
||||
DIRECT_UPLOAD_TOKEN_LENGTH = 32
|
||||
|
||||
def generate_direct_upload_token(attachment_name, service_name, session)
|
||||
token = direct_upload_token(session, attachment_name)
|
||||
encode_direct_upload_token([service_name, token].join(SEPARATOR))
|
||||
end
|
||||
|
||||
def verify_direct_upload_token(token, attachment_name, session)
|
||||
raise ActiveStorage::InvalidDirectUploadTokenError if token.nil?
|
||||
|
||||
service_name, *token_components = decode_token(token).split(SEPARATOR)
|
||||
decoded_token = token_components.join(SEPARATOR)
|
||||
|
||||
return service_name if valid_direct_upload_token?(decoded_token, attachment_name, session)
|
||||
|
||||
raise ActiveStorage::InvalidDirectUploadTokenError
|
||||
end
|
||||
|
||||
private
|
||||
def direct_upload_token(session, attachment_name) # :doc:
|
||||
direct_upload_token_hmac(session, "direct_upload##{attachment_name}")
|
||||
end
|
||||
|
||||
def valid_direct_upload_token?(token, attachment_name, session) # :doc:
|
||||
correct_token = direct_upload_token(session, attachment_name)
|
||||
ActiveSupport::SecurityUtils.fixed_length_secure_compare(token, correct_token)
|
||||
rescue ArgumentError
|
||||
raise ActiveStorage::InvalidDirectUploadTokenError
|
||||
end
|
||||
|
||||
def direct_upload_token_hmac(session, identifier) # :doc:
|
||||
OpenSSL::HMAC.digest(
|
||||
OpenSSL::Digest::SHA256.new,
|
||||
real_direct_upload_token(session),
|
||||
identifier
|
||||
)
|
||||
end
|
||||
|
||||
def real_direct_upload_token(session) # :doc:
|
||||
session[:_direct_upload_token] ||= SecureRandom.urlsafe_base64(DIRECT_UPLOAD_TOKEN_LENGTH, padding: false)
|
||||
encode_direct_upload_token(session[:_direct_upload_token])
|
||||
end
|
||||
|
||||
def decode_token(encoded_token) # :nodoc:
|
||||
Base64.urlsafe_decode64(encoded_token)
|
||||
end
|
||||
|
||||
def encode_direct_upload_token(raw_token) # :nodoc:
|
||||
Base64.urlsafe_encode64(raw_token)
|
||||
end
|
||||
end
|
||||
end
|
|
@ -26,4 +26,7 @@ module ActiveStorage
|
|||
|
||||
# Raised when a Previewer is unable to generate a preview image.
|
||||
class PreviewError < Error; end
|
||||
|
||||
# Raised when direct upload fails because of the invalid token
|
||||
class InvalidDirectUploadTokenError < Error; end
|
||||
end
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
require "test_helper"
|
||||
require "database/setup"
|
||||
require "minitest/mock"
|
||||
|
||||
if SERVICE_CONFIGURATIONS[:s3] && SERVICE_CONFIGURATIONS[:s3][:access_key_id].present?
|
||||
class ActiveStorage::S3DirectUploadsControllerTest < ActionDispatch::IntegrationTest
|
||||
|
@ -24,8 +25,10 @@ if SERVICE_CONFIGURATIONS[:s3] && SERVICE_CONFIGURATIONS[:s3][:access_key_id].pr
|
|||
"library_ID": "12345"
|
||||
}
|
||||
|
||||
post rails_direct_uploads_url, params: { blob: {
|
||||
filename: "hello.txt", byte_size: 6, checksum: checksum, content_type: "text/plain", metadata: metadata } }
|
||||
ActiveStorage::DirectUploadToken.stub(:verify_direct_upload_token, "local") do
|
||||
post rails_direct_uploads_url, params: { blob: {
|
||||
filename: "hello.txt", byte_size: 6, checksum: checksum, content_type: "text/plain", metadata: metadata } }
|
||||
end
|
||||
|
||||
response.parsed_body.tap do |details|
|
||||
assert_equal ActiveStorage::Blob.find(details["id"]), ActiveStorage::Blob.find_signed!(details["signed_id"])
|
||||
|
@ -67,8 +70,10 @@ if SERVICE_CONFIGURATIONS[:gcs]
|
|||
"library_ID": "12345"
|
||||
}
|
||||
|
||||
post rails_direct_uploads_url, params: { blob: {
|
||||
filename: "hello.txt", byte_size: 6, checksum: checksum, content_type: "text/plain", metadata: metadata } }
|
||||
ActiveStorage::DirectUploadToken.stub(:verify_direct_upload_token, "local") do
|
||||
post rails_direct_uploads_url, params: { blob: {
|
||||
filename: "hello.txt", byte_size: 6, checksum: checksum, content_type: "text/plain", metadata: metadata } }
|
||||
end
|
||||
|
||||
@response.parsed_body.tap do |details|
|
||||
assert_equal ActiveStorage::Blob.find(details["id"]), ActiveStorage::Blob.find_signed!(details["signed_id"])
|
||||
|
@ -109,8 +114,10 @@ if SERVICE_CONFIGURATIONS[:azure]
|
|||
"library_ID": "12345"
|
||||
}
|
||||
|
||||
post rails_direct_uploads_url, params: { blob: {
|
||||
filename: "hello.txt", byte_size: 6, checksum: checksum, content_type: "text/plain", metadata: metadata } }
|
||||
ActiveStorage::DirectUploadToken.stub(:verify_direct_upload_token, "local") do
|
||||
post rails_direct_uploads_url, params: { blob: {
|
||||
filename: "hello.txt", byte_size: 6, checksum: checksum, content_type: "text/plain", metadata: metadata } }
|
||||
end
|
||||
|
||||
@response.parsed_body.tap do |details|
|
||||
assert_equal ActiveStorage::Blob.find(details["id"]), ActiveStorage::Blob.find_signed!(details["signed_id"])
|
||||
|
@ -139,8 +146,10 @@ class ActiveStorage::DiskDirectUploadsControllerTest < ActionDispatch::Integrati
|
|||
"library_ID": "12345"
|
||||
}
|
||||
|
||||
post rails_direct_uploads_url, params: { blob: {
|
||||
filename: "hello.txt", byte_size: 6, checksum: checksum, content_type: "text/plain", metadata: metadata } }
|
||||
with_valid_service_name do
|
||||
post rails_direct_uploads_url, params: { blob: {
|
||||
filename: "hello.txt", byte_size: 6, checksum: checksum, content_type: "text/plain", metadata: metadata } }
|
||||
end
|
||||
|
||||
@response.parsed_body.tap do |details|
|
||||
assert_equal ActiveStorage::Blob.find(details["id"]), ActiveStorage::Blob.find_signed!(details["signed_id"])
|
||||
|
@ -164,9 +173,11 @@ class ActiveStorage::DiskDirectUploadsControllerTest < ActionDispatch::Integrati
|
|||
"library_ID": "12345"
|
||||
}
|
||||
|
||||
set_include_root_in_json(true) do
|
||||
post rails_direct_uploads_url, params: { blob: {
|
||||
filename: "hello.txt", byte_size: 6, checksum: checksum, content_type: "text/plain", metadata: metadata } }
|
||||
with_valid_service_name do
|
||||
set_include_root_in_json(true) do
|
||||
post rails_direct_uploads_url, params: { blob: {
|
||||
filename: "hello.txt", byte_size: 6, checksum: checksum, content_type: "text/plain", metadata: metadata } }
|
||||
end
|
||||
end
|
||||
|
||||
@response.parsed_body.tap do |details|
|
||||
|
@ -175,6 +186,33 @@ class ActiveStorage::DiskDirectUploadsControllerTest < ActionDispatch::Integrati
|
|||
end
|
||||
end
|
||||
|
||||
test "handling direct upload with custom service name" do
|
||||
checksum = OpenSSL::Digest::MD5.base64digest("Hello")
|
||||
metadata = {
|
||||
"foo": "bar",
|
||||
"my_key_1": "my_value_1",
|
||||
"my_key_2": "my_value_2",
|
||||
"platform": "my_platform",
|
||||
"library_ID": "12345"
|
||||
}
|
||||
|
||||
with_valid_service_name do
|
||||
post rails_direct_uploads_url, params: { blob: {
|
||||
filename: "hello.txt", byte_size: 6, checksum: checksum, content_type: "text/plain", metadata: metadata } }
|
||||
end
|
||||
|
||||
@response.parsed_body.tap do |details|
|
||||
assert_equal ActiveStorage::Blob.find(details["id"]), ActiveStorage::Blob.find_signed!(details["signed_id"])
|
||||
assert_equal "hello.txt", details["filename"]
|
||||
assert_equal 6, details["byte_size"]
|
||||
assert_equal checksum, details["checksum"]
|
||||
assert_equal metadata, details["metadata"].transform_keys(&:to_sym)
|
||||
assert_equal "text/plain", details["content_type"]
|
||||
assert_match(/rails\/active_storage\/disk/, details["direct_upload"]["url"])
|
||||
assert_equal({ "Content-Type" => "text/plain" }, details["direct_upload"]["headers"])
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
def set_include_root_in_json(value)
|
||||
original = ActiveRecord::Base.include_root_in_json
|
||||
|
@ -183,4 +221,8 @@ class ActiveStorage::DiskDirectUploadsControllerTest < ActionDispatch::Integrati
|
|||
ensure
|
||||
ActiveRecord::Base.include_root_in_json = original
|
||||
end
|
||||
|
||||
def with_valid_service_name(&block)
|
||||
ActiveStorage::DirectUploadToken.stub(:verify_direct_upload_token, "local", &block)
|
||||
end
|
||||
end
|
||||
|
|
54
activestorage/test/direct_upload_token_test.rb
Normal file
54
activestorage/test/direct_upload_token_test.rb
Normal file
|
@ -0,0 +1,54 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require "test_helper"
|
||||
|
||||
class DirectUploadTokenTest < ActionController::TestCase
|
||||
setup do
|
||||
@session = {}
|
||||
@service_name = "local"
|
||||
@avatar_attachment_name = "user#avatar"
|
||||
end
|
||||
|
||||
def test_validates_correct_token
|
||||
token = ActiveStorage::DirectUploadToken.generate_direct_upload_token(@avatar_attachment_name, @service_name, @session)
|
||||
verified_service_name = ActiveStorage::DirectUploadToken.verify_direct_upload_token(token, @avatar_attachment_name, @session)
|
||||
|
||||
assert_equal verified_service_name, @service_name
|
||||
end
|
||||
|
||||
def test_not_validates_nil_token
|
||||
token = nil
|
||||
|
||||
assert_raises(ActiveStorage::InvalidDirectUploadTokenError) do
|
||||
ActiveStorage::DirectUploadToken.verify_direct_upload_token(token, @avatar_attachment_name, @session)
|
||||
end
|
||||
end
|
||||
|
||||
def test_not_validates_token_when_session_is_empty
|
||||
token = ActiveStorage::DirectUploadToken.generate_direct_upload_token(@avatar_attachment_name, @service_name, {})
|
||||
|
||||
assert_raises(ActiveStorage::InvalidDirectUploadTokenError) do
|
||||
ActiveStorage::DirectUploadToken.verify_direct_upload_token(token, @avatar_attachment_name, @session)
|
||||
end
|
||||
end
|
||||
|
||||
def test_not_validates_token_from_different_attachment
|
||||
background_attachment_name = "user#background"
|
||||
token = ActiveStorage::DirectUploadToken.generate_direct_upload_token(background_attachment_name, @service_name, @session)
|
||||
|
||||
assert_raises(ActiveStorage::InvalidDirectUploadTokenError) do
|
||||
ActiveStorage::DirectUploadToken.verify_direct_upload_token(token, @avatar_attachment_name, @session)
|
||||
end
|
||||
end
|
||||
|
||||
def test_not_validates_token_from_different_session
|
||||
token = ActiveStorage::DirectUploadToken.generate_direct_upload_token(@avatar_attachment_name, @service_name, @session)
|
||||
|
||||
another_session = {}
|
||||
ActiveStorage::DirectUploadToken.generate_direct_upload_token(@avatar_attachment_name, @service_name, another_session)
|
||||
|
||||
assert_raises(ActiveStorage::InvalidDirectUploadTokenError) do
|
||||
ActiveStorage::DirectUploadToken.verify_direct_upload_token(token, @avatar_attachment_name, another_session)
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1148,9 +1148,12 @@ input.addEventListener('change', (event) => {
|
|||
|
||||
const uploadFile = (file) => {
|
||||
// your form needs the file_field direct_upload: true, which
|
||||
// provides data-direct-upload-url
|
||||
// provides data-direct-upload-url, data-direct-upload-token
|
||||
// and data-direct-upload-attachment-name
|
||||
const url = input.dataset.directUploadUrl
|
||||
const upload = new DirectUpload(file, url)
|
||||
const token = input.dataset.directUploadToken
|
||||
const attachmentName = input.dataset.directUploadAttachmentName
|
||||
const upload = new DirectUpload(file, url, token, attachmentName)
|
||||
|
||||
upload.create((error, blob) => {
|
||||
if (error) {
|
||||
|
@ -1169,7 +1172,7 @@ const uploadFile = (file) => {
|
|||
}
|
||||
```
|
||||
|
||||
If you need to track the progress of the file upload, you can pass a third
|
||||
If you need to track the progress of the file upload, you can pass a fifth
|
||||
parameter to the `DirectUpload` constructor. During the upload, DirectUpload
|
||||
will call the object's `directUploadWillStoreFileWithXHR` method. You can then
|
||||
bind your own progress handler on the XHR.
|
||||
|
@ -1178,8 +1181,8 @@ bind your own progress handler on the XHR.
|
|||
import { DirectUpload } from "@rails/activestorage"
|
||||
|
||||
class Uploader {
|
||||
constructor(file, url) {
|
||||
this.upload = new DirectUpload(this.file, this.url, this)
|
||||
constructor(file, url, token, attachmentName) {
|
||||
this.upload = new DirectUpload(file, url, token, attachmentName, this)
|
||||
}
|
||||
|
||||
upload(file) {
|
||||
|
|
Loading…
Reference in a new issue