FieldOps/README.md
2026-05-16 12:01:26 +01:00

173 lines
6.1 KiB
Markdown

# 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 <http://localhost:3000> — 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.