Files
mpm/apps/platform-backend/src/modules/module.repository.ts
2026-10-08 21:13:57 +02:00

171 lines
5.8 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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<ModuleRecord[]> {
const result = await this.database.query<ModuleRow>(
`SELECT ${MODULE_COLUMNS} FROM modules ORDER BY created_at ASC`,
);
return result.rows.map((row) => this.mapRow(row));
}
async findByModuleId(moduleId: string): Promise<ModuleRecord | null> {
const result = await this.database.query<ModuleRow>(
`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<ModuleRecord | null> {
const result = await this.database.query<ModuleRow>(
`SELECT ${MODULE_COLUMNS} FROM modules WHERE slug = $1`,
[slug],
);
return result.rows[0] ? this.mapRow(result.rows[0]) : null;
}
async findByPort(port: number): Promise<ModuleRecord | null> {
const result = await this.database.query<ModuleRow>(
`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<ModuleRecord | null> {
const result = await this.database.query<ModuleRow>(
`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<ModuleRecord[]> {
const result = await this.database.query<ModuleRow>(
`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<ModuleRecord> {
const result = await this.database.query<ModuleRow>(
`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<void> {
await this.database.query(
'UPDATE modules SET status = $2, updated_at = now() WHERE id = $1',
[id, status],
);
}
async updateEnabled(id: string, enabled: boolean): Promise<void> {
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<void> {
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<void> {
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<void> {
await this.database.query(
'UPDATE modules SET configuration_ready = $2, updated_at = now() WHERE id = $1',
[id, ready],
);
}
}