Skip to content

feat(api): límite de 20 preguntas por minuto por usuario (RNF-04) - #23

Merged
ErgoCodes merged 1 commit into
mainfrom
feat/rate-limit-rnf-04
Sep 30, 2026
Merged

ErgoCodes merged 1 commit into
mainfrom
feat/rate-limit-rnf-04

Conversation

@ErgoCodes

Copy link
Copy Markdown
Owner

Resumen

  • POST /conversations/:id/messages rechaza con 429 (RATE_LIMIT_EXCEEDED, cabecera Retry-After) la pregunta número 21 dentro de un minuto. Cubre el criterio de aceptación de la sección 8. El límite es por userId: agotarlo no afecta a otros usuarios.
  • Módulo nuevo modules/rate-limit/ con el puerto RateLimiter en interfaces/ y el adaptador RedisRateLimiter en repositories/. Se engancha como preHandler justo después de authenticate, solo en ese endpoint: RNF-04 limita preguntas, no peticiones en general.
  • Ventana fija de 60 s: INCR + EXPIRE NX + TTL en un solo MULTI. El primer intento abre la ventana y las ráfagas no la alargan.
  • Código propio en vez de @fastify/rate-limit: la clave es el userId del token, que solo existe después de authenticate, y el contador comparte el Redis que ya usan la caché y la cola.
  • Las respuestas servidas desde caché también cuentan: el límite protege tanto el coste del LLM como el abuso del endpoint.
  • Configurable con RATE_LIMIT_QUESTIONS_PER_MINUTE (por defecto 20, con valor por defecto, así que no rompe ningún .env). El frontend no necesita cambios: ya muestra el message de la API en el banner de error. Solo se añade el código al tipo ApiErrorCode.
  • README: la sección RNF-04 pasa de "pendiente" a implementada e incluye el compromiso conocido de la ventana fija (hasta ~40 preguntas en pocos segundos alrededor del cambio de ventana).

Base: #22 (Groq). Toca las mismas líneas de config.ts/dependencies.ts, así que va apilada. Al mergear #22, GitHub la redirige a main.

Test plan

  • pnpm lint / pnpm typecheck (los tres paquetes)
  • pnpm test: tests unitarios del adaptador (comandos Redis, límite, TTL ausente, transacción abortada) y test de ruta (dos preguntas OK, tercera 429 con Retry-After, otro usuario sigue en 200)
  • pnpm --filter @docqa/api test:coverage (100% en dominio)
  • pnpm --filter @docqa/api test:integration (318 passed, Mongo real)
  • Contra el Redis real de docker compose: 1-20 permitidas, la 21 rechazada con retryAfterSeconds: 60, TTL de 60 s tras la ráfaga (no se alarga)

🤖 Generated with Claude Code

POST /conversations/:id/messages rechaza con 429 (RATE_LIMIT_EXCEEDED y
cabecera Retry-After) la pregunta número 21 dentro de un minuto, que es el
criterio de aceptación de la sección 8. El límite es por userId, así que
un usuario que lo agota no afecta a los demás.

Ventana fija de 60 s en Redis con INCR y EXPIRE NX dentro del mismo MULTI:
el primer intento abre la ventana y las ráfagas no la alargan. Es código
propio en vez de @fastify/rate-limit porque la clave es el userId del
token, que solo existe después de authenticate. Las respuestas desde caché
también cuentan como pregunta.

El límite se configura con RATE_LIMIT_QUESTIONS_PER_MINUTE (por defecto 20)
y el README pasa la sección de RNF-04 de "pendiente" a implementada.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Base automatically changed from feat/llm-groq-provider to main September 30, 2026 16:26
@ErgoCodes
ErgoCodes merged commit 7d7ec31 into main Sep 30, 2026
2 checks passed
@ErgoCodes
ErgoCodes deleted the feat/rate-limit-rnf-04 branch September 30, 2026 16:26
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant