feat: remove application-side API rate limits

This commit is contained in:
2026-03-29 23:29:16 +02:00
parent f9fc9bd4a5
commit 2365a95f98
2 changed files with 11 additions and 122 deletions
+5 -59
View File
@@ -1,12 +1,10 @@
import "fake-indexeddb/auto"; import "fake-indexeddb/auto";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import { openDB } from "../storage/db";
import type { GeminiConfig } from "./config"; import type { GeminiConfig } from "./config";
import { import {
checkRateLimit, checkRateLimit,
generateImage, generateImage,
generateText, generateText,
incrementApiCall,
} from "./gemini"; } from "./gemini";
const TEST_CONFIG: GeminiConfig = { const TEST_CONFIG: GeminiConfig = {
@@ -210,62 +208,10 @@ describe("generateImage", () => {
}); });
}); });
describe("Rate limiting", () => { describe("Rate limiting (disabled)", () => {
let db: IDBDatabase; it("checkRateLimit always returns true (no limits)", async () => {
const db = {} as IDBDatabase;
beforeEach(async () => { expect(await checkRateLimit(db, "text")).toBe(true);
vi.restoreAllMocks(); expect(await checkRateLimit(db, "image")).toBe(true);
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);
}); });
}); });
+6 -63
View File
@@ -1,12 +1,8 @@
import { getSettings, saveSettings } from "../storage/db";
import type { Settings } from "../types";
import type { GeminiConfig } from "./config"; import type { GeminiConfig } from "./config";
const GEMINI_BASE_URL = const GEMINI_BASE_URL =
"https://generativelanguage.googleapis.com/v1beta/models"; "https://generativelanguage.googleapis.com/v1beta/models";
const RETRY_DELAYS = [1000, 2000, 4000]; const RETRY_DELAYS = [1000, 2000, 4000];
const TEXT_RATE_LIMIT = 10;
const IMAGE_RATE_LIMIT = 5;
function sleep(ms: number): Promise<void> { function sleep(ms: number): Promise<void> {
return new Promise((resolve) => setTimeout(resolve, ms)); 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( export async function checkRateLimit(
db: IDBDatabase, _db: IDBDatabase,
type: "text" | "image", _type: "text" | "image",
): Promise<boolean> { ): Promise<boolean> {
const settings = await getOrCreateSettings(db); return true;
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;
} }
export async function incrementApiCall( export async function incrementApiCall(
db: IDBDatabase, _db: IDBDatabase,
type: "text" | "image", _type: "text" | "image",
): Promise<void> { ): Promise<void> {
const settings = await getOrCreateSettings(db); // No-op: rate limits removed
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);
} }