diff --git a/libs/ruby/warp_engine/README.md b/libs/ruby/warp_engine/README.md index 948ec27..5aa4cba 100644 --- a/libs/ruby/warp_engine/README.md +++ b/libs/ruby/warp_engine/README.md @@ -12,6 +12,11 @@ Repository: `https://git.teletypegames.org/engines/warp_engine` - **Catalog domain**: `Software`, `Release`, `ReleaseAsset`, `ExternalLink`, `PlatformLink`, `SoftwareImage`, `Download` models with soft-delete semantics and download statistics. +- **Platform registry**: `WarpEngine::Platform` is the one place a platform is + named. `Platform.names` lists them, `Platform.find!("godot")` answers with a + value object that knows its `label`, its `expected_kinds` and its updater + `service`. Reference it instead of writing a platform list of your own — + `PlatformLink::SUPPORTED_PLATFORMS` is kept as an alias of `Platform::NAMES`. - **CI-callable updater**: your build pipeline uploads artifacts over HTTP and calls one endpoint — WarpEngine extracts archives, parses metadata and upserts the catalog records. Supported platforms out of the @@ -203,6 +208,10 @@ Rails.application.config.to_prepare do # Where CI drops build artifacts c.file_container_path = ENV.fetch("FILE_CONTAINER_PATH", "/softwares") + # Where the site this catalog belongs to lives. The admin links a software + # to its public page from here; left nil, the link is left out. + c.site_url = ENV["SITE_URL"] + # Your image model (see "Images" below). "Image" is the default. # c.image_class_name = "Media::Picture" # c.image_adapter = MyImageLibrary.new diff --git a/libs/ruby/warp_engine/app/admin/platform_links.rb b/libs/ruby/warp_engine/app/admin/platform_links.rb index e404d13..b0aa8c2 100644 --- a/libs/ruby/warp_engine/app/admin/platform_links.rb +++ b/libs/ruby/warp_engine/app/admin/platform_links.rb @@ -25,12 +25,12 @@ ActiveAdmin.register WarpEngine::PlatformLink, as: "Platform Link" do actions end - filter :platform, as: :select, collection: %w[tic80 ebitengine love c64 godot bevy phaser] + filter :platform, as: :select, collection: WarpEngine::Platform.names filter :name form do |f| f.inputs do - f.input :platform, as: :select, collection: %w[tic80 ebitengine love c64 godot bevy phaser] + f.input :platform, as: :select, collection: WarpEngine::Platform.names f.input :name f.input :url f.input :position, as: :number, input_html: { min: 0 } diff --git a/libs/ruby/warp_engine/app/admin/softwares.rb b/libs/ruby/warp_engine/app/admin/softwares.rb index 21ff3b6..bd23243 100644 --- a/libs/ruby/warp_engine/app/admin/softwares.rb +++ b/libs/ruby/warp_engine/app/admin/softwares.rb @@ -38,18 +38,19 @@ ActiveAdmin.register WarpEngine::Software, as: "Software" do filter :name filter :title filter :author - filter :platform, as: :select, collection: %w[tic80 ebitengine love c64 godot bevy phaser] - filter :status, as: :select, collection: %w[development demo released archived] + filter :platform, as: :select, collection: WarpEngine::Platform.names + filter :status, as: :select, collection: WarpEngine::Software::STATUSES filter :highlighted sidebar "Quick Links", only: :show, priority: 0 do - site_url = ENV.fetch("SITE_URL", "https://teletypegames.org") - catalog_url = "#{site_url}/catalog/#{resource.name}" + if WarpEngine.config.site_url.present? + catalog_url = "#{WarpEngine.config.site_url.chomp('/')}/catalog/#{resource.name}" - div style: "margin-bottom:8px;" do - a href: catalog_url, target: "_blank", style: "display:inline-flex;align-items:center;gap:6px;font-weight:bold;color:#5850ec;" do - span "🌐", style: "font-size:16px;" - text_node "View on site" + div style: "margin-bottom:8px;" do + a href: catalog_url, target: "_blank", style: "display:inline-flex;align-items:center;gap:6px;font-weight:bold;color:#5850ec;" do + span "🌐", style: "font-size:16px;" + text_node "View on site" + end end end @@ -103,8 +104,8 @@ ActiveAdmin.register WarpEngine::Software, as: "Software" do f.input :name f.input :title f.input :author - f.input :platform, as: :select, collection: %w[tic80 ebitengine love c64 godot bevy phaser] - f.input :status, as: :select, collection: %w[development demo released archived] + f.input :platform, as: :select, collection: WarpEngine::Platform.names + f.input :status, as: :select, collection: WarpEngine::Software::STATUSES f.input :highlighted f.input :license f.input :desc, as: :text, input_html: { rows: 4 } diff --git a/libs/ruby/warp_engine/app/controllers/warp_engine/api/software_controller.rb b/libs/ruby/warp_engine/app/controllers/warp_engine/api/software_controller.rb index 1322e8e..dbd0d34 100644 --- a/libs/ruby/warp_engine/app/controllers/warp_engine/api/software_controller.rb +++ b/libs/ruby/warp_engine/app/controllers/warp_engine/api/software_controller.rb @@ -39,8 +39,8 @@ module WarpEngine property :desc, String, desc: "Short description" property :story, String, desc: "Long description / story" property :license, String, desc: "License type" - property :platform, String, desc: "Platform (tic80, love, ebitengine, c64, godot, bevy, phaser)" - property :status, String, desc: "Status (active, inactive)" + property :platform, String, desc: "Platform (one of WarpEngine::Platform::NAMES)" + property :status, String, desc: "Status (development, demo, released, archived)" property :highlighted, :boolean, desc: "Currently highlighted" property :imageUrl, String, desc: "Default image URL" property :externalLinks, Array, desc: "External links" do diff --git a/libs/ruby/warp_engine/app/models/warp_engine/platform_link.rb b/libs/ruby/warp_engine/app/models/warp_engine/platform_link.rb index c2de668..106df81 100644 --- a/libs/ruby/warp_engine/app/models/warp_engine/platform_link.rb +++ b/libs/ruby/warp_engine/app/models/warp_engine/platform_link.rb @@ -2,13 +2,14 @@ module WarpEngine class PlatformLink < ApplicationRecord self.table_name = "platform_links" - SUPPORTED_PLATFORMS = %w[tic80 ebitengine love c64 godot bevy phaser].freeze + SUPPORTED_PLATFORMS = WarpEngine::Platform::NAMES validates :name, presence: true validates :url, presence: true validates :platform, presence: true, inclusion: { in: SUPPORTED_PLATFORMS } - default_scope { where(deleted_at: nil).order(:position) } + default_scope { where(deleted_at: nil) } + scope :ordered, -> { order(:position) } def self.ransackable_attributes(auth_object = nil) %w[created_at deleted_at id name platform position updated_at url] @@ -19,7 +20,7 @@ module WarpEngine end def self.for_platform(platform) - where(platform: platform).to_a + ordered.where(platform: platform).to_a end ActiveSupport.run_load_hooks(:warp_engine_platform_link, self) diff --git a/libs/ruby/warp_engine/app/models/warp_engine/software.rb b/libs/ruby/warp_engine/app/models/warp_engine/software.rb index 8d9cdb8..a255071 100644 --- a/libs/ruby/warp_engine/app/models/warp_engine/software.rb +++ b/libs/ruby/warp_engine/app/models/warp_engine/software.rb @@ -2,9 +2,11 @@ module WarpEngine class Software < ApplicationRecord self.table_name = "softwares" + STATUSES = %w[development demo released archived].freeze + belongs_to :owner, polymorphic: true, optional: true - has_many :software_images, foreign_key: :software_id, dependent: :destroy + has_many :software_images, -> { ordered }, foreign_key: :software_id, dependent: :destroy has_many :images, through: :software_images has_many :releases, foreign_key: :software_id has_many :downloads, through: :releases @@ -17,7 +19,8 @@ module WarpEngine validates :name, presence: true, uniqueness: true validates :title, presence: true - validates :platform, presence: true + validates :platform, presence: true, inclusion: { in: WarpEngine::Platform::NAMES } + validates :status, inclusion: { in: STATUSES }, allow_blank: true default_scope { where(deleted_at: nil) } diff --git a/libs/ruby/warp_engine/app/services/warp_engine/builds_service.rb b/libs/ruby/warp_engine/app/services/warp_engine/builds_service.rb index 7ab9285..65bac81 100644 --- a/libs/ruby/warp_engine/app/services/warp_engine/builds_service.rb +++ b/libs/ruby/warp_engine/app/services/warp_engine/builds_service.rb @@ -1,12 +1,8 @@ module WarpEngine class BuildsService def index - platforms = WarpEngine::PlatformLink::SUPPORTED_PLATFORMS.each_with_object({}) do |platform, hash| - service_class = "WarpEngine::Platforms::#{platform.camelize}::Service".constantize - hash[platform] = { - label: service_class.label, - kinds: service_class.expected_kinds - } + platforms = WarpEngine::Platform.all.each_with_object({}) do |platform, hash| + hash[platform.name] = { label: platform.label, kinds: platform.expected_kinds } end all_kinds = WarpEngine::ReleaseAsset::KINDS @@ -16,8 +12,7 @@ module WarpEngine def show(name) software = WarpEngine::Software.find_by!(name: name) - service_class = "WarpEngine::Platforms::#{software.platform.camelize}::Service".constantize - expected = service_class.expected_kinds + expected = WarpEngine::Platform.find!(software.platform).expected_kinds releases = software.releases.includes(:release_assets).order(updated_at: :desc) diff --git a/libs/ruby/warp_engine/app/services/warp_engine/publish_service.rb b/libs/ruby/warp_engine/app/services/warp_engine/publish_service.rb index 2b0dc60..90a6fec 100644 --- a/libs/ruby/warp_engine/app/services/warp_engine/publish_service.rb +++ b/libs/ruby/warp_engine/app/services/warp_engine/publish_service.rb @@ -4,12 +4,7 @@ module WarpEngine NOTIFICATION = "warp_engine.publish".freeze def publish(input) - unless WarpEngine::PlatformLink::SUPPORTED_PLATFORMS.include?(input.platform) - raise ArgumentError, "Unsupported platform: #{input.platform}" - end - - release = "WarpEngine::Platforms::#{input.platform.camelize}::Service".constantize - .new.update(input.name, input.version) + release = WarpEngine::Platform.find!(input.platform).service.update(input.name, input.version) ActiveSupport::Notifications.instrument( NOTIFICATION, diff --git a/libs/ruby/warp_engine/lib/warp_engine.rb b/libs/ruby/warp_engine/lib/warp_engine.rb index 3442782..b9b5d6b 100644 --- a/libs/ruby/warp_engine/lib/warp_engine.rb +++ b/libs/ruby/warp_engine/lib/warp_engine.rb @@ -5,6 +5,7 @@ require "apipie-rails" require "warp_engine/version" require "warp_engine/configuration" +require "warp_engine/platform" require "warp_engine/storage" require "warp_engine/access" require "warp_engine/images" diff --git a/libs/ruby/warp_engine/lib/warp_engine/platform.rb b/libs/ruby/warp_engine/lib/warp_engine/platform.rb new file mode 100644 index 0000000..5b96f77 --- /dev/null +++ b/libs/ruby/warp_engine/lib/warp_engine/platform.rb @@ -0,0 +1,48 @@ +module WarpEngine + class Platform + NAMES = %w[tic80 ebitengine love c64 godot bevy phaser].freeze + + class << self + def names = NAMES + + def all = NAMES.map { |name| new(name) } + + def exists?(name) = NAMES.include?(name.to_s) + + def find(name) + return nil unless exists?(name) + + new(name.to_s) + end + + def find!(name) + find(name) || raise(ArgumentError, "Unsupported platform: #{name}") + end + end + + attr_reader :name + + def initialize(name) + @name = name.to_s + end + + def service_class + "WarpEngine::Platforms::#{name.camelize}::Service".constantize + end + + def service = service_class.new + + def label = service_class.label + + def expected_kinds = service_class.expected_kinds + + def to_s = name + + def ==(other) + other.is_a?(Platform) ? name == other.name : name == other.to_s + end + alias eql? == + + def hash = name.hash + end +end