Refactor code structure for improved readability and maintainability
This commit is contained in:
+2
-2
@@ -1,9 +1,9 @@
|
||||
FROM node:20-alpine
|
||||
FROM node:22-alpine
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
COPY package*.json ./
|
||||
RUN npm install
|
||||
RUN npm ci
|
||||
|
||||
COPY src ./src
|
||||
|
||||
|
||||
+3
-2
@@ -5,7 +5,8 @@
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "node --watch src/index.js",
|
||||
"start": "node src/index.js"
|
||||
"start": "node src/index.js",
|
||||
"check": "node --check src/index.js && node --check src/db.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"bcryptjs": "^2.4.3",
|
||||
@@ -16,4 +17,4 @@
|
||||
"jsonwebtoken": "^9.0.2",
|
||||
"mysql2": "^3.12.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
+29
-10
@@ -1,4 +1,4 @@
|
||||
import mysql from "mysql2/promise";
|
||||
import mysql from "mysql2/promise";
|
||||
import { config } from "./config.js";
|
||||
|
||||
function createPool(password) {
|
||||
@@ -24,7 +24,15 @@ function isAccessDenied(error) {
|
||||
return error?.code === "ER_ACCESS_DENIED_ERROR";
|
||||
}
|
||||
|
||||
async function query(sql, values) {
|
||||
function isTransientConnectionError(error) {
|
||||
return ["ECONNREFUSED", "ECONNRESET", "ETIMEDOUT", "PROTOCOL_CONNECTION_LOST"].includes(error?.code);
|
||||
}
|
||||
|
||||
function delay(milliseconds) {
|
||||
return new Promise((resolve) => setTimeout(resolve, milliseconds));
|
||||
}
|
||||
|
||||
async function queryWithCredentials(sql, values) {
|
||||
try {
|
||||
return await activePool.query(sql, values);
|
||||
} catch (error) {
|
||||
@@ -33,21 +41,18 @@ async function query(sql, values) {
|
||||
}
|
||||
|
||||
for (const password of candidatePasswords) {
|
||||
if (password === activePassword) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (password === activePassword) continue;
|
||||
const fallbackPool = createPool(password);
|
||||
try {
|
||||
const result = await fallbackPool.query(sql, values);
|
||||
const previousPool = activePool;
|
||||
activePool = fallbackPool;
|
||||
activePassword = password;
|
||||
previousPool.end().catch(() => undefined);
|
||||
return result;
|
||||
} catch (fallbackError) {
|
||||
await fallbackPool.end().catch(() => undefined);
|
||||
if (!isAccessDenied(fallbackError)) {
|
||||
throw fallbackError;
|
||||
}
|
||||
if (!isAccessDenied(fallbackError)) throw fallbackError;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -55,4 +60,18 @@ async function query(sql, values) {
|
||||
}
|
||||
}
|
||||
|
||||
export const pool = { query };
|
||||
async function query(sql, values) {
|
||||
let lastError;
|
||||
for (let attempt = 0; attempt < 5; attempt += 1) {
|
||||
try {
|
||||
return await queryWithCredentials(sql, values);
|
||||
} catch (error) {
|
||||
lastError = error;
|
||||
if (!isTransientConnectionError(error) || attempt === 4) throw error;
|
||||
await delay(250 * 2 ** attempt);
|
||||
}
|
||||
}
|
||||
throw lastError;
|
||||
}
|
||||
|
||||
export const pool = { query };
|
||||
+26
-11
@@ -202,18 +202,33 @@ async function ensureRolesSchema() {
|
||||
}
|
||||
|
||||
|
||||
function sanitizeApiSettings(body = {}) {
|
||||
function updateSecret(body, existing, field, clearField) {
|
||||
if (body[clearField] === true) return "";
|
||||
const replacement = String(body[field] ?? "").trim();
|
||||
return replacement || String(existing[field] || "").trim();
|
||||
}
|
||||
|
||||
function sanitizeApiSettings(body = {}, existing = {}) {
|
||||
return {
|
||||
mailUrl: String(body.mailUrl || "https://mailapi.se-inno.de").trim() || "https://mailapi.se-inno.de",
|
||||
mailApiKey: String(body.mailApiKey || "").trim(),
|
||||
whatsappUrl: String(body.whatsappUrl || "https://whatsappapi.se-inno.de").trim() || "https://whatsappapi.se-inno.de",
|
||||
whatsappApiKey: String(body.whatsappApiKey || "").trim(),
|
||||
voipUrl: String(body.voipUrl || "https://voipapi.se-inno.de").trim() || "https://voipapi.se-inno.de",
|
||||
voipApiKey: String(body.voipApiKey || "").trim(),
|
||||
mailUrl: String(body.mailUrl ?? existing.mailUrl ?? "https://mailapi.se-inno.de").trim() || "https://mailapi.se-inno.de",
|
||||
mailApiKey: updateSecret(body, existing, "mailApiKey", "clearMailApiKey"),
|
||||
whatsappUrl: String(body.whatsappUrl ?? existing.whatsappUrl ?? "https://whatsappapi.se-inno.de").trim() || "https://whatsappapi.se-inno.de",
|
||||
whatsappApiKey: updateSecret(body, existing, "whatsappApiKey", "clearWhatsappApiKey"),
|
||||
voipUrl: String(body.voipUrl ?? existing.voipUrl ?? "https://voipapi.se-inno.de").trim() || "https://voipapi.se-inno.de",
|
||||
voipApiKey: updateSecret(body, existing, "voipApiKey", "clearVoipApiKey"),
|
||||
updatedAt: new Date().toISOString(),
|
||||
};
|
||||
}
|
||||
|
||||
function publicApiSettings(settings = {}) {
|
||||
return {
|
||||
mailUrl: settings.mailUrl || "https://mailapi.se-inno.de",
|
||||
whatsappUrl: settings.whatsappUrl || "https://whatsappapi.se-inno.de",
|
||||
voipUrl: settings.voipUrl || "https://voipapi.se-inno.de",
|
||||
updatedAt: settings.updatedAt || null,
|
||||
};
|
||||
}
|
||||
|
||||
function normalizeRunnerTime(value) {
|
||||
const raw = String(value ?? "").trim().replace(/\s*uhr$/i, "").replace(".", ":");
|
||||
const match = raw.match(/^(\d{1,2}):(\d{2})$/);
|
||||
@@ -834,9 +849,8 @@ app.post("/api/dashboard/data", authenticateTokenOptional, wrap(async (req, res)
|
||||
|
||||
app.get("/api/integrations", authenticateToken, requirePermission("manage_integrations"), wrap(async (_req, res) => {
|
||||
const current = JSON.parse(await redis.get("integration:api") || "{}");
|
||||
const settings = sanitizeApiSettings(current);
|
||||
res.json({
|
||||
settings,
|
||||
settings: publicApiSettings(current),
|
||||
mailConfigured: Boolean(current.mailApiKey),
|
||||
whatsappConfigured: Boolean(current.whatsappApiKey),
|
||||
voipConfigured: Boolean(current.voipApiKey),
|
||||
@@ -844,10 +858,11 @@ app.get("/api/integrations", authenticateToken, requirePermission("manage_integr
|
||||
}));
|
||||
|
||||
app.put("/api/integrations", authenticateToken, requirePermission("manage_integrations"), wrap(async (req, res) => {
|
||||
const payload = sanitizeApiSettings(req.body || {});
|
||||
const current = JSON.parse(await redis.get("integration:api") || "{}");
|
||||
const payload = sanitizeApiSettings(req.body || {}, current);
|
||||
await redis.set("integration:api", JSON.stringify(payload));
|
||||
res.json({
|
||||
settings: payload,
|
||||
settings: publicApiSettings(payload),
|
||||
mailConfigured: Boolean(payload.mailApiKey),
|
||||
whatsappConfigured: Boolean(payload.whatsappApiKey),
|
||||
voipConfigured: Boolean(payload.voipApiKey),
|
||||
|
||||
Reference in New Issue
Block a user