98 lines
3.8 KiB
TypeScript
98 lines
3.8 KiB
TypeScript
import { Pool, type PoolConfig } from 'pg';
|
|
import type { AssessorRepository } from '../src/persistence/assessor-repository';
|
|
import { LocalAssessorRepository } from '../src/persistence/local-assessor-repository';
|
|
import { runDatabaseMigrations } from './postgres/migration-runner';
|
|
import { PostgresAssessorRepository } from './postgres/postgres-assessor-repository';
|
|
|
|
export type PersistenceKind = 'memory' | 'postgresql';
|
|
|
|
export interface RepositoryRuntime {
|
|
repository: AssessorRepository;
|
|
persistence: PersistenceKind;
|
|
close(): Promise<void>;
|
|
}
|
|
|
|
export interface RepositoryEnvironment {
|
|
ASSESSOR_DATABASE_URL?: string;
|
|
ASSESSOR_DATABASE_SSL?: string;
|
|
ASSESSOR_DATABASE_POOL_SIZE?: string;
|
|
ASSESSOR_POSTGRES_DB?: string;
|
|
ASSESSOR_POSTGRES_USER?: string;
|
|
ASSESSOR_POSTGRES_PASSWORD?: string;
|
|
ASSESSOR_POSTGRES_PORT?: string;
|
|
}
|
|
|
|
function parsePoolSize(value: string | undefined): number {
|
|
if (value === undefined || value.trim() === '') return 10;
|
|
const parsed = Number(value);
|
|
if (!Number.isInteger(parsed) || parsed < 1 || parsed > 20) {
|
|
throw new Error('ASSESSOR_DATABASE_POOL_SIZE muss eine Ganzzahl zwischen 1 und 20 sein.');
|
|
}
|
|
return parsed;
|
|
}
|
|
|
|
function sslConfiguration(value: string | undefined): PoolConfig['ssl'] {
|
|
const mode = value?.trim().toLowerCase() || 'disable';
|
|
if (mode === 'disable') return false;
|
|
if (mode === 'require') return { rejectUnauthorized: true };
|
|
throw new Error('ASSESSOR_DATABASE_SSL muss disable oder require sein.');
|
|
}
|
|
|
|
export async function createRepositoryRuntime(environment: RepositoryEnvironment = {
|
|
ASSESSOR_DATABASE_URL: process.env.ASSESSOR_DATABASE_URL,
|
|
ASSESSOR_DATABASE_SSL: process.env.ASSESSOR_DATABASE_SSL,
|
|
ASSESSOR_DATABASE_POOL_SIZE: process.env.ASSESSOR_DATABASE_POOL_SIZE,
|
|
ASSESSOR_POSTGRES_DB: process.env.ASSESSOR_POSTGRES_DB,
|
|
ASSESSOR_POSTGRES_USER: process.env.ASSESSOR_POSTGRES_USER,
|
|
ASSESSOR_POSTGRES_PASSWORD: process.env.ASSESSOR_POSTGRES_PASSWORD,
|
|
ASSESSOR_POSTGRES_PORT: process.env.ASSESSOR_POSTGRES_PORT
|
|
}): Promise<RepositoryRuntime> {
|
|
const explicitConnectionString = environment.ASSESSOR_DATABASE_URL?.trim();
|
|
const localPassword = environment.ASSESSOR_POSTGRES_PASSWORD?.trim();
|
|
const connectionString = explicitConnectionString || (localPassword
|
|
? `postgresql://${encodeURIComponent(environment.ASSESSOR_POSTGRES_USER?.trim() || 'assessor')}:${encodeURIComponent(localPassword)}@127.0.0.1:${environment.ASSESSOR_POSTGRES_PORT?.trim() || '5432'}/${encodeURIComponent(environment.ASSESSOR_POSTGRES_DB?.trim() || 'assessor')}`
|
|
: undefined);
|
|
if (!connectionString) {
|
|
return {
|
|
repository: new LocalAssessorRepository(),
|
|
persistence: 'memory',
|
|
async close() {}
|
|
};
|
|
}
|
|
|
|
let databaseUrl: URL;
|
|
try {
|
|
databaseUrl = new URL(connectionString);
|
|
} catch {
|
|
throw new Error('ASSESSOR_DATABASE_URL ist keine gültige PostgreSQL-URL.');
|
|
}
|
|
if (!['postgres:', 'postgresql:'].includes(databaseUrl.protocol)) {
|
|
throw new Error('ASSESSOR_DATABASE_URL muss postgres:// oder postgresql:// verwenden.');
|
|
}
|
|
const loopbackHosts = new Set(['localhost', '127.0.0.1', '[::1]']);
|
|
const sslMode = environment.ASSESSOR_DATABASE_SSL?.trim()
|
|
|| (loopbackHosts.has(databaseUrl.hostname) ? 'disable' : 'require');
|
|
|
|
const pool = new Pool({
|
|
connectionString,
|
|
ssl: sslConfiguration(sslMode),
|
|
max: parsePoolSize(environment.ASSESSOR_DATABASE_POOL_SIZE),
|
|
connectionTimeoutMillis: 5000,
|
|
idleTimeoutMillis: 30000,
|
|
application_name: 'assessor'
|
|
});
|
|
try {
|
|
await pool.query('SELECT 1');
|
|
await runDatabaseMigrations(pool);
|
|
const repository = await PostgresAssessorRepository.create(pool);
|
|
return {
|
|
repository,
|
|
persistence: 'postgresql',
|
|
close: () => pool.end()
|
|
};
|
|
} catch (error) {
|
|
await pool.end().catch(() => undefined);
|
|
throw error;
|
|
}
|
|
}
|