API added

This commit is contained in:
jhartworks
2026-07-24 13:43:47 +02:00
parent 19d27f9de9
commit 986a43d5a5
12 changed files with 1112 additions and 146 deletions
+53 -8
View File
@@ -1,4 +1,4 @@
import bcrypt from "bcryptjs";
import bcrypt from "bcryptjs";
import cors from "cors";
import express from "express";
import { authenticateToken, authenticateTokenOptional, createToken, requireRoles } from "./auth.js";
@@ -214,11 +214,22 @@ function sanitizeApiSettings(body = {}) {
};
}
function sanitizeRunnerJob(body = {}, existing = {}) {
const time = String(body.time ?? existing.time ?? "06:00").trim();
if (!/^\d{2}:\d{2}$/.test(time)) {
throw new Error("Uhrzeit bitte im Format HH:MM angeben.");
function normalizeRunnerTime(value) {
const raw = String(value ?? "").trim().replace(/\s*uhr$/i, "").replace(".", ":");
const match = raw.match(/^(\d{1,2}):(\d{2})$/);
if (!match) {
throw new Error("Uhrzeit bitte im deutschen Format HH:MM Uhr angeben.");
}
const hours = Number(match[1]);
const minutes = Number(match[2]);
if (hours > 23 || minutes > 59) {
throw new Error("Uhrzeit ist ungültig.");
}
return `${String(hours).padStart(2, "0")}:${String(minutes).padStart(2, "0")}`;
}
function sanitizeRunnerJob(body = {}, existing = {}) {
const time = normalizeRunnerTime(body.time ?? existing.time ?? "06:00");
return {
...existing,
id: existing.id || body.id || `job-${Date.now()}`,
@@ -246,6 +257,26 @@ async function listRunnerJobs() {
}));
return jobs.filter(Boolean).sort((left, right) => String(left.time).localeCompare(String(right.time)));
}
async function listRunnerQueue() {
const entries = await redis.lrange("runner:run-now", 0, 99);
const jobs = await listRunnerJobs();
const jobsById = new Map(jobs.map((job) => [job.id, job]));
return entries.map((raw, index) => {
try {
const queued = JSON.parse(raw);
const job = jobsById.get(queued.jobId);
return {
position: index + 1,
jobId: queued.jobId || "",
jobName: job?.name || queued.jobId || "Unbekannter Job",
requestedAt: queued.requestedAt || "",
requestedBy: queued.requestedBy || "",
};
} catch {
return { position: index + 1, jobId: "", jobName: "Ungültiger Auftrag", requestedAt: "", requestedBy: "" };
}
});
}
async function resolveSelectionPoints(body, user) {
if (body.selectionId) {
const selection = await getSelection(redis, body.selectionId);
@@ -803,8 +834,9 @@ 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({
...sanitizeApiSettings(current),
settings,
mailConfigured: Boolean(current.mailApiKey),
whatsappConfigured: Boolean(current.whatsappApiKey),
voipConfigured: Boolean(current.voipApiKey),
@@ -814,11 +846,16 @@ 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 || {});
await redis.set("integration:api", JSON.stringify(payload));
res.json({ ...payload, mailConfigured: Boolean(payload.mailApiKey), whatsappConfigured: Boolean(payload.whatsappApiKey), voipConfigured: Boolean(payload.voipApiKey) });
res.json({
settings: payload,
mailConfigured: Boolean(payload.mailApiKey),
whatsappConfigured: Boolean(payload.whatsappApiKey),
voipConfigured: Boolean(payload.voipApiKey),
});
}));
app.get("/api/runner/jobs", authenticateToken, requirePermission("manage_runner"), wrap(async (_req, res) => {
res.json({ jobs: await listRunnerJobs() });
res.json({ jobs: await listRunnerJobs(), queue: await listRunnerQueue() });
}));
app.post("/api/runner/jobs", authenticateToken, requirePermission("manage_runner"), wrap(async (req, res) => {
@@ -839,6 +876,14 @@ app.patch("/api/runner/jobs/:jobId", authenticateToken, requirePermission("manag
res.json(job);
}));
app.post("/api/runner/jobs/:jobId/test", authenticateToken, requirePermission("manage_runner"), wrap(async (req, res) => {
const id = req.params.jobId;
const existing = JSON.parse(await redis.get(`runner:job:${id}`) || "null");
if (!existing) return res.status(404).json({ error: "Runner-Job nicht gefunden." });
await redis.rpush("runner:run-now", JSON.stringify({ jobId: id, requestedAt: new Date().toISOString(), requestedBy: req.user.sub }));
res.status(202).json({ queued: true, jobId: id });
}));
app.delete("/api/runner/jobs/:jobId", authenticateToken, requirePermission("manage_runner"), wrap(async (req, res) => {
const id = req.params.jobId;
await redis.del(`runner:job:${id}`, `runner:last:${id}`, `runner:last-result:${id}`);