58 lines
1.5 KiB
TypeScript
58 lines
1.5 KiB
TypeScript
import { prisma } from "./prisma";
|
|
|
|
type RateLimitResult = {
|
|
ok: boolean;
|
|
remaining: number;
|
|
resetAt: Date;
|
|
};
|
|
|
|
const parseNumber = (value: string | undefined, fallback: number) => {
|
|
if (!value) return fallback;
|
|
const parsed = Number(value);
|
|
return Number.isFinite(parsed) && parsed > 0 ? parsed : fallback;
|
|
};
|
|
|
|
export const getRateLimitConfig = (envKey: string, defaultLimit: number) => {
|
|
const limit = parseNumber(process.env[envKey], defaultLimit);
|
|
const windowMinutes = parseNumber(process.env.RATE_LIMIT_WINDOW_MINUTES, 15);
|
|
return { limit, windowMs: windowMinutes * 60 * 1000 };
|
|
};
|
|
|
|
export async function checkRateLimit({
|
|
key,
|
|
limit,
|
|
windowMs
|
|
}: {
|
|
key: string;
|
|
limit: number;
|
|
windowMs: number;
|
|
}): Promise<RateLimitResult> {
|
|
const now = new Date();
|
|
const resetAt = new Date(now.getTime() + windowMs);
|
|
|
|
const existing = await prisma.rateLimit.findUnique({ where: { key } });
|
|
if (!existing || existing.resetAt <= now) {
|
|
await prisma.rateLimit.upsert({
|
|
where: { key },
|
|
update: { count: 1, resetAt },
|
|
create: { key, count: 1, resetAt }
|
|
});
|
|
return { ok: true, remaining: Math.max(0, limit - 1), resetAt };
|
|
}
|
|
|
|
if (existing.count >= limit) {
|
|
return { ok: false, remaining: 0, resetAt: existing.resetAt };
|
|
}
|
|
|
|
const updated = await prisma.rateLimit.update({
|
|
where: { key },
|
|
data: { count: { increment: 1 } }
|
|
});
|
|
|
|
return {
|
|
ok: true,
|
|
remaining: Math.max(0, limit - updated.count),
|
|
resetAt: updated.resetAt
|
|
};
|
|
}
|