Files
HiveGrid-Wireframes/hivegrid-cloud/apps/web/app/utils/services/rateLimitService.ts
T

79 lines
2.8 KiB
TypeScript

import { getPlanFeatureUsage } from "./.server/subscriptionService";
import { DefaultFeatures } from "~/application/dtos/shared/DefaultFeatures";
import { SubscriptionFeatureLimitType } from "~/application/enums/subscriptions/SubscriptionFeatureLimitType";
import { cache } from "../cache.server";
import { TenantSubscriptionWithDetails } from "../db/tenantSubscriptions.db.server";
const DEFAULT_RATE_LIMIT_PER_MINUTE = 60;
const DEFAULT_RATE_LIMIT_PER_SECOND = 5;
function getCacheKey(apiKey: string, period: string, timestamp: number): string {
return `rateLimit:${apiKey}:${period}:${timestamp}`;
}
export async function rateLimitByKey({
apiKey,
tenantSubscription,
tenantId,
limitByMinute,
limitBySecond,
}: {
apiKey: string;
tenantSubscription: TenantSubscriptionWithDetails | null;
tenantId: string;
limitByMinute?: number;
limitBySecond?: number;
}): Promise<{
success: boolean;
error: string | null;
}> {
const currentTimestamp = Math.floor(Date.now() / 1000);
const minuteTimestamp = Math.floor(currentTimestamp / 60);
const secondTimestamp = currentTimestamp;
const minuteKey = getCacheKey(apiKey, "minute", minuteTimestamp);
const secondKey = getCacheKey(apiKey, "second", secondTimestamp);
let minuteCount = (cache.get(minuteKey)?.value as number) || 0;
let secondCount = (cache.get(secondKey)?.value as number) || 0;
if (limitByMinute === undefined) {
const feature = await getPlanFeatureUsage(tenantId, DefaultFeatures.RateLimitPerMinute, tenantSubscription);
if (!feature) {
limitByMinute = DEFAULT_RATE_LIMIT_PER_MINUTE;
} else if (feature.type === SubscriptionFeatureLimitType.UNLIMITED) {
limitByMinute = 0;
} else {
limitByMinute = feature.value;
}
}
if (limitBySecond === undefined) {
const feature = await getPlanFeatureUsage(tenantId, DefaultFeatures.RateLimitPerSecond, tenantSubscription);
if (!feature) {
limitBySecond = DEFAULT_RATE_LIMIT_PER_SECOND;
} else if (feature.type === SubscriptionFeatureLimitType.UNLIMITED) {
limitBySecond = 0;
} else {
limitBySecond = feature.value;
}
}
minuteCount += 1;
secondCount += 1;
if (limitByMinute !== 0 && minuteCount > limitByMinute) {
let tryAgainIn = 60 - (currentTimestamp % 60);
return { success: false, error: `You've exceeded the ${limitByMinute} request/min rate limit, try again in ${tryAgainIn} seconds` };
}
if (limitBySecond !== 0 && secondCount > limitBySecond) {
return { success: false, error: `You've exceeded the ${limitBySecond} request/sec rate limit` };
}
cache.set(minuteKey, { value: minuteCount, metadata: { createdTime: Date.now() } }, { ttl: 60 * 1000 });
cache.set(secondKey, { value: secondCount, metadata: { createdTime: Date.now() } }, { ttl: 1000 });
return { success: true, error: null };
}