refactor: normalize project paths across database and service modules

This commit is contained in:
Haileyesus
2026-04-29 18:02:33 +03:00
parent 0f93ef2781
commit f175d20c4e
13 changed files with 113 additions and 60 deletions

View File

@@ -3,6 +3,7 @@ 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() : '';
@@ -17,7 +18,8 @@ function normalizeProjectDisplayName(projectPath: string, customProjectName: str
export const projectsDb = {
createProjectPath(projectPath: string, customProjectName: string | null = null): CreateProjectPathResult {
const db = getConnection();
const normalizedProjectName = normalizeProjectDisplayName(projectPath, customProjectName);
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)
@@ -26,7 +28,7 @@ export const projectsDb = {
isArchived = 0
WHERE projects.isArchived = 1
RETURNING project_id, project_path, custom_project_name, isStarred, isArchived
`).get(attemptedId, projectPath, normalizedProjectName) as ProjectRepositoryRow | undefined;
`).get(attemptedId, normalizedProjectPath, normalizedProjectName) as ProjectRepositoryRow | undefined;
if (row) {
return {
@@ -35,7 +37,7 @@ export const projectsDb = {
};
}
const existingProject = projectsDb.getProjectPath(projectPath);
const existingProject = projectsDb.getProjectPath(normalizedProjectPath);
return {
outcome: 'active_conflict',
project: existingProject,
@@ -44,11 +46,12 @@ export const projectsDb = {
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(projectPath) as ProjectRepositoryRow | undefined;
`).get(normalizedProjectPath) as ProjectRepositoryRow | undefined;
return row ?? null;
},
@@ -94,22 +97,24 @@ export const projectsDb = {
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(projectPath) as Pick<ProjectRepositoryRow, 'custom_project_name'> | undefined;
`).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(), projectPath, customProjectName);
`).run(randomUUID(), normalizedProjectPath, customProjectName);
},
updateCustomProjectNameById(projectId: string, customProjectName: string | null): void {
@@ -123,11 +128,12 @@ export const projectsDb = {
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, projectPath);
`).run(isStarred ? 1 : 0, normalizedProjectPath);
},
updateProjectIsStarredById(projectId: string, isStarred: boolean): void {
@@ -141,11 +147,12 @@ export const projectsDb = {
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, projectPath);
`).run(isArchived ? 1 : 0, normalizedProjectPath);
},
updateProjectIsArchivedById(projectId: string, isArchived: boolean): void {
@@ -159,10 +166,11 @@ export const projectsDb = {
deleteProjectPath(projectPath: string): void {
const db = getConnection();
const normalizedProjectPath = normalizeProjectPath(projectPath);
db.prepare(`
DELETE FROM projects
WHERE project_path = ?
`).run(projectPath);
`).run(normalizedProjectPath);
},
deleteProjectById(projectId: string): void {

View File

@@ -1,7 +1,6 @@
import path from 'node:path';
import { getConnection } from '@/modules/database/connection.js';
import { projectsDb } from '@/modules/database/repositories/projects.db.js';
import { normalizeProjectPath } from '@/shared/utils.js';
type SessionRow = {
session_id: string;
@@ -29,32 +28,9 @@ function normalizeTimestamp(value?: string): string | null {
return parsed.toISOString();
}
function normalizeCodexProjectPath(projectPath: string): string {
const trimmedPath = projectPath.trim();
if (!trimmedPath) {
return projectPath;
}
if (process.platform !== 'win32') {
return path.normalize(trimmedPath);
}
let strippedPath = trimmedPath;
if (strippedPath.startsWith('\\\\?\\UNC\\')) {
strippedPath = `\\\\${strippedPath.slice('\\\\?\\UNC\\'.length)}`;
} else if (strippedPath.startsWith('\\\\?\\')) {
strippedPath = strippedPath.slice('\\\\?\\'.length);
}
return path.win32.normalize(strippedPath);
}
function normalizeProjectPathForProvider(provider: string, projectPath: string): string {
if (provider !== 'codex') {
return projectPath;
}
return normalizeCodexProjectPath(projectPath);
void provider;
return normalizeProjectPath(projectPath);
}
export const sessionsDb = {
@@ -142,17 +118,19 @@ export const sessionsDb = {
getSessionsByProjectPath(projectPath: string): SessionRow[] {
const db = getConnection();
const normalizedProjectPath = normalizeProjectPath(projectPath);
return db
.prepare(
`SELECT session_id, provider, project_path, jsonl_path, custom_name, created_at, updated_at
FROM sessions
WHERE project_path = ?`
)
.all(projectPath) as SessionRow[];
.all(normalizedProjectPath) as SessionRow[];
},
getSessionsByProjectPathPage(projectPath: string, limit: number, offset: number): SessionRow[] {
const db = getConnection();
const normalizedProjectPath = normalizeProjectPath(projectPath);
return db
.prepare(
`SELECT session_id, provider, project_path, jsonl_path, custom_name, created_at, updated_at
@@ -161,25 +139,27 @@ export const sessionsDb = {
ORDER BY datetime(COALESCE(updated_at, created_at)) DESC, session_id DESC
LIMIT ? OFFSET ?`
)
.all(projectPath, limit, offset) as SessionRow[];
.all(normalizedProjectPath, limit, offset) as SessionRow[];
},
countSessionsByProjectPath(projectPath: string): number {
const db = getConnection();
const normalizedProjectPath = normalizeProjectPath(projectPath);
const row = db
.prepare(
`SELECT COUNT(*) AS count
FROM sessions
WHERE project_path = ?`
)
.get(projectPath) as { count: number } | undefined;
.get(normalizedProjectPath) as { count: number } | undefined;
return Number(row?.count ?? 0);
},
deleteSessionsByProjectPath(projectPath: string): void {
const db = getConnection();
db.prepare(`DELETE FROM sessions WHERE project_path = ?`).run(projectPath);
const normalizedProjectPath = normalizeProjectPath(projectPath);
db.prepare(`DELETE FROM sessions WHERE project_path = ?`).run(normalizedProjectPath);
},
getSessionName(sessionId: string, provider: string): string | null {

View File

@@ -7,7 +7,7 @@ import type {
ProjectRepositoryRow,
WorkspacePathValidationResult,
} from '@/shared/types.js';
import { AppError, validateWorkspacePath } from '@/shared/utils.js';
import { AppError, normalizeProjectPath, validateWorkspacePath } from '@/shared/utils.js';
type CreateProjectInput = {
projectPath: string;
@@ -95,7 +95,7 @@ export async function createProject(
input: CreateProjectInput,
dependencies: CreateProjectDependencies = defaultDependencies,
): Promise<CreateProjectServiceResult> {
const normalizedPath = (input.projectPath || '').trim();
const normalizedPath = normalizeProjectPath(input.projectPath || '');
if (!normalizedPath) {
throw new AppError('path is required', {
code: 'PROJECT_PATH_REQUIRED',
@@ -112,7 +112,7 @@ export async function createProject(
});
}
const resolvedProjectPath = pathValidation.resolvedPath;
const resolvedProjectPath = normalizeProjectPath(pathValidation.resolvedPath);
await dependencies.ensureWorkspaceDirectory(resolvedProjectPath);
const normalizedCustomName = resolveDisplayName(input.customName ?? null, resolvedProjectPath);