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:
uroma
2026-01-19 16:46:33 +00:00
Unverified
parent 4eadd94e8d
commit 5da440e19e

167
server.js
View File

@@ -8,6 +8,7 @@ const hljs = require('highlight.js');
const WebSocket = require('ws'); const WebSocket = require('ws');
const ClaudeCodeService = require('./services/claude-service'); const ClaudeCodeService = require('./services/claude-service');
const terminalService = require('./services/terminal-service'); const terminalService = require('./services/terminal-service');
const { db } = require('./services/database');
const app = express(); const app = express();
const md = new MarkdownIt({ 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 ===== // ===== Terminal API Endpoints =====
// Create a new terminal // Create a new terminal