Restrict cockpit summary to own data + shared areas for tenants

This commit is contained in:
Giuseppe Lombardo 2026-08-12 18:24:28 +00:00
parent ffea782e96
commit efcbcab0d8

View File

@ -1,56 +1,112 @@
import { Router, Request, Response } from 'express'; import { Router, Response } from 'express';
import { PrismaClient } from '@prisma/client'; import { PrismaClient } from '@prisma/client';
import { PaymentService } from '../modules/banking/paymentService'; import { PaymentService } from '../modules/banking/paymentService';
import { requireAuth } from '../middleware/auth'; import { AuthedRequest, requireAuth } from '../middleware/auth';
const prisma = new PrismaClient(); const prisma = new PrismaClient();
const paymentService = new PaymentService(prisma); const paymentService = new PaymentService(prisma);
/** Router für das Vermieter-Cockpit-Dashboard (aggregierte Übersichtsdaten). */ /** Router für das Cockpit-Dashboard (aggregierte Übersichtsdaten). */
export const cockpitRouter = Router(); export const cockpitRouter = Router();
const TICKET_SELECT = {
id: true,
title: true,
category: true,
priority: true,
status: true,
createdAt: true,
room: { select: { roomNumber: true } },
creator: { select: { id: true, fullName: true } },
} as const;
// Erfordert Login: sensible Miet- und Kontaktdaten dürfen nicht öffentlich sein. // Erfordert Login: sensible Miet- und Kontaktdaten dürfen nicht öffentlich sein.
cockpitRouter.get('/cockpit/summary', requireAuth, async (_req: Request, res: Response) => { //
const [trafficLights, openTickets, rooms] = await Promise.all([ // Berechtigungsregel: Mieter dürfen NUR ihre eigenen Miet-/Vertragsdaten und
// Tickets sehen (plus Gemeinschaftsbereich-Tickets ohne Zimmerzuordnung) —
// niemals die Miet-/Kontaktdaten oder privaten Zimmer-Tickets anderer Mieter.
// Vermieter/Admin sehen weiterhin die vollständige Übersicht über die WG.
cockpitRouter.get('/cockpit/summary', requireAuth, async (req: AuthedRequest, res: Response) => {
const isLandlord = req.user!.role === 'LANDLORD' || req.user!.role === 'ADMIN';
if (isLandlord) {
const [trafficLights, openTickets, rooms] = await Promise.all([
paymentService.getRentTrafficLights(),
prisma.ticket.findMany({
where: { status: { in: ['OPEN', 'IN_PROGRESS'] } },
orderBy: { createdAt: 'desc' },
select: TICKET_SELECT,
}),
prisma.room.findMany({
select: { id: true, roomNumber: true, sizeSqm: true, status: true },
orderBy: { roomNumber: 'asc' },
}),
]);
const counts = { GREEN: 0, YELLOW: 0, RED: 0 } as Record<'GREEN' | 'YELLOW' | 'RED', number>;
let totalWarmRentSum = 0;
for (const light of trafficLights) {
counts[light.status]++;
totalWarmRentSum += light.amountDue;
}
return res.status(200).json({
role: req.user!.role,
generatedAt: new Date().toISOString(),
rooms,
trafficLights,
summary: {
totalRooms: rooms.length,
rentGreen: counts.GREEN,
rentYellow: counts.YELLOW,
rentRed: counts.RED,
totalMonthlyRentTarget: totalWarmRentSum,
openTicketsCount: openTickets.length,
},
openTickets,
});
}
// --- Mieter-Ansicht: nur eigene Daten + Gemeinschaftsbereich ---
const me = await prisma.user.findUnique({
where: { id: req.user!.id },
select: { roomId: true },
});
const ownRoomId = me?.roomId ?? null;
const [allTrafficLights, openTickets] = await Promise.all([
paymentService.getRentTrafficLights(), paymentService.getRentTrafficLights(),
prisma.ticket.findMany({ prisma.ticket.findMany({
where: { status: { in: ['OPEN', 'IN_PROGRESS'] } }, where: {
orderBy: { createdAt: 'desc' }, status: { in: ['OPEN', 'IN_PROGRESS'] },
select: { OR: [
id: true, { creatorId: req.user!.id },
title: true, { roomId: null },
category: true, ...(ownRoomId ? [{ roomId: ownRoomId }] : []),
priority: true, ],
status: true,
createdAt: true,
room: { select: { roomNumber: true } },
}, },
}), orderBy: { createdAt: 'desc' },
prisma.room.findMany({ select: TICKET_SELECT,
select: { id: true, roomNumber: true, sizeSqm: true, status: true },
orderBy: { roomNumber: 'asc' },
}), }),
]); ]);
const counts = { GREEN: 0, YELLOW: 0, RED: 0 } as Record<'GREEN' | 'YELLOW' | 'RED', number>; // Eigenen aktiven Vertrag laden, um die passende Ampel aus der Gesamtliste herauszufiltern
let totalWarmRentSum = 0; // (getRentTrafficLights liefert alle Verträge — hier wird strikt auf den eigenen reduziert).
for (const light of trafficLights) { const myContract = await prisma.contract.findFirst({
counts[light.status]++; where: { userId: req.user!.id, isActive: true },
totalWarmRentSum += light.amountDue; include: { room: true },
});
let myRent = null;
if (myContract) {
myRent = allTrafficLights.find((l) => l.contractId === myContract.id) || null;
} }
res.status(200).json({ res.status(200).json({
role: req.user!.role,
generatedAt: new Date().toISOString(), generatedAt: new Date().toISOString(),
rooms, myRoom: myContract?.room ? { id: myContract.room.id, roomNumber: myContract.room.roomNumber } : null,
trafficLights, myRent,
summary: {
totalRooms: rooms.length,
rentGreen: counts.GREEN,
rentYellow: counts.YELLOW,
rentRed: counts.RED,
totalMonthlyRentTarget: totalWarmRentSum,
openTicketsCount: openTickets.length,
},
openTickets, openTickets,
}); });
}); });