Phase 5: ship the catalog ActiveAdmin resources from the engine

- the 7 catalog admin files (softwares, releases, external_links,
  platform_links, images, files page, downloads) move to the engine's
  app/admin; the host ActiveAdmin instance loads them via
  ActiveAdmin.application.load_paths (single admin, URLs unchanged)
- engine initializers: Zeitwerk ignore for app/admin (production eager load)
  and load_paths + watchable_dirs registration, guarded by defined?(ActiveAdmin)
  so the admin-less dummy app boots
- images admin reads image owners from WarpEngine.config.image_owners; the
  host registers the Member owner in config/initializers/warp_engine.rb;
  the interim ImageUsage registry is gone
- fix: SoftwareImage.distinct.pluck clashed with its order(:position)
  default scope on MySQL (unscope(:order)) — introduced in phase 0, caught
  by the first authenticated /admin/images smoke test
- files page: download links use the public /file/ URL instead of the raw
  container path; the picker's stored path comes from config

Verified: both suites green, zeitwerk:check (production) clean, JSON
baselines intact, authenticated admin smoke test on every page incl.
the 3-level nested software form and Files picker mode.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-04 19:18:28 +02:00
co-authored by Claude Fable 5
parent b79a73d62d
commit d2b4b67e44
11 changed files with 39 additions and 43 deletions
-30
View File
@@ -1,30 +0,0 @@
# A katalóguson kívüli modellek regisztrálják ide, hogy mely Image rekordokat
# használják. A leendő WarpEngine.config.image_owners hook elődje: az admin
# Images oldal In use/Orphan logikája ezen keresztül marad domain-független.
#
# Owner kontraktus:
# image_ids: -> { Array<Integer> } — az owner által használt image id-k
# usage_label: ->(image) { String vagy nil } — megjelenítendő címke, ha használja
module ImageUsage
Owner = Struct.new(:label, :image_ids, :usage_label, keyword_init: true)
def self.owners
@owners ||= []
end
def self.reset!
@owners = []
end
def self.register(label:, image_ids:, usage_label:)
owners << Owner.new(label:, image_ids:, usage_label:)
end
def self.used_image_ids
owners.flat_map { |o| o.image_ids.call }
end
def self.usage_labels_for(image)
owners.filter_map { |o| o.usage_label.call(image) }
end
end
@@ -1,9 +0,0 @@
# TTG-specifikus image-használók regisztrációja. to_prepare: reload után is újrafut.
Rails.application.config.to_prepare do
ImageUsage.reset!
ImageUsage.register(
label: "member",
image_ids: -> { Member.where.not(image_id: nil).distinct.pluck(:image_id) },
usage_label: ->(image) { "member" if Member.where(image_id: image.id).exists? }
)
end
@@ -0,0 +1,13 @@
# WarpEngine host-konfiguráció. to_prepare: reload után is újrafut, ezért
# értékadás (nem <<), hogy idempotens legyen.
Rails.application.config.to_prepare do
WarpEngine.configure do |c|
c.image_owners = [
{
label: "member",
image_ids: -> { Member.where.not(image_id: nil).distinct.pluck(:image_id) },
usage_label: ->(image) { "member" if Member.where(image_id: image.id).exists? }
}
]
end
end
@@ -147,7 +147,7 @@ ActiveAdmin.register_page "Files" do
# Download (files only)
if entry[:type] != :directory
a "⬇️", href: "/softwares/#{entry[:path]}", class: "fm-icon-btn", title: "Download", download: entry[:name]
a "⬇️", href: "/file/#{entry[:path]}", class: "fm-icon-btn", title: "Download", download: entry[:name]
end
# Rename
@@ -165,7 +165,7 @@ ActiveAdmin.register_page "Files" do
# Picker mode: Select button
if picker_mode
abs_path = "/softwares/#{entry[:path]}"
abs_path = File.join(WarpEngine.config.file_container_path, entry[:path])
a "Select", href: "#", class: "fm-btn fm-btn-select",
onclick: "var inp=window.parent.document.getElementById('#{j picker_field}');if(inp){inp.value='#{j abs_path}';}var ov=window.parent.document.querySelector('.fm-modal-overlay');if(ov){ov.remove();window.parent.document.body.style.overflow='';}return false;"
end
@@ -3,7 +3,12 @@ ActiveAdmin.register WarpEngine::Image, as: "Image" do
menu priority: 5, label: "🖼️ Images"
used_ids = -> { WarpEngine::SoftwareImage.distinct.pluck(:image_id) + ImageUsage.used_image_ids }
# SoftwareImage a natív használó; a hoston regisztrált image_owners
# (WarpEngine.config) további használókat adhat hozzá (pl. TTG Member).
used_ids = -> {
WarpEngine::SoftwareImage.unscope(:order).distinct.pluck(:image_id) +
WarpEngine.config.image_owners.flat_map { |o| o[:image_ids].call }
}
scope :all, default: true
scope("In use") { |scope| scope.where(id: used_ids.call) }
@@ -31,7 +36,7 @@ ActiveAdmin.register WarpEngine::Image, as: "Image" do
column(:usage) do |img|
uses = []
uses << "#{img.software_images.size} software(s)" if img.software_images.any?
uses.concat(ImageUsage.usage_labels_for(img))
uses.concat(WarpEngine.config.image_owners.filter_map { |o| o[:usage_label].call(img) })
uses.any? ? uses.join(", ") : status_tag("orphan", class: "warning")
end
column :created_at
@@ -1,5 +1,22 @@
module WarpEngine
class Engine < ::Rails::Engine
isolate_namespace WarpEngine
# Az app/admin fájlok ActiveAdmin DSL-t tartalmaznak, nem definiálnak a
# fájlnévnek megfelelő konstansokat — a Zeitwerk (eager load) nem nyúlhat hozzájuk.
initializer "warp_engine.ignore_admin_dir", before: :setup_main_autoloader do
Rails.autoloaders.main.ignore(Engine.root.join("app/admin"))
end
# Az admin erőforrásokat a HOST ActiveAdmin példánya tölti be; az engine csak
# regisztrálja a saját app/admin könyvtárát. Auth/téma/route-ok a host dolga.
initializer "warp_engine.active_admin" do |app|
if defined?(ActiveAdmin)
admin_dir = Engine.root.join("app/admin").to_s
ActiveAdmin.application.load_paths << admin_dir
# dev módban az engine admin-fájljainak szerkesztése is triggerel reloadot
app.config.watchable_dirs[admin_dir] = [ :rb ]
end
end
end
end