67b50240d6
Aufgabe 3 — TDD zuerst (7 weitere Faelle in dashboard.service.spec.ts, 20 vorher/27 nach dieser Aufgabe), dann die Umstellung: - getSearchProviders/addSearchProvider/removeSearchProvider laufen ueber forTenant(); removeSearchProvider fuehrt Besitzpruefung UND Schreibzugriff ueber DENSELBEN gebundenen Klienten. Die drei Vorgabe-Suchmaschinen aus der Konstante bleiben unveraendert vorangestellt. - Der eine Katalogzugriff (this.prisma.module in getWidgets) bleibt begruendet ungebunden: Messung und Bedingung getrennt (Tabelle traegt heute keinen Zeilenschutz, wirkungslos statt katastrophal — katastrophal erst, wenn Etappe 3 eine Regel gibt), unter Berufung auf die bestehende Werkzeugpruefung module-tabelle-traegt-keinen-zeilenschutz statt einer neuen Behauptung. Ein Wachhund-Testfall haelt den Katalogzugriff aus dem Bindungsprotokoll heraus (und beweist zuerst, dass der Katalogpfad tatsaechlich durchlaufen wird, nicht nur theoretisch geprueft ist). - docs/mandantentrennung-zugriffsklassifikation.md an allen fuenf handgepflegten Stellen nachgezogen: vier Bestandsaufnahme-Zeilen (inkl. eigenstaendiger Nachpruefung der widerlegten SearchProvider-Praemisse), Uebersichtszeile (13/0 -> 1/12), Summenzeile (95/147), Klassen-Verteilung (unveraendert 63 Paare, ausdruecklich vermerkt), Hintergrunddienst- Abschnitt (dashboard hat keinen sechsten Fall, mit Messanweisung), "Was diese Etappe NICHT entscheidet" (dienst-interner forTenant()-Weg wie alle sieben Bereiche vor ihm). - .planning/WINDOWS.md traegt Eintrag #25 (offen, Tabelle + JSON): die beweisvernichtende Schleife (leeres Dashboard -> Neuaufbau -> automatisches Zurueckschreiben -> ueberschriebene Anordnung, Widget- Dubletten) samt der Vorabpruefung fuer Etappe 4 und dem Verweis auf #22 fuer die verwandte Eindeutigkeitsfrage. Zwei weitere Falsifizierungsnachweise durchgefuehrt: (1) den Katalogzugriff probeweise gebunden (tenantPrisma.module.findMany) — acht Tests werden rot mit "TypeError: Cannot read properties of undefined (reading 'findMany')", weil `module` bewusst nicht in der Testdouble-Bindungsliste steht; Rueckbau zurueckgenommen, 27/27 wieder gruen. (2) den Stand von dashboardLayout in der Klassifikationsdatei probeweise auf "ungebunden" gesetzt — rls-access-inventory.spec.ts wird rot mit "Abweichender Stand (Dokument vs. Quelltext): ... dokumentiert=ungebunden, gemessen=gebunden"; Ruecknahme, Testlauf wieder gruen (10/10). Baseline gehalten: 858 Tests / 56 Dateien gruen, Typpruefung sauber, Wegwerf-Werkzeug 87/87. Schalter bleibt aus.
127 lines
4.0 KiB
TypeScript
127 lines
4.0 KiB
TypeScript
import {
|
|
Body,
|
|
Controller,
|
|
Delete,
|
|
ForbiddenException,
|
|
Get,
|
|
Param,
|
|
Patch,
|
|
Post,
|
|
Put,
|
|
Req,
|
|
} from '@nestjs/common';
|
|
import { Request } from 'express';
|
|
import { DashboardService } from './dashboard.service';
|
|
import { CreateSearchProviderDto } from './dto/create-search-provider.dto';
|
|
import { CreateWidgetDto } from './dto/create-widget.dto';
|
|
import { SaveLayoutDto } from './dto/save-layout.dto';
|
|
import { UpdateWidgetConfigDto } from './dto/update-widget-config.dto';
|
|
|
|
/**
|
|
* REST controller for dashboard layout and widget instance management.
|
|
*
|
|
* All endpoints require JWT auth (global JwtAuthGuard).
|
|
* Every handler extracts userId and tenantId from the request
|
|
* and scopes all operations to the calling user (T-05-01, T-05-02).
|
|
*
|
|
* Routes:
|
|
* - GET /dashboard/layout — get user's saved layout
|
|
* - PUT /dashboard/layout — upsert user's layout
|
|
* - GET /dashboard/widgets — list user's widget instances
|
|
* - POST /dashboard/widgets — create a new widget instance
|
|
* - PATCH /dashboard/widgets/:id/config — update widget config
|
|
* - DELETE /dashboard/widgets/:id — remove a widget instance
|
|
* - GET /dashboard/search-providers — list default + user's custom providers
|
|
* - POST /dashboard/search-providers — create a custom search provider
|
|
* - DELETE /dashboard/search-providers/:id — remove a custom provider
|
|
*/
|
|
@Controller('dashboard')
|
|
export class DashboardController {
|
|
constructor(private readonly dashboardService: DashboardService) {}
|
|
|
|
private extractContext(req: Request) {
|
|
const userId = (req as any).user?.id;
|
|
const tenantId =
|
|
(req as any).tenantId ?? (req as any).user?.tenantId;
|
|
|
|
if (!tenantId) {
|
|
throw new ForbiddenException('No tenant context');
|
|
}
|
|
if (!userId) {
|
|
throw new ForbiddenException('No user context');
|
|
}
|
|
|
|
return { userId, tenantId };
|
|
}
|
|
|
|
@Get('layout')
|
|
async getLayout(@Req() req: Request) {
|
|
const { userId, tenantId } = this.extractContext(req);
|
|
return this.dashboardService.getLayout(userId, tenantId);
|
|
}
|
|
|
|
@Put('layout')
|
|
async saveLayout(@Req() req: Request, @Body() dto: SaveLayoutDto) {
|
|
const { userId, tenantId } = this.extractContext(req);
|
|
return this.dashboardService.saveLayout(userId, tenantId, dto);
|
|
}
|
|
|
|
@Get('widgets')
|
|
async getWidgets(@Req() req: Request) {
|
|
const { userId, tenantId } = this.extractContext(req);
|
|
const role = (req as any).user?.role;
|
|
return this.dashboardService.getWidgets(userId, tenantId, role);
|
|
}
|
|
|
|
@Post('widgets')
|
|
async addWidget(@Req() req: Request, @Body() dto: CreateWidgetDto) {
|
|
const { userId, tenantId } = this.extractContext(req);
|
|
return this.dashboardService.addWidget(userId, tenantId, dto);
|
|
}
|
|
|
|
@Patch('widgets/:id/config')
|
|
async updateWidgetConfig(
|
|
@Param('id') id: string,
|
|
@Req() req: Request,
|
|
@Body() dto: UpdateWidgetConfigDto,
|
|
) {
|
|
const { userId, tenantId } = this.extractContext(req);
|
|
return this.dashboardService.updateWidgetConfig(id, userId, tenantId, dto);
|
|
}
|
|
|
|
@Delete('widgets/:id')
|
|
async removeWidget(
|
|
@Param('id') id: string,
|
|
@Req() req: Request,
|
|
) {
|
|
const { userId, tenantId } = this.extractContext(req);
|
|
return this.dashboardService.removeWidget(id, userId, tenantId);
|
|
}
|
|
|
|
// --- Search Providers (05-02, D-15) ---
|
|
|
|
@Get('search-providers')
|
|
async getSearchProviders(@Req() req: Request) {
|
|
const { userId, tenantId } = this.extractContext(req);
|
|
return this.dashboardService.getSearchProviders(userId, tenantId);
|
|
}
|
|
|
|
@Post('search-providers')
|
|
async addSearchProvider(
|
|
@Req() req: Request,
|
|
@Body() dto: CreateSearchProviderDto,
|
|
) {
|
|
const { userId, tenantId } = this.extractContext(req);
|
|
return this.dashboardService.addSearchProvider(userId, tenantId, dto);
|
|
}
|
|
|
|
@Delete('search-providers/:id')
|
|
async removeSearchProvider(
|
|
@Param('id') id: string,
|
|
@Req() req: Request,
|
|
) {
|
|
const { userId, tenantId } = this.extractContext(req);
|
|
return this.dashboardService.removeSearchProvider(id, userId, tenantId);
|
|
}
|
|
}
|