Phase 6: polish — apipie matcher, install generator, engine migrations, docs
- host apipie matcher now globs the engine controllers, so /api/docs and /api/swagger keep documenting the catalog endpoints - rails g warp_engine:install: initializer template + a clean create_warp_engine_tables migration (signed bigint PKs) for new hosts; TTG never runs it - engine append_migrations initializer: future catalog migrations in the engine's db/migrate run via the host's rails db:migrate - README documents the updater contract, config surface, host expectations (admin JS picker, apipie matcher) and the soft-delete/resurrection behavior - make api-test runs both suites plus a production zeitwerk:check Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -3,32 +3,80 @@
|
||||
Mountable Rails engine: retro szoftverkatalógus CI-pipeline-ból hívható release-updaterrel,
|
||||
publikus read-only JSON API-val és a host adminjába betöltődő ActiveAdmin erőforrásokkal.
|
||||
|
||||
## Használat
|
||||
## Mit ad
|
||||
|
||||
- **Modellek**: `Software`, `Release`, `ReleaseAsset`, `ExternalLink`, `PlatformLink`,
|
||||
`Image`, `SoftwareImage`, `Download` (mind `WarpEngine::` alatt, prefix nélküli táblákkal)
|
||||
- **Updater**: `GET /update?platform=&name=&version=` (`X-Update-Secret` fejléc vagy
|
||||
`?secret=`) — a CI a build-artifactokat a `file_container_path` alá másolja
|
||||
(`<name>-<version>*` konvencióval), majd meghívja az endpointot; az updater kicsomagol,
|
||||
metadatát parse-ol, és upserteli a Software/Release/ReleaseAsset/ExternalLink rekordokat.
|
||||
Támogatott platformok: tic80, ebitengine, love, c64, godot, bevy, phaser.
|
||||
- **Publikus API**: `/api/software`, `/api/software/highlighted`, `/api/builds`,
|
||||
`/api/softwares/:name/builds`, `/api/image/:id`, `/api/download?path=`, `/file/*path`
|
||||
- **Admin**: ActiveAdmin resource-fájlok (softwares a 3 szintű beágyazott formmal,
|
||||
releases, external links, platform links, images orphan-kezeléssel, Files fájlkezelő
|
||||
picker móddal, download stats) — a host ActiveAdmin példányába töltődnek be.
|
||||
|
||||
## Telepítés
|
||||
|
||||
```ruby
|
||||
# Gemfile
|
||||
gem "warp_engine", path: "../../libs/ruby/warp_engine"
|
||||
```
|
||||
|
||||
```sh
|
||||
rails g warp_engine:install # initializer + create_warp_engine_tables migráció
|
||||
rails db:migrate
|
||||
```
|
||||
|
||||
```ruby
|
||||
# config/routes.rb — utolsó sorként, hogy a host route-jai nyerjenek
|
||||
mount WarpEngine::Engine => "/"
|
||||
```
|
||||
|
||||
# config/initializers/warp_engine.rb
|
||||
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"]
|
||||
# Ha a host modelljei is használnak katalógus-képeket:
|
||||
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? }
|
||||
}
|
||||
## Konfiguráció
|
||||
|
||||
```ruby
|
||||
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 mindig elutasít
|
||||
# Ha a host modelljei is használnak katalógus-képeket:
|
||||
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
|
||||
```
|
||||
|
||||
## Megjegyzések
|
||||
## Host-elvárások
|
||||
|
||||
- A modellek soft-delete-et használnak (`default_scope { where(deleted_at: nil) }`);
|
||||
az updater `.unscoped`-dal "feltámasztja" az újra beküldött, korábban törölt rekordokat.
|
||||
- A táblanevek prefix nélküliek (`softwares`, `releases`, ...).
|
||||
- Az admin erőforrások a host ActiveAdmin példányába töltődnek be; auth (Devise) a host dolga.
|
||||
- **ActiveAdmin + Devise a hostban él**: auth, téma, assetek és a `/admin` route-ok a
|
||||
host dolga; az engine csak resource-fájlokat ad a `ActiveAdmin.application.load_paths`-hoz.
|
||||
- **Files picker JS**: a release-asset path-mezők melletti fájlkiválasztó a host
|
||||
`active_admin.js`-ében élő pár soros JS-re támaszkodik (iframe a
|
||||
`/admin/files?picker=1&field=<dom_id>` címre) — új hostba ezt is át kell venni.
|
||||
- **apipie**: ha a host apipie-dokut generál, vegye fel a matcherbe:
|
||||
`"#{WarpEngine::Engine.root}/app/controllers/**/*.rb"`.
|
||||
|
||||
## Viselkedési megjegyzések
|
||||
|
||||
- Minden modell soft-delete-es (`default_scope { where(deleted_at: nil) }`); az updater
|
||||
`.unscoped`-dal "feltámasztja" az újra beküldött, korábban törölt rekordokat.
|
||||
- A JSON-formátum szándékosan bug-kompatibilis az egykori Go backenddel
|
||||
(Go zero-time timestampek, camelCase kulcsok, legacy flat path mezők).
|
||||
- Modell-kiterjesztési pontok: `ActiveSupport.on_load(:warp_engine_<model>)` hookok.
|
||||
|
||||
## Tesztek
|
||||
|
||||
```sh
|
||||
bundle install
|
||||
bundle exec rake app:db:prepare RAILS_ENV=test # warp_engine_test DB a dummy apphoz
|
||||
bundle exec rspec
|
||||
```
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
require "rails/generators"
|
||||
require "rails/generators/migration"
|
||||
require "rails/generators/active_record"
|
||||
|
||||
module WarpEngine
|
||||
module Generators
|
||||
class InstallGenerator < Rails::Generators::Base
|
||||
include Rails::Generators::Migration
|
||||
|
||||
source_root File.expand_path("templates", __dir__)
|
||||
|
||||
def self.next_migration_number(dirname)
|
||||
ActiveRecord::Generators::Base.next_migration_number(dirname)
|
||||
end
|
||||
|
||||
def copy_initializer
|
||||
template "initializer.rb", "config/initializers/warp_engine.rb"
|
||||
end
|
||||
|
||||
def copy_migration
|
||||
migration_template "create_warp_engine_tables.rb", "db/migrate/create_warp_engine_tables.rb"
|
||||
end
|
||||
|
||||
def show_readme
|
||||
say <<~MSG
|
||||
|
||||
WarpEngine telepítve. Következő lépések:
|
||||
1. rails db:migrate
|
||||
2. mount WarpEngine::Engine => "/" a config/routes.rb végére
|
||||
3. állítsd be a config/initializers/warp_engine.rb-t
|
||||
MSG
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,91 @@
|
||||
class CreateWarpEngineTables < ActiveRecord::Migration[8.0]
|
||||
def change
|
||||
create_table :softwares do |t|
|
||||
t.string :name, limit: 128
|
||||
t.string :title
|
||||
t.string :author
|
||||
t.text :desc
|
||||
t.text :story
|
||||
t.string :license, limit: 128
|
||||
t.string :platform, limit: 128
|
||||
t.string :site
|
||||
t.string :status, limit: 20, default: "development"
|
||||
t.boolean :highlighted, default: false
|
||||
t.datetime :deleted_at, precision: 3
|
||||
t.timestamps precision: 3, null: true
|
||||
t.index :name, unique: true
|
||||
t.index :deleted_at
|
||||
end
|
||||
|
||||
create_table :releases do |t|
|
||||
t.references :software, foreign_key: { on_delete: :cascade }
|
||||
t.string :version, limit: 64
|
||||
t.datetime :deleted_at, precision: 3
|
||||
t.timestamps precision: 3, null: true
|
||||
t.index :deleted_at
|
||||
end
|
||||
|
||||
create_table :release_assets do |t|
|
||||
t.references :release, null: false, foreign_key: { on_delete: :cascade }
|
||||
t.string :kind, limit: 32, null: false
|
||||
t.string :path, null: false
|
||||
t.datetime :deleted_at, precision: 3
|
||||
t.timestamps precision: 3
|
||||
t.index [ :release_id, :kind ], unique: true
|
||||
t.index :path
|
||||
t.index :deleted_at
|
||||
end
|
||||
|
||||
create_table :external_links do |t|
|
||||
t.references :software, foreign_key: { on_delete: :cascade }
|
||||
t.string :label, limit: 128
|
||||
t.string :url
|
||||
t.datetime :deleted_at, precision: 3
|
||||
t.timestamps precision: 3, null: true
|
||||
t.index :deleted_at
|
||||
end
|
||||
|
||||
create_table :platform_links do |t|
|
||||
t.string :name, limit: 128
|
||||
t.string :platform, limit: 128
|
||||
t.string :url
|
||||
t.integer :position, default: 0, null: false
|
||||
t.datetime :deleted_at, precision: 3
|
||||
t.timestamps precision: 3, null: true
|
||||
t.index :platform
|
||||
t.index :deleted_at
|
||||
end
|
||||
|
||||
create_table :images do |t|
|
||||
t.string :filename, null: false
|
||||
t.string :original_filename, null: false
|
||||
t.string :content_type, default: "application/octet-stream", null: false
|
||||
t.datetime :deleted_at, precision: 3
|
||||
t.timestamps
|
||||
t.index :deleted_at
|
||||
end
|
||||
|
||||
create_table :software_images do |t|
|
||||
t.references :software, null: false, foreign_key: { on_delete: :cascade }
|
||||
t.references :image, null: false, foreign_key: true, index: false
|
||||
t.boolean :is_default, default: false, null: false
|
||||
t.integer :position, default: 0, null: false
|
||||
t.timestamps
|
||||
t.index [ :software_id, :image_id ], unique: true
|
||||
t.index [ :software_id, :position ]
|
||||
end
|
||||
|
||||
create_table :downloads do |t|
|
||||
t.string :file_path, null: false
|
||||
t.references :release, foreign_key: { on_delete: :nullify }, index: false
|
||||
t.string :ip_address
|
||||
t.string :user_agent, limit: 500
|
||||
t.string :referer, limit: 500
|
||||
t.datetime :deleted_at, precision: 3
|
||||
t.timestamps precision: 3, null: true
|
||||
t.index :file_path
|
||||
t.index :release_id
|
||||
t.index :deleted_at
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,22 @@
|
||||
Rails.application.config.to_prepare do
|
||||
WarpEngine.configure do |c|
|
||||
# A build-artifactok és képek tárolási helye (defaultok az env-ből:
|
||||
# FILE_CONTAINER_PATH ill. IMAGE_CONTAINER_PATH).
|
||||
# c.file_container_path = "/softwares"
|
||||
# c.image_container_path = "/images"
|
||||
|
||||
# A /update endpoint shared secretje (default: ENV["UPDATE_SECRET"]).
|
||||
# Beállítatlan secret esetén az endpoint minden kérést elutasít.
|
||||
# c.update_secret = ENV["UPDATE_SECRET"]
|
||||
|
||||
# Ha a host modelljei is hivatkoznak katalógus-képekre, regisztráld őket,
|
||||
# hogy az admin Images oldal orphan-detektálása figyelembe vegye:
|
||||
# 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
|
||||
@@ -8,6 +8,16 @@ module WarpEngine
|
||||
Rails.autoloaders.main.ignore(Engine.root.join("app/admin"))
|
||||
end
|
||||
|
||||
# A jövőbeli katalógus-migrációk az engine db/migrate-jéből futnak a host
|
||||
# rails db:migrate-jével, másolás nélkül.
|
||||
initializer "warp_engine.append_migrations" do |app|
|
||||
unless app.root.to_s.start_with?(root.to_s)
|
||||
config.paths["db/migrate"].expanded.each do |path|
|
||||
app.config.paths["db/migrate"] << path
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Az admin erőforrásokat a HOST ActiveAdmin példánya tölti be; az engine csak
|
||||
# regisztrálja a saját app/admin könyvtárát. Auth/téma/route-ok a host dolga.
|
||||
initializer "warp_engine.active_admin" do |app|
|
||||
|
||||
Reference in New Issue
Block a user