import { db } from "@epicure/db"; import { tierDefinitions, userUsage } from "@epicure/db"; import { eq, and, sql } from "@epicure/db"; function currentMonth() { const now = new Date(); return `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, "0")}`; } export type LimitKey = "recipe" | "aiCall" | "storage"; export class TierLimitError extends Error { constructor(public readonly limit: LimitKey, public readonly tier: string) { super(`Tier limit reached: ${limit} (tier: ${tier})`); this.name = "TierLimitError"; } } export async function checkTierLimit( userId: string, userTier: "free" | "pro", key: LimitKey ): Promise { const [tierDef] = await db .select() .from(tierDefinitions) .where(eq(tierDefinitions.tier, userTier)); if (!tierDef) return; const month = currentMonth(); const [usage] = await db .select() .from(userUsage) .where(and(eq(userUsage.userId, userId), eq(userUsage.month, month))); const current = usage ?? { aiCallsUsed: 0, recipeCount: 0, storageUsedMb: 0, }; if (key === "recipe" && current.recipeCount >= tierDef.maxRecipes) { throw new TierLimitError("recipe", userTier); } if (key === "aiCall" && current.aiCallsUsed >= tierDef.aiCallsPerMonth) { throw new TierLimitError("aiCall", userTier); } } export async function incrementUsage( userId: string, key: LimitKey, amount = 1 ): Promise { const month = currentMonth(); const id = `${userId}-${month}`; const initialValues = { id, userId, month, aiCallsUsed: key === "aiCall" ? amount : 0, recipeCount: key === "recipe" ? amount : 0, storageUsedMb: key === "storage" ? amount : 0, }; const incrementSet = key === "aiCall" ? { aiCallsUsed: sql`${userUsage.aiCallsUsed} + ${amount}` } : key === "recipe" ? { recipeCount: sql`${userUsage.recipeCount} + ${amount}` } : { storageUsedMb: sql`${userUsage.storageUsedMb} + ${amount}` }; await db .insert(userUsage) .values(initialValues) .onConflictDoUpdate({ target: [userUsage.userId, userUsage.month], set: incrementSet, }); }