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.
This commit is contained in:
@@ -0,0 +1,78 @@
|
||||
# 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)
|
||||
|
||||
```sql
|
||||
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`:
|
||||
|
||||
```sql
|
||||
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.
|
||||
|
||||
```sql
|
||||
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`:
|
||||
|
||||
```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.
|
||||
Reference in New Issue
Block a user