Initial commit: Project Tracker - Node.js + Express + SQLite backend, dark-themed frontend\n\n- Express REST API (GET/POST/PUT/DELETE /api/projects)\n- SQLite database with better-sqlite3\n- Dark-themed single-page UI with filter bar and drill-down panel\n- nginx reverse proxy config\n- Deployment script

This commit is contained in:
Ada
2026-04-05 21:07:13 -04:00
commit bd658a822b
6 changed files with 889 additions and 0 deletions

103
backend/server.js Normal file
View File

@@ -0,0 +1,103 @@
const express = require('express');
const sqlite3 = require('better-sqlite3');
const cors = require('cors');
const fs = require('fs');
const path = require('path');
const app = express();
const PORT = 3000;
const DB_PATH = '/opt/project-tracker/data/projects.db';
const DATA_DIR = '/opt/project-tracker/data';
// Ensure data directory exists
if (!fs.existsSync(DATA_DIR)) {
fs.mkdirSync(DATA_DIR, { recursive: true });
}
// Initialize database
const db = new sqlite3(DB_PATH);
// Create table
db.exec(`
CREATE TABLE IF NOT EXISTS projects (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
priority TEXT NOT NULL,
url TEXT DEFAULT '',
notes TEXT DEFAULT '',
status TEXT DEFAULT 'Backlog',
owner TEXT DEFAULT 'Ada',
tags TEXT DEFAULT '',
created_at TEXT DEFAULT (datetime('now')),
updated_at TEXT DEFAULT (datetime('now'))
)
`);
// Middleware
app.use(cors());
app.use(express.json());
// GET all projects
app.get('/api/projects', (req, res) => {
const { status } = req.query;
let stmt = status
? db.prepare('SELECT * FROM projects WHERE status = ? ORDER BY CASE priority WHEN \'High\' THEN 1 WHEN \'Med-High\' THEN 2 WHEN \'Medium\' THEN 3 WHEN \'Low\' THEN 4 ELSE 5 END, created_at DESC')
: db.prepare('SELECT * FROM projects ORDER BY CASE priority WHEN \'High\' THEN 1 WHEN \'Med-High\' THEN 2 WHEN \'Medium\' THEN 3 WHEN \'Low\' THEN 4 ELSE 5 END, created_at DESC');
const rows = status ? stmt.all(status) : stmt.all();
res.json(rows);
});
// GET single project
app.get('/api/projects/:id', (req, res) => {
const stmt = db.prepare('SELECT * FROM projects WHERE id = ?');
const row = stmt.get(req.params.id);
if (row) res.json(row);
else res.status(404).json({ error: 'Not found' });
});
// POST create project
app.post('/api/projects', (req, res) => {
const { name, priority, url, notes, status, owner, tags } = req.body;
if (!name || !priority) {
return res.status(400).json({ error: 'name and priority are required' });
}
const stmt = db.prepare(`
INSERT INTO projects (name, priority, url, notes, status, owner, tags)
VALUES (?, ?, ?, ?, ?, ?, ?)
`);
const result = stmt.run(name, priority, url || '', notes || '', status || 'Backlog', owner || 'Ada', tags || '');
res.json({ id: result.lastInsertRowid, message: 'Created' });
});
// PUT update project
app.put('/api/projects/:id', (req, res) => {
const { name, priority, url, notes, status, owner, tags } = req.body;
const fields = [];
const values = [];
if (name !== undefined) { fields.push('name = ?'); values.push(name); }
if (priority !== undefined) { fields.push('priority = ?'); values.push(priority); }
if (url !== undefined) { fields.push('url = ?'); values.push(url); }
if (notes !== undefined) { fields.push('notes = ?'); values.push(notes); }
if (status !== undefined) { fields.push('status = ?'); values.push(status); }
if (owner !== undefined) { fields.push('owner = ?'); values.push(owner); }
if (tags !== undefined) { fields.push('tags = ?'); values.push(tags); }
fields.push('updated_at = datetime(\'now\')');
values.push(req.params.id);
if (fields.length === 1) {
return res.status(400).json({ error: 'No fields to update' });
}
const stmt = db.prepare(`UPDATE projects SET ${fields.join(', ')} WHERE id = ?`);
stmt.run(...values);
res.json({ message: 'Updated' });
});
// DELETE project
app.delete('/api/projects/:id', (req, res) => {
const stmt = db.prepare('DELETE FROM projects WHERE id = ?');
stmt.run(req.params.id);
res.json({ message: 'Deleted' });
});
app.listen(PORT, '0.0.0.0', () => {
console.log(`Project Tracker API running on port ${PORT}`);
});