55 lines
1.7 KiB
TypeScript
55 lines
1.7 KiB
TypeScript
|
|
import "server-only";
|
||
|
|
import { prisma } from "@/lib/db";
|
||
|
|
import { periodKey } from "@/lib/utils";
|
||
|
|
import type { UsageMetric } from "@/lib/billing/plans";
|
||
|
|
|
||
|
|
const METRICS: UsageMetric[] = ["script", "audio", "art", "repurpose"];
|
||
|
|
|
||
|
|
export interface UsageHistoryPoint {
|
||
|
|
/** Monthly bucket key, e.g. "2026-06". */
|
||
|
|
period: string;
|
||
|
|
script: number;
|
||
|
|
audio: number;
|
||
|
|
art: number;
|
||
|
|
repurpose: number;
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Read-only usage history for a billing subject: the last `months` monthly
|
||
|
|
* buckets (oldest → newest), with per-metric counts. Missing buckets are zero
|
||
|
|
* so the series is always dense (handy for a usage chart). `ownerId` is a
|
||
|
|
* user.id or organization.id (the billing subject).
|
||
|
|
*/
|
||
|
|
export async function getUsageHistory(
|
||
|
|
ownerId: string,
|
||
|
|
months = 6,
|
||
|
|
now = new Date()
|
||
|
|
): Promise<UsageHistoryPoint[]> {
|
||
|
|
const n = Math.max(1, Math.min(36, months));
|
||
|
|
|
||
|
|
// Build the ordered list of period keys we care about (oldest first).
|
||
|
|
const periods: string[] = [];
|
||
|
|
for (let i = n - 1; i >= 0; i--) {
|
||
|
|
const d = new Date(Date.UTC(now.getUTCFullYear(), now.getUTCMonth() - i, 1));
|
||
|
|
periods.push(periodKey(d));
|
||
|
|
}
|
||
|
|
|
||
|
|
const rows = await prisma.usageRecord.findMany({
|
||
|
|
where: { ownerId, periodKey: { in: periods } },
|
||
|
|
select: { periodKey: true, metric: true, count: true },
|
||
|
|
});
|
||
|
|
|
||
|
|
const byPeriod = new Map<string, UsageHistoryPoint>();
|
||
|
|
for (const period of periods) {
|
||
|
|
byPeriod.set(period, { period, script: 0, audio: 0, art: 0, repurpose: 0 });
|
||
|
|
}
|
||
|
|
for (const row of rows) {
|
||
|
|
const point = byPeriod.get(row.periodKey);
|
||
|
|
if (point && (METRICS as string[]).includes(row.metric)) {
|
||
|
|
point[row.metric as UsageMetric] = row.count;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
return periods.map((p) => byPeriod.get(p)!);
|
||
|
|
}
|