import { Router, Response } from 'express'; import { PrismaClient, ItemCondition } from '@prisma/client'; import { AuthedRequest, requireAuth, requireRole } from '../middleware/auth'; const prisma = new PrismaClient(); /** * Router für die Inventarverwaltung (Möbel & Geräte je Zimmer bzw. * Gemeinschaftsfläche). Anlegen/Ändern/Löschen ist Vermieter/Admin * vorbehalten. Sichtbarkeit wie überall sonst: Mieter sehen nur Inventar * ihres eigenen Zimmers plus echte Gemeinschaftsgegenstände (roomId NULL), * nicht das private Zimmerinventar (inkl. Kaufpreis) anderer Mieter. * Vermieter/Admin sehen alles. * * Pro Gegenstand können mehrere Fotos hinterlegt werden (photoUrls) — das * Web-Dashboard sendet dafür clientseitig eingelesene Bilder als Data-URLs, * es gibt keine separate Objekt-Storage-Anbindung in diesem Demo-Stand. */ export const inventoryRouter = Router(); const CONDITIONS = Object.values(ItemCondition); const MAX_PHOTOS = 6; const INVENTORY_SELECT = { id: true, itemName: true, purchaseDate: true, purchasePrice: true, condition: true, manualPdfUrl: true, photoUrls: true, createdAt: true, room: { select: { id: true, roomNumber: true } }, }; function sanitizePhotoUrls(input: unknown): string[] { if (!Array.isArray(input)) return []; return input .filter((u) => typeof u === 'string' && u.trim()) .slice(0, MAX_PHOTOS) .map((u) => (u as string).trim()); } inventoryRouter.get('/inventory', requireAuth, async (req: AuthedRequest, res: Response) => { const isLandlord = req.user!.role === 'LANDLORD' || req.user!.role === 'ADMIN'; let where = {}; if (!isLandlord) { const self = await prisma.user.findUnique({ where: { id: req.user!.id }, select: { roomId: true } }); where = { OR: [{ roomId: null }, { roomId: self?.roomId ?? '__none__' }] }; } const items = await prisma.inventory.findMany({ where, orderBy: { createdAt: 'desc' }, select: INVENTORY_SELECT, }); res.status(200).json({ items }); }); inventoryRouter.post( '/inventory', requireAuth, requireRole('LANDLORD', 'ADMIN'), async (req: AuthedRequest, res: Response) => { const { itemName, roomId, purchaseDate, purchasePrice, condition, manualPdfUrl, photoUrls } = req.body || {}; if (!itemName || typeof itemName !== 'string' || !itemName.trim()) { return res.status(400).json({ error: 'itemName ist erforderlich' }); } if (condition && !CONDITIONS.includes(condition)) { return res.status(400).json({ error: `Ungültiger Zustand. Erlaubt: ${CONDITIONS.join(', ')}` }); } if (roomId) { const room = await prisma.room.findUnique({ where: { id: roomId } }); if (!room) return res.status(400).json({ error: 'Zimmer nicht gefunden' }); } const item = await prisma.inventory.create({ data: { itemName: itemName.trim().slice(0, 120), roomId: roomId || null, purchaseDate: purchaseDate && !Number.isNaN(Date.parse(purchaseDate)) ? new Date(purchaseDate) : null, purchasePrice: purchasePrice !== undefined && purchasePrice !== null && purchasePrice !== '' ? Number(purchasePrice) : null, condition: condition || ItemCondition.GOOD, manualPdfUrl: typeof manualPdfUrl === 'string' && manualPdfUrl.trim() ? manualPdfUrl.trim() : null, photoUrls: sanitizePhotoUrls(photoUrls), }, select: INVENTORY_SELECT, }); res.status(201).json({ item }); }, ); inventoryRouter.patch( '/inventory/:id', requireAuth, requireRole('LANDLORD', 'ADMIN'), async (req: AuthedRequest, res: Response) => { const { condition, purchasePrice, manualPdfUrl, photoUrls } = req.body || {}; if (condition && !CONDITIONS.includes(condition)) { return res.status(400).json({ error: `Ungültiger Zustand. Erlaubt: ${CONDITIONS.join(', ')}` }); } try { const item = await prisma.inventory.update({ where: { id: req.params.id }, data: { condition: condition || undefined, purchasePrice: purchasePrice !== undefined && purchasePrice !== null && purchasePrice !== '' ? Number(purchasePrice) : undefined, manualPdfUrl: typeof manualPdfUrl === 'string' ? manualPdfUrl.trim() || null : undefined, photoUrls: Array.isArray(photoUrls) ? sanitizePhotoUrls(photoUrls) : undefined, }, select: INVENTORY_SELECT, }); res.status(200).json({ item }); } catch { res.status(404).json({ error: 'Inventar-Eintrag nicht gefunden' }); } }, ); inventoryRouter.delete( '/inventory/:id', requireAuth, requireRole('LANDLORD', 'ADMIN'), async (req: AuthedRequest, res: Response) => { try { await prisma.inventory.delete({ where: { id: req.params.id } }); res.status(204).send(); } catch { res.status(404).json({ error: 'Inventar-Eintrag nicht gefunden' }); } }, );