commit 33789b13d1e2272b3f515d42b97c6c7a73917558 Author: Pedro Gomes Date: Sat May 16 12:01:26 2026 +0100 gitignore + readme diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b7dac32 --- /dev/null +++ b/.gitignore @@ -0,0 +1,52 @@ +# dependencies +node_modules/ +.pnpm-store/ + +# build output +dist/ +build/ +out/ +.next/ +.turbo/ +*.tsbuildinfo +next-env.d.ts + +# environment +.env +.env.local +.env.*.local +!.env.example + +# logs +*.log +npm-debug.log* +yarn-debug.log* +pnpm-debug.log* +lerna-debug.log* + +# testing +coverage/ +playwright-report/ +playwright/.cache/ +test-results/ +.playwright/ + +# editor +.vscode/ +!.vscode/extensions.json +!.vscode/settings.json.example +.idea/ +*.swp +*.swo + +# OS +.DS_Store +Thumbs.db + +# Prisma +packages/db/prisma/*.db +packages/db/prisma/*.db-journal + +# misc +*.pem +.cache/ diff --git a/README.md b/README.md new file mode 100644 index 0000000..fdca652 --- /dev/null +++ b/README.md @@ -0,0 +1,172 @@ +# FieldOps + +Modular industrial SaaS monorepo. **This is the scaffold phase** — no business +features yet. The goal of this branch is to prove the end-to-end wiring: +client → tRPC → Prisma → Postgres, with multi-tenancy enforced from day zero. + +## What's here + +``` +apps/ + operator-pwa/ Next 15 — operator console (port 3000) + admin-web/ Next 15 — backoffice placeholder (port 3001) +packages/ + db/ Prisma 6 schema + tenant-scoping extension + api/ tRPC v11 routers + ui/ Shared shadcn-style components + domain/ Pure domain logic (empty in this phase) + config/ tsconfig / eslint / tailwind presets +e2e/ Playwright smoke test +``` + +## Prerequisites + +- **Node.js 22+** (use `nvm use` or install matching `.nvmrc`) +- **pnpm 11+** (`corepack enable pnpm` or `npm i -g pnpm`) +- **Docker Desktop** with the engine running (provides Postgres) +- **Git** + +## From-zero setup + +These steps assume a fresh clone with no `node_modules`, no Docker volumes, +no `.env`. Run them in order from the repo root. + +```sh +# 1. environment +cp .env.example .env +# Open .env and set AUTH_DEV_AUTOLOGIN="true" for local development. +# (Default is "false" on purpose — see "Auth" below.) + +# 2. install +pnpm install + +# 3. start Postgres +docker compose up -d + +# 4. apply the schema +pnpm db:migrate + +# 5. seed the demo tenant +pnpm db:seed + +# 6. start the operator app +pnpm --filter @repo/operator-pwa dev +``` + +Open — you should see a **Connected** card with +`Tenant: Demo Factory`. + +### Run the E2E + +In a separate shell (with the dev server NOT running, or with +`reuseExistingServer` left on — Playwright handles either): + +```sh +pnpm --filter @repo/e2e install-browsers # one-time, downloads Chromium +pnpm test:e2e +``` + +The Playwright config force-sets `AUTH_DEV_AUTOLOGIN=true` for the dev server +it launches, so the test does not depend on the developer's `.env`. + +## Auth + +This scaffold has **no real authentication**. Auth.js v5 is wired up with a +single Credentials provider that accepts any email present in the seeded +`User` table — no password check. + +The `AUTH_DEV_AUTOLOGIN` env flag controls a server-side fallback: + +- **`AUTH_DEV_AUTOLOGIN=false`** (default in `.env.example`) — requests with + no Auth.js session are unauthenticated. `protectedProcedure` returns 401. +- **`AUTH_DEV_AUTOLOGIN=true`** — requests with no session silently resolve + to the seeded `admin@demo.local` user, skipping the login UI. + +> ⚠️ **Never set `AUTH_DEV_AUTOLOGIN=true` in production.** The fallback is a +> back door intended only for local dev and CI. The chokepoint is +> `apps/operator-pwa/lib/auth.ts → resolveUser()`. Replace it with real +> authentication before any non-dev deployment. + +## Multi-tenancy + +Every Prisma model except `Tenant` has a `tenantId` column. Tenant scoping is +enforced at runtime by an extension in +`packages/db/src/tenant-extension.ts` — read the header comment in that file +for the full list of operations it covers and the operations it does **not** +(`$queryRaw`, interactive `$transaction` with external clients, etc.). + +Call sites get the scoped client from the tRPC context: `ctx.db.*`. The +unscoped root client (`ctx.prisma`) is available for the rare cross-tenant +case but should be a red flag at PR review. + +## tRPC + +The operator app uses both tRPC paths: + +- **RSC caller** — `apps/operator-pwa/lib/trpc/server.ts` — direct in-process + invocation from Server Components. Default for reads. +- **Client hooks** — `apps/operator-pwa/lib/trpc/client.ts` — + `@trpc/react-query` against `/api/trpc`. Default for mutations and + client-rendered reads. + +The home page exercises both: the `Connected` card is rendered server-side; +the `Client-side ping` card uses `useQuery`. Both must succeed for the +hybrid wiring to be considered green. + +## Common commands + +| Command | What it does | +| --- | --- | +| `pnpm install` | Install all workspace deps | +| `docker compose up -d` | Start Postgres | +| `docker compose down` | Stop Postgres (data persists) | +| `docker compose down -v` | Stop Postgres **and drop the volume** | +| `pnpm dev` | Run all dev tasks via Turbo | +| `pnpm --filter @repo/operator-pwa dev` | Operator app only | +| `pnpm --filter @repo/admin-web dev` | Admin app only (port 3001) | +| `pnpm db:migrate` | Apply pending Prisma migrations | +| `pnpm db:reset` | Drop, recreate, migrate, seed | +| `pnpm db:seed` | Re-seed the demo tenant (idempotent) | +| `pnpm db:studio` | Open Prisma Studio | +| `pnpm typecheck` | Typecheck every package | +| `pnpm test:e2e` | Run Playwright | +| `pnpm format` | Prettier write | + +## Versions of note + +| Package | Version | Why | +| --- | --- | --- | +| Node | 22 LTS | Required by Next 15 | +| pnpm | 11 | Workspace + `allowBuilds` security feature | +| Next.js | 15 | App Router, React 19 | +| React | 19 | | +| Prisma | 6 | Kept on 6.x — Prisma 7 mandates the new `prisma-client` ESM generator, which is a separate migration | +| Auth.js (next-auth) | 5.0 beta | v5 split-config pattern (edge-safe middleware + Node providers) | +| tRPC | 11 | Stable on the v11 series | +| Tailwind | 3 | Conservative choice; v4 swap is a separate spike | +| shadcn/ui | components inlined | Not the CLI scaffold — components live in `packages/ui/src/components` | + +## Troubleshooting + +**`ERR_PNPM_IGNORED_BUILDS` after adding a dep** — pnpm 11 blocks postinstall +scripts unless approved. Edit `pnpm-workspace.yaml` `allowBuilds:` and set +the offending package to `true` (or `false` if you don't want its +postinstall to run). + +**Page loads but says `UNAUTHORIZED`** — your `.env` has +`AUTH_DEV_AUTOLOGIN=false` (the default). Flip it to `true` for local dev, +or sign in through Auth.js. + +**Page errors with `Tenant not found`** — the seeded tenant was wiped. +Run `pnpm db:seed` again. + +**Migration fails with `Environment variable not found: DATABASE_URL`** — +make sure `.env` exists at the repo root and Docker Postgres is running. + +**Playwright complains the port is busy** — kill any leftover dev servers: +on Windows `Get-Process node | Stop-Process -Force`; macOS/Linux +`pkill -f 'next dev'`. + +## License + +Internal. All rights reserved.