# Teletype Games Monorepo for the Teletype Games portal: a Vue 3 frontend, a Rails 8 API host app, and the reusable **WarpEngine** software-catalog engine. ## Project structure ``` apps/ frontend/ # Vue 3 + Vite + TypeScript + Tailwind SPA api/ # Rails 8 host app: TTG-specific API + ActiveAdmin shell libs/ ruby/warp_engine/ # WarpEngine: mountable Rails engine (catalog, updater, admin resources) ``` The API is split in two layers: - **WarpEngine** (`libs/ruby/warp_engine`) owns the software catalog: models (softwares, releases, release assets, images, platform links, download stats), the CI-callable `/build/*` publishing endpoints, the public read-only JSON API (`/api/software*`, `/api/builds*`, `/api/image`, `/api/download`, `/file/*`) and the catalog ActiveAdmin resources. See its [README](libs/ruby/warp_engine/README.md). - **The host app** (`apps/api`) owns everything TTG-specific: members, events, the store registry, wiki proxy, RSS feeds, Devise/ActiveAdmin authentication, theming and assets. It consumes WarpEngine as a path gem and mounts it at `/`. ## The store registry `GET /api/stores` lists the stores a client can install from. The desktop graphical client reads it on first run, which is why it is **public**: a client has nobody to log in as. ```json [ { "name": "Teletype Games", "catalogUrl": "https://teletypegames.org", "storeRepositoryUrl": null } ] ``` A `Store` row has two required fields — `name` and `catalog_url` — plus an **optional** `store_repository_url`, maintained from **ActiveAdmin ▸ 🛒 Stores**; `db/seeds.rb` creates our own. **A store does not need a repository.** The store engine's own defaults already cover the host-to-asset mapping, the install modes, the platforms and the behaviour; what they cannot know is identity — a slug, a name and a catalog — and that is what this row carries. With no repository the client derives the slug from the catalog host, writes a small config and installs. Given one, it reads that repository's `config.json` and points it at `catalog_url`, and that file remains the authority on how the store behaves; a repository without a config file is treated as no repository at all. This lives in the host app **on purpose, not in WarpEngine**. The engine serves one catalog and has no business knowing which stores exist for it; who ships a store for a catalog is a property of the site. | | | |---|---| | Model | `apps/api/app/models/store.rb` | | Endpoint | `apps/api/app/controllers/api/stores_controller.rb` | | Admin | `apps/api/app/admin/stores.rb` | | Client | [`warp-engine-client`](https://git.teletypegames.org/stores/warp-engine-client) | ## Development environment ```bash docker compose up -d ``` | Service | URL | |---|---| | Frontend | `http://${WEBAPP_DOMAIN}` | | API | `http://${WEBAPP_DOMAIN}/api` | | Admin | `http://${WEBAPP_DOMAIN}/admin` | | API docs | `http://${WEBAPP_DOMAIN}/api/swagger` | The api image is built from the repo root (so the `libs/` path gems are visible during `bundle install`) and mounts `./libs` at runtime. After changing the compose file or the Gemfile, rebuild with `docker compose up -d --build api`. ## Testing ```bash make api-test ``` Runs the host suite (`apps/api`, `softwares_test` DB), the WarpEngine suite (dummy app, `warp_engine_test` DB) and a production-mode `zeitwerk:check`. Individually: ```bash docker exec -e RAILS_ENV=test api bundle exec rspec # host docker exec -w /libs/ruby/warp_engine -e RAILS_ENV=test api bundle exec rspec # engine ``` JSON contract baselines for `/api/software` and `/api/builds` live in `apps/api/spec/snapshots/` — diff against them after refactors. ## Linting ### Backend (RuboCop) ```bash docker compose run --rm --no-deps api bundle exec rubocop # check docker compose run --rm --no-deps api bundle exec rubocop -A # autofix ``` Config: `apps/api/.rubocop.yml` (rubocop-rails-omakase preset) ### Frontend (ESLint) ```bash docker compose run --rm --no-deps frontend npm run lint # check docker compose run --rm --no-deps frontend npm run lint:fix # autofix ``` Config: `apps/frontend/eslint.config.js` (ESLint 9 flat config, Vue + TypeScript)