# O que mudou 1 Schema: failedAttempts + lockedUntil em User; migration auth_v0_2_lockout aplicada; crypto.ts com hashSecret/verifySecret (Node scrypt nativo, zero deps) 2 packages/api/src/auth.ts — authenticateCredential com lockout de 5 tentativas 3 Seed reescrito: admin hashed admin1234, operadores hashed 1111/2222/3333 4 Porta das traseiras fechada: AUTH_DEV_AUTOLOGIN ignorado quando NODE_ENV=production, em ambas as apps 5 operator-pwa: Credentials provider usa PIN + allowedRoles:['OPERATOR']; cookies fieldops-op.* 6 Picker em 2 estados: lista → teclado PIN (botões grandes, dots de progresso, mensagem de erro sem dar pistas) 7 admin-web: Auth.js completo (auth.config, auth.ts, route handler, middleware, /login page, AUTH_SECRET no env) com cookies fieldops-admin.* 8 scripts/auth-smoke.ts (11/11 ✓); .env.example e README atualizados
276 lines
10 KiB
Markdown
276 lines
10 KiB
Markdown
# 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 log-in, navigate to
|
||
http://localhost:3000/select-operator, tap **op1@demo.local**, then
|
||
enter PIN **1111** on the keypad.
|
||
(op2 = **2222**, op3 = **3333**)
|
||
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.
|
||
|
||
> ⚠️ The offline navigation test only works against a **production build** of
|
||
> `operator-pwa`. In `pnpm dev` mode, Next.js generates page chunks on demand,
|
||
> so the service worker has nothing to precache and navigation to
|
||
> `/maintenance/new` fails with `Failed to fetch`. See the
|
||
> [Troubleshooting](#troubleshooting) section for the workaround.
|
||
|
||
### As admin / maintenance supervisor (port 3001)
|
||
|
||
1. Open http://localhost:3001.
|
||
With `AUTH_DEV_AUTOLOGIN=true` you land on the maintenance queue
|
||
automatically. Without it, you see a login form — use
|
||
**admin@demo.local** / **admin1234**.
|
||
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 in dev** | `AUTH_DEV_AUTOLOGIN=true` lets anyone in as admin (dev/test only — ignored when `NODE_ENV=production`). Real auth is implemented: admin uses email + password, operators use list + PIN. | — |
|
||
| **Operator picker + PIN, not TAG/card** | Operator identity is chosen from a list and confirmed with a PIN, 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 for local dev and CI only. It is **ignored at the code level**
|
||
> when `NODE_ENV=production`, so a misconfigured `.env` in production won't
|
||
> open a hole. The chokepoints are `apps/*/lib/auth.ts → resolveUser()` and
|
||
> `apps/*/middleware.ts`.
|
||
|
||
---
|
||
|
||
## 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 |
|
||
| `pnpm tsx scripts/auth-smoke.ts` | Verify hashing, PIN/password login, and lockout |
|
||
|
||
---
|
||
|
||
## 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`).
|
||
|
||
**Offline navigation fails with `Failed to fetch` / `NetworkOnly`** — the PWA
|
||
service worker can only serve pages it has precached, and `pnpm dev` builds
|
||
pages on demand (no precache). To test the full offline flow, run the
|
||
operator PWA in production mode:
|
||
```sh
|
||
# Stop the running `pnpm dev` for operator-pwa first, then:
|
||
pnpm --filter @repo/operator-pwa build
|
||
pnpm --filter @repo/operator-pwa start
|
||
```
|
||
Load http://localhost:3000 once while online (so the SW precaches the shell),
|
||
reload the tab to activate the new SW, then switch DevTools to Offline. The
|
||
admin-web can stay in `dev` mode — only the operator PWA needs the production
|
||
build for offline navigation. The IndexedDB sync queue itself works in both
|
||
modes; only the page-navigation layer requires precache.
|
||
|
||
---
|
||
|
||
## License
|
||
|
||
Internal. All rights reserved.
|