Improve error handling and ordering flows

This commit is contained in:
ecki
2026-04-06 17:50:00 +02:00
parent 8aa85d47a7
commit fef108cf53
8 changed files with 488 additions and 110 deletions
+163 -68
View File
@@ -1,105 +1,136 @@
const pool = require("../db");
const clean = (value) => value === "" ? null : value;
const decimal = (value) => {
if(value === undefined || value === null || value === "") return null;
return String(value).replace(",", ".");
};
const isMissingPositionColumn = (error) =>
error && error.code === "ER_BAD_FIELD_ERROR" && String(error.sqlMessage || "").includes("position");
const attachIps = async (resources) => {
if(!resources.length) return resources;
const ids = resources.map((resource) => resource.id);
const placeholders = ids.map(() => "?").join(",");
const [ipRows] = await pool.query(
`SELECT id, resource_id, ip, type, comment
FROM resource_ips
WHERE resource_id IN (${placeholders})
ORDER BY id ASC`,
ids
);
const ipMap = new Map();
ipRows.forEach((ipRow) => {
if(!ipMap.has(ipRow.resource_id)){
ipMap.set(ipRow.resource_id, []);
}
ipMap.get(ipRow.resource_id).push({
id: ipRow.id,
ip: ipRow.ip,
type: ipRow.type,
comment: ipRow.comment
});
});
return resources.map((resource) => ({
...resource,
ips: ipMap.get(resource.id) || []
}));
};
/* ACTIVE */
exports.getActive = async (req,res)=>{
try{
let rows;
const [rows] = await pool.query(`
SELECT
r.*,
JSON_ARRAYAGG(
JSON_OBJECT(
'id', ip.id,
'ip', ip.ip,
'type', ip.type,
'comment', ip.comment
)
) AS ips
try{
[rows] = await pool.query(`
SELECT r.*
FROM resources r
LEFT JOIN resource_ips ip ON r.id = ip.resource_id
WHERE r.status != 'gekündigt'
GROUP BY r.id
ORDER BY COALESCE(position, id) ASC
`);
}catch(e){
if(!isMissingPositionColumn(e)) throw e;
[rows] = await pool.query(`
SELECT r.*
FROM resources r
WHERE r.status != 'gekündigt'
ORDER BY r.id ASC
`);
rows.forEach(r=>{
/* JSON string → array */
if(typeof r.ips === "string"){
try{
r.ips = JSON.parse(r.ips);
}catch{
r.ips=[];
}
}
/* remove null entries */
if(r.ips && r.ips[0] && r.ips[0].id === null){
r.ips=[];
res.json(await attachIps(rows));
}catch(e){
console.error("GET active resources error:",e);
res.status(500).json({error:"Ressourcen konnten nicht geladen werden"});
}
});
res.json(rows);
};
/* CANCELLED */
exports.getCancelled = async (req,res)=>{
try{
const [rows] = await pool.query(`
SELECT
r.*,
JSON_ARRAYAGG(
JSON_OBJECT(
'id', ip.id,
'ip', ip.ip,
'type', ip.type,
'comment', ip.comment
)
) AS ips
let rows;
try{
[rows] = await pool.query(`
SELECT r.*
FROM resources r
LEFT JOIN resource_ips ip ON r.id = ip.resource_id
WHERE r.status = 'gekündigt'
GROUP BY r.id
ORDER BY COALESCE(position, id) ASC
`);
}catch(e){
if(!isMissingPositionColumn(e)) throw e;
[rows] = await pool.query(`
SELECT r.*
FROM resources r
WHERE r.status = 'gekündigt'
ORDER BY r.id ASC
`);
rows.forEach(r=>{
if(typeof r.ips === "string"){
try{
r.ips = JSON.parse(r.ips);
}catch{
r.ips=[];
}
}
if(r.ips && r.ips[0] && r.ips[0].id === null){
r.ips=[];
res.json(await attachIps(rows));
}catch(e){
console.error("GET cancelled resources error:",e);
res.status(500).json({error:"Gekuendigte Ressourcen konnten nicht geladen werden"});
}
});
res.json(rows);
};
/* CREATE */
exports.create = async (req,res)=>{
try{
const clean = v => v === "" ? null : v;
function decimal(val){
if(!val) return null
return String(val).replace(",", ".")
if(!req.body.name || !String(req.body.name).trim()){
return res.status(400).json({error:"Name darf nicht leer sein"});
}
const data = {
name: req.body.name,
name: String(req.body.name).trim(),
produkt: clean(req.body.produkt),
provider: clean(req.body.provider),
art: clean(req.body.art),
@@ -121,14 +152,41 @@ kuendbar_ab: clean(req.body.kuendbar_ab),
status: clean(req.body.status),
kuendigungsdatum: clean(req.body.kuendigungsdatum),
bemerkung: clean(req.body.bemerkung)
};
try{
const [[positionRow]] = await pool.query(
"SELECT COALESCE(MAX(position), 0) + 1 AS nextPosition FROM resources"
);
data.position = positionRow.nextPosition;
}catch(e){
if(!isMissingPositionColumn(e)) throw e;
}
await pool.query("INSERT INTO resources SET ?",data);
res.json({message:"created"});
}catch(e){
console.error("CREATE resource error:",e);
let message = "Ressource konnte nicht gespeichert werden";
let status = 500;
if(e.code === "WARN_DATA_TRUNCATED"){
message = "Ungueltiges Zahlenformat";
status = 400;
}
res.status(status).json({error:message});
}
};
@@ -136,10 +194,23 @@ res.json({message:"created"});
/* UPDATE */
exports.update = async (req,res)=>{
try{
const clean = v => v === "" ? null : v;
if(req.body.name !== undefined && !String(req.body.name).trim()){
return res.status(400).json({error:"Name darf nicht leer sein"});
}
Object.keys(req.body).forEach(k=>{
if(k === "kosten_monat" || k === "kosten_jahr"){
req.body[k] = decimal(req.body[k]);
return;
}
if(k === "name" && req.body[k] !== undefined && req.body[k] !== null){
req.body[k] = String(req.body[k]).trim();
return;
}
req.body[k]=clean(req.body[k]);
});
@@ -150,12 +221,29 @@ await pool.query(
res.json({message:"updated"});
}catch(e){
console.error("UPDATE resource error:",e);
let message = "Ressource konnte nicht gespeichert werden";
let status = 500;
if(e.code === "WARN_DATA_TRUNCATED"){
message = "Ungueltiges Zahlenformat";
status = 400;
}
res.status(status).json({error:message});
}
};
/* DELETE */
exports.remove = async (req,res)=>{
try{
await pool.query(
"DELETE FROM resources WHERE id=?",
@@ -164,4 +252,11 @@ await pool.query(
res.json({message:"deleted"});
}catch(e){
console.error("DELETE resource error:",e);
res.status(500).json({error:"Ressource konnte nicht geloescht werden"});
}
};