diff --git a/.env.example b/.env.example index 1aca5cba..0259bbd7 100644 --- a/.env.example +++ b/.env.example @@ -14,6 +14,10 @@ COSMOS_DATABASE=lysnrai # ── Auth (platform-service) ───────────────────────── JWT_SECRET=change-me-prototype-jwt-secret +RATE_LIMIT_STORE_MODE=datastore +RATE_LIMIT_CONFIG_JSON= +API_KEY_RATE_LIMIT_CONFIG_JSON= +API_KEY_PRODUCT_RATE_LIMIT_CONFIG_JSON= # ── Azure Blob Storage (platform-service) ───────────────────── STORAGE_PROVIDER=azure diff --git a/services/platform-service/src/lib/config.test.ts b/services/platform-service/src/lib/config.test.ts index 3388bee4..f72d4a08 100644 --- a/services/platform-service/src/lib/config.test.ts +++ b/services/platform-service/src/lib/config.test.ts @@ -34,6 +34,15 @@ describe('lib/config', () => { vi.stubEnv('NODE_ENV', 'test'); vi.stubEnv('SERVICE_NAME', 'platform-service-test'); vi.stubEnv('USAGE_WARN_THRESHOLD', '0.9'); + vi.stubEnv('RATE_LIMIT_STORE_MODE', 'memory'); + vi.stubEnv( + 'API_KEY_RATE_LIMIT_CONFIG_JSON', + '{"jobs:read":{"maxRequests":10,"windowSeconds":60}}' + ); + vi.stubEnv( + 'API_KEY_PRODUCT_RATE_LIMIT_CONFIG_JSON', + '{"jobs:read":{"maxRequests":20,"windowSeconds":60}}' + ); const { config } = await import('./config.js'); @@ -41,6 +50,9 @@ describe('lib/config', () => { expect(config.NODE_ENV).toBe('test'); expect(config.SERVICE_NAME).toBe('platform-service-test'); expect(config.USAGE_WARN_THRESHOLD).toBe(0.9); + expect(config.RATE_LIMIT_STORE_MODE).toBe('memory'); + expect(config.API_KEY_RATE_LIMIT_CONFIG_JSON).toContain('"jobs:read"'); + expect(config.API_KEY_PRODUCT_RATE_LIMIT_CONFIG_JSON).toContain('"jobs:read"'); }); it('throws when required env vars are missing', async () => { diff --git a/services/platform-service/src/lib/config.ts b/services/platform-service/src/lib/config.ts index fa07fbeb..507e6c0a 100644 --- a/services/platform-service/src/lib/config.ts +++ b/services/platform-service/src/lib/config.ts @@ -14,6 +14,9 @@ const envSchema = z.object({ AZURE_BLOB_ACCOUNT_NAME: z.string().optional(), AZURE_BLOB_ACCOUNT_KEY: z.string().optional(), RATE_LIMIT_CONFIG_JSON: z.string().optional(), + RATE_LIMIT_STORE_MODE: z.enum(['datastore', 'memory']).default('datastore'), + API_KEY_RATE_LIMIT_CONFIG_JSON: z.string().optional(), + API_KEY_PRODUCT_RATE_LIMIT_CONFIG_JSON: z.string().optional(), // ── Growth (merged) ── WEBHOOK_INVITATION_REDEEMED_URL: z.string().optional(), WEBHOOK_REFERRAL_STATUS_URL: z.string().optional(), diff --git a/services/platform-service/src/modules/ratelimit/types.ts b/services/platform-service/src/modules/ratelimit/types.ts index 19869480..8d6eac71 100644 --- a/services/platform-service/src/modules/ratelimit/types.ts +++ b/services/platform-service/src/modules/ratelimit/types.ts @@ -1,7 +1,7 @@ /** * Rate limiting types — sliding window limiter with per-product config. * - * Storage: in-memory Map (default) or Redis-backed (when REDIS_URL is set). + * Storage: datastore-backed by default, or in-memory when RATE_LIMIT_STORE_MODE=memory. * Config: per-product rate limit rules loaded from RATE_LIMIT_CONFIG_JSON env * or defaults to sensible values. */