/** * server.js — Node.js CRUD (Express + MariaDB). * * Переменные окружения: * DB_HOST, DB_PORT, DB_NAME, DB_USER, DB_PASSWORD * TABLE_NAME (по умолчанию crud_items) * PORT (по умолчанию 3000) * * При старте: CREATE TABLE IF NOT EXISTS. */ const express = require("express"); const mysql = require("mysql2/promise"); const path = require("path"); const VERSION = "1.0.0"; const TABLE_NAME = process.env.TABLE_NAME || "crud_items"; if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(TABLE_NAME)) { throw new Error("TABLE_NAME must be a simple SQL identifier"); } const TABLE_SQL = `\`${TABLE_NAME}\``; // SERVICE_NAME — какое приложение добавило строку (колонка created_by). // Задаётся в json_env манифеста для каждого сервиса (flask / lucee / nodejs). const SERVICE_NAME = process.env.SERVICE_NAME || "nodejs"; const PORT = process.env.PORT || 3000; const pool = mysql.createPool({ host: process.env.DB_HOST || "127.0.0.1", port: Number.parseInt(process.env.DB_PORT || "3306", 10), database: process.env.DB_NAME || "maria", user: process.env.DB_USER || "", password: process.env.DB_PASSWORD || "", waitForConnections: true, connectionLimit: 5, queueLimit: 0, charset: "utf8mb4", }); const app = express(); app.use(express.urlencoded({ extended: true })); app.use(express.static(path.join(__dirname, "public"))); app.set("view engine", "ejs"); app.set("views", path.join(__dirname, "views")); // --- Инициализация БД --- // Таблица выставочная: кроме id/value — две метки: created_at (когда добавлено) // и created_by (какое приложение добавило). ALTER ... IF NOT EXISTS досыпает // колонки в таблицу, созданную прошлой версией приложения, не теряя данные. async function initDB() { const client = await pool.getConnection(); try { await client.query(` CREATE TABLE IF NOT EXISTS ${TABLE_SQL} ( id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, \`value\` TEXT NOT NULL, created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, created_by VARCHAR(255) NOT NULL DEFAULT '' ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 `); await client.query( `ALTER TABLE ${TABLE_SQL} ADD COLUMN IF NOT EXISTS created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP` ); await client.query( `ALTER TABLE ${TABLE_SQL} ADD COLUMN IF NOT EXISTS created_by VARCHAR(255) NOT NULL DEFAULT ''` ); } finally { client.release(); } } // --- Главная страница (GET + POST) --- app.get("/", async (req, res) => { try { let editRow = null; if (req.query.edit) { const [editRows] = await pool.execute( `SELECT id, \`value\`, created_at, created_by FROM ${TABLE_SQL} WHERE id = ?`, [parseInt(req.query.edit)] ); if (editRows.length > 0) editRow = editRows[0]; } const [rows] = await pool.execute( `SELECT id, \`value\`, created_at, created_by FROM ${TABLE_SQL} ORDER BY id` ); res.render("index", { table_name: TABLE_NAME, rows, editRow, version: VERSION, }); } catch (err) { res.status(500).send(err.message); } }); app.post("/", async (req, res) => { try { const { action, value, id } = req.body; if (action === "create") { // created_at ставит СУБД (DEFAULT now()), created_by — имя этого сервиса await pool.execute( `INSERT INTO ${TABLE_SQL} (\`value\`, created_by) VALUES (?, ?)`, [value || "", SERVICE_NAME] ); } else if (action === "update") { await pool.execute(`UPDATE ${TABLE_SQL} SET \`value\` = ? WHERE id = ?`, [ value || "", parseInt(id || 0), ]); } else if (action === "delete") { await pool.execute(`DELETE FROM ${TABLE_SQL} WHERE id = ?`, [ parseInt(id || 0), ]); } res.redirect("/"); } catch (err) { res.status(500).send(err.message); } }); // --- Старт --- // Сервер стартует всегда, даже если PG недоступен. // initDB — асинхронно, ошибка логируется но не роняет под. initDB() .then(() => console.log("DB ready")) .catch((err) => console.error("DB init failed:", err.message)); app.listen(PORT, () => { console.log(`Node.js CRUD v${VERSION} — http://0.0.0.0:${PORT}`); });