293 lines
9.6 KiB
TypeScript
293 lines
9.6 KiB
TypeScript
import Joi from 'joi';
|
|
|
|
import { logger } from './logger.ts';
|
|
import type { NodeEnvironment, ValidatedEnvironment } from '../types/index.ts';
|
|
|
|
type EnvBooleanString = 'true' | 'false';
|
|
type EnvLogLevel = ValidatedEnvironment['LOG_LEVEL'];
|
|
|
|
const envSchema = Joi.object({
|
|
NODE_ENV: Joi.string()
|
|
.valid('development', 'test', 'production', 'dev_stage')
|
|
.default('development'),
|
|
|
|
PORT: Joi.number().default(8080),
|
|
NEXT_PUBLIC_BACK_API: Joi.string().uri().allow('').default(''),
|
|
|
|
DB_HOST: Joi.string().default('localhost'),
|
|
DB_PORT: Joi.number().default(5432),
|
|
DB_NAME: Joi.string().default('db_tour_builder_platform'),
|
|
DB_USER: Joi.string().default('postgres'),
|
|
DB_PASS: Joi.string().allow('').default(''),
|
|
|
|
SECRET_KEY: Joi.string()
|
|
.min(16)
|
|
.default('88dbeaf8-e906-405e-9e41-c3baadeda5c6'),
|
|
|
|
ADMIN_PASS: Joi.string().default('88dbeaf8'),
|
|
USER_PASS: Joi.string().default('c3baadeda5c6'),
|
|
ADMIN_EMAIL: Joi.string().email().default('admin@flatlogic.com'),
|
|
|
|
GOOGLE_CLIENT_ID: Joi.string().allow('').default(''),
|
|
GOOGLE_CLIENT_SECRET: Joi.string().allow('').default(''),
|
|
MS_CLIENT_ID: Joi.string().allow('').default(''),
|
|
MS_CLIENT_SECRET: Joi.string().allow('').default(''),
|
|
|
|
AWS_ACCESS_KEY_ID: Joi.string().allow('').default(''),
|
|
AWS_SECRET_ACCESS_KEY: Joi.string().allow('').default(''),
|
|
AWS_S3_BUCKET: Joi.string().allow('').default(''),
|
|
AWS_S3_REGION: Joi.string().default('us-east-1'),
|
|
AWS_S3_PREFIX: Joi.string().default('afeefb9d49f5b7977577876b99532ac7'),
|
|
AWS_S3_CONNECTION_TIMEOUT: Joi.number().integer().positive().default(5000),
|
|
AWS_S3_REQUEST_TIMEOUT: Joi.number().integer().positive().default(30000),
|
|
AWS_S3_MAX_ATTEMPTS: Joi.number().integer().positive().default(3),
|
|
AWS_S3_MAX_SOCKETS: Joi.number().integer().positive().default(50),
|
|
AWS_S3_KEEP_ALIVE: Joi.string().valid('true', 'false').default('true'),
|
|
AWS_S3_PRESIGN_EXPIRY: Joi.number().integer().positive().default(3600),
|
|
|
|
FILE_STORAGE_PROVIDER: Joi.string()
|
|
.trim()
|
|
.lowercase()
|
|
.valid('', 's3', 'gcloud', 'local')
|
|
.default(''),
|
|
GC_PROJECT_ID: Joi.string().allow('').default(''),
|
|
GC_CLIENT_EMAIL: Joi.string().allow('').default(''),
|
|
GC_PRIVATE_KEY: Joi.string().allow('').default(''),
|
|
|
|
S3_CACHE_DIR: Joi.string().allow('').default(''),
|
|
S3_CACHE_ENABLED: Joi.string().valid('true', 'false').default('true'),
|
|
S3_CACHE_MAX_AGE: Joi.number().integer().positive().default(86400),
|
|
|
|
FFMPEG_REVERSE_TIMEOUT_MS: Joi.number().integer().positive().default(600000),
|
|
FFPROBE_TIMEOUT_MS: Joi.number().integer().positive().default(30000),
|
|
FFMPEG_BREAKER_FAILURE_THRESHOLD: Joi.number()
|
|
.integer()
|
|
.positive()
|
|
.default(3),
|
|
FFMPEG_BREAKER_COOLDOWN_MS: Joi.number()
|
|
.integer()
|
|
.positive()
|
|
.default(120000),
|
|
FFMPEG_BREAKER_SUCCESS_THRESHOLD: Joi.number()
|
|
.integer()
|
|
.positive()
|
|
.default(1),
|
|
FILE_STORAGE_BREAKER_FAILURE_THRESHOLD: Joi.number()
|
|
.integer()
|
|
.positive()
|
|
.default(5),
|
|
FILE_STORAGE_BREAKER_COOLDOWN_MS: Joi.number()
|
|
.integer()
|
|
.positive()
|
|
.default(30000),
|
|
FILE_STORAGE_BREAKER_SUCCESS_THRESHOLD: Joi.number()
|
|
.integer()
|
|
.positive()
|
|
.default(2),
|
|
|
|
EMAIL_USER: Joi.string().allow('').default(''),
|
|
EMAIL_PASS: Joi.string().allow('').default(''),
|
|
EMAIL_TLS_REJECT_UNAUTHORIZED: Joi.string()
|
|
.valid('true', 'false')
|
|
.default('true'),
|
|
|
|
PEXELS_KEY: Joi.string().allow('').default(''),
|
|
|
|
LOG_LEVEL: Joi.string()
|
|
.valid('fatal', 'error', 'warn', 'info', 'debug', 'trace')
|
|
.default('info'),
|
|
LOG_PRETTY: Joi.string().valid('true', 'false').default('false'),
|
|
}).unknown(true);
|
|
|
|
function isNodeEnvironment(value: unknown): value is NodeEnvironment {
|
|
return (
|
|
value === 'development' ||
|
|
value === 'test' ||
|
|
value === 'production' ||
|
|
value === 'dev_stage'
|
|
);
|
|
}
|
|
|
|
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
return Boolean(value) && typeof value === 'object' && !Array.isArray(value);
|
|
}
|
|
|
|
function isEnvBooleanString(value: unknown): value is EnvBooleanString {
|
|
return value === 'true' || value === 'false';
|
|
}
|
|
|
|
function isEnvLogLevel(value: unknown): value is EnvLogLevel {
|
|
return (
|
|
value === 'fatal' ||
|
|
value === 'error' ||
|
|
value === 'warn' ||
|
|
value === 'info' ||
|
|
value === 'debug' ||
|
|
value === 'trace'
|
|
);
|
|
}
|
|
|
|
function readString(
|
|
values: Record<string, unknown>,
|
|
key: keyof ValidatedEnvironment,
|
|
fallback: string,
|
|
): string {
|
|
const value = values[key];
|
|
return typeof value === 'string' ? value : fallback;
|
|
}
|
|
|
|
function readNumber(
|
|
values: Record<string, unknown>,
|
|
key: keyof ValidatedEnvironment,
|
|
fallback: number,
|
|
): number {
|
|
const value = values[key];
|
|
return typeof value === 'number' ? value : fallback;
|
|
}
|
|
|
|
function toValidatedEnvironment(
|
|
values: Record<string, unknown>,
|
|
): ValidatedEnvironment {
|
|
const nodeEnv = values.NODE_ENV;
|
|
const tlsRejectUnauthorized = values.EMAIL_TLS_REJECT_UNAUTHORIZED;
|
|
const logLevel = values.LOG_LEVEL;
|
|
const s3KeepAlive = values.AWS_S3_KEEP_ALIVE;
|
|
const fileStorageProvider = values.FILE_STORAGE_PROVIDER;
|
|
const s3CacheEnabled = values.S3_CACHE_ENABLED;
|
|
const logPretty = values.LOG_PRETTY;
|
|
|
|
return {
|
|
NODE_ENV: isNodeEnvironment(nodeEnv) ? nodeEnv : 'development',
|
|
PORT: readNumber(values, 'PORT', 8080),
|
|
NEXT_PUBLIC_BACK_API: readString(values, 'NEXT_PUBLIC_BACK_API', ''),
|
|
DB_HOST: readString(values, 'DB_HOST', 'localhost'),
|
|
DB_PORT: readNumber(values, 'DB_PORT', 5432),
|
|
DB_NAME: readString(values, 'DB_NAME', 'db_tour_builder_platform'),
|
|
DB_USER: readString(values, 'DB_USER', 'postgres'),
|
|
DB_PASS: readString(values, 'DB_PASS', ''),
|
|
SECRET_KEY: readString(
|
|
values,
|
|
'SECRET_KEY',
|
|
'88dbeaf8-e906-405e-9e41-c3baadeda5c6',
|
|
),
|
|
ADMIN_PASS: readString(values, 'ADMIN_PASS', '88dbeaf8'),
|
|
USER_PASS: readString(values, 'USER_PASS', 'c3baadeda5c6'),
|
|
ADMIN_EMAIL: readString(values, 'ADMIN_EMAIL', 'admin@flatlogic.com'),
|
|
GOOGLE_CLIENT_ID: readString(values, 'GOOGLE_CLIENT_ID', ''),
|
|
GOOGLE_CLIENT_SECRET: readString(values, 'GOOGLE_CLIENT_SECRET', ''),
|
|
MS_CLIENT_ID: readString(values, 'MS_CLIENT_ID', ''),
|
|
MS_CLIENT_SECRET: readString(values, 'MS_CLIENT_SECRET', ''),
|
|
AWS_ACCESS_KEY_ID: readString(values, 'AWS_ACCESS_KEY_ID', ''),
|
|
AWS_SECRET_ACCESS_KEY: readString(values, 'AWS_SECRET_ACCESS_KEY', ''),
|
|
AWS_S3_BUCKET: readString(values, 'AWS_S3_BUCKET', ''),
|
|
AWS_S3_REGION: readString(values, 'AWS_S3_REGION', 'us-east-1'),
|
|
AWS_S3_PREFIX: readString(
|
|
values,
|
|
'AWS_S3_PREFIX',
|
|
'afeefb9d49f5b7977577876b99532ac7',
|
|
),
|
|
AWS_S3_CONNECTION_TIMEOUT: readNumber(
|
|
values,
|
|
'AWS_S3_CONNECTION_TIMEOUT',
|
|
5000,
|
|
),
|
|
AWS_S3_REQUEST_TIMEOUT: readNumber(
|
|
values,
|
|
'AWS_S3_REQUEST_TIMEOUT',
|
|
30000,
|
|
),
|
|
AWS_S3_MAX_ATTEMPTS: readNumber(values, 'AWS_S3_MAX_ATTEMPTS', 3),
|
|
AWS_S3_MAX_SOCKETS: readNumber(values, 'AWS_S3_MAX_SOCKETS', 50),
|
|
AWS_S3_KEEP_ALIVE: isEnvBooleanString(s3KeepAlive) ? s3KeepAlive : 'true',
|
|
AWS_S3_PRESIGN_EXPIRY: readNumber(
|
|
values,
|
|
'AWS_S3_PRESIGN_EXPIRY',
|
|
3600,
|
|
),
|
|
FILE_STORAGE_PROVIDER:
|
|
fileStorageProvider === 's3' ||
|
|
fileStorageProvider === 'gcloud' ||
|
|
fileStorageProvider === 'local'
|
|
? fileStorageProvider
|
|
: '',
|
|
GC_PROJECT_ID: readString(values, 'GC_PROJECT_ID', ''),
|
|
GC_CLIENT_EMAIL: readString(values, 'GC_CLIENT_EMAIL', ''),
|
|
GC_PRIVATE_KEY: readString(values, 'GC_PRIVATE_KEY', ''),
|
|
S3_CACHE_DIR: readString(values, 'S3_CACHE_DIR', ''),
|
|
S3_CACHE_ENABLED: isEnvBooleanString(s3CacheEnabled)
|
|
? s3CacheEnabled
|
|
: 'true',
|
|
S3_CACHE_MAX_AGE: readNumber(values, 'S3_CACHE_MAX_AGE', 86400),
|
|
FFMPEG_REVERSE_TIMEOUT_MS: readNumber(
|
|
values,
|
|
'FFMPEG_REVERSE_TIMEOUT_MS',
|
|
600000,
|
|
),
|
|
FFPROBE_TIMEOUT_MS: readNumber(values, 'FFPROBE_TIMEOUT_MS', 30000),
|
|
FFMPEG_BREAKER_FAILURE_THRESHOLD: readNumber(
|
|
values,
|
|
'FFMPEG_BREAKER_FAILURE_THRESHOLD',
|
|
3,
|
|
),
|
|
FFMPEG_BREAKER_COOLDOWN_MS: readNumber(
|
|
values,
|
|
'FFMPEG_BREAKER_COOLDOWN_MS',
|
|
120000,
|
|
),
|
|
FFMPEG_BREAKER_SUCCESS_THRESHOLD: readNumber(
|
|
values,
|
|
'FFMPEG_BREAKER_SUCCESS_THRESHOLD',
|
|
1,
|
|
),
|
|
FILE_STORAGE_BREAKER_FAILURE_THRESHOLD: readNumber(
|
|
values,
|
|
'FILE_STORAGE_BREAKER_FAILURE_THRESHOLD',
|
|
5,
|
|
),
|
|
FILE_STORAGE_BREAKER_COOLDOWN_MS: readNumber(
|
|
values,
|
|
'FILE_STORAGE_BREAKER_COOLDOWN_MS',
|
|
30000,
|
|
),
|
|
FILE_STORAGE_BREAKER_SUCCESS_THRESHOLD: readNumber(
|
|
values,
|
|
'FILE_STORAGE_BREAKER_SUCCESS_THRESHOLD',
|
|
2,
|
|
),
|
|
EMAIL_USER: readString(values, 'EMAIL_USER', ''),
|
|
EMAIL_PASS: readString(values, 'EMAIL_PASS', ''),
|
|
EMAIL_TLS_REJECT_UNAUTHORIZED: isEnvBooleanString(tlsRejectUnauthorized)
|
|
? tlsRejectUnauthorized
|
|
: 'true',
|
|
PEXELS_KEY: readString(values, 'PEXELS_KEY', ''),
|
|
LOG_LEVEL: isEnvLogLevel(logLevel) ? logLevel : 'info',
|
|
LOG_PRETTY: isEnvBooleanString(logPretty) ? logPretty : 'false',
|
|
};
|
|
}
|
|
|
|
function validateEnv(): ValidatedEnvironment {
|
|
const result: Joi.ValidationResult<Record<string, unknown>> =
|
|
envSchema.validate(process.env, {
|
|
abortEarly: false,
|
|
stripUnknown: false,
|
|
});
|
|
|
|
if (result.error) {
|
|
const messages = result.error.details.map(
|
|
(detail) => ` - ${detail.message}`,
|
|
);
|
|
logger.error({ errors: messages }, 'Environment validation failed');
|
|
|
|
if (process.env.NODE_ENV === 'production') {
|
|
process.exit(1);
|
|
} else {
|
|
logger.warn('Continuing with default values in non-production mode');
|
|
}
|
|
}
|
|
|
|
const resultValue: unknown = result.value;
|
|
return toValidatedEnvironment(isRecord(resultValue) ? resultValue : {});
|
|
}
|
|
|
|
export { envSchema, validateEnv };
|