Skip to content

Commit fc52233

Browse files
authored
Merge pull request #72 from amatsuda/show_attachments
Show attachments in HTML
2 parents a3fc7f6 + 8d13309 commit fc52233

File tree

11 files changed

+146
-10
lines changed

11 files changed

+146
-10
lines changed
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
# frozen_string_literal: true
2+
3+
class AttachmentsController < ApplicationController
4+
skip_forgery_protection
5+
6+
def show
7+
if (key = decode_verified_key)
8+
send_data ActiveStorage::Blob.service.download(key[:key]), content_type: key[:content_type], disposition: key[:disposition]
9+
else
10+
head :not_found
11+
end
12+
rescue Errno::ENOENT
13+
head :not_found
14+
end
15+
16+
private
17+
18+
def decode_verified_key
19+
key = ActiveStorage.verifier.verified(params[:encoded_key], purpose: :blob_key)
20+
key&.deep_symbolize_keys
21+
end
22+
end
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
<% if attachment.content_type&.start_with?('image/') %>
2+
<div class="space-y-2">
3+
<div class="flex items-center gap-3 text-sm">
4+
<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">
5+
<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>
6+
</svg>
7+
<%= 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" %>
8+
<span class="text-gray-500 dark:text-gray-400">(<%= number_to_human_size(attachment.byte_size) %>)</span>
9+
</div>
10+
<%= 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 %>
11+
</div>
12+
<% elsif attachment.content_type&.start_with?('text/') %>
13+
<div class="space-y-2">
14+
<div class="flex items-center gap-3 text-sm">
15+
<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">
16+
<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>
17+
</svg>
18+
<%= 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" %>
19+
<span class="text-gray-500 dark:text-gray-400">(<%= number_to_human_size(attachment.byte_size) %>, <%= attachment.content_type %>)</span>
20+
</div>
21+
<% if attachment.byte_size < 100_000 %>
22+
<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>
23+
<% end %>
24+
</div>
25+
<% elsif attachment.content_type == 'application/pdf' %>
26+
<div class="flex items-center gap-3 text-sm">
27+
<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">
28+
<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>
29+
</svg>
30+
<%= 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" %>
31+
<span class="text-gray-500 dark:text-gray-400">(<%= number_to_human_size(attachment.byte_size) %>, PDF)</span>
32+
</div>
33+
<% elsif attachment.content_type&.match?(/zip|tar|gzip|compress|bzip|lha/) %>
34+
<div class="flex items-center gap-3 text-sm">
35+
<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">
36+
<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>
37+
</svg>
38+
<%= 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" %>
39+
<span class="text-gray-500 dark:text-gray-400">(<%= number_to_human_size(attachment.byte_size) %>, Archive)</span>
40+
</div>
41+
<% else %>
42+
<div class="flex items-center gap-3 text-sm">
43+
<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">
44+
<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>
45+
</svg>
46+
<%= 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" %>
47+
<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>
48+
</div>
49+
<% end %>

app/views/messages/_message.html.erb

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,4 +23,13 @@
2323
<div class="px-6 py-4">
2424
<pre class="whitespace-pre-wrap font-mono text-sm text-gray-800 dark:text-gray-300 leading-relaxed"><%= message.body %></pre>
2525
</div>
26+
27+
<% if message.attachments.attached? %>
28+
<div class="border-t border-gray-200 dark:border-gray-700 px-6 py-4 bg-gray-50 dark:bg-gray-900">
29+
<h3 class="text-sm font-medium text-gray-700 dark:text-gray-300 mb-3">Attachments (<%= message.attachments.count %>)</h3>
30+
<div class="space-y-4">
31+
<%= render partial: 'messages/attachment', collection: message.attachments %>
32+
</div>
33+
</div>
34+
<% end %>
2635
</div>

config/routes.rb

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
get '/:list_name/:list_seq', to: 'messages#show'
44
get '/:list_name/', to: 'messages#index'
55

6+
get '/attachments/:encoded_key/*filename' => 'attachments#show', as: :attachment
7+
68
# Define your application routes per the DSL in https://guides.rubyonrails.org/routing.html
79

810
# Reveal health status on /up that returns 200 if the app boots with no exceptions, otherwise 500.

lib/active_storage/service/database_service.rb

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,34 @@ def exist?(key)
3333

3434
private
3535

36+
def private_url(key, expires_in:, filename:, content_type:, disposition:, **)
37+
content_disposition = content_disposition_with(type: disposition, filename: filename)
38+
verified_key_with_expiration = ActiveStorage.verifier.generate(
39+
{
40+
key: key,
41+
disposition: content_disposition,
42+
content_type: content_type,
43+
service_name: name
44+
},
45+
expires_in: expires_in,
46+
purpose: :blob_key
47+
)
48+
49+
if url_options.blank?
50+
raise ArgumentError, "Cannot generate URL for #{filename} using Database service, please set ActiveStorage::Current.url_options."
51+
end
52+
53+
url_helpers.attachment_url(verified_key_with_expiration, filename: filename, **url_options)
54+
end
55+
56+
def url_helpers
57+
@url_helpers ||= Rails.application.routes.url_helpers
58+
end
59+
60+
def url_options
61+
ActiveStorage::Current.url_options
62+
end
63+
3664
def service_name
3765
'Database'
3866
end

test/controllers/messages_controller_test.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
class MessagesControllerTest < ActionDispatch::IntegrationTest
44
setup do
5-
@message = messages(:one)
5+
@message = messages(:message1)
66
end
77

88
test "should get index" do
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
attachment2:
2+
name: attachments
3+
record_type: Message
4+
record: message2
5+
blob: blob2
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
blob2:
2+
key: 3mj2l9tdw1nb531oajdax3r7vylh
3+
filename: 1.patch
4+
content_type: text/x-diff
5+
metadata: '{"identified":true,"analyzed":true}'
6+
byte_size: 5
7+
service_name: local
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
file_blob2:
2+
key: 3mj2l9tdw1nb531oajdax3r7vylh
3+
data: Hello

test/fixtures/messages.yml

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,15 @@
11
# Read about fixtures at https://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html
22

3-
one:
4-
subject: MyString
5-
from: MyString
6-
body: MyText
3+
message1:
4+
subject: Mail1
5+
from: From1
6+
body: Body1
77
list_id: 1
88
list_seq: 123
99

10-
two:
11-
subject: MyString
12-
from: MyString
13-
body: MyText
10+
message2:
11+
subject: Mail2
12+
from: From2
13+
body: Body2
1414
list_id: 2
1515
list_seq: 234

0 commit comments

Comments
 (0)