MAI CALL - step 14

Both services running, seed is idempotent. Passo 14 completo.

O que foi feito:

O README foi completamente reescrito para MAI CALL v0.1. Cobertura:

Quick-start em <15 min — 6 passos ordenados com comentários inline; a partir de git clone, nenhum pré-requisito adicional além do que está documentado
Demo flow — fluxo como operador (picker → novo pedido → offline test) e como admin (queue → claim → resolve)
MinIO — endpoint, console (localhost:9001), credenciais, bucket, convenção de chaves, como fazer backup com mc mirror
Limitações conhecidas v0.1 — tabela com 10 itens: autologin, sem auth real, sem TAG/RFID, sem SLAs, sem push, Safari Background Sync, sem observabilidade, sem i18n
Aviso em destaque — AUTH_DEV_AUTOLOGIN=true é uma back door, nunca usar em produção
Notas de arquitectura — multi-tenancy, offline sync, storage
Troubleshooting actualizado** — inclui MinIO photos not loading
This commit is contained in:
Pedro Gomes 2026-05-16 17:10:00 +01:00
parent 9418b360bc
commit 50839d081f

295
README.md
View File

@ -1,171 +1,246 @@
# FieldOps
# FieldOps — MAI CALL v0.1
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.
Modular industrial SaaS monorepo. **MAI CALL v0.1** is the first shipped
feature: a full maintenance-request loop that runs on factory Wi-Fi (and
survives losing it).
## What's here
```
apps/
operator-pwa/ Next 15 — operator console (port 3000)
admin-web/ Next 15 — backoffice placeholder (port 3001)
operator-pwa/ Next.js 15 — operator mobile console (port 3000)
admin-web/ Next.js 15 — maintenance queue for admin/supervisor (port 3001)
packages/
db/ Prisma 6 schema + tenant-scoping extension
api/ tRPC v11 routers
db/ Prisma 6 schema + multi-tenant extension
api/ tRPC v11 routers (maintenanceRequest, workstation, user, storage)
storage/ ObjectStorage abstraction + MinIO/S3 implementation
ui/ Shared shadcn-style components
domain/ Pure domain logic (empty in this phase)
config/ tsconfig / eslint / tailwind presets
e2e/ Playwright smoke test
domain/ Pure domain logic (reserved for later modules)
config/ TypeScript / ESLint / Tailwind presets
e2e/ Playwright happy-path 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**
| Tool | Version | Notes |
| --- | --- | --- |
| Node.js | 22 LTS | Required by Next.js 15 |
| pnpm | 11+ | `npm i -g pnpm@11` or `corepack enable pnpm` |
| Docker Desktop | any recent | Provides Postgres 16 + MinIO |
| Git | any | — |
## 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.
## Quick-start (< 15 min)
Run these commands from the **repo root** in order.
```sh
# 1. environment
# 1. Copy and configure the environment file
cp .env.example .env
# Open .env and set AUTH_DEV_AUTOLOGIN="true" for local development.
# (Default is "false" on purpose — see "Auth" below.)
# The defaults work out of the box for local dev.
# AUTH_DEV_AUTOLOGIN is already "true" in .env — leave it.
# 2. install
# 2. Install dependencies
pnpm install
# 3. start Postgres
# 3. Start Postgres + MinIO (creates the fieldops bucket automatically)
docker compose up -d
# 4. apply the schema
# 4. Apply the database schema
pnpm db:migrate
# 5. seed the demo tenant
# 5. Seed the demo tenant, 3 operators, and 3 workstations
pnpm db:seed
# 6. start the operator app
pnpm --filter @repo/operator-pwa dev
# 6. Start both apps
pnpm --filter @repo/operator-pwa dev &
pnpm --filter @repo/admin-web dev
```
Open <http://localhost:3000> — you should see a **Connected** card with
`Tenant: Demo Factory`.
| URL | What it is |
| --- | --- |
| http://localhost:3000 | Operator PWA |
| http://localhost:3001 | Admin / Manutenção queue |
| http://localhost:9001 | MinIO console (see credentials below) |
### Run the E2E
---
In a separate shell (with the dev server NOT running, or with
`reuseExistingServer` left on — Playwright handles either):
## Demo flow
### As an operator (port 3000)
1. Open http://localhost:3000.
With `AUTH_DEV_AUTOLOGIN=true` you land on the home page as
`admin@demo.local`. To simulate a real operator, navigate to
http://localhost:3000/select-operator and tap **op1@demo.local**.
2. Tap **Pedir manutenção**.
3. Select a workstation, optionally attach a photo, write a description,
and tap **Enviar pedido**.
4. The page shows **"Pedido enviado"** once the sync completes (usually
within 12 seconds when online).
**Offline test:**
Chrome DevTools → Network → Offline → create 3 requests → Network → Online.
The requests sync automatically within ~10 s; "Tudo sincronizado" appears.
### As admin / maintenance supervisor (port 3001)
1. Open http://localhost:3001 — it lands on the maintenance queue.
2. The queue refreshes every 5 s; new requests appear automatically.
3. Click **Aceitar** to claim a request (status: Em curso).
4. Click **Marcar resolvido**, optionally add a note, click **Confirmar**
(status: Resolvido).
5. The document tab title shows `(N) FieldOps — Manutenção` when there are
open requests.
---
## MinIO (photo storage)
| Setting | Value |
| --- | --- |
| API endpoint | http://localhost:9000 |
| Web console | http://localhost:9001 |
| Root user | `fieldops` |
| Root password | `fieldops123` |
| Bucket | `fieldops` |
Photos are stored as presigned-URL objects under
`tenants/{tenantId}/maintenance/{uuid}.jpg`.
To back up locally:
```sh
# Install mc (MinIO client) then:
mc alias set local http://localhost:9000 fieldops fieldops123
mc mirror local/fieldops ./backup-photos
```
---
## Running the E2E tests
```sh
pnpm --filter @repo/e2e install-browsers # one-time, downloads Chromium
# One-time browser install (downloads ~170 MB of Chromium)
pnpm --filter @repo/e2e install-browsers
# Run the happy-path test (starts both dev servers automatically)
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`.
The Playwright config force-sets `AUTH_DEV_AUTOLOGIN=true` for the child
servers, so the test does not depend on the developer's `.env`.
Expected: **1 passed** in ~30 s.
## 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.
## Known limitations — v0.1 (demo only)
The `AUTH_DEV_AUTOLOGIN` env flag controls a server-side fallback:
| Limitation | Detail | Target |
| --- | --- | --- |
| **No real authentication** | `AUTH_DEV_AUTOLOGIN=true` lets anyone in as admin. The Credentials provider accepts any seeded email without a password. | v0.2 pre-pilot |
| **Operator picker, not TAG/card** | Operator identity is chosen from a list rather than read from an RFID badge. | MY QUALITY module |
| **No multi-tenant onboarding UI** | Tenants are created via `pnpm db:seed` / SQL only. | when 2nd customer onboards |
| **No SLAs, alerts, or timers** | `DomainEvent` rows are written and ready; reporting is not built yet. | v0.2 |
| **Single photo per request** | No video, audio, or multiple photos. | when pilot asks |
| **Safari / iOS Background Sync** | Background Sync API is not supported on Safari; sync falls back to main-thread polling every 10 s when the tab is open. | acceptable for pilot |
| **No push notifications** | Polling at 5 s on the admin-web tab is the notification mechanism. | if pilot requires it |
| **Dev-only storage** | MinIO runs in Docker. No backup cron, no lifecycle policy, no cloud migration yet. | before pilot |
| **No i18n** | Hardcoded Portuguese (Mangualde plant). | v0.2 with pilot |
| **No observability** | Structured logs via Pino; no trace/metric pipeline. | when pilot requires it |
- **`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 deploy with `AUTH_DEV_AUTOLOGIN=true`.** That flag is a
> back door. The chokepoint is `apps/operator-pwa/lib/auth.ts →
> resolveUser()` (and the equivalent in `apps/admin-web/lib/auth.ts`).
> Replace with real authentication before any non-dev deployment.
> ⚠️ **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) |
| `docker compose up -d` | Start Postgres + MinIO (detached) |
| `docker compose down` | Stop services (data persists in volumes) |
| `docker compose down -v` | Stop and **delete all data** |
| `pnpm --filter @repo/operator-pwa dev` | Operator PWA only (port 3000) |
| `pnpm --filter @repo/admin-web dev` | Admin web only (port 3001) |
| `pnpm db:migrate` | Apply pending Prisma migrations |
| `pnpm db:seed` | Re-seed demo data (idempotent, safe to re-run) |
| `pnpm db:reset` | Drop, recreate, migrate, seed |
| `pnpm db:seed` | Re-seed the demo tenant (idempotent) |
| `pnpm db:studio` | Open Prisma Studio |
| `pnpm db:studio` | Open Prisma Studio at http://localhost:5555 |
| `pnpm typecheck` | Typecheck every package |
| `pnpm test:e2e` | Run Playwright |
| `pnpm format` | Prettier write |
| `pnpm test:e2e` | Run the happy-path Playwright test |
| `pnpm format` | Prettier write across the workspace |
| `pnpm tsx scripts/storage-smoke.ts` | Verify MinIO presigned upload/download |
| `pnpm tsx scripts/maintenance-smoke.ts` | Verify the full create→claim→resolve cycle |
## 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` |
## Architecture notes
### Multi-tenancy
Every Prisma model except `Tenant` carries a `tenantId` column. Tenant
scoping is enforced at the Prisma layer via an extension in
`packages/db/src/tenant-extension.ts`. Application code always goes
through `ctx.db.*` (scoped) — the unscoped `ctx.prisma` is a code-review
red flag.
**Note on `$transaction`:** The interactive-callback form of `$transaction`
receives a Prisma client that does NOT support `$extends`. Tenant scoping
inside transactions is done by manually injecting `tenantId` into each
`where`/`data` clause. See `packages/api/src/routers/maintenance-request.ts`
for the pattern.
### Offline sync
The operator PWA stores pending requests in IndexedDB (Dexie 4). The sync
loop (`lib/queue/sync.ts`) runs in the main thread — triggered by the
`online` event, `visibilitychange`, and a 10 s polling interval. The
Background Sync API is registered opportunistically (works in Chrome,
ignored elsewhere). Communication between the sync loop and UI is via
`BroadcastChannel('mai-call-sync')`.
### Storage
Photos are uploaded directly from the browser to MinIO via S3 presigned
PUT URLs. The tRPC layer signs the URL; the browser performs the PUT. The
object key is returned and stored in the `MaintenanceRequest` row.
`packages/storage` implements the `ObjectStorage` interface with MinIO (via
AWS SDK v3 S3 protocol) and is portable to AWS S3, Cloudflare R2, or Wasabi
by changing the endpoint env var.
---
## 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).
**`UNAUTHORIZED` on every page** — `AUTH_DEV_AUTOLOGIN` is `false` in your
`.env`. Set it to `true` for local dev, or sign in via the operator picker.
**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.
**`Tenant not found`** — the seed was wiped. Run `pnpm db:seed`.
**Page errors with `Tenant not found`** — the seeded tenant was wiped.
Run `pnpm db:seed` again.
**`DATABASE_URL not found`** — `.env` is missing or Docker Postgres is not
running. Run `docker compose up -d` then retry.
**Migration fails with `Environment variable not found: DATABASE_URL`** —
make sure `.env` exists at the repo root and Docker Postgres is running.
**`ERR_PNPM_IGNORED_BUILDS` after adding a package** — pnpm 11 blocks
postinstall scripts by default. Add the package to `pnpm-workspace.yaml`
`allowBuilds:`.
**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'`.
**Playwright: port already in use** — kill leftover dev servers:
```sh
# Windows
Get-Process node | Stop-Process -Force
# macOS / Linux
pkill -f 'next dev'
```
**MinIO photos not loading in admin-web** — verify MinIO is running
(`docker compose ps`) and that the `fieldops` bucket exists
(`docker logs fieldops-minio-init-1`).
---
## License