const express = require('express'); const { Pool } = require('pg'); const path = require('path'); const app = express(); app.use(express.json()); const pool = new Pool({ host: process.env.DB_HOST || 'dokploy-postgres', port: parseInt(process.env.DB_PORT || '5432'), user: process.env.DB_USER || 'dokploy', password: process.env.DB_PASSWORD || '', database: process.env.DB_NAME || 'calvana', }); // Health check app.get('/api/health', async (req, res) => { try { await pool.query('SELECT 1'); res.json({ status: 'ok', db: 'connected' }); } catch (e) { res.status(500).json({ status: 'error', db: e.message }); } }); // GET ships app.get('/api/ships', async (req, res) => { try { const { rows } = await pool.query( 'SELECT * FROM ships ORDER BY created_at DESC' ); res.json(rows); } catch (e) { res.status(500).json({ error: e.message }); } }); // POST ship app.post('/api/ships', async (req, res) => { const { title, status, metric, details } = req.body; if (!title) return res.status(400).json({ error: 'title required' }); try { const { rows } = await pool.query( 'INSERT INTO ships (title, status, metric, details) VALUES ($1, $2, $3, $4) RETURNING *', [title, status || 'planned', metric || null, details || null] ); res.status(201).json(rows[0]); } catch (e) { res.status(500).json({ error: e.message }); } }); // PATCH ship app.patch('/api/ships/:id', async (req, res) => { const { id } = req.params; const { title, status, metric, details } = req.body; try { const sets = []; const vals = []; let i = 1; if (title !== undefined) { sets.push(`title=$${i++}`); vals.push(title); } if (status !== undefined) { sets.push(`status=$${i++}`); vals.push(status); } if (metric !== undefined) { sets.push(`metric=$${i++}`); vals.push(metric); } if (details !== undefined) { sets.push(`details=$${i++}`); vals.push(details); } if (sets.length === 0) return res.status(400).json({ error: 'nothing to update' }); sets.push(`updated_at=NOW()`); vals.push(id); const { rows } = await pool.query( `UPDATE ships SET ${sets.join(', ')} WHERE id=$${i} RETURNING *`, vals ); if (rows.length === 0) return res.status(404).json({ error: 'not found' }); res.json(rows[0]); } catch (e) { res.status(500).json({ error: e.message }); } }); // DELETE ship app.delete('/api/ships/:id', async (req, res) => { const { id } = req.params; try { const { rows } = await pool.query( 'DELETE FROM ships WHERE id=$1 RETURNING *', [id] ); if (rows.length === 0) return res.status(404).json({ error: 'not found' }); res.json({ deleted: true, ship: rows[0] }); } catch (e) { res.status(500).json({ error: e.message }); } }); // DELETE oops app.delete('/api/oops/:id', async (req, res) => { const { id } = req.params; try { const { rows } = await pool.query( 'DELETE FROM oops WHERE id=$1 RETURNING *', [id] ); if (rows.length === 0) return res.status(404).json({ error: 'not found' }); res.json({ deleted: true, oops: rows[0] }); } catch (e) { res.status(500).json({ error: e.message }); } }); // GET oops app.get('/api/oops', async (req, res) => { try { const { rows } = await pool.query( 'SELECT * FROM oops ORDER BY created_at DESC' ); res.json(rows); } catch (e) { res.status(500).json({ error: e.message }); } }); // POST oops app.post('/api/oops', async (req, res) => { const { description, fix_time, commit_link } = req.body; if (!description) return res.status(400).json({ error: 'description required' }); try { const { rows } = await pool.query( 'INSERT INTO oops (description, fix_time, commit_link) VALUES ($1, $2, $3) RETURNING *', [description, fix_time || null, commit_link || null] ); res.status(201).json(rows[0]); } catch (e) { res.status(500).json({ error: e.message }); } }); // Serve static files app.use(express.static(path.join(__dirname, '..', 'html'))); // SPA fallback — serve index.html for unmatched routes app.get('*', (req, res) => { // Check if requesting a known page directory const pagePath = path.join(__dirname, '..', 'html', req.path, 'index.html'); const fs = require('fs'); if (fs.existsSync(pagePath)) { return res.sendFile(pagePath); } res.sendFile(path.join(__dirname, '..', 'html', 'index.html')); }); const PORT = process.env.PORT || 80; app.listen(PORT, '0.0.0.0', () => { console.log(`Calvana server listening on :${PORT}`); });