Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions app/controllers/attachments_controller.rb
Original file line number Diff line number Diff line change
@@ -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
49 changes: 49 additions & 0 deletions app/views/messages/_attachment.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
<% if attachment.content_type&.start_with?('image/') %>
<div class="space-y-2">
<div class="flex items-center gap-3 text-sm">
<svg class="w-4 h-4 text-gray-400 dark:text-gray-500 flex-shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 16l4.586-4.586a2 2 0 012.828 0L16 16m-2-2l1.586-1.586a2 2 0 012.828 0L20 14m-6-6h.01M6 20h12a2 2 0 002-2V6a2 2 0 00-2-2H6a2 2 0 00-2 2v12a2 2 0 002 2z"></path>
</svg>
<%= 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" %>
<span class="text-gray-500 dark:text-gray-400">(<%= number_to_human_size(attachment.byte_size) %>)</span>
</div>
<%= 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 %>
</div>
<% elsif attachment.content_type&.start_with?('text/') %>
<div class="space-y-2">
<div class="flex items-center gap-3 text-sm">
<svg class="w-4 h-4 text-gray-400 dark:text-gray-500 flex-shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"></path>
</svg>
<%= 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" %>
<span class="text-gray-500 dark:text-gray-400">(<%= number_to_human_size(attachment.byte_size) %>, <%= attachment.content_type %>)</span>
</div>
<% if attachment.byte_size < 100_000 %>
<pre class="whitespace-pre-wrap font-mono text-sm text-gray-800 dark:text-gray-300 bg-gray-100 dark:bg-gray-800 p-4 rounded border border-gray-300 dark:border-gray-600 overflow-x-auto"><%= attachment.download %></pre>
<% end %>
</div>
<% elsif attachment.content_type == 'application/pdf' %>
<div class="flex items-center gap-3 text-sm">
<svg class="w-4 h-4 text-red-500 dark:text-red-400 flex-shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 21h10a2 2 0 002-2V9.414a1 1 0 00-.293-.707l-5.414-5.414A1 1 0 0012.586 3H7a2 2 0 00-2 2v14a2 2 0 002 2z"></path>
</svg>
<%= 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" %>
<span class="text-gray-500 dark:text-gray-400">(<%= number_to_human_size(attachment.byte_size) %>, PDF)</span>
</div>
<% elsif attachment.content_type&.match?(/zip|tar|gzip|compress|bzip|lha/) %>
<div class="flex items-center gap-3 text-sm">
<svg class="w-4 h-4 text-gray-400 dark:text-gray-500 flex-shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 19a2 2 0 01-2-2V7a2 2 0 012-2h4l2 2h4a2 2 0 012 2v1M5 19h14a2 2 0 002-2v-5a2 2 0 00-2-2H9a2 2 0 00-2 2v5a2 2 0 01-2 2z"></path>
</svg>
<%= 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" %>
<span class="text-gray-500 dark:text-gray-400">(<%= number_to_human_size(attachment.byte_size) %>, Archive)</span>
</div>
<% else %>
<div class="flex items-center gap-3 text-sm">
<svg class="w-4 h-4 text-gray-400 dark:text-gray-500 flex-shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15.172 7l-6.586 6.586a2 2 0 102.828 2.828l6.414-6.586a4 4 0 00-5.656-5.656l-6.415 6.585a6 6 0 108.486 8.486L20.5 13"></path>
</svg>
<%= 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" %>
<span class="text-gray-500 dark:text-gray-400">(<%= number_to_human_size(attachment.byte_size) %><% if attachment.content_type %>, <%= attachment.content_type %><% end %>)</span>
</div>
<% end %>
9 changes: 9 additions & 0 deletions app/views/messages/_message.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -23,4 +23,13 @@
<div class="px-6 py-4">
<pre class="whitespace-pre-wrap font-mono text-sm text-gray-800 dark:text-gray-300 leading-relaxed"><%= message.body %></pre>
</div>

<% if message.attachments.attached? %>
<div class="border-t border-gray-200 dark:border-gray-700 px-6 py-4 bg-gray-50 dark:bg-gray-900">
<h3 class="text-sm font-medium text-gray-700 dark:text-gray-300 mb-3">Attachments (<%= message.attachments.count %>)</h3>
<div class="space-y-4">
<%= render partial: 'messages/attachment', collection: message.attachments %>
</div>
</div>
<% end %>
</div>
2 changes: 2 additions & 0 deletions config/routes.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
28 changes: 28 additions & 0 deletions lib/active_storage/service/database_service.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion test/controllers/messages_controller_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

class MessagesControllerTest < ActionDispatch::IntegrationTest
setup do
@message = messages(:one)
@message = messages(:message1)
end

test "should get index" do
Expand Down
5 changes: 5 additions & 0 deletions test/fixtures/active_storage/attachments.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
attachment2:
name: attachments
record_type: Message
record: message2
blob: blob2
7 changes: 7 additions & 0 deletions test/fixtures/active_storage/blobs.yml
Original file line number Diff line number Diff line change
@@ -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
3 changes: 3 additions & 0 deletions test/fixtures/active_storage/file_blobs.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
file_blob2:
key: 3mj2l9tdw1nb531oajdax3r7vylh
data: Hello
16 changes: 8 additions & 8 deletions test/fixtures/messages.yml
Original file line number Diff line number Diff line change
@@ -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
13 changes: 12 additions & 1 deletion test/system/messages_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Loading