delete if necc.
This commit is contained in:
@@ -5,12 +5,18 @@ import { DmsService } from './dms.js';
|
||||
export class SyncService {
|
||||
constructor(private dms = new DmsService()) {}
|
||||
|
||||
async syncFromDms(): Promise<{ domains: number; mailboxes: number }> {
|
||||
async syncFromDms(): Promise<{
|
||||
domains: number;
|
||||
mailboxes: number;
|
||||
removed_mailboxes: number;
|
||||
removed_domains: number;
|
||||
}> {
|
||||
const accounts = await this.dms.listAccounts();
|
||||
const domains = [...new Set(accounts.map((a) => a.domain))].sort();
|
||||
|
||||
await pool.query('BEGIN');
|
||||
try {
|
||||
// 1. Upsert every domain that currently exists in DMS as 'active'.
|
||||
for (const domain of domains) {
|
||||
await pool.query(
|
||||
`INSERT INTO domains(domain, current_node, status, last_seen_at, last_synced_at)
|
||||
@@ -25,6 +31,7 @@ export class SyncService {
|
||||
);
|
||||
}
|
||||
|
||||
// 2. Upsert every mailbox that currently exists in DMS as 'active'.
|
||||
const localEmails = accounts.map((a) => a.email);
|
||||
for (const account of accounts) {
|
||||
await pool.query(
|
||||
@@ -41,25 +48,72 @@ export class SyncService {
|
||||
);
|
||||
}
|
||||
|
||||
await pool.query(
|
||||
`UPDATE domains
|
||||
SET status='missing_on_node', last_synced_at=now(), updated_at=now()
|
||||
WHERE current_node=$1 AND NOT (domain = ANY($2::text[]))`,
|
||||
[config.nodeName, domains],
|
||||
// 3. Find mailboxes that this node previously owned but that are
|
||||
// no longer in DMS (or never were). They get hard-deleted.
|
||||
// We collect (email, domain) first so we can emit billing events
|
||||
// for any of them that don't already have a 'deleted' event.
|
||||
const toRemoveResult = await pool.query<{ email_address: string; domain: string; previous_status: string }>(
|
||||
`SELECT email_address, domain, status AS previous_status
|
||||
FROM mailboxes
|
||||
WHERE node_name=$1 AND NOT (email_address = ANY($2::text[]))`,
|
||||
[config.nodeName, localEmails],
|
||||
);
|
||||
const toRemove = toRemoveResult.rows;
|
||||
|
||||
await pool.query(
|
||||
`UPDATE mailboxes
|
||||
SET status='missing_on_node', updated_at=now()
|
||||
WHERE node_name=$1 AND status='active' AND NOT (email_address = ANY($2::text[]))`,
|
||||
// 3a. Emit a synthetic 'deleted' billing event for any mailbox
|
||||
// that was previously active and doesn't already have one.
|
||||
// This keeps billing history correct even though we hard-delete
|
||||
// the mailbox row itself in the next step.
|
||||
for (const row of toRemove) {
|
||||
if (row.previous_status === 'active' || row.previous_status === 'missing_on_node') {
|
||||
await pool.query(
|
||||
`INSERT INTO mailbox_billing_events (domain, email, action, actor_email, notes)
|
||||
SELECT $1, $2, 'deleted', NULL, 'removed via DMS resync (was ' || $3 || ')'
|
||||
WHERE NOT EXISTS (
|
||||
SELECT 1 FROM mailbox_billing_events b
|
||||
WHERE b.email = $2
|
||||
AND b.action = 'deleted'
|
||||
AND b.occurred_at >= (
|
||||
SELECT COALESCE(MAX(occurred_at), '1970-01-01'::timestamptz)
|
||||
FROM mailbox_billing_events
|
||||
WHERE email = $2 AND action = 'created'
|
||||
)
|
||||
)`,
|
||||
[row.domain, row.email_address, row.previous_status],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// 3b. Hard-delete the mailbox rows themselves.
|
||||
const removedMailboxes = await pool.query(
|
||||
`DELETE FROM mailboxes
|
||||
WHERE node_name=$1 AND NOT (email_address = ANY($2::text[]))`,
|
||||
[config.nodeName, localEmails],
|
||||
);
|
||||
|
||||
// 4. Hard-delete domains that no longer have any mailbox on this node.
|
||||
// We restrict to current_node so we don't touch domains owned by
|
||||
// other nodes if that ever becomes a thing.
|
||||
const removedDomains = await pool.query(
|
||||
`DELETE FROM domains
|
||||
WHERE current_node=$1
|
||||
AND NOT EXISTS (
|
||||
SELECT 1 FROM mailboxes m
|
||||
WHERE m.domain = domains.domain AND m.node_name = $1
|
||||
)`,
|
||||
[config.nodeName],
|
||||
);
|
||||
|
||||
await pool.query('COMMIT');
|
||||
return { domains: domains.length, mailboxes: accounts.length };
|
||||
return {
|
||||
domains: domains.length,
|
||||
mailboxes: accounts.length,
|
||||
removed_mailboxes: removedMailboxes.rowCount ?? 0,
|
||||
removed_domains: removedDomains.rowCount ?? 0,
|
||||
};
|
||||
} catch (err) {
|
||||
await pool.query('ROLLBACK');
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user