import { promises as fs } from 'node:fs'; import path from 'node:path'; import { projectsDb, sessionsDb } from '@/modules/database/index.js'; import { AppError } from '@/shared/utils.js'; function uniqueJsonlPathsFromSessions( sessions: Array<{ jsonl_path: string | null }>, ): string[] { const seen = new Set(); const result: string[] = []; for (const row of sessions) { const raw = row.jsonl_path?.trim(); if (!raw) { continue; } const absolute = path.isAbsolute(raw) ? path.normalize(raw) : path.resolve(raw); if (seen.has(absolute)) { continue; } seen.add(absolute); result.push(absolute); } return result; } async function unlinkJsonlIfExists(filePath: string): Promise { try { await fs.unlink(filePath); } catch (error) { const code = (error as NodeJS.ErrnoException).code; if (code === 'ENOENT') { return; } console.warn(`[project-delete] Failed to remove ${filePath}:`, (error as Error).message); } } /** * Loads all session rows for the project path and removes each distinct `jsonl_path` file on disk. */ export async function deleteSessionJsonlFilesForProjectPath(projectPath: string): Promise { const sessions = sessionsDb.getSessionsByProjectPath(projectPath); const paths = uniqueJsonlPathsFromSessions(sessions); for (const filePath of paths) { await unlinkJsonlIfExists(filePath); } } /** * - **Soft delete** (`force` false): set `isArchived` on the `projects` row (hide from the active list; DB only). * - **Force** (`force` true): for each session row for that `project_path`, delete the file at `jsonl_path` * (when set), then remove session rows and the `projects` row. */ export async function deleteOrArchiveProject(projectId: string, force: boolean): Promise { const row = projectsDb.getProjectById(projectId); if (!row) { throw new AppError(`Unknown projectId: ${projectId}`, { code: 'PROJECT_NOT_FOUND', statusCode: 404, }); } if (!force) { projectsDb.updateProjectIsArchivedById(projectId, true); return; } await deleteSessionJsonlFilesForProjectPath(row.project_path); sessionsDb.deleteSessionsByProjectPath(row.project_path); projectsDb.deleteProjectById(projectId); }