Files
B2BCall-dialer/apps/api/src/health/health.controller.ts
Matheus 68b403a7ff feat: add apps/api (NestJS + Fastify) with authentication endpoints
- POST /auth/login, /auth/refresh, /auth/logout, /auth/select-tenant,
  /auth/change-password, GET /auth/tenants — wired to packages/auth
- JwtAuthGuard + DomainExceptionFilter (401/403 without leaking internals)
- LoginRateLimitGuard: Redis-backed 5/min per IP and per email (agente.md
  secao 149), safe across multiple API instances
- helmet + restrictive cors (deny-by-default) + global rate limit
- GET /health, /health/live, /health/ready checking Postgres and Redis
- changePassword() added to packages/auth for the mustChangePassword flow
- fixed REDIS_HOST/POSTGRES_HOST docker-compose-only hostnames not
  resolving from the host process; added REDIS_URL for host-side use
- verified end-to-end with curl: login, wrong password / unknown email
  (same generic error), authenticated route, missing token, refresh
  rotation, logout revocation, and the 429 rate limit kicking in after 5
  attempts
2026-08-28 06:12:34 -03:00

41 lines
912 B
TypeScript

import { Controller, Get, HttpException, HttpStatus } from "@nestjs/common";
import { getPrismaClient } from "@b2bcall/database";
import { getRedisClient } from "../common/redis";
@Controller("health")
export class HealthController {
@Get()
root() {
return { status: "ok" };
}
@Get("live")
live() {
return { status: "ok" };
}
@Get("ready")
async ready() {
const checks: Record<string, "ok" | "fail"> = { postgres: "ok", redis: "ok" };
try {
await getPrismaClient().$queryRaw`SELECT 1`;
} catch {
checks.postgres = "fail";
}
try {
await getRedisClient().ping();
} catch {
checks.redis = "fail";
}
const healthy = Object.values(checks).every((v) => v === "ok");
if (!healthy) {
throw new HttpException({ status: "unhealthy", checks }, HttpStatus.SERVICE_UNAVAILABLE);
}
return { status: "ok", checks };
}
}