# FieldOps — MAI CALL v0.1 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.js 15 — operator mobile console (port 3000) admin-web/ Next.js 15 — maintenance queue for admin/supervisor (port 3001) packages/ 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 (reserved for later modules) config/ TypeScript / ESLint / Tailwind presets e2e/ Playwright happy-path test ``` ## Prerequisites | 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 | — | --- ## Quick-start (< 15 min) Run these commands from the **repo root** in order. ```sh # 1. Copy and configure the environment file cp .env.example .env # The defaults work out of the box for local dev. # AUTH_DEV_AUTOLOGIN is already "true" in .env — leave it. # 2. Install dependencies pnpm install # 3. Start Postgres + MinIO (creates the fieldops bucket automatically) docker compose up -d # 4. Apply the database schema pnpm db:migrate # 5. Seed the demo tenant, 3 operators, and 3 workstations pnpm db:seed # 6. Start both apps pnpm --filter @repo/operator-pwa dev & pnpm --filter @repo/admin-web dev ``` | URL | What it is | | --- | --- | | http://localhost:3000 | Operator PWA | | http://localhost:3001 | Admin / Manutenção queue | | http://localhost:9001 | MinIO console (see credentials below) | --- ## 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 # 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 child servers, so the test does not depend on the developer's `.env`. Expected: **1 passed** in ~30 s. --- ## Known limitations — v0.1 (demo only) | 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 | > ⚠️ **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. --- ## Common commands | Command | What it does | | --- | --- | | `pnpm install` | Install all workspace deps | | `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:studio` | Open Prisma Studio at http://localhost:5555 | | `pnpm typecheck` | Typecheck every package | | `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 | --- ## 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 **`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. **`Tenant not found`** — the seed was wiped. Run `pnpm db:seed`. **`DATABASE_URL not found`** — `.env` is missing or Docker Postgres is not running. Run `docker compose up -d` then retry. **`ERR_PNPM_IGNORED_BUILDS` after adding a package** — pnpm 11 blocks postinstall scripts by default. Add the package to `pnpm-workspace.yaml` `allowBuilds:`. **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 Internal. All rights reserved.