Files
Kordant/web/src/server/jobs/handlers/reports.generate.ts
Michael Freno eb8e57c674 feat: implement background job system with queue, worker, scheduler, and handlers
- Add job queue abstraction (InMemoryQueue and Redis/BullMQ adapter)
- Add polling worker with retry logic and exponential backoff
- Add 6 job handlers: darkwatch.scan, voiceprint.batch, hometitle.scan,
  removebrokers.process, reports.generate, notifications.send
- Add cron-based scheduler with tier-appropriate frequencies
  (Basic/Plus/Premium)
- Add tRPC scheduler router for admin (runJobNow, getJobStatus, etc.)
- Add entry point with graceful shutdown support
- Achieve 100% test pass rate for new job system
2026-05-25 17:16:21 -04:00

59 lines
1.7 KiB
TypeScript

import { eq, and } from "drizzle-orm";
import { db } from "~/server/db";
import { reportSchedules } from "~/server/db/schema";
import { generateReport } from "~/server/services/reports.service";
interface ReportsGeneratePayload {
userId: string;
reportScheduleId?: string;
reportType: string;
}
export async function handler(payload: ReportsGeneratePayload): Promise<void> {
const { userId, reportScheduleId, reportType } = payload;
if (reportScheduleId) {
const [schedule] = await db
.select()
.from(reportSchedules)
.where(and(eq(reportSchedules.id, reportScheduleId), eq(reportSchedules.enabled, true)))
.limit(1);
if (!schedule) {
console.warn(`[reports.generate] Schedule ${reportScheduleId} not found or disabled`);
return;
}
await generateReport(userId, schedule.reportType as any, schedule.lastGeneratedAt?.toISOString());
} else {
await generateReport(userId, reportType as any);
}
if (reportScheduleId) {
await db
.update(reportSchedules)
.set({
lastGeneratedAt: new Date(),
nextScheduledAt: calculateNextRun(reportType),
updatedAt: new Date(),
})
.where(eq(reportSchedules.id, reportScheduleId));
}
console.log(`[reports.generate] Generated report for user ${userId}`);
}
function calculateNextRun(reportType: string): Date {
const now = new Date();
switch (reportType) {
case "WEEKLY_DIGEST":
return new Date(now.getTime() + 7 * 24 * 60 * 60 * 1000);
case "MONTHLY_PLUS":
return new Date(now.getTime() + 30 * 24 * 60 * 60 * 1000);
case "ANNUAL_PREMIUM":
return new Date(now.getTime() + 365 * 24 * 60 * 60 * 1000);
default:
return new Date(now.getTime() + 30 * 24 * 60 * 60 * 1000);
}
}