josh e60d049e69
CI / Lint · Typecheck · Test · Build (push) Successful in 45s
CI / Playwright (smoke) (push) Has been skipped
CI / Build & push images (push) Successful in 1m9s
fix(repairs): Log repair submit silently ignored with empty defaults
The broken-model UUID fields used z.string().uuid().optional(), which
only accepts undefined — not the '' defaults. When the broken serial
matched an existing part, those fields unmounted before their
FormMessage could render, so handleSubmit aborted on hidden errors and
the mutation never fired. Accept the empty-string sentinel alongside
UUIDs.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-17 15:50:24 -04:00
2026-04-16 20:52:32 -04:00
2026-04-17 13:36:11 -04:00
2026-04-16 20:52:32 -04:00

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.

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

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`.

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.


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

pnpm install
pnpm -C packages/db exec prisma generate

Environment

Copy the API env template and generate a JWT secret:

cp apps/api/.env.example apps/api/.env
node -e "console.log(require('crypto').randomBytes(48).toString('hex'))"   # paste into JWT_SECRET

The default DATABASE_URL points at a local SQLite file. To use the containerized Postgres instead, start Docker and update the URL:

docker compose up -d postgres redis
# then edit apps/api/.env:
# DATABASE_URL=postgresql://vector:vector@localhost:5432/vector

Database

pnpm -C packages/db exec prisma migrate dev     # apply migrations
pnpm -C packages/db run db:seed                 # create default admin:admin user

Run

pnpm dev

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 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

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.

  • 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:

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. 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 — 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. Invalidate by domain (queryKeys.parts.all) or by filter (queryKeys.parts.list(filters)).
  • Commits: Conventional Commits — 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<T>, 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 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.
S
Description
Serialized hardware inventory for engineering fleets
Readme 486 KiB
Languages
TypeScript 98.6%
CSS 0.5%
Dockerfile 0.5%
JavaScript 0.3%