A generative-media SaaS. Users sign in and can:
Video and image generation are routed through OpenRouter; face swaps run on a self-hosted FaceFusion service. All generated media and uploaded inputs are stored in an S3-compatible object store (MinIO).
This is a Turborepo monorepo managed with bun.
| Path | Description |
|---|---|
apps/frontend |
React + Vite + TypeScript SPA (Tailwind v4 + shadcn-style UI). |
apps/backend |
TypeScript + Express API. Auth (better-auth), OpenRouter + MinIO services. |
packages/db |
Prisma schema + client. Shared Postgres data layer reused by the backend. |
packages/typescript-config |
Shared tsconfig presets. |
packages/eslint-config |
Shared ESLint config. |
Services (via docker-compose.yml):
infra/facefusion, behind a compose profile).# 1. Configure environment
cp .env.example .env
# (optional) add OPENROUTER_API_KEY and Google OAuth creds when you have them
# 2. Build & start the full stack
bun run docker:up # docker compose up -d --build
# 3. Tail logs
bun run docker:logs
Once up:
/health).env, default minioadmin)Database migrations are applied automatically when the backend container starts.
Stop everything:
bun run docker:down # stop containers
bun run docker:reset # stop AND delete volumes (wipes DB + objects)
Face swapping requires the self-hosted FaceFusion service, which is not started by default (the image is ~5GB and downloads models on first use). Start it with:
bun run docker:facefusion
# = docker compose --profile facefusion up -d --build facefusion
It exposes a small HTTP wrapper (infra/facefusion) around FaceFusion's
headless-run CLI at http://localhost:7865/swap. The backend reaches it via
the FACEFUSION_URL env var.
On first boot the container pre-downloads the FaceFusion models (lite scope,
which includes the face swapper) into the facefusion_data volume, so the models
persist across restarts and the first swap isn't slow. This initial download
takes a few minutes — the service reports healthy (and swaps work) once it
finishes. Subsequent starts skip the download via a marker file.
Run only Postgres + MinIO in Docker, and the apps on your machine with hot reload:
# 1. Start infra
bun run infra:up
# 2. Install deps
bun install
# 3. Configure per-app env
cp packages/db/.env.example packages/db/.env
cp apps/backend/.env.example apps/backend/.env
cp apps/frontend/.env.example apps/frontend/.env
# -> set BETTER_AUTH_SECRET in apps/backend/.env
# 4. Set up the database
bun run db:generate
bun run db:push # sync schema -> Postgres (use db:migrate once you add migrations)
# 5. Run all apps with hot reload
bun run dev
Secrets such as OPENROUTER_API_KEY and the Google OAuth client id/secret are
provided later — until then the app runs, but model listing and video
generation will return errors, and Google sign-in is hidden/disabled. See the
.env.example files:
.env.example (root) — docker-compose variablesapps/backend/.env.exampleapps/frontend/.env.examplepackages/db/.env.exampleFor Google OAuth, set the authorized redirect URI to:
http://localhost:4000/api/auth/callback/google
| Command | Description |
|---|---|
bun run docker:up |
Build & start the full stack in Docker. |
bun run docker:down |
Stop the stack. |
bun run docker:reset |
Stop the stack and delete volumes. |
bun run docker:facefusion |
Build & start the FaceFusion face-swap service. |
bun run infra:up |
Start only Postgres + MinIO. |
bun run dev |
Run all apps locally with hot reload. |
bun run build |
Build all apps & packages. |
bun run check-types |
Type-check the whole monorepo. |
bun run db:migrate |
Run Prisma migrations. |
bun run db:studio |
Open Prisma Studio. |
$ claude mcp add pixovid \
-- python -m otcore.mcp_server <graph>