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:
parent
9418b360bc
commit
50839d081f
295
README.md
295
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 <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 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
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user