222 lines
6.1 KiB
JavaScript
222 lines
6.1 KiB
JavaScript
const query = require("../db.js");
|
|
|
|
const WORK_ORDER_TIMESTAMP_FIELDS = new Map([
|
|
["Request date", "request_date"],
|
|
["Request date 2", "request_date_2"],
|
|
["Created", "created"],
|
|
["Start", "start"],
|
|
["WO Accepted By Vendor", "wo_accepted_by_vendor"],
|
|
["Start Work", "start_work"],
|
|
["Downtime start date", "downtime_start_date"],
|
|
["Downtime end", "downtime_end"],
|
|
["Note closed time", "note_closed_time"],
|
|
["End Work", "end_work"],
|
|
["End", "end"],
|
|
["Closed", "closed"],
|
|
]);
|
|
|
|
const WORK_ORDER_FIELD_ALIASES = new Map([
|
|
["Address", "address"],
|
|
["Assignee", "assignee"],
|
|
["Assignee profile", "assignee_profile"],
|
|
["Bookmark", "bookmark"],
|
|
["Category", "category"],
|
|
["Contact", "contact"],
|
|
["District", "district"],
|
|
["Division manager", "division_manager"],
|
|
["Downtime Cat.", "downtime_cat"],
|
|
["Downtime?", "downtime"],
|
|
["From", "from_requestor"],
|
|
["Labor", "labor_amount"],
|
|
["Qty. Labor Hours", "labor_hours"],
|
|
["Location", "location"],
|
|
["Material", "material_amount"],
|
|
["NTE Amt. $", "nte_amount"],
|
|
["NTE Amount", "nte_amount_2"],
|
|
["Part of", "part_of"],
|
|
["Priority", "priority"],
|
|
["mainProblem", "problem"],
|
|
["Problem Type", "problemtype"],
|
|
["Property", "property"],
|
|
["Reference", "reference"],
|
|
["Site #", "site_number"],
|
|
["State", "state"],
|
|
["Status", "status"],
|
|
["Subject", "subject"],
|
|
["Total", "total_amount"],
|
|
["Travel", "travel_amount"],
|
|
["Travel count", "travel_count"],
|
|
["Type", "type"],
|
|
["VAT", "vat"],
|
|
["Vendor Response Time", "vendor_response_time"],
|
|
]);
|
|
|
|
function normalizeIdentifier(value) {
|
|
return String(value)
|
|
.replace(/([a-z0-9])([A-Z])/g, "$1_$2")
|
|
.replace(/[^a-zA-Z0-9]+/g, "_")
|
|
.replace(/^_+|_+$/g, "")
|
|
.replace(/_+/g, "_")
|
|
.toLowerCase();
|
|
}
|
|
|
|
function quoteIdentifier(value) {
|
|
return `"${String(value).replace(/"/g, "\"\"")}"`;
|
|
}
|
|
|
|
function quoteLiteral(value) {
|
|
return `'${String(value).replace(/'/g, "''")}'`;
|
|
}
|
|
|
|
function getColumnAlias(key) {
|
|
return WORK_ORDER_TIMESTAMP_FIELDS.get(key)
|
|
|| WORK_ORDER_FIELD_ALIASES.get(key)
|
|
|| normalizeIdentifier(key);
|
|
}
|
|
|
|
function buildTimestampExpression(tableName, key, alias) {
|
|
return `
|
|
CASE
|
|
WHEN ((${tableName}.data ->> ${quoteLiteral(key)}) = ''::text) THEN NULL::timestamp with time zone
|
|
ELSE to_timestamp((${tableName}.data ->> ${quoteLiteral(key)}), 'YYYY-MM-DD"T"HH24:MI:SS'::text)
|
|
END AS ${quoteIdentifier(alias)}`;
|
|
}
|
|
|
|
function buildTextExpression(tableName, key, alias) {
|
|
return `(${tableName}.data ->> ${quoteLiteral(key)}) AS ${quoteIdentifier(alias)}`;
|
|
}
|
|
|
|
async function ensureTableExists(tableName) {
|
|
await query(`
|
|
CREATE TABLE IF NOT EXISTS ${tableName} (
|
|
id text primary key,
|
|
created_at timestamp with time zone not null default now(),
|
|
updated_at timestamp with time zone not null default now(),
|
|
data jsonb not null
|
|
)`);
|
|
|
|
await query(`
|
|
DROP TRIGGER IF EXISTS set_update_${tableName} ON ${tableName}`);
|
|
|
|
await query(`
|
|
CREATE TRIGGER set_update_${tableName}
|
|
BEFORE UPDATE ON ${tableName}
|
|
FOR EACH ROW
|
|
EXECUTE PROCEDURE trigger_set_timestamp()`);
|
|
}
|
|
|
|
async function getFlattenedKeys(tableName) {
|
|
const result = await query(`
|
|
SELECT DISTINCT key
|
|
FROM ${tableName},
|
|
LATERAL jsonb_object_keys(data) AS key
|
|
ORDER BY key`);
|
|
|
|
return result.rows
|
|
.map((row) => row.key)
|
|
.filter((key) => !["id", "created_at", "updated_at", "data"].includes(key));
|
|
}
|
|
|
|
async function refreshFlattenedView(tableName, viewName) {
|
|
const keys = await getFlattenedKeys(tableName);
|
|
const seenAliases = new Set(["id", "created_at", "updated_at", "data"]);
|
|
|
|
const selectFragments = [
|
|
`${tableName}.id`,
|
|
`${tableName}.created_at`,
|
|
`${tableName}.updated_at`,
|
|
];
|
|
|
|
for (const key of keys) {
|
|
let alias = getColumnAlias(key);
|
|
if (!alias) {
|
|
continue;
|
|
}
|
|
|
|
let suffix = 2;
|
|
while (seenAliases.has(alias)) {
|
|
alias = `${alias}_${suffix}`;
|
|
suffix += 1;
|
|
}
|
|
seenAliases.add(alias);
|
|
|
|
const expression = WORK_ORDER_TIMESTAMP_FIELDS.has(key)
|
|
? buildTimestampExpression(tableName, key, alias)
|
|
: buildTextExpression(tableName, key, alias);
|
|
|
|
selectFragments.push(expression);
|
|
}
|
|
|
|
await query(`
|
|
DROP VIEW IF EXISTS ${viewName}`);
|
|
|
|
await query(`
|
|
CREATE VIEW ${viewName} AS
|
|
SELECT ${selectFragments.join(",\n ")}
|
|
FROM ${tableName}`);
|
|
}
|
|
|
|
const addData = async (req, res) => {
|
|
try {
|
|
const client = req.params.client;
|
|
const { tableName, data } = req.body;
|
|
|
|
if (typeof tableName !== "string" || !tableName.trim()) {
|
|
return res.status(400).json({ error: "tableName is required." });
|
|
}
|
|
|
|
if (!Array.isArray(data)) {
|
|
return res.status(400).json({ error: "data must be an array." });
|
|
}
|
|
|
|
const normalizedClient = normalizeIdentifier(client);
|
|
const normalizedTableName = normalizeIdentifier(tableName);
|
|
|
|
if (!normalizedClient || !normalizedTableName) {
|
|
return res.status(400).json({ error: "Invalid client or tableName." });
|
|
}
|
|
|
|
const dBtableName = `${normalizedClient}_${normalizedTableName}`;
|
|
const flattenedViewName = `${normalizedClient}_flattened_${normalizedTableName}`;
|
|
const insertQuery = `
|
|
INSERT INTO ${dBtableName} (id, data) VALUES ($1, $2)
|
|
ON CONFLICT (id) DO UPDATE SET data = $2`;
|
|
|
|
for (const item of data) {
|
|
if (!item || typeof item !== "object" || item.id == null) {
|
|
return res.status(400).json({ error: "Each data item must be an object with an id." });
|
|
}
|
|
}
|
|
|
|
await query("BEGIN");
|
|
|
|
try {
|
|
await ensureTableExists(dBtableName);
|
|
|
|
for (const item of data) {
|
|
await query(insertQuery, [String(item.id), item]);
|
|
}
|
|
|
|
await refreshFlattenedView(dBtableName, flattenedViewName);
|
|
await query("COMMIT");
|
|
} catch (err) {
|
|
await query("ROLLBACK");
|
|
throw err;
|
|
}
|
|
|
|
res.status(200).json({
|
|
message: "Data added successfully",
|
|
tableName: dBtableName,
|
|
viewName: flattenedViewName,
|
|
});
|
|
|
|
} catch (err) {
|
|
console.error("Error handling the request:", err);
|
|
res
|
|
.status(500)
|
|
.json({ error: "An error occurred while processing the request" });
|
|
}
|
|
};
|
|
|
|
module.exports = addData;
|