mirror of
https://github.com/tiennm99/ccs.git
synced 2026-10-11 03:13:12 +00:00
Epic P5. Splits 4 test-backed god-files into focused submodules using the Monster File Splitting methodology; each original file is now a thin barrel re-exporting the full public surface, so consumers are unchanged. - src/management/shared-manager.ts (1631 -> barrel 21 + 10 submodules, max 337 LOC): fs-helpers, symlink-helpers, plugin-metadata-normalizer, plugin-layout-internals, shared-dir-linker, project-context-sync, project-memory-sync, migrations, orchestrator, types. - src/web-server/routes/cliproxy-stats-routes.ts (1216 -> barrel 23 + 8 submodules, max 290 LOC): shared, quota-helpers, version-helpers, restart-route, quota-routes, error-log-routes, config-routes, router. - src/commands/persist-command.ts (1071 -> barrel + 8 submodules, max 258): types, arg-parsing, secure-file, backup-rotation, secret-detection, receipt, help, handler. - src/commands/cliproxy/quota-subcommand.ts (1130 -> barrel + 14 submodules, max 303): types, format-helpers, codex/claude-window-helpers, provider-runtime, handlers, per-provider sections. Public API preserved verbatim (default + named exports). All P3 structured logging and P4 typed errors preserved through the splits. Each resulting file < 400 LOC. Existing tests (shared-manager x4, cliproxy-stats x4, persist x2, quota-subcommand x1) green, unchanged. Metric: files > 400 LOC 95 -> 91. validate + validate:ci-parity green.
71 lines
2.2 KiB
TypeScript
71 lines
2.2 KiB
TypeScript
/**
|
|
* Shared internals for cliproxy-stats-routes: logger and rate-limit helpers.
|
|
*
|
|
* These are NOT part of the public barrel surface; they exist so the sibling
|
|
* submodules (quota helpers, version helpers, route handlers) can share the
|
|
* same logger instance and rate-limit state without re-declaring them.
|
|
*/
|
|
|
|
import type { Request } from 'express';
|
|
import { createLogger } from '../../../services/logging';
|
|
|
|
/**
|
|
* Shared logger for every cliproxy-stats submodule. Preserves the
|
|
* `web-server:routes:cliproxy-stats` channel used by P3 error conversions
|
|
* (`logger.error('stats.route.error', ...)`).
|
|
*/
|
|
export const logger = createLogger('web-server:routes:cliproxy-stats');
|
|
|
|
// ==================== Quota Rate Limiting ====================
|
|
|
|
export const QUOTA_RATE_LIMIT_WINDOW_MS = 60_000;
|
|
export const QUOTA_RATE_LIMIT_MAX_REQUESTS = 120;
|
|
|
|
export interface QuotaRateLimitEntry {
|
|
windowStart: number;
|
|
count: number;
|
|
}
|
|
|
|
/**
|
|
* In-memory rate limit state. Shared across all quota route handlers so that
|
|
* the same IP+provider key is throttled regardless of which handler runs.
|
|
*/
|
|
export const quotaRateLimits = new Map<string, QuotaRateLimitEntry>();
|
|
|
|
/**
|
|
* Build the rate-limit key for a request + provider pair.
|
|
*/
|
|
export function buildQuotaRateLimitKey(req: Request, provider: string): string {
|
|
const clientIp = req.ip || req.socket.remoteAddress || 'unknown';
|
|
return `${clientIp}:${provider}`;
|
|
}
|
|
|
|
/**
|
|
* Returns true when the caller should be rejected with 429.
|
|
* Evicts stale entries to prevent unbounded memory growth.
|
|
*/
|
|
export function isQuotaRouteRateLimited(req: Request, provider: string): boolean {
|
|
const key = buildQuotaRateLimitKey(req, provider);
|
|
const now = Date.now();
|
|
|
|
// Evict stale entries to prevent unbounded memory growth
|
|
if (quotaRateLimits.size > 1000) {
|
|
for (const [k, v] of quotaRateLimits) {
|
|
if (now - v.windowStart >= QUOTA_RATE_LIMIT_WINDOW_MS * 2) {
|
|
quotaRateLimits.delete(k);
|
|
}
|
|
}
|
|
}
|
|
|
|
const current = quotaRateLimits.get(key);
|
|
|
|
if (!current || now - current.windowStart >= QUOTA_RATE_LIMIT_WINDOW_MS) {
|
|
quotaRateLimits.set(key, { windowStart: now, count: 1 });
|
|
return false;
|
|
}
|
|
|
|
current.count += 1;
|
|
quotaRateLimits.set(key, current);
|
|
return current.count > QUOTA_RATE_LIMIT_MAX_REQUESTS;
|
|
}
|