diff --git a/Makefile b/Makefile index e6c3552..f24c21e 100644 --- a/Makefile +++ b/Makefile @@ -18,3 +18,9 @@ stop: $(call docker_compose,down --remove-orphans) restart: stop start + +# Teljes API-teszt: host suite + warp_engine suite + production eager-load check +api-test: + docker exec -e RAILS_ENV=test api bundle exec rspec + docker exec -w /libs/ruby/warp_engine -e RAILS_ENV=test api bundle exec rspec + docker exec -e RAILS_ENV=production -e SECRET_KEY_BASE=zeitwerkcheck api bundle exec rails zeitwerk:check diff --git a/apps/api/config/initializers/apipie.rb b/apps/api/config/initializers/apipie.rb index 97db23a..7dd2c53 100644 --- a/apps/api/config/initializers/apipie.rb +++ b/apps/api/config/initializers/apipie.rb @@ -4,8 +4,7 @@ Apipie.configure do |config| config.doc_base_url = "/api/docs" config.api_controllers_matcher = [ "#{Rails.root}/app/controllers/api/**/*.rb", - "#{Rails.root}/app/controllers/update_controller.rb", - "#{Rails.root}/app/controllers/files_controller.rb" + "#{WarpEngine::Engine.root}/app/controllers/**/*.rb" ] config.validate = false config.translate = false diff --git a/libs/ruby/warp_engine/README.md b/libs/ruby/warp_engine/README.md index e8cce07..346da0d 100644 --- a/libs/ruby/warp_engine/README.md +++ b/libs/ruby/warp_engine/README.md @@ -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 + (`-*` 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=` 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_)` hookok. + +## Tesztek + +```sh +bundle install +bundle exec rake app:db:prepare RAILS_ENV=test # warp_engine_test DB a dummy apphoz +bundle exec rspec +``` diff --git a/libs/ruby/warp_engine/lib/generators/warp_engine/install/install_generator.rb b/libs/ruby/warp_engine/lib/generators/warp_engine/install/install_generator.rb new file mode 100644 index 0000000..7bdd761 --- /dev/null +++ b/libs/ruby/warp_engine/lib/generators/warp_engine/install/install_generator.rb @@ -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 diff --git a/libs/ruby/warp_engine/lib/generators/warp_engine/install/templates/create_warp_engine_tables.rb b/libs/ruby/warp_engine/lib/generators/warp_engine/install/templates/create_warp_engine_tables.rb new file mode 100644 index 0000000..47f52e7 --- /dev/null +++ b/libs/ruby/warp_engine/lib/generators/warp_engine/install/templates/create_warp_engine_tables.rb @@ -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 diff --git a/libs/ruby/warp_engine/lib/generators/warp_engine/install/templates/initializer.rb b/libs/ruby/warp_engine/lib/generators/warp_engine/install/templates/initializer.rb new file mode 100644 index 0000000..24657a6 --- /dev/null +++ b/libs/ruby/warp_engine/lib/generators/warp_engine/install/templates/initializer.rb @@ -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 diff --git a/libs/ruby/warp_engine/lib/warp_engine/engine.rb b/libs/ruby/warp_engine/lib/warp_engine/engine.rb index b163d6b..595864f 100644 --- a/libs/ruby/warp_engine/lib/warp_engine/engine.rb +++ b/libs/ruby/warp_engine/lib/warp_engine/engine.rb @@ -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|