Improve error handling and ordering flows
This commit is contained in:
@@ -75,6 +75,7 @@ resources
|
|||||||
|
|
||||||
CREATE TABLE resources (
|
CREATE TABLE resources (
|
||||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||||
|
position INT,
|
||||||
name VARCHAR(255),
|
name VARCHAR(255),
|
||||||
produkt VARCHAR(255),
|
produkt VARCHAR(255),
|
||||||
provider VARCHAR(255),
|
provider VARCHAR(255),
|
||||||
@@ -109,6 +110,7 @@ domains
|
|||||||
|
|
||||||
CREATE TABLE domains (
|
CREATE TABLE domains (
|
||||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||||
|
position INT,
|
||||||
domain_name VARCHAR(255),
|
domain_name VARCHAR(255),
|
||||||
provider VARCHAR(255),
|
provider VARCHAR(255),
|
||||||
ip_address VARCHAR(100),
|
ip_address VARCHAR(100),
|
||||||
@@ -196,4 +198,3 @@ DNS Requests können ohne Cache langsam sein
|
|||||||
Keine Authentifizierung im Backend
|
Keine Authentifizierung im Backend
|
||||||
Kein Rollen-/Rechtesystem
|
Kein Rollen-/Rechtesystem
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,105 +1,136 @@
|
|||||||
const pool = require("../db");
|
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 */
|
/* ACTIVE */
|
||||||
exports.getActive = async (req,res)=>{
|
exports.getActive = async (req,res)=>{
|
||||||
|
try{
|
||||||
|
let rows;
|
||||||
|
|
||||||
const [rows] = await pool.query(`
|
try{
|
||||||
SELECT
|
[rows] = await pool.query(`
|
||||||
r.*,
|
SELECT r.*
|
||||||
JSON_ARRAYAGG(
|
|
||||||
JSON_OBJECT(
|
|
||||||
'id', ip.id,
|
|
||||||
'ip', ip.ip,
|
|
||||||
'type', ip.type,
|
|
||||||
'comment', ip.comment
|
|
||||||
)
|
|
||||||
) AS ips
|
|
||||||
FROM resources r
|
FROM resources r
|
||||||
LEFT JOIN resource_ips ip ON r.id = ip.resource_id
|
|
||||||
WHERE r.status != 'gekündigt'
|
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 */
|
res.json(await attachIps(rows));
|
||||||
if(r.ips && r.ips[0] && r.ips[0].id === null){
|
|
||||||
r.ips=[];
|
}catch(e){
|
||||||
|
|
||||||
|
console.error("GET active resources error:",e);
|
||||||
|
res.status(500).json({error:"Ressourcen konnten nicht geladen werden"});
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
res.json(rows);
|
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/* CANCELLED */
|
/* CANCELLED */
|
||||||
|
|
||||||
exports.getCancelled = async (req,res)=>{
|
exports.getCancelled = async (req,res)=>{
|
||||||
|
try{
|
||||||
|
|
||||||
const [rows] = await pool.query(`
|
let rows;
|
||||||
SELECT
|
|
||||||
r.*,
|
try{
|
||||||
JSON_ARRAYAGG(
|
[rows] = await pool.query(`
|
||||||
JSON_OBJECT(
|
SELECT r.*
|
||||||
'id', ip.id,
|
|
||||||
'ip', ip.ip,
|
|
||||||
'type', ip.type,
|
|
||||||
'comment', ip.comment
|
|
||||||
)
|
|
||||||
) AS ips
|
|
||||||
FROM resources r
|
FROM resources r
|
||||||
LEFT JOIN resource_ips ip ON r.id = ip.resource_id
|
|
||||||
WHERE r.status = 'gekündigt'
|
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){
|
res.json(await attachIps(rows));
|
||||||
r.ips=[];
|
|
||||||
|
}catch(e){
|
||||||
|
|
||||||
|
console.error("GET cancelled resources error:",e);
|
||||||
|
res.status(500).json({error:"Gekuendigte Ressourcen konnten nicht geladen werden"});
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
res.json(rows);
|
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/* CREATE */
|
/* CREATE */
|
||||||
|
|
||||||
exports.create = async (req,res)=>{
|
exports.create = async (req,res)=>{
|
||||||
|
try{
|
||||||
|
|
||||||
const clean = v => v === "" ? null : v;
|
if(!req.body.name || !String(req.body.name).trim()){
|
||||||
|
return res.status(400).json({error:"Name darf nicht leer sein"});
|
||||||
function decimal(val){
|
|
||||||
|
|
||||||
if(!val) return null
|
|
||||||
|
|
||||||
return String(val).replace(",", ".")
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
const data = {
|
const data = {
|
||||||
name: req.body.name,
|
name: String(req.body.name).trim(),
|
||||||
produkt: clean(req.body.produkt),
|
produkt: clean(req.body.produkt),
|
||||||
provider: clean(req.body.provider),
|
provider: clean(req.body.provider),
|
||||||
art: clean(req.body.art),
|
art: clean(req.body.art),
|
||||||
@@ -121,14 +152,41 @@ kuendbar_ab: clean(req.body.kuendbar_ab),
|
|||||||
|
|
||||||
status: clean(req.body.status),
|
status: clean(req.body.status),
|
||||||
kuendigungsdatum: clean(req.body.kuendigungsdatum),
|
kuendigungsdatum: clean(req.body.kuendigungsdatum),
|
||||||
|
|
||||||
bemerkung: clean(req.body.bemerkung)
|
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);
|
await pool.query("INSERT INTO resources SET ?",data);
|
||||||
|
|
||||||
res.json({message:"created"});
|
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 */
|
/* UPDATE */
|
||||||
|
|
||||||
exports.update = async (req,res)=>{
|
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=>{
|
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]);
|
req.body[k]=clean(req.body[k]);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -150,12 +221,29 @@ await pool.query(
|
|||||||
|
|
||||||
res.json({message:"updated"});
|
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 */
|
/* DELETE */
|
||||||
|
|
||||||
exports.remove = async (req,res)=>{
|
exports.remove = async (req,res)=>{
|
||||||
|
try{
|
||||||
|
|
||||||
await pool.query(
|
await pool.query(
|
||||||
"DELETE FROM resources WHERE id=?",
|
"DELETE FROM resources WHERE id=?",
|
||||||
@@ -164,4 +252,11 @@ await pool.query(
|
|||||||
|
|
||||||
res.json({message:"deleted"});
|
res.json({message:"deleted"});
|
||||||
|
|
||||||
|
}catch(e){
|
||||||
|
|
||||||
|
console.error("DELETE resource error:",e);
|
||||||
|
res.status(500).json({error:"Ressource konnte nicht geloescht werden"});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -48,6 +48,7 @@ Domains jährlich: <span id="costDomain">0</span> €<br>
|
|||||||
|
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
|
<th></th>
|
||||||
<th>Name</th>
|
<th>Name</th>
|
||||||
<th>Produkt</th>
|
<th>Produkt</th>
|
||||||
<th>Provider</th>
|
<th>Provider</th>
|
||||||
@@ -85,6 +86,7 @@ Domains jährlich: <span id="costDomain">0</span> €<br>
|
|||||||
|
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
|
<th></th>
|
||||||
<th>Domain</th>
|
<th>Domain</th>
|
||||||
<th>Provider</th>
|
<th>Provider</th>
|
||||||
<th>IP</th>
|
<th>IP</th>
|
||||||
@@ -131,6 +133,7 @@ Domains jährlich: <span id="costDomain">0</span> €<br>
|
|||||||
|
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
|
<th></th>
|
||||||
<th>Name</th>
|
<th>Name</th>
|
||||||
<th>Produkt</th>
|
<th>Produkt</th>
|
||||||
<th>Provider</th>
|
<th>Provider</th>
|
||||||
|
|||||||
@@ -70,6 +70,10 @@ window.loadDomains = async function(){
|
|||||||
`).join("")
|
`).join("")
|
||||||
|
|
||||||
tr.innerHTML = `
|
tr.innerHTML = `
|
||||||
|
<td>
|
||||||
|
<button onclick="moveDomain(${d.id}, 'up')">⬆</button>
|
||||||
|
<button onclick="moveDomain(${d.id}, 'down')">⬇</button>
|
||||||
|
</td>
|
||||||
<td>
|
<td>
|
||||||
${d.domain_name}
|
${d.domain_name}
|
||||||
${sublist}
|
${sublist}
|
||||||
@@ -241,6 +245,24 @@ window.deleteDomain = async function(id){
|
|||||||
loadDomains()
|
loadDomains()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
window.moveDomain = async function(id, direction){
|
||||||
|
|
||||||
|
try{
|
||||||
|
|
||||||
|
await api(API + "/domains/" + id + "/move", {
|
||||||
|
method: "POST",
|
||||||
|
headers: {'Content-Type':'application/json'},
|
||||||
|
body: JSON.stringify({ direction })
|
||||||
|
})
|
||||||
|
|
||||||
|
loadDomains()
|
||||||
|
loadMapping()
|
||||||
|
|
||||||
|
}catch(e){
|
||||||
|
console.log(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/* =========================
|
/* =========================
|
||||||
SUBDOMAIN CRUD
|
SUBDOMAIN CRUD
|
||||||
|
|||||||
@@ -37,6 +37,10 @@ window.loadResources = async function(){
|
|||||||
|
|
||||||
tr.innerHTML = `
|
tr.innerHTML = `
|
||||||
<td>
|
<td>
|
||||||
|
<button onclick="moveResource(${r.id}, 'up')">⬆</button>
|
||||||
|
<button onclick="moveResource(${r.id}, 'down')">⬇</button>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
<span style="cursor:pointer;color:#1e3a8a;font-weight:bold"
|
<span style="cursor:pointer;color:#1e3a8a;font-weight:bold"
|
||||||
onclick='openServerDetail(${JSON.stringify(r)})'>
|
onclick='openServerDetail(${JSON.stringify(r)})'>
|
||||||
${r.name}
|
${r.name}
|
||||||
@@ -311,3 +315,21 @@ window.cancelIPEdit = function(){
|
|||||||
document.getElementById("ipCancelBtn").style.display = "none"
|
document.getElementById("ipCancelBtn").style.display = "none"
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
window.moveResource = async function(id, direction){
|
||||||
|
|
||||||
|
try {
|
||||||
|
|
||||||
|
await api(API + "/resources/" + id + "/move", {
|
||||||
|
method: "POST",
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify({ direction })
|
||||||
|
})
|
||||||
|
|
||||||
|
loadResources()
|
||||||
|
|
||||||
|
} catch (e) {
|
||||||
|
console.log(e)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
+165
-32
@@ -2,11 +2,59 @@ const express = require('express');
|
|||||||
const router = express.Router();
|
const router = express.Router();
|
||||||
const pool = require('../db');
|
const pool = require('../db');
|
||||||
|
|
||||||
|
const clean = (value) => value === "" ? null : value;
|
||||||
|
|
||||||
|
const normalizeCost = (value) => {
|
||||||
|
if (value === undefined || value === null || value === "") return null;
|
||||||
|
return String(value).replace(",", ".");
|
||||||
|
};
|
||||||
|
|
||||||
|
const mapDomainError = (err, fallbackMessage) => {
|
||||||
|
let message = fallbackMessage;
|
||||||
|
let status = 500;
|
||||||
|
|
||||||
|
if (err.code === "WARN_DATA_TRUNCATED") {
|
||||||
|
status = 400;
|
||||||
|
message = "Ungueltiges Preisformat";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (err.code === "ER_DUP_ENTRY") {
|
||||||
|
status = 400;
|
||||||
|
message = "Domain existiert bereits";
|
||||||
|
}
|
||||||
|
|
||||||
|
return { status, message };
|
||||||
|
};
|
||||||
|
|
||||||
|
const isMissingPositionColumn = (error) =>
|
||||||
|
error && error.code === "ER_BAD_FIELD_ERROR" && String(error.sqlMessage || "").includes("position");
|
||||||
|
|
||||||
|
|
||||||
// GET ALL DOMAINS
|
// GET ALL DOMAINS
|
||||||
router.get('/', async (req, res) => {
|
router.get('/', async (req, res) => {
|
||||||
try {
|
try {
|
||||||
const [rows] = await pool.query(`
|
let rows;
|
||||||
|
|
||||||
|
try {
|
||||||
|
[rows] = await pool.query(`
|
||||||
|
SELECT
|
||||||
|
d.id,
|
||||||
|
d.domain_name,
|
||||||
|
d.provider,
|
||||||
|
d.ip_address,
|
||||||
|
d.yearly_cost,
|
||||||
|
d.notes,
|
||||||
|
d.position,
|
||||||
|
r.name AS resource_name
|
||||||
|
FROM domains d
|
||||||
|
LEFT JOIN resource_ips ip ON d.ip_address = ip.ip
|
||||||
|
LEFT JOIN resources r ON ip.resource_id = r.id
|
||||||
|
ORDER BY COALESCE(d.position, d.id) ASC, d.domain_name ASC
|
||||||
|
`);
|
||||||
|
} catch (err) {
|
||||||
|
if (!isMissingPositionColumn(err)) throw err;
|
||||||
|
|
||||||
|
[rows] = await pool.query(`
|
||||||
SELECT
|
SELECT
|
||||||
d.id,
|
d.id,
|
||||||
d.domain_name,
|
d.domain_name,
|
||||||
@@ -18,8 +66,9 @@ router.get('/', async (req, res) => {
|
|||||||
FROM domains d
|
FROM domains d
|
||||||
LEFT JOIN resource_ips ip ON d.ip_address = ip.ip
|
LEFT JOIN resource_ips ip ON d.ip_address = ip.ip
|
||||||
LEFT JOIN resources r ON ip.resource_id = r.id
|
LEFT JOIN resources r ON ip.resource_id = r.id
|
||||||
ORDER BY d.domain_name
|
ORDER BY d.domain_name ASC
|
||||||
`);
|
`);
|
||||||
|
}
|
||||||
|
|
||||||
res.json(rows);
|
res.json(rows);
|
||||||
|
|
||||||
@@ -56,22 +105,48 @@ router.get('/:id', async (req, res) => {
|
|||||||
|
|
||||||
router.post('/', async (req, res) => {
|
router.post('/', async (req, res) => {
|
||||||
try {
|
try {
|
||||||
|
const domainName = req.body.domain_name ? String(req.body.domain_name).trim() : "";
|
||||||
|
|
||||||
|
if (!domainName) {
|
||||||
|
return res.status(400).json({ error: "Domain darf nicht leer sein" });
|
||||||
|
}
|
||||||
|
|
||||||
const { domain_name, provider, ip_address, yearly_cost, notes } = req.body;
|
let result;
|
||||||
|
|
||||||
const [result] = await pool.query(
|
try {
|
||||||
`INSERT INTO domains
|
const [[positionRow]] = await pool.query(
|
||||||
(domain_name, provider, ip_address, yearly_cost, notes)
|
"SELECT COALESCE(MAX(position), 0) + 1 AS nextPosition FROM domains"
|
||||||
VALUES (?, ?, ?, ?, ?)`,
|
);
|
||||||
[
|
|
||||||
domain_name,
|
[result] = await pool.query(
|
||||||
provider,
|
`INSERT INTO domains
|
||||||
ip_address,
|
(domain_name, provider, ip_address, yearly_cost, notes, position)
|
||||||
yearly_cost || null,
|
VALUES (?, ?, ?, ?, ?, ?)`,
|
||||||
notes
|
[
|
||||||
]
|
domainName,
|
||||||
);
|
clean(req.body.provider),
|
||||||
|
clean(req.body.ip_address),
|
||||||
|
normalizeCost(req.body.yearly_cost),
|
||||||
|
clean(req.body.notes),
|
||||||
|
positionRow.nextPosition
|
||||||
|
]
|
||||||
|
);
|
||||||
|
} catch (err) {
|
||||||
|
if (!isMissingPositionColumn(err)) throw err;
|
||||||
|
|
||||||
|
[result] = await pool.query(
|
||||||
|
`INSERT INTO domains
|
||||||
|
(domain_name, provider, ip_address, yearly_cost, notes)
|
||||||
|
VALUES (?, ?, ?, ?, ?)`,
|
||||||
|
[
|
||||||
|
domainName,
|
||||||
|
clean(req.body.provider),
|
||||||
|
clean(req.body.ip_address),
|
||||||
|
normalizeCost(req.body.yearly_cost),
|
||||||
|
clean(req.body.notes)
|
||||||
|
]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -84,20 +159,11 @@ notes
|
|||||||
|
|
||||||
console.error("CREATE domain error:", err);
|
console.error("CREATE domain error:", err);
|
||||||
|
|
||||||
let message="Database error"
|
const { status, message } = mapDomainError(err, "Domain konnte nicht gespeichert werden");
|
||||||
|
|
||||||
if(err.code==="WARN_DATA_TRUNCATED"){
|
res.status(status).json({
|
||||||
message="Invalid price format (use 1.99)"
|
error: message
|
||||||
}
|
});
|
||||||
|
|
||||||
if(err.code==="ER_DUP_ENTRY"){
|
|
||||||
message="Domain already exists"
|
|
||||||
}
|
|
||||||
|
|
||||||
res.status(500).json({
|
|
||||||
error: message,
|
|
||||||
details: err.sqlMessage
|
|
||||||
})
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -106,7 +172,11 @@ details: err.sqlMessage
|
|||||||
// UPDATE DOMAIN
|
// UPDATE DOMAIN
|
||||||
router.put('/:id', async (req, res) => {
|
router.put('/:id', async (req, res) => {
|
||||||
try {
|
try {
|
||||||
const { domain_name, provider, ip_address, yearly_cost, notes } = req.body;
|
const domainName = req.body.domain_name ? String(req.body.domain_name).trim() : "";
|
||||||
|
|
||||||
|
if (!domainName) {
|
||||||
|
return res.status(400).json({ error: "Domain darf nicht leer sein" });
|
||||||
|
}
|
||||||
|
|
||||||
await pool.query(
|
await pool.query(
|
||||||
`UPDATE domains SET
|
`UPDATE domains SET
|
||||||
@@ -116,14 +186,77 @@ router.put('/:id', async (req, res) => {
|
|||||||
yearly_cost = ?,
|
yearly_cost = ?,
|
||||||
notes = ?
|
notes = ?
|
||||||
WHERE id = ?`,
|
WHERE id = ?`,
|
||||||
[domain_name, provider, ip_address, yearly_cost, notes, req.params.id]
|
[
|
||||||
|
domainName,
|
||||||
|
clean(req.body.provider),
|
||||||
|
clean(req.body.ip_address),
|
||||||
|
normalizeCost(req.body.yearly_cost),
|
||||||
|
clean(req.body.notes),
|
||||||
|
req.params.id
|
||||||
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
res.json({ message: "Domain updated" });
|
res.json({ message: "Domain updated" });
|
||||||
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error("UPDATE domain error:", err);
|
console.error("UPDATE domain error:", err);
|
||||||
res.status(500).json({ error: "DB error" });
|
const { status, message } = mapDomainError(err, "Domain konnte nicht gespeichert werden");
|
||||||
|
res.status(status).json({ error: message });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
router.post('/:id/move', async (req, res) => {
|
||||||
|
try {
|
||||||
|
const { direction } = req.body
|
||||||
|
|
||||||
|
if (direction !== "up" && direction !== "down") {
|
||||||
|
return res.status(400).json({ error: "Ungueltige Richtung" })
|
||||||
|
}
|
||||||
|
|
||||||
|
let rows
|
||||||
|
|
||||||
|
try {
|
||||||
|
await pool.query(
|
||||||
|
"UPDATE domains SET position = id WHERE position IS NULL"
|
||||||
|
)
|
||||||
|
|
||||||
|
;[rows] = await pool.query(
|
||||||
|
"SELECT id, domain_name FROM domains ORDER BY position, domain_name, id"
|
||||||
|
)
|
||||||
|
} catch (err) {
|
||||||
|
if (isMissingPositionColumn(err)) {
|
||||||
|
return res.status(400).json({ error: "Position-Spalte in domains fehlt noch" })
|
||||||
|
}
|
||||||
|
throw err
|
||||||
|
}
|
||||||
|
|
||||||
|
const index = rows.findIndex(row => row.id == req.params.id)
|
||||||
|
|
||||||
|
if (index === -1) {
|
||||||
|
return res.status(404).json({ error: "Domain nicht gefunden" })
|
||||||
|
}
|
||||||
|
|
||||||
|
const swapIndex = direction === "up" ? index - 1 : index + 1
|
||||||
|
|
||||||
|
if (swapIndex < 0 || swapIndex >= rows.length) {
|
||||||
|
return res.json({ success: true })
|
||||||
|
}
|
||||||
|
|
||||||
|
const moved = rows.splice(index, 1)[0]
|
||||||
|
rows.splice(swapIndex, 0, moved)
|
||||||
|
|
||||||
|
for(let i = 0; i < rows.length; i++){
|
||||||
|
await pool.query(
|
||||||
|
"UPDATE domains SET position = ? WHERE id = ?",
|
||||||
|
[i + 1, rows[i].id]
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
res.json({ success: true })
|
||||||
|
|
||||||
|
} catch (err) {
|
||||||
|
console.error("MOVE domain error:", err);
|
||||||
|
res.status(500).json({ error: "Reihenfolge konnte nicht geaendert werden" })
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -140,7 +273,7 @@ router.delete('/:id', async (req, res) => {
|
|||||||
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error("DELETE domain error:", err);
|
console.error("DELETE domain error:", err);
|
||||||
res.status(500).json({ error: "DB error" });
|
res.status(500).json({ error: "Domain konnte nicht geloescht werden" });
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -1,12 +1,76 @@
|
|||||||
const express = require("express");
|
const express = require("express");
|
||||||
const router = express.Router();
|
const router = express.Router();
|
||||||
|
const db = require("../db");
|
||||||
const controller = require("../controllers/resourceController");
|
const controller = require("../controllers/resourceController");
|
||||||
|
|
||||||
|
const isMissingPositionColumn = (error) =>
|
||||||
|
error && error.code === "ER_BAD_FIELD_ERROR" && String(error.sqlMessage || "").includes("position");
|
||||||
|
|
||||||
router.get("/active", controller.getActive);
|
router.get("/active", controller.getActive);
|
||||||
router.get("/cancelled", controller.getCancelled);
|
router.get("/cancelled", controller.getCancelled);
|
||||||
|
|
||||||
|
router.post("/:id/move", async (req, res) => {
|
||||||
|
|
||||||
|
const { direction } = req.body
|
||||||
|
|
||||||
|
if (direction !== "up" && direction !== "down") {
|
||||||
|
return res.status(400).json({ error: "Ungueltige Richtung" })
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
|
||||||
|
let rows
|
||||||
|
|
||||||
|
try {
|
||||||
|
await db.query(
|
||||||
|
"UPDATE resources SET position = id WHERE position IS NULL"
|
||||||
|
)
|
||||||
|
|
||||||
|
;[rows] = await db.query(
|
||||||
|
"SELECT id FROM resources WHERE status != 'gekündigt' ORDER BY position, id"
|
||||||
|
)
|
||||||
|
} catch (e) {
|
||||||
|
if (isMissingPositionColumn(e)) {
|
||||||
|
return res.status(400).json({ error: "Position-Spalte in resources fehlt noch" })
|
||||||
|
}
|
||||||
|
throw e
|
||||||
|
}
|
||||||
|
|
||||||
|
const index = rows.findIndex(r => r.id == req.params.id)
|
||||||
|
|
||||||
|
if (index === -1) {
|
||||||
|
return res.status(404).json({ error: "Ressource nicht gefunden" })
|
||||||
|
}
|
||||||
|
|
||||||
|
const swapIndex = direction === "up" ? index - 1 : index + 1
|
||||||
|
|
||||||
|
if (swapIndex < 0 || swapIndex >= rows.length) {
|
||||||
|
return res.json({ success: true })
|
||||||
|
}
|
||||||
|
|
||||||
|
const moved = rows.splice(index, 1)[0]
|
||||||
|
rows.splice(swapIndex, 0, moved)
|
||||||
|
|
||||||
|
for(let i = 0; i < rows.length; i++){
|
||||||
|
await db.query(
|
||||||
|
"UPDATE resources SET position=? WHERE id=?",
|
||||||
|
[i + 1, rows[i].id]
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
res.json({ success: true })
|
||||||
|
|
||||||
|
} catch (e) {
|
||||||
|
console.error("MOVE resource error:", e)
|
||||||
|
res.status(500).json({ error: "Reihenfolge konnte nicht geaendert werden" })
|
||||||
|
}
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
router.post("/", controller.create);
|
router.post("/", controller.create);
|
||||||
router.put("/:id", controller.update);
|
router.put("/:id", controller.update);
|
||||||
router.delete("/:id", controller.remove);
|
router.delete("/:id", controller.remove);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
module.exports = router;
|
module.exports = router;
|
||||||
|
|||||||
@@ -2,6 +2,20 @@ const express = require("express")
|
|||||||
const router = express.Router()
|
const router = express.Router()
|
||||||
const db = require("../db")
|
const db = require("../db")
|
||||||
|
|
||||||
|
const clean = (value) => value === "" ? null : value
|
||||||
|
|
||||||
|
const mapSubdomainError = (err, fallbackMessage) => {
|
||||||
|
let message = fallbackMessage
|
||||||
|
let status = 500
|
||||||
|
|
||||||
|
if(err.code === "ER_DUP_ENTRY"){
|
||||||
|
status = 400
|
||||||
|
message = "Subdomain existiert bereits"
|
||||||
|
}
|
||||||
|
|
||||||
|
return {status, message}
|
||||||
|
}
|
||||||
|
|
||||||
/* alle Subdomains laden */
|
/* alle Subdomains laden */
|
||||||
|
|
||||||
router.get("/", async (req,res)=>{
|
router.get("/", async (req,res)=>{
|
||||||
@@ -24,7 +38,7 @@ res.json(rows)
|
|||||||
}catch(e){
|
}catch(e){
|
||||||
|
|
||||||
console.error("SUBDOMAIN LIST error:",e)
|
console.error("SUBDOMAIN LIST error:",e)
|
||||||
res.status(500).json({error:"DB error"})
|
res.status(500).json({error:"Subdomains konnten nicht geladen werden"})
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -46,7 +60,7 @@ res.json(rows)
|
|||||||
}catch(e){
|
}catch(e){
|
||||||
|
|
||||||
console.error("SUBDOMAIN DOMAIN error:",e)
|
console.error("SUBDOMAIN DOMAIN error:",e)
|
||||||
res.status(500).json({error:"DB error"})
|
res.status(500).json({error:"Subdomains der Domain konnten nicht geladen werden"})
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -54,11 +68,19 @@ res.status(500).json({error:"DB error"})
|
|||||||
|
|
||||||
|
|
||||||
router.delete("/:id", async (req,res)=>{
|
router.delete("/:id", async (req,res)=>{
|
||||||
|
try{
|
||||||
|
|
||||||
await db.query("DELETE FROM subdomains WHERE id=?",[req.params.id])
|
await db.query("DELETE FROM subdomains WHERE id=?",[req.params.id])
|
||||||
|
|
||||||
res.json({success:true})
|
res.json({success:true})
|
||||||
|
|
||||||
|
}catch(e){
|
||||||
|
|
||||||
|
console.error("DELETE subdomain error:",e)
|
||||||
|
res.status(500).json({error:"Subdomain konnte nicht geloescht werden"})
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
@@ -66,20 +88,31 @@ router.post("/", async (req,res)=>{
|
|||||||
|
|
||||||
try{
|
try{
|
||||||
|
|
||||||
const {domain_id, subdomain, ip_address} = req.body
|
const domainId = clean(req.body.domain_id)
|
||||||
|
const subdomain = req.body.subdomain ? String(req.body.subdomain).trim() : ""
|
||||||
|
const ipAddress = clean(req.body.ip_address)
|
||||||
|
|
||||||
|
if(!domainId){
|
||||||
|
return res.status(400).json({error:"Domain fehlt"})
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!subdomain){
|
||||||
|
return res.status(400).json({error:"Subdomain darf nicht leer sein"})
|
||||||
|
}
|
||||||
|
|
||||||
await db.query(`
|
await db.query(`
|
||||||
INSERT INTO subdomains
|
INSERT INTO subdomains
|
||||||
(domain_id, subdomain, ip_address)
|
(domain_id, subdomain, ip_address)
|
||||||
VALUES (?,?,?)
|
VALUES (?,?,?)
|
||||||
`,[domain_id, subdomain, ip_address])
|
`,[domainId, subdomain, ipAddress])
|
||||||
|
|
||||||
res.json({success:true})
|
res.json({success:true})
|
||||||
|
|
||||||
}catch(e){
|
}catch(e){
|
||||||
|
|
||||||
console.error("CREATE subdomain error:",e)
|
console.error("CREATE subdomain error:",e)
|
||||||
res.status(500).json({error:"DB error"})
|
const {status, message} = mapSubdomainError(e, "Subdomain konnte nicht gespeichert werden")
|
||||||
|
res.status(status).json({error:message})
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -89,20 +122,26 @@ router.put("/:id", async (req,res)=>{
|
|||||||
|
|
||||||
try{
|
try{
|
||||||
|
|
||||||
const {subdomain, ip_address} = req.body
|
const subdomain = req.body.subdomain ? String(req.body.subdomain).trim() : ""
|
||||||
|
const ipAddress = clean(req.body.ip_address)
|
||||||
|
|
||||||
|
if(!subdomain){
|
||||||
|
return res.status(400).json({error:"Subdomain darf nicht leer sein"})
|
||||||
|
}
|
||||||
|
|
||||||
await db.query(`
|
await db.query(`
|
||||||
UPDATE subdomains
|
UPDATE subdomains
|
||||||
SET subdomain=?, ip_address=?
|
SET subdomain=?, ip_address=?
|
||||||
WHERE id=?
|
WHERE id=?
|
||||||
`,[subdomain, ip_address, req.params.id])
|
`,[subdomain, ipAddress, req.params.id])
|
||||||
|
|
||||||
res.json({success:true})
|
res.json({success:true})
|
||||||
|
|
||||||
}catch(e){
|
}catch(e){
|
||||||
|
|
||||||
console.error("UPDATE subdomain error:",e)
|
console.error("UPDATE subdomain error:",e)
|
||||||
res.status(500).json({error:"DB error"})
|
const {status, message} = mapSubdomainError(e, "Subdomain konnte nicht gespeichert werden")
|
||||||
|
res.status(status).json({error:message})
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -111,4 +150,3 @@ res.status(500).json({error:"DB error"})
|
|||||||
|
|
||||||
module.exports = router
|
module.exports = router
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user