const express = require('express'); const sqlite3 = require('sqlite3').verbose(); const cors = require('cors'); const path = require('path'); const multer = require('multer'); const fs = require('fs'); const app = express(); // Use DATA_DIR env var if set, otherwise use local "data" folder const DATA_DIR = process.env.DATA_DIR || path.join(__dirname, 'data'); const dbPath = path.join(DATA_DIR, 'db.sqlite'); const db = new sqlite3.Database(dbPath); const upload = multer({ dest: path.join(DATA_DIR, 'uploads/') }); app.use(cors()); app.use(express.json()); app.use('/uploads', express.static(path.join(DATA_DIR, 'uploads'))); // Create uploads directory if it doesn't exist fs.mkdirSync(path.join(DATA_DIR, 'uploads'), { recursive: true }); // Initialize database and ensure counters table exists db.serialize(() => { db.run(`CREATE TABLE IF NOT EXISTS counters ( id INTEGER PRIMARY KEY AUTOINCREMENT, value INTEGER NOT NULL DEFAULT 0, name TEXT NOT NULL DEFAULT 'Counter', image TEXT )`); }); // Get all counters app.get('/api/counters', (req, res) => { db.all('SELECT * FROM counters', (err, rows) => { if (err) return res.status(500).json({ error: err.message }); // Ensure id and value are numbers const counters = rows.map(row => ({ ...row, id: Number(row.id), value: Number(row.value) })); res.json(counters); }); }); // Create a new counter with image app.post('/api/counters', upload.single('image'), (req, res) => { const { name = 'Counter', value = 0 } = req.body; const image = req.file ? `/uploads/${req.file.filename}` : null; db.run('INSERT INTO counters (name, value, image) VALUES (?, ?, ?)', [name, value, image], function (err) { if (err) return res.status(500).json({ error: err.message }); res.json({ id: Number(this.lastID), name, value: Number(value), image }); }); }); // Update a counter (now supports image upload) app.put('/api/counters/:id', upload.single('image'), (req, res) => { const { name, value } = req.body; const id = req.params.id; let image = null; // If a new image is uploaded, get its path if (req.file) { image = `/uploads/${req.file.filename}`; // Optionally, delete the old image file db.get('SELECT image FROM counters WHERE id = ?', [id], (err, row) => { if (row && row.image) { const oldImagePath = path.join(__dirname, '..', row.image); fs.unlink(oldImagePath, () => { }); // Ignore errors } }); } // Build dynamic SQL and params let fields = []; let params = []; if (name !== undefined) { fields.push('name = ?'); params.push(name); } if (value !== undefined) { fields.push('value = ?'); params.push(value); } if (image !== null) { fields.push('image = ?'); params.push(image); } if (fields.length === 0) return res.status(400).json({ error: 'No valid fields to update.' }); params.push(id); const sql = `UPDATE counters SET ${fields.join(', ')} WHERE id = ?`; db.run(sql, params, function (err) { if (err) return res.status(500).json({ error: err.message }); res.json({ updated: this.changes }); }); }); // Delete a counter app.delete('/api/counters/:id', (req, res) => { db.run('DELETE FROM counters WHERE id = ?', [req.params.id], function (err) { if (err) return res.status(500).json({ error: err.message }); res.json({ deleted: this.changes }); }); }); // Serve static frontend files const clientBuildPath = path.join(__dirname, '..', 'frontend', 'dist'); app.use(express.static(clientBuildPath)); // For SPA: serve index.html for any unknown route (after API and uploads) app.get('/*path', (req, res) => { if (req.path.startsWith('/api') || req.path.startsWith('/uploads')) return res.status(404).end(); res.sendFile(path.join(clientBuildPath, 'index.html')); }); const PORT = 3000; app.listen(PORT, () => { console.log(`API server running on http://localhost:${PORT}`); }); process.on('SIGTERM', () => { console.log('SIGTERM received, shutting down gracefully...'); db.close(() => { console.log('Database connection closed.'); process.exit(0); }); });