feat: add soft delete, restore, permanent delete, and recycle bin endpoints

- Add DELETE /api/projects/:id - Soft delete project (sets deletedAt)
- Add POST /api/projects/:id/restore - Restore from recycle bin
- Add DELETE /api/projects/:id/permanent - Permanent delete
- Add GET /api/recycle-bin - List deleted items sorted by deletedAt DESC

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
uroma
2026-01-19 16:52:45 +00:00
Unverified
parent 3e72d6c0ba
commit e6caf9399b

131
server.js
View File

@@ -977,6 +977,137 @@ app.put('/api/projects/:id', requireAuth, (req, res) => {
}
});
// DELETE /api/projects/:id - Soft delete project
app.delete('/api/projects/:id', requireAuth, (req, res) => {
try {
const { id } = req.params;
// Validate ID
const validatedId = validateProjectId(id);
if (!validatedId) {
return res.status(400).json({ error: 'Invalid project ID' });
}
// Check if project exists and is not already deleted
const existing = db.prepare(`
SELECT id FROM projects
WHERE id = ? AND deletedAt IS NULL
`).get(validatedId);
if (!existing) {
return res.status(404).json({ error: 'Project not found' });
}
// Soft delete by setting deletedAt
const now = new Date().toISOString();
db.prepare(`
UPDATE projects
SET deletedAt = ?
WHERE id = ?
`).run(now, validatedId);
res.json({ success: true });
} catch (error) {
console.error('Error soft deleting project:', error);
res.status(500).json({ error: 'Failed to delete project' });
}
});
// POST /api/projects/:id/restore - Restore project from recycle bin
app.post('/api/projects/:id/restore', requireAuth, (req, res) => {
try {
const { id } = req.params;
// Validate ID
const validatedId = validateProjectId(id);
if (!validatedId) {
return res.status(400).json({ error: 'Invalid project ID' });
}
// Check if project exists and is in recycle bin
const existing = db.prepare(`
SELECT id FROM projects
WHERE id = ? AND deletedAt IS NOT NULL
`).get(validatedId);
if (!existing) {
return res.status(404).json({ error: 'Project not found in recycle bin' });
}
// Restore by setting deletedAt to NULL
db.prepare(`
UPDATE projects
SET deletedAt = NULL
WHERE id = ?
`).run(validatedId);
res.json({ success: true });
} catch (error) {
console.error('Error restoring project:', error);
res.status(500).json({ error: 'Failed to restore project' });
}
});
// DELETE /api/projects/:id/permanent - Permanently delete project
app.delete('/api/projects/:id/permanent', requireAuth, (req, res) => {
try {
const { id } = req.params;
// Validate ID
const validatedId = validateProjectId(id);
if (!validatedId) {
return res.status(400).json({ error: 'Invalid project ID' });
}
// Check if project exists
const existing = db.prepare(`
SELECT id FROM projects
WHERE id = ?
`).get(validatedId);
if (!existing) {
return res.status(404).json({ error: 'Project not found' });
}
// Permanently delete the project
db.prepare(`
DELETE FROM projects
WHERE id = ?
`).run(validatedId);
res.json({ success: true });
} catch (error) {
console.error('Error permanently deleting project:', error);
res.status(500).json({ error: 'Failed to permanently delete project' });
}
});
// GET /api/recycle-bin - List deleted items
app.get('/api/recycle-bin', requireAuth, (req, res) => {
try {
const items = db.prepare(`
SELECT id, name, description, icon, path, deletedAt
FROM projects
WHERE deletedAt IS NOT NULL
ORDER BY deletedAt DESC
`).all();
// Add sessionCount (0 for now, will be implemented in Task 4)
const itemsWithSessionCount = items.map(item => ({
...item,
sessionCount: 0
}));
res.json({
success: true,
items: itemsWithSessionCount
});
} catch (error) {
console.error('Error listing recycle bin:', error);
res.status(500).json({ error: 'Failed to list recycle bin' });
}
});
// ===== Terminal API Endpoints =====
// Create a new terminal