mirror of
https://github.com/siteboon/claudecodeui.git
synced 2026-05-26 21:26:39 +08:00
refactor: remove sessions names db
This commit is contained in:
@@ -5,7 +5,7 @@ import path from 'path';
|
|||||||
import os from 'os';
|
import os from 'os';
|
||||||
import TOML from '@iarna/toml';
|
import TOML from '@iarna/toml';
|
||||||
import { getCodexSessions, deleteCodexSession } from '../../../projects.js';
|
import { getCodexSessions, deleteCodexSession } from '../../../projects.js';
|
||||||
import { applyCustomSessionNames, sessionNamesDb } from '@/shared/database/repositories/session-names.js';
|
import { sessionsDb } from '@/shared/database/repositories/sessions.db.js';
|
||||||
|
|
||||||
const router = express.Router();
|
const router = express.Router();
|
||||||
|
|
||||||
@@ -60,7 +60,7 @@ router.get('/sessions', async (req, res) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const sessions = await getCodexSessions(projectPath);
|
const sessions = await getCodexSessions(projectPath);
|
||||||
applyCustomSessionNames(sessions, 'codex');
|
sessionsDb.applyCustomSessionNames(sessions, 'codex');
|
||||||
res.json({ success: true, sessions });
|
res.json({ success: true, sessions });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error fetching Codex sessions:', error);
|
console.error('Error fetching Codex sessions:', error);
|
||||||
@@ -72,7 +72,7 @@ router.delete('/sessions/:sessionId', async (req, res) => {
|
|||||||
try {
|
try {
|
||||||
const { sessionId } = req.params;
|
const { sessionId } = req.params;
|
||||||
await deleteCodexSession(sessionId);
|
await deleteCodexSession(sessionId);
|
||||||
sessionNamesDb.deleteSessionName(sessionId, 'codex');
|
sessionsDb.deleteSession(sessionId);
|
||||||
res.json({ success: true });
|
res.json({ success: true });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(`Error deleting Codex session ${req.params.sessionId}:`, error);
|
console.error(`Error deleting Codex session ${req.params.sessionId}:`, error);
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import sqlite3 from 'sqlite3';
|
|||||||
import { open } from 'sqlite';
|
import { open } from 'sqlite';
|
||||||
import crypto from 'crypto';
|
import crypto from 'crypto';
|
||||||
import { CURSOR_MODELS } from '../../../../shared/modelConstants.js';
|
import { CURSOR_MODELS } from '../../../../shared/modelConstants.js';
|
||||||
import { applyCustomSessionNames } from '@/shared/database/repositories/session-names.js';
|
import { sessionsDb } from '@/shared/database/repositories/sessions.db.js';
|
||||||
|
|
||||||
const router = express.Router();
|
const router = express.Router();
|
||||||
|
|
||||||
@@ -561,7 +561,7 @@ router.get('/sessions', async (req, res) => {
|
|||||||
return new Date(b.createdAt) - new Date(a.createdAt);
|
return new Date(b.createdAt) - new Date(a.createdAt);
|
||||||
});
|
});
|
||||||
|
|
||||||
applyCustomSessionNames(sessions, 'cursor');
|
sessionsDb.applyCustomSessionNames(sessions, 'cursor');
|
||||||
|
|
||||||
res.json({
|
res.json({
|
||||||
success: true,
|
success: true,
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import express from 'express';
|
import express from 'express';
|
||||||
import sessionManager from '../../../sessionManager.js';
|
import sessionManager from '../../../sessionManager.js';
|
||||||
import { sessionNamesDb } from '@/shared/database/repositories/session-names.js';
|
import { sessionsDb } from '@/shared/database/repositories/sessions.db.js';
|
||||||
|
|
||||||
const router = express.Router();
|
const router = express.Router();
|
||||||
|
|
||||||
@@ -13,7 +13,7 @@ router.delete('/sessions/:sessionId', async (req, res) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
await sessionManager.deleteSession(sessionId);
|
await sessionManager.deleteSession(sessionId);
|
||||||
sessionNamesDb.deleteSessionName(sessionId, 'gemini');
|
sessionsDb.deleteSession(sessionId);
|
||||||
res.json({ success: true });
|
res.json({ success: true });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(`Error deleting Gemini session ${req.params.sessionId}:`, error);
|
console.error(`Error deleting Gemini session ${req.params.sessionId}:`, error);
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import {
|
|||||||
deleteProject,
|
deleteProject,
|
||||||
searchConversations
|
searchConversations
|
||||||
} from '../../../projects.js';
|
} from '../../../projects.js';
|
||||||
import { applyCustomSessionNames, sessionNamesDb } from '@/shared/database/repositories/session-names.js';
|
import { sessionsDb } from '@/shared/database/repositories/sessions.db.js';
|
||||||
import { workspaceOriginalPathsDb } from '@/shared/database/repositories/workspace-original-paths.db.js';
|
import { workspaceOriginalPathsDb } from '@/shared/database/repositories/workspace-original-paths.db.js';
|
||||||
import { authenticateToken } from '../auth/auth.middleware.js';
|
import { authenticateToken } from '../auth/auth.middleware.js';
|
||||||
import { getWorkspaceNameFromPath, WORKSPACES_ROOT, validateWorkspacePath } from './projects.utils.js';
|
import { getWorkspaceNameFromPath, WORKSPACES_ROOT, validateWorkspacePath } from './projects.utils.js';
|
||||||
@@ -46,7 +46,7 @@ router.get('/api/projects/:projectName/sessions', authenticateToken, async (req,
|
|||||||
try {
|
try {
|
||||||
const { limit = 5, offset = 0 } = req.query;
|
const { limit = 5, offset = 0 } = req.query;
|
||||||
const result = await getSessions(req.params.projectName, parseInt(limit), parseInt(offset));
|
const result = await getSessions(req.params.projectName, parseInt(limit), parseInt(offset));
|
||||||
applyCustomSessionNames(result.sessions, 'claude');
|
sessionsDb.applyCustomSessionNames(result.sessions, 'claude');
|
||||||
res.json(result);
|
res.json(result);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
res.status(500).json({ error: error.message });
|
res.status(500).json({ error: error.message });
|
||||||
@@ -70,7 +70,7 @@ router.delete('/api/projects/:projectName/sessions/:sessionId', authenticateToke
|
|||||||
const { projectName, sessionId } = req.params;
|
const { projectName, sessionId } = req.params;
|
||||||
console.log(`[API] Deleting session: ${sessionId} from project: ${projectName}`);
|
console.log(`[API] Deleting session: ${sessionId} from project: ${projectName}`);
|
||||||
await deleteSession(projectName, sessionId);
|
await deleteSession(projectName, sessionId);
|
||||||
sessionNamesDb.deleteSessionName(sessionId, 'claude');
|
sessionsDb.deleteSession(sessionId);
|
||||||
console.log(`[API] Session ${sessionId} deleted successfully`);
|
console.log(`[API] Session ${sessionId} deleted successfully`);
|
||||||
res.json({ success: true });
|
res.json({ success: true });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import os from 'os';
|
import os from 'os';
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
|
import fsp from 'node:fs/promises';
|
||||||
import { sessionsDb } from '@/shared/database/repositories/sessions.db.js';
|
import { sessionsDb } from '@/shared/database/repositories/sessions.db.js';
|
||||||
import { buildLookupMap, extractFirstValidJsonlData, findFilesRecursivelyCreatedAfterLastScan } from '@/modules/providers/shared/session-parser.utils.js';
|
import { buildLookupMap, extractFirstValidJsonlData, findFilesRecursivelyCreatedAfterLastScan } from '@/modules/providers/shared/session-parser.utils.js';
|
||||||
import { SessionData } from '@/shared/types/session.js';
|
import { SessionData } from '@/shared/types/session.js';
|
||||||
@@ -28,7 +29,23 @@ export async function processClaudeSessions() {
|
|||||||
const result = await processClaudeSessionFile(file, nameMap);
|
const result = await processClaudeSessionFile(file, nameMap);
|
||||||
|
|
||||||
if (result) {
|
if (result) {
|
||||||
sessionsDb.createSession(result.sessionId, 'claude', result.workspacePath, result.sessionName);
|
let createdAt: string | undefined;
|
||||||
|
let updatedAt: string | undefined;
|
||||||
|
try {
|
||||||
|
const stat = await fsp.stat(file);
|
||||||
|
createdAt = stat.birthtime.toISOString();
|
||||||
|
updatedAt = stat.mtime.toISOString();
|
||||||
|
} catch {
|
||||||
|
// Ignore stat failures and let DB defaults handle created_at/updated_at.
|
||||||
|
}
|
||||||
|
sessionsDb.createSession(
|
||||||
|
result.sessionId,
|
||||||
|
'claude',
|
||||||
|
result.workspacePath,
|
||||||
|
result.sessionName,
|
||||||
|
createdAt,
|
||||||
|
updatedAt,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import os from 'os';
|
import os from 'os';
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
|
import fsp from 'node:fs/promises';
|
||||||
import { sessionsDb } from '@/shared/database/repositories/sessions.db.js';
|
import { sessionsDb } from '@/shared/database/repositories/sessions.db.js';
|
||||||
import { buildLookupMap, extractFirstValidJsonlData, findFilesRecursivelyCreatedAfterLastScan } from '@/modules/providers/shared/session-parser.utils.js';
|
import { buildLookupMap, extractFirstValidJsonlData, findFilesRecursivelyCreatedAfterLastScan } from '@/modules/providers/shared/session-parser.utils.js';
|
||||||
import { SessionData } from '@/shared/types/session.js';
|
import { SessionData } from '@/shared/types/session.js';
|
||||||
@@ -29,7 +30,23 @@ export async function processCodexSessions() {
|
|||||||
const result = await processCodexSessionFile(file, nameMap);
|
const result = await processCodexSessionFile(file, nameMap);
|
||||||
|
|
||||||
if (result) {
|
if (result) {
|
||||||
sessionsDb.createSession(result.sessionId, 'codex', result.workspacePath, result.sessionName);
|
let createdAt: string | undefined;
|
||||||
|
let updatedAt: string | undefined;
|
||||||
|
try {
|
||||||
|
const stat = await fsp.stat(file);
|
||||||
|
createdAt = stat.birthtime.toISOString();
|
||||||
|
updatedAt = stat.mtime.toISOString();
|
||||||
|
} catch {
|
||||||
|
// Ignore stat failures and let DB defaults handle created_at/updated_at.
|
||||||
|
}
|
||||||
|
sessionsDb.createSession(
|
||||||
|
result.sessionId,
|
||||||
|
'codex',
|
||||||
|
result.workspacePath,
|
||||||
|
result.sessionName,
|
||||||
|
createdAt,
|
||||||
|
updatedAt,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -80,7 +80,23 @@ export async function processCursorSessions() {
|
|||||||
const result = await processCursorSessionFile(file);
|
const result = await processCursorSessionFile(file);
|
||||||
|
|
||||||
if (result) {
|
if (result) {
|
||||||
sessionsDb.createSession(result.sessionId, 'cursor', result.workspacePath, result.sessionName);
|
let createdAt: string | undefined;
|
||||||
|
let updatedAt: string | undefined;
|
||||||
|
try {
|
||||||
|
const stat = await fsp.stat(file);
|
||||||
|
createdAt = stat.birthtime.toISOString();
|
||||||
|
updatedAt = stat.mtime.toISOString();
|
||||||
|
} catch {
|
||||||
|
// Ignore stat failures and let DB defaults handle created_at/updated_at.
|
||||||
|
}
|
||||||
|
sessionsDb.createSession(
|
||||||
|
result.sessionId,
|
||||||
|
'cursor',
|
||||||
|
result.workspacePath,
|
||||||
|
result.sessionName,
|
||||||
|
createdAt,
|
||||||
|
updatedAt,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,7 +31,23 @@ export async function processGeminiSessions() {
|
|||||||
for (const file of files) {
|
for (const file of files) {
|
||||||
const result = await processGeminiSessionFile(file);
|
const result = await processGeminiSessionFile(file);
|
||||||
if (result) {
|
if (result) {
|
||||||
sessionsDb.createSession(result.sessionId, 'gemini', result.workspacePath, result.sessionName);
|
let createdAt: string | undefined;
|
||||||
|
let updatedAt: string | undefined;
|
||||||
|
try {
|
||||||
|
const stat = await fsp.stat(file);
|
||||||
|
createdAt = stat.birthtime.toISOString();
|
||||||
|
updatedAt = stat.mtime.toISOString();
|
||||||
|
} catch {
|
||||||
|
// Ignore stat failures and let DB defaults handle created_at/updated_at.
|
||||||
|
}
|
||||||
|
sessionsDb.createSession(
|
||||||
|
result.sessionId,
|
||||||
|
'gemini',
|
||||||
|
result.workspacePath,
|
||||||
|
result.sessionName,
|
||||||
|
createdAt,
|
||||||
|
updatedAt,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import express from 'express';
|
|||||||
import path from 'path';
|
import path from 'path';
|
||||||
import os from 'os';
|
import os from 'os';
|
||||||
import { promises as fsPromises } from 'fs';
|
import { promises as fsPromises } from 'fs';
|
||||||
import { sessionNamesDb } from '@/shared/database/repositories/session-names.js';
|
import { sessionsDb } from '@/shared/database/repositories/sessions.db.js';
|
||||||
import { extractProjectDirectory } from '../../../projects.js';
|
import { extractProjectDirectory } from '../../../projects.js';
|
||||||
import { authenticateToken } from '../auth/auth.middleware.js';
|
import { authenticateToken } from '../auth/auth.middleware.js';
|
||||||
|
|
||||||
@@ -27,7 +27,7 @@ router.put('/api/sessions/:sessionId/rename', authenticateToken, async (req, res
|
|||||||
if (!provider || !VALID_PROVIDERS.includes(provider)) {
|
if (!provider || !VALID_PROVIDERS.includes(provider)) {
|
||||||
return res.status(400).json({ error: `Provider must be one of: ${VALID_PROVIDERS.join(', ')}` });
|
return res.status(400).json({ error: `Provider must be one of: ${VALID_PROVIDERS.join(', ')}` });
|
||||||
}
|
}
|
||||||
sessionNamesDb.createSessionName(safeSessionId, provider, summary.trim());
|
sessionsDb.createSessionName(safeSessionId, provider, summary.trim());
|
||||||
res.json({ success: true });
|
res.json({ success: true });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(`[API] Error renaming session ${req.params.sessionId}:`, error);
|
console.error(`[API] Error renaming session ${req.params.sessionId}:`, error);
|
||||||
|
|||||||
@@ -100,7 +100,24 @@ const onUpdate = async (
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (sessionId && workspacePath) {
|
if (sessionId && workspacePath) {
|
||||||
sessionsDb.createSession(sessionId, provider, workspacePath, sessionName);
|
let createdAt: string | undefined;
|
||||||
|
let updatedAt: string | undefined;
|
||||||
|
try {
|
||||||
|
const stat = await fsPromises.stat(filePath);
|
||||||
|
createdAt = stat.birthtime.toISOString();
|
||||||
|
updatedAt = stat.mtime.toISOString();
|
||||||
|
} catch {
|
||||||
|
// Ignore stat failures and let DB defaults handle created_at/updated_at.
|
||||||
|
}
|
||||||
|
|
||||||
|
sessionsDb.createSession(
|
||||||
|
sessionId,
|
||||||
|
provider,
|
||||||
|
workspacePath,
|
||||||
|
sessionName,
|
||||||
|
createdAt,
|
||||||
|
updatedAt,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import webPush from 'web-push';
|
|||||||
|
|
||||||
import { notificationPreferencesDb } from '@/shared/database/repositories/notification-preferences.js';
|
import { notificationPreferencesDb } from '@/shared/database/repositories/notification-preferences.js';
|
||||||
import { pushSubscriptionsDb } from '@/shared/database/repositories/push-subscriptions.js';
|
import { pushSubscriptionsDb } from '@/shared/database/repositories/push-subscriptions.js';
|
||||||
import { sessionNamesDb } from '@/shared/database/repositories/session-names.js';
|
import { sessionsDb } from '@/shared/database/repositories/sessions.db.js';
|
||||||
|
|
||||||
type NotificationKind = 'action_required' | 'stop' | 'error' | 'info' | string;
|
type NotificationKind = 'action_required' | 'stop' | 'error' | 'info' | string;
|
||||||
|
|
||||||
@@ -126,7 +126,7 @@ function resolveSessionName(event: NotificationEvent): string | null {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return normalizeSessionName(sessionNamesDb.getSessionName(event.sessionId, event.provider));
|
return normalizeSessionName(sessionsDb.getSessionName(event.sessionId, event.provider));
|
||||||
}
|
}
|
||||||
|
|
||||||
function buildPushBody(event: NotificationEvent) {
|
function buildPushBody(event: NotificationEvent) {
|
||||||
@@ -314,4 +314,3 @@ export {
|
|||||||
notifyRunStopped,
|
notifyRunStopped,
|
||||||
notifyRunFailed,
|
notifyRunFailed,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ import {
|
|||||||
APP_CONFIG_TABLE_SCHEMA_SQL,
|
APP_CONFIG_TABLE_SCHEMA_SQL,
|
||||||
LAST_SCANNED_AT_SQL,
|
LAST_SCANNED_AT_SQL,
|
||||||
PUSH_SUBSCRIPTIONS_TABLE_SCHEMA_SQL,
|
PUSH_SUBSCRIPTIONS_TABLE_SCHEMA_SQL,
|
||||||
SESSION_NAMES_TABLE_SCHEMA_SQL,
|
|
||||||
SESSIONS_TABLE_SCHEMA_SQL,
|
SESSIONS_TABLE_SCHEMA_SQL,
|
||||||
USER_NOTIFICATION_PREFERENCES_TABLE_SCHEMA_SQL,
|
USER_NOTIFICATION_PREFERENCES_TABLE_SCHEMA_SQL,
|
||||||
VAPID_KEYS_TABLE_SCHEMA_SQL,
|
VAPID_KEYS_TABLE_SCHEMA_SQL,
|
||||||
@@ -45,15 +44,17 @@ export const runMigrations = (db: Database) => {
|
|||||||
db.exec(PUSH_SUBSCRIPTIONS_TABLE_SCHEMA_SQL);
|
db.exec(PUSH_SUBSCRIPTIONS_TABLE_SCHEMA_SQL);
|
||||||
db.exec("CREATE INDEX IF NOT EXISTS idx_push_subscriptions_user_id ON push_subscriptions(user_id)");
|
db.exec("CREATE INDEX IF NOT EXISTS idx_push_subscriptions_user_id ON push_subscriptions(user_id)");
|
||||||
|
|
||||||
// Create session_names table if it doesn't exist (for existing installations)
|
|
||||||
db.exec(SESSION_NAMES_TABLE_SCHEMA_SQL);
|
|
||||||
db.exec("CREATE INDEX IF NOT EXISTS idx_session_names_lookup ON session_names(session_id, provider)");
|
|
||||||
|
|
||||||
// Create sessions table if it doesn't exist (for existing installations)
|
// Create sessions table if it doesn't exist (for existing installations)
|
||||||
db.exec(SESSIONS_TABLE_SCHEMA_SQL);
|
db.exec(SESSIONS_TABLE_SCHEMA_SQL);
|
||||||
db.exec(
|
db.exec(
|
||||||
"CREATE INDEX IF NOT EXISTS idx_session_ids_lookup ON sessions(session_id)"
|
"CREATE INDEX IF NOT EXISTS idx_session_ids_lookup ON sessions(session_id)"
|
||||||
);
|
);
|
||||||
|
const sessionsTableInfo = db.prepare("PRAGMA table_info(sessions)").all() as { name: string }[];
|
||||||
|
const sessionColumnNames = sessionsTableInfo.map((col) => col.name);
|
||||||
|
addColumnToTableIfNotExists(db, "sessions", sessionColumnNames, "created_at", "DATETIME");
|
||||||
|
addColumnToTableIfNotExists(db, "sessions", sessionColumnNames, "updated_at", "DATETIME");
|
||||||
|
db.exec("UPDATE sessions SET created_at = COALESCE(created_at, CURRENT_TIMESTAMP)");
|
||||||
|
db.exec("UPDATE sessions SET updated_at = COALESCE(updated_at, CURRENT_TIMESTAMP)");
|
||||||
|
|
||||||
db.exec(WORK_SPACE_PATH_SQL);
|
db.exec(WORK_SPACE_PATH_SQL);
|
||||||
const workspaceOriginalPathsTableInfo = db.prepare("PRAGMA table_info(workspace_original_paths)").all() as { name: string }[];
|
const workspaceOriginalPathsTableInfo = db.prepare("PRAGMA table_info(workspace_original_paths)").all() as { name: string }[];
|
||||||
|
|||||||
@@ -1,106 +0,0 @@
|
|||||||
/**
|
|
||||||
* Session names repository.
|
|
||||||
*
|
|
||||||
* Stores provider-scoped custom names for sessions and exposes helpers
|
|
||||||
* to overlay those names onto in-memory session lists.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { getConnection } from '@/shared/database/connection.js';
|
|
||||||
import type {
|
|
||||||
SessionNameLookupRow,
|
|
||||||
SessionWithSummary,
|
|
||||||
} from '@/shared/database/types.js';
|
|
||||||
|
|
||||||
export const sessionNamesDb = {
|
|
||||||
/** Upserts a custom session name for a provider-scoped session id. */
|
|
||||||
createSessionName(sessionId: string, provider: string, customName: string): void {
|
|
||||||
const db = getConnection();
|
|
||||||
db.prepare(
|
|
||||||
`INSERT INTO session_names (session_id, provider, custom_name)
|
|
||||||
VALUES (?, ?, ?)
|
|
||||||
ON CONFLICT(session_id, provider)
|
|
||||||
DO UPDATE SET custom_name = excluded.custom_name, updated_at = CURRENT_TIMESTAMP`
|
|
||||||
).run(sessionId, provider, customName);
|
|
||||||
},
|
|
||||||
|
|
||||||
/** Alias to keep write semantics explicit when callers perform edits. */
|
|
||||||
updateSessionName(sessionId: string, provider: string, customName: string): void {
|
|
||||||
sessionNamesDb.createSessionName(sessionId, provider, customName);
|
|
||||||
},
|
|
||||||
|
|
||||||
/** Returns a custom name for one session/provider pair or null if unset. */
|
|
||||||
getSessionName(sessionId: string, provider: string): string | null {
|
|
||||||
const db = getConnection();
|
|
||||||
const row = db
|
|
||||||
.prepare(
|
|
||||||
'SELECT custom_name FROM session_names WHERE session_id = ? AND provider = ?'
|
|
||||||
)
|
|
||||||
.get(sessionId, provider) as { custom_name: string } | undefined;
|
|
||||||
return row?.custom_name ?? null;
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Batch lookup for multiple session ids.
|
|
||||||
* Returns a Map<sessionId, customName> for efficient overlay onto lists.
|
|
||||||
*/
|
|
||||||
getSessionNames(sessionIds: string[], provider: string): Map<string, string> {
|
|
||||||
if (sessionIds.length === 0) return new Map();
|
|
||||||
|
|
||||||
const db = getConnection();
|
|
||||||
const placeholders = sessionIds.map(() => '?').join(',');
|
|
||||||
const rows = db
|
|
||||||
.prepare(
|
|
||||||
`SELECT session_id, custom_name FROM session_names
|
|
||||||
WHERE session_id IN (${placeholders}) AND provider = ?`
|
|
||||||
)
|
|
||||||
.all(...sessionIds, provider) as SessionNameLookupRow[];
|
|
||||||
|
|
||||||
return new Map(rows.map((row) => [row.session_id, row.custom_name]));
|
|
||||||
},
|
|
||||||
|
|
||||||
/** Deletes a custom name. Returns true if a row was removed. */
|
|
||||||
deleteSessionName(sessionId: string, provider: string): boolean {
|
|
||||||
const db = getConnection();
|
|
||||||
return (
|
|
||||||
db
|
|
||||||
.prepare(
|
|
||||||
'DELETE FROM session_names WHERE session_id = ? AND provider = ?'
|
|
||||||
)
|
|
||||||
.run(sessionId, provider).changes > 0
|
|
||||||
);
|
|
||||||
},
|
|
||||||
|
|
||||||
// Legacy aliases used by existing routes/services
|
|
||||||
setName(sessionId: string, provider: string, customName: string): void {
|
|
||||||
sessionNamesDb.createSessionName(sessionId, provider, customName);
|
|
||||||
},
|
|
||||||
getName(sessionId: string, provider: string): string | null {
|
|
||||||
return sessionNamesDb.getSessionName(sessionId, provider);
|
|
||||||
},
|
|
||||||
getNames(sessionIds: string[], provider: string): Map<string, string> {
|
|
||||||
return sessionNamesDb.getSessionNames(sessionIds, provider);
|
|
||||||
},
|
|
||||||
deleteName(sessionId: string, provider: string): boolean {
|
|
||||||
return sessionNamesDb.deleteSessionName(sessionId, provider);
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Overlay custom names onto a session list in place.
|
|
||||||
* If a custom name exists, `summary` is replaced.
|
|
||||||
*/
|
|
||||||
export function applyCustomSessionNames(
|
|
||||||
sessions: SessionWithSummary[] | undefined | null,
|
|
||||||
provider: string
|
|
||||||
): void {
|
|
||||||
if (!sessions?.length) return;
|
|
||||||
|
|
||||||
const ids = sessions.map((session) => session.id);
|
|
||||||
const customNames = sessionNamesDb.getSessionNames(ids, provider);
|
|
||||||
for (const session of sessions) {
|
|
||||||
const customName = customNames.get(session.id);
|
|
||||||
if (customName) {
|
|
||||||
session.summary = customName;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,22 +1,92 @@
|
|||||||
import { workspaceOriginalPathsDb } from '@/shared/database/repositories/workspace-original-paths.db.js';
|
import { workspaceOriginalPathsDb } from '@/shared/database/repositories/workspace-original-paths.db.js';
|
||||||
import { getConnection } from '@/shared/database/connection.js';
|
import { getConnection } from '@/shared/database/connection.js';
|
||||||
|
import type { SessionWithSummary } from '@/shared/database/types.js';
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
// Queries
|
// Queries
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
type SessionNameLookupRow = {
|
||||||
|
session_id: string;
|
||||||
|
custom_name: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
function normalizeTimestamp(value?: string): string | null {
|
||||||
|
if (!value) return null;
|
||||||
|
|
||||||
|
const parsed = new Date(value);
|
||||||
|
if (Number.isNaN(parsed.getTime())) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return parsed.toISOString();
|
||||||
|
}
|
||||||
|
|
||||||
export const sessionsDb = {
|
export const sessionsDb = {
|
||||||
|
|
||||||
createSession(session_id: string, provider: string, workspacePath: string, customName?: string): void {
|
createSession(
|
||||||
|
session_id: string,
|
||||||
|
provider: string,
|
||||||
|
workspacePath: string,
|
||||||
|
customName?: string,
|
||||||
|
createdAt?: string,
|
||||||
|
updatedAt?: string,
|
||||||
|
): void {
|
||||||
const db = getConnection();
|
const db = getConnection();
|
||||||
|
const createdAtValue = normalizeTimestamp(createdAt);
|
||||||
|
const updatedAtValue = normalizeTimestamp(updatedAt);
|
||||||
|
|
||||||
// First, ensure the workspace path is recorded in the workspace_original_paths table
|
// First, ensure the workspace path is recorded in the workspace_original_paths table
|
||||||
// since it's a foreign key in the sessions table.
|
// since it's a foreign key in the sessions table.
|
||||||
workspaceOriginalPathsDb.createWorkspacePath(workspacePath);
|
workspaceOriginalPathsDb.createWorkspacePath(workspacePath);
|
||||||
|
|
||||||
db.prepare(
|
db.prepare(
|
||||||
'INSERT OR IGNORE INTO sessions (session_id, provider, custom_name, workspace_path) VALUES (?, ?, ?, ?)'
|
`INSERT INTO sessions (session_id, provider, custom_name, workspace_path, created_at, updated_at)
|
||||||
).run(session_id, provider, customName, workspacePath);
|
VALUES (?, ?, ?, ?, COALESCE(?, CURRENT_TIMESTAMP), COALESCE(?, CURRENT_TIMESTAMP))
|
||||||
|
ON CONFLICT(session_id) DO UPDATE SET updated_at = excluded.updated_at
|
||||||
|
WHERE sessions.provider = excluded.provider`
|
||||||
|
).run(session_id, provider, customName, workspacePath, createdAtValue, updatedAtValue);
|
||||||
|
},
|
||||||
|
|
||||||
|
/** Updates a custom session name for an existing session row. */
|
||||||
|
createSessionName(sessionId: string, provider: string, customName: string): void {
|
||||||
|
const db = getConnection();
|
||||||
|
db.prepare(
|
||||||
|
`UPDATE sessions
|
||||||
|
SET custom_name = ?, updated_at = CURRENT_TIMESTAMP
|
||||||
|
WHERE session_id = ? AND provider = ?`
|
||||||
|
).run(customName, sessionId, provider);
|
||||||
|
},
|
||||||
|
|
||||||
|
getSessionName(sessionId: string, provider: string): string | null {
|
||||||
|
const db = getConnection();
|
||||||
|
const row = db
|
||||||
|
.prepare(
|
||||||
|
`SELECT custom_name
|
||||||
|
FROM sessions
|
||||||
|
WHERE session_id = ? AND provider = ?`
|
||||||
|
)
|
||||||
|
.get(sessionId, provider) as { custom_name: string | null } | undefined;
|
||||||
|
|
||||||
|
return row?.custom_name ?? null;
|
||||||
|
},
|
||||||
|
|
||||||
|
getSessionNames(sessionIds: string[], provider: string): Map<string, string> {
|
||||||
|
if (sessionIds.length === 0) return new Map();
|
||||||
|
|
||||||
|
const db = getConnection();
|
||||||
|
const placeholders = sessionIds.map(() => '?').join(',');
|
||||||
|
const rows = db
|
||||||
|
.prepare(
|
||||||
|
`SELECT session_id, custom_name
|
||||||
|
FROM sessions
|
||||||
|
WHERE session_id IN (${placeholders})
|
||||||
|
AND provider = ?
|
||||||
|
AND custom_name IS NOT NULL`
|
||||||
|
)
|
||||||
|
.all(...sessionIds, provider) as SessionNameLookupRow[];
|
||||||
|
|
||||||
|
return new Map(rows.map((row) => [row.session_id, row.custom_name]));
|
||||||
},
|
},
|
||||||
|
|
||||||
deleteSession(session_id: string): void {
|
deleteSession(session_id: string): void {
|
||||||
@@ -24,59 +94,18 @@ export const sessionsDb = {
|
|||||||
db.prepare('DELETE FROM sessions WHERE session_id = ?').run(session_id);
|
db.prepare('DELETE FROM sessions WHERE session_id = ?').run(session_id);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
applyCustomSessionNames(sessions: SessionWithSummary[] | undefined | null, provider: string): void {
|
||||||
|
if (!sessions?.length) return;
|
||||||
|
|
||||||
// /** Inserts or updates a custom session name (upsert on session_id + provider). */
|
const ids = sessions.map((session) => session.id);
|
||||||
// setName(sessionId: string, provider: string, customName: string): void {
|
const customNames = sessionsDb.getSessionNames(ids, provider);
|
||||||
// const db = getConnection();
|
|
||||||
// db.prepare(
|
|
||||||
// `INSERT INTO session_names (session_id, provider, custom_name)
|
|
||||||
// VALUES (?, ?, ?)
|
|
||||||
// ON CONFLICT(session_id, provider)
|
|
||||||
// DO UPDATE SET custom_name = excluded.custom_name,
|
|
||||||
// updated_at = CURRENT_TIMESTAMP`
|
|
||||||
// ).run(sessionId, provider, customName);
|
|
||||||
// },
|
|
||||||
|
|
||||||
/** Returns the custom name for a single session, or null if unset. */
|
for (const session of sessions) {
|
||||||
// getName(sessionId: string, provider: string): string | null {
|
const customName = customNames.get(session.id);
|
||||||
// const db = getConnection();
|
if (customName) {
|
||||||
// const row = db
|
session.summary = customName;
|
||||||
// .prepare(
|
}
|
||||||
// 'SELECT custom_name FROM session_names WHERE session_id = ? AND provider = ?'
|
}
|
||||||
// )
|
},
|
||||||
// .get(sessionId, provider) as { custom_name: string } | undefined;
|
|
||||||
// return row?.custom_name ?? null;
|
|
||||||
// },
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Batch lookup for multiple session IDs.
|
|
||||||
* Returns a Map<sessionId, customName> for efficient overlay onto session lists.
|
|
||||||
*/
|
|
||||||
// getNames(sessionIds: string[], provider: string): Map<string, string> {
|
|
||||||
// if (sessionIds.length === 0) return new Map();
|
|
||||||
|
|
||||||
// const db = getConnection();
|
|
||||||
// const placeholders = sessionIds.map(() => '?').join(',');
|
|
||||||
// const rows = db
|
|
||||||
// .prepare(
|
|
||||||
// `SELECT session_id, custom_name FROM session_names
|
|
||||||
// WHERE session_id IN (${placeholders}) AND provider = ?`
|
|
||||||
// )
|
|
||||||
// .all(...sessionIds, provider) as SessionNameLookupRow[];
|
|
||||||
|
|
||||||
// return new Map(rows.map((r) => [r.session_id, r.custom_name]));
|
|
||||||
// },
|
|
||||||
|
|
||||||
/** Removes a custom session name. Returns true if a row was deleted. */
|
|
||||||
// deleteName(sessionId: string, provider: string): boolean {
|
|
||||||
// const db = getConnection();
|
|
||||||
// return (
|
|
||||||
// db
|
|
||||||
// .prepare(
|
|
||||||
// 'DELETE FROM session_names WHERE session_id = ? AND provider = ?'
|
|
||||||
// )
|
|
||||||
// .run(sessionId, provider).changes > 0
|
|
||||||
// );
|
|
||||||
// },
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -69,24 +69,14 @@ CREATE TABLE IF NOT EXISTS push_subscriptions (
|
|||||||
);
|
);
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export const SESSION_NAMES_TABLE_SCHEMA_SQL = `
|
|
||||||
CREATE TABLE IF NOT EXISTS session_names (
|
|
||||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
||||||
session_id TEXT NOT NULL,
|
|
||||||
provider TEXT NOT NULL DEFAULT 'claude',
|
|
||||||
custom_name TEXT NOT NULL,
|
|
||||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
|
||||||
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
|
||||||
UNIQUE(session_id, provider)
|
|
||||||
);
|
|
||||||
`;
|
|
||||||
|
|
||||||
export const SESSIONS_TABLE_SCHEMA_SQL = `
|
export const SESSIONS_TABLE_SCHEMA_SQL = `
|
||||||
CREATE TABLE IF NOT EXISTS sessions (
|
CREATE TABLE IF NOT EXISTS sessions (
|
||||||
session_id TEXT PRIMARY KEY NOT NULL,
|
session_id TEXT PRIMARY KEY NOT NULL,
|
||||||
provider TEXT NOT NULL,
|
provider TEXT NOT NULL,
|
||||||
custom_name TEXT,
|
custom_name TEXT,
|
||||||
workspace_path TEXT NOT NULL,
|
workspace_path TEXT NOT NULL,
|
||||||
|
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||||
FOREIGN KEY (workspace_path) REFERENCES workspace_original_paths(workspace_path)
|
FOREIGN KEY (workspace_path) REFERENCES workspace_original_paths(workspace_path)
|
||||||
ON DELETE CASCADE
|
ON DELETE CASCADE
|
||||||
ON UPDATE CASCADE
|
ON UPDATE CASCADE
|
||||||
@@ -143,9 +133,6 @@ ${VAPID_KEYS_TABLE_SCHEMA_SQL}
|
|||||||
${PUSH_SUBSCRIPTIONS_TABLE_SCHEMA_SQL}
|
${PUSH_SUBSCRIPTIONS_TABLE_SCHEMA_SQL}
|
||||||
CREATE INDEX IF NOT EXISTS idx_push_subscriptions_user_id ON push_subscriptions(user_id);
|
CREATE INDEX IF NOT EXISTS idx_push_subscriptions_user_id ON push_subscriptions(user_id);
|
||||||
|
|
||||||
${SESSION_NAMES_TABLE_SCHEMA_SQL}
|
|
||||||
CREATE INDEX IF NOT EXISTS idx_session_names_lookup ON session_names(session_id, provider);
|
|
||||||
|
|
||||||
${SESSIONS_TABLE_SCHEMA_SQL}
|
${SESSIONS_TABLE_SCHEMA_SQL}
|
||||||
CREATE INDEX IF NOT EXISTS idx_session_ids_lookup ON sessions(session_id);
|
CREATE INDEX IF NOT EXISTS idx_session_ids_lookup ON sessions(session_id);
|
||||||
|
|
||||||
|
|||||||
@@ -101,6 +101,8 @@ export type SessionsRow = {
|
|||||||
provider: LLMProvider;
|
provider: LLMProvider;
|
||||||
workspace_path: string;
|
workspace_path: string;
|
||||||
custom_name: string | null;
|
custom_name: string | null;
|
||||||
|
created_at: string;
|
||||||
|
updated_at: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type SessionNameRow = {
|
export type SessionNameRow = {
|
||||||
|
|||||||
Reference in New Issue
Block a user