- Datenmodell: Testphasen-Lebenszyklus am Mandanten (plan, trialEndsAt, readOnlySince, deletionDueAt, Versandmarker), TrialSignup (Plattform, Hashes statt Klartext), TenantExport (RLS), Onboarding-Status - /testen: 5-Schritte-Wizard (Betrieb, Admin-Konto, Enddatum, Einrichtung, Zusammenfassung), Bestätigung per POST, direkte Anmeldung über login-ticket; Rate-Limit je IP/E-Mail, Honeypot, Enumeration-Schutz, Slug-Kollisionen - Plattform: Wizard „Testmandant anlegen“ mit Einladung, Badges/Filter, Enddatum ändern, umwandeln, beenden, Löschung vormerken/abbrechen (Bestätigung + Audit) - Schreibsperre nach Ablauf zentral in moduleGuard und requireApiContext (non-GET über withApi), Upload-Routen, Einstellungen/Nutzerverwaltung, Worker-Jobs; Banner Backoffice + mobil - Datenexport (ZIP mit CSV/JSON + Dateien) als Worker-Job, auch im Nur-Lesen-Zustand - Täglicher Job trial-lifecycle: Erinnerungen 7/3/1, Ablauf, Löschhinweis, Löschung über das Offboarding - Erste-Schritte-Checkliste im Dashboard, Mail-Vorlagen de/en, Tests + Smoke, Betriebsdoku Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
30 lines
1.9 KiB
TypeScript
30 lines
1.9 KiB
TypeScript
import type { JobPayload, JobQueueName } from "../queues";
|
|
|
|
export type JobProcessor = (payload: JobPayload) => Promise<void>;
|
|
|
|
/**
|
|
* Processor registry. Each lane adds exactly ONE line for its queue, e.g.
|
|
* [JOB_QUEUES.importExtraction]: () => import("./import-extraction").then((m) => m.process),
|
|
* Lazy imports keep the app bundle free of worker-only dependencies (Playwright etc.).
|
|
*/
|
|
export const PROCESSORS: Partial<Record<JobQueueName, () => Promise<JobProcessor>>> = {
|
|
"import-extraction": () => import("./import-extraction").then((m) => m.process),
|
|
transcription: () => import("./transcription").then((m) => m.process),
|
|
"report-pdf": () => import(/* turbopackIgnore: true */ "./report-pdf").then((m) => m.process), // worker-only (react-dom/server + Chromium), kept out of the app bundle
|
|
"image-derivatives": () => import("./image-derivatives").then((m) => m.process),
|
|
"ai-retention": () => import("./ai-retention").then((m) => m.process), // L10b: daily AI log retention (scheduled by the worker)
|
|
"geocode-site": () => import("./geocode-site").then((m) => m.process), // L13: geocoding of sites (OSM Nominatim)
|
|
"planning-watch": () => import("./planning-watch").then((m) => m.process), // L13: delay/overrun/freed-capacity alerts (every 5 min)
|
|
"billing-pdf": () => import(/* turbopackIgnore: true */ "./billing-pdf").then((m) => m.process), // L14: worker-only billing sheet PDF (react-dom/server + Chromium)
|
|
"trial-lifecycle": () => import("./trial-lifecycle").then((m) => m.process), // L15: daily trial reminders/expiry/deletion
|
|
"tenant-export": () => import("./tenant-export").then((m) => m.process), // L15: tenant data export ZIP
|
|
};
|
|
|
|
/** Inline fallback when no Redis is available (dev/demo). */
|
|
export async function runInline(name: JobQueueName, payload: JobPayload): Promise<void> {
|
|
const load = PROCESSORS[name];
|
|
if (!load) throw new Error(`no processor registered for ${name}`);
|
|
const process = await load();
|
|
await process(payload);
|
|
}
|