import { TRPCError } from "@trpc/server"; import { and, eq } from "drizzle-orm"; import type { PgTable } from "drizzle-orm/pg-core"; import { objects, forms, propertyDefinitions, templates, objectTypeDefs, } from "@tasks/database/schema"; import type { db as defaultDb } from "@tasks/database"; type Db = typeof defaultDb; /** * Generic "this row belongs to this workspace" guard used by tenant-scoped * routers when a mutation targets a specific row by id. Throws NOT_FOUND if the * row either doesn't exist or lives in a different workspace, so callers can't * use the error code to probe IDs across tenants. * * The `table` parameter is constrained structurally to having `id` and * `workspaceId` columns; the `as PgTable` cast inside the body is the * standard drizzle escape hatch when a structural generic table can't be * proven equivalent to drizzle's nominal `PgTable` shape. */ export async function assertRowInWorkspace< T extends { id: typeof objects.id; workspaceId: typeof objects.workspaceId }, >(args: { db: Db; table: T; rowId: string; workspaceId: string; notFoundMessage?: string; }): Promise { const [row] = await args.db .select({ id: args.table.id }) .from(args.table as unknown as PgTable) .where(and(eq(args.table.id, args.rowId), eq(args.table.workspaceId, args.workspaceId))) .limit(1); if (!row) { throw new TRPCError({ code: "NOT_FOUND", message: args.notFoundMessage ?? "Resource not found", }); } } export const tableForms = forms; export const tablePropertyDefs = propertyDefinitions; export const tableTemplates = templates; export const tableObjectTypeDefs = objectTypeDefs; export const tableObjects = objects;