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:
2026-05-25 17:16:21 -04:00
parent 659ab9b71a
commit eb8e57c674
19 changed files with 1429 additions and 0 deletions

View File

@@ -0,0 +1,38 @@
import { eq, and } from "drizzle-orm";
import { db } from "~/server/db";
import { watchlistItems, subscriptions } from "~/server/db/schema";
import { runScan } from "~/server/services/darkwatch.service";
interface DarkWatchScanPayload {
userId: string;
subscriptionId: string;
}
export async function handler(payload: DarkWatchScanPayload): Promise<void> {
const { userId, subscriptionId } = payload;
const sub = await db
.select()
.from(subscriptions)
.where(and(eq(subscriptions.id, subscriptionId), eq(subscriptions.status, "active")))
.limit(1)
.then((r) => r[0]);
if (!sub) {
console.warn(`[darkwatch.scan] Subscription ${subscriptionId} not found or inactive, skipping`);
return;
}
const items = await db
.select()
.from(watchlistItems)
.where(and(eq(watchlistItems.subscriptionId, subscriptionId), eq(watchlistItems.isActive, true)));
if (items.length === 0) {
console.log(`[darkwatch.scan] No active watchlist items for subscription ${subscriptionId}`);
return;
}
await runScan(userId);
console.log(`[darkwatch.scan] Completed scan for subscription ${subscriptionId}`);
}