import { PrismaClient } from '@prisma/client'; import { createLogger } from '@tower/logger'; import { validateEnv } from '@tower/config'; import { IngestJobData } from '@tower/types'; import { createWhatsAppSession } from './whatsapp/session'; import { normalizeMessage } from './whatsapp/normalizer'; import { detectTags, isFlagged } from './whatsapp/tag-detector'; import { syncGroups } from './whatsapp/group-sync'; import { createIngestQueue } from './queues/ingest.queue'; import { createIngestWorker } from './queues/ingest.processor'; const logger = createLogger('tower-worker'); async function bootstrap() { const env = validateEnv(); const prisma = new PrismaClient(); await prisma.$connect(); const adminJids = env.TOWER_ADMIN_JIDS ? env.TOWER_ADMIN_JIDS.split(',').map((j) => j.trim()).filter(Boolean) : []; const ingestQueue = createIngestQueue(env.REDIS_URL); const ingestWorker = createIngestWorker(env.REDIS_URL, prisma); ingestWorker.on('completed', (job) => { logger.info({ jobId: job.id }, 'Ingest job completed'); }); ingestWorker.on('failed', (job, err) => { logger.error({ jobId: job?.id, err }, 'Ingest job failed'); }); let groupMap = new Map(); await createWhatsAppSession( env.WHATSAPP_SESSION_PATH, async (msg) => { const normalized = normalizeMessage(msg); if (!normalized) return; const tags = detectTags(normalized.content, normalized.senderJid, adminJids); if (!isFlagged(tags)) return; const sourceGroupId = groupMap.get(normalized.sourceGroupJid); if (!sourceGroupId) { logger.warn({ jid: normalized.sourceGroupJid }, 'Unknown group — skipping message'); return; } const jobData: IngestJobData = { platformMsgId: normalized.platformMsgId, platform: 'whatsapp', sourceGroupId, senderJid: normalized.senderJid, senderName: normalized.senderName, content: normalized.content, tags, }; await ingestQueue.add('ingest', jobData, { attempts: 3, backoff: { type: 'exponential', delay: 1000 }, }); logger.info({ platformMsgId: normalized.platformMsgId, tags }, 'Message enqueued'); }, async (groups) => { logger.info({ count: Object.keys(groups).length }, 'Syncing groups'); groupMap = await syncGroups(groups, prisma); logger.info({ count: groupMap.size }, 'Groups synced'); }, ); logger.info('Tower worker ready'); const shutdown = async () => { logger.info('Shutting down...'); await ingestWorker.close(); await ingestQueue.close(); await prisma.$disconnect(); process.exit(0); }; process.on('SIGTERM', shutdown); process.on('SIGINT', shutdown); } bootstrap().catch((err) => { console.error('Worker failed to start', err); process.exit(1); });