From 74ae283feaca3946a0902d944987619e65d2534b Mon Sep 17 00:00:00 2001 From: Bastian Wagner Date: Mon, 17 Aug 2026 14:08:56 +0200 Subject: [PATCH] feat: expose safe build metadata and verify foundation --- README.md | 8 +++++ .../src/version/version.controller.spec.ts | 33 +++++++++++++++++++ .../api/src/version/version.controller.ts | 25 ++++++++++++++ .../apps/api/src/version/version.module.ts | 7 ++++ 4 files changed, 73 insertions(+) create mode 100644 backend/apps/api/src/version/version.controller.spec.ts create mode 100644 backend/apps/api/src/version/version.controller.ts create mode 100644 backend/apps/api/src/version/version.module.ts diff --git a/README.md b/README.md index ec592be..83355c1 100644 --- a/README.md +++ b/README.md @@ -51,3 +51,11 @@ scripts/teamcity/rollback.sh # redeploy the previous IMAGE_TAG ## Production topology Production Docker Compose (`compose.yml`) publishes **exactly one** host port, on the `edge` (Nginx) service, which serves the built Angular app and reverse-proxies `/api/*` and `/health/*` to the internal `api` service. `api`, `worker`, `postgres`, and `redis` are reachable only over the internal Docker network. See `docs/architecture/deployment.md` for the full contract and `scripts/teamcity/` for the TeamCity-invoked build/deploy/rollback scripts. + +## Phase 01 status: foundation complete + +- `GET /health/live` — process liveness only, no dependency checks. +- `GET /health/ready` — validates PostgreSQL and Redis connectivity. +- `GET /api/v1/version` — safe build metadata only (`appVersion`, `teamCityBuildNumber`, `sourceRevision`); never database/Redis URLs. +- The compiled no-op migration entry point (`backend/dist/apps/api/src/migration.js`) gives `deploy.sh` a stable container command contract; Phase 02 replaces its body with the real versioned migration runner. +- Verified end-to-end: `docker compose -f compose.yml up` with a real TLS certificate serves `/health/live`, `/health/ready`, `/`, and `/api/v1/version` through the single published edge port, with `postgres`/`redis`/`api`/`worker` unreachable from the host. diff --git a/backend/apps/api/src/version/version.controller.spec.ts b/backend/apps/api/src/version/version.controller.spec.ts new file mode 100644 index 0000000..4fad16c --- /dev/null +++ b/backend/apps/api/src/version/version.controller.spec.ts @@ -0,0 +1,33 @@ +import { Test } from '@nestjs/testing'; +import { + APP_ENVIRONMENT, + AppEnvironment, +} from '../../../../libs/configuration/src'; +import { VersionController } from './version.controller'; + +describe('VersionController', () => { + it('returns only safe build metadata', async () => { + const environment: AppEnvironment = { + databaseUrl: 'postgresql://u:p@postgres:5432/db', + redisUrl: 'redis://redis:6379', + appVersion: '1.0.0', + teamCityBuildNumber: '123', + sourceRevision: 'abc123', + }; + + const moduleRef = await Test.createTestingModule({ + controllers: [VersionController], + providers: [{ provide: APP_ENVIRONMENT, useValue: environment }], + }).compile(); + + const response = moduleRef.get(VersionController).version(); + + expect(response).toEqual({ + appVersion: '1.0.0', + teamCityBuildNumber: '123', + sourceRevision: 'abc123', + }); + expect(response).not.toHaveProperty('databaseUrl'); + expect(response).not.toHaveProperty('redisUrl'); + }); +}); diff --git a/backend/apps/api/src/version/version.controller.ts b/backend/apps/api/src/version/version.controller.ts new file mode 100644 index 0000000..fcbba20 --- /dev/null +++ b/backend/apps/api/src/version/version.controller.ts @@ -0,0 +1,25 @@ +import { Controller, Get, Inject } from '@nestjs/common'; +import { APP_ENVIRONMENT } from '../../../../libs/configuration/src'; +import type { AppEnvironment } from '../../../../libs/configuration/src'; + +interface SafeVersionInfo { + appVersion: string; + teamCityBuildNumber: string; + sourceRevision: string; +} + +@Controller('version') +export class VersionController { + constructor( + @Inject(APP_ENVIRONMENT) private readonly environment: AppEnvironment, + ) {} + + @Get() + version(): SafeVersionInfo { + return { + appVersion: this.environment.appVersion, + teamCityBuildNumber: this.environment.teamCityBuildNumber, + sourceRevision: this.environment.sourceRevision, + }; + } +} diff --git a/backend/apps/api/src/version/version.module.ts b/backend/apps/api/src/version/version.module.ts new file mode 100644 index 0000000..9d7e584 --- /dev/null +++ b/backend/apps/api/src/version/version.module.ts @@ -0,0 +1,7 @@ +import { Module } from '@nestjs/common'; +import { VersionController } from './version.controller'; + +@Module({ + controllers: [VersionController], +}) +export class VersionModule {}