381 lines
11 KiB
TypeScript
381 lines
11 KiB
TypeScript
// 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 },
|
||
);
|
||
}
|
||
}
|