Contributing
This guide gets a developer from a fresh clone to a running local stack in < 10 minutes, then sketches the architecture so you can land a useful PR.
Repository layout
octopuslab/
├─ apps/
│ ├─ web/ ← Next.js 16 (App Router, RSC, next-intl)
│ └─ api/ ← NestJS 11 (CJS), Drizzle ORM, postgres.js
├─ packages/
│ ├─ db/ ← Drizzle schema, shared types
│ ├─ ui/ ← design tokens, primitive components
│ ├─ config/ ← shared TS / ESLint / Prettier
│ └─ db/sql/ ← hand-written SQL (RLS, GRANTs) Drizzle can't emit
└─ pnpm-workspace.yamlPrerequisites
- Node 22 LTS.
nvm use 22if you have nvm. - pnpm 9+.
corepack enableto pick up the version pinned inpackage.json. - A Supabase project (free is fine). You'll need the DB connection string, the project URL, and the service-role key.
- A Railway account if you want to deploy the API end-to-end; for local dev you can run the API directly with
pnpm dev.
First-time setup
git clone git@github.com:jarwiscode/OctopusLab.git
cd OctopusLab
pnpm installCopy .env.example to .env.local in both apps/web and apps/api and fill in:
DATABASE_URL— Supabase pooler URL (not the direct connection; our Drizzle config expects the pooler).SUPABASE_URL,SUPABASE_SERVICE_ROLE_KEY,NEXT_PUBLIC_SUPABASE_URL,NEXT_PUBLIC_SUPABASE_ANON_KEY.OPENROUTER_API_KEYif you want the AI to actually run; otherwise the chat endpoint returns 503 cleanly.BYOK_ENCRYPTION_KEY— 32 raw bytes base64-encoded (openssl rand -base64 32).
Migrations
Schema lives in packages/db/src/schema/. Drizzle Kit handles the regular flow:
# add / modify a table in packages/db/src/schema/*.ts, then:
pnpm --filter @octopuslab/db drizzle-kit generate
pnpm --filter @octopuslab/db drizzle-kit pushFor anything Drizzle can't emit — RLS toggles, GRANT / REVOKE, check constraints, triggers — append to packages/db/sql/0001_enable_rls.sql or a new 000X_…sql and apply via the Supabase MCP or dashboard. See packages/db/sql/README.md for the rationale.
Run the stack
# in two terminals:
pnpm --filter @octopuslab/api dev
pnpm --filter @octopuslab/web devWeb on localhost:3000, API on localhost:8080. The web is wired to the local API when NEXT_PUBLIC_API_URL=http://localhost:8080.
Architecture
- Web — Next.js 16 App Router. Server Components for marketing + shells; Client Components everywhere there's state. next-intl drives the EN/RU locale switcher with
localePrefix: 'as-needed'. TanStack Query is the only data-fetching layer in the client (no SWR, no axios). - API — NestJS 11 with Zod env validation. All persistence goes through Drizzle queries against the Supabase Postgres pooler. Auth: Supabase JWT for dashboard routes, an HMAC-checked API key for
/api/v1/public/*. - RLS — every table in
publichas RLS enabled but no policies (deny-all). The API usesSERVICE_ROLE_KEYwhich bypasses RLS. If you add a table, append it topackages/db/sql/0001_enable_rls.sqland re-apply. - AI — OpenRouter streaming SSE with tool-calling loop. Project files live in a per-project Vercel Sandbox microVM.
- Payments — xRocket Pay + NOWPayments webhooks land at
POST /api/v1/billing/webhook/…/nowpayments-webhookwith HMAC verification on raw body.
Style + checks
pnpm typecheck # all workspaces
pnpm lint # ESLint flat config
pnpm test # vitest where applicableTS strict mode is on. We don't accept PRs that disable rules without a comment explaining why. New code follows the existing patterns: server-side Zod for input, Drizzle query builder (no raw SQL except in packages/db/sql/), small focused commits, conventional commit messages.
Deploy targets
- web → Vercel.
mainauto-deploys. PR previews via the GitHub integration. - api → Railway. Service
@octopuslab/api, Dockerfile atapps/api/Dockerfile. Healthcheck on/api/v1/health. - db → Supabase. Migrations applied via the Supabase MCP or
supabase db push.
Issues
Repo: github.com/jarwiscode/OctopusLab. Open an issue for bugs; for features sketch the change in a discussion first so we can agree on the shape before you write code.