- session.ts: add onReconnect callback so reconnected socket replaces stale pool entry
- session-pool.ts: pass onReconnect to update pool on reconnect; add closeAll() to gracefully end WebSocket connections on shutdown
- approval.ts: use approved flag so double-approval race (updateMany count=0) returns null instead of sending duplicate forward jobs
- main.ts: wrap pool.add() in try/catch to continue if one account fails; replace silent groupMap fallback with explicit error log and early return; call pool.closeAll() before BullMQ shutdown to prevent hanging sockets
- Tests: add closeAll() test to session-pool.test.ts; add double-approval race test to approval.test.ts (56 tests total, all passing)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Wrap message.update + approval.create in a $transaction using updateMany
with a PENDING status guard to prevent duplicate approvals and audit gaps.
Filter out null targetGroup routes to prevent runtime errors on DB inconsistency.
Add TODO comment for multi-platform support and fallback accountId comment.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Implement approval core logic for Task 5: when admin reacts to WhatsApp
message with ⭐, verify admin status, update message to APPROVED, create
Approval record, find active SyncRoutes from source group, and return
ForwardJobData[] for each target group. All 7 tests pass.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds Account model (platform, jid, sessionPath, displayName, status) with
AccountStatus enum (ACTIVE/DISCONNECTED/BANNED) and optional accountId FK
on Group for multi-account WhatsApp session tracking.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Wrap groupFetchAllParticipating in try/catch to prevent unhandled rejection on connect
- Catch errors from async onMessage/onGroups callbacks via Promise.resolve().catch
- Return null from normalizer when key.id is missing (prevents empty upsert key collision)
- Extract parseRedisUrl to redis-connection.ts to eliminate duplication
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Idempotent message persistence via prisma.message.upsert. Uses URL parsing
to pass connection options to BullMQ, avoiding ioredis version conflicts.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds packageManager field to root package.json (required by turbo v2.9),
loads .env via dotenv in API jest config so Prisma integration tests find
DATABASE_URL when run from the monorepo root. pnpm build + pnpm test: 8/8 passing.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds apps/worker with TypeScript config, BullMQ/ioredis dependencies,
pino logger bootstrap, and a smoke test. Queue consumers wired in later phases.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>