You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

80 lines
2.6 KiB
TypeScript

import type { Database } from 'bun:sqlite';
import { ensureUserExists } from '../../db';
import { toIsoSqlUTC } from '../../utils/datetime';
/**
* Reconciliación idempotente de membresías de un grupo.
*/
export function reconcileGroupMembers(
db: Database,
groupId: string,
snapshot: Array<{ userId: string; isAdmin: boolean }>,
nowIso?: string
): { added: number; updated: number; deactivated: number } {
if (!groupId || !Array.isArray(snapshot)) {
throw new Error('Invalid arguments for reconcileGroupMembers');
}
const now = nowIso || toIsoSqlUTC(new Date());
let added = 0, updated = 0, deactivated = 0;
const incoming = new Map<string, { isAdmin: boolean }>();
for (const m of snapshot) {
if (!m?.userId) continue;
incoming.set(m.userId, { isAdmin: !!m.isAdmin });
}
db.transaction(() => {
const existingRows = db.prepare(`
SELECT user_id, is_admin, is_active
FROM group_members
WHERE group_id = ?
`).all(groupId) as Array<{ user_id: string; is_admin: number; is_active: number }>;
const existing = new Map(existingRows.map(r => [r.user_id, { isAdmin: !!r.is_admin, isActive: !!r.is_active }]));
for (const [userId, { isAdmin }] of incoming.entries()) {
ensureUserExists(userId, db);
const row = existing.get(userId);
if (!row) {
db.prepare(`
INSERT INTO group_members (group_id, user_id, is_admin, is_active, first_seen_at, last_seen_at)
VALUES (?, ?, ?, 1, ?, ?)
`).run(groupId, userId, isAdmin ? 1 : 0, now, now);
added++;
} else {
const roleChanged = row.isAdmin !== isAdmin;
if (!row.isActive || roleChanged) {
db.prepare(`
UPDATE group_members
SET is_active = 1,
is_admin = ?,
last_seen_at = ?,
last_role_change_at = CASE WHEN ? THEN ? ELSE last_role_change_at END
WHERE group_id = ? AND user_id = ?
`).run(isAdmin ? 1 : 0, now, roleChanged ? 1 : 0, roleChanged ? now : null, groupId, userId);
updated++;
} else {
db.prepare(`
UPDATE group_members
SET last_seen_at = ?
WHERE group_id = ? AND user_id = ?
`).run(now, groupId, userId);
}
}
}
for (const [userId, state] of existing.entries()) {
if (!incoming.has(userId) && state.isActive) {
db.prepare(`
UPDATE group_members
SET is_active = 0,
last_seen_at = ?
WHERE group_id = ? AND user_id = ?
`).run(now, groupId, userId);
deactivated++;
}
}
})();
return { added, updated, deactivated };
}