- CREATE TABLE IF NOT EXISTS дополнена колонками:
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
created_by TEXT NOT NULL DEFAULT ''
- ALTER TABLE ... ADD COLUMN IF NOT EXISTS досыпает колонки в уже существующую
таблицу — данные при этом не теряются;
- INSERT пишет created_by из env SERVICE_NAME (задаётся в json_env манифеста),
created_at ставит сама СУБД;
- список записей и UI показывают обе колонки.
Зачем: три приложения (flask / lucee / nodejs) пишут в ОДНУ таблицу — теперь
сразу видно, какое из них и когда добавило строку.
127 lines
4.2 KiB
JavaScript
127 lines
4.2 KiB
JavaScript
/**
|
|
* server.js — Node.js CRUD (Express + PostgreSQL).
|
|
*
|
|
* Переменные окружения:
|
|
* PGHOST, PGPORT, PGDATABASE, PGUSER, PGPASSWORD
|
|
* TABLE_NAME (по умолчанию crud_items)
|
|
* PORT (по умолчанию 3000)
|
|
*
|
|
* При старте: CREATE TABLE IF NOT EXISTS.
|
|
*/
|
|
const express = require("express");
|
|
const { Pool } = require("pg");
|
|
const path = require("path");
|
|
|
|
const VERSION = "1.0.0";
|
|
const TABLE_NAME = process.env.TABLE_NAME || "crud_items";
|
|
// 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 = new Pool({
|
|
host: process.env.PGHOST || "127.0.0.1",
|
|
port: parseInt(process.env.PGPORT || "5432"),
|
|
database: process.env.PGDATABASE || "postgres",
|
|
user: process.env.PGUSER || "postgres",
|
|
password: process.env.PGPASSWORD || "",
|
|
ssl: process.env.PGSSLMODE === "require" ? { rejectUnauthorized: false } : false,
|
|
});
|
|
|
|
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.connect();
|
|
try {
|
|
await client.query(`
|
|
CREATE TABLE IF NOT EXISTS ${TABLE_NAME} (
|
|
id SERIAL PRIMARY KEY,
|
|
value TEXT NOT NULL DEFAULT '',
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
created_by TEXT NOT NULL DEFAULT ''
|
|
)
|
|
`);
|
|
await client.query(
|
|
`ALTER TABLE ${TABLE_NAME} ADD COLUMN IF NOT EXISTS created_at TIMESTAMPTZ NOT NULL DEFAULT now()`
|
|
);
|
|
await client.query(
|
|
`ALTER TABLE ${TABLE_NAME} ADD COLUMN IF NOT EXISTS created_by TEXT NOT NULL DEFAULT ''`
|
|
);
|
|
} finally {
|
|
client.release();
|
|
}
|
|
}
|
|
|
|
// --- Главная страница (GET + POST) ---
|
|
app.get("/", async (req, res) => {
|
|
try {
|
|
let editRow = null;
|
|
if (req.query.edit) {
|
|
const result = await pool.query(
|
|
`SELECT id, value, created_at, created_by FROM ${TABLE_NAME} WHERE id = $1`,
|
|
[parseInt(req.query.edit)]
|
|
);
|
|
if (result.rows.length > 0) editRow = result.rows[0];
|
|
}
|
|
|
|
const result = await pool.query(
|
|
`SELECT id, value, created_at, created_by FROM ${TABLE_NAME} ORDER BY id`
|
|
);
|
|
|
|
res.render("index", {
|
|
table_name: TABLE_NAME,
|
|
rows: result.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.query(
|
|
`INSERT INTO ${TABLE_NAME} (value, created_by) VALUES ($1, $2)`,
|
|
[value || "", SERVICE_NAME]
|
|
);
|
|
} else if (action === "update") {
|
|
await pool.query(`UPDATE ${TABLE_NAME} SET value = $1 WHERE id = $2`, [
|
|
value || "",
|
|
parseInt(id || 0),
|
|
]);
|
|
} else if (action === "delete") {
|
|
await pool.query(`DELETE FROM ${TABLE_NAME} WHERE id = $1`, [
|
|
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}`);
|
|
});
|