feat(quick-260910-krx): Suchmaschinen gebunden, Katalog begruendet offen, Klassifikation nachgezogen
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.
This commit is contained in:
@@ -274,9 +274,13 @@ export class DashboardService {
|
||||
/**
|
||||
* Returns the three default providers merged with any user-custom providers.
|
||||
* Defaults are always returned even with an empty DB (no seed migration needed).
|
||||
* The three defaults come from the TypeScript constant above (decision
|
||||
* 05-02), never from the database — they are unaffected by the binding
|
||||
* below and are always prepended unchanged.
|
||||
*/
|
||||
async getSearchProviders(userId: string) {
|
||||
const custom = await this.prisma.searchProvider.findMany({
|
||||
async getSearchProviders(userId: string, tenantId: string) {
|
||||
const tenantPrisma = forTenant(this.prisma, tenantId);
|
||||
const custom = await tenantPrisma.searchProvider.findMany({
|
||||
where: { userId },
|
||||
orderBy: { createdAt: 'asc' },
|
||||
});
|
||||
@@ -285,14 +289,19 @@ export class DashboardService {
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a user-custom search provider.
|
||||
* Creates a user-custom search provider. `tenantId` stays a required
|
||||
* parameter of this method — the only write path this model has (260910-krx,
|
||||
* Aufgabe 1, Befund F, WINDOWS #19): no application path exists that
|
||||
* creates a tenant-less row, which is why the RLS rule on `SearchProvider`
|
||||
* was deliberately left unchanged/strict in migration 20260910120000.
|
||||
*/
|
||||
async addSearchProvider(
|
||||
userId: string,
|
||||
tenantId: string,
|
||||
dto: CreateSearchProviderDto,
|
||||
) {
|
||||
return this.prisma.searchProvider.create({
|
||||
const tenantPrisma = forTenant(this.prisma, tenantId);
|
||||
return tenantPrisma.searchProvider.create({
|
||||
data: {
|
||||
userId,
|
||||
tenantId,
|
||||
@@ -305,11 +314,14 @@ export class DashboardService {
|
||||
|
||||
/**
|
||||
* Removes a user-custom search provider.
|
||||
* Verifies ownership — default providers (userId null) cannot be deleted (T-05-07).
|
||||
* Verifies ownership — default providers (userId null) cannot be deleted
|
||||
* (T-05-07) — REAL, same reasoning as `updateWidgetConfig`/`removeWidget`
|
||||
* above: both queries run over the SAME bound client and tenant id.
|
||||
*/
|
||||
async removeSearchProvider(id: string, userId: string) {
|
||||
async removeSearchProvider(id: string, userId: string, tenantId: string) {
|
||||
const tenantPrisma = forTenant(this.prisma, tenantId);
|
||||
// Default providers have hardcoded IDs that won't exist in DB
|
||||
const provider = await this.prisma.searchProvider.findUnique({
|
||||
const provider = await tenantPrisma.searchProvider.findUnique({
|
||||
where: { id },
|
||||
});
|
||||
|
||||
@@ -319,8 +331,24 @@ export class DashboardService {
|
||||
);
|
||||
}
|
||||
|
||||
return this.prisma.searchProvider.delete({
|
||||
return tenantPrisma.searchProvider.delete({
|
||||
where: { id },
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// --- Modulkatalog: bewusst ungebunden (260910-krx, Aufgabe 3) --------------
|
||||
//
|
||||
// Der eine verbleibende ungebundene Modellzugriff dieser Datei (das
|
||||
// `module`-Modell in `getWidgets`, ueber den ungebundenen Basisclient)
|
||||
// betrifft den plattformweiten Modulkatalog (`Module`).
|
||||
// MESSUNG (rls-scratch-check.mjs, Pruefung `module-tabelle-traegt-keinen-
|
||||
// zeilenschutz`, uebernommen aus dem Bereich `module-registry`, 260910-exd
|
||||
// Befund E): die Tabelle traegt heute KEINEN Zeilenschutz — `pg_class.
|
||||
// relrowsecurity` ist `false`, eine Bindung waere heute WIRKUNGSLOS, nicht
|
||||
// katastrophal. BEDINGUNG: sie wuerde katastrophal, WENN Etappe 3 dieser
|
||||
// Tabelle eine Regel gibt — dann verschwaende der gesamte Katalog fuer jeden
|
||||
// Mandanten. Die Katalogaufloesung, die dieser Dienst fuer den Widget-
|
||||
// Modulfilter aufruft (`ModuleAccessService.getAccessibleModuleIds`), bindet
|
||||
// bereits seit 260910-exd in ihrem eigenen Dienst — dieser Zugriff wird hier
|
||||
// NICHT ein zweites Mal gebunden.
|
||||
|
||||
Reference in New Issue
Block a user