diff --git a/app/controllers/attachments_controller.rb b/app/controllers/attachments_controller.rb new file mode 100644 index 0000000..e394240 --- /dev/null +++ b/app/controllers/attachments_controller.rb @@ -0,0 +1,22 @@ +# frozen_string_literal: true + +class AttachmentsController < ApplicationController + skip_forgery_protection + + def show + if (key = decode_verified_key) + send_data ActiveStorage::Blob.service.download(key[:key]), content_type: key[:content_type], disposition: key[:disposition] + else + head :not_found + end + rescue Errno::ENOENT + head :not_found + end + + private + + def decode_verified_key + key = ActiveStorage.verifier.verified(params[:encoded_key], purpose: :blob_key) + key&.deep_symbolize_keys + end +end diff --git a/app/views/messages/_attachment.html.erb b/app/views/messages/_attachment.html.erb new file mode 100644 index 0000000..bb47e56 --- /dev/null +++ b/app/views/messages/_attachment.html.erb @@ -0,0 +1,49 @@ +<% if attachment.content_type&.start_with?('image/') %> +
+
+ + + + <%= link_to attachment.filename.to_s, rails_blob_path(attachment, disposition: "attachment"), class: "text-red-600 dark:text-red-400 hover:text-red-800 dark:hover:text-red-300 font-medium" %> + (<%= number_to_human_size(attachment.byte_size) %>) +
+ <%= image_tag rails_blob_path(attachment), class: "max-w-full h-auto rounded border border-gray-300 dark:border-gray-600", alt: attachment.filename.to_s %> +
+<% elsif attachment.content_type&.start_with?('text/') %> +
+
+ + + + <%= link_to attachment.filename.to_s, rails_blob_path(attachment, disposition: "attachment"), class: "text-red-600 dark:text-red-400 hover:text-red-800 dark:hover:text-red-300 font-medium" %> + (<%= number_to_human_size(attachment.byte_size) %>, <%= attachment.content_type %>) +
+ <% if attachment.byte_size < 100_000 %> +
<%= attachment.download %>
+ <% end %> +
+<% elsif attachment.content_type == 'application/pdf' %> +
+ + + + <%= link_to attachment.filename.to_s, rails_blob_path(attachment, disposition: "attachment"), class: "text-red-600 dark:text-red-400 hover:text-red-800 dark:hover:text-red-300 font-medium" %> + (<%= number_to_human_size(attachment.byte_size) %>, PDF) +
+<% elsif attachment.content_type&.match?(/zip|tar|gzip|compress|bzip|lha/) %> +
+ + + + <%= link_to attachment.filename.to_s, rails_blob_path(attachment, disposition: "attachment"), class: "text-red-600 dark:text-red-400 hover:text-red-800 dark:hover:text-red-300 font-medium" %> + (<%= number_to_human_size(attachment.byte_size) %>, Archive) +
+<% else %> +
+ + + + <%= link_to attachment.filename.to_s, rails_blob_path(attachment, disposition: "attachment"), class: "text-red-600 dark:text-red-400 hover:text-red-800 dark:hover:text-red-300 font-medium" %> + (<%= number_to_human_size(attachment.byte_size) %><% if attachment.content_type %>, <%= attachment.content_type %><% end %>) +
+<% end %> diff --git a/app/views/messages/_message.html.erb b/app/views/messages/_message.html.erb index 09d923e..be51739 100644 --- a/app/views/messages/_message.html.erb +++ b/app/views/messages/_message.html.erb @@ -23,4 +23,13 @@
<%= message.body %>
+ + <% if message.attachments.attached? %> +
+

Attachments (<%= message.attachments.count %>)

+
+ <%= render partial: 'messages/attachment', collection: message.attachments %> +
+
+ <% end %> diff --git a/config/routes.rb b/config/routes.rb index 60f87d3..b9488a4 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -3,6 +3,8 @@ get '/:list_name/:list_seq', to: 'messages#show' get '/:list_name/', to: 'messages#index' + get '/attachments/:encoded_key/*filename' => 'attachments#show', as: :attachment + # Define your application routes per the DSL in https://guides.rubyonrails.org/routing.html # Reveal health status on /up that returns 200 if the app boots with no exceptions, otherwise 500. diff --git a/lib/active_storage/service/database_service.rb b/lib/active_storage/service/database_service.rb index 6dfd866..18ffb70 100644 --- a/lib/active_storage/service/database_service.rb +++ b/lib/active_storage/service/database_service.rb @@ -33,6 +33,34 @@ def exist?(key) private + def private_url(key, expires_in:, filename:, content_type:, disposition:, **) + content_disposition = content_disposition_with(type: disposition, filename: filename) + verified_key_with_expiration = ActiveStorage.verifier.generate( + { + key: key, + disposition: content_disposition, + content_type: content_type, + service_name: name + }, + expires_in: expires_in, + purpose: :blob_key + ) + + if url_options.blank? + raise ArgumentError, "Cannot generate URL for #{filename} using Database service, please set ActiveStorage::Current.url_options." + end + + url_helpers.attachment_url(verified_key_with_expiration, filename: filename, **url_options) + end + + def url_helpers + @url_helpers ||= Rails.application.routes.url_helpers + end + + def url_options + ActiveStorage::Current.url_options + end + def service_name 'Database' end diff --git a/test/controllers/messages_controller_test.rb b/test/controllers/messages_controller_test.rb index b4bf6f1..3301e33 100644 --- a/test/controllers/messages_controller_test.rb +++ b/test/controllers/messages_controller_test.rb @@ -2,7 +2,7 @@ class MessagesControllerTest < ActionDispatch::IntegrationTest setup do - @message = messages(:one) + @message = messages(:message1) end test "should get index" do diff --git a/test/fixtures/active_storage/attachments.yml b/test/fixtures/active_storage/attachments.yml new file mode 100644 index 0000000..c565eaa --- /dev/null +++ b/test/fixtures/active_storage/attachments.yml @@ -0,0 +1,5 @@ +attachment2: + name: attachments + record_type: Message + record: message2 + blob: blob2 diff --git a/test/fixtures/active_storage/blobs.yml b/test/fixtures/active_storage/blobs.yml new file mode 100644 index 0000000..61f177f --- /dev/null +++ b/test/fixtures/active_storage/blobs.yml @@ -0,0 +1,7 @@ +blob2: + key: 3mj2l9tdw1nb531oajdax3r7vylh + filename: 1.patch + content_type: text/x-diff + metadata: '{"identified":true,"analyzed":true}' + byte_size: 5 + service_name: local diff --git a/test/fixtures/active_storage/file_blobs.yml b/test/fixtures/active_storage/file_blobs.yml new file mode 100644 index 0000000..cebe17b --- /dev/null +++ b/test/fixtures/active_storage/file_blobs.yml @@ -0,0 +1,3 @@ +file_blob2: + key: 3mj2l9tdw1nb531oajdax3r7vylh + data: Hello diff --git a/test/fixtures/messages.yml b/test/fixtures/messages.yml index 24fc45e..426b835 100644 --- a/test/fixtures/messages.yml +++ b/test/fixtures/messages.yml @@ -1,15 +1,15 @@ # Read about fixtures at https://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html -one: - subject: MyString - from: MyString - body: MyText +message1: + subject: Mail1 + from: From1 + body: Body1 list_id: 1 list_seq: 123 -two: - subject: MyString - from: MyString - body: MyText +message2: + subject: Mail2 + from: From2 + body: Body2 list_id: 2 list_seq: 234 diff --git a/test/system/messages_test.rb b/test/system/messages_test.rb index 6eb747e..92045ed 100644 --- a/test/system/messages_test.rb +++ b/test/system/messages_test.rb @@ -2,7 +2,18 @@ class MessagesTest < ApplicationSystemTestCase setup do - @message = messages(:one) + @message1 = messages(:message1) + @message2 = messages(:message2) + end + + test 'visiting ruby-dev index, and showing a message with an attachment' do + visit '/ruby-dev' + assert_content @message2.subject + + click_link @message2.subject + assert_content @message2.body + + click_link @message2.attachments_attachments.first.blob.filename.to_s end test "visiting the index" do