mirror of
https://github.com/siteboon/claudecodeui.git
synced 2026-04-30 09:21:33 +00:00
184 lines
6.8 KiB
TypeScript
184 lines
6.8 KiB
TypeScript
import { randomUUID } from 'node:crypto';
|
|
import path from 'node:path';
|
|
|
|
import { getConnection } from '@/modules/database/connection.js';
|
|
import type { CreateProjectPathResult, ProjectRepositoryRow } from '@/shared/types.js';
|
|
import { normalizeProjectPath } from '@/shared/utils.js';
|
|
|
|
function normalizeProjectDisplayName(projectPath: string, customProjectName: string | null): string {
|
|
const trimmedCustomName = typeof customProjectName === 'string' ? customProjectName.trim() : '';
|
|
if (trimmedCustomName.length > 0) {
|
|
return trimmedCustomName;
|
|
}
|
|
|
|
const directoryName = path.basename(projectPath);
|
|
return directoryName || projectPath;
|
|
}
|
|
|
|
export const projectsDb = {
|
|
createProjectPath(projectPath: string, customProjectName: string | null = null): CreateProjectPathResult {
|
|
const db = getConnection();
|
|
const normalizedProjectPath = normalizeProjectPath(projectPath);
|
|
const normalizedProjectName = normalizeProjectDisplayName(normalizedProjectPath, customProjectName);
|
|
const attemptedId = randomUUID();
|
|
const row = db.prepare(`
|
|
INSERT INTO projects (project_id, project_path, custom_project_name, isArchived)
|
|
VALUES (?, ?, ?, 0)
|
|
ON CONFLICT(project_path) DO UPDATE SET
|
|
isArchived = 0
|
|
WHERE projects.isArchived = 1
|
|
RETURNING project_id, project_path, custom_project_name, isStarred, isArchived
|
|
`).get(attemptedId, normalizedProjectPath, normalizedProjectName) as ProjectRepositoryRow | undefined;
|
|
|
|
if (row) {
|
|
return {
|
|
outcome: row.project_id === attemptedId ? 'created' : 'reactivated_archived',
|
|
project: row,
|
|
};
|
|
}
|
|
|
|
const existingProject = projectsDb.getProjectPath(normalizedProjectPath);
|
|
return {
|
|
outcome: 'active_conflict',
|
|
project: existingProject,
|
|
};
|
|
},
|
|
|
|
getProjectPath(projectPath: string): ProjectRepositoryRow | null {
|
|
const db = getConnection();
|
|
const normalizedProjectPath = normalizeProjectPath(projectPath);
|
|
const row = db.prepare(`
|
|
SELECT project_id, project_path, custom_project_name, isStarred, isArchived
|
|
FROM projects
|
|
WHERE project_path = ?
|
|
`).get(normalizedProjectPath) as ProjectRepositoryRow | undefined;
|
|
|
|
return row ?? null;
|
|
},
|
|
|
|
getProjectById(projectId: string): ProjectRepositoryRow | null {
|
|
const db = getConnection();
|
|
const row = db.prepare(`
|
|
SELECT project_id, project_path, custom_project_name, isStarred, isArchived
|
|
FROM projects
|
|
WHERE project_id = ?
|
|
`).get(projectId) as ProjectRepositoryRow | undefined;
|
|
|
|
return row ?? null;
|
|
},
|
|
|
|
/**
|
|
* Resolve the absolute project directory from a database project_id.
|
|
*
|
|
* This is the canonical lookup used after the projectName → projectId migration:
|
|
* API routes receive the DB-assigned `projectId` and must resolve the real folder
|
|
* path through this helper before touching the filesystem. Returns `null` when the
|
|
* project row does not exist so callers can respond with a 404.
|
|
*/
|
|
getProjectPathById(projectId: string): string | null {
|
|
const db = getConnection();
|
|
const row = db.prepare(`
|
|
SELECT project_path
|
|
FROM projects
|
|
WHERE project_id = ?
|
|
`).get(projectId) as Pick<ProjectRepositoryRow, 'project_path'> | undefined;
|
|
|
|
return row?.project_path ?? null;
|
|
},
|
|
|
|
getProjectPaths(): ProjectRepositoryRow[] {
|
|
const db = getConnection();
|
|
return db.prepare(`
|
|
SELECT project_id, project_path, custom_project_name, isStarred, isArchived
|
|
FROM projects
|
|
WHERE isArchived = 0
|
|
`).all() as ProjectRepositoryRow[];
|
|
},
|
|
|
|
getCustomProjectName(projectPath: string): string | null {
|
|
const db = getConnection();
|
|
const normalizedProjectPath = normalizeProjectPath(projectPath);
|
|
const row = db.prepare(`
|
|
SELECT custom_project_name
|
|
FROM projects
|
|
WHERE project_path = ?
|
|
`).get(normalizedProjectPath) as Pick<ProjectRepositoryRow, 'custom_project_name'> | undefined;
|
|
|
|
return row?.custom_project_name ?? null;
|
|
},
|
|
|
|
updateCustomProjectName(projectPath: string, customProjectName: string | null): void {
|
|
const db = getConnection();
|
|
const normalizedProjectPath = normalizeProjectPath(projectPath);
|
|
db.prepare(`
|
|
INSERT INTO projects (project_id, project_path, custom_project_name)
|
|
VALUES (?, ?, ?)
|
|
ON CONFLICT(project_path) DO UPDATE SET custom_project_name = excluded.custom_project_name
|
|
`).run(randomUUID(), normalizedProjectPath, customProjectName);
|
|
},
|
|
|
|
updateCustomProjectNameById(projectId: string, customProjectName: string | null): void {
|
|
const db = getConnection();
|
|
db.prepare(`
|
|
UPDATE projects
|
|
SET custom_project_name = ?
|
|
WHERE project_id = ?
|
|
`).run(customProjectName, projectId);
|
|
},
|
|
|
|
updateProjectIsStarred(projectPath: string, isStarred: boolean): void {
|
|
const db = getConnection();
|
|
const normalizedProjectPath = normalizeProjectPath(projectPath);
|
|
db.prepare(`
|
|
UPDATE projects
|
|
SET isStarred = ?
|
|
WHERE project_path = ?
|
|
`).run(isStarred ? 1 : 0, normalizedProjectPath);
|
|
},
|
|
|
|
updateProjectIsStarredById(projectId: string, isStarred: boolean): void {
|
|
const db = getConnection();
|
|
db.prepare(`
|
|
UPDATE projects
|
|
SET isStarred = ?
|
|
WHERE project_id = ?
|
|
`).run(isStarred ? 1 : 0, projectId);
|
|
},
|
|
|
|
updateProjectIsArchived(projectPath: string, isArchived: boolean): void {
|
|
const db = getConnection();
|
|
const normalizedProjectPath = normalizeProjectPath(projectPath);
|
|
db.prepare(`
|
|
UPDATE projects
|
|
SET isArchived = ?
|
|
WHERE project_path = ?
|
|
`).run(isArchived ? 1 : 0, normalizedProjectPath);
|
|
},
|
|
|
|
updateProjectIsArchivedById(projectId: string, isArchived: boolean): void {
|
|
const db = getConnection();
|
|
db.prepare(`
|
|
UPDATE projects
|
|
SET isArchived = ?
|
|
WHERE project_id = ?
|
|
`).run(isArchived ? 1 : 0, projectId);
|
|
},
|
|
|
|
deleteProjectPath(projectPath: string): void {
|
|
const db = getConnection();
|
|
const normalizedProjectPath = normalizeProjectPath(projectPath);
|
|
db.prepare(`
|
|
DELETE FROM projects
|
|
WHERE project_path = ?
|
|
`).run(normalizedProjectPath);
|
|
},
|
|
|
|
deleteProjectById(projectId: string): void {
|
|
const db = getConnection();
|
|
db.prepare(`
|
|
DELETE FROM projects
|
|
WHERE project_id = ?
|
|
`).run(projectId);
|
|
},
|
|
};
|