The Teletype Games public platform: one monorepo (
services/teletypegames) with the catalog backend (Catalog API) and the retro-styled frontend, run by Docker Compose.
apps/
frontend/ # Vue 3 + Vite + TypeScript + Tailwind
api/ # Rails 8 API + ActiveAdmin
docker compose up -d starts the dev environment. Both apps sit under one domain:
| Path | Serves |
|---|---|
/ |
frontend |
/api |
Catalog API |
/admin |
ActiveAdmin |
/api/swagger |
API docs |
Linting: RuboCop (rubocop-rails-omakase) for the backend, ESLint 9 (flat config, Vue + TypeScript) for the frontend.
Rails 8 + MySQL. ActiveAdmin panel (blaze theme, Devise auth), Blueprinter serializers, apipie-rails docs with a browsable Swagger UI. It is the single source of truth for software metadata, releases, members, events and download logs, and it serves images and binaries.
Software (name, title, author, desc, story, license, platform, status, highlighted; soft-deleted) has many Releases and ExternalLinks, and images through SoftwareImage (default flag + ordering). A Release holds the version and the cartridge / source / HTML folder / docs folder paths. Every download is a Download row (IP, user agent, referer, release), surfacing as downloadCount / totalDownloads in API responses.
| Endpoint | Purpose |
|---|---|
GET /api/software |
Catalog listing: software + sorted releases + latestRelease (skipping dev-) + webPlayableRelease + totalDownloads |
GET /api/software/highlighted |
Featured software |
GET /api/events |
Community events |
GET /api/members |
Team members |
GET /api/stores |
Store registry (see below) |
GET /api/image/:id |
Image serving |
GET /api/wiki/pages?tag=blog\|howto[&limit][&body=1][&lang=en\|hu] |
Wiki proxy: fetches Grav's custom/pages.json via WIKI_GRAV_URL, so the frontend never talks to the wiki. lang picks the wiki language, defaulting to en |
GET /api/rss/blog |
RSS of blog-tagged wiki pages |
GET /api/rss/releases |
RSS of the latest releases |
GET /api/download?path=… |
Logged download (records IP / UA / referer, then serves) |
GET /api/swagger |
apipie docs |
GET /file/*path |
Direct file download; release paths in API responses point here |
POST /build/upload |
Artifact upload, one file per request; application token with the upload scope |
POST /build/publish |
Release publishing; application token with the update scope, called by CI after upload |
Tokens are described in the WarpEngine article.
The store registry (GET /api/stores) says which catalogs a client can install from and where each store's config lives. It is public, because the desktop client reads it on first run and has nobody to log in as. A row is three fields (name, catalog_url, store_repository_url), edited in ActiveAdmin ▸ 🛒 Stores. It lives in the host app, not in WarpEngine: the engine serves one catalog and does not know who ships stores for it.
/build/publish dispatches by platform to a Platforms service (tic80, love, ebitengine, c64, godot, bevy, phaser). Each one picks up the uploaded artifacts (C64: <name>-<version>.prg + <name>-<version>.metadata.json), upserts the Software from the metadata, adds a Source Code external link from its site field, and creates the Release. The Gitea + Woodpecker side is in the Deployment article.
A Vue 3 SPA built with Vite in TypeScript: vue-router (one module per page), Pinia stores per domain (software, events, members, git, blog, howtos, youtube, ui), vue-i18n, and Tailwind CSS + TuiCSS for the retro terminal look. Traefik proxies it on the same base domain as the API.
Pages: home, catalog (index + show), blog, code, howtos, team, contact. The catalog show page has an image lightbox, the release list, download counts and web-playable releases.
One client module per source under src/api/:
| Module | Source |
|---|---|
software.api.ts, event.api.ts, member.api.ts |
Catalog API |
wiki.api.ts |
Catalog API wiki proxy (blog / howto tags), passing the interface language as lang. VITE_WIKI_BASE is only used to build external links to Grav |
git.api.ts |
Gitea REST API v1 (token WEBAPP_GITEA_TOKEN) for the Code page |
youtube.api.ts |
YouTube Data API: latest channel video and view count |