cm_whatsapp_bot_v1/apps/bot/src/ipc/unpair-handler.ts
yiekheng 9437df74ee feat(web): split Add Account from Pair; add Unpair/Re-pair/Delete actions
Reshape the account lifecycle to match how operators actually want to
work the system:

- Add Account → creates a row with status='unpaired'. No QR yet; the
  operator lands on the detail page.
- Pair / Re-pair → transitions an unpaired account to status='pending'
  and opens the live QR flow. Works for first-time pair AND for re-pair
  of an account that was previously unpaired.
- Unpair → asks the bot to stop the live Baileys session and clean
  session files; sets status='unpaired' but KEEPS the row (and its
  reminders) so the operator can re-pair without retyping anything.
- Delete → permanently removes the account and cascades to its groups,
  reminders, run history.

Schema:
- whatsapp_groups.account_id and reminders.account_id now have
  ON DELETE CASCADE so deleting an account fans out cleanly.

UI:
- /accounts list shows everything except the transient 'pending' state.
- /accounts/[id] shows state-aware buttons: Pair (when unpaired/banned/
  disconnected), Sync + Unpair (when connected), Delete (always).
- /accounts/new is now an "Add Account" form (label only).

Other fixes:
- next.config.ts: allowedDevOrigins includes 192.168.0.253 +
  test/rexwa subdomains so Server Actions work across the LAN.
- packages/shared/src/rrule.ts: rrule@2.8.1 has no exports field and
  ships ESM that some bundlers can't resolve via default OR named
  import. Use createRequire to bridge — works under both NodeNext
  (bot runtime) and Turbopack (web SSR).
2026-05-10 00:27:33 +08:00

33 lines
1.2 KiB
TypeScript

import { rm } from "node:fs/promises";
import { join } from "node:path";
import { db } from "../db.js";
import { env } from "../env.js";
import { sessionManager } from "../whatsapp/session-manager.js";
import { writeAuditLog } from "../audit.js";
import { pgNotifyWeb } from "./notify.js";
import { logger } from "../logger.js";
/**
* Unpair handler: stop the live Baileys session and remove session files.
* The web's unpair action deletes the account row before notifying us,
* so we expect the row to be gone by the time we run. Audit log uses a
* null operator since the row is no longer queryable.
*/
export async function handleUnpair(accountId: string): Promise<void> {
await sessionManager.stop(accountId);
await rm(join(env.SESSIONS_DIR, accountId), { recursive: true, force: true });
try {
await writeAuditLog(db, {
operatorId: null,
source: "web",
action: "account.unpaired",
targetType: "whatsapp_account",
targetId: accountId,
payload: {},
});
} catch (err) {
logger.warn({ err, accountId }, "unpair: audit log failed (non-fatal)");
}
await pgNotifyWeb({ type: "session.disconnected", accountId });
}