/** * Usage tracking and free tier enforcement. * * FREE TIER LIMITS (from PRD): * - users: 3 (hard block) * - employees: 10 (hard block) * - invoices: 25/month (soft warn at 80%) * - payroll_runs: 1/month (hard block) * - expenses: 50/month (soft warn at 80%) * - storage_mb: 500 (soft warn at 80%) * - ai_queries: 10/month (hard block) */ import { createAdminClient } from "@/lib/supabase/server"; export const FREE_TIER_LIMITS = { users: 3, employees: 10, invoices: 25, payroll_runs: 1, expenses: 50, storage_mb: 500, ai_queries: 10, } as const; export const HARD_BLOCK_FEATURES = ["users", "employees", "payroll_runs", "ai_queries"] as const; export const SOFT_WARN_FEATURES = ["invoices", "expenses", "storage_mb"] as const; export type FeatureKey = keyof typeof FREE_TIER_LIMITS; function getCurrentPeriod(): string { const now = new Date(); return `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, "0")}`; } /** * Check usage for a feature. Returns allowed status and warning state. */ export async function checkUsage( orgId: string, feature: FeatureKey ): Promise<{ allowed: boolean; current: number; limit: number; warn: boolean }> { const supabase = await createAdminClient(); const period = getCurrentPeriod(); const { data } = await supabase .from("usage_tracking") .select("count") .eq("organisation_id", orgId) .eq("feature", feature) .eq("period", period) .single(); const current = data?.count || 0; const limit = FREE_TIER_LIMITS[feature]; const isHard = HARD_BLOCK_FEATURES.includes(feature as any); return { allowed: isHard ? current < limit : true, current, limit, warn: current >= limit * 0.8, }; } /** * Increment usage for a feature. Call after successful inserts. */ export async function incrementUsage( orgId: string, feature: FeatureKey ): Promise { const supabase = await createAdminClient(); const period = getCurrentPeriod(); await supabase .from("usage_tracking") .upsert( { organisation_id: orgId, feature, period, count: 1 }, { onConflict: "organisation_id,feature,period" } ); } /** * Check and increment atomically. Returns false if blocked. */ export async function checkAndIncrement( orgId: string, feature: FeatureKey ): Promise<{ allowed: boolean; current: number; limit: number; warn: boolean }> { const check = await checkUsage(orgId, feature); if (!check.allowed) return check; await incrementUsage(orgId, feature); return { ...check, current: check.current + 1 }; }