import { first, run } from "../db"; import { accountDb } from "../scoped-db"; const DAY_MS = 86_400_000; const ISO_DATE_LENGTH = 10; /** * Mirrors the usage-metering `UsageInput` for meter `ops.integration_deliveries` (one unit per outbound * delivery, keyed `integration:` so retries never double-count). Once roadmap/metering lands the * body becomes `recordUsageSoon(null, { accountId: ownerId, repoId, meter: "ops.integration_deliveries", * quantity: 1, attributes: { kind, ok, bytes }, idempotencyKey })`. Until then it sums into * `integration_usage_daily`, which billing does not read. */ export type OutboundUsage = { ownerId: string; repoId: string | null; kind: string; ok: boolean; bytes: number; deliveryId: string; }; export async function recordOutboundDelivery(usage: OutboundUsage, now = Date.now()): Promise { const day = new Date(Math.floor(now / DAY_MS) * DAY_MS).toISOString().slice(0, ISO_DATE_LENGTH); try { const claimed = await run( "INSERT OR IGNORE INTO integration_usage_keys (key, created_at) VALUES (?, ?)", `integration:${usage.deliveryId}`, now, ); if (!claimed.meta.changes) return; await accountDb(usage.ownerId).run( `INSERT INTO integration_usage_daily (owner_id, day, kind, deliveries, failures, bytes) VALUES (?, ?, ?, 1, ?, ?) ON CONFLICT(owner_id, day, kind) DO UPDATE SET deliveries = deliveries + 1, failures = failures + excluded.failures, bytes = bytes + excluded.bytes`, usage.ownerId, day, usage.kind, usage.ok ? 0 : 1, Math.max(0, Math.floor(usage.bytes)), ); } catch (error) { console.error("Failed to record outbound usage", error); } } const KEY_RETENTION_MS = 40 * DAY_MS; export async function pruneUsageKeys(now = Date.now()): Promise { await run("DELETE FROM integration_usage_keys WHERE created_at < ?", now - KEY_RETENTION_MS); } /** The account billed for an integration: the org itself, or the owner of the repo. */ export async function integrationOwner(scopeType: "repo" | "org", scopeId: string): Promise { if (scopeType === "org") return scopeId; const repo = await first<{ owner_id: string }>("SELECT owner_id FROM repos WHERE id = ?", scopeId); return repo?.owner_id ?? null; }