Files
mailadmin/backend/src/routes/billing.ts
2026-04-30 09:50:18 -05:00

77 lines
2.5 KiB
TypeScript

import { Router } from 'express';
import { requireAuth, requireSuperAdmin } from '../middleware/auth.js';
import { computeMonthlyBilling, listBillingEvents, PRICE_PER_INBOX } from '../services/billing.js';
import {
getDomainVolumeForMonth,
getVolumeOverview,
currentYm,
previousYm,
} from '../services/ses-events.js';
export const billingRouter = Router();
billingRouter.use(requireAuth);
billingRouter.use(requireSuperAdmin);
billingRouter.get('/summary', async (req, res) => {
const domain = req.query.domain ? String(req.query.domain).toLowerCase() : undefined;
const months = await computeMonthlyBilling({ domain });
res.json({ price_per_inbox: PRICE_PER_INBOX, months });
});
billingRouter.get('/events', async (req, res) => {
const events = await listBillingEvents({
domain: req.query.domain ? String(req.query.domain).toLowerCase() : undefined,
fromIso: req.query.from ? String(req.query.from) : undefined,
toIso: req.query.to ? String(req.query.to) : undefined,
limit: req.query.limit ? Number(req.query.limit) : 500,
});
res.json(events);
});
/**
* GET /api/billing/volume?domain=foo.com&ym=2026-04
* Per-inbox drilldown for a single domain.
*/
billingRouter.get('/volume', async (req, res) => {
const domain = req.query.domain ? String(req.query.domain).toLowerCase() : '';
if (!domain) {
res.status(400).json({ error: 'domain query parameter is required' });
return;
}
const ym = req.query.ym ? String(req.query.ym) : currentYm();
if (!/^\d{4}-\d{2}$/.test(ym)) {
res.status(400).json({ error: 'ym must be in YYYY-MM format' });
return;
}
res.json(await getDomainVolumeForMonth(domain, ym));
});
/**
* GET /api/billing/volume-overview?ym=2026-04
* Cross-domain overview for super admin. One DynamoDB scan, aggregates
* per domain. ym defaults to the current month.
*
* Returns:
* {
* ym: 'YYYY-MM',
* total_send_count, total_bounce_count, total_complaint_count, total_inbox_count,
* rows: [{ domain, send_count, bytes_total, bounce_count, complaint_count, inbox_count }, ...]
* }
*/
billingRouter.get('/volume-overview', async (req, res) => {
// Limit to current/previous month per product decision — no point in
// accepting arbitrary ym values from the client right now.
const ym = req.query.ym ? String(req.query.ym) : currentYm();
const allowed = new Set([currentYm(), previousYm()]);
if (!allowed.has(ym)) {
res.status(400).json({
error: `ym must be one of: ${[...allowed].join(', ')}`,
});
return;
}
res.json(await getVolumeOverview(ym));
});