SES volume
This commit is contained in:
@@ -1,29 +1,23 @@
|
||||
import { Router } from 'express';
|
||||
import { requireAuth, requireSuperAdmin } from '../middleware/auth.js';
|
||||
import { computeMonthlyBilling, listBillingEvents, PRICE_PER_INBOX } from '../services/billing.js';
|
||||
import { getDomainVolumeForMonth, currentYm } from '../services/ses-events.js';
|
||||
import {
|
||||
getDomainVolumeForMonth,
|
||||
getVolumeOverview,
|
||||
currentYm,
|
||||
previousYm,
|
||||
} from '../services/ses-events.js';
|
||||
|
||||
export const billingRouter = Router();
|
||||
billingRouter.use(requireAuth);
|
||||
billingRouter.use(requireSuperAdmin);
|
||||
|
||||
/**
|
||||
* GET /api/billing/summary?domain=foo.com
|
||||
* Inbox-count based monthly summary ($5 per inbox per month).
|
||||
*/
|
||||
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,
|
||||
});
|
||||
res.json({ price_per_inbox: PRICE_PER_INBOX, months });
|
||||
});
|
||||
|
||||
/**
|
||||
* GET /api/billing/events?domain=foo.com
|
||||
* Raw mailbox lifecycle event log (created/deleted).
|
||||
*/
|
||||
billingRouter.get('/events', async (req, res) => {
|
||||
const events = await listBillingEvents({
|
||||
domain: req.query.domain ? String(req.query.domain).toLowerCase() : undefined,
|
||||
@@ -36,13 +30,7 @@ billingRouter.get('/events', async (req, res) => {
|
||||
|
||||
/**
|
||||
* GET /api/billing/volume?domain=foo.com&ym=2026-04
|
||||
* SES outbound volume for a domain in a specific month.
|
||||
*
|
||||
* Returns per-inbox + domain totals with send count, total bytes,
|
||||
* bounce count and complaint count.
|
||||
*
|
||||
* Defaults to the current month if ym is omitted.
|
||||
* Domain is required because volume is always per-domain.
|
||||
* Per-inbox drilldown for a single domain.
|
||||
*/
|
||||
billingRouter.get('/volume', async (req, res) => {
|
||||
const domain = req.query.domain ? String(req.query.domain).toLowerCase() : '';
|
||||
@@ -51,16 +39,38 @@ billingRouter.get('/volume', async (req, res) => {
|
||||
return;
|
||||
}
|
||||
|
||||
const ym = req.query.ym
|
||||
? String(req.query.ym)
|
||||
: currentYm();
|
||||
|
||||
// Validate ym format
|
||||
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;
|
||||
}
|
||||
|
||||
const volume = await getDomainVolumeForMonth(domain, ym);
|
||||
res.json(volume);
|
||||
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));
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user