Phase 2: move the 8 catalog models into the WarpEngine engine
- Software, Release, ReleaseAsset, ExternalLink, PlatformLink, Image, SoftwareImage, Download now live in the engine under WarpEngine::, on top of WarpEngine::ApplicationRecord; table names unchanged (empty prefix) - each model runs an ActiveSupport load hook (:warp_engine_<model>) as a host extension point - WarpEngine::Image reads its upload path from WarpEngine.config - host references fully qualified (services, serializers, admin, specs); admin registrations renamed with as: so /admin URLs and route helpers are byte-identical; factories pinned to the namespaced classes - Member#image now class_name: "WarpEngine::Image" Verified: full suite green, /api/software and /api/builds byte-identical to the phase-0 baselines, admin routes unchanged. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
module WarpEngine
|
||||
class ApplicationRecord < ActiveRecord::Base
|
||||
self.abstract_class = true
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,19 @@
|
||||
module WarpEngine
|
||||
class Download < ApplicationRecord
|
||||
belongs_to :release, optional: true
|
||||
|
||||
validates :file_path, presence: true
|
||||
|
||||
default_scope { where(deleted_at: nil) }
|
||||
|
||||
def self.ransackable_attributes(auth_object = nil)
|
||||
%w[created_at deleted_at file_path id ip_address referer release_id updated_at user_agent]
|
||||
end
|
||||
|
||||
def self.ransackable_associations(auth_object = nil)
|
||||
%w[release]
|
||||
end
|
||||
|
||||
ActiveSupport.run_load_hooks(:warp_engine_download, self)
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,22 @@
|
||||
module WarpEngine
|
||||
class ExternalLink < ApplicationRecord
|
||||
self.table_name = "external_links"
|
||||
|
||||
belongs_to :software
|
||||
|
||||
validates :label, presence: true
|
||||
validates :url, presence: true
|
||||
|
||||
default_scope { where(deleted_at: nil) }
|
||||
|
||||
def self.ransackable_attributes(auth_object = nil)
|
||||
%w[created_at deleted_at id label software_id updated_at url]
|
||||
end
|
||||
|
||||
def self.ransackable_associations(auth_object = nil)
|
||||
%w[software]
|
||||
end
|
||||
|
||||
ActiveSupport.run_load_hooks(:warp_engine_external_link, self)
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,36 @@
|
||||
module WarpEngine
|
||||
class Image < ApplicationRecord
|
||||
def self.upload_path
|
||||
WarpEngine.config.image_container_path
|
||||
end
|
||||
|
||||
has_many :software_images, dependent: :restrict_with_error
|
||||
|
||||
default_scope { where(deleted_at: nil) }
|
||||
|
||||
attr_accessor :file_upload
|
||||
|
||||
before_save :process_upload, if: -> { file_upload.present? }
|
||||
|
||||
def self.ransackable_attributes(auth_object = nil)
|
||||
%w[content_type created_at deleted_at filename id original_filename updated_at]
|
||||
end
|
||||
|
||||
def file_path
|
||||
File.join(self.class.upload_path, filename.to_s)
|
||||
end
|
||||
|
||||
ActiveSupport.run_load_hooks(:warp_engine_image, self)
|
||||
|
||||
private
|
||||
|
||||
def process_upload
|
||||
FileUtils.mkdir_p(self.class.upload_path)
|
||||
self.original_filename = file_upload.original_filename
|
||||
self.content_type = file_upload.content_type.presence || "application/octet-stream"
|
||||
ext = File.extname(file_upload.original_filename)
|
||||
self.filename = "#{SecureRandom.uuid}#{ext}"
|
||||
IO.copy_stream(file_upload.to_io, file_path)
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,27 @@
|
||||
module WarpEngine
|
||||
class PlatformLink < ApplicationRecord
|
||||
self.table_name = "platform_links"
|
||||
|
||||
SUPPORTED_PLATFORMS = %w[tic80 ebitengine love c64 godot bevy phaser].freeze
|
||||
|
||||
validates :name, presence: true
|
||||
validates :url, presence: true
|
||||
validates :platform, presence: true, inclusion: { in: SUPPORTED_PLATFORMS }
|
||||
|
||||
default_scope { where(deleted_at: nil).order(:position) }
|
||||
|
||||
def self.ransackable_attributes(auth_object = nil)
|
||||
%w[created_at deleted_at id name platform position updated_at url]
|
||||
end
|
||||
|
||||
def self.ransackable_associations(auth_object = nil)
|
||||
[]
|
||||
end
|
||||
|
||||
def self.for_platform(platform)
|
||||
where(platform: platform).to_a
|
||||
end
|
||||
|
||||
ActiveSupport.run_load_hooks(:warp_engine_platform_link, self)
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,37 @@
|
||||
module WarpEngine
|
||||
class Release < ApplicationRecord
|
||||
self.table_name = "releases"
|
||||
|
||||
belongs_to :software
|
||||
has_many :downloads
|
||||
has_many :release_assets
|
||||
|
||||
accepts_nested_attributes_for :release_assets, allow_destroy: true
|
||||
|
||||
validates :version, presence: true
|
||||
validates :version, uniqueness: { scope: :software_id }
|
||||
|
||||
default_scope { where(deleted_at: nil) }
|
||||
|
||||
validate :c64_cannot_be_web_playable
|
||||
|
||||
def c64_cannot_be_web_playable
|
||||
return unless software&.platform == "c64"
|
||||
|
||||
has_html = release_assets.reject(&:marked_for_destruction?).any? { |a| a.kind == "html" }
|
||||
if has_html
|
||||
errors.add(:base, "C64 releases cannot be web playable (download only)")
|
||||
end
|
||||
end
|
||||
|
||||
def self.ransackable_attributes(auth_object = nil)
|
||||
%w[created_at deleted_at id software_id updated_at version]
|
||||
end
|
||||
|
||||
def self.ransackable_associations(auth_object = nil)
|
||||
%w[downloads release_assets software]
|
||||
end
|
||||
|
||||
ActiveSupport.run_load_hooks(:warp_engine_release, self)
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,26 @@
|
||||
module WarpEngine
|
||||
class ReleaseAsset < ApplicationRecord
|
||||
KINDS = %w[cartridge source html docs
|
||||
win_x86 win_x64 linux_x86 linux_x64
|
||||
mac_x64 mac_arm64 mac_universal].freeze
|
||||
|
||||
belongs_to :release
|
||||
|
||||
enum :kind, KINDS.index_by(&:itself)
|
||||
|
||||
validates :path, presence: true
|
||||
validates :kind, uniqueness: { scope: :release_id }
|
||||
|
||||
default_scope { where(deleted_at: nil) }
|
||||
|
||||
def self.ransackable_attributes(auth_object = nil)
|
||||
%w[created_at deleted_at id kind path release_id updated_at]
|
||||
end
|
||||
|
||||
def self.ransackable_associations(auth_object = nil)
|
||||
%w[release]
|
||||
end
|
||||
|
||||
ActiveSupport.run_load_hooks(:warp_engine_release_asset, self)
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,31 @@
|
||||
module WarpEngine
|
||||
class Software < ApplicationRecord
|
||||
self.table_name = "softwares"
|
||||
|
||||
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 :downloads, through: :releases
|
||||
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
|
||||
|
||||
validates :name, presence: true, uniqueness: true
|
||||
validates :title, presence: true
|
||||
validates :platform, presence: true
|
||||
|
||||
default_scope { where(deleted_at: nil) }
|
||||
|
||||
def self.ransackable_attributes(auth_object = nil)
|
||||
%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 software_images images]
|
||||
end
|
||||
|
||||
ActiveSupport.run_load_hooks(:warp_engine_software, self)
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,45 @@
|
||||
module WarpEngine
|
||||
class SoftwareImage < ApplicationRecord
|
||||
belongs_to :software
|
||||
belongs_to :image, optional: true
|
||||
|
||||
attr_accessor :file_upload
|
||||
|
||||
before_validation :create_image_from_upload, if: -> { file_upload.present? }
|
||||
|
||||
validates :image_id, presence: true
|
||||
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
|
||||
|
||||
ActiveSupport.run_load_hooks(:warp_engine_software_image, self)
|
||||
|
||||
private
|
||||
|
||||
def create_image_from_upload
|
||||
img = Image.new(file_upload: file_upload)
|
||||
if img.save
|
||||
self.image = img
|
||||
else
|
||||
errors.add(:file_upload, img.errors.full_messages.join(", "))
|
||||
end
|
||||
end
|
||||
|
||||
def unset_other_defaults
|
||||
SoftwareImage.where(software_id: software_id, is_default: true)
|
||||
.where.not(id: id)
|
||||
.update_all(is_default: false)
|
||||
end
|
||||
end
|
||||
end
|
||||
Reference in New Issue
Block a user