import { inArray } from "drizzle-orm"; import type { CapabilityDb } from "../capabilities/context"; import { githubRepositories, invoiceLineItems, linearProjectMappings, liveActivitySessions, projectRoots, projects, timelineRollups, } from "../db/schema"; /** * Delete projects and re-point everything that references them, so nothing is * left dangling. Mirrors the `DELETE /api/project-groups/[id]` route so the * chat `mergeProjects` capability deletes exactly as safely: * - roots / GitHub repos / live sessions / invoice line items → projectId null * (become unassigned; invoice line items keep their audit trail), * - Linear mappings and rollup-cache rows → deleted (meaningless without their * project), * - then the project rows themselves. * * Sequential (not a single transaction) to stay driver-agnostic across libSQL * and bun:sqlite; each step is additive-then-delete, so a partial failure is * recoverable rather than corrupting. */ export async function deleteProjectsCascade( db: CapabilityDb, ids: string[], ): Promise { if (ids.length === 0) return; await db .update(projectRoots) .set({ projectId: null }) .where(inArray(projectRoots.projectId, ids)); await db .update(githubRepositories) .set({ projectId: null }) .where(inArray(githubRepositories.projectId, ids)); await db .update(liveActivitySessions) .set({ projectId: null }) .where(inArray(liveActivitySessions.projectId, ids)); await db .update(invoiceLineItems) .set({ projectId: null }) .where(inArray(invoiceLineItems.projectId, ids)); await db .delete(linearProjectMappings) .where(inArray(linearProjectMappings.projectId, ids)); await db .delete(timelineRollups) .where(inArray(timelineRollups.projectId, ids)); await db.delete(projects).where(inArray(projects.id, ids)); }