feat: expose safe build metadata and verify foundation

This commit is contained in:
Bastian Wagner
2026-08-17 14:08:56 +02:00
parent 1957e3b337
commit 74ae283fea
4 changed files with 73 additions and 0 deletions

View File

@@ -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.

View File

@@ -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');
});
});

View File

@@ -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,
};
}
}

View File

@@ -0,0 +1,7 @@
import { Module } from '@nestjs/common';
import { VersionController } from './version.controller';
@Module({
controllers: [VersionController],
})
export class VersionModule {}