feat: remove application-side API rate limits
This commit is contained in:
+5
-59
@@ -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
@@ -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);
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user