Files
josh 7c0d422228
CI / Lint · Typecheck · Test · Build (push) Failing after 5m41s
CI / Playwright (smoke) (push) Has been skipped
chore: initial Vector 2.0 monorepo
Ground-up TypeScript rewrite of the Vector hardware parts inventory
system. Ships the full roadmap (Phases 0-8) in one initial commit:

- pnpm + Turbo monorepo: apps/{api,web,e2e}, packages/{db,shared,ui,config}
- Express 5 + Prisma 5 + zod validation + JWT w/ refresh-token rotation
- React 19 + Vite + shadcn/ui + TanStack Query/Table + nuqs URL state
- Repair/RMA, tags, bulk ops, saved views, CSV audit export
- Analytics dashboard on Recharts + EOL tracking
- Signed webhook subscriptions (HMAC-SHA256) with in-process emitter
- Vitest unit tests (shared schemas, api services/helpers) + Playwright skeleton
- Gitea Actions CI (lint, typecheck, test+coverage, build) + Renovate

Deferred follow-ups: Postgres cutover (data-migration script ready),
BullMQ worker for webhook delivery, @react-pdf PDF export, CSV import wizard.
2026-04-16 20:52:32 -04:00

2.3 KiB

Postgres-only migrations (apply post-cutover)

Phase 3 locked in the full schema shape on SQLite. When the datasource flips to postgresql we apply a follow-up migration that upgrades a few columns to Postgres-native types and adds the Full-Text Search column required by the plan.

1. Part full-text search (tsvector + GIN)

ALTER TABLE "Part"
  ADD COLUMN "searchVector" tsvector
  GENERATED ALWAYS AS (
    setweight(to_tsvector('simple', coalesce("serialNumber", '')), 'A') ||
    setweight(to_tsvector('simple', coalesce("mpn", '')),          'B') ||
    setweight(to_tsvector('english', coalesce("notes", '')),       'C')
  ) STORED;

CREATE INDEX "Part_searchVector_idx" ON "Part" USING GIN ("searchVector");

Query shape the API will use for the q= filter on /api/parts:

SELECT * FROM "Part"
WHERE "searchVector" @@ plainto_tsquery('simple', $1)
ORDER BY ts_rank("searchVector", plainto_tsquery('simple', $1)) DESC
LIMIT $2 OFFSET $3;

Expose on the Prisma model with @ignore (Prisma can't represent GENERATED columns) and read via prisma.$queryRaw inside the parts service.

2. Convert JSON-string columns to native Postgres types

These were stored as String for SQLite portability.

ALTER TABLE "WebhookSubscription"
  ALTER COLUMN "events" TYPE text[] USING string_to_array(
    trim(both '[]' from "events"), ','
  );

ALTER TABLE "SavedView"
  ALTER COLUMN "filterJson" TYPE jsonb USING "filterJson"::jsonb;

ALTER TABLE "CsvImportJob"
  ALTER COLUMN "errors" TYPE jsonb USING "errors"::jsonb;

After the DDL change, update packages/db/prisma/schema.prisma:

model WebhookSubscription {
  // ...
  events String[]
}

model SavedView {
  // ...
  filterJson Json
}

model CsvImportJob {
  // ...
  errors Json?
}

Regenerate the Prisma client and tighten the zod → Prisma marshaling in the service layer (drop the JSON.stringify / JSON.parse bridges).

Verification checklist (P3)

  • prisma migrate deploy applies cleanly against a fresh Postgres snapshot.
  • EXPLAIN ANALYZE on the FTS query shows a Bitmap Index Scan on Part_searchVector_idx and returns ranked results in <50ms at 100k rows.
  • Integration test inserts 3 parts with overlapping serial/mpn tokens, queries q=..., and asserts order-by-rank.