import { and, eq, notInArray, sql } from "drizzle-orm"; import type { WASocket } from "@whiskeysockets/baileys"; import { whatsappGroups } from "@cmbot/db"; import { db } from "../db.js"; import { logger } from "../logger.js"; export async function syncGroupsForAccount( accountId: string, socket: WASocket, ): Promise<{ synced: number; archived: number }> { const meta = await socket.groupFetchAllParticipating(); const entries = Object.values(meta); const liveJids = entries.map((g) => g.id); // Mark DB rows as archived when they're no longer in the live // participant list (group deleted, bot removed, etc). We don't // physically DELETE because reminder_targets.group_id is a NOT // NULL FK to this row — a hard delete throws "violates foreign // key constraint reminder_targets_group_id_whatsapp_groups_id_fk" // and aborts the WHOLE group-sync transaction (which then strands // the post-pair open event and the operator sees it as a failed // pairing). Soft-archive keeps reminders that targeted the group // intact and gives the operator the option to clean them up // explicitly later. Only run the sweep when we got at least one // live group back — an empty result is usually a transient WA // fetch failure and we don't want to mass-archive valid data. let archived = 0; if (liveJids.length > 0) { const rows = await db .update(whatsappGroups) .set({ isArchived: true, lastSyncedAt: new Date() }) .where( and( eq(whatsappGroups.accountId, accountId), notInArray(whatsappGroups.waGroupJid, liveJids), eq(whatsappGroups.isArchived, false), ), ) .returning({ id: whatsappGroups.id }); archived = rows.length; } if (entries.length === 0) { logger.info( { accountId }, "group-sync: empty fetch — skipping archive sweep (treating as transient)", ); return { synced: 0, archived: 0 }; } const rows = entries.map((g) => ({ accountId, waGroupJid: g.id, name: g.subject ?? "(no subject)", participantCount: g.participants?.length ?? 0, isArchived: false, lastSyncedAt: new Date(), })); await db .insert(whatsappGroups) .values(rows) .onConflictDoUpdate({ target: [whatsappGroups.accountId, whatsappGroups.waGroupJid], set: { name: sql`excluded.name`, participantCount: sql`excluded.participant_count`, lastSyncedAt: sql`excluded.last_synced_at`, // If a previously-archived group reappears in the live list // (operator was re-added, group was un-deleted, etc.), flip // the flag back so it shows up in the picker again. isArchived: sql`excluded.is_archived`, }, }); logger.info( { accountId, count: rows.length, archived }, "group-sync: synced", ); return { synced: rows.length, archived }; }