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