feat: add session move endpoint and project-session cascading delete

- Add sessions table to database with projectId and deletedAt columns
- Create POST /api/sessions/:id/move endpoint to reassign sessions
- Update DELETE /api/projects/:id to cascade soft-delete to sessions
- Support moving sessions between projects or to unassigned state
- Handle both active (in-memory) and historical sessions

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
uroma
2026-01-19 16:55:15 +00:00
Unverified
parent 76d914397f
commit 1fbc565a66
6 changed files with 2335 additions and 0 deletions

BIN
database.sqlite Normal file

Binary file not shown.

BIN
database.sqlite-shm Normal file

Binary file not shown.

BIN
database.sqlite-wal Normal file

Binary file not shown.

File diff suppressed because it is too large Load Diff

View File

@@ -633,6 +633,74 @@ app.delete('/claude/api/claude/sessions/:id', requireAuth, (req, res) => {
}
});
// Move session to different project
app.post('/api/sessions/:id/move', requireAuth, (req, res) => {
try {
const { id } = req.params;
const { projectId } = req.body;
// Check if session exists (in-memory or historical)
let session = claudeService.sessions.get(id);
if (!session) {
// Check historical sessions
const historicalSessions = claudeService.loadHistoricalSessions();
session = historicalSessions.find(s => s.id === id);
}
if (!session) {
return res.status(404).json({ error: 'Session not found' });
}
// If projectId is provided, validate it exists and is not deleted
if (projectId !== null && projectId !== undefined) {
const validatedId = validateProjectId(projectId);
if (!validatedId) {
return res.status(400).json({ error: 'Invalid project ID' });
}
const project = db.prepare(`
SELECT id FROM projects
WHERE id = ? AND deletedAt IS NULL
`).get(validatedId);
if (!project) {
return res.status(404).json({ error: 'Project not found' });
}
// Update session's metadata with projectId
if (claudeService.sessions.get(id)) {
// Active session - update metadata
const activeSession = claudeService.sessions.get(id);
activeSession.metadata.projectId = validatedId;
} else {
// Historical session - update in database
db.prepare(`
INSERT OR REPLACE INTO sessions (id, projectId, deletedAt)
VALUES (?, ?, NULL)
`).run(id, validatedId);
}
} else {
// Move to unassigned (projectId = null)
if (claudeService.sessions.get(id)) {
// Active session - remove projectId from metadata
const activeSession = claudeService.sessions.get(id);
delete activeSession.metadata.projectId;
} else {
// Historical session - update in database
db.prepare(`
INSERT OR REPLACE INTO sessions (id, projectId, deletedAt)
VALUES (?, NULL, NULL)
`).run(id);
}
}
res.json({ success: true });
} catch (error) {
console.error('Error moving session:', error);
res.status(500).json({ error: 'Failed to move session' });
}
});
// Preview Management - Start preview server
app.post('/claude/api/claude/sessions/:id/preview/start', requireAuth, async (req, res) => {
try {
@@ -1006,6 +1074,21 @@ app.delete('/api/projects/:id', requireAuth, (req, res) => {
WHERE id = ?
`).run(now, validatedId);
// Soft delete all sessions associated with this project
db.prepare(`
UPDATE sessions
SET deletedAt = ?
WHERE projectId = ?
`).run(now, validatedId);
// Also update in-memory sessions
for (const [sessionId, session] of claudeService.sessions.entries()) {
if (session.metadata.projectId === validatedId) {
// Mark as deleted in metadata
session.metadata.deletedAt = now;
}
}
res.json({ success: true });
} catch (error) {
console.error('Error soft deleting project:', error);

View File

@@ -48,6 +48,26 @@ function initializeDatabase() {
CREATE INDEX IF NOT EXISTS idx_projects_name ON projects(name)
`);
// Create sessions table
db.exec(`
CREATE TABLE IF NOT EXISTS sessions (
id TEXT PRIMARY KEY,
projectId INTEGER NULL,
deletedAt TEXT NULL,
FOREIGN KEY (projectId) REFERENCES projects(id) ON DELETE SET NULL
)
`);
// Create index on projectId for efficient session queries
db.exec(`
CREATE INDEX IF NOT EXISTS idx_sessions_projectId ON sessions(projectId)
`);
// Create index on deletedAt for efficient soft-delete queries
db.exec(`
CREATE INDEX IF NOT EXISTS idx_sessions_deletedAt ON sessions(deletedAt)
`);
return db;
}