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
|
// Preview Management - Start preview server
|
||||||
app.post('/claude/api/claude/sessions/:id/preview/start', requireAuth, async (req, res) => {
|
app.post('/claude/api/claude/sessions/:id/preview/start', requireAuth, async (req, res) => {
|
||||||
try {
|
try {
|
||||||
@@ -1006,6 +1074,21 @@ app.delete('/api/projects/:id', requireAuth, (req, res) => {
|
|||||||
WHERE id = ?
|
WHERE id = ?
|
||||||
`).run(now, validatedId);
|
`).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 });
|
res.json({ success: true });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error soft deleting project:', 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 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;
|
return db;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user