The desktop graphical client had our store's config URL compiled into it. That is backwards: which stores exist for a catalog is something the site knows, and a client should be able to ask. `GET /api/stores` answers, and the client picks from what comes back. A `Store` row is three fields — name, catalog_url, store_repository_url — with an ActiveAdmin panel, a Blueprinter serializer in the catalog's camelCase, and a service+controller pair following the events and members shape. `db/seeds.rb` creates our own record, so a fresh database serves a working registry. The endpoint is **public**, which is the point: the client runs on someone's laptop before any store exists and has nobody to log in as. It exposes three URLs that are public anyway. This deliberately does not live in WarpEngine. The engine serves one catalog and has no business knowing who ships stores for it; a registry of stores is a property of this site, not of the catalog software. Anyone mounting WarpEngine can keep their own list, or none. 16 model and controller examples pass. Worth noting for the next person who runs them: the specs need RAILS_ENV=test, as the README says — without it rspec runs in the development environment, host authorization rejects Rack::Test's hostname, and every request spec fails with a 403 and an HTML body that looks nothing like a routing problem. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
114 lines
3.8 KiB
Markdown
114 lines
3.8 KiB
Markdown
# 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": "https://git.teletypegames.org/stores/ttg-desktop-store"
|
|
}
|
|
]
|
|
```
|
|
|
|
A `Store` row is three fields — `name`, `catalog_url`, `store_repository_url` —
|
|
maintained from **ActiveAdmin ▸ 🛒 Stores**, and `db/seeds.rb` creates our own.
|
|
The client derives everything else: it reads `config.json` from the store
|
|
repository, points it at `catalog_url`, and falls back to the engine's defaults if
|
|
that repository has no config file.
|
|
|
|
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-desktop-gui`](https://git.teletypegames.org/stores/warp-engine-desktop-gui) |
|
|
|
|
## 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)
|