advanced image management

This commit is contained in:
2026-07-27 08:19:48 +02:00
parent f776919769
commit b193ac6caf
14 changed files with 268 additions and 24 deletions
+29 -9
View File
@@ -1,6 +1,7 @@
ActiveAdmin.register Software do
permit_params :name, :title, :author, :desc, :story,
:license, :platform, :status, :highlighted, :image_id,
:license, :platform, :status, :highlighted,
software_images_attributes: [ :id, :image_id, :is_default, :position, :_destroy ],
external_links_attributes: [ :id, :label, :url, :_destroy ],
releases_attributes: [ :id, :version, :html_folder_path, :cartridge_path, :source_path, :docs_folder_path, :web_playable, :_destroy ]
@@ -19,8 +20,9 @@ ActiveAdmin.register Software do
end
column :highlighted
column(:image) do |sw|
if sw.image && File.exist?(sw.image.file_path)
image_tag "/api/image/#{sw.image.id}", style: "max-height:40px;max-width:80px;object-fit:contain;"
si = sw.software_images.detect(&:is_default?) || sw.software_images.first
if si&.image && File.exist?(si.image.file_path)
image_tag "/api/image/#{si.image_id}", style: "max-height:40px;max-width:80px;object-fit:contain;"
end
end
column :created_at
@@ -44,13 +46,19 @@ ActiveAdmin.register Software do
f.input :status, as: :select, collection: %w[development demo released archived]
f.input :highlighted
f.input :license
f.input :image_id, as: :select, label: "Image",
collection: Image.order(:original_filename).map { |img| [ img.original_filename, img.id ] },
include_blank: "— no image —"
f.input :desc, as: :text, input_html: { rows: 4 }
f.input :story, as: :text, input_html: { rows: 8 }
end
f.inputs "Images" do
f.has_many :software_images, allow_destroy: true, new_record: true do |si|
si.input :image_id, as: :select, label: "Image",
collection: Image.order(:original_filename).map { |img| [img.original_filename, img.id] }
si.input :is_default, as: :boolean
si.input :position, as: :number, input_html: { min: 0 }
end
end
f.inputs "External Links" do
f.has_many :external_links, allow_destroy: true, new_record: true do |el|
el.input :label
@@ -73,6 +81,12 @@ ActiveAdmin.register Software do
end
end
controller do
def scoped_collection
super.includes(:software_images)
end
end
form do |f|
f.inputs "Details" do
f.input :name
@@ -82,13 +96,19 @@ ActiveAdmin.register Software do
f.input :status, as: :select, collection: %w[development demo released archived]
f.input :highlighted
f.input :license
f.input :image_id, as: :select, label: "Image",
collection: Image.order(:original_filename).map { |img| [ img.original_filename, img.id ] },
include_blank: "— no image —"
f.input :desc, as: :text, input_html: { rows: 4 }
f.input :story, as: :text, input_html: { rows: 8 }
end
f.inputs "Images" do
f.has_many :software_images, allow_destroy: true, new_record: true do |si|
si.input :image_id, as: :select, label: "Image",
collection: Image.order(:original_filename).map { |img| [img.original_filename, img.id] }
si.input :is_default, as: :boolean
si.input :position, as: :number, input_html: { min: 0 }
end
end
f.inputs "External Links" do
f.has_many :external_links, allow_destroy: true, new_record: true do |el|
el.input :label
+2
View File
@@ -1,6 +1,8 @@
class Image < ApplicationRecord
UPLOAD_PATH = ENV.fetch("IMAGE_CONTAINER_PATH", "/images")
has_many :software_images, dependent: :restrict_with_error
attr_accessor :file_upload
before_save :process_upload, if: -> { file_upload.present? }
+5 -4
View File
@@ -1,21 +1,22 @@
class Software < ApplicationRecord
self.table_name = "softwares"
belongs_to :image, optional: true
has_many :software_images, foreign_key: :software_id, dependent: :destroy
has_many :images, through: :software_images
has_many :releases, foreign_key: :software_id
has_many :external_links, foreign_key: :software_id
accepts_nested_attributes_for :software_images, allow_destroy: true
accepts_nested_attributes_for :external_links, allow_destroy: true
accepts_nested_attributes_for :releases, allow_destroy: true
default_scope { where(deleted_at: nil) }
def self.ransackable_attributes(auth_object = nil)
%w[author created_at desc highlighted id image_id license name platform site status story title updated_at]
%w[author created_at desc highlighted id license name platform site status story title updated_at]
end
def self.ransackable_associations(auth_object = nil)
%w[releases external_links image]
%w[releases external_links software_images images]
end
end
+27
View File
@@ -0,0 +1,27 @@
class SoftwareImage < ApplicationRecord
belongs_to :software
belongs_to :image
validates :image_id, uniqueness: { scope: :software_id }
validates :position, numericality: { only_integer: true, greater_than_or_equal_to: 0 }
before_save :unset_other_defaults, if: -> { is_default? && is_default_changed? }
default_scope { order(:position) }
def self.ransackable_attributes(auth_object = nil)
%w[created_at id image_id is_default position software_id updated_at]
end
def self.ransackable_associations(auth_object = nil)
%w[image software]
end
private
def unset_other_defaults
SoftwareImage.where(software_id: software_id, is_default: true)
.where.not(id: id)
.update_all(is_default: false)
end
end
@@ -16,5 +16,13 @@ class SoftwareSerializer < Blueprinter::Base
field :status
field(:highlighted) { |sw| sw.highlighted ? true : false }
field(:externalLinks) { |sw| ExternalLinkSerializer.render_as_hash(sw.external_links) }
field(:imageUrl) { |sw| sw.image_id ? "/api/image/#{sw.image_id}" : nil }
field(:imageUrl) { |sw|
si = sw.software_images.detect(&:is_default?) || sw.software_images.first
si ? "/api/image/#{si.image_id}" : nil
}
field(:images) { |sw|
sw.software_images.map { |si|
{ url: "/api/image/#{si.image_id}", isDefault: si.is_default?, position: si.position }
}
}
end
@@ -2,7 +2,7 @@ class SoftwareHighlightedService
include SoftwareResponseBuilder
def index
software = Software.includes(:external_links, :image)
software = Software.includes(:external_links, :software_images)
.where(highlighted: true)
.order(id: :desc)
.first
+1 -1
View File
@@ -2,7 +2,7 @@ class SoftwareService
include SoftwareResponseBuilder
def index
softwares = Software.includes(:releases, :external_links, :image).all
softwares = Software.includes(:releases, :external_links, :software_images).all
{ softwares: softwares.map { |sw| build_response(sw, sw.releases.to_a) } }
end
end