L15 Testphase & Onboarding: Selbstanmeldung mit Double-Opt-in, Plattform-Wizard, Nur-Lesen-Sperre, Export, Lebenszyklus-Job
- 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>
This commit is contained in:
@@ -2,6 +2,7 @@ import "dotenv/config";
|
||||
import { Worker } from "bullmq";
|
||||
import { JOB_QUEUES, workerConnection, closeJobQueues, scheduleRecurringJobs, type JobPayload } from "../src/server/jobs/queues";
|
||||
import { PROCESSORS } from "../src/server/jobs/processors";
|
||||
import { isJobBlockedByTrial } from "../src/server/services/trial/jobs";
|
||||
|
||||
/** Craftvia background worker: `npm run worker:craftvia`. One BullMQ worker per registered queue. */
|
||||
async function main() {
|
||||
@@ -20,7 +21,14 @@ async function main() {
|
||||
const processor = await load();
|
||||
const geocode = name === JOB_QUEUES.geocodeSite; // L13: OSM Nominatim policy — max. 1 request/s
|
||||
const concurrency = name === JOB_QUEUES.reportPdf ? 2 : geocode ? 1 : 4;
|
||||
const w = new Worker<JobPayload>(name, async (job) => processor(job.data), { connection, concurrency, ...(geocode ? { limiter: { max: 1, duration: 1_000 } } : {}) });
|
||||
const w = new Worker<JobPayload>(name, async (job) => {
|
||||
// L15 Testphase: user-triggered jobs of an expired trial tenant (read-only) are skipped
|
||||
if (await isJobBlockedByTrial(name, job.data)) {
|
||||
console.warn(`[worker] ${name} job ${job.id} skipped: tenant is read-only (trial expired)`);
|
||||
return;
|
||||
}
|
||||
return processor(job.data);
|
||||
}, { connection, concurrency, ...(geocode ? { limiter: { max: 1, duration: 1_000 } } : {}) });
|
||||
w.on("failed", (job, err) => console.error(`[worker] ${name} job ${job?.id} failed:`, err.message));
|
||||
workers.push(w);
|
||||
console.info(`[worker] listening on ${name}`);
|
||||
|
||||
Reference in New Issue
Block a user