import { Injectable } from '@nestjs/common'; import { DatabaseService } from '../database/database.service'; import type { ModuleConfigurationField, ModuleManifest, ModuleRecord, ModuleStatus } from './manifest.types'; interface ModuleRow { id: string; module_id: string; name: string; slug: string; version: string; description: string; author: string; path: string; status: ModuleStatus; internal_port: number; healthcheck_url: string; enabled: boolean; created_at: Date; updated_at: Date; compose_file: string | null; app_service: string | null; configuration_schema: ModuleConfigurationField[]; configuration_ready: boolean; } const MODULE_COLUMNS = `id, module_id, name, slug, version, description, author, path, status, internal_port, healthcheck_url, enabled, created_at, updated_at, compose_file, app_service, configuration_schema, configuration_ready`; /** * Modul-Repository (Infrastructure): Datenbankzugriffe für die Modul-Registry. * Keine Business-Logik – nur parametrisierten Datenzugriff. */ @Injectable() export class ModuleRepository { constructor(private readonly database: DatabaseService) {} async list(): Promise { const result = await this.database.query( `SELECT ${MODULE_COLUMNS} FROM modules ORDER BY created_at ASC`, ); return result.rows.map((row) => this.mapRow(row)); } async findByModuleId(moduleId: string): Promise { const result = await this.database.query( `SELECT ${MODULE_COLUMNS} FROM modules WHERE module_id = $1`, [moduleId], ); return result.rows[0] ? this.mapRow(result.rows[0]) : null; } async findBySlug(slug: string): Promise { const result = await this.database.query( `SELECT ${MODULE_COLUMNS} FROM modules WHERE slug = $1`, [slug], ); return result.rows[0] ? this.mapRow(result.rows[0]) : null; } async findByPort(port: number): Promise { const result = await this.database.query( `SELECT ${MODULE_COLUMNS} FROM modules WHERE internal_port = $1`, [port], ); return result.rows[0] ? this.mapRow(result.rows[0]) : null; } async findById(id: string): Promise { const result = await this.database.query( `SELECT ${MODULE_COLUMNS} FROM modules WHERE id = $1`, [id], ); return result.rows[0] ? this.mapRow(result.rows[0]) : null; } async findByStatus(statuses: readonly ModuleStatus[]): Promise { const result = await this.database.query( `SELECT ${MODULE_COLUMNS} FROM modules WHERE status = ANY($1::text[])`, [statuses], ); return result.rows.map((row) => this.mapRow(row)); } async create(manifest: ModuleManifest, directory: string): Promise { const result = await this.database.query( `INSERT INTO modules (module_id, name, slug, version, description, author, path, status, internal_port, healthcheck_url, compose_file, app_service, configuration_schema, configuration_ready) VALUES ($1, $2, $3, $4, $5, $6, $7, 'INSTALLED', $8, $9, $10, $11, $12::jsonb, $13) RETURNING ${MODULE_COLUMNS}`, [ manifest.id, manifest.name, manifest.slug, manifest.version, manifest.description, manifest.author, directory, manifest.port, manifest.healthcheck, manifest.composeFile ?? null, manifest.appService ?? null, JSON.stringify(manifest.configuration), manifest.configuration.every((field) => !field.required || field.defaultValue !== undefined), ], ); return this.mapRow(result.rows[0]); } async updateStatus(id: string, status: ModuleStatus): Promise { await this.database.query( 'UPDATE modules SET status = $2, updated_at = now() WHERE id = $1', [id, status], ); } async updateEnabled(id: string, enabled: boolean): Promise { await this.database.query( 'UPDATE modules SET enabled = $2, updated_at = now() WHERE id = $1', [id, enabled], ); } async updateManifest(id: string, manifest: ModuleManifest, configurationReady: boolean): Promise { await this.database.query( `UPDATE modules SET name = $2, version = $3, description = $4, author = $5, internal_port = $6, healthcheck_url = $7, compose_file = $8, app_service = $9, configuration_schema = $10::jsonb, configuration_ready = $11, updated_at = now() WHERE id = $1`, [id, manifest.name, manifest.version, manifest.description, manifest.author, manifest.port, manifest.healthcheck, manifest.composeFile ?? null, manifest.appService ?? null, JSON.stringify(manifest.configuration), configurationReady], ); } async delete(id: string): Promise { await this.database.query('DELETE FROM modules WHERE id = $1', [id]); } private mapRow(row: ModuleRow): ModuleRecord { return { id: row.id, moduleId: row.module_id, name: row.name, slug: row.slug, version: row.version, description: row.description, author: row.author, path: row.path, status: row.status, internalPort: row.internal_port, healthcheckUrl: row.healthcheck_url, enabled: row.enabled, createdAt: row.created_at, updatedAt: row.updated_at, composeFile: row.compose_file, appService: row.app_service, configuration: row.configuration_schema, configurationReady: row.configuration_ready, }; } async updateConfigurationReady(id: string, ready: boolean): Promise { await this.database.query( 'UPDATE modules SET configuration_ready = $2, updated_at = now() WHERE id = $1', [id, ready], ); } }