Files
josh d739411510
CI / Lint · Typecheck · Test · Build (push) Successful in 51s
CI / Playwright (smoke) (push) Has been skipped
CI / Build & push images (push) Successful in 1m9s
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 <noreply@anthropic.com>
2026-04-17 16:34:51 -04:00

216 lines
8.4 KiB
Markdown

# Vector
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 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.
---
## Architecture
```
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 — 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 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.
---
## Tech stack
| Layer | Choice |
| -------------- | ---------------------------------------------------------------------- |
| 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 (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.
---
## Quick start
Prerequisites: **Node 20+**, **pnpm 10+** (`corepack enable`).
```bash
pnpm install
cp apps/api/.env.example apps/api/.env
node -e "console.log(require('crypto').randomBytes(48).toString('hex'))" # paste as JWT_SECRET
pnpm -C packages/db exec prisma migrate dev
pnpm -C packages/db run db:seed
pnpm dev
```
- API: <http://localhost:3001>
- Web: <http://localhost:5173> (proxies `/api` → API)
- Default credentials: **`admin` / `admin`** — change them immediately.
---
## Deployment
`docker-compose.yml` runs the full stack from prebuilt images
(`vector-api`, `vector-web`, `redis`). The SQLite database lives in the
`vector-data` volume.
```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`). The API covers
services, `lib/` helpers, and the analytics aggregator (tested with an
in-memory `Tx` double). Shared covers every zod contract.
**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 # 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
```
Reports land in `apps/e2e/playwright-report/`.
---
## Continuous integration
`.gitea/workflows/ci.yaml` runs on every push and PR:
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 a workflow artifact.
A second Playwright job is gated by repo variable `ENABLE_E2E=true` and needs
secrets `E2E_BASE_URL`, `E2E_USERNAME`, `E2E_PASSWORD`.
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** — 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.