Development Guide
Everyday commands, the database lifecycle (schema changes, seed, reset, backups), the seeded accounts, tests and troubleshooting.
The rules behind these commands live in AGENTS.md; this page is the how-to.
First run
git clone https://github.com/krizaka/orochia.git && cd orochia
npm install
npm run setup # .env + PostgreSQL 16 + migrations + seed
npm run dev # http://localhost:3000npm run setup is idempotent: run it again after pulling, or whenever the databases were stopped.
Clone the admin console and the design system next to this repository with npm run workspace -- clone, then
npm run workspace -- status shows the three repositories at a glance.
| App | Directory | Port | Start |
|---|---|---|---|
| Web app & API | orochia/ | 3000 | npm run dev |
| Admin console | ../orochia-admin/ | 3001 | OROCHIA_API_URL=http://localhost:3000 npm run dev -- -p 3001 |
| Design system showcase | ../orochia-design-system/ | 3002 | npm run dev |
Accounts of the development seed
| Role | Password | Notes | |
|---|---|---|---|
| Administrator | admin@orochia.org | admin1234 | signs in to the admin console |
| Creator | elena@orochia.org | elena1234 | verified; public, paid and followers-only videos; payout in progress |
| Creator | mia@orochia.org | mia1234 | verified; public, contacts-only and encoding videos |
| Creator | nova@orochia.org | nova1234 | not verified — waits in the 2257 queue |
| Member | alex@sanctuary.io | alex1234 | approved follower of Elena, contact of Mia, two unlocks |
| Member | sam@sanctuary.io | sam1234 | pending follow of Elena, pending contact request to Mia |
The database
PostgreSQL runs in the orochia-postgres-dev container
(deploy/docker/docker-compose.dev.yml, data in a named Docker volume).
| I want to… | Command |
|---|---|
| start / stop the databases | npm run db:up / npm run db:down |
| see applied and pending migrations, row counts | npm run db:status |
| browse and edit data | npm run db:studio (Drizzle Studio) or npm run db:psql |
| start over from a clean state | npm run db:reset -- --yes |
| keep a copy before an experiment | npm run db:dump → backups/orochia-<date>.sql |
| come back to that copy | npm run db:restore -- backups/orochia-<date>.sql |
db:reset, db:restore and db:psql work only on the local container: they refuse NODE_ENV=production and any
DATABASE_URL that does not point at this machine.
Changing the schema
- Edit the tables in
packages/db/src/schema/*.ts(and their relations inschema/index.ts). npm run db:generate -- --name short_descriptionwritespackages/db/drizzle/NNNN_short_description.sql.- Read the SQL. Renames are generated as drop + add — rewrite them as
ALTER … RENAMEby hand when data must survive. npm run db:migrate, thennpm run db:check.npm run docs:generate—docs/DATABASE.mdfollows the schema.- Commit the schema, the migration and the docs together. Never edit a migration that was already applied anywhere.
The seed
packages/db/src/seed.ts builds a small, coherent platform that exercises every feature. It is idempotent
(rows are keyed by e-mail, Bunny video id, gateway reference…), so npm run db:seed can run any time; it writes
ledger rows exactly as settlement does (10 % platform fee) and refuses to run with NODE_ENV=production.
Tests
npm run check # lint + types + unit tests + generated docs up to date
npm run db:reset -- --yes # the scenarios change data: start them from the seed
npm run test:e2e # needs the app running (npm run dev); OROCHIA_URL to target another oneEnvironment
One .env at the repository root (created by npm run setup from .env.example). Everything starts without any
external account: the feed, profiles, followers, contacts, collections, comments, image stories and the seeded videos'
pages work on the local database, files are stored on disk (STORAGE_DRIVER=local), e-mail is logged instead of
sent, and with OROCHIA_DEMO_MODE=true unlocks settle immediately when no gateway is configured. In production every
secret is mandatory (see Deployment).
Video features locally (Bunny Stream)
Uploading and playing videos, video stories and editor drafts need a Bunny Stream library — video never passes through the app. A free trial library is enough:
- Create a Stream library; in API, copy the library id, the API key and the Read-Only key; note the CDN hostname
(
vz-….b-cdn.net). - In Security, turn on CDN token authentication (copy its key) and add
localhostto the allowed domains. - In Encoding, keep Keep original files on (drafts reopen from the original).
- Fill
BUNNY_STREAM_API_KEY,BUNNY_STREAM_LIBRARY_ID,BUNNY_STREAM_HOSTNAME,BUNNY_STREAM_TOKEN_AUTH_KEYandBUNNY_WEBHOOK_SECRET(the Read-Only key), then restartnpm run dev.
Bunny cannot call localhost, and a library has one webhook URL — a library shared by several environments (local
and dev) notifies only one of them. Without the webhook, video stories still go live: the stories rail asks the
Stream API about any video story unsettled for 20 s (reconcileStoryVideos, lib/stories.ts) and settles it as the
webhook would, and the author sees their story as Processing meanwhile. Videos and drafts still wait for the webhook:
to exercise it, expose the app with a tunnel (cloudflared tunnel --url http://localhost:3000, ngrok http 3000) and
set the library's webhook to <tunnel>/api/webhooks/bunny. The in-browser editor (ffmpeg.wasm) needs nothing: its engine is fetched once from a CDN.
E-mail templates
Every e-mail is a template in apps/web/mail-templates/<id>/<locale>/ (subject.txt, body.html, body.txt), inside
the shared _layout/ (rules: AGENTS.md §3.G). Locally they are read from those files (MAIL_TEMPLATES_SOURCE=local)
and the messages go to the log; MAIL_DELIVERY=on with a Resend or Mailgun key really sends them.
npm run mail:templates:preview # apps/web/.mail-preview/index.html — every template, en and fr
npm run mail:templates:push # dry run against Bunny Storage; -- --apply publishes (production credentials)Troubleshooting
| Symptom | Fix |
|---|---|
port 5432 is already allocated | Another PostgreSQL is running: stop it, or change the published port in the dev compose file and DATABASE_URL |
Too many attempts at sign-in | Login attempts are rate-limited per account, in memory: restart npm run dev |
| Pages show empty states | The database is empty — npm run db:seed |
relation … does not exist | Pending migrations — npm run db:status, then npm run db:migrate |
| Docs check fails in CI | npm run docs:generate and commit the regenerated files |
| A page reloads again and again | A stale build cache (often after npm run build): stop the server, rm -rf apps/web/.next, npm run dev |
| Thumbnails or draft clips answer 403 | Bunny's allowed domains do not include localhost (library → Security) |
| Uploaded videos stay "processing" | Bunny's webhook cannot reach localhost — use a tunnel (see Video features locally). Video stories catch up on their own (the rail asks Bunny) |