Added Documents Feature

This commit is contained in:
Eric Gullickson
2025-09-28 20:35:46 -05:00
parent 2e1b588270
commit 775a1ff69e
66 changed files with 5655 additions and 944 deletions

View File

@@ -0,0 +1,295 @@
/**
* K8s-aligned Configuration Loader
* Loads configuration from YAML files and secrets from mounted files
* Replaces environment variable based configuration for production k8s compatibility
*/
import * as yaml from 'js-yaml';
import * as fs from 'fs';
import * as path from 'path';
import { z } from 'zod';
import { logger } from '../logging/logger';
// Configuration schema definition
const configSchema = z.object({
// Server configuration
server: z.object({
name: z.string(),
port: z.number(),
environment: z.string(),
tenant_id: z.string(),
node_env: z.string(),
}),
// Database configuration
database: z.object({
host: z.string(),
port: z.number(),
name: z.string(),
user: z.string(),
pool_size: z.number().optional().default(20),
}),
// Redis configuration
redis: z.object({
host: z.string(),
port: z.number(),
db: z.number().optional().default(0),
}),
// Auth0 configuration
auth0: z.object({
domain: z.string(),
audience: z.string(),
}),
// Platform services configuration
platform: z.object({
services: z.object({
vehicles: z.object({
url: z.string(),
timeout: z.string(),
}),
tenants: z.object({
url: z.string(),
timeout: z.string(),
}),
}),
}),
// MinIO configuration
minio: z.object({
endpoint: z.string(),
port: z.number(),
bucket: z.string(),
}),
// External APIs configuration
external: z.object({
vpic: z.object({
url: z.string(),
timeout: z.string(),
}),
}),
// Service configuration
service: z.object({
name: z.string(),
}),
// CORS configuration
cors: z.object({
origins: z.array(z.string()),
allow_credentials: z.boolean(),
max_age: z.number(),
}),
// Frontend configuration
frontend: z.object({
tenant_id: z.string(),
api_base_url: z.string(),
auth0: z.object({
domain: z.string(),
audience: z.string(),
}),
}),
// Health check configuration
health: z.object({
endpoints: z.object({
basic: z.string(),
ready: z.string(),
live: z.string(),
startup: z.string(),
}),
probes: z.object({
startup: z.object({
initial_delay: z.string(),
period: z.string(),
timeout: z.string(),
failure_threshold: z.number(),
}),
readiness: z.object({
period: z.string(),
timeout: z.string(),
failure_threshold: z.number(),
}),
liveness: z.object({
period: z.string(),
timeout: z.string(),
failure_threshold: z.number(),
}),
}),
}),
// Logging configuration
logging: z.object({
level: z.string(),
format: z.string(),
destinations: z.array(z.string()),
}),
// Performance configuration
performance: z.object({
request_timeout: z.string(),
max_request_size: z.string(),
compression_enabled: z.boolean(),
circuit_breaker: z.object({
enabled: z.boolean(),
failure_threshold: z.number(),
timeout: z.string(),
}),
}),
});
// Secrets schema definition
const secretsSchema = z.object({
postgres_password: z.string(),
minio_access_key: z.string(),
minio_secret_key: z.string(),
platform_vehicles_api_key: z.string(),
auth0_client_secret: z.string(),
google_maps_api_key: z.string(),
});
type Config = z.infer<typeof configSchema>;
type Secrets = z.infer<typeof secretsSchema>;
export interface AppConfiguration {
config: Config;
secrets: Secrets;
// Convenience accessors for common patterns
getDatabaseUrl(): string;
getRedisUrl(): string;
getAuth0Config(): { domain: string; audience: string; clientSecret: string };
getPlatformServiceConfig(service: 'vehicles' | 'tenants'): { url: string; apiKey: string };
getMinioConfig(): { endpoint: string; port: number; accessKey: string; secretKey: string; bucket: string };
}
class ConfigurationLoader {
private configPath: string;
private secretsDir: string;
private cachedConfig: AppConfiguration | null = null;
constructor() {
this.configPath = process.env.CONFIG_PATH || '/app/config/production.yml';
this.secretsDir = process.env.SECRETS_DIR || '/run/secrets';
}
private loadYamlConfig(): Config {
if (!fs.existsSync(this.configPath)) {
throw new Error(`Configuration file not found at ${this.configPath}`);
}
try {
const fileContents = fs.readFileSync(this.configPath, 'utf8');
const yamlData = yaml.load(fileContents) as any;
return configSchema.parse(yamlData);
} catch (error) {
logger.error(`Failed to load configuration from ${this.configPath}`, { error });
throw new Error(`Configuration loading failed: ${error}`);
}
}
private loadSecrets(): Secrets {
const secrets: Partial<Secrets> = {};
const secretFiles = [
'postgres-password',
'minio-access-key',
'minio-secret-key',
'platform-vehicles-api-key',
'auth0-client-secret',
'google-maps-api-key',
];
for (const secretFile of secretFiles) {
const secretPath = path.join(this.secretsDir, secretFile);
const secretKey = secretFile.replace(/-/g, '_') as keyof Secrets;
if (fs.existsSync(secretPath)) {
try {
const secretValue = fs.readFileSync(secretPath, 'utf8').trim();
(secrets as any)[secretKey] = secretValue;
} catch (error) {
logger.error(`Failed to read secret file ${secretPath}`, { error });
}
} else {
logger.error(`Secret file not found: ${secretPath}`);
}
}
try {
return secretsSchema.parse(secrets);
} catch (error) {
logger.error('Secrets validation failed', { error });
throw new Error(`Secrets loading failed: ${error}`);
}
}
public load(): AppConfiguration {
if (this.cachedConfig) {
return this.cachedConfig;
}
const config = this.loadYamlConfig();
const secrets = this.loadSecrets();
this.cachedConfig = {
config,
secrets,
getDatabaseUrl(): string {
return `postgresql://${config.database.user}:${secrets.postgres_password}@${config.database.host}:${config.database.port}/${config.database.name}`;
},
getRedisUrl(): string {
return `redis://${config.redis.host}:${config.redis.port}/${config.redis.db}`;
},
getAuth0Config() {
return {
domain: config.auth0.domain,
audience: config.auth0.audience,
clientSecret: secrets.auth0_client_secret,
};
},
getPlatformServiceConfig(service: 'vehicles' | 'tenants') {
const serviceConfig = config.platform.services[service];
const apiKey = service === 'vehicles' ? secrets.platform_vehicles_api_key : 'mvp-platform-tenants-secret-key';
return {
url: serviceConfig.url,
apiKey,
};
},
getMinioConfig() {
return {
endpoint: config.minio.endpoint,
port: config.minio.port,
accessKey: secrets.minio_access_key,
secretKey: secrets.minio_secret_key,
bucket: config.minio.bucket,
};
},
};
logger.info('Configuration loaded successfully', {
configSource: 'yaml',
secretsSource: 'files',
});
return this.cachedConfig;
}
}
// Export singleton instance
const configLoader = new ConfigurationLoader();
export const appConfig = configLoader.load();
// Export types for use in other modules
export type { Config, Secrets };