This commit is contained in:
2026-04-26 16:05:04 -05:00
parent 00932aeaff
commit fdcec00bc9
6 changed files with 487 additions and 148 deletions

View File

@@ -20,12 +20,70 @@ function ensureDomain(req: any, domain: string): void {
if (!canAccessDomain(req.user, domain)) throw Object.assign(new Error('Forbidden'), { status: 403 });
}
async function refreshQuotaForMailbox(emailAddress: string): Promise<void> {
const quota = await dms.getMailboxQuota(emailAddress);
await pool.query(
`UPDATE mailboxes
SET used_bytes=$2,
quota_bytes=$3,
quota_percent=$4,
message_count=$5,
message_limit=$6,
usage_scanned_at=now(),
updated_at=now()
WHERE email_address=$1`,
[
emailAddress,
quota.usedBytes,
quota.quotaBytes,
quota.quotaPercent,
quota.messageCount,
quota.messageLimit,
],
);
}
async function refreshQuotaForDomain(req: any, domain: string): Promise<number> {
ensureDomain(req, domain);
const rows = (await pool.query(
`SELECT email_address, domain
FROM mailboxes
WHERE node_name=$1 AND domain=$2 AND status='active'
ORDER BY email_address`,
[config.nodeName, domain],
)).rows;
let count = 0;
for (const row of rows) {
if (!canAccessDomain(req.user!, row.domain)) continue;
try {
await refreshQuotaForMailbox(row.email_address);
count++;
} catch (err) {
console.warn(`Could not refresh quota for ${row.email_address}:`, err);
}
}
return count;
}
mailboxesRouter.get('/', async (req, res) => {
const domain = String(req.query.domain ?? '').toLowerCase();
if (domain) ensureDomain(req, domain);
const refreshQuota = String(req.query.refreshQuota ?? '').toLowerCase() === 'true';
if (domain) {
ensureDomain(req, domain);
if (refreshQuota) {
const count = await refreshQuotaForDomain(req, domain);
await audit(req.user!.email, 'mailbox.quota_refresh', 'domain', domain, { count }, req.ip);
}
}
const params: unknown[] = [config.nodeName];
let where = 'WHERE node_name=$1';
if (domain) { params.push(domain); where += ` AND domain=$${params.length}`; }
const result = await pool.query(
`SELECT * FROM mailboxes ${where} ORDER BY domain, local_part`,
params,
@@ -40,6 +98,7 @@ mailboxesRouter.post('/', async (req, res) => {
ensureDomain(req, domain);
await dms.addMailbox(email, body.password);
await sync.syncFromDms();
await refreshQuotaForMailbox(email).catch((err) => console.warn(`Could not refresh quota for ${email}:`, err));
await audit(req.user!.email, 'mailbox.create', 'mailbox', email, { domain }, req.ip);
res.status(201).json({ email, domain, local_part: localPartFromEmail(email) });
});
@@ -64,24 +123,6 @@ mailboxesRouter.post('/:email/password', async (req, res) => {
res.json({ ok: true });
});
mailboxesRouter.post('/usage/rescan', async (req, res) => {
const domain = String(req.body?.domain ?? '').toLowerCase();
if (domain) ensureDomain(req, domain);
const params: unknown[] = [config.nodeName];
let where = `WHERE node_name=$1 AND status='active'`;
if (domain) { params.push(domain); where += ` AND domain=$${params.length}`; }
const rows = (await pool.query(`SELECT email_address, domain FROM mailboxes ${where}`, params)).rows;
let count = 0;
for (const row of rows) {
if (!canAccessDomain(req.user!, row.domain)) continue;
const bytes = await dms.getMailboxUsageBytes(row.email_address);
await pool.query(`UPDATE mailboxes SET used_bytes=$2, usage_scanned_at=now(), updated_at=now() WHERE email_address=$1`, [row.email_address, bytes]);
count++;
}
await audit(req.user!.email, 'mailbox.usage_rescan', 'domain', domain || '*', { count }, req.ip);
res.json({ count });
});
mailboxesRouter.get('/:email/rules', async (req, res) => {
const email = normalizeEmail(req.params.email);
ensureDomain(req, domainFromEmail(email));
@@ -110,4 +151,4 @@ mailboxesRouter.put('/:email/blocklist', async (req, res) => {
const saved = await dynamo.putBlocklist(email, body.blocked_patterns);
await audit(req.user!.email, 'mailbox.blocklist_update', 'mailbox', email, saved, req.ip);
res.json(saved);
});
});