import { QuotaExtensionRequestStatus } from "@prisma/client"; import { MAX_ADMIN_API_RATE_BONUS } from "./api-rate-limit"; import { prisma } from "./db"; import { getPlanLimits } from "./plans"; export const QUOTA_EXTENSION_ANNUAL_LIMIT = 5; /** Max bonus videos an admin may grant per approval. */ export const MAX_ADMIN_BONUS_QUOTA = 50; export const EXTENSION_KIND = { VIDEO_QUOTA: "VIDEO_QUOTA", API_RATE_LIMIT: "API_RATE_LIMIT", } as const; export type ExtensionKind = (typeof EXTENSION_KIND)[keyof typeof EXTENSION_KIND]; function getCalendarYearBounds(year = new Date().getFullYear()) { return { start: new Date(year, 0, 1), end: new Date(year + 1, 0, 1), }; } export async function getQuotaExtensionUsage( userId: string, kind: ExtensionKind = EXTENSION_KIND.VIDEO_QUOTA, ) { const { start, end } = getCalendarYearBounds(); const requests = await prisma.quotaExtensionRequest.findMany({ where: { userId, kind, requestedAt: { gte: start, lt: end }, }, orderBy: { requestedAt: "desc" }, }); const used = requests.filter((r) => r.status !== QuotaExtensionRequestStatus.REJECTED).length; return { used, limit: QUOTA_EXTENSION_ANNUAL_LIMIT, remaining: Math.max(0, QUOTA_EXTENSION_ANNUAL_LIMIT - used), kind, requests: requests.map((r) => ({ id: r.id, kind: r.kind, status: r.status, message: r.message, requestedAt: r.requestedAt.toISOString(), processedAt: r.processedAt?.toISOString() ?? null, adminNote: r.adminNote, })), }; } export async function createQuotaExtensionRequest( userId: string, message = "", kind: ExtensionKind = EXTENSION_KIND.VIDEO_QUOTA, ) { const user = await prisma.user.findUniqueOrThrow({ where: { id: userId } }); if (user.plan !== "PREMIUM") { throw new Error("Only Pro subscribers can request quota resets or extensions."); } const usage = await getQuotaExtensionUsage(userId, kind); if (usage.remaining <= 0) { throw new Error( `You have used all ${QUOTA_EXTENSION_ANNUAL_LIMIT} ${ kind === EXTENSION_KIND.API_RATE_LIMIT ? "API rate limit" : "quota" } extension requests for this year.`, ); } const pending = await prisma.quotaExtensionRequest.findFirst({ where: { userId, kind, status: QuotaExtensionRequestStatus.PENDING }, }); if (pending) { throw new Error("You already have a pending request. Please wait for it to be processed."); } const request = await prisma.quotaExtensionRequest.create({ data: { userId, kind, message: message.trim().slice(0, 1000), status: QuotaExtensionRequestStatus.PENDING, }, }); const updatedUsage = await getQuotaExtensionUsage(userId, kind); return { requestId: request.id, ...updatedUsage, }; } /** Call when approving a request in the database (admin / support). */ export async function approveQuotaExtensionRequest( requestId: string, options?: { bonusQuota?: number; bonusRateLimit?: number; adminNote?: string }, ) { const request = await prisma.quotaExtensionRequest.findUniqueOrThrow({ where: { id: requestId }, include: { user: true }, }); if (request.status !== QuotaExtensionRequestStatus.PENDING) { throw new Error("Request is not pending."); } if (request.kind === EXTENSION_KIND.API_RATE_LIMIT) { const bonus = Math.min( MAX_ADMIN_API_RATE_BONUS, Math.max(0, Math.floor(options?.bonusRateLimit ?? 30)), ); await prisma.$transaction([ prisma.quotaExtensionRequest.update({ where: { id: requestId }, data: { status: QuotaExtensionRequestStatus.APPROVED, processedAt: new Date(), adminNote: options?.adminNote?.trim().slice(0, 500) ?? null, }, }), prisma.user.update({ where: { id: request.userId }, data: { apiRateLimitBonus: request.user.apiRateLimitBonus + bonus, }, }), ]); return; } const bonus = Math.min( MAX_ADMIN_BONUS_QUOTA, Math.max(0, Math.floor(options?.bonusQuota ?? 0)), ); await prisma.$transaction([ prisma.quotaExtensionRequest.update({ where: { id: requestId }, data: { status: QuotaExtensionRequestStatus.APPROVED, processedAt: new Date(), adminNote: options?.adminNote?.trim().slice(0, 500) ?? null, }, }), prisma.user.update({ where: { id: request.userId }, data: { videosUsed: 0, bonusQuota: request.user.bonusQuota + bonus, }, }), ]); } export async function rejectQuotaExtensionRequest(requestId: string, adminNote?: string) { const request = await prisma.quotaExtensionRequest.findUniqueOrThrow({ where: { id: requestId }, }); if (request.status !== QuotaExtensionRequestStatus.PENDING) { throw new Error("Request is not pending."); } await prisma.quotaExtensionRequest.update({ where: { id: requestId }, data: { status: QuotaExtensionRequestStatus.REJECTED, processedAt: new Date(), adminNote: adminNote?.trim().slice(0, 500) ?? null, }, }); } export function getEffectiveQuotaLimit(plan: Parameters[0], bonusQuota: number) { return getPlanLimits(plan).monthlyQuota + bonusQuota; }