feat: remove application-side API rate limits
This commit is contained in:
+5
-59
@@ -1,12 +1,10 @@
|
||||
import "fake-indexeddb/auto";
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { openDB } from "../storage/db";
|
||||
import type { GeminiConfig } from "./config";
|
||||
import {
|
||||
checkRateLimit,
|
||||
generateImage,
|
||||
generateText,
|
||||
incrementApiCall,
|
||||
} from "./gemini";
|
||||
|
||||
const TEST_CONFIG: GeminiConfig = {
|
||||
@@ -210,62 +208,10 @@ describe("generateImage", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("Rate limiting", () => {
|
||||
let db: IDBDatabase;
|
||||
|
||||
beforeEach(async () => {
|
||||
vi.restoreAllMocks();
|
||||
testCounter++;
|
||||
db = await openDB(`zauberwald-rate-test-${testCounter}`);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
vi.restoreAllMocks();
|
||||
});
|
||||
|
||||
it("checkRateLimit returns true when under limit (0 calls today)", async () => {
|
||||
const canCallText = await checkRateLimit(db, "text");
|
||||
const canCallImage = await checkRateLimit(db, "image");
|
||||
expect(canCallText).toBe(true);
|
||||
expect(canCallImage).toBe(true);
|
||||
});
|
||||
|
||||
it("checkRateLimit returns false when at limit (10 text, 5 image)", async () => {
|
||||
for (let i = 0; i < 10; i++) await incrementApiCall(db, "text");
|
||||
for (let i = 0; i < 5; i++) await incrementApiCall(db, "image");
|
||||
|
||||
const canCallText = await checkRateLimit(db, "text");
|
||||
const canCallImage = await checkRateLimit(db, "image");
|
||||
expect(canCallText).toBe(false);
|
||||
expect(canCallImage).toBe(false);
|
||||
});
|
||||
|
||||
it("incrementApiCall increments correct counter", async () => {
|
||||
for (let i = 0; i < 10; i++) await incrementApiCall(db, "text");
|
||||
|
||||
const canCallText = await checkRateLimit(db, "text");
|
||||
const canCallImage = await checkRateLimit(db, "image");
|
||||
expect(canCallText).toBe(false);
|
||||
expect(canCallImage).toBe(true);
|
||||
});
|
||||
|
||||
it("rate limit resets when lastApiCallDate differs from today", async () => {
|
||||
// Increment to hit limit
|
||||
for (let i = 0; i < 10; i++) await incrementApiCall(db, "text");
|
||||
for (let i = 0; i < 5; i++) await incrementApiCall(db, "image");
|
||||
|
||||
// Manually set lastApiCallDate to yesterday
|
||||
const { getSettings, saveSettings } = await import("../storage/db");
|
||||
const settings = await getSettings(db);
|
||||
if (settings) {
|
||||
settings.lastApiCallDate = "2020-01-01";
|
||||
await saveSettings(db, settings);
|
||||
}
|
||||
|
||||
// Should be reset now
|
||||
const canCallText = await checkRateLimit(db, "text");
|
||||
const canCallImage = await checkRateLimit(db, "image");
|
||||
expect(canCallText).toBe(true);
|
||||
expect(canCallImage).toBe(true);
|
||||
describe("Rate limiting (disabled)", () => {
|
||||
it("checkRateLimit always returns true (no limits)", async () => {
|
||||
const db = {} as IDBDatabase;
|
||||
expect(await checkRateLimit(db, "text")).toBe(true);
|
||||
expect(await checkRateLimit(db, "image")).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
+6
-63
@@ -1,12 +1,8 @@
|
||||
import { getSettings, saveSettings } from "../storage/db";
|
||||
import type { Settings } from "../types";
|
||||
import type { GeminiConfig } from "./config";
|
||||
|
||||
const GEMINI_BASE_URL =
|
||||
"https://generativelanguage.googleapis.com/v1beta/models";
|
||||
const RETRY_DELAYS = [1000, 2000, 4000];
|
||||
const TEXT_RATE_LIMIT = 10;
|
||||
const IMAGE_RATE_LIMIT = 5;
|
||||
|
||||
function sleep(ms: number): Promise<void> {
|
||||
return new Promise((resolve) => setTimeout(resolve, ms));
|
||||
@@ -117,70 +113,17 @@ export async function generateImage(
|
||||
}
|
||||
}
|
||||
|
||||
function getTodayDate(): string {
|
||||
return new Date().toISOString().split("T")[0] ?? "";
|
||||
}
|
||||
|
||||
function getDefaultSettings(): Settings {
|
||||
return {
|
||||
id: 1,
|
||||
audioEnabled: true,
|
||||
speechEnabled: false,
|
||||
apiKey: "",
|
||||
apiCallsToday: { text: 0, image: 0 },
|
||||
lastApiCallDate: getTodayDate(),
|
||||
};
|
||||
}
|
||||
|
||||
async function getOrCreateSettings(db: IDBDatabase): Promise<Settings> {
|
||||
const settings = await getSettings(db);
|
||||
if (!settings) {
|
||||
const defaults = getDefaultSettings();
|
||||
await saveSettings(db, defaults);
|
||||
return defaults;
|
||||
}
|
||||
// Migrate old settings that don't have rate-limit fields
|
||||
if (!settings.apiCallsToday) {
|
||||
settings.apiCallsToday = { text: 0, image: 0 };
|
||||
settings.lastApiCallDate = getTodayDate();
|
||||
await saveSettings(db, settings);
|
||||
}
|
||||
return settings;
|
||||
}
|
||||
|
||||
export async function checkRateLimit(
|
||||
db: IDBDatabase,
|
||||
type: "text" | "image",
|
||||
_db: IDBDatabase,
|
||||
_type: "text" | "image",
|
||||
): Promise<boolean> {
|
||||
const settings = await getOrCreateSettings(db);
|
||||
const today = getTodayDate();
|
||||
|
||||
// Reset if date changed
|
||||
if (settings.lastApiCallDate !== today) {
|
||||
settings.apiCallsToday = { text: 0, image: 0 };
|
||||
settings.lastApiCallDate = today;
|
||||
await saveSettings(db, settings);
|
||||
}
|
||||
|
||||
return type === "text"
|
||||
? settings.apiCallsToday.text < TEXT_RATE_LIMIT
|
||||
: settings.apiCallsToday.image < IMAGE_RATE_LIMIT;
|
||||
return true;
|
||||
}
|
||||
|
||||
export async function incrementApiCall(
|
||||
db: IDBDatabase,
|
||||
type: "text" | "image",
|
||||
_db: IDBDatabase,
|
||||
_type: "text" | "image",
|
||||
): Promise<void> {
|
||||
const settings = await getOrCreateSettings(db);
|
||||
const today = getTodayDate();
|
||||
|
||||
// Reset if date changed
|
||||
if (settings.lastApiCallDate !== today) {
|
||||
settings.apiCallsToday = { text: 0, image: 0 };
|
||||
settings.lastApiCallDate = today;
|
||||
}
|
||||
|
||||
settings.apiCallsToday[type]++;
|
||||
settings.lastApiCallDate = today;
|
||||
await saveSettings(db, settings);
|
||||
// No-op: rate limits removed
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user