feat(api,web): eigene Module — Tabelle, API, Seitenleiste, Rahmen-Seite
- Tabelle CustomModule mit Zeilenschutz (tenant_isolation_policy), Migration 20260929120000 - API /custom-modules: Lesen für jeden Angemeldeten, Schreiben nur Administrator, nur https ohne Zugangsdaten - Seitenleiste zeigt eigene Module unter ihrer Kategorie, Rahmen-Seite mit Sandbox und „In neuem Tab öffnen“ - MODULE_CATEGORIES als gemeinsame Liste, Zugriffsklassifikation nachgemessen fortgeschrieben Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,85 @@
|
||||
import { Injectable, NotFoundException } from '@nestjs/common';
|
||||
import { PrismaService } from '../prisma/prisma.service';
|
||||
import { forTenant } from '../prisma/prisma-tenant.extension';
|
||||
import type { CreateCustomModuleDto, UpdateCustomModuleDto } from './dto/custom-module.dto';
|
||||
|
||||
/** Antwortfelder — genau diese, nichts anderes verlaesst den Dienst. */
|
||||
const CUSTOM_MODULE_SELECT = {
|
||||
id: true,
|
||||
name: true,
|
||||
url: true,
|
||||
category: true,
|
||||
createdAt: true,
|
||||
updatedAt: true,
|
||||
};
|
||||
|
||||
/**
|
||||
* Eigene Module (quick-260929-9wc): vom Administrator angelegte
|
||||
* Seitenleisten-Eintraege mit externer https-Adresse. `tenantId` kommt
|
||||
* ausschliesslich als Argument (aus `req.tenantId`), nie aus dem DTO. Je
|
||||
* Methode ein eigener `forTenant`-Klient; zusaetzlich pruefen
|
||||
* getOne/update/remove `row.tenantId` — zweites Netz, solange der
|
||||
* RLS-Schalter aus ist (Muster DashboardImage). Eine fremde oder unbekannte
|
||||
* id ergibt immer `NotFoundException`, nie einen Hinweis auf die Existenz.
|
||||
*/
|
||||
@Injectable()
|
||||
export class CustomModulesService {
|
||||
constructor(private readonly prisma: PrismaService) {}
|
||||
|
||||
async list(tenantId: string) {
|
||||
const tenantPrisma = forTenant(this.prisma, tenantId);
|
||||
return tenantPrisma.customModule.findMany({
|
||||
where: { tenantId },
|
||||
orderBy: { name: 'asc' },
|
||||
select: CUSTOM_MODULE_SELECT,
|
||||
});
|
||||
}
|
||||
|
||||
async getOne(tenantId: string, id: string) {
|
||||
const tenantPrisma = forTenant(this.prisma, tenantId);
|
||||
const row = await tenantPrisma.customModule.findUnique({
|
||||
where: { id },
|
||||
select: { ...CUSTOM_MODULE_SELECT, tenantId: true },
|
||||
});
|
||||
if (!row || row.tenantId !== tenantId) {
|
||||
throw new NotFoundException('Eigenes Modul nicht gefunden');
|
||||
}
|
||||
const { tenantId: _omit, ...result } = row;
|
||||
return result;
|
||||
}
|
||||
|
||||
async create(tenantId: string, dto: CreateCustomModuleDto) {
|
||||
const tenantPrisma = forTenant(this.prisma, tenantId);
|
||||
return tenantPrisma.customModule.create({
|
||||
data: { tenantId, name: dto.name, url: dto.url, category: dto.category },
|
||||
select: CUSTOM_MODULE_SELECT,
|
||||
});
|
||||
}
|
||||
|
||||
async update(tenantId: string, id: string, dto: UpdateCustomModuleDto) {
|
||||
const tenantPrisma = forTenant(this.prisma, tenantId);
|
||||
const existing = await tenantPrisma.customModule.findUnique({ where: { id } });
|
||||
if (!existing || existing.tenantId !== tenantId) {
|
||||
throw new NotFoundException('Eigenes Modul nicht gefunden');
|
||||
}
|
||||
const data: { name?: string; url?: string; category?: string } = {};
|
||||
if (dto.name !== undefined) data.name = dto.name;
|
||||
if (dto.url !== undefined) data.url = dto.url;
|
||||
if (dto.category !== undefined) data.category = dto.category;
|
||||
return tenantPrisma.customModule.update({
|
||||
where: { id },
|
||||
data,
|
||||
select: CUSTOM_MODULE_SELECT,
|
||||
});
|
||||
}
|
||||
|
||||
async remove(tenantId: string, id: string) {
|
||||
const tenantPrisma = forTenant(this.prisma, tenantId);
|
||||
const existing = await tenantPrisma.customModule.findUnique({ where: { id } });
|
||||
if (!existing || existing.tenantId !== tenantId) {
|
||||
throw new NotFoundException('Eigenes Modul nicht gefunden');
|
||||
}
|
||||
await tenantPrisma.customModule.delete({ where: { id } });
|
||||
return { deleted: true };
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user