From d739411510efcc550568dc115170c0efa6433532 Mon Sep 17 00:00:00 2001 From: josh Date: Fri, 17 Apr 2026 16:34:51 -0400 Subject: [PATCH] docs: rewrite README for current feature set Lead with a user-facing summary, group features by domain (inventory, field workflow, operations dashboard, integrations), add the docker-compose deployment path, and drop the stale 2.0 phase roadmap and deferred-followups lists. Co-Authored-By: Claude Opus 4.7 --- README.md | 251 +++++++++++++++++++++++++++++------------------------- 1 file changed, 137 insertions(+), 114 deletions(-) diff --git a/README.md b/README.md index 4cd4bc4..daefe2b 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,50 @@ # Vector -Hardware parts inventory system. Tracks serialized parts across sites → rooms → bins and hosts (with externally-driven state/stack lifecycle), with a full audit trail, repair/RMA workflow, per-tech custody for broken-part holds and pre-staged spares, tag-based organization, category-per-model taxonomy, manufacturer EOL tracking, and signed webhook delivery for external integrations. +Serialized hardware inventory for engineering fleets. Track every part from +receiving → bin → host → repair → destruction, with a complete per-part audit +trail, field-maintenance ticketing, per-technician custody, and manufacturer +EOL visibility. -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. +Vector is a pnpm + Turborepo monorepo written in strict TypeScript end to end: +an Express/Prisma API, a React/Vite web client, shared zod contracts, and a +shadcn/ui design system. + +--- + +## What it does + +**Inventory** + +- Serialized `Part`s live under `Site → Room → Bin` locations or on a `Host`. +- Every state change writes a `PartEvent` in the same transaction — nothing + mutates silently. +- `PartModel`s are grouped by `Manufacturer` and `Category`, with optional EOL + dates that feed dashboard risk widgets. +- `Tag`s and `SavedView`s for cross-cutting organization and reusable filters. + +**Field workflow** + +- `FM` tickets track active issues per host, with attached repairs and close + times. +- `Repair` logs pair a broken serial with its replacement and auto-ingest parts + from a tech's input when the broken serial isn't yet in Vector. +- Per-technician **custody** tracks parts held for repair, pending drop-off, or + pending destruction — so broken or pre-staged parts never go missing between + events. + +**Operations dashboard** + +- Universal KPIs: total parts, total spent, deployed value, open FMs, + past-EOL deployments, upcoming-EOL deployments (≤180 d). +- Admin-only: 7/30-day repair tempo, FMs opened, average FM close time, repairs + trend chart, open-FMs-by-host, and a custody backlog by user. + +**Integrations** + +- Outbound webhooks signed with `HMAC-SHA256(timestamp + "." + body)`, headers + `x-vector-signature`, `x-vector-timestamp`, `x-vector-event`, plus a + `x-vector-webhook: v1` recursion guard. +- Streaming CSV export of the audit log for admins. --- @@ -11,20 +53,24 @@ Vector 2.0 is a ground-up TypeScript rewrite of the original JavaScript codebase ``` 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). +│ ├── 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`. +│ ├── db/ Prisma schema, migrations, seed, singleton client. +│ ├── shared/ zod schemas + DTOs — the source of truth for the API. +│ ├── ui/ shadcn primitives + Vector design tokens. +│ └── config/ Shared tsconfig + Tailwind tokens. +├── .gitea/workflows/ci.yaml lint · typecheck · test · build, gated E2E job. +├── docker-compose.yml api + web + redis (production-style stack). +└── turbo.json dev · build · test · typecheck · lint. ``` -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. +The API is strict about its boundaries. Every service takes `(tx, input, actor)` +so controllers can compose multiple services inside a single +`prisma.$transaction` — part mutations and their audit rows are always atomic +or not at all. Controllers do no validation and no business logic; they parse +through a zod schema and dispatch. --- @@ -32,100 +78,100 @@ The API is split along a strict controller → service → transaction boundary. | Layer | Choice | | -------------- | ---------------------------------------------------------------------- | -| Language | TypeScript strict mode across all workspaces | +| Language | TypeScript strict mode across every workspace | | 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) | +| Database | SQLite (single-file, shipped in prod via Docker volume) | +| Observability | pino (JSON in prod, pretty in dev) with per-request `requestId` | +| Testing | Vitest (unit), Playwright (E2E) | +| Monorepo | pnpm workspaces + Turborepo | +| CI / deps | Gitea Actions + self-hosted Renovate | + +The schema is intentionally Postgres-portable; SQLite is the default because +Vector is deployed as a single-container stack per site. --- -## Getting started +## Quick start -### Prerequisites - -- Node.js ≥ 20 -- pnpm ≥ 10 (`corepack enable` or `npm i -g pnpm`) -- Docker (optional; required for Postgres + Redis) - -### Install +Prerequisites: **Node 20+**, **pnpm 10+** (`corepack enable`). ```bash pnpm install -pnpm -C packages/db exec prisma generate -``` - -### Environment - -Copy the API env template and generate a JWT secret: - -```bash cp apps/api/.env.example apps/api/.env -node -e "console.log(require('crypto').randomBytes(48).toString('hex'))" # paste into JWT_SECRET -``` +node -e "console.log(require('crypto').randomBytes(48).toString('hex'))" # paste as JWT_SECRET -The default `DATABASE_URL` points at a local SQLite file. To use the containerized Postgres instead, start Docker and update the URL: +pnpm -C packages/db exec prisma migrate dev +pnpm -C packages/db run db:seed -```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.** +- Default credentials: **`admin` / `admin`** — change them immediately. --- -## Common tasks +## Deployment -All tasks run at the workspace root; Turbo fans them out to the right packages with caching. +`docker-compose.yml` runs the full stack from prebuilt images +(`vector-api`, `vector-web`, `redis`). The SQLite database lives in the +`vector-data` volume. -| 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 on services/lib (report only) | -| `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 | +```bash +# 1. authenticate to the registry +docker login gitea.thewrightserver.net + +# 2. create a .env next to docker-compose.yml with at minimum: +# JWT_SECRET=<64-char hex> +# CLIENT_ORIGIN=https://vector.example.com +# WEB_PORT=8080 +# COOKIE_SECURE=true # required once you're behind TLS +# TAG=latest # or a pinned commit SHA + +docker compose pull && docker compose up -d +``` + +Redis is included in anticipation of the BullMQ-backed webhook worker; the +in-process emitter currently used by the API doesn't depend on it yet. + +--- + +## Development commands + +All commands run at the workspace root; Turbo fans them out with caching. + +| Command | Does | +| -------------------------------------- | ------------------------------------------------------ | +| `pnpm dev` | API + web concurrently | +| `pnpm build` | Build every package and app | +| `pnpm typecheck` | `tsc --noEmit` across the graph | +| `pnpm lint` | ESLint across the graph | +| `pnpm test` | Vitest across packages that define it | +| `pnpm -C apps/api test:coverage` | Services/lib coverage report (no threshold gate yet) | +| `pnpm -C apps/e2e test` | Playwright, against a live stack (see below) | +| `pnpm -C packages/db run db:studio` | Prisma Studio | +| `pnpm -C packages/db run db:reset` | Drop schema, migrate, seed | --- ## Testing -**Unit tests** live next to the code they cover (`*.test.ts`). Coverage is reported on `apps/api/src/{services,lib}/**` and uploaded by CI as an artifact — no threshold gate today; add one once service-level coverage catches up. +**Unit tests** live next to the code they cover (`*.test.ts`). The API covers +services, `lib/` helpers, and the analytics aggregator (tested with an +in-memory `Tx` double). Shared covers every zod contract. -- `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: +**End-to-end tests** in `apps/e2e/` run Playwright against a live stack and +skip themselves unless `TEST_USERNAME` and `TEST_PASSWORD` are set: ```bash -pnpm dev # in one terminal +pnpm dev # terminal 1 + 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 ``` @@ -136,7 +182,7 @@ 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: +`.gitea/workflows/ci.yaml` runs on every push and PR: 1. `pnpm install --frozen-lockfile` 2. `prisma generate` @@ -144,49 +190,26 @@ CI runs on Gitea Actions — [.gitea/workflows/ci.yaml](.gitea/workflows/ci.yaml 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. +7. API coverage uploaded as a workflow artifact. -A second Playwright job is gated behind the repository variable `ENABLE_E2E=true` and requires these secrets: +A second Playwright job is gated by repo variable `ENABLE_E2E=true` and needs +secrets `E2E_BASE_URL`, `E2E_USERNAME`, `E2E_PASSWORD`. -| 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. +Dependency updates come from self-hosted Renovate — 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. +- **API shape** — errors are `{ code, message, requestId, details? }`; + paginated lists are `{ data, page, pageSize, total }`. +- **Validation** — every request body and query is parsed through a zod schema + from `@vector/shared` before reaching a controller. No ad-hoc validation + inside route handlers. +- **Query keys** — hierarchical factory at `apps/web/src/lib/queryKeys.ts`. + Invalidate by domain (`queryKeys.parts.all`) or by filter set + (`queryKeys.parts.list(filters)`). +- **Commits** — [Conventional Commits](https://www.conventionalcommits.org/). + Renovate already expects them.