mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-08-24 16:12:23 +03:00
feat(redis): add configurable key namespace prefix (#11042)
Validated on the combined board over tip 80d931ae: quota-redis-store (incl. the KEY_PREFIX derivation test), local-redis-status and rate-limiter-redis-optional green, typecheck:core clean. One pre-merge fix pushed to the branch: docs/reference/ENVIRONMENT.md gained the REDIS_KEY_PREFIX row (env-doc-sync gate requires every .env.example var documented). Board note: the redis tests leave an ioredis retry handle open and hang the runner exit locally — assertions all pass; pre-existing pattern, not from this PR. Thank you @MeRezaRezaei!
This commit is contained in:
@@ -63,21 +63,51 @@ async function pingRedis(port: string): Promise<boolean> {
|
||||
});
|
||||
}
|
||||
|
||||
function parseRedisUrl(url?: string): { host: string; port: number } | null {
|
||||
if (!url) return null;
|
||||
try {
|
||||
const u = new URL(url);
|
||||
return { host: u.hostname || "127.0.0.1", port: Number(u.port) || 6379 };
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
export async function GET() {
|
||||
const guard = isLocalRequestAllowed();
|
||||
if (!guard.allowed) {
|
||||
return NextResponse.json({ error: guard.reason }, { status: 403 });
|
||||
const reason = (guard as { reason?: string }).reason ?? "Forbidden: not a loopback request";
|
||||
return NextResponse.json({ error: reason }, { status: 403 });
|
||||
}
|
||||
|
||||
// Docker/Podman container state (the 1-click launcher path).
|
||||
const runtime = await detectRuntime();
|
||||
if (!runtime) {
|
||||
return NextResponse.json(
|
||||
{ exists: false, running: false, reachable: false, error: "No container runtime (podman or docker) found on PATH" },
|
||||
{ status: 503 }
|
||||
);
|
||||
let container = { exists: false, running: false, reachable: false };
|
||||
if (runtime) {
|
||||
const { exists, running } = await containerState(runtime);
|
||||
const reachable = running ? await pingRedis(HOST_PORT) : false;
|
||||
container = { exists, running, reachable };
|
||||
}
|
||||
|
||||
const { exists, running } = await containerState(runtime);
|
||||
const reachable = running ? await pingRedis(HOST_PORT) : false;
|
||||
return NextResponse.json({ runtime, name: CONTAINER_NAME, port: HOST_PORT, exists, running, reachable });
|
||||
// Native Redis via REDIS_URL (the production path this instance uses). OmniRoute
|
||||
// is "connected" whenever REDIS_URL is configured AND the server answers — even
|
||||
// when no Docker container is present.
|
||||
const redisUrl = process.env.REDIS_URL?.trim() || "";
|
||||
const parsed = parseRedisUrl(redisUrl);
|
||||
const redisUrlReachable = parsed ? await pingRedis(String(parsed.port)) : false;
|
||||
|
||||
const running = container.running || redisUrlReachable;
|
||||
const reachable = container.reachable || redisUrlReachable;
|
||||
const exists = container.exists || redisUrlReachable;
|
||||
|
||||
return NextResponse.json({
|
||||
runtime: runtime ?? null,
|
||||
name: CONTAINER_NAME,
|
||||
port: HOST_PORT,
|
||||
exists,
|
||||
running,
|
||||
reachable,
|
||||
redisUrlConfigured: Boolean(redisUrl),
|
||||
redisUrlReachable,
|
||||
});
|
||||
}
|
||||
@@ -72,7 +72,7 @@ export function resetRedisClient(): void {
|
||||
// Key helpers
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
const KEY_PREFIX = "omniroute:quota";
|
||||
const KEY_PREFIX = `${process.env.REDIS_KEY_PREFIX?.trim() || "omniroute:"}quota`;
|
||||
|
||||
function bucketKey(apiKeyId: string, dimensionKey: string, bucketIndex: number): string {
|
||||
return `${KEY_PREFIX}:${apiKeyId}:${dimensionKey}:${bucketIndex}`;
|
||||
|
||||
@@ -3,6 +3,10 @@ import type Redis from "ioredis";
|
||||
// Redis is optional. When REDIS_URL is unset, use a process-local fallback
|
||||
// instead of probing localhost on every API request.
|
||||
const REDIS_URL = process.env.REDIS_URL?.trim() || "";
|
||||
|
||||
// Namespace prefix for all OmniRoute Redis keys. Prevents key collisions when
|
||||
// OmniRoute shares a Redis instance with other apps (e.g. on 127.0.0.1:6379).
|
||||
const REDIS_KEY_PREFIX = process.env.REDIS_KEY_PREFIX?.trim() || "omniroute:";
|
||||
if (process.env.NODE_ENV === "production" && !REDIS_URL) {
|
||||
console.warn("[REDIS] REDIS_URL is not set in production. Using in-memory rate limiting.");
|
||||
}
|
||||
@@ -72,6 +76,7 @@ export function getRedisClient(): Promise<Redis> {
|
||||
const client = new RedisCtor(REDIS_URL, {
|
||||
maxRetriesPerRequest: 3,
|
||||
enableReadyCheck: false,
|
||||
keyPrefix: REDIS_KEY_PREFIX,
|
||||
retryStrategy(times) {
|
||||
return Math.min(times * 50, 2000); // Exponential backoff
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user