import { accountDb } from "../scoped-db"; import { notifyQuietly, quiet } from "./deliver"; import { accountAdmins } from "./recipients"; const FULL_PERCENT = 100; export type QuotaThresholdInput = { accountId: string; /** e.g. "lfs_storage", "lfs_bandwidth", "ci_minutes", "budget" */ metric: string; label: string; /** 50, 80 or 100 */ percent: number; used: string; limit: string; url: string; /** When set (e.g. "2026-10"), each metric and threshold notifies at most once per period. */ period?: string; }; /** * Notifies the account owner (or an org's owners) that usage crossed a threshold. Callers dedupe per * period; this uses the "quota" category prefs, the account access check and the email budget. */ async function notifyQuotaThresholdUnsafe(input: QuotaThresholdInput): Promise { if (input.period) { const claimed = await accountDb(input.accountId).run( `INSERT OR IGNORE INTO quota_notices (account_id, metric, period, threshold, notified_at) VALUES (?, ?, ?, ?, ?)`, input.accountId, input.metric, input.period, input.percent, Date.now(), ); if (claimed.meta.changes === 0) return; } const recipients = await accountAdmins(input.accountId); const over = input.percent >= FULL_PERCENT; await notifyQuietly({ category: "quota", repoId: null, accountId: input.accountId, access: "account", recipients, title: over ? `${input.label} limit reached` : `${input.label} at ${input.percent}% of limit`, details: [`Usage: ${input.used} of ${input.limit}.`], url: input.url, }); } const THRESHOLDS = [100, 80] as const; const UNITS = ["B", "KB", "MB", "GB", "TB"]; const KIB = 1024; function formatBytes(bytes: number): string { let value = bytes; let unit = 0; while (value >= KIB && unit < UNITS.length - 1) { value /= KIB; unit += 1; } return `${value.toFixed(unit === 0 ? 0 : 1)} ${UNITS[unit]}`; } /** Notifies at most once per period when LFS usage crosses 80% or 100% of its limit. */ async function checkLfsThresholdUnsafe( accountId: string, metric: "lfs_storage" | "lfs_bandwidth", used: number, limit: number, period: string, ): Promise { if (limit <= 0) return; const percent = THRESHOLDS.find((threshold) => used * 100 >= limit * threshold); if (!percent) return; await notifyQuotaThreshold({ accountId, metric, label: metric === "lfs_storage" ? "Git LFS storage" : "Git LFS bandwidth", percent, used: formatBytes(used), limit: formatBytes(limit), url: "/settings", period, }); } export const notifyQuotaThreshold = quiet("notifyQuotaThreshold", notifyQuotaThresholdUnsafe); export const checkLfsThreshold = quiet("checkLfsThreshold", checkLfsThresholdUnsafe);