trails/apps/journal/app/routes/api.auth.register.ts
Ullrich Schäfer 49aadd04a9
Add observability: health endpoints, structured logging, metrics, Grafana stack
Health endpoints:
- /api/health (Journal) and /health (Planner) with DB connectivity check
- Docker healthchecks updated to use app health endpoints

Structured logging:
- Pino with JSON output in production, pretty-print in dev
- Request logging middleware in Planner (method, path, status, duration)
- Replaced console.log/error with structured logger in email and auth flows

Prometheus metrics:
- prom-client with default Node.js metrics + custom histograms/gauges
- /metrics endpoints on both apps
- http_request_duration, planner_active_sessions, brouter_request_duration

Monitoring stack:
- Prometheus, Loki, Grafana containers in docker-compose
- Grafana provisioned with datasources, dashboards, and alert rules
- Caddy access logging (JSON to stdout for Loki)
- grafana.trails.cool with basic auth via Caddy

Dashboards and alerting:
- Overview: request rate, error rate, latency p50/p95/p99
- Planner: active sessions, connected clients, BRouter latency
- Infrastructure: memory, CPU, event loop lag
- Alerts: disk >80%, app down 2min, error rate >5%

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 22:59:44 +01:00

41 lines
1.6 KiB
TypeScript

import { data } from "react-router";
import type { Route } from "./+types/api.auth.register";
import { startRegistration, finishRegistration, createSession, addPasskeyStart, addPasskeyFinish } from "~/lib/auth.server";
import { sendWelcome } from "~/lib/email.server";
import { logger } from "~/lib/logger.server";
export async function action({ request }: Route.ActionArgs) {
const body = await request.json();
const { step, email, username, response, challenge, userId } = body;
try {
if (step === "start") {
const result = await startRegistration(email, username);
return data({ step: "challenge", options: result.options, userId: result.userId });
}
if (step === "finish") {
const newUserId = await finishRegistration(userId, email, username, response, challenge);
const cookie = await createSession(newUserId, request);
// Send welcome email (fire-and-forget — don't block registration on email)
sendWelcome(email, username).catch((err) =>
logger.error({ err }, "Failed to send welcome email"),
);
return data({ step: "done" }, { headers: { "Set-Cookie": cookie } });
}
if (step === "add-passkey") {
const options = await addPasskeyStart(userId);
return data({ step: "challenge", options });
}
if (step === "finish-add-passkey") {
await addPasskeyFinish(userId, response, challenge);
return data({ step: "done" });
}
return data({ error: "Invalid step" }, { status: 400 });
} catch (e) {
return data({ error: (e as Error).message }, { status: 400 });
}
}