Files
siop2/apps/api/src/config/env.ts
pr-daaif 2ffdd13cf4 feat(r3.2): bibliothèque de documents (FileStorage réel) + analytics
- FileStorage : putObject/getObjectStream/removeObject, bucket créé au
  démarrage — MinIO confiné à son implémentation (règle ESLint intacte)
- documents : upload multipart (PDF/JPG/PNG, 20 Mo max, rattachement
  appareil OU OT requis, permission d'édition sur la CIBLE), liste
  filtrable, téléchargement STREAMÉ par l'API (MinIO jamais exposé),
  suppression ; e2e : octets téléchargés identiques aux octets envoyés
- analytics : GET /analytics/summary dérivé du réel — coûts/mois
  (mouvements + main-d'œuvre figés), pannes par organe (bilans codés),
  taux de préventif, durée moyenne de résolution, top équipements
- générateur OpenAPI : query params, multipart, réponse binaire (71 ops)
- CI : service MinIO (bitnami) sur les jobs api et e2e
- test de régression du tri « Interventions récentes » rendu déterministe
  (positions absolues instables sous 12 suites parallèles) ; 58 tests,
  6 runs complets consécutifs verts

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-16 21:24:04 +01:00

54 lines
1.7 KiB
TypeScript

import 'dotenv/config';
import { z } from 'zod';
const EnvSchema = z.object({
NODE_ENV: z.enum(['development', 'test', 'production']).default('development'),
PORT: z.coerce.number().default(3000),
DATABASE_URL: z.string().min(1),
REDIS_URL: z.string().default('redis://localhost:6379'),
JWT_SECRET: z.string().min(16, 'JWT_SECRET : 16 caractères minimum'),
JWT_EXPIRES_IN: z.string().default('8h'),
MINIO_ENDPOINT: z.string().default('localhost'),
MINIO_PORT: z.coerce.number().default(9000),
MINIO_USE_SSL: z.string().optional(),
MINIO_ACCESS_KEY: z.string().default('siop'),
MINIO_SECRET_KEY: z.string().default('siop-minio'),
MINIO_BUCKET: z.string().default('siop2'),
});
export type Env = z.infer<typeof EnvSchema>;
/** Validée au démarrage — l'API refuse de booter sur une config invalide. */
export function loadEnv(): Env {
const parsed = EnvSchema.safeParse(process.env);
if (!parsed.success) {
throw new Error(
`Configuration invalide :\n${parsed.error.issues
.map((i) => ` - ${i.path.join('.')}: ${i.message}`)
.join('\n')}`,
);
}
return parsed.data;
}
/** Lues à l'appel (et non à l'import) pour rester testables. */
export function demoModeEnabled(): boolean {
return process.env.DEMO_MODE === 'true';
}
/**
* ADR-002, double verrou : DEMO_MODE=true en production exige l'aveu explicite
* DEMO_MODE_I_KNOW=true (réservé à l'instance de démonstration publique).
*/
export function assertDemoModeAllowed(): void {
if (
demoModeEnabled() &&
process.env.NODE_ENV === 'production' &&
process.env.DEMO_MODE_I_KNOW !== 'true'
) {
throw new Error(
'DEMO_MODE=true en production sans DEMO_MODE_I_KNOW=true — démarrage refusé (ADR-002).',
);
}
}