From 50839d081fca136833473dcfce9ed8302e3a9334 Mon Sep 17 00:00:00 2001 From: Pedro Gomes Date: Sat, 16 May 2026 17:10:00 +0100 Subject: [PATCH] MAI CALL - step 14 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- README.md | 295 ++++++++++++++++++++++++++++++++++-------------------- 1 file changed, 185 insertions(+), 110 deletions(-) diff --git a/README.md b/README.md index fdca652..835b547 100644 --- a/README.md +++ b/README.md @@ -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 — 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 1–2 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