Compare commits
5
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b09610bc33 | ||
|
|
211dcccbf3 | ||
|
|
b1b619befa | ||
|
|
af779b0988 | ||
|
|
b4d0198d2a |
@@ -0,0 +1,57 @@
|
||||
# Read-only split mirror: a libs/ruby/warp_engine alkönyvtárat kitükrözi a
|
||||
# tools/warp_engine repóba (fejlesztés itt, a monorepóban történik; a tükör
|
||||
# csak publikálásra való). Tag-elt release (warp_engine-v*) esetén a gem a
|
||||
# Forgejo rubygems registry-be is felmegy.
|
||||
#
|
||||
# Szükséges Woodpecker secret: forge_token — Forgejo access token
|
||||
# repository:write (tools/warp_engine) és package:write joggal.
|
||||
|
||||
when:
|
||||
- event: push
|
||||
branch: master
|
||||
path: "libs/ruby/warp_engine/**"
|
||||
- event: manual
|
||||
- event: tag
|
||||
ref: refs/tags/warp_engine-v*
|
||||
|
||||
clone:
|
||||
git:
|
||||
image: woodpeckerci/plugin-git
|
||||
settings:
|
||||
partial: false
|
||||
depth: 0 # a subtree splithez teljes history kell
|
||||
|
||||
steps:
|
||||
split-mirror:
|
||||
image: alpine/git
|
||||
environment:
|
||||
FORGE_TOKEN:
|
||||
from_secret: forge_token
|
||||
commands:
|
||||
- apk add --no-cache git-subtree
|
||||
- git subtree split --prefix=libs/ruby/warp_engine HEAD -b warp-engine-split
|
||||
# a secretbe másolt token végén lehet sortörés — levágjuk
|
||||
- TOKEN="$$(printf '%s' "$${FORGE_TOKEN}" | tr -d '[:space:]')"
|
||||
- git push --force "https://ci:$${TOKEN}@git.teletypegames.org/tools/warp_engine.git" warp-engine-split:master
|
||||
when:
|
||||
- event: push
|
||||
branch: master
|
||||
- event: manual
|
||||
|
||||
publish-gem:
|
||||
image: ruby:3.3-slim
|
||||
environment:
|
||||
FORGE_TOKEN:
|
||||
from_secret: forge_token
|
||||
commands:
|
||||
- cd libs/ruby/warp_engine
|
||||
- gem build warp_engine.gemspec
|
||||
- mkdir -p ~/.gem
|
||||
- TOKEN="$$(printf '%s' "$${FORGE_TOKEN}" | tr -d '[:space:]')"
|
||||
- |
|
||||
printf -- '---\n:https://git.teletypegames.org/api/packages/tools/rubygems: Bearer %s\n' "$${TOKEN}" > ~/.gem/credentials
|
||||
- chmod 600 ~/.gem/credentials
|
||||
- gem push --host https://git.teletypegames.org/api/packages/tools/rubygems warp_engine-*.gem
|
||||
when:
|
||||
- event: tag
|
||||
ref: refs/tags/warp_engine-v*
|
||||
+115
-46
@@ -1,89 +1,158 @@
|
||||
# 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.
|
||||
A mountable Rails engine that turns any Rails application into a retro
|
||||
software catalog: catalog models, a CI-pipeline-callable release updater, a
|
||||
public read-only JSON API, and optional ActiveAdmin resources that plug into
|
||||
your app's existing admin.
|
||||
|
||||
## What it provides
|
||||
Repository: `https://git.teletypegames.org/tools/warp_engine`
|
||||
|
||||
- **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.
|
||||
## Features
|
||||
|
||||
- **Catalog domain**: `Software`, `Release`, `ReleaseAsset`, `ExternalLink`,
|
||||
`PlatformLink`, `Image`, `SoftwareImage`, `Download` models with soft-delete
|
||||
semantics and download statistics.
|
||||
- **CI-callable updater**: your build pipeline drops artifacts into a
|
||||
directory and calls one endpoint — WarpEngine extracts archives, parses
|
||||
metadata and upserts the catalog records. Supported platforms out of the
|
||||
box: TIC-80, Ebitengine, LÖVE, C64, Godot, Bevy, Phaser.
|
||||
- **Public JSON API**: catalog listing, highlighted title, per-platform build
|
||||
matrix, image serving, download tracking, and a static file server for
|
||||
web-playable builds.
|
||||
- **Admin (optional)**: if the host runs ActiveAdmin, WarpEngine contributes
|
||||
ready-made resources — a catalog editor with nested release/asset forms, an
|
||||
image library with orphan cleanup, a file manager with a picker mode, and
|
||||
download statistics. Without ActiveAdmin the engine runs headless
|
||||
(API + updater only).
|
||||
|
||||
## Requirements
|
||||
|
||||
- Rails >= 8.0
|
||||
- A relational database (developed and tested against MySQL 8)
|
||||
- Optional: ActiveAdmin + Devise in the host app for the admin UI
|
||||
|
||||
## Installation
|
||||
|
||||
From the git repository:
|
||||
|
||||
```ruby
|
||||
# Gemfile
|
||||
gem "warp_engine", path: "../../libs/ruby/warp_engine"
|
||||
gem "warp_engine", git: "https://git.teletypegames.org/tools/warp_engine.git"
|
||||
```
|
||||
|
||||
Or from the Forgejo rubygems registry (tagged releases):
|
||||
|
||||
```ruby
|
||||
source "https://git.teletypegames.org/api/packages/tools/rubygems" do
|
||||
gem "warp_engine"
|
||||
end
|
||||
```
|
||||
|
||||
Then:
|
||||
|
||||
```sh
|
||||
rails g warp_engine:install # initializer + create_warp_engine_tables migration
|
||||
rails db:migrate
|
||||
```
|
||||
|
||||
```ruby
|
||||
# config/routes.rb — keep it the last entry so host routes win
|
||||
# config/routes.rb — keep it the last entry so your own routes win
|
||||
mount WarpEngine::Engine => "/"
|
||||
```
|
||||
|
||||
## Configuration
|
||||
|
||||
```ruby
|
||||
# config/initializers/warp_engine.rb
|
||||
Rails.application.config.to_prepare do
|
||||
WarpEngine.configure do |c|
|
||||
# Where CI drops build artifacts and where images are stored
|
||||
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? }
|
||||
}
|
||||
]
|
||||
|
||||
# Shared secret for the /update endpoint.
|
||||
# nil => the endpoint rejects every request.
|
||||
c.update_secret = ENV["UPDATE_SECRET"]
|
||||
|
||||
# If your app's own models reference catalog images, register them so the
|
||||
# admin Images page counts them as "in use":
|
||||
# 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
|
||||
## The updater contract
|
||||
|
||||
- **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"`.
|
||||
Publishing a release from CI is two steps:
|
||||
|
||||
1. **Upload** build artifacts into `file_container_path`, named by convention:
|
||||
`<name>-<version>.metadata.json`, `<name>-<version>.html.zip`,
|
||||
`<name>-<version>-win-x64.zip`, `<name>-<version>.tic`, ... (each platform
|
||||
declares which asset kinds it expects — see `GET /api/builds`).
|
||||
2. **Call the endpoint**:
|
||||
|
||||
```sh
|
||||
curl -H "X-Update-Secret: $UPDATE_SECRET" \
|
||||
"https://your-host/update?platform=tic80&name=mygame&version=1.2.0"
|
||||
```
|
||||
|
||||
WarpEngine extracts the archives, parses the metadata (JSON, or the Lua
|
||||
comment header for TIC-80), and upserts the `Software`, `ExternalLink`,
|
||||
`Release` and `ReleaseAsset` records in a single transaction. Previously
|
||||
deleted records are resurrected on re-ingest.
|
||||
|
||||
## Public API
|
||||
|
||||
| Endpoint | Purpose |
|
||||
| --- | --- |
|
||||
| `GET /api/software` | Full catalog with releases, assets, links, download counts |
|
||||
| `GET /api/software/highlighted` | The currently highlighted title |
|
||||
| `GET /api/builds` | Expected asset kinds per platform (build matrix) |
|
||||
| `GET /api/softwares/:name/builds` | Actual vs. missing build assets per release |
|
||||
| `GET /api/image/:id` | Serves catalog images |
|
||||
| `GET /api/download?path=` | Serves an artifact and logs a download record |
|
||||
| `GET /file/*path` | Serves static build output (web-playable games, docs) |
|
||||
|
||||
## Admin integration
|
||||
|
||||
The host owns the single ActiveAdmin instance — authentication (Devise),
|
||||
theme, assets and the `/admin` routes. WarpEngine only appends its resource
|
||||
files to `ActiveAdmin.application.load_paths`. Two things to copy into a new
|
||||
host:
|
||||
|
||||
- the small file-picker JS for release-asset path inputs (an iframe pointing
|
||||
at `/admin/files?picker=1&field=<dom_id>`) in your `active_admin.js`;
|
||||
- if you generate apipie docs, add
|
||||
`"#{WarpEngine::Engine.root}/app/controllers/**/*.rb"` to your
|
||||
`api_controllers_matcher`.
|
||||
|
||||
## 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).
|
||||
- Every model is soft-deleted (`default_scope { where(deleted_at: nil) }`).
|
||||
- The JSON shape is stable and intentionally bug-compatible with the project's
|
||||
former Go backend (Go zero-time timestamps, camelCase keys, legacy flat
|
||||
path fields).
|
||||
- Model extension points: `ActiveSupport.on_load(:warp_engine_<model>)` hooks.
|
||||
|
||||
## Tests
|
||||
|
||||
The engine ships an RSpec suite running against a bundled dummy app:
|
||||
|
||||
```sh
|
||||
bundle install
|
||||
bundle exec rake app:db:prepare RAILS_ENV=test # warp_engine_test DB for the dummy app
|
||||
bundle exec rake app:db:prepare RAILS_ENV=test
|
||||
bundle exec rspec
|
||||
```
|
||||
|
||||
## Development
|
||||
|
||||
This repository is a **read-only split mirror** — development happens in the
|
||||
[`tools/teletypegames`](https://git.teletypegames.org/tools/teletypegames)
|
||||
monorepo under `libs/ruby/warp_engine`, and CI republishes the mirror on every
|
||||
change. Please do not open pull requests against the mirror.
|
||||
|
||||
Reference in New Issue
Block a user