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 Modular industrial SaaS monorepo. **MAI CALL v0.1** is the first shipped
features yet. The goal of this branch is to prove the end-to-end wiring: feature: a full maintenance-request loop that runs on factory Wi-Fi (and
client → tRPC → Prisma → Postgres, with multi-tenancy enforced from day zero. survives losing it).
## What's here ## What's here
``` ```
apps/ apps/
operator-pwa/ Next 15 — operator console (port 3000) operator-pwa/ Next.js 15 — operator mobile console (port 3000)
admin-web/ Next 15 — backoffice placeholder (port 3001) admin-web/ Next.js 15 — maintenance queue for admin/supervisor (port 3001)
packages/ packages/
db/ Prisma 6 schema + tenant-scoping extension db/ Prisma 6 schema + multi-tenant extension
api/ tRPC v11 routers api/ tRPC v11 routers (maintenanceRequest, workstation, user, storage)
storage/ ObjectStorage abstraction + MinIO/S3 implementation
ui/ Shared shadcn-style components ui/ Shared shadcn-style components
domain/ Pure domain logic (empty in this phase) domain/ Pure domain logic (reserved for later modules)
config/ tsconfig / eslint / tailwind presets config/ TypeScript / ESLint / Tailwind presets
e2e/ Playwright smoke test e2e/ Playwright happy-path test
``` ```
## Prerequisites ## Prerequisites
- **Node.js 22+** (use `nvm use` or install matching `.nvmrc`) | Tool | Version | Notes |
- **pnpm 11+** (`corepack enable pnpm` or `npm i -g pnpm`) | --- | --- | --- |
- **Docker Desktop** with the engine running (provides Postgres) | Node.js | 22 LTS | Required by Next.js 15 |
- **Git** | 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, ## Quick-start (< 15 min)
no `.env`. Run them in order from the repo root.
Run these commands from the **repo root** in order.
```sh ```sh
# 1. environment # 1. Copy and configure the environment file
cp .env.example .env cp .env.example .env
# Open .env and set AUTH_DEV_AUTOLOGIN="true" for local development. # The defaults work out of the box for local dev.
# (Default is "false" on purpose — see "Auth" below.) # AUTH_DEV_AUTOLOGIN is already "true" in .env — leave it.
# 2. install # 2. Install dependencies
pnpm install pnpm install
# 3. start Postgres # 3. Start Postgres + MinIO (creates the fieldops bucket automatically)
docker compose up -d docker compose up -d
# 4. apply the schema # 4. Apply the database schema
pnpm db:migrate pnpm db:migrate
# 5. seed the demo tenant # 5. Seed the demo tenant, 3 operators, and 3 workstations
pnpm db:seed pnpm db:seed
# 6. start the operator app # 6. Start both apps
pnpm --filter @repo/operator-pwa dev pnpm --filter @repo/operator-pwa dev &
pnpm --filter @repo/admin-web dev
``` ```
Open <http://localhost:3000> — you should see a **Connected** card with | URL | What it is |
`Tenant: Demo Factory`. | --- | --- |
| 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 ## Demo flow
`reuseExistingServer` left on — Playwright handles either):
### 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 ```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 pnpm test:e2e
``` ```
The Playwright config force-sets `AUTH_DEV_AUTOLOGIN=true` for the dev server The Playwright config force-sets `AUTH_DEV_AUTOLOGIN=true` for the child
it launches, so the test does not depend on the developer's `.env`. 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 ## Known limitations — v0.1 (demo only)
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: | 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 > ⚠️ **NEVER deploy with `AUTH_DEV_AUTOLOGIN=true`.** That flag is a
no Auth.js session are unauthenticated. `protectedProcedure` returns 401. > back door. The chokepoint is `apps/operator-pwa/lib/auth.ts →
- **`AUTH_DEV_AUTOLOGIN=true`** — requests with no session silently resolve > resolveUser()` (and the equivalent in `apps/admin-web/lib/auth.ts`).
to the seeded `admin@demo.local` user, skipping the login UI. > 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 ## Common commands
| Command | What it does | | Command | What it does |
| --- | --- | | --- | --- |
| `pnpm install` | Install all workspace deps | | `pnpm install` | Install all workspace deps |
| `docker compose up -d` | Start Postgres | | `docker compose up -d` | Start Postgres + MinIO (detached) |
| `docker compose down` | Stop Postgres (data persists) | | `docker compose down` | Stop services (data persists in volumes) |
| `docker compose down -v` | Stop Postgres **and drop the volume** | | `docker compose down -v` | Stop and **delete all data** |
| `pnpm dev` | Run all dev tasks via Turbo | | `pnpm --filter @repo/operator-pwa dev` | Operator PWA only (port 3000) |
| `pnpm --filter @repo/operator-pwa dev` | Operator app only | | `pnpm --filter @repo/admin-web dev` | Admin web only (port 3001) |
| `pnpm --filter @repo/admin-web dev` | Admin app only (port 3001) |
| `pnpm db:migrate` | Apply pending Prisma migrations | | `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:reset` | Drop, recreate, migrate, seed |
| `pnpm db:seed` | Re-seed the demo tenant (idempotent) | | `pnpm db:studio` | Open Prisma Studio at http://localhost:5555 |
| `pnpm db:studio` | Open Prisma Studio |
| `pnpm typecheck` | Typecheck every package | | `pnpm typecheck` | Typecheck every package |
| `pnpm test:e2e` | Run Playwright | | `pnpm test:e2e` | Run the happy-path Playwright test |
| `pnpm format` | Prettier write | | `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 | ## Architecture notes
| --- | --- | --- |
| Node | 22 LTS | Required by Next 15 | ### Multi-tenancy
| pnpm | 11 | Workspace + `allowBuilds` security feature |
| Next.js | 15 | App Router, React 19 | Every Prisma model except `Tenant` carries a `tenantId` column. Tenant
| React | 19 | | scoping is enforced at the Prisma layer via an extension in
| Prisma | 6 | Kept on 6.x — Prisma 7 mandates the new `prisma-client` ESM generator, which is a separate migration | `packages/db/src/tenant-extension.ts`. Application code always goes
| Auth.js (next-auth) | 5.0 beta | v5 split-config pattern (edge-safe middleware + Node providers) | through `ctx.db.*` (scoped) — the unscoped `ctx.prisma` is a code-review
| tRPC | 11 | Stable on the v11 series | red flag.
| 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` | **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 ## Troubleshooting
**`ERR_PNPM_IGNORED_BUILDS` after adding a dep** — pnpm 11 blocks postinstall **`UNAUTHORIZED` on every page** — `AUTH_DEV_AUTOLOGIN` is `false` in your
scripts unless approved. Edit `pnpm-workspace.yaml` `allowBuilds:` and set `.env`. Set it to `true` for local dev, or sign in via the operator picker.
the offending package to `true` (or `false` if you don't want its
postinstall to run).
**Page loads but says `UNAUTHORIZED`** — your `.env` has **`Tenant not found`** — the seed was wiped. Run `pnpm db:seed`.
`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. **`DATABASE_URL not found`** — `.env` is missing or Docker Postgres is not
Run `pnpm db:seed` again. running. Run `docker compose up -d` then retry.
**Migration fails with `Environment variable not found: DATABASE_URL`** — **`ERR_PNPM_IGNORED_BUILDS` after adding a package** — pnpm 11 blocks
make sure `.env` exists at the repo root and Docker Postgres is running. postinstall scripts by default. Add the package to `pnpm-workspace.yaml`
`allowBuilds:`.
**Playwright complains the port is busy** — kill any leftover dev servers: **Playwright: port already in use** — kill leftover dev servers:
on Windows `Get-Process node | Stop-Process -Force`; macOS/Linux ```sh
`pkill -f 'next dev'`. # 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 ## License