268 lines
7.6 KiB
TypeScript
268 lines
7.6 KiB
TypeScript
// 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 });
|
||
}
|
||
}
|