151 lines
4.7 KiB
JavaScript
151 lines
4.7 KiB
JavaScript
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.`);
|
|
});
|