Files
DogFatherGit e87aca3019 Postfach/Team-Verwaltung/Supporter-Abo als Node.js/Express-Server portiert
Vollständiger 1:1-Port des cloudflare-worker/ (dogfather-universe-postfach)
auf server-internal/: Bewerbungs-Postfach mit Rollen/Rechten, Team-
Zugangsverwaltung (verschlüsselte Owner-only-Codes), DogiCrew-Supporter-Abo
(PayPal Subscriptions, Google-Login, Resend-E-Mail, 30-Monats-Prämienzyklus)
und automatische TikTok-Live-Erkennung. D1 -> better-sqlite3, Cloudflare-
Cron -> node-cron. Läuft ohne gesetzte Secrets weiter (PayPal/Google/Resend/
Ticketanizer/Discord bleiben "nicht konfiguriert" statt kaputt), bis die
echten Zugangsdaten eingetragen werden.
2026-08-05 18:57:19 +02:00

61 lines
2.8 KiB
JavaScript

/* =====================================================================
lib/ticketanizer.js — Bewerbungen als Ticket bei Ticketanizer eröffnen.
1:1 portiert aus cloudflare-worker/src/lib/ticketanizer.js.
===================================================================== */
const TICKETANIZER_BASIS_URL = "https://api.ticketanizer.com";
const PANEL_IDS = { modi: 4, kooperation: 5, scout: 6, creator: 7 };
const FIELD_LABELS = {
name: "Name", email: "E-Mail", kontakt: "Kontakt (Discord/TikTok)", erfahrung: "Erfahrung",
verfuegbarkeit: "Verfügbarkeit", warum: "Warum möchtest du dabei sein?", platform: "Plattform",
content: "Content-Art", firma: "Firma / Projekt", idee: "Idee / Anliegen",
};
function ersteNachrichtAufbauen(type, data) {
const zeilen = Object.entries(data || {})
.filter(([, v]) => String(v ?? "").trim() !== "")
.map(([key, value]) => `**${FIELD_LABELS[key] || key}:** ${String(value).trim()}`);
if (!zeilen.length) zeilen.push("_Keine weiteren Angaben übermittelt._");
const text = zeilen.join("\n");
return text.length > 3500 ? text.slice(0, 3500) + "…" : text;
}
const warten = (ms) => new Promise((fertig) => setTimeout(fertig, ms));
const MAX_VERSUCHE = 3;
const STANDARD_WARTEZEITEN_MS = [2000, 5000];
async function ticketAnfrageSenden(apiKey, panelId, visitorName, firstMessage) {
for (let versuch = 1; versuch <= MAX_VERSUCHE; versuch++) {
const antwort = await fetch(`${TICKETANIZER_BASIS_URL}/v1/tickets`, {
method: "POST",
headers: { "X-API-Key": apiKey, "Content-Type": "application/json" },
body: JSON.stringify({ panel_id: panelId, visitor_name: visitorName, first_message: firstMessage }),
});
if (antwort.status !== 429 || versuch === MAX_VERSUCHE) return antwort;
const retryAfterHeader = Number(antwort.headers.get("Retry-After"));
const wartezeit = Number.isFinite(retryAfterHeader) && retryAfterHeader > 0 ? retryAfterHeader * 1000 : STANDARD_WARTEZEITEN_MS[versuch - 1] || 5000;
await warten(wartezeit);
}
}
export async function bewerbungAlsTicketAnlegen(type, data) {
const apiKey = process.env.TICKETANIZER_API_KEY;
const panelId = PANEL_IDS[type];
if (!apiKey || !panelId) return;
const visitorName = String(data?.name || data?.firma || "Unbekannt").trim().slice(0, 80) || "Unbekannt";
const firstMessage = ersteNachrichtAufbauen(type, data);
try {
const antwort = await ticketAnfrageSenden(apiKey, panelId, visitorName, firstMessage);
if (!antwort.ok) {
const antwortText = await antwort.text().catch(() => "");
console.error(`[ticketanizer] Fehler beim Ticket-Anlegen (${type}): HTTP ${antwort.status}${antwortText}`);
}
} catch (fehler) {
console.error(`[ticketanizer] Ausnahme beim Ticket-Anlegen (${type}):`, fehler && fehler.message);
}
}