// app/api/devices/route.ts import { NextResponse } from 'next/server'; import { prisma } from '@/lib/prisma'; import type { Prisma } from '@/generated/prisma/client'; import { getCurrentUserId } from '@/lib/auth'; import type { Server as IOServer } from 'socket.io'; export async function GET() { try { const devices = await prisma.device.findMany({ include: { group: true, location: true, tags: true, parentDevice: true, accessories: true, }, orderBy: { inventoryNumber: 'asc' }, }); return NextResponse.json( devices.map((d) => ({ inventoryNumber: d.inventoryNumber, name: d.name, manufacturer: d.manufacturer, model: d.model, serialNumber: d.serialNumber, productNumber: d.productNumber, comment: d.comment, ipv4Address: d.ipv4Address, ipv6Address: d.ipv6Address, macAddress: d.macAddress, username: d.username, passwordHash: d.passwordHash, group: d.group?.name ?? null, location: d.location?.name ?? null, tags: d.tags.map((t) => t.name), loanedTo: d.loanedTo, loanedFrom: d.loanedFrom ? d.loanedFrom.toISOString() : null, loanedUntil: d.loanedUntil ? d.loanedUntil.toISOString() : null, loanComment: d.loanComment, createdAt: d.createdAt.toISOString(), updatedAt: d.updatedAt.toISOString(), // 🔹 wichtig für dein Dropdown: parentInventoryNumber: d.parentDeviceId, // Hauptgerät-Nummer oder null parentName: d.parentDevice?.name ?? null, accessories: d.accessories.map((a) => ({ inventoryNumber: a.inventoryNumber, name: a.name, })), })), ); } catch (err) { console.error('[GET /api/devices]', err); return NextResponse.json( { error: 'INTERNAL_ERROR' }, { status: 500 }, ); } } export async function POST(req: Request) { try { const body = await req.json(); const { inventoryNumber, name, manufacturer, model, serialNumber, productNumber, comment, group, location, ipv4Address, ipv6Address, macAddress, username, passwordHash, tags, loanedTo, loanedFrom, loanedUntil, loanComment, parentInventoryNumber, } = body; if (!inventoryNumber || !name) { return NextResponse.json( { error: 'inventoryNumber und name sind Pflichtfelder.' }, { status: 400 }, ); } const existing = await prisma.device.findUnique({ where: { inventoryNumber }, }); if (existing) { return NextResponse.json( { error: 'Gerät mit dieser Inventar-Nr. existiert bereits.' }, { status: 409 }, ); } const userId = await getCurrentUserId(); let canConnectUser = false; if (userId) { const user = await prisma.user.findUnique({ where: { nwkennung: userId }, select: { nwkennung: true }, }); if (user) { canConnectUser = true; } else { console.warn( `[POST /api/devices] User mit id=${userId} nicht gefunden – createdBy/changedBy werden nicht gesetzt.`, ); } } // Gruppe let groupId: string | null = null; if (group && typeof group === 'string' && group.trim() !== '') { const grp = await prisma.deviceGroup.upsert({ where: { name: group.trim() }, update: {}, create: { name: group.trim() }, }); groupId = grp.id; } // Standort let locationId: string | null = null; if (location && typeof location === 'string' && location.trim() !== '') { const loc = await prisma.location.upsert({ where: { name: location.trim() }, update: {}, create: { name: location.trim() }, }); locationId = loc.id; } const tagNames: string[] = Array.isArray(tags) ? tags.map((t: unknown) => String(t).trim()).filter(Boolean) : []; // 🔹 NEU: Hauptgerät-Relation (optional) let parentDeviceRelation: Prisma.DeviceCreateInput['parentDevice'] | undefined; if ( parentInventoryNumber && typeof parentInventoryNumber === 'string' && parentInventoryNumber.trim() !== '' ) { const trimmed = parentInventoryNumber.trim(); // sich selbst als Hauptgerät → Fehler if (trimmed === inventoryNumber) { return NextResponse.json( { error: 'Ein Gerät kann nicht gleichzeitig sein eigenes Hauptgerät sein.', }, { status: 400 }, ); } const parent = await prisma.device.findUnique({ where: { inventoryNumber: trimmed }, select: { inventoryNumber: true }, }); if (!parent) { return NextResponse.json( { error: `Hauptgerät mit Inventar-Nr. ${trimmed} wurde nicht gefunden.`, }, { status: 400 }, ); } parentDeviceRelation = { connect: { inventoryNumber: trimmed }, }; } const created = await prisma.device.create({ data: { inventoryNumber, name, manufacturer, model, serialNumber: serialNumber ?? null, productNumber: productNumber ?? null, comment: comment ?? null, ipv4Address: ipv4Address ?? null, ipv6Address: ipv6Address ?? null, macAddress: macAddress ?? null, username: username ?? null, passwordHash: passwordHash ?? null, // Verleih-Felder loanedTo: loanedTo ?? null, loanedFrom: loanedFrom ? new Date(loanedFrom) : null, loanedUntil: loanedUntil ? new Date(loanedUntil) : null, loanComment: loanComment ?? null, // 🔹 optionales Hauptgerät ...(parentDeviceRelation ? { parentDevice: parentDeviceRelation } : {}), ...(groupId ? { group: { connect: { id: groupId }, }, } : {}), ...(locationId ? { location: { connect: { id: locationId }, }, } : {}), // 🔹 FIX: User via nwkennung verbinden ...(canConnectUser && userId ? { createdBy: { connect: { nwkennung: userId }, }, } : {}), ...(tagNames.length ? { tags: { connectOrCreate: tagNames.map((name) => ({ where: { name }, create: { name }, })), }, } : {}), }, include: { group: true, location: true, tags: true, // 🔹 NEU: parentDevice für Antwort/Event parentDevice: { select: { inventoryNumber: true, name: true }, }, }, }); const snapshot: Prisma.JsonObject = { before: null, after: { inventoryNumber: created.inventoryNumber, name: created.name, manufacturer: created.manufacturer, model: created.model, serialNumber: created.serialNumber, productNumber: created.productNumber, comment: created.comment, ipv4Address: created.ipv4Address, ipv6Address: created.ipv6Address, macAddress: created.macAddress, username: created.username, passwordHash: created.passwordHash, group: created.group?.name ?? null, location: created.location?.name ?? null, tags: created.tags.map((t) => t.name), loanedTo: created.loanedTo, loanedFrom: created.loanedFrom ? created.loanedFrom.toISOString() : null, loanedUntil: created.loanedUntil ? created.loanedUntil.toISOString() : null, loanComment: created.loanComment, // 🔹 NEU parentInventoryNumber: created.parentDeviceId, parentName: created.parentDevice?.name ?? null, createdAt: created.createdAt.toISOString(), updatedAt: created.updatedAt.toISOString(), }, changes: [], }; await prisma.deviceHistory.create({ data: { deviceId: created.inventoryNumber, changeType: 'CREATED', snapshot, changedById: canConnectUser && userId ? userId : null, }, }); const io = (global as any).devicesIo as IOServer | undefined; if (io) { io.emit('device:created', { inventoryNumber: created.inventoryNumber, name: created.name, manufacturer: created.manufacturer, model: created.model, serialNumber: created.serialNumber, productNumber: created.productNumber, comment: created.comment, ipv4Address: created.ipv4Address, ipv6Address: created.ipv6Address, macAddress: created.macAddress, username: created.username, group: created.group?.name ?? null, location: created.location?.name ?? null, tags: created.tags.map((t) => t.name), loanedTo: created.loanedTo, loanedFrom: created.loanedFrom ? created.loanedFrom.toISOString() : null, loanedUntil: created.loanedUntil ? created.loanedUntil.toISOString() : null, loanComment: created.loanComment, updatedAt: created.updatedAt.toISOString(), }); } return NextResponse.json( { inventoryNumber: created.inventoryNumber, name: created.name, manufacturer: created.manufacturer, model: created.model, serialNumber: created.serialNumber, productNumber: created.productNumber, comment: created.comment, ipv4Address: created.ipv4Address, ipv6Address: created.ipv6Address, macAddress: created.macAddress, username: created.username, passwordHash: created.passwordHash, group: created.group?.name ?? null, location: created.location?.name ?? null, tags: created.tags.map((t) => t.name), loanedTo: created.loanedTo, loanedFrom: created.loanedFrom ? created.loanedFrom.toISOString() : null, loanedUntil: created.loanedUntil ? created.loanedUntil.toISOString() : null, loanComment: created.loanComment, // 🔹 NEU parentInventoryNumber: created.parentDeviceId, parentName: created.parentDevice?.name ?? null, updatedAt: created.updatedAt.toISOString(), }, { status: 201 }, ); } catch (err) { console.error('[POST /api/devices]', err); return NextResponse.json( { error: 'Interner Serverfehler beim Anlegen des Geräts.' }, { status: 500 }, ); } }