35 lines · 1.4 KB
import { badRequest, forbidden } from "../errors";
import { billingAccountFor } from "../metering/access";
import { periodOf } from "../metering/record";
import { usageSummary } from "../metering/summary";
import { requireAccountScope } from "./http";
import type { ApiContext } from "./http";

const PERIOD = /^\d{4}-(0[1-9]|1[0-2])$/;

function periodParam(ctx: ApiContext): string {
  const period = ctx.url.searchParams.get("period") ?? periodOf();
  if (!PERIOD.test(period)) throw badRequest("period must be YYYY-MM");
  return period;
}

/** Billing data is account-level: repo-scoped tokens (fine-grained PATs, agent tokens) may not read it (INT-A15/AZ-A5). */
export async function getUserUsage(ctx: ApiContext): Promise<Response> {
  requireAccountScope(ctx);
  const account = await billingAccountFor(ctx.viewer, null);
  return Response.json(await usageSummary(account.accountId, periodParam(ctx)));
}

export async function getOrgUsage(ctx: ApiContext, org: string): Promise<Response> {
  requireAccountScope(ctx);
  let name: string;
  try {
    name = decodeURIComponent(org);
  } catch {
    throw badRequest("Invalid organization name");
  }
  const account = await billingAccountFor(ctx.viewer, name);
  if (!account.canView) throw forbidden("Only owners can view usage for this organization");
  return Response.json(await usageSummary(account.accountId, periodParam(ctx)));
}