Files
TREK/server/src/nest/health/features.controller.ts
T
Maurice a869269591 fix(deploy): follow PORT in every probe, wait for a slow boot, and probe the database
Changing PORT left the Dockerfile and compose healthchecks, the chart's
containerPort, Service target and probes on 3000, so the container went
unhealthy and Kubernetes restarted it in a loop. All of them follow PORT
now. The chart gains a startup probe (ten minutes for a first boot, a
long migration or a restore at boot, which liveness used to kill after
about 105 s), a readiness probe on the new /api/health/ready that asks
the database, extraEnv and extraEnvFrom for the settings the chart does
not list, slots for the four secrets it had none for, and the
DEFAULT_LANGUAGE and MCP_MAX_SESSION_PER_USER values it documented but
never set. The Dockerfile no longer hides a failed npm ci behind a ';',
pins gosu, and the language lists name all 27 locales.
2026-10-07 16:40:40 +02:00

57 lines
2.2 KiB
TypeScript

import { Controller, Get, Res } from '@nestjs/common';
import { EntityManager } from '@mikro-orm/core';
import type { Response } from 'express';
import { MaintenanceRepository } from '../../db/repositories/MaintenanceRepository';
import { KitineraryExtractorService } from '../booking-import/kitinerary-extractor.service';
import { AddonsService } from '../addons/addons.service';
import { ADDON_IDS } from '../../addons';
import { Public } from '../auth/public.decorator';
/** Exposes the container probe and the server feature flags consumed by the
* frontend to show/hide optional UI. */
@Public('server capability flags the login screen reads to decide what to offer')
@Controller('api/health')
export class FeaturesController {
constructor(
private readonly extractor: KitineraryExtractorService,
private readonly addons: AddonsService,
private readonly em: EntityManager,
) {}
/** The container/uptime probe. The forced-HTTPS redirect and HSTS exempt this
* path inside globalMiddleware, so probes work regardless of proxy setup;
* @Res() keeps the exact legacy header casing and body bytes. */
@Get()
health(@Res() res: Response): void {
res.setHeader('Cache-Control', 'no-store, must-revalidate');
res.json({ status: 'ok' });
}
/**
* The readiness probe: 503 while the database does not answer (a restore
* swapping it, a boot still migrating), so an orchestrator stops sending
* traffic without killing the process. Liveness stays on the plain probe
* above, which a long restore must not fail.
*/
@Get('ready')
async ready(@Res() res: Response): Promise<void> {
res.setHeader('Cache-Control', 'no-store, must-revalidate');
try {
await new MaintenanceRepository(this.em).ping();
res.json({ status: 'ready' });
} catch {
res.status(503).json({ status: 'unavailable' });
}
}
@Get('features')
async features() {
return {
bookingImport: this.extractor.isAvailable(),
// Addon-level flag (per-user config availability is reported per-file in
// the preview response). Drives whether the client shows AI affordances.
aiParsing: await this.addons.isAddonEnabled(ADDON_IDS.LLM_PARSING),
};
}
}