diff --git a/app/controllers/users/avatars_controller.rb b/app/controllers/users/avatars_controller.rb index 61ce75f9f..d85e5f869 100644 --- a/app/controllers/users/avatars_controller.rb +++ b/app/controllers/users/avatars_controller.rb @@ -10,7 +10,7 @@ class Users::AvatarsController < ApplicationController if @user.system? redirect_to view_context.image_path("system_user.png") elsif @user.avatar.attached? - redirect_to rails_blob_url(@user.avatar.variant(:thumb), disposition: "inline") + redirect_to rails_blob_url(@user.avatar_thumbnail, disposition: "inline") elsif stale? @user, cache_control: cache_control render_initials end diff --git a/app/models/user.rb b/app/models/user.rb index d70a260ea..8c0ac42a5 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -1,12 +1,8 @@ class User < ApplicationRecord - include Accessor, Assignee, Attachable, Configurable, EmailAddressChangeable, + include Accessor, Assignee, Attachable, Avatar, Configurable, EmailAddressChangeable, Mentionable, Named, Notifiable, Role, Searcher, Watcher include Timelined # Depends on Accessor - has_one_attached :avatar do |attachable| - attachable.variant :thumb, resize_to_fill: [ 256, 256 ] - end - belongs_to :account belongs_to :identity, optional: true diff --git a/app/models/user/avatar.rb b/app/models/user/avatar.rb new file mode 100644 index 000000000..a4b2ae631 --- /dev/null +++ b/app/models/user/avatar.rb @@ -0,0 +1,24 @@ +module User::Avatar + extend ActiveSupport::Concern + + ALLOWED_AVATAR_CONTENT_TYPES = %w[ image/jpeg image/png image/gif image/webp ].freeze + + included do + has_one_attached :avatar do |attachable| + attachable.variant :thumb, resize_to_fill: [ 256, 256 ] + end + + validate :avatar_content_type_allowed + end + + def avatar_thumbnail + avatar.variable? ? avatar.variant(:thumb) : avatar + end + + private + def avatar_content_type_allowed + if avatar.attached? && !ALLOWED_AVATAR_CONTENT_TYPES.include?(avatar.content_type) + errors.add(:avatar, "must be a JPEG, PNG, GIF, or WebP image") + end + end +end diff --git a/app/views/users/edit.html.erb b/app/views/users/edit.html.erb index ab189c6ab..a3b5845e8 100644 --- a/app/views/users/edit.html.erb +++ b/app/views/users/edit.html.erb @@ -14,7 +14,7 @@ diff --git a/test/controllers/users/avatars_controller_test.rb b/test/controllers/users/avatars_controller_test.rb index 737c28267..3ded884b8 100644 --- a/test/controllers/users/avatars_controller_test.rb +++ b/test/controllers/users/avatars_controller_test.rb @@ -32,7 +32,7 @@ class Users::AvatarsControllerTest < ActionDispatch::IntegrationTest get user_avatar_path(users(:david)) - assert_redirected_to rails_blob_url(users(:david).avatar.variant(:thumb), disposition: "inline") + assert_redirected_to rails_blob_url(users(:david).avatar_thumbnail, disposition: "inline") end test "show other image redirects to the blob url" do @@ -41,7 +41,7 @@ class Users::AvatarsControllerTest < ActionDispatch::IntegrationTest get user_avatar_path(users(:kevin)) - assert_redirected_to rails_blob_url(users(:kevin).avatar.variant(:thumb), disposition: "inline") + assert_redirected_to rails_blob_url(users(:kevin).avatar_thumbnail, disposition: "inline") end test "delete self" do diff --git a/test/fixtures/files/avatar.svg b/test/fixtures/files/avatar.svg new file mode 100644 index 000000000..1f0c5b682 --- /dev/null +++ b/test/fixtures/files/avatar.svg @@ -0,0 +1,3 @@ + + + diff --git a/test/models/user/avatar_test.rb b/test/models/user/avatar_test.rb new file mode 100644 index 000000000..96ea5bac7 --- /dev/null +++ b/test/models/user/avatar_test.rb @@ -0,0 +1,30 @@ +require "test_helper" + +class User::AvatarTest < ActiveSupport::TestCase + test "avatar_thumbnail returns variant for variable images" do + users(:david).avatar.attach(io: File.open(file_fixture("moon.jpg")), filename: "moon.jpg", content_type: "image/jpeg") + + assert users(:david).avatar.variable? + assert_equal users(:david).avatar.variant(:thumb).blob, users(:david).avatar_thumbnail.blob + end + + test "avatar_thumbnail returns original blob for non-variable images" do + users(:david).avatar.attach(io: File.open(file_fixture("avatar.svg")), filename: "avatar.svg", content_type: "image/svg+xml") + + assert_not users(:david).avatar.variable? + assert_equal users(:david).avatar.blob, users(:david).avatar_thumbnail.blob + end + + test "allows valid image content types" do + users(:david).avatar.attach(io: File.open(file_fixture("moon.jpg")), filename: "test.jpg") + + assert users(:david).valid? + end + + test "rejects SVG uploads" do + users(:david).avatar.attach(io: File.open(file_fixture("avatar.svg")), filename: "avatar.svg") + + assert_not users(:david).valid? + assert_includes users(:david).errors[:avatar], "must be a JPEG, PNG, GIF, or WebP image" + end +end