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
This commit is contained in:
58
web/src/server/jobs/handlers/reports.generate.ts
Normal file
58
web/src/server/jobs/handlers/reports.generate.ts
Normal file
@@ -0,0 +1,58 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user