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
|
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 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
|
```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
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user