WarpEngine

Mountable Rails engine: a retro software catalog with a CI-pipeline-callable release updater, a public read-only JSON API, and ActiveAdmin resources that load into the host application's admin.

What it provides

  • Models: Software, Release, ReleaseAsset, ExternalLink, PlatformLink, Image, SoftwareImage, Download (all under WarpEngine::, with unprefixed table names)
  • Updater: GET /update?platform=&name=&version= (auth via the X-Update-Secret header or ?secret=) — CI copies build artifacts under file_container_path using the <name>-<version>* naming convention, then calls the endpoint; the updater extracts archives, parses metadata, and upserts the Software/Release/ReleaseAsset/ExternalLink records. Supported platforms: tic80, ebitengine, love, c64, godot, bevy, phaser.
  • Public API: /api/software, /api/software/highlighted, /api/builds, /api/softwares/:name/builds, /api/image/:id, /api/download?path=, /file/*path
  • Admin: ActiveAdmin resource files (softwares with a 3-level nested form, releases, external links, platform links, images with orphan management, a Files file-manager page with picker mode, download stats) — loaded into the host's single ActiveAdmin instance.

Installation

# Gemfile
gem "warp_engine", path: "../../libs/ruby/warp_engine"
rails g warp_engine:install   # initializer + create_warp_engine_tables migration
rails db:migrate
# config/routes.rb — keep it the last entry so host routes win
mount WarpEngine::Engine => "/"

Configuration

Rails.application.config.to_prepare do
  WarpEngine.configure do |c|
    c.file_container_path  = ENV.fetch("FILE_CONTAINER_PATH", "/softwares")
    c.image_container_path = ENV.fetch("IMAGE_CONTAINER_PATH", "/images")
    c.update_secret        = ENV["UPDATE_SECRET"] # nil => /update rejects everything
    # If host models also reference catalog images:
    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

Host expectations

  • ActiveAdmin + Devise live in the host: authentication, theme, assets and the /admin routes are the host's responsibility; the engine only appends its resource files to ActiveAdmin.application.load_paths.
  • Files picker JS: the file-picker next to release-asset path inputs relies on a few lines of JS in the host's active_admin.js (an iframe pointing at /admin/files?picker=1&field=<dom_id>) — copy that over to a new host too.
  • apipie: if the host generates apipie docs, add the engine to the matcher: "#{WarpEngine::Engine.root}/app/controllers/**/*.rb".

Behavioral notes

  • Every model is soft-deleted (default_scope { where(deleted_at: nil) }); the updater "resurrects" re-submitted, previously deleted records via .unscoped.
  • The JSON shape is intentionally bug-compatible with the former Go backend (Go zero-time timestamps, camelCase keys, legacy flat path fields).
  • Model extension points: ActiveSupport.on_load(:warp_engine_<model>) hooks.

Tests

bundle install
bundle exec rake app:db:prepare RAILS_ENV=test   # warp_engine_test DB for the dummy app
bundle exec rspec
S
Description
No description provided
Readme
89 KiB
Languages
Ruby 100%