diff --git a/alchemy_cms.gemspec b/alchemy_cms.gemspec index 4f20fd46c4..4bd2aec3e1 100644 --- a/alchemy_cms.gemspec +++ b/alchemy_cms.gemspec @@ -43,8 +43,6 @@ Gem::Specification.new do |gem| gem.add_runtime_dependency "cancancan", [">= 2.1", "< 4.0"] gem.add_runtime_dependency "coffee-rails", [">= 4.0", "< 6.0"] gem.add_runtime_dependency "csv", ["~> 3.3"] - gem.add_runtime_dependency "dragonfly", ["~> 1.4"] - gem.add_runtime_dependency "dragonfly_svg", ["~> 0.0.4"] gem.add_runtime_dependency "gutentag", ["~> 2.2", ">= 2.2.1"] gem.add_runtime_dependency "image_processing", [">= 1.2"] gem.add_runtime_dependency "importmap-rails", ["~> 1.2", ">= 1.2.1"] diff --git a/app/models/alchemy/attachment.rb b/app/models/alchemy/attachment.rb index cd0d08b496..5623cbf32c 100644 --- a/app/models/alchemy/attachment.rb +++ b/app/models/alchemy/attachment.rb @@ -24,19 +24,6 @@ class Attachment < BaseRecord include Alchemy::Taggable include Alchemy::TouchElements - # Legacy Dragonfly file attachments - extend Dragonfly::Model - dragonfly_accessor :legacy_file, app: :alchemy_attachments - DEPRECATED_COLUMNS = %i[ - legacy_file - legacy_file_name - legacy_file_size - legacy_file_uid - ].each do |column| - deprecate column, deprecator: Alchemy::Deprecation - deprecate :"#{column}=", deprecator: Alchemy::Deprecation - end - # Use ActiveStorage file attachments has_one_attached :file, service: :alchemy_cms diff --git a/app/models/alchemy/picture.rb b/app/models/alchemy/picture.rb index 52da0ee5e9..269101ea02 100644 --- a/app/models/alchemy/picture.rb +++ b/app/models/alchemy/picture.rb @@ -80,22 +80,6 @@ def self.preprocessor_class=(klass) @_preprocessor_class = klass end - # Legacy Dragonfly image attachments - extend Dragonfly::Model - dragonfly_accessor :legacy_image_file, app: :alchemy_pictures - DEPRECATED_COLUMNS = %i[ - legacy_image_file - legacy_image_file_format - legacy_image_file_height - legacy_image_file_name - legacy_image_file_size - legacy_image_file_uid - legacy_image_file_width - ].each do |column| - deprecate column, deprecator: Alchemy::Deprecation - deprecate :"#{column}=", deprecator: Alchemy::Deprecation - end - # Use ActiveStorage image processing has_one_attached :image_file, service: :alchemy_cms do |attachable| # Only works in Rails 7.1 diff --git a/lib/alchemy/upgrader/eight_zero.rb b/lib/alchemy/upgrader/eight_zero.rb index fe1dbfc5cd..db6a3124a3 100644 --- a/lib/alchemy/upgrader/eight_zero.rb +++ b/lib/alchemy/upgrader/eight_zero.rb @@ -1,22 +1,34 @@ require "alchemy/shell" +require "alchemy/upgrader/tasks/active_storage_migration" require "benchmark" -require "active_storage/service/disk_service" +require "fileutils" +require "thor" module Alchemy class Upgrader::EightZero < Upgrader - extend Alchemy::Shell - DEFAULT_CONTENT_TYPE = "application/octet-stream" - DISK_SERVICE = ActiveStorage::Service::DiskService - SERVICE_NAME = :alchemy_cms - - # Prevents (down)loading the original file - METADATA = { - identified: true, # Skip identifying file type - analyzed: true, # Skip analyze job - composed: true # Skip checksum check - } + include Thor::Base + include Thor::Actions class << self + def install_active_storage + Rake::Task["active_storage:install"].invoke + Rake::Task["db:migrate"].invoke + + text = <<-YAML.strip_heredoc + + alchemy_cms: + service: Disk + root: <%= Rails.root.join("storage") %> + YAML + + storage_yml = Rails.application.root.join("config/storage.yml") + if File.exist?(storage_yml) + task.insert_into_file(storage_yml, text) + else + task.create_file(storage_yml, text) + end + end + def migrate_pictures_to_active_storage pictures_without_as_attachment = Alchemy::Picture.where.missing(:image_file_attachment) count = pictures_without_as_attachment.count @@ -24,30 +36,7 @@ def migrate_pictures_to_active_storage log "Migrating #{count} Dragonfly image file(s) to ActiveStorage." realtime = Benchmark.realtime do pictures_without_as_attachment.find_each do |picture| - Alchemy::Deprecation.silence do - uid = picture.legacy_image_file_uid - key = key_for_uid(uid) - content_type = Mime::Type.lookup_by_extension(picture.legacy_image_file_format) || DEFAULT_CONTENT_TYPE - Alchemy::Picture.transaction do - blob = ActiveStorage::Blob.create!( - key: key, - filename: picture.legacy_image_file_name, - byte_size: picture.legacy_image_file_size, - content_type: content_type, - metadata: METADATA.merge( - width: picture.legacy_image_file_width, - height: picture.legacy_image_file_height - ), - service_name: SERVICE_NAME - ) - picture.create_image_file_attachment!( - name: :image_file, - record: picture, - blob: blob - ) - end - move_file(Rails.root.join("uploads/pictures", uid), key) - end + Alchemy::Upgrader::Tasks::ActiveStorageMigration.migrate_picture(picture) print "." end end @@ -64,26 +53,7 @@ def migrate_attachments_to_active_storage log "Migrating #{count} Dragonfly attachment file(s) to ActiveStorage." realtime = Benchmark.realtime do attachments_without_as_attachment.find_each do |attachment| - Alchemy::Deprecation.silence do - uid = attachment.legacy_file_uid - key = key_for_uid(uid) - Alchemy::Attachment.transaction do - blob = ActiveStorage::Blob.create!( - key: key, - filename: attachment.legacy_file_name, - byte_size: attachment.legacy_file_size, - content_type: attachment.file_mime_type.presence || DEFAULT_CONTENT_TYPE, - metadata: METADATA, - service_name: SERVICE_NAME - ) - attachment.create_file_attachment!( - record: attachment, - name: :file, - blob: blob - ) - end - move_file(Rails.root.join("uploads/attachments", uid), key) - end + Alchemy::Upgrader::Tasks::ActiveStorageMigration.migrate_attachment(attachment) print "." end end @@ -95,31 +65,8 @@ def migrate_attachments_to_active_storage private - # ActiveStorage::Service::DiskService stores files in a folder structure - # based on the first two characters of the file uid. - def key_for_uid(uid) - case service - when DISK_SERVICE - uid.split("/").last - else - uid - end - end - - # ActiveStorage::Service::DiskService stores files in a folder structure - # based on the first two characters of the file uid. - def move_file(uid, key) - case service - when DISK_SERVICE - if File.exist?(uid) - service.send(:make_path_for, key) - FileUtils.mv uid, service.send(:path_for, key) - end - end - end - - def service - ActiveStorage::Blob.services.fetch(SERVICE_NAME) + def task + @_task || new end end end diff --git a/lib/alchemy/upgrader/tasks/.keep b/lib/alchemy/upgrader/tasks/.keep deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/lib/alchemy/upgrader/tasks/active_storage_migration.rb b/lib/alchemy/upgrader/tasks/active_storage_migration.rb new file mode 100644 index 0000000000..e7bd68f0c0 --- /dev/null +++ b/lib/alchemy/upgrader/tasks/active_storage_migration.rb @@ -0,0 +1,108 @@ +require "active_storage/service" +require "active_storage/service/disk_service" + +module Alchemy + class Upgrader + module Tasks + class ActiveStorageMigration + DEFAULT_CONTENT_TYPE = "application/octet-stream" + DISK_SERVICE = ActiveStorage::Service::DiskService + SERVICE_NAME = :alchemy_cms + + METADATA = { + identified: true, # Skip identifying file type + analyzed: true, # Skip analyze job + composed: true # Skip checksum check + } + + class << self + def migrate_picture(picture) + Alchemy::Picture.extend Dragonfly::Model + Alchemy::Picture.dragonfly_accessor :legacy_image_file, app: :alchemy_pictures + + Alchemy::Deprecation.silence do + uid = picture.legacy_image_file_uid + key = key_for_uid(uid) + content_type = Mime::Type.lookup_by_extension(picture.legacy_image_file_format) || DEFAULT_CONTENT_TYPE + Alchemy::Picture.transaction do + blob = ActiveStorage::Blob.create!( + key: key, + filename: picture.legacy_image_file_name, + byte_size: picture.legacy_image_file_size, + content_type: content_type, + # Prevents (down)loading the original file + metadata: METADATA.merge( + width: picture.legacy_image_file_width, + height: picture.legacy_image_file_height + ), + service_name: SERVICE_NAME + ) + picture.create_image_file_attachment!( + name: :image_file, + record: picture, + blob: blob + ) + end + move_file(Rails.root.join("uploads/pictures", uid), key) + end + end + + def migrate_attachment(attachment) + Alchemy::Attachment.extend Dragonfly::Model + Alchemy::Attachment.dragonfly_accessor :legacy_file, app: :alchemy_attachments + + Alchemy::Deprecation.silence do + uid = attachment.legacy_file_uid + key = key_for_uid(uid) + Alchemy::Attachment.transaction do + blob = ActiveStorage::Blob.create!( + key: key, + filename: attachment.legacy_file_name, + byte_size: attachment.legacy_file_size, + content_type: attachment.file_mime_type.presence || DEFAULT_CONTENT_TYPE, + metadata: METADATA, + service_name: SERVICE_NAME + ) + attachment.create_file_attachment!( + record: attachment, + name: :file, + blob: blob + ) + end + move_file(Rails.root.join("uploads/attachments", uid), key) + end + end + + private + + # ActiveStorage::Service::DiskService stores files in a folder structure + # based on the first two characters of the file uid. + def key_for_uid(uid) + case service + when DISK_SERVICE + uid.split("/").last + else + uid + end + end + + # ActiveStorage::Service::DiskService stores files in a folder structure + # based on the first two characters of the file uid. + def move_file(uid, key) + case service + when DISK_SERVICE + if File.exist?(uid) + service.send(:make_path_for, key) + FileUtils.mv uid, service.send(:path_for, key) + end + end + end + + def service + ActiveStorage::Blob.services.fetch(SERVICE_NAME) + end + end + end + end + end +end diff --git a/lib/alchemy_cms.rb b/lib/alchemy_cms.rb index e793efdb3d..a294992831 100644 --- a/lib/alchemy_cms.rb +++ b/lib/alchemy_cms.rb @@ -7,10 +7,9 @@ require "acts_as_list" require "action_view/dependency_tracker" require "active_model_serializers" +require "active_storage/engine" require "awesome_nested_set" require "cancan" -require "dragonfly" -require "dragonfly_svg" require "gutentag" require "importmap-rails" require "kaminari" diff --git a/lib/generators/alchemy/install/install_generator.rb b/lib/generators/alchemy/install/install_generator.rb index 594f53f42f..fc47a0e902 100644 --- a/lib/generators/alchemy/install/install_generator.rb +++ b/lib/generators/alchemy/install/install_generator.rb @@ -69,6 +69,10 @@ def install_assets append_to_file Rails.root.join("app/assets/config/manifest.js"), "//= link alchemy/admin/custom.css\n" end + def install_active_storage + rake "active_storage:install:migrations" + end + def set_active_storage_service insert_into_file app_config_path.join("storage.yml"), <<-YAML.strip_heredoc diff --git a/lib/tasks/alchemy/upgrade.rake b/lib/tasks/alchemy/upgrade.rake index fe9af41e28..ab13684f64 100644 --- a/lib/tasks/alchemy/upgrade.rake +++ b/lib/tasks/alchemy/upgrade.rake @@ -15,10 +15,33 @@ namespace :alchemy do namespace :upgrade do desc "Alchemy Upgrader: Prepares the database and updates Alchemys configuration file." task prepare: [ + "alchemy:upgrade:ensure_dragonfly_gems", "alchemy:upgrade:database", "alchemy:upgrade:config" ] + task :ensure_dragonfly_gems do + require "dragonfly" + require "dragonfly_svg" + rescue LoadError + abort <<~WARN + + == Alchemy Upgrader == + + Please make sure you have `dragonfly` and `dragonfly_svg` gems installed in order to migrate Alchemy. + + Add these to your Gemfile: + + gem "dragonfly", "~> 1.4", require: false + gem "dragonfly_svg", "~> 0.0.4", require: false + + and run `bundle install`. + + Then try again, please! + + WARN + end + desc "Alchemy Upgrader: Prepares the database." task database: [ "alchemy:install:migrations", @@ -32,10 +55,16 @@ namespace :alchemy do namespace "8.0" do task "run" => [ + "alchemy:upgrade:8.0:install_active_storage", "alchemy:upgrade:8.0:migrate_pictures_to_active_storage", "alchemy:upgrade:8.0:migrate_attachments_to_active_storage" ] + desc "Install active_storage" + task install_active_storage: [:environment] do + Alchemy::Upgrader::EightZero.install_active_storage + end + desc "Migrate pictures to active_storage" task migrate_pictures_to_active_storage: [:environment] do Alchemy::Upgrader::EightZero.migrate_pictures_to_active_storage