fix: Populate extension and include content_type in attachment webhook payload (#13945)
Attachment webhook event payloads (`message_created`) were missing the
file extension and content type. The `extension` column existed but was
never populated, and `content_type` was not included in the payload at
all.
## What changed
- Added `before_save :set_extension` callback to extract file extension
from the filename when saving an attachment.
- Added `content_type` (from ActiveStorage) to the `file_metadata` used
in `push_event_data`.
### Before
```json
{
"extension": null,
"data_url": "...",
"file_size": 11960
}
```
### After
```json
{
"extension": "pdf",
"content_type": "application/pdf",
"data_url": "...",
"file_size": 11960
}
```
## How to reproduce
1. Send a message with a file attachment (e.g., PDF) via any channel
2. Inspect the `message_created` webhook payload
3. Observe `extension` is `null` and `content_type` is missing
🤖 Generated with [Claude Code](https://claude.com/claude-code)
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -37,6 +37,7 @@ class Attachment < ApplicationRecord
|
||||
belongs_to :account
|
||||
belongs_to :message
|
||||
has_one_attached :file
|
||||
before_save :set_extension
|
||||
validate :acceptable_file
|
||||
validates :external_url, length: { maximum: Limits::URL_LENGTH_LIMIT }
|
||||
enum file_type: { :image => 0, :audio => 1, :video => 2, :file => 3, :location => 4, :fallback => 5, :share => 6, :story_mention => 7,
|
||||
@@ -111,6 +112,7 @@ class Attachment < ApplicationRecord
|
||||
def file_metadata
|
||||
metadata = {
|
||||
extension: extension,
|
||||
content_type: file.content_type,
|
||||
data_url: file_url,
|
||||
thumb_url: thumb_url,
|
||||
file_size: file.byte_size,
|
||||
@@ -154,6 +156,13 @@ class Attachment < ApplicationRecord
|
||||
}
|
||||
end
|
||||
|
||||
def set_extension
|
||||
return unless file.attached?
|
||||
return if extension.present?
|
||||
|
||||
self.extension = File.extname(file.filename.to_s).delete_prefix('.').presence
|
||||
end
|
||||
|
||||
def should_validate_file?
|
||||
return unless file.attached?
|
||||
# we are only limiting attachment types in case of website widget
|
||||
|
||||
@@ -187,6 +187,44 @@ RSpec.describe Attachment do
|
||||
end
|
||||
end
|
||||
|
||||
describe 'set_extension' do
|
||||
it 'sets extension from filename on save' do
|
||||
attachment = message.attachments.new(account_id: message.account_id, file_type: :file)
|
||||
attachment.file.attach(io: StringIO.new('fake pdf'), filename: 'test.pdf', content_type: 'application/pdf')
|
||||
attachment.save!
|
||||
|
||||
expect(attachment.extension).to eq('pdf')
|
||||
end
|
||||
|
||||
it 'does not overwrite extension if already set' do
|
||||
attachment = message.attachments.new(account_id: message.account_id, file_type: :file, extension: 'doc')
|
||||
attachment.file.attach(io: StringIO.new('fake pdf'), filename: 'test.pdf', content_type: 'application/pdf')
|
||||
attachment.save!
|
||||
|
||||
expect(attachment.extension).to eq('doc')
|
||||
end
|
||||
|
||||
it 'handles filenames without extension' do
|
||||
attachment = message.attachments.new(account_id: message.account_id, file_type: :file)
|
||||
attachment.file.attach(io: StringIO.new('fake data'), filename: 'README', content_type: 'text/plain')
|
||||
attachment.save!
|
||||
|
||||
expect(attachment.extension).to be_nil
|
||||
end
|
||||
end
|
||||
|
||||
describe 'push_event_data includes extension and content_type' do
|
||||
it 'returns extension and content_type for file attachments' do
|
||||
attachment = message.attachments.new(account_id: message.account_id, file_type: :file)
|
||||
attachment.file.attach(io: StringIO.new('fake pdf'), filename: 'test.pdf', content_type: 'application/pdf')
|
||||
attachment.save!
|
||||
|
||||
event_data = attachment.push_event_data
|
||||
expect(event_data[:extension]).to eq('pdf')
|
||||
expect(event_data[:content_type]).to eq('application/pdf')
|
||||
end
|
||||
end
|
||||
|
||||
describe 'file size validation' do
|
||||
let(:attachment) { message.attachments.new(account_id: message.account_id, file_type: :image) }
|
||||
|
||||
|
||||
Reference in New Issue
Block a user