env vars
This commit is contained in:
@@ -17,12 +17,20 @@ import { RaceModule } from './domain/race/RaceModule';
|
||||
import { SponsorModule } from './domain/sponsor/SponsorModule';
|
||||
import { TeamModule } from './domain/team/TeamModule';
|
||||
|
||||
import { getApiPersistence, getEnableBootstrap } from './env';
|
||||
|
||||
const API_PERSISTENCE = getApiPersistence();
|
||||
const USE_DATABASE = API_PERSISTENCE === 'postgres';
|
||||
|
||||
// Keep bootstrap on by default; tests can disable explicitly.
|
||||
const ENABLE_BOOTSTRAP = getEnableBootstrap();
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
HelloModule,
|
||||
DatabaseModule,
|
||||
...(USE_DATABASE ? [DatabaseModule] : []),
|
||||
LoggingModule,
|
||||
BootstrapModule,
|
||||
...(ENABLE_BOOTSTRAP ? [BootstrapModule] : []),
|
||||
AnalyticsModule,
|
||||
AuthModule,
|
||||
DashboardModule,
|
||||
|
||||
34
apps/api/src/env.d.ts
vendored
Normal file
34
apps/api/src/env.d.ts
vendored
Normal file
@@ -0,0 +1,34 @@
|
||||
declare global {
|
||||
namespace NodeJS {
|
||||
interface ProcessEnv {
|
||||
NODE_ENV?: 'development' | 'production' | 'test';
|
||||
|
||||
// API runtime toggles
|
||||
GRIDPILOT_API_PERSISTENCE?: 'postgres' | 'inmemory';
|
||||
GRIDPILOT_API_BOOTSTRAP?: string;
|
||||
GENERATE_OPENAPI?: string;
|
||||
|
||||
// Database (TypeORM)
|
||||
DATABASE_URL?: string;
|
||||
DATABASE_HOST?: string;
|
||||
DATABASE_PORT?: string;
|
||||
DATABASE_USER?: string;
|
||||
DATABASE_PASSWORD?: string;
|
||||
DATABASE_NAME?: string;
|
||||
|
||||
// Policy / operational mode
|
||||
GRIDPILOT_POLICY_CACHE_MS?: string;
|
||||
GRIDPILOT_POLICY_PATH?: string;
|
||||
GRIDPILOT_OPERATIONAL_MODE?: string;
|
||||
GRIDPILOT_FEATURES_JSON?: string;
|
||||
GRIDPILOT_MAINTENANCE_ALLOW_VIEW?: string;
|
||||
GRIDPILOT_MAINTENANCE_ALLOW_MUTATE?: string;
|
||||
|
||||
// Authorization
|
||||
GRIDPILOT_AUTHZ_CACHE_MS?: string;
|
||||
GRIDPILOT_USER_ROLES_JSON?: string;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export {};
|
||||
62
apps/api/src/env.ts
Normal file
62
apps/api/src/env.ts
Normal file
@@ -0,0 +1,62 @@
|
||||
export type ApiPersistence = 'postgres' | 'inmemory';
|
||||
|
||||
function isTruthyEnv(value: string | undefined): boolean {
|
||||
if (!value) return false;
|
||||
return value !== '0' && value.toLowerCase() !== 'false';
|
||||
}
|
||||
|
||||
function isSet(value: string | undefined): boolean {
|
||||
return value !== undefined;
|
||||
}
|
||||
|
||||
function readLower(name: string): string | undefined {
|
||||
const raw = process.env[name];
|
||||
if (raw === undefined) return undefined;
|
||||
return raw.toLowerCase();
|
||||
}
|
||||
|
||||
function requireOneOf<T extends string>(name: string, value: string, allowed: readonly T[]): T {
|
||||
if ((allowed as readonly string[]).includes(value)) {
|
||||
return value as T;
|
||||
}
|
||||
|
||||
const valid = allowed.join(', ');
|
||||
throw new Error(`Invalid ${name}: "${value}". Must be one of: ${valid}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Controls whether the API uses Postgres or runs in-memory.
|
||||
*
|
||||
* If `GRIDPILOT_API_PERSISTENCE` is set, it must be `postgres|inmemory`.
|
||||
* Otherwise, it falls back to: `postgres` when `DATABASE_URL` exists, else `inmemory`.
|
||||
*/
|
||||
export function getApiPersistence(): ApiPersistence {
|
||||
const configured = readLower('GRIDPILOT_API_PERSISTENCE');
|
||||
if (configured) {
|
||||
return requireOneOf('GRIDPILOT_API_PERSISTENCE', configured, ['postgres', 'inmemory'] as const);
|
||||
}
|
||||
|
||||
return process.env.DATABASE_URL ? 'postgres' : 'inmemory';
|
||||
}
|
||||
|
||||
/**
|
||||
* Keep bootstrap on by default; tests can disable explicitly.
|
||||
*
|
||||
* `GRIDPILOT_API_BOOTSTRAP` uses "truthy" parsing:
|
||||
* - false when unset / "0" / "false"
|
||||
* - true otherwise
|
||||
*/
|
||||
export function getEnableBootstrap(): boolean {
|
||||
const raw = process.env.GRIDPILOT_API_BOOTSTRAP;
|
||||
if (raw === undefined) return true;
|
||||
return isTruthyEnv(raw);
|
||||
}
|
||||
|
||||
/**
|
||||
* When set, the API will generate `openapi.json` and optionally reduce logging noise.
|
||||
*
|
||||
* Matches previous behavior: any value (even "0") counts as enabled if the var is present.
|
||||
*/
|
||||
export function getGenerateOpenapi(): boolean {
|
||||
return isSet(process.env.GENERATE_OPENAPI);
|
||||
}
|
||||
@@ -11,8 +11,11 @@ import { AuthenticationGuard } from './domain/auth/AuthenticationGuard';
|
||||
import { AuthorizationGuard } from './domain/auth/AuthorizationGuard';
|
||||
import { FeatureAvailabilityGuard } from './domain/policy/FeatureAvailabilityGuard';
|
||||
|
||||
import { getGenerateOpenapi } from './env';
|
||||
|
||||
async function bootstrap() {
|
||||
const app = await NestFactory.create(AppModule, process.env.GENERATE_OPENAPI ? { logger: false } : undefined);
|
||||
const generateOpenapi = getGenerateOpenapi();
|
||||
const app = await NestFactory.create(AppModule, generateOpenapi ? { logger: false } : undefined);
|
||||
|
||||
// Website runs on a different origin in dev/docker (e.g. http://localhost:3000 -> http://localhost:3001),
|
||||
// and our website HTTP client uses `credentials: 'include'`, so we must support CORS with credentials.
|
||||
@@ -64,7 +67,7 @@ async function bootstrap() {
|
||||
SwaggerModule.setup('api/docs', app as any, document);
|
||||
|
||||
// Export OpenAPI spec as JSON file when GENERATE_OPENAPI env var is set
|
||||
if (process.env.GENERATE_OPENAPI) {
|
||||
if (generateOpenapi) {
|
||||
const outputPath = join(__dirname, '../openapi.json');
|
||||
writeFileSync(outputPath, JSON.stringify(document, null, 2));
|
||||
console.log(`✅ OpenAPI spec generated at: ${outputPath}`);
|
||||
|
||||
Reference in New Issue
Block a user