import { and, desc, eq, inArray } from "drizzle-orm"; import type { Db } from "@paperclipai/db"; import { executionWorkspaces } from "@paperclipai/db"; import type { ExecutionWorkspace } from "@paperclipai/shared"; type ExecutionWorkspaceRow = typeof executionWorkspaces.$inferSelect; function toExecutionWorkspace(row: ExecutionWorkspaceRow): ExecutionWorkspace { return { id: row.id, companyId: row.companyId, projectId: row.projectId, projectWorkspaceId: row.projectWorkspaceId ?? null, sourceIssueId: row.sourceIssueId ?? null, mode: row.mode as ExecutionWorkspace["mode"], strategyType: row.strategyType as ExecutionWorkspace["strategyType"], name: row.name, status: row.status as ExecutionWorkspace["status"], cwd: row.cwd ?? null, repoUrl: row.repoUrl ?? null, baseRef: row.baseRef ?? null, branchName: row.branchName ?? null, providerType: row.providerType as ExecutionWorkspace["providerType"], providerRef: row.providerRef ?? null, derivedFromExecutionWorkspaceId: row.derivedFromExecutionWorkspaceId ?? null, lastUsedAt: row.lastUsedAt, openedAt: row.openedAt, closedAt: row.closedAt ?? null, cleanupEligibleAt: row.cleanupEligibleAt ?? null, cleanupReason: row.cleanupReason ?? null, metadata: (row.metadata as Record | null) ?? null, createdAt: row.createdAt, updatedAt: row.updatedAt, }; } export function executionWorkspaceService(db: Db) { return { list: async (companyId: string, filters?: { projectId?: string; projectWorkspaceId?: string; issueId?: string; status?: string; reuseEligible?: boolean; }) => { const conditions = [eq(executionWorkspaces.companyId, companyId)]; if (filters?.projectId) conditions.push(eq(executionWorkspaces.projectId, filters.projectId)); if (filters?.projectWorkspaceId) { conditions.push(eq(executionWorkspaces.projectWorkspaceId, filters.projectWorkspaceId)); } if (filters?.issueId) conditions.push(eq(executionWorkspaces.sourceIssueId, filters.issueId)); if (filters?.status) { const statuses = filters.status.split(",").map((value) => value.trim()).filter(Boolean); if (statuses.length === 1) conditions.push(eq(executionWorkspaces.status, statuses[0]!)); else if (statuses.length > 1) conditions.push(inArray(executionWorkspaces.status, statuses)); } if (filters?.reuseEligible) { conditions.push(inArray(executionWorkspaces.status, ["active", "idle", "in_review"])); } const rows = await db .select() .from(executionWorkspaces) .where(and(...conditions)) .orderBy(desc(executionWorkspaces.lastUsedAt), desc(executionWorkspaces.createdAt)); return rows.map(toExecutionWorkspace); }, getById: async (id: string) => { const row = await db .select() .from(executionWorkspaces) .where(eq(executionWorkspaces.id, id)) .then((rows) => rows[0] ?? null); return row ? toExecutionWorkspace(row) : null; }, create: async (data: typeof executionWorkspaces.$inferInsert) => { const row = await db .insert(executionWorkspaces) .values(data) .returning() .then((rows) => rows[0] ?? null); return row ? toExecutionWorkspace(row) : null; }, update: async (id: string, patch: Partial) => { const row = await db .update(executionWorkspaces) .set({ ...patch, updatedAt: new Date() }) .where(eq(executionWorkspaces.id, id)) .returning() .then((rows) => rows[0] ?? null); return row ? toExecutionWorkspace(row) : null; }, }; } export { toExecutionWorkspace };