mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-10-22 15:06:32 +00:00
feat(server): add nest AppConfigModule bound to @nestjs/config
This commit is contained in:
@@ -0,0 +1,38 @@
|
||||
/**
|
||||
* Global binding of @nestjs/config onto TREK's app-config layer.
|
||||
*
|
||||
* INVARIANTS — breaking either silently breaks ~60 env-mutating tests:
|
||||
* - `cache: false`, always. `cache: true` would freeze ConfigService lookups
|
||||
* for the whole app lifetime; the runtime-toggled vars (DEMO_MODE, NODE_ENV,
|
||||
* OIDC_*, …) must stay live via RuntimeEnvService/readEnv().
|
||||
* - No `validate`/`validationSchema` here. Fail-fast validation runs exactly
|
||||
* once, from the production entrypoint (src/app-config/boot-validate.ts) —
|
||||
* buildApp() is invoked dozens of times per test run and must stay
|
||||
* side-effect-free.
|
||||
* - `ignoreEnvFile: true`, always. dotenv is loaded ONCE in index.ts; tests
|
||||
* deliberately never load .env, and reading it here would leak a
|
||||
* developer's local server/.env into every integration test.
|
||||
*
|
||||
* The framework's ConfigModule is aliased ONLY inside this file: app.module.ts
|
||||
* already imports a local `ConfigModule` (the /api/config endpoint module), so
|
||||
* the alias keeps the two from colliding anywhere else.
|
||||
*/
|
||||
import { Global, Module } from '@nestjs/common';
|
||||
import { ConfigModule as NestConfigModule } from '@nestjs/config';
|
||||
import { BOOT_STABLE_TOKENS } from './tokens';
|
||||
import { RuntimeEnvService } from './runtime-env.service';
|
||||
|
||||
@Global()
|
||||
@Module({
|
||||
imports: [
|
||||
NestConfigModule.forRoot({
|
||||
isGlobal: true,
|
||||
ignoreEnvFile: true,
|
||||
cache: false,
|
||||
load: BOOT_STABLE_TOKENS,
|
||||
}),
|
||||
],
|
||||
providers: [RuntimeEnvService],
|
||||
exports: [RuntimeEnvService],
|
||||
})
|
||||
export class AppConfigModule {}
|
||||
@@ -0,0 +1,13 @@
|
||||
export { AppConfigModule } from './app-config.module';
|
||||
export { RuntimeEnvService } from './runtime-env.service';
|
||||
export {
|
||||
httpConfig,
|
||||
sessionConfig,
|
||||
mcpConfig,
|
||||
integrationsConfig,
|
||||
backupConfig,
|
||||
dbConfig,
|
||||
pathsConfig,
|
||||
netConfig,
|
||||
BOOT_STABLE_TOKENS,
|
||||
} from './tokens';
|
||||
@@ -0,0 +1,45 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { readEnv, type AppEnv } from '../../app-config';
|
||||
|
||||
/**
|
||||
* DI-friendly access to the RUNTIME-TOGGLED env values (DEMO_MODE, NODE_ENV,
|
||||
* APP_VERSION, OIDC_*, …). Every method delegates to readEnv() at call time —
|
||||
* never snapshot a namespace into a field or the constructor, or the ~60 tests
|
||||
* that mutate process.env mid-lifetime break.
|
||||
*
|
||||
* Boot-stable values are NOT accessed through this service; inject their
|
||||
* registerAs token instead (see tokens.ts).
|
||||
*/
|
||||
@Injectable()
|
||||
export class RuntimeEnvService {
|
||||
/** The full live-derived env — prefer the convenience getters where one exists. */
|
||||
env(): AppEnv {
|
||||
return readEnv();
|
||||
}
|
||||
|
||||
isDemoMode(): boolean {
|
||||
return readEnv().demo.enabled;
|
||||
}
|
||||
|
||||
nodeEnv(): string | undefined {
|
||||
return readEnv().app.nodeEnv;
|
||||
}
|
||||
|
||||
isProduction(): boolean {
|
||||
return readEnv().app.isProduction;
|
||||
}
|
||||
|
||||
isDevelopment(): boolean {
|
||||
return readEnv().app.isDevelopment;
|
||||
}
|
||||
|
||||
/** Raw APP_VERSION — call sites keep their own fallback ('0.0.0' vs package.json vs semver). */
|
||||
appVersion(): string | undefined {
|
||||
return readEnv().app.appVersion;
|
||||
}
|
||||
|
||||
/** Raw APP_URL — call sites keep their own trailing-slash handling. */
|
||||
appUrl(): string | undefined {
|
||||
return readEnv().app.appUrl;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
/**
|
||||
* registerAs tokens over the app-config derive functions, for the BOOT-STABLE
|
||||
* namespaces only (see src/app-config/README.md for the classification).
|
||||
*
|
||||
* A token's factory runs during NestFactory.create(), so the injected object is
|
||||
* a SNAPSHOT per built app — integration tests that mutate env and then rebuild
|
||||
* buildApp() get fresh values automatically. Runtime-toggled values (DEMO_MODE,
|
||||
* NODE_ENV, OIDC_*, …) must NOT get tokens here; they go through
|
||||
* RuntimeEnvService / readEnv() so mid-lifetime env mutations stay visible.
|
||||
*
|
||||
* Consumption pattern:
|
||||
* constructor(@Inject(httpConfig.KEY) private readonly http: ConfigType<typeof httpConfig>) {}
|
||||
*/
|
||||
import { registerAs } from '@nestjs/config';
|
||||
import {
|
||||
deriveBackup,
|
||||
deriveDb,
|
||||
deriveHttp,
|
||||
deriveIntegrations,
|
||||
deriveMcp,
|
||||
deriveNet,
|
||||
derivePaths,
|
||||
deriveSession,
|
||||
} from '../../app-config';
|
||||
|
||||
/**
|
||||
* http is snapshot-consumed only by the pre-init Express layer (bootstrap
|
||||
* threads it into applyGlobalMiddleware) — apply-time freeze per built app.
|
||||
* Request-time reads of the same vars (cookie.ts FORCE_HTTPS checks) stay live
|
||||
* via readEnv().
|
||||
*/
|
||||
export const httpConfig = registerAs('http', () => deriveHttp(process.env));
|
||||
export const sessionConfig = registerAs('session', () => deriveSession(process.env));
|
||||
export const mcpConfig = registerAs('mcp', () => deriveMcp(process.env));
|
||||
export const integrationsConfig = registerAs('integrations', () => deriveIntegrations(process.env));
|
||||
export const backupConfig = registerAs('backup', () => deriveBackup(process.env));
|
||||
export const dbConfig = registerAs('db', () => deriveDb(process.env));
|
||||
export const pathsConfig = registerAs('paths', () => derivePaths(process.env));
|
||||
export const netConfig = registerAs('net', () => deriveNet(process.env));
|
||||
|
||||
export const BOOT_STABLE_TOKENS = [
|
||||
httpConfig,
|
||||
sessionConfig,
|
||||
mcpConfig,
|
||||
integrationsConfig,
|
||||
backupConfig,
|
||||
dbConfig,
|
||||
pathsConfig,
|
||||
netConfig,
|
||||
];
|
||||
@@ -1,5 +1,6 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { APP_FILTER, APP_INTERCEPTOR } from '@nestjs/core';
|
||||
import { AppConfigModule } from './app-config/app-config.module';
|
||||
import { DatabaseModule } from './database/database.module';
|
||||
import { HealthController } from './health/health.controller';
|
||||
import { HealthService } from './health/health.service';
|
||||
@@ -52,7 +53,7 @@ import { IdempotencyInterceptor } from './common/idempotency.interceptor';
|
||||
* migrated.
|
||||
*/
|
||||
@Module({
|
||||
imports: [DatabaseModule, WeatherModule, HelpModule, AirportsModule, ConfigModule, SystemNoticesModule, MapsModule, CategoriesModule, TagsModule, NotificationsModule, AtlasModule, VacayModule, PackingModule, TodoModule, BudgetModule, ReservationsModule, DaysModule, AssignmentsModule, PlacesModule, TripsModule, CollabModule, FilesModule, PhotosModule, MemoriesModule, AirtrailModule, JourneyModule, CollectionsModule, ShareModule, TripInviteModule, TransitModule, FeedsModule, SettingsModule, BackupModule, AuthModule, OidcModule, OauthModule, AdminModule, AddonsModule, PluginsModule, BookingImportModule],
|
||||
imports: [AppConfigModule, DatabaseModule, WeatherModule, HelpModule, AirportsModule, ConfigModule, SystemNoticesModule, MapsModule, CategoriesModule, TagsModule, NotificationsModule, AtlasModule, VacayModule, PackingModule, TodoModule, BudgetModule, ReservationsModule, DaysModule, AssignmentsModule, PlacesModule, TripsModule, CollabModule, FilesModule, PhotosModule, MemoriesModule, AirtrailModule, JourneyModule, CollectionsModule, ShareModule, TripInviteModule, TransitModule, FeedsModule, SettingsModule, BackupModule, AuthModule, OidcModule, OauthModule, AdminModule, AddonsModule, PluginsModule, BookingImportModule],
|
||||
controllers: [HealthController],
|
||||
providers: [
|
||||
HealthService,
|
||||
|
||||
Reference in New Issue
Block a user