diff --git a/backend/apps/api/src/api.module.ts b/backend/apps/api/src/api.module.ts index 763be8e..effae60 100644 --- a/backend/apps/api/src/api.module.ts +++ b/backend/apps/api/src/api.module.ts @@ -1,9 +1,10 @@ import { Module } from '@nestjs/common'; +import { ConfigurationModule } from '../../../libs/configuration/src'; import { AppController } from './app.controller'; import { AppService } from './app.service'; @Module({ - imports: [], + imports: [ConfigurationModule], controllers: [AppController], providers: [AppService], }) diff --git a/backend/apps/api/test/jest-e2e.json b/backend/apps/api/test/jest-e2e.json index a750e43..be300d0 100644 --- a/backend/apps/api/test/jest-e2e.json +++ b/backend/apps/api/test/jest-e2e.json @@ -3,6 +3,7 @@ "rootDir": "../../..", "testEnvironment": "node", "testRegex": "apps/api/test/.*\\.e2e-spec\\.ts$", + "setupFiles": ["/test/setup-env.ts"], "transform": { "^.+\\.(t|j)s$": "ts-jest" } diff --git a/backend/apps/api/tsconfig.app.json b/backend/apps/api/tsconfig.app.json index 9e5319c..5804d97 100644 --- a/backend/apps/api/tsconfig.app.json +++ b/backend/apps/api/tsconfig.app.json @@ -1,8 +1,7 @@ { "extends": "../../tsconfig.json", "compilerOptions": { - "declaration": false, - "outDir": "../../dist/apps/api" + "declaration": false }, "exclude": ["node_modules", "dist", "test", "**/*.spec.ts"] } diff --git a/backend/apps/worker/src/worker.module.ts b/backend/apps/worker/src/worker.module.ts index a11e106..9a91269 100644 --- a/backend/apps/worker/src/worker.module.ts +++ b/backend/apps/worker/src/worker.module.ts @@ -1,4 +1,7 @@ import { Module } from '@nestjs/common'; +import { ConfigurationModule } from '../../../libs/configuration/src'; -@Module({}) +@Module({ + imports: [ConfigurationModule], +}) export class WorkerModule {} diff --git a/backend/apps/worker/tsconfig.app.json b/backend/apps/worker/tsconfig.app.json index 409991c..5804d97 100644 --- a/backend/apps/worker/tsconfig.app.json +++ b/backend/apps/worker/tsconfig.app.json @@ -1,8 +1,7 @@ { "extends": "../../tsconfig.json", "compilerOptions": { - "declaration": false, - "outDir": "../../dist/apps/worker" + "declaration": false }, "exclude": ["node_modules", "dist", "test", "**/*.spec.ts"] } diff --git a/backend/libs/configuration/src/configuration.module.ts b/backend/libs/configuration/src/configuration.module.ts new file mode 100644 index 0000000..acff124 --- /dev/null +++ b/backend/libs/configuration/src/configuration.module.ts @@ -0,0 +1,9 @@ +import { Global, Module } from '@nestjs/common'; +import { appEnvironmentProvider } from './environment'; + +@Global() +@Module({ + providers: [appEnvironmentProvider], + exports: [appEnvironmentProvider], +}) +export class ConfigurationModule {} diff --git a/backend/libs/configuration/src/environment.spec.ts b/backend/libs/configuration/src/environment.spec.ts new file mode 100644 index 0000000..101c5d6 --- /dev/null +++ b/backend/libs/configuration/src/environment.spec.ts @@ -0,0 +1,25 @@ +import { loadEnvironment } from './environment'; + +describe('loadEnvironment', () => { + it('requires database and redis URLs', () => { + expect(() => loadEnvironment({})).toThrow('DATABASE_URL'); + }); + + it('returns build metadata without secrets', () => { + expect( + loadEnvironment({ + DATABASE_URL: 'postgresql://u:p@postgres:5432/db', + REDIS_URL: 'redis://redis:6379', + APP_VERSION: '1.2.3', + TEAMCITY_BUILD_NUMBER: '42', + SOURCE_REVISION: 'abc123', + }), + ).toEqual({ + databaseUrl: 'postgresql://u:p@postgres:5432/db', + redisUrl: 'redis://redis:6379', + appVersion: '1.2.3', + teamCityBuildNumber: '42', + sourceRevision: 'abc123', + }); + }); +}); diff --git a/backend/libs/configuration/src/environment.ts b/backend/libs/configuration/src/environment.ts new file mode 100644 index 0000000..40b483a --- /dev/null +++ b/backend/libs/configuration/src/environment.ts @@ -0,0 +1,30 @@ +export interface AppEnvironment { + databaseUrl: string; + redisUrl: string; + appVersion: string; + teamCityBuildNumber: string; + sourceRevision: string; +} + +function required(env: NodeJS.ProcessEnv, name: string): string { + const value = env[name]?.trim(); + if (!value) throw new Error(`Missing required environment variable: ${name}`); + return value; +} + +export function loadEnvironment(env: NodeJS.ProcessEnv): AppEnvironment { + return { + databaseUrl: required(env, 'DATABASE_URL'), + redisUrl: required(env, 'REDIS_URL'), + appVersion: env.APP_VERSION?.trim() || 'dev', + teamCityBuildNumber: env.TEAMCITY_BUILD_NUMBER?.trim() || 'local', + sourceRevision: env.SOURCE_REVISION?.trim() || 'local', + }; +} + +export const APP_ENVIRONMENT = Symbol('APP_ENVIRONMENT'); + +export const appEnvironmentProvider = { + provide: APP_ENVIRONMENT, + useFactory: () => loadEnvironment(process.env), +}; diff --git a/backend/libs/configuration/src/index.ts b/backend/libs/configuration/src/index.ts new file mode 100644 index 0000000..64402a0 --- /dev/null +++ b/backend/libs/configuration/src/index.ts @@ -0,0 +1,2 @@ +export * from './environment'; +export * from './configuration.module'; diff --git a/backend/nest-cli.json b/backend/nest-cli.json index 6666174..845d955 100644 --- a/backend/nest-cli.json +++ b/backend/nest-cli.json @@ -5,7 +5,7 @@ "root": "apps/api", "sourceRoot": "apps/api/src", "compilerOptions": { - "deleteOutDir": true + "deleteOutDir": false }, "projects": { "api": { diff --git a/backend/package.json b/backend/package.json index e6d1b2f..77a5fb3 100644 --- a/backend/package.json +++ b/backend/package.json @@ -13,8 +13,8 @@ "start": "nest start api", "start:dev": "nest start api --watch", "start:debug": "nest start api --debug --watch", - "start:api": "node dist/apps/api/main.js", - "start:worker": "node dist/apps/worker/main.js", + "start:api": "node dist/apps/api/src/main.js", + "start:worker": "node dist/apps/worker/src/main.js", "lint": "eslint \"{apps,libs}/**/*.ts\" --fix", "test": "jest --runInBand", "test:watch": "jest --watch", @@ -63,13 +63,10 @@ "rootDir": ".", "testRegex": ".*\\.spec\\.ts$", "testPathIgnorePatterns": ["/node_modules/", "/dist/"], + "setupFiles": ["/test/setup-env.ts"], "transform": { "^.+\\.(t|j)s$": "ts-jest" }, - "moduleNameMapper": { - "^@travel/configuration$": "/libs/configuration/src", - "^@travel/infrastructure$": "/libs/infrastructure/src" - }, "collectCoverageFrom": [ "apps/**/*.(t|j)s", "libs/**/*.(t|j)s" diff --git a/backend/test/setup-env.ts b/backend/test/setup-env.ts new file mode 100644 index 0000000..4c2b285 --- /dev/null +++ b/backend/test/setup-env.ts @@ -0,0 +1,2 @@ +process.env.DATABASE_URL ??= 'postgresql://test:test@localhost:5432/test'; +process.env.REDIS_URL ??= 'redis://localhost:6379'; diff --git a/backend/tsconfig.json b/backend/tsconfig.json index dac7ed8..526b4aa 100644 --- a/backend/tsconfig.json +++ b/backend/tsconfig.json @@ -13,6 +13,7 @@ "target": "ES2023", "sourceMap": true, "outDir": "./dist", + "rootDir": ".", "baseUrl": "./", "incremental": true, "skipLibCheck": true, @@ -20,10 +21,6 @@ "forceConsistentCasingInFileNames": true, "noImplicitAny": false, "strictBindCallApply": false, - "noFallthroughCasesInSwitch": false, - "paths": { - "@travel/configuration": ["libs/configuration/src"], - "@travel/infrastructure": ["libs/infrastructure/src"] - } + "noFallthroughCasesInSwitch": false } }