171 lines
5.8 KiB
TypeScript
171 lines
5.8 KiB
TypeScript
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],
|
||
);
|
||
}
|
||
}
|