geraete/app/api/devices/route.ts
2025-11-26 08:02:48 +01:00

381 lines
11 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// 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 },
);
}
}