Files
tessera-ctl/apps/api/src/proxmox/proxmox.controller.ts
T
schalli 710034c80a fix(260923-ku6): Verbindungstest prueft Formularwerte statt gespeicherten Stand
Nachbesserung aus dem Browser-Rundgang zu 260923-dhh (Befund 1): "Verbindung
testen" pruefte bislang immer den in der Datenbank gespeicherten Server, nicht
das ungespeicherte Formular. Eine im Formular abgeschaltete Zertifikatspruefung
oder ein neu eingetipptes Token-Geheimnis wurden dadurch beim Test ignoriert
und erst nach "Speichern" wirksam — eine Falle fuer genau den Ablauf, den
Nutzer instinktiv waehlen (eintippen, testen, dann erst speichern).

Neues `TestProxmoxServerDto` plus Merge-Baustein `resolveEffectiveTestServer`
in `ProxmoxService`: normale Felder folgen dem Formular (auch wenn absichtlich
geleert), Geheimnisfelder folgen der bestehenden "leer -> gespeicherten Wert
behalten"-Regel, weil `ServerForm` sie beim Laden nie aus der Datenbank
vorbefuellt. Neue Route `POST servers/test` (ohne `:id`) deckt die Neuanlage
ab, wo es noch keinen gespeicherten Server gibt. Der Testen-Knopf steht jetzt
immer zur Verfuegung, nicht mehr nur nach dem ersten Speichern.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-23 15:11:56 +02:00

124 lines
4.3 KiB
TypeScript

import {
Body,
Controller,
Delete,
ForbiddenException,
Get,
Param,
Post,
Put,
Req,
} from '@nestjs/common';
import { Role } from '@prisma/client';
import { Roles } from '../auth/decorators/roles.decorator';
import type { AuthenticatedRequest } from '../auth/types/auth-user';
import { UseModule } from '../module-registry/module.guard';
import {
CreateProxmoxServerDto,
TestProxmoxServerDto,
UpdateProxmoxServerDto,
} from './dto/proxmox-server.dto';
import { ProxmoxSchedulerService } from './proxmox-scheduler.service';
import { ProxmoxService } from './proxmox.service';
/**
* `@UseModule('proxmox')` auf Klassenebene (D-09, Vorbild
* `domaincheck.controller.ts`) — Aktivierung UND Freigabe. `tenantId` kommt
* ausschliesslich aus `req.tenantId` (gesetzt vom `TenantGuard`), nie aus
* Body oder Query. Lesen (`GET servers`) steht jedem Benutzer mit
* Modulzugriff offen; Schreiben (`POST servers`, `POST servers/test`,
* `POST servers/:id/poll`, `POST servers/:id/test`) zusaetzlich
* `@Roles(ADMIN, SUPER_ADMIN)` (T-DHH-05). `servers/test` (statisch, zwei
* Segmente) und `servers/:id/test` (drei Segmente) ueberschneiden sich
* nicht — beide POST, aber unterschiedliche Segmentzahl, deshalb keine
* Reihenfolge-Abhaengigkeit (anders als `GET :id` vs. statische Routen).
*/
@Controller('modules/proxmox')
@UseModule('proxmox')
export class ProxmoxController {
constructor(
private readonly proxmoxService: ProxmoxService,
private readonly scheduler: ProxmoxSchedulerService,
) {}
private requireTenantId(req: AuthenticatedRequest): string {
const tenantId = req.tenantId;
if (!tenantId) {
throw new ForbiddenException('Kein Mandantenkontext');
}
return tenantId;
}
@Get('servers')
async list(@Req() req: AuthenticatedRequest) {
return this.proxmoxService.listWithStatus(this.requireTenantId(req));
}
@Post('servers')
@Roles(Role.ADMIN, Role.SUPER_ADMIN)
async create(@Req() req: AuthenticatedRequest, @Body() dto: CreateProxmoxServerDto) {
const tenantId = this.requireTenantId(req);
const created = await this.proxmoxService.createServer(tenantId, dto);
// Planer sofort nachziehen — ohne Neustart (Aufgabe 4, Vorbild DkvController).
await this.scheduler.refreshTenant(tenantId);
return created;
}
@Put('servers/:id')
@Roles(Role.ADMIN, Role.SUPER_ADMIN)
async update(
@Req() req: AuthenticatedRequest,
@Param('id') id: string,
@Body() dto: UpdateProxmoxServerDto,
) {
const tenantId = this.requireTenantId(req);
const updated = await this.proxmoxService.updateServer(tenantId, id, dto);
await this.scheduler.refreshTenant(tenantId);
return updated;
}
@Delete('servers/:id')
@Roles(Role.ADMIN, Role.SUPER_ADMIN)
async remove(@Req() req: AuthenticatedRequest, @Param('id') id: string) {
const tenantId = this.requireTenantId(req);
const deleted = await this.proxmoxService.deleteServer(tenantId, id);
await this.scheduler.refreshTenant(tenantId);
return { deleted };
}
@Post('servers/:id/poll')
@Roles(Role.ADMIN, Role.SUPER_ADMIN)
async poll(@Req() req: AuthenticatedRequest, @Param('id') id: string) {
return this.proxmoxService.pollServer(this.requireTenantId(req), id);
}
/**
* Verbindungstest fuer einen gespeicherten Server (Aufgabe 4, `<behavior>`;
* Nachbesserung Befund 1: `dto` traegt den aktuellen Formularstand,
* `ProxmoxService.testConnection` prueft diesen statt blind des
* gespeicherten Stands). Liefert bei Erfolg eine Erfolgsmeldung und bei
* Misserfolg einen der sieben Fehlerschluessel samt kurzer Ergaenzung,
* OHNE den Zwischenlagerstand zu ueberschreiben.
*/
@Post('servers/:id/test')
@Roles(Role.ADMIN, Role.SUPER_ADMIN)
async test(
@Req() req: AuthenticatedRequest,
@Param('id') id: string,
@Body() dto: TestProxmoxServerDto,
) {
return this.proxmoxService.testConnection(this.requireTenantId(req), id, dto);
}
/**
* Verbindungstest waehrend der Neuanlage (Nachbesserung Befund 1): es gibt
* noch keinen gespeicherten Server, `dto` ist deshalb die einzige Quelle.
*/
@Post('servers/test')
@Roles(Role.ADMIN, Role.SUPER_ADMIN)
async testDraft(@Req() req: AuthenticatedRequest, @Body() dto: TestProxmoxServerDto) {
this.requireTenantId(req);
return this.proxmoxService.testDraftConnection(dto);
}
}