2025-11-15 10:01:44 +01:00

268 lines
7.6 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/[id]/route.ts
import { NextResponse } from 'next/server';
import { prisma } from '@/lib/prisma';
import type { Prisma } from '@prisma/client';
import { getCurrentUserId } from '@/lib/auth';
type RouteParams = { id: string };
// ⬇️ Next liefert params als Promise
type RouteContext = { params: Promise<RouteParams> };
export async function GET(
_req: Request,
ctx: RouteContext,
) {
// ⬇️ Promise auflösen
const { id } = await ctx.params;
if (!id) {
return NextResponse.json({ error: 'MISSING_ID' }, { status: 400 });
}
try {
const device = await prisma.device.findUnique({
where: { inventoryNumber: id },
include: {
group: true,
location: true,
},
});
if (!device) {
return NextResponse.json({ error: 'NOT_FOUND' }, { status: 404 });
}
return NextResponse.json({
inventoryNumber: device.inventoryNumber,
name: device.name,
manufacturer: device.manufacturer,
model: device.model,
serialNumber: device.serialNumber,
productNumber: device.productNumber,
comment: device.comment,
ipv4Address: device.ipv4Address,
ipv6Address: device.ipv6Address,
macAddress: device.macAddress,
username: device.username,
// passwordHash gebe ich hier absichtlich nicht zurück
group: device.group?.name ?? null,
location: device.location?.name ?? null,
createdAt: device.createdAt.toISOString(),
updatedAt: device.updatedAt.toISOString(),
});
} catch (err) {
console.error('[GET /api/devices/[id]]', err);
return NextResponse.json({ error: 'INTERNAL_ERROR' }, { status: 500 });
}
}
export async function PATCH(
req: Request,
ctx: RouteContext,
) {
// ⬇️ Promise auflösen
const { id } = await ctx.params;
const body = await req.json();
if (!id) {
return NextResponse.json({ error: 'MISSING_ID' }, { status: 400 });
}
try {
// User für updatedBy / History bestimmen
const userId = await getCurrentUserId();
// aktuelles Gerät inkl. Relations laden (für "before"-Snapshot)
const existing = await prisma.device.findUnique({
where: { inventoryNumber: id },
include: {
group: true,
location: true,
},
});
if (!existing) {
return NextResponse.json({ error: 'NOT_FOUND' }, { status: 404 });
}
// Basis-Update-Daten
const data: Prisma.DeviceUpdateInput = {
name: body.name,
manufacturer: body.manufacturer,
model: body.model,
serialNumber: body.serialNumber,
productNumber: body.productNumber,
comment: body.comment,
ipv4Address: body.ipv4Address,
ipv6Address: body.ipv6Address,
macAddress: body.macAddress,
username: body.username,
passwordHash: body.passwordHash,
};
// updatedBy setzen, wenn User da
if (userId) {
data.updatedBy = {
connect: { id: userId },
};
}
// Gruppe (per Name) DeviceGroup.name ist @unique
if (body.group != null && body.group !== '') {
data.group = {
connectOrCreate: {
where: { name: body.group },
create: { name: body.group },
},
};
} else {
data.group = { disconnect: true };
}
// Standort (per Name) Location.name ist @unique
if (body.location != null && body.location !== '') {
data.location = {
connectOrCreate: {
where: { name: body.location },
create: { name: body.location },
},
};
} else {
data.location = { disconnect: true };
}
// Update durchführen (für "after"-Snapshot)
const updated = await prisma.device.update({
where: { inventoryNumber: id },
data,
include: {
group: true,
location: true,
},
});
const trackedFields = [
'name',
'manufacturer',
'model',
'serialNumber',
'productNumber',
'comment',
'ipv4Address',
'ipv6Address',
'macAddress',
'username',
'passwordHash',
] as const;
type TrackedField = (typeof trackedFields)[number];
const changes: {
field: TrackedField | 'group' | 'location';
before: string | null;
after: string | null;
}[] = [];
for (const field of trackedFields) {
const before = (existing as any)[field] as string | null;
const after = (updated as any)[field] as string | null;
if (before !== after) {
changes.push({ field, before, after });
}
}
const beforeGroup = (existing.group?.name ?? null) as string | null;
const afterGroup = (updated.group?.name ?? null) as string | null;
if (beforeGroup !== afterGroup) {
changes.push({
field: 'group',
before: beforeGroup,
after: afterGroup,
});
}
const beforeLocation = (existing.location?.name ?? null) as string | null;
const afterLocation = (updated.location?.name ?? null) as string | null;
if (beforeLocation !== afterLocation) {
changes.push({
field: 'location',
before: beforeLocation,
after: afterLocation,
});
}
if (changes.length > 0) {
const snapshot: Prisma.JsonObject = {
before: {
inventoryNumber: existing.inventoryNumber,
name: existing.name,
manufacturer: existing.manufacturer,
model: existing.model,
serialNumber: existing.serialNumber,
productNumber: existing.productNumber,
comment: existing.comment,
ipv4Address: existing.ipv4Address,
ipv6Address: existing.ipv6Address,
macAddress: existing.macAddress,
username: existing.username,
passwordHash: existing.passwordHash,
group: existing.group?.name ?? null,
location: existing.location?.name ?? null,
createdAt: existing.createdAt.toISOString(),
updatedAt: existing.updatedAt.toISOString(),
},
after: {
inventoryNumber: updated.inventoryNumber,
name: updated.name,
manufacturer: updated.manufacturer,
model: updated.model,
serialNumber: updated.serialNumber,
productNumber: updated.productNumber,
comment: updated.comment,
ipv4Address: updated.ipv4Address,
ipv6Address: updated.ipv6Address,
macAddress: updated.macAddress,
username: updated.username,
passwordHash: updated.passwordHash,
group: updated.group?.name ?? null,
location: updated.location?.name ?? null,
createdAt: updated.createdAt.toISOString(),
updatedAt: updated.updatedAt.toISOString(),
},
changes,
};
await prisma.deviceHistory.create({
data: {
deviceId: updated.inventoryNumber,
changeType: 'UPDATED',
snapshot,
changedById: userId ?? null,
},
});
}
return NextResponse.json({
inventoryNumber: updated.inventoryNumber,
name: updated.name,
manufacturer: updated.manufacturer,
model: updated.model,
serialNumber: updated.serialNumber,
productNumber: updated.productNumber,
comment: updated.comment,
ipv4Address: updated.ipv4Address,
ipv6Address: updated.ipv6Address,
macAddress: updated.macAddress,
username: updated.username,
group: updated.group?.name ?? null,
location: updated.location?.name ?? null,
createdAt: updated.createdAt.toISOString(),
updatedAt: updated.updatedAt.toISOString(),
});
} catch (err) {
console.error('[PATCH /api/devices/[id]]', err);
return NextResponse.json({ error: 'INTERNAL_ERROR' }, { status: 500 });
}
}