Add Docker Compose deployment

- Dockerfile: Node.js 20 Alpine, single-stage build
- docker-compose.yml: backend + nginx, named volume for SQLite
- server.js: updated for Docker (env vars for PORT/DATA_DIR)
- nginx.conf: updated for Docker networking (backend hostname)
- README.md: full deployment instructions
This commit is contained in:
Ada
2026-04-06 09:04:24 -04:00
parent c283e591a4
commit c995161fa6
7 changed files with 270 additions and 0 deletions

103
deploy/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 = process.env.PORT || 3000;
const DATA_DIR = process.env.DATA_DIR || '/app/data';
const DB_PATH = path.join(DATA_DIR, 'projects.db');
// 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}`);
});