diff --git a/README.md b/README.md index 4b4677b..c169a17 100644 --- a/README.md +++ b/README.md @@ -1,41 +1,192 @@ -# Vector 2.0 +# Vector -Hardware parts inventory — monorepo. +Hardware parts inventory system. Tracks serialized parts across sites → rooms → bins, with a full audit trail, repair/RMA workflow, tag-based organization, manufacturer EOL tracking, and signed webhook delivery for external integrations. -## Layout +Vector 2.0 is a ground-up TypeScript rewrite of the original JavaScript codebase, delivered as a pnpm + Turbo monorepo with shadcn/ui on the frontend and a service-layered Express API on the backend. + +--- + +## Architecture ``` -apps/ - web/ # React + Vite client - api/ # Express + Prisma API -packages/ - db/ # Prisma schema + client (placeholder) - shared/ # Shared zod schemas + types (placeholder) - ui/ # Design system + shadcn primitives (placeholder) - config/ # Shared eslint / tsconfig / tailwind (placeholder) +vector/ +├── apps/ +│ ├── api/ Express 5 + Prisma + zod. Controllers → services → tx. +│ ├── web/ React 19 + Vite + TanStack Query/Table + shadcn/ui. +│ └── e2e/ Playwright smoke tests (login, parts, repairs, admin). +├── packages/ +│ ├── db/ Prisma schema, migrations, seed, singleton client. +│ ├── shared/ zod schemas + DTOs — single source of truth for the API contract. +│ ├── ui/ shadcn primitives + Vector design tokens. +│ └── config/ Shared tsconfig presets + Tailwind preset. +├── .gitea/workflows/ CI (lint · typecheck · test · build) and gated E2E job. +├── docker-compose.yml Postgres + Redis for local development. +└── turbo.json Pipeline: `build`, `test`, `typecheck`, `lint`. ``` -## Prereqs +The API is split along a strict controller → service → transaction boundary. Every service function takes `(tx, input, actor)` so controllers can compose multiple services inside a single `prisma.$transaction`, guaranteeing that part mutations and their `PartEvent` audit rows are atomic. -- Node >= 20 -- pnpm (via `npm i -g pnpm` or corepack) -- Docker (for Postgres + Redis in later phases — current apps still use SQLite) +--- -## Quick start +## Tech stack + +| Layer | Choice | +| -------------- | ---------------------------------------------------------------------- | +| Language | TypeScript strict mode across all workspaces | +| API | Express 5, Prisma 5, zod, JWT + refresh-token rotation, CSRF, helmet | +| Web | React 19, Vite, TanStack Query, TanStack Table, react-hook-form, nuqs | +| UI | shadcn/ui on Radix primitives, Tailwind v4, Sonner toasts, Recharts | +| Database | SQLite for dev (Postgres cutover prepared — schema is portable) | +| Observability | pino (JSON logs in prod, pretty in dev) + request-scoped `requestId` | +| Testing | Vitest (unit, coverage gate on services/lib), Playwright (E2E) | +| Monorepo tools | pnpm workspaces + Turborepo | +| CI | Gitea Actions + Renovate (self-hosted) | + +--- + +## Getting started + +### Prerequisites + +- Node.js ≥ 20 +- pnpm ≥ 10 (`corepack enable` or `npm i -g pnpm`) +- Docker (optional; required for Postgres + Redis) + +### Install ```bash pnpm install -pnpm dev # runs apps/web and apps/api concurrently via Turbo +pnpm -C packages/db exec prisma generate ``` -The API listens on `http://localhost:3001`; the web app proxies `/api` to it and serves on `http://localhost:5173`. +### Environment -## Phase status +Copy the API env template and generate a JWT secret: -**Phase 0 — Monorepo foundation** ✅ -- pnpm workspaces + Turbo -- `apps/web` and `apps/api` scaffolded -- `packages/*` placeholders -- `docker-compose.yml` for Postgres + Redis +```bash +cp apps/api/.env.example apps/api/.env +node -e "console.log(require('crypto').randomBytes(48).toString('hex'))" # paste into JWT_SECRET +``` -Later phases: TypeScript + Postgres migration, API refactor, schema extensions, shadcn redesign, feature slices, observability. +The default `DATABASE_URL` points at a local SQLite file. To use the containerized Postgres instead, start Docker and update the URL: + +```bash +docker compose up -d postgres redis +# then edit apps/api/.env: +# DATABASE_URL=postgresql://vector:vector@localhost:5432/vector +``` + +### Database + +```bash +pnpm -C packages/db exec prisma migrate dev # apply migrations +pnpm -C packages/db run db:seed # create default admin:admin user +``` + +### Run + +```bash +pnpm dev +``` + +- API: +- Web: (proxies `/api` → API) +- Default login: `admin` / `admin` — **change immediately.** + +--- + +## Common tasks + +All tasks run at the workspace root; Turbo fans them out to the right packages with caching. + +| Command | What it does | +| --------------------------------- | --------------------------------------------------------------- | +| `pnpm dev` | Run `apps/api` and `apps/web` concurrently | +| `pnpm build` | Build every package and app | +| `pnpm typecheck` | `tsc --noEmit` across every workspace | +| `pnpm lint` | ESLint across every workspace | +| `pnpm test` | Run all Vitest unit test suites | +| `pnpm -C apps/api test:coverage` | Unit tests + v8 coverage report (gate: 60% on services/lib) | +| `pnpm -C apps/e2e test` | Run Playwright smoke tests (requires stack running + creds) | +| `pnpm -C packages/db run db:studio` | Open Prisma Studio against the current database | +| `pnpm -C packages/db run db:reset` | Drop schema, re-migrate, re-seed | + +--- + +## Testing + +**Unit tests** live next to the code they cover (`*.test.ts`). Coverage is enforced on `apps/api/src/{services,lib}/**`. + +- `packages/shared` — zod schema contracts (parts, webhooks, auth). +- `apps/api` — pure helpers (`signBody`, `csvCell`, `http-error`), plus the analytics aggregator tested with an in-memory `Tx` double. + +**End-to-end tests** in `apps/e2e/` run against a live stack. Each spec skips itself unless `TEST_USERNAME` and `TEST_PASSWORD` are present in the environment: + +```bash +pnpm dev # in one terminal +TEST_USERNAME=admin TEST_PASSWORD=admin \ + pnpm -C apps/e2e exec playwright install --with-deps chromium +TEST_USERNAME=admin TEST_PASSWORD=admin \ + pnpm -C apps/e2e test +``` + +Reports land in `apps/e2e/playwright-report/`. + +--- + +## Continuous integration + +CI runs on Gitea Actions — [.gitea/workflows/ci.yaml](.gitea/workflows/ci.yaml). Every push and pull request executes: + +1. `pnpm install --frozen-lockfile` +2. `prisma generate` +3. `pnpm lint` +4. `pnpm typecheck` +5. `pnpm -C packages/shared test` + `pnpm -C apps/api test:coverage` +6. `pnpm build` +7. API coverage uploaded as an artifact. + +A second Playwright job is gated behind the repository variable `ENABLE_E2E=true` and requires these secrets: + +| Secret | Purpose | +| ----------------- | --------------------------------------- | +| `E2E_BASE_URL` | URL of a running Vector stack | +| `E2E_USERNAME` | Test admin username | +| `E2E_PASSWORD` | Test admin password | + +Dependency updates come from a self-hosted Renovate instance configured via [renovate.json](renovate.json) — grouped minor/patch PRs weekly, auto-merge for dev-dep patches, `prisma` + `@prisma/client` grouped together, Radix/shadcn held for manual review. + +--- + +## Conventions + +- **API shape**: all responses follow `{ code, message, requestId, details? }` for errors. Paginated lists use `{ data, page, pageSize, total }`. +- **Validation**: every request body and query string is parsed through a zod schema from `@vector/shared` before it reaches a controller. No ad-hoc validation inside controllers. +- **Query keys**: the web app uses a hierarchical factory at [apps/web/src/lib/queryKeys.ts](apps/web/src/lib/queryKeys.ts). Invalidate by domain (`queryKeys.parts.all`) or by filter (`queryKeys.parts.list(filters)`). +- **Commits**: [Conventional Commits](https://www.conventionalcommits.org/) — Renovate already expects them. +- **Webhooks**: every delivery is signed with HMAC-SHA256 over `${timestamp}.${body}` and sent with headers `x-vector-signature`, `x-vector-timestamp`, `x-vector-event`, and the recursion-guard `x-vector-webhook: v1`. + +--- + +## Roadmap + +All nine phases of the 2.0 rewrite are in-tree: + +| Phase | Scope | +| ----- | ------------------------------------------------------------------------ | +| 0 | Monorepo foundation (pnpm + Turbo, apps scaffolded) | +| 1 | Strict TypeScript, Prisma schema, env validation, singleton client | +| 2 | Service-layered API, transactional mutations, auth hardening, pagination | +| 3 | Schema extensions (hosts, repairs, tags, categories, webhooks, FTS-ready) | +| 4 | shadcn/ui design system + `DataTable`, Form pattern, query-keys factory| +| 5 | Page-by-page rewrite (Parts, Locations split, Manufacturers, Users) | +| 6 | Feature slice — Repair/RMA, Tags, Bulk ops, Saved views | +| 7 | Analytics dashboard (Recharts) + EOL + Webhooks + streaming CSV export | +| 8 | Vitest + Playwright + Gitea Actions CI + Renovate | + +### Deferred follow-ups + +- **Postgres cutover.** Schema is already portable; the data-migration script lives at `packages/db/POSTGRES_FTS.md`. +- **BullMQ worker.** Replace the in-process webhook emitter in [apps/api/src/lib/webhook-emitter.ts](apps/api/src/lib/webhook-emitter.ts) with a Redis-backed worker. Signature is stable — one-line swap. +- **PDF audit export** via `@react-pdf/renderer` in the worker. +- **CSV import wizard UI** to pair with the existing `CsvImportJob` staging table.