From 58061f68bb049522491fb59dedcb516c3b9cbef0 Mon Sep 17 00:00:00 2001
From: Brian Austin
Date: Thu, 4 Apr 2024 13:22:59 -0400
Subject: [PATCH 01/33] AO3-5578 Replace kt-paperclip with ActiveStorage
---
.gitignore | 3 +
Gemfile | 3 +-
Gemfile.lock | 13 ++---
app/controllers/application_controller.rb | 1 +
app/helpers/collections_helper.rb | 7 +++
app/helpers/skins_helper.rb | 10 +++-
app/helpers/users_helper.rb | 11 ++--
app/models/collection.rb | 25 ++++----
app/models/pseud.rb | 35 ++++++-----
app/models/skin.rb | 35 ++++++-----
.../_unposted_claim_blurb.html.erb | 2 +-
.../collection_items/_item_fields.html.erb | 2 +-
.../collections/_collection_blurb.html.erb | 2 +-
app/views/collections/_form.html.erb | 4 +-
app/views/collections/_header.html.erb | 2 +-
app/views/pseuds/_pseuds_form.html.erb | 2 +-
config/docker/Dockerfile | 1 +
config/environments/production.rb | 8 +--
config/environments/staging.rb | 7 +--
config/locales/models/en.yml | 3 +
config/storage.yml | 35 +++--------
...te_active_storage_tables.active_storage.rb | 58 +++++++++++++++++++
lib/tasks/after_tasks.rake | 36 ------------
spec/controllers/pseuds_controller_spec.rb | 9 ++-
24 files changed, 168 insertions(+), 146 deletions(-)
create mode 100644 db/migrate/20240323013245_create_active_storage_tables.active_storage.rb
diff --git a/.gitignore b/.gitignore
index 53da1b09dbc..df5aa242f87 100644
--- a/.gitignore
+++ b/.gitignore
@@ -58,6 +58,9 @@ public/system/test
# /tmp/
/tmp/*
+# ActiveRecord storage path
+storage/
+
# /vendor/
/vendor/gems
diff --git a/Gemfile b/Gemfile
index d12d130f256..3094efdbff3 100644
--- a/Gemfile
+++ b/Gemfile
@@ -56,7 +56,6 @@ gem "aws-sdk-s3"
gem 'css_parser'
gem "terrapin"
-gem "kt-paperclip", ">= 5.2.0"
# for looking up image dimensions quickly
gem 'fastimage'
@@ -188,3 +187,5 @@ group :staging, :production do
# frameworks above it to be instrumented when the gem initializes.
gem "newrelic_rpm"
end
+
+gem "image_processing", "~> 1.12"
diff --git a/Gemfile.lock b/Gemfile.lock
index 9bd481f3879..726522cf42b 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -321,15 +321,12 @@ GEM
rails-i18n
rainbow (>= 2.2.2, < 4.0)
terminal-table (>= 1.5.1)
+ image_processing (1.12.2)
+ mini_magick (>= 4.9.5, < 5)
+ ruby-vips (>= 2.0.17, < 3)
jmespath (1.6.2)
json (2.7.1)
kgio (2.10.0)
- kt-paperclip (7.2.2)
- activemodel (>= 4.2.0)
- activesupport (>= 4.2.0)
- marcel (~> 1.0.1)
- mime-types
- terrapin (>= 0.6.0, < 2.0)
launchy (2.5.2)
addressable (~> 2.8)
lograge (0.14.0)
@@ -538,6 +535,8 @@ GEM
rubocop-rspec (2.6.0)
rubocop (~> 1.19)
ruby-progressbar (1.13.0)
+ ruby-vips (2.2.1)
+ ffi (~> 1.12)
ruby2_keywords (0.0.5)
rubyntlm (0.6.3)
rubyzip (2.3.2)
@@ -673,8 +672,8 @@ DEPENDENCIES
htmlentities
httparty
i18n-tasks
+ image_processing (~> 1.12)
kgio (= 2.10.0)
- kt-paperclip (>= 5.2.0)
launchy
lograge
mail (>= 2.8)
diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb
index 5cf4afd6843..561bc26f1b0 100644
--- a/app/controllers/application_controller.rb
+++ b/app/controllers/application_controller.rb
@@ -1,4 +1,5 @@
class ApplicationController < ActionController::Base
+ include ActiveStorage::SetCurrent
include Pundit::Authorization
protect_from_forgery with: :exception, prepend: true
rescue_from ActionController::InvalidAuthenticityToken, with: :display_auth_error
diff --git a/app/helpers/collections_helper.rb b/app/helpers/collections_helper.rb
index 59fcc53ef66..14b843aacd0 100644
--- a/app/helpers/collections_helper.rb
+++ b/app/helpers/collections_helper.rb
@@ -123,4 +123,11 @@ def collection_item_approval_options(actor:, item_type:)
[t("#{key}.rejected"), :rejected]
]
end
+
+ # Fetches the icon URL for the given collection, using the standard (100x100>) variant.
+ def standard_icon_url(collection)
+ return "/images/skins/iconsets/default/icon_collection.png" unless collection.icon.attached?
+
+ collection.icon.variant(:standard).processed.url
+ end
end
diff --git a/app/helpers/skins_helper.rb b/app/helpers/skins_helper.rb
index e0768992e7f..c81892679ea 100644
--- a/app/helpers/skins_helper.rb
+++ b/app/helpers/skins_helper.rb
@@ -9,9 +9,13 @@ def skin_author_link(skin)
# we only actually display an image if there's a file
def skin_preview_display(skin)
- if skin && skin.icon_file_name
- link_to image_tag(skin.icon.url(:standard), alt: skin.icon_alt_text, class: "icon", skip_pipeline: true), skin.icon.url(:original)
- end
+ return unless skin&.icon&.attached?
+
+ link_to image_tag(skin.icon.variant(:standard).processed.url,
+ alt: skin.icon_alt_text,
+ class: "icon",
+ skip_pipeline: true),
+ skin.icon.url
end
def skin_tag
diff --git a/app/helpers/users_helper.rb b/app/helpers/users_helper.rb
index 938044a272f..538c9fce08c 100644
--- a/app/helpers/users_helper.rb
+++ b/app/helpers/users_helper.rb
@@ -22,13 +22,10 @@ def print_pseuds(user)
# Determine which icon to show on user pages
def standard_icon(user = nil, pseud = nil)
- if pseud && pseud.icon
- pseud.icon.url(:standard).gsub(/^http:/, "https:")
- elsif user && user.default_pseud && user.default_pseud.icon
- user.default_pseud.icon.url(:standard).gsub(/^http:/, "https:")
- else
- '/images/skins/iconsets/default/icon_user.png'
- end
+ return pseud.icon.variant(:standard).processed.url if pseud&.icon&.attached?
+ return user.default_pseud.icon.variant(:standard).processed.url if user&.default_pseud&.icon&.attached?
+
+ "/images/skins/iconsets/default/icon_user.png"
end
# no alt text if there isn't specific alt text
diff --git a/app/models/collection.rb b/app/models/collection.rb
index d1d71b2466f..6812163d3bc 100755
--- a/app/models/collection.rb
+++ b/app/models/collection.rb
@@ -2,16 +2,21 @@ class Collection < ApplicationRecord
include Filterable
include WorksOwner
- has_attached_file :icon,
- styles: { standard: "100x100>" },
- url: "/system/:class/:attachment/:id/:style/:basename.:extension",
- path: %w(staging production).include?(Rails.env) ? ":class/:attachment/:id/:style.:extension" : ":rails_root/public:url",
- storage: %w(staging production).include?(Rails.env) ? :s3 : :filesystem,
- s3_protocol: "https",
- default_url: "/images/skins/iconsets/default/icon_collection.png"
+ has_one_attached :icon do |attachable|
+ attachable.variant(:standard, resize_to_fill: [100, nil])
+ end
+
+ validate :check_icon_properties
+ def check_icon_properties
+ return unless icon.attached?
+
+ errors.add(:icon, :invalid_format) unless %r{image/\S+}.match?(icon.content_type)
- validates_attachment_content_type :icon, content_type: /image\/\S+/, allow_nil: true
- validates_attachment_size :icon, less_than: 500.kilobytes, allow_nil: true
+ size_limit_kb = 500
+ errors.add(:icon, :too_large, size_limit_kb: size_limit_kb) unless icon.blob.byte_size < size_limit_kb.kilobytes
+
+ icon.purge if errors[:icon].any?
+ end
belongs_to :parent, class_name: "Collection", inverse_of: :children
has_many :children, class_name: "Collection", foreign_key: "parent_id", inverse_of: :parent
@@ -419,6 +424,6 @@ def delete_icon
alias_method :delete_icon?, :delete_icon
def clear_icon
- self.icon = nil if delete_icon? && !icon.dirty?
+ self.icon.purge if delete_icon?
end
end
diff --git a/app/models/pseud.rb b/app/models/pseud.rb
index ac298bc5fee..102f789b4d6 100644
--- a/app/models/pseud.rb
+++ b/app/models/pseud.rb
@@ -3,24 +3,22 @@ class Pseud < ApplicationRecord
include WorksOwner
include Justifiable
- has_attached_file :icon,
- styles: { standard: "100x100>" },
- path: if Rails.env.production?
- ":attachment/:id/:style.:extension"
- elsif Rails.env.staging?
- ":rails_env/:attachment/:id/:style.:extension"
- else
- ":rails_root/public/system/:rails_env/:class/:attachment/:id_partition/:style/:filename"
- end,
- storage: %w(staging production).include?(Rails.env) ? :s3 : :filesystem,
- s3_protocol: "https",
- default_url: "/images/skins/iconsets/default/icon_user.png"
+ has_one_attached :icon do |attachable|
+ attachable.variant(:standard, resize_to_fill: [100, nil])
+ end
+
+ validate :check_icon_properties
+ def check_icon_properties
+ return unless icon.attached?
- validates_attachment_content_type :icon,
- content_type: %w[image/gif image/jpeg image/png],
- allow_nil: true
+ allowed_formats = %w[image/gif image/jpeg image/png]
+ errors.add(:icon, :invalid_format) unless allowed_formats.include?(icon.content_type)
- validates_attachment_size :icon, less_than: 500.kilobytes, allow_nil: true
+ size_limit_kb = 500
+ errors.add(:icon, :too_large, size_limit_kb: size_limit_kb) unless icon.blob.byte_size < size_limit_kb.kilobytes
+
+ icon.purge if errors[:icon].any?
+ end
NAME_LENGTH_MIN = 1
NAME_LENGTH_MAX = 40
@@ -419,9 +417,10 @@ def delete_icon
def clear_icon
return unless delete_icon?
-
- self.icon = nil unless icon.dirty?
+
+ self.icon.purge
self.icon_alt_text = nil
+ self.icon = nil if delete_icon? && !icon.dirty?
end
#################################
diff --git a/app/models/skin.rb b/app/models/skin.rb
index 610f307f269..5804cf87e55 100755
--- a/app/models/skin.rb
+++ b/app/models/skin.rb
@@ -48,13 +48,21 @@ class Skin < ApplicationRecord
accepts_nested_attributes_for :skin_parents, allow_destroy: true, reject_if: proc { |attrs| attrs[:position].blank? || (attrs[:parent_skin_title].blank? && attrs[:parent_skin_id].blank?) }
- has_attached_file :icon,
- styles: { standard: "100x100>" },
- url: "/system/:class/:attachment/:id/:style/:basename.:extension",
- path: %w(staging production).include?(Rails.env) ? ":class/:attachment/:id/:style.:extension" : ":rails_root/public:url",
- storage: %w(staging production).include?(Rails.env) ? :s3 : :filesystem,
- s3_protocol: "https",
- default_url: "/images/skins/iconsets/default/icon_skins.png"
+ has_one_attached :icon do |attachable|
+ attachable.variant(:standard, resize_to_fill: [100, nil])
+ end
+
+ validate :check_icon_properties
+ def check_icon_properties
+ return unless icon.attached?
+
+ errors.add(:icon, :invalid_format) unless %r{image/\S+}.match?(icon.content_type)
+
+ size_limit_kb = 500
+ errors.add(:icon, :too_large, size_limit_kb: size_limit_kb) unless icon.blob.byte_size < size_limit_kb.kilobytes
+
+ icon.purge if errors[:icon].any?
+ end
after_save :skin_invalidate_cache
def skin_invalidate_cache
@@ -70,8 +78,6 @@ def skin_invalidate_cache
end
end
- validates_attachment_content_type :icon, content_type: /image\/\S+/, allow_nil: true
- validates_attachment_size :icon, less_than: 500.kilobytes, allow_nil: true
validates_length_of :icon_alt_text, allow_blank: true, maximum: ArchiveConfig.ICON_ALT_MAX,
too_long: ts("must be less than %{max} characters long.", max: ArchiveConfig.ICON_ALT_MAX)
@@ -102,7 +108,8 @@ def valid_media
validate :valid_public_preview
def valid_public_preview
- return true if (self.official? || !self.public? || self.icon_file_name)
+ return true if (self.official? || !self.public? || self.icon.attached?)
+
errors.add(:base, ts("You need to upload a screencap if you want to share your skin."))
end
@@ -476,7 +483,7 @@ def self.load_site_css
skin.ie_condition = skin_ie
skin.unusable = true
skin.official = true
- File.open(version_dir + 'preview.png', 'rb') {|preview_file| skin.icon = preview_file}
+ skin.icon.attach(io: File.open("#{version_dir}preview.png", "rb"), content_type: "image/png", filename: "preview.png")
skin.save!
skins << skin
end
@@ -490,7 +497,7 @@ def self.load_site_css
top_skin = Skin.new(title: "Archive #{version}", css: "", description: "Version #{version} of the default Archive style.",
public: true, role: "site", media: ["screen"])
end
- File.open(version_dir + 'preview.png', 'rb') {|preview_file| top_skin.icon = preview_file}
+ top_skin.icon.attach(io: File.open("#{version_dir}preview.png", "rb"), content_type: "image/png", filename: "preview.png")
top_skin.official = true
top_skin.save!
skins.each_with_index do |skin, index|
@@ -579,8 +586,6 @@ def set_thumbnail_from_current_version
self.class.site_skins_dir + "preview.png"
end
- File.open(icon_path) do |icon_file|
- self.icon = icon_file
- end
+ self.icon.attach(io: File.open(icon_path), content_type: "image/png", filename: "preview.png")
end
end
diff --git a/app/views/challenge_claims/_unposted_claim_blurb.html.erb b/app/views/challenge_claims/_unposted_claim_blurb.html.erb
index 2114208bd0e..e7e72c3458f 100755
--- a/app/views/challenge_claims/_unposted_claim_blurb.html.erb
+++ b/app/views/challenge_claims/_unposted_claim_blurb.html.erb
@@ -35,7 +35,7 @@
- <%= image_tag(claim.collection.icon.url(:standard), :size => "100x100", :alt => claim.collection.icon_alt_text, :class => "icon", skip_pipeline: true) %>
+ <%= image_tag(standard_icon_url(claim.collection), size: "100x100", alt: claim.collection.icon_alt_text, class: "icon", skip_pipeline: true) %>
diff --git a/app/views/collection_items/_item_fields.html.erb b/app/views/collection_items/_item_fields.html.erb
index de0f79510d6..06ba9a3f628 100755
--- a/app/views/collection_items/_item_fields.html.erb
+++ b/app/views/collection_items/_item_fields.html.erb
@@ -33,7 +33,7 @@
<%= collection_item.item_date.to_date %>
<% end %>
- <%= image_tag(collection_item.collection.icon.url(:standard), size: "100x100", alt: collection_item.collection.icon_alt_text, class: "icon", skip_pipeline: true) %>
+ <%= image_tag(standard_icon_url(collection_item.collection), size: "100x100", alt: collection_item.collection.icon_alt_text, class: "icon", skip_pipeline: true) %>
diff --git a/app/views/collections/_collection_blurb.html.erb b/app/views/collections/_collection_blurb.html.erb
index 055927b3be7..59720dbd009 100644
--- a/app/views/collections/_collection_blurb.html.erb
+++ b/app/views/collections/_collection_blurb.html.erb
@@ -12,7 +12,7 @@
- <%= image_tag(collection.icon.url(:standard), :size => "100x100", :alt => collection.icon_alt_text, :class => "icon", skip_pipeline: true) %>
+ <%= image_tag(standard_icon_url(collection), size: "100x100", alt: collection.icon_alt_text, class: "icon", skip_pipeline: true) %>
<%= set_format_for_date(collection.updated_at) %>
<% if collection.all_moderators.length > 0%>
diff --git a/app/views/collections/_form.html.erb b/app/views/collections/_form.html.erb
index 35b79c0546e..c6ec6a1d8e7 100644
--- a/app/views/collections/_form.html.erb
+++ b/app/views/collections/_form.html.erb
@@ -66,7 +66,7 @@
<% unless @collection.new_record? %>
-
- <%= image_tag(@collection.icon.url(:standard), size: "100x100", alt: @collection.icon_alt_text, skip_pipeline: true) %>
+ <%= image_tag(standard_icon_url(@collection), size: "100x100", alt: @collection.icon_alt_text, skip_pipeline: true) %>
<%= ts("This is the collection's icon.") %>
<% end %>
@@ -74,7 +74,7 @@
- <%= ts("Icons can be in png, jpeg or gif form") %>
- <%= ts("Icons should be sized 100x100 pixels for best results") %>
- <% if @collection.icon_file_name %>
+ <% if @collection.icon.attached? %>
<%= collection_form.check_box :delete_icon, {:checked => false} %>
<%= collection_form.label :delete_icon, ts("Delete collection icon and revert to our default") %>
<% end %>
diff --git a/app/views/collections/_header.html.erb b/app/views/collections/_header.html.erb
index b6cbad579ab..869035841d2 100644
--- a/app/views/collections/_header.html.erb
+++ b/app/views/collections/_header.html.erb
@@ -2,7 +2,7 @@
diff --git a/app/views/collection_items/_item_fields.html.erb b/app/views/collection_items/_item_fields.html.erb
index 06ba9a3f628..286838217b4 100755
--- a/app/views/collection_items/_item_fields.html.erb
+++ b/app/views/collection_items/_item_fields.html.erb
@@ -33,7 +33,7 @@
<%= collection_item.item_date.to_date %>
<% end %>
- <%= image_tag(standard_icon_url(collection_item.collection), size: "100x100", alt: collection_item.collection.icon_alt_text, class: "icon", skip_pipeline: true) %>
+ <%= collection_icon_display(collection_item.collection) %>
diff --git a/app/views/collections/_collection_blurb.html.erb b/app/views/collections/_collection_blurb.html.erb
index 59720dbd009..5fc67b1df88 100644
--- a/app/views/collections/_collection_blurb.html.erb
+++ b/app/views/collections/_collection_blurb.html.erb
@@ -12,7 +12,7 @@
- <%= image_tag(standard_icon_url(collection), size: "100x100", alt: collection.icon_alt_text, class: "icon", skip_pipeline: true) %>
+ <%= collection_icon_display(collection) %>
<%= set_format_for_date(collection.updated_at) %>
<% if collection.all_moderators.length > 0%>
diff --git a/app/views/collections/_form.html.erb b/app/views/collections/_form.html.erb
index c6ec6a1d8e7..79a50ef1645 100644
--- a/app/views/collections/_form.html.erb
+++ b/app/views/collections/_form.html.erb
@@ -66,7 +66,7 @@
<% unless @collection.new_record? %>
-
- <%= image_tag(standard_icon_url(@collection), size: "100x100", alt: @collection.icon_alt_text, skip_pipeline: true) %>
+ <%= collection_icon_display(@collection) %>
<%= ts("This is the collection's icon.") %>
<% end %>
diff --git a/app/views/collections/_header.html.erb b/app/views/collections/_header.html.erb
index 869035841d2..087ffbdc2c9 100644
--- a/app/views/collections/_header.html.erb
+++ b/app/views/collections/_header.html.erb
@@ -2,7 +2,7 @@