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:
BIN
database.sqlite
Normal file
BIN
database.sqlite
Normal file
Binary file not shown.
BIN
database.sqlite-shm
Normal file
BIN
database.sqlite-shm
Normal file
Binary file not shown.
BIN
database.sqlite-wal
Normal file
BIN
database.sqlite-wal
Normal file
Binary file not shown.
2232
docs/plans/2025-01-19-project-session-organization.md
Normal file
2232
docs/plans/2025-01-19-project-session-organization.md
Normal file
File diff suppressed because it is too large
Load Diff
83
server.js
83
server.js
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user