feat: add project CRUD API endpoints (SQLite)
Added three new API endpoints for managing projects using SQLite: - GET /api/projects - Lists all active projects (deletedAt IS NULL) * Sorts by lastActivity DESC * Returns id, name, description, icon, color, path, sessionCount, createdAt, lastActivity - POST /api/projects - Creates new project * Required fields: name, path * Optional fields: description, icon (default '📁'), color (default '#4a9eff') * Validates required fields and checks for duplicate names * Returns 201 status on success - PUT /api/projects/:id - Updates existing project * Allows updating: name, description, icon, color, path * Only updates projects where deletedAt IS NULL * Returns 404 if project not found * Validates duplicate names on name change All endpoints use synchronous better-sqlite3 API with parameterized queries. SessionCount set to 0 for now (will be implemented in Task 3). Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
167
server.js
167
server.js
@@ -8,6 +8,7 @@ const hljs = require('highlight.js');
|
||||
const WebSocket = require('ws');
|
||||
const ClaudeCodeService = require('./services/claude-service');
|
||||
const terminalService = require('./services/terminal-service');
|
||||
const { db } = require('./services/database');
|
||||
|
||||
const app = express();
|
||||
const md = new MarkdownIt({
|
||||
@@ -758,6 +759,172 @@ Created via Claude Code Web IDE
|
||||
}
|
||||
});
|
||||
|
||||
// ============================================
|
||||
// Project CRUD API Endpoints (SQLite)
|
||||
// ============================================
|
||||
|
||||
// GET /api/projects - List all active projects
|
||||
app.get('/api/projects', requireAuth, (req, res) => {
|
||||
try {
|
||||
const projects = db.prepare(`
|
||||
SELECT id, name, description, icon, color, path, createdAt, lastActivity
|
||||
FROM projects
|
||||
WHERE deletedAt IS NULL
|
||||
ORDER BY lastActivity DESC
|
||||
`).all();
|
||||
|
||||
// Add sessionCount (0 for now, will be implemented in Task 3)
|
||||
const projectsWithSessionCount = projects.map(project => ({
|
||||
...project,
|
||||
sessionCount: 0
|
||||
}));
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
projects: projectsWithSessionCount
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Error listing projects:', error);
|
||||
res.status(500).json({ error: 'Failed to list projects' });
|
||||
}
|
||||
});
|
||||
|
||||
// POST /api/projects - Create new project
|
||||
app.post('/api/projects', requireAuth, (req, res) => {
|
||||
try {
|
||||
const { name, path: projectPath, description, icon, color } = req.body;
|
||||
|
||||
// Validate required fields
|
||||
if (!name || !projectPath) {
|
||||
return res.status(400).json({ error: 'Name and path are required' });
|
||||
}
|
||||
|
||||
// Check for duplicate names (only among non-deleted projects)
|
||||
const existing = db.prepare(`
|
||||
SELECT id FROM projects
|
||||
WHERE name = ? AND deletedAt IS NULL
|
||||
`).get(name);
|
||||
|
||||
if (existing) {
|
||||
return res.status(409).json({ error: 'Project with this name already exists' });
|
||||
}
|
||||
|
||||
const now = new Date().toISOString();
|
||||
|
||||
// Set defaults
|
||||
const projectIcon = icon || '📁';
|
||||
const projectColor = color || '#4a9eff';
|
||||
|
||||
const result = db.prepare(`
|
||||
INSERT INTO projects (name, description, icon, color, path, createdAt, lastActivity)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?)
|
||||
`).run(name, description || null, projectIcon, projectColor, projectPath, now, now);
|
||||
|
||||
// Get the inserted project
|
||||
const project = db.prepare(`
|
||||
SELECT id, name, description, icon, color, path, createdAt, lastActivity
|
||||
FROM projects
|
||||
WHERE id = ?
|
||||
`).get(result.lastInsertRowid);
|
||||
|
||||
res.status(201).json({
|
||||
success: true,
|
||||
project: {
|
||||
...project,
|
||||
sessionCount: 0
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Error creating project:', error);
|
||||
res.status(500).json({ error: 'Failed to create project' });
|
||||
}
|
||||
});
|
||||
|
||||
// PUT /api/projects/:id - Update project
|
||||
app.put('/api/projects/:id', requireAuth, (req, res) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
const { name, description, icon, color, path: projectPath } = req.body;
|
||||
|
||||
// Check if project exists and is not deleted
|
||||
const existing = db.prepare(`
|
||||
SELECT id FROM projects
|
||||
WHERE id = ? AND deletedAt IS NULL
|
||||
`).get(id);
|
||||
|
||||
if (!existing) {
|
||||
return res.status(404).json({ error: 'Project not found' });
|
||||
}
|
||||
|
||||
// If updating name, check for duplicates
|
||||
if (name) {
|
||||
const duplicate = db.prepare(`
|
||||
SELECT id FROM projects
|
||||
WHERE name = ? AND id != ? AND deletedAt IS NULL
|
||||
`).get(name, id);
|
||||
|
||||
if (duplicate) {
|
||||
return res.status(409).json({ error: 'Project with this name already exists' });
|
||||
}
|
||||
}
|
||||
|
||||
// Build update query dynamically based on provided fields
|
||||
const updates = [];
|
||||
const values = [];
|
||||
|
||||
if (name !== undefined) {
|
||||
updates.push('name = ?');
|
||||
values.push(name);
|
||||
}
|
||||
if (description !== undefined) {
|
||||
updates.push('description = ?');
|
||||
values.push(description);
|
||||
}
|
||||
if (icon !== undefined) {
|
||||
updates.push('icon = ?');
|
||||
values.push(icon);
|
||||
}
|
||||
if (color !== undefined) {
|
||||
updates.push('color = ?');
|
||||
values.push(color);
|
||||
}
|
||||
if (projectPath !== undefined) {
|
||||
updates.push('path = ?');
|
||||
values.push(projectPath);
|
||||
}
|
||||
|
||||
if (updates.length === 0) {
|
||||
return res.status(400).json({ error: 'No fields to update' });
|
||||
}
|
||||
|
||||
values.push(id);
|
||||
|
||||
db.prepare(`
|
||||
UPDATE projects
|
||||
SET ${updates.join(', ')}
|
||||
WHERE id = ? AND deletedAt IS NULL
|
||||
`).run(...values);
|
||||
|
||||
// Get the updated project
|
||||
const project = db.prepare(`
|
||||
SELECT id, name, description, icon, color, path, createdAt, lastActivity
|
||||
FROM projects
|
||||
WHERE id = ?
|
||||
`).get(id);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
project: {
|
||||
...project,
|
||||
sessionCount: 0
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Error updating project:', error);
|
||||
res.status(500).json({ error: 'Failed to update project' });
|
||||
}
|
||||
});
|
||||
|
||||
// ===== Terminal API Endpoints =====
|
||||
|
||||
// Create a new terminal
|
||||
|
||||
Reference in New Issue
Block a user