zkt26/skuska/backend/server.js
Bohdan Kapliuk 15f4373858 skuska
2026-05-13 19:50:55 +03:00

235 lines
5.7 KiB
JavaScript

const express = require("express");
const cors = require("cors");
const { Pool } = require("pg");
const fs = require("fs/promises");
const path = require("path");
const app = express();
const port = Number(process.env.PORT || 3000);
const dataFile = process.env.DATA_FILE;
let databaseReady = false;
let databaseInitError = null;
app.use(cors());
app.use(express.json());
const pool = dataFile
? null
: new Pool({
host: process.env.DB_HOST || "postgres-service",
user: process.env.DB_USER || "user",
password: process.env.DB_PASSWORD,
database: process.env.DB_NAME || "mydb",
port: Number(process.env.DB_PORT || 5432),
ssl: process.env.DB_SSL === "true" ? { rejectUnauthorized: false } : false
});
async function ensureDataFile() {
await fs.mkdir(path.dirname(dataFile), { recursive: true });
try {
await fs.access(dataFile);
} catch (_error) {
await fs.writeFile(dataFile, "[]\n", "utf8");
}
}
async function readUsersFromFile() {
await ensureDataFile();
const content = await fs.readFile(dataFile, "utf8");
return JSON.parse(content || "[]");
}
async function saveUserToFile(name) {
const users = await readUsersFromFile();
const nextId = users.length > 0 ? Math.max(...users.map((user) => user.id || 0)) + 1 : 1;
users.push({ id: nextId, name });
await fs.writeFile(dataFile, `${JSON.stringify(users, null, 2)}\n`, "utf8");
}
async function prepareDatabase() {
if (dataFile) {
await ensureDataFile();
return;
}
await pool.query(`
CREATE TABLE IF NOT EXISTS guestbook_entries (
id SERIAL PRIMARY KEY,
name TEXT NOT NULL,
message TEXT NOT NULL DEFAULT '',
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
)
`);
}
async function waitForDatabase(maxAttempts = 120, delayMs = 5000) {
for (let attempt = 1; attempt <= maxAttempts; attempt += 1) {
try {
await prepareDatabase();
console.log(`Database is ready after attempt ${attempt}.`);
databaseReady = true;
databaseInitError = null;
return;
} catch (error) {
databaseReady = false;
databaseInitError = error;
console.error(`Database is not ready yet (attempt ${attempt}/${maxAttempts}):`, error.message);
if (attempt === maxAttempts) {
throw error;
}
await new Promise((resolve) => setTimeout(resolve, delayMs));
}
}
}
app.get("/health", async (_req, res) => {
try {
if (dataFile) {
await ensureDataFile();
} else {
await pool.query("SELECT 1");
}
res.json({ status: "ok" });
} catch (error) {
console.error("Healthcheck failed:", error);
res.status(500).json({ status: "error" });
}
});
app.get("/ready", (_req, res) => {
if (databaseReady) {
return res.json({ status: "ready" });
}
return res.status(503).json({
status: "starting",
error: databaseInitError ? databaseInitError.message : "Database is not ready yet"
});
});
app.get("/status", (_req, res) => {
res.json({
backend: "online",
database: databaseReady ? "ready" : "starting"
});
});
app.post("/save", async (req, res) => {
if (!databaseReady) {
return res.status(503).send("Database is still starting");
}
const name = (req.body.name || "").trim();
const message = (req.body.message || "").trim();
if (!name) {
return res.status(400).send("Name is required");
}
if (!message) {
return res.status(400).send("Message is required");
}
try {
if (dataFile) {
await saveUserToFile(name);
} else {
await pool.query("INSERT INTO guestbook_entries(name, message) VALUES($1, $2)", [name, message]);
}
return res.send(`Saved: ${name}`);
} catch (error) {
console.error("Insert failed:", error);
return res.status(500).send("Error");
}
});
app.get("/users", async (_req, res) => {
if (!databaseReady) {
return res.status(503).json([]);
}
try {
if (dataFile) {
const users = await readUsersFromFile();
return res.json(users);
}
const result = await pool.query(`
SELECT id, name, message, created_at
FROM guestbook_entries
ORDER BY created_at DESC, id DESC
`);
return res.json(result.rows);
} catch (error) {
console.error("Select failed:", error);
return res.status(500).send("Error");
}
});
app.get("/entries", async (_req, res) => {
if (!databaseReady) {
return res.status(503).json([]);
}
try {
const result = await pool.query(`
SELECT id, name, message, created_at
FROM guestbook_entries
ORDER BY created_at DESC, id DESC
`);
return res.json(result.rows);
} catch (error) {
console.error("Select entries failed:", error);
return res.status(500).send("Error");
}
});
app.delete("/entries/:id", async (req, res) => {
if (!databaseReady) {
return res.status(503).send("Database is still starting");
}
const id = Number(req.params.id);
if (!Number.isInteger(id) || id < 1) {
return res.status(400).send("Invalid entry id");
}
try {
await pool.query("DELETE FROM guestbook_entries WHERE id = $1", [id]);
return res.send("Deleted");
} catch (error) {
console.error("Delete entry failed:", error);
return res.status(500).send("Error");
}
});
app.delete("/entries", async (_req, res) => {
if (!databaseReady) {
return res.status(503).send("Database is still starting");
}
try {
await pool.query("DELETE FROM guestbook_entries");
return res.send("Cleared");
} catch (error) {
console.error("Clear entries failed:", error);
return res.status(500).send("Error");
}
});
app.listen(port, () => {
console.log(`Backend running on port ${port}`);
});
waitForDatabase()
.catch((error) => {
console.error("Database initialization failed:", error);
});