# @blade-and-brawn/api ElysiaJS backend for Blade & Brawn: the level calculator endpoint, Printful/Webflow commerce sync, and the admin API used by `apps/portal`. ## Stack - [ElysiaJS](https://elysiajs.com) on Bun, run in `node:cluster` worker processes (see `src/index.ts`; worker count is `min(CPU cores, MAX_WORKER_COUNT)`) - PostgreSQL via [Kysely](https://kysely.dev) - JWT cookie auth (`@elysia/jwt`) ## Environment variables All required unless noted. See `src/util.ts` for the source of truth. | Variable | Purpose | | --- | --- | | `PRINTFUL_AUTH` | Printful API token | | `PRINTFUL_STORE_ID` | Printful store ID | | `PRINTFUL_WEBHOOK_SECRET` | Shared secret appended to the Printful webhook URL | | `WEBFLOW_SITE_ID` | Webflow site ID | | `WEBFLOW_COLLECTION_PRODUCTS_ID` | Webflow CMS collection ID for products | | `WEBFLOW_COLLECTION_SKUS_ID` | Webflow CMS collection ID for SKUs | | `WEBFLOW_AUTH` | Webflow API token | | `WEBFLOW_WEBHOOK_SECRET` | Webflow webhook signing secret | | `AUTH_SECRET` | JWT signing secret (must match `AUTH_SECRET` in `apps/portal`) | | `ADMIN_PASSWORD` | sha256 hex digest checked against on `/auth/login` | | `DATABASE_URL` | Postgres connection string | | `DATABASE_POOL_MAX` | Postgres pool size | | `MAX_WORKER_COUNT` | Upper bound on cluster worker processes | | `NODE_ENV` | optional, defaults to `development` | | `LOG_LEVEL` | optional, defaults to `info` | ## Scripts Run from the repo root as `bun run dev:api` / `bun run build:api`, or from this directory: - `dev` — run with hot reload - `start` — run without hot reload - `build` — compile to a standalone binary at `dist/server` - `db:migrate:latest` / `:rollback` / `:up` / `:down` / `:reset` — Kysely migrations (`src/database/migrations`) - `db:types:gen` — regenerate `src/database/out/db.d.ts` from the database schema - `db:seed` — run `src/database/seed.ts` (loads `seed-data/standards-config.json`) ## Structure - `src/server.ts` — Elysia app: routes, plugins, error handling - `src/services/` — calculator, standards, commerce, events, event queue - `src/database/` — Kysely instance, migrations, seed data - `src/scripts/` — one-off scripts (e.g. `register-webhooks.ts` to (re)register Printful/Webflow webhooks against this API's domain) ## Deployment Deployed to Fly.io (`fly.toml`). After a domain changes, re-run `src/scripts/register-webhooks.ts` with production env vars to point Printful's and Webflow's webhooks at the new domain — see issue #3 for the full checklist. ### Running migrations/seed against production The deployed image only contains the compiled binary (see `Dockerfile`) — no source, no `bun_modules`, no migration files — so these can't be run from `fly ssh console` on the API app itself. Instead, use `migrate.sh` at the repo root, which tunnels to the Postgres app (`blade-and-brawn-db`, legacy/unmanaged Fly Postgres) via `fly proxy`, fetches the production `DATABASE_URL` for you, and runs the scripts against it: ```bash ./migrate.sh migrate # db:migrate:latest (default if no argument given) ./migrate.sh seed # db:seed ./migrate.sh both # both, in order ``` Both underlying scripts prompt for a `y/N` confirmation before touching the database, and `db:seed` is idempotent (skips seeding if the default rows already exist). If the tunneled connection fails on TLS, legacy Postgres sometimes needs `?sslmode=disable` appended — edit `migrate.sh` if so.