import { sql } from "drizzle-orm"; import { db } from "@/db/client"; import { rateLimits } from "@/db/schema"; /** Fenêtre glissante approximée par tranches : compte les tranches actives, incrémente la courante. Jamais de blocage irréversible. */ export async function hit(key: string, limit: number, windowSec: number, slices = 6): Promise<{ ok: boolean; remaining: number; retryAfterSec: number }> { const sliceMs = Math.max(1000, Math.floor((windowSec * 1000) / slices)); const now = Date.now(); const cur = new Date(Math.floor(now / sliceMs) * sliceMs); const since = new Date(now - windowSec * 1000); const d = db(); await d.insert(rateLimits).values({ key, windowStart: cur, count: 1 }).onConflictDoUpdate({ target: [rateLimits.key, rateLimits.windowStart], set: { count: sql`${rateLimits.count} + 1` } }); const [row] = await d.select({ total: sql`coalesce(sum(${rateLimits.count}), 0)::int` }).from(rateLimits).where(sql`${rateLimits.key} = ${key} and ${rateLimits.windowStart} >= ${since.toISOString()}::timestamptz`); const total = Number(row?.total ?? 0); if (Math.random() < 0.02) await d.execute(sql`delete from ${rateLimits} where ${rateLimits.windowStart} < now() - interval '2 days'`); return { ok: total <= limit, remaining: Math.max(0, limit - total), retryAfterSec: total <= limit ? 0 : Math.ceil(sliceMs / 1000) }; }