import express from 'express'; import cors from 'cors'; import fs from 'fs'; import path from 'path'; import { fileURLToPath } from 'url'; import pg from 'pg'; const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); const app = express(); const PORT = 3001; // Use DATA_DIR from environment if available (for Dokploy volumes), otherwise fallback to the current directory const DATA_DIR = process.env.DATA_DIR || __dirname; const DATA_FILE = path.join(DATA_DIR, 'quotes.json'); // Database Setup const { Pool } = pg; const pool = process.env.DATABASE_URL ? new Pool({ connectionString: process.env.DATABASE_URL, ssl: process.env.PG_SSL === 'true' ? { rejectUnauthorized: false } : false }) : null; if (pool) { pool.query(` CREATE TABLE IF NOT EXISTS quotes ( id SERIAL PRIMARY KEY, quote_id VARCHAR(255) UNIQUE NOT NULL, data JSONB NOT NULL ) `).then(() => console.log("PostgreSQL Connected & Table Verified")) .catch(err => console.error("Could not create PostgreSQL table:", err)); } app.use(cors()); app.use(express.json()); // Log every request app.use((req, res, next) => { console.log(`${new Date().toISOString()} - ${req.method} ${req.url}`); next(); }); // Helper to read data (legacy JSON mode) const readData = () => { try { if (!fs.existsSync(DATA_FILE)) { return []; } const data = fs.readFileSync(DATA_FILE, 'utf8'); return JSON.parse(data); } catch (err) { console.error("Error reading data:", err); return []; } }; // Helper to write data (legacy JSON mode) const writeData = (data) => { try { fs.writeFileSync(DATA_FILE, JSON.stringify(data, null, 2), 'utf8'); } catch (err) { console.error("Error writing data:", err); } }; // GET all quotes app.get('/api/quotes', async (req, res) => { if (pool) { try { const result = await pool.query('SELECT data FROM quotes ORDER BY id ASC'); res.json(result.rows.map(row => row.data)); } catch (err) { console.error(err); res.status(500).json({ error: "Failed to fetch from database" }); } } else { const quotes = readData(); res.json(quotes); } }); // POST a new or updated quote app.post('/api/quotes', async (req, res) => { const newQuote = req.body; if (pool) { try { await pool.query( 'INSERT INTO quotes (quote_id, data) VALUES ($1, $2) ON CONFLICT (quote_id) DO UPDATE SET data = $2', [String(newQuote.id), newQuote] ); res.status(201).json(newQuote); } catch (err) { console.error(err); res.status(500).json({ error: "Failed to save to database" }); } } else { let quotes = readData(); const existingIndex = quotes.findIndex(q => q.id === newQuote.id); if (existingIndex >= 0) { quotes[existingIndex] = newQuote; // Update } else { quotes.push(newQuote); // Add new } writeData(quotes); res.status(201).json(newQuote); } }); // DELETE a quote app.delete('/api/quotes/:id', async (req, res) => { const idToDelete = req.params.id; if (pool) { try { const result = await pool.query('DELETE FROM quotes WHERE quote_id = $1', [String(idToDelete)]); if (result.rowCount > 0) res.status(200).json({ message: "Quote deleted successfully" }); else res.status(404).json({ message: "Quote not found" }); } catch (err) { console.error(err); res.status(500).json({ error: "Failed to delete from database" }); } } else { const idToDeleteFallback = parseInt(idToDelete) || idToDelete; // Handle number or string let quotes = readData(); const initialLength = quotes.length; quotes = quotes.filter(q => q.id != idToDeleteFallback); if (quotes.length < initialLength) { writeData(quotes); res.status(200).json({ message: "Quote deleted successfully" }); } else { res.status(404).json({ message: "Quote not found" }); } } }); // Serve frontend static files app.use(express.static(path.join(__dirname, 'dist'))); // Fallback for SPA (React Router, etc.) app.get(/(.*)/, (req, res) => { res.sendFile(path.join(__dirname, 'dist', 'index.html')); }); app.listen(PORT, '0.0.0.0', () => { console.log(`Backend server running on port ${PORT}`); if (pool) console.log(`[INFO] DATABASE_URL detected. Running in PostgreSQL mode.`); else console.log(`[INFO] No DATABASE_URL detected. Running in legacy JSON file mode.`); });