Feature: API PostgreSQL — upsert по «Название», загрузка из БД при открытии
Made-with: Cursor
This commit is contained in:
129
server/index.js
Normal file
129
server/index.js
Normal file
@@ -0,0 +1,129 @@
|
||||
/**
|
||||
* API сохранения инцидентов ServiceDesk: upsert по ключу «Название» (number_key).
|
||||
*/
|
||||
import express from "express";
|
||||
import { createPool, migrate } from "./db.js";
|
||||
|
||||
const PORT = Number(process.env.PORT || 3910);
|
||||
const app = express();
|
||||
app.use(express.json({ limit: "50mb" }));
|
||||
|
||||
const FIELDS_RU = [
|
||||
"Название",
|
||||
"Статус",
|
||||
"Ответственный (команда)",
|
||||
"Ответственный (сотрудник)",
|
||||
"Инициатор заявки",
|
||||
"Услуга",
|
||||
"Дата создания",
|
||||
"Регламентное время решения запроса",
|
||||
"Дата решения",
|
||||
"Кем решен (сотрудник)",
|
||||
"Уникальный идентификатор"
|
||||
];
|
||||
|
||||
function trimKeys(row) {
|
||||
const o = {};
|
||||
if (!row || typeof row !== "object") return o;
|
||||
for (const [k, v] of Object.entries(row)) {
|
||||
const key = String(k).trim();
|
||||
o[key] = v;
|
||||
}
|
||||
return o;
|
||||
}
|
||||
|
||||
function extractNumber(row) {
|
||||
const r = trimKeys(row);
|
||||
const raw = r["Название"];
|
||||
if (raw === undefined || raw === null || raw === "") return NaN;
|
||||
const n = parseInt(String(raw).replace(/\s/g, ""), 10);
|
||||
return n;
|
||||
}
|
||||
|
||||
function rowToJsonb(row) {
|
||||
const r = trimKeys(row);
|
||||
const out = {};
|
||||
for (const key of FIELDS_RU) {
|
||||
if (Object.prototype.hasOwnProperty.call(r, key)) {
|
||||
let v = r[key];
|
||||
if (v === undefined || v === null) v = "";
|
||||
out[key] = typeof v === "string" ? v.trim() : v;
|
||||
} else {
|
||||
out[key] = "";
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
let pool;
|
||||
|
||||
app.get("/api/health", (_req, res) => {
|
||||
res.json({ ok: true, service: "omc-servicedesk-monitor" });
|
||||
});
|
||||
|
||||
app.get("/api/incidents", async (_req, res) => {
|
||||
try {
|
||||
const r = await pool.query(
|
||||
"SELECT data FROM incidents ORDER BY number_key ASC"
|
||||
);
|
||||
const rows = r.rows.map((x) => x.data);
|
||||
res.json({ rows });
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
res.status(500).json({ error: String(e.message || e) });
|
||||
}
|
||||
});
|
||||
|
||||
app.post("/api/incidents/sync", async (req, res) => {
|
||||
const body = req.body;
|
||||
const rowsIn = body && body.rows;
|
||||
if (!Array.isArray(rowsIn)) {
|
||||
return res.status(400).json({ error: "Ожидается body: { rows: [...] }" });
|
||||
}
|
||||
|
||||
const client = await pool.connect();
|
||||
let applied = 0;
|
||||
let skipped = 0;
|
||||
try {
|
||||
await client.query("BEGIN");
|
||||
for (const raw of rowsIn) {
|
||||
const num = extractNumber(raw);
|
||||
if (Number.isNaN(num) || num <= 0) {
|
||||
skipped++;
|
||||
continue;
|
||||
}
|
||||
const data = rowToJsonb(raw);
|
||||
data["Название"] = num;
|
||||
await client.query(
|
||||
`INSERT INTO incidents (number_key, data, updated_at)
|
||||
VALUES ($1, $2::jsonb, NOW())
|
||||
ON CONFLICT (number_key) DO UPDATE SET
|
||||
data = EXCLUDED.data,
|
||||
updated_at = NOW()`,
|
||||
[num, JSON.stringify(data)]
|
||||
);
|
||||
applied++;
|
||||
}
|
||||
await client.query("COMMIT");
|
||||
res.json({ ok: true, applied, skipped, total: rowsIn.length });
|
||||
} catch (e) {
|
||||
await client.query("ROLLBACK");
|
||||
console.error(e);
|
||||
res.status(500).json({ error: String(e.message || e) });
|
||||
} finally {
|
||||
client.release();
|
||||
}
|
||||
});
|
||||
|
||||
async function main() {
|
||||
pool = createPool();
|
||||
await migrate(pool);
|
||||
app.listen(PORT, "0.0.0.0", () => {
|
||||
console.log(`omc-sd API слушает :${PORT}`);
|
||||
});
|
||||
}
|
||||
|
||||
main().catch((e) => {
|
||||
console.error(e);
|
||||
process.exit(1);
|
||||
});
|
||||
Reference in New Issue
Block a user