This commit is contained in:
jhartworks
2026-06-25 14:35:57 +02:00
parent bf1214239f
commit a12ef41d1b
116 changed files with 978 additions and 59769 deletions
+39 -15
View File
@@ -7,6 +7,7 @@ import { pool } from "./db.js";
import {
createWideTrendTable,
ensureIspMetadata,
LOCAL_POINT_LIMIT,
fetchLatestValues,
fetchTrendSeries,
getAvailableIsps,
@@ -39,18 +40,24 @@ const allowedOrigins = config.corsOrigin
.map((entry) => entry.trim())
.filter(Boolean);
app.use(
cors({
origin(origin, callback) {
if (!origin || allowedOrigins.includes("*") || allowedOrigins.includes(origin)) {
callback(null, true);
return;
}
const corsOptions = {
origin(origin, callback) {
if (!origin || allowedOrigins.includes("*") || allowedOrigins.includes(origin)) {
callback(null, true);
return;
}
callback(new Error(`Origin not allowed: ${origin}`));
},
credentials: true,
methods: ["GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"],
allowedHeaders: ["Content-Type", "Authorization"],
};
app.use(cors(corsOptions));
app.options(/.*/, cors(corsOptions));
callback(new Error("Origin not allowed."));
},
})
);
app.use(express.json({ limit: "50mb" }));
function serializeUser(row) {
@@ -370,6 +377,21 @@ app.get("/api/auth/me", authenticateToken, wrap(async (req, res) => {
return res.json({ user: await serializeUserWithPermissions(rows[0]) });
}));
async function resolveIspPointCount(isp) {
const safeIsp = sanitizeIspName(isp);
const [rows] = await pool.query(
`
SELECT COUNT(*) AS pointCount
FROM information_schema.COLUMNS
WHERE TABLE_SCHEMA = ?
AND TABLE_NAME = ?
AND COLUMN_NAME LIKE 'Wert%'
`,
[config.dbName, safeIsp]
);
return Number(rows[0]?.pointCount || 0) || (safeIsp.startsWith("trend_") ? LOCAL_POINT_LIMIT : 200);
}
app.get("/api/isps", authenticateToken, wrap(async (_req, res) => {
const isps = await getAvailableIsps(pool, config.dbName);
const response = await Promise.all(
@@ -387,7 +409,8 @@ app.get("/api/isps", authenticateToken, wrap(async (_req, res) => {
}));
app.get("/api/isps/:isp/aliases", authenticateToken, wrap(async (req, res) => {
const metadata = await ensureIspMetadata(redis, req.params.isp);
const pointCount = await resolveIspPointCount(req.params.isp);
const metadata = await ensureIspMetadata(redis, req.params.isp, pointCount);
res.json(metadata);
}));
@@ -397,6 +420,7 @@ app.put(
requirePermission("edit_aliases"),
wrap(async (req, res) => {
const { displayName, description, points } = req.body || {};
const pointCount = await resolveIspPointCount(req.params.isp);
const updated = await updateIspMetadata(redis, req.params.isp, (metadata) => {
if (typeof displayName === "string") {
metadata.displayName = displayName.trim() || metadata.displayName;
@@ -424,7 +448,7 @@ app.put(
}
return metadata;
});
}, pointCount);
res.json(updated);
})
@@ -437,7 +461,7 @@ app.post("/api/trend-tables", authenticateToken, requirePermission("manage_sourc
const metadata = await updateIspMetadata(redis, safeName, (current) => ({
...current,
displayName: displayName || current.displayName,
}));
}), LOCAL_POINT_LIMIT);
res.status(201).json({ tableName: safeName, metadata });
}));
@@ -458,7 +482,7 @@ app.post(
const normalized = [];
for (const isp of targets) {
const metadata = await ensureIspMetadata(redis, isp.tableName, isp.pointCount || 200);
const metadata = await ensureIspMetadata(redis, isp.tableName, isp.pointCount || (isp.tableName.startsWith("trend_") ? LOCAL_POINT_LIMIT : 200));
normalized.push({ isp: isp.tableName, metadata });
}
+37 -8
View File
@@ -1,4 +1,7 @@
const META_PREFIX = "meta:isp:";
export const LEGACY_POINT_LIMIT = 200;
export const LOCAL_POINT_LIMIT = 2000;
export function buildPointKey(isp, pointIndex) {
return `${isp}:${pointIndex}`;
@@ -17,8 +20,8 @@ export function sanitizeIspName(value) {
export function sanitizePointIndex(value) {
const pointIndex = Number(value);
if (!Number.isInteger(pointIndex) || pointIndex < 1 || pointIndex > 200) {
throw new Error("Point index must be between 1 and 200.");
if (!Number.isInteger(pointIndex) || pointIndex < 1 || pointIndex > LOCAL_POINT_LIMIT) {
throw new Error(`Point index must be between 1 and ${LOCAL_POINT_LIMIT}.`);
}
return pointIndex;
@@ -48,7 +51,7 @@ export function parseTrendValue(rawValue, factor = 1) {
function buildValueColumns() {
const columns = [];
for (let index = 1; index <= 200; index += 1) {
for (let index = 1; index <= LOCAL_POINT_LIMIT; index += 1) {
columns.push('`Wert' + index + '` TEXT NULL');
}
@@ -136,6 +139,31 @@ function createDefaultPoint(index) {
};
}
async function ensureWideTrendColumns(pool, tableName) {
const [columns] = await pool.query(
`
SELECT COLUMN_NAME AS columnName
FROM information_schema.COLUMNS
WHERE TABLE_SCHEMA = DATABASE()
AND TABLE_NAME = ?
AND COLUMN_NAME LIKE 'Wert%'
`,
[tableName]
);
const existing = new Set(columns.map((column) => column.columnName));
const missing = [];
for (let index = 1; index <= LOCAL_POINT_LIMIT; index += 1) {
const columnName = `Wert${index}`;
if (!existing.has(columnName)) {
missing.push(`ADD COLUMN \`${columnName}\` TEXT NULL`);
}
}
for (let index = 0; index < missing.length; index += 50) {
await pool.query(`ALTER TABLE \`${tableName}\` ${missing.slice(index, index + 50).join(", ")}`);
}
}
export async function createWideTrendTable(pool, tableName) {
const safeTable = sanitizeIspName(tableName);
const sql = `
@@ -149,6 +177,7 @@ export async function createWideTrendTable(pool, tableName) {
`;
await pool.query(sql);
await ensureWideTrendColumns(pool, safeTable);
return safeTable;
}
@@ -197,7 +226,7 @@ export async function getAvailableIsps(pool, dbName) {
});
}
export function createDefaultMetadata(isp, pointCount = 200) {
export function createDefaultMetadata(isp, pointCount = LEGACY_POINT_LIMIT) {
const points = {};
for (let index = 1; index <= pointCount; index += 1) {
@@ -212,7 +241,7 @@ export function createDefaultMetadata(isp, pointCount = 200) {
};
}
export async function ensureIspMetadata(redis, isp, pointCount = 200) {
export async function ensureIspMetadata(redis, isp, pointCount = LEGACY_POINT_LIMIT) {
const safeIsp = sanitizeIspName(isp);
const key = `${META_PREFIX}${safeIsp}`;
const existing = await redis.get(key);
@@ -285,8 +314,8 @@ export async function ensureIspMetadata(redis, isp, pointCount = 200) {
return metadata;
}
export async function updateIspMetadata(redis, isp, updater) {
const metadata = await ensureIspMetadata(redis, isp);
export async function updateIspMetadata(redis, isp, updater, pointCount = LEGACY_POINT_LIMIT) {
const metadata = await ensureIspMetadata(redis, isp, pointCount);
const updated = updater(JSON.parse(JSON.stringify(metadata)));
await redis.set(`${META_PREFIX}${sanitizeIspName(isp)}`, JSON.stringify(updated));
return updated;
@@ -309,7 +338,7 @@ export async function getPointDescriptors(redis, points) {
const descriptors = [];
for (const [isp, pointIndexes] of grouped.entries()) {
const metadata = await ensureIspMetadata(redis, isp);
const metadata = await ensureIspMetadata(redis, isp, LOCAL_POINT_LIMIT);
pointIndexes.forEach((pointIndex) => {
const pointMeta = metadata.points[String(pointIndex)];