chore: initial Vector 2.0 monorepo
CI / Lint · Typecheck · Test · Build (push) Failing after 5m41s
CI / Playwright (smoke) (push) Has been skipped

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:
2026-04-16 20:52:32 -04:00
commit 7c0d422228
216 changed files with 19393 additions and 0 deletions
+145
View File
@@ -0,0 +1,145 @@
import { Prisma } from '@vector/db';
import type {
CreateRepairJobRequest,
RepairJobListQuery,
UpdateRepairJobRequest,
} from '@vector/shared';
import { errors } from '../lib/http-error.js';
import type { Actor, Tx } from './types.js';
const repairInclude = {
part: {
include: { manufacturer: true },
},
host: true,
assignee: { select: { id: true, username: true, email: true, role: true } },
} satisfies Prisma.RepairJobInclude;
export async function list(tx: Tx, q: RepairJobListQuery) {
const { page, pageSize, status, partId, hostId, assigneeId, openOnly } = q;
const where: Prisma.RepairJobWhereInput = {};
if (status) where.status = status;
if (partId) where.partId = partId;
if (hostId) where.hostId = hostId;
if (assigneeId) where.assigneeId = assigneeId;
if (openOnly) where.status = { in: ['PENDING', 'IN_PROGRESS'] };
const [data, total] = await Promise.all([
tx.repairJob.findMany({
where,
orderBy: [{ status: 'asc' }, { openedAt: 'desc' }],
include: repairInclude,
skip: (page - 1) * pageSize,
take: pageSize,
}),
tx.repairJob.count({ where }),
]);
return { data, page, pageSize, total };
}
export function get(tx: Tx, id: string) {
return tx.repairJob.findUnique({ where: { id }, include: repairInclude });
}
export function listForPart(tx: Tx, partId: string) {
return tx.repairJob.findMany({
where: { partId },
orderBy: { openedAt: 'desc' },
include: repairInclude,
});
}
export async function create(
tx: Tx,
input: CreateRepairJobRequest,
actor: Actor | null,
) {
const part = await tx.part.findUnique({ where: { id: input.partId } });
if (!part) throw errors.notFound('Part');
try {
const repair = await tx.repairJob.create({
data: {
partId: input.partId,
hostId: input.hostId ?? null,
assigneeId: input.assigneeId ?? null,
notes: input.notes ?? null,
status: 'PENDING',
},
include: repairInclude,
});
await tx.partEvent.create({
data: {
partId: part.id,
userId: actor?.id ?? null,
type: 'REPAIR_STARTED',
newValue: repair.id,
},
});
return repair;
} catch (err) {
if (err instanceof Prisma.PrismaClientKnownRequestError && err.code === 'P2003') {
throw errors.badRequest('Invalid host or assignee id');
}
throw err;
}
}
export async function update(
tx: Tx,
id: string,
input: UpdateRepairJobRequest,
actor: Actor | null,
) {
const current = await tx.repairJob.findUnique({ where: { id } });
if (!current) throw errors.notFound('Repair');
const data: Prisma.RepairJobUpdateInput = {};
if (input.status !== undefined && input.status !== current.status) {
data.status = input.status;
// closedAt follows terminal status transitions.
const nowTerminal = input.status === 'COMPLETED' || input.status === 'CANCELLED';
const wasTerminal = current.status === 'COMPLETED' || current.status === 'CANCELLED';
if (nowTerminal && !wasTerminal) data.closedAt = new Date();
if (!nowTerminal && wasTerminal) data.closedAt = null;
}
if (input.hostId !== undefined) {
data.host = input.hostId ? { connect: { id: input.hostId } } : { disconnect: true };
}
if (input.assigneeId !== undefined) {
data.assignee = input.assigneeId
? { connect: { id: input.assigneeId } }
: { disconnect: true };
}
if (input.notes !== undefined) data.notes = input.notes;
const repair = await tx.repairJob.update({
where: { id },
data,
include: repairInclude,
});
if (input.status === 'COMPLETED' && current.status !== 'COMPLETED') {
await tx.partEvent.create({
data: {
partId: repair.partId,
userId: actor?.id ?? null,
type: 'REPAIR_COMPLETED',
newValue: repair.id,
},
});
}
return repair;
}
export async function remove(tx: Tx, id: string) {
try {
await tx.repairJob.delete({ where: { id } });
} catch (err) {
if (err instanceof Prisma.PrismaClientKnownRequestError && err.code === 'P2025') {
throw errors.notFound('Repair');
}
throw err;
}
}