updated
This commit is contained in:
parent
3af854663e
commit
d1f13fd77e
@ -566,7 +566,7 @@ export default function DevicesPage() {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div className='col-span-2'>
|
||||||
<p className="text-xs font-semibold uppercase tracking-wide text-gray-400">
|
<p className="text-xs font-semibold uppercase tracking-wide text-gray-400">
|
||||||
MAC-Adresse
|
MAC-Adresse
|
||||||
</p>
|
</p>
|
||||||
|
|||||||
@ -2,14 +2,19 @@
|
|||||||
import { NextResponse } from 'next/server';
|
import { NextResponse } from 'next/server';
|
||||||
import { prisma } from '@/lib/prisma';
|
import { prisma } from '@/lib/prisma';
|
||||||
import type { Prisma } from '@prisma/client';
|
import type { Prisma } from '@prisma/client';
|
||||||
|
import { getCurrentUserId } from '@/lib/auth';
|
||||||
|
|
||||||
type RouteParams = { id: string };
|
type RouteParams = { id: string };
|
||||||
|
|
||||||
|
// ⬇️ Next liefert params als Promise
|
||||||
|
type RouteContext = { params: Promise<RouteParams> };
|
||||||
|
|
||||||
export async function GET(
|
export async function GET(
|
||||||
_req: Request,
|
_req: Request,
|
||||||
ctx: { params: Promise<RouteParams> }, // 👈 params ist ein Promise
|
ctx: RouteContext,
|
||||||
) {
|
) {
|
||||||
const { id } = await ctx.params; // 👈 Promise auflösen
|
// ⬇️ Promise auflösen
|
||||||
|
const { id } = await ctx.params;
|
||||||
|
|
||||||
if (!id) {
|
if (!id) {
|
||||||
return NextResponse.json({ error: 'MISSING_ID' }, { status: 400 });
|
return NextResponse.json({ error: 'MISSING_ID' }, { status: 400 });
|
||||||
@ -40,6 +45,7 @@ export async function GET(
|
|||||||
ipv6Address: device.ipv6Address,
|
ipv6Address: device.ipv6Address,
|
||||||
macAddress: device.macAddress,
|
macAddress: device.macAddress,
|
||||||
username: device.username,
|
username: device.username,
|
||||||
|
// passwordHash gebe ich hier absichtlich nicht zurück
|
||||||
group: device.group?.name ?? null,
|
group: device.group?.name ?? null,
|
||||||
location: device.location?.name ?? null,
|
location: device.location?.name ?? null,
|
||||||
createdAt: device.createdAt.toISOString(),
|
createdAt: device.createdAt.toISOString(),
|
||||||
@ -53,14 +59,27 @@ export async function GET(
|
|||||||
|
|
||||||
export async function PATCH(
|
export async function PATCH(
|
||||||
req: Request,
|
req: Request,
|
||||||
ctx: { params: Promise<RouteParams> }, // 👈 hier auch
|
ctx: RouteContext,
|
||||||
) {
|
) {
|
||||||
const { id } = await ctx.params; // 👈 Promise auflösen
|
// ⬇️ Promise auflösen
|
||||||
|
const { id } = await ctx.params;
|
||||||
const body = await req.json();
|
const body = await req.json();
|
||||||
|
|
||||||
|
if (!id) {
|
||||||
|
return NextResponse.json({ error: 'MISSING_ID' }, { status: 400 });
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
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({
|
const existing = await prisma.device.findUnique({
|
||||||
where: { inventoryNumber: id },
|
where: { inventoryNumber: id },
|
||||||
|
include: {
|
||||||
|
group: true,
|
||||||
|
location: true,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!existing) {
|
if (!existing) {
|
||||||
@ -79,9 +98,17 @@ export async function PATCH(
|
|||||||
ipv6Address: body.ipv6Address,
|
ipv6Address: body.ipv6Address,
|
||||||
macAddress: body.macAddress,
|
macAddress: body.macAddress,
|
||||||
username: body.username,
|
username: body.username,
|
||||||
|
passwordHash: body.passwordHash,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Gruppe (per Name) – Name ist @unique in DeviceGroup
|
// 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 !== '') {
|
if (body.group != null && body.group !== '') {
|
||||||
data.group = {
|
data.group = {
|
||||||
connectOrCreate: {
|
connectOrCreate: {
|
||||||
@ -105,6 +132,7 @@ export async function PATCH(
|
|||||||
data.location = { disconnect: true };
|
data.location = { disconnect: true };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Update durchführen (für "after"-Snapshot)
|
||||||
const updated = await prisma.device.update({
|
const updated = await prisma.device.update({
|
||||||
where: { inventoryNumber: id },
|
where: { inventoryNumber: id },
|
||||||
data,
|
data,
|
||||||
@ -114,33 +142,106 @@ export async function PATCH(
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const snapshot: Prisma.JsonObject = {
|
const trackedFields = [
|
||||||
inventoryNumber: updated.inventoryNumber,
|
'name',
|
||||||
name: updated.name,
|
'manufacturer',
|
||||||
manufacturer: updated.manufacturer,
|
'model',
|
||||||
model: updated.model,
|
'serialNumber',
|
||||||
serialNumber: updated.serialNumber,
|
'productNumber',
|
||||||
productNumber: updated.productNumber,
|
'comment',
|
||||||
comment: updated.comment,
|
'ipv4Address',
|
||||||
ipv4Address: updated.ipv4Address,
|
'ipv6Address',
|
||||||
ipv6Address: updated.ipv6Address,
|
'macAddress',
|
||||||
macAddress: updated.macAddress,
|
'username',
|
||||||
username: updated.username,
|
'passwordHash',
|
||||||
passwordHash: updated.passwordHash,
|
] as const;
|
||||||
group: updated.group?.name ?? null,
|
|
||||||
location: updated.location?.name ?? null,
|
|
||||||
createdAt: updated.createdAt.toISOString(),
|
|
||||||
updatedAt: updated.updatedAt.toISOString(),
|
|
||||||
};
|
|
||||||
|
|
||||||
await prisma.deviceHistory.create({
|
type TrackedField = (typeof trackedFields)[number];
|
||||||
data: {
|
|
||||||
deviceId: updated.inventoryNumber,
|
const changes: {
|
||||||
changeType: 'UPDATED',
|
field: TrackedField | 'group' | 'location';
|
||||||
snapshot,
|
before: string | null;
|
||||||
changedById: 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({
|
return NextResponse.json({
|
||||||
inventoryNumber: updated.inventoryNumber,
|
inventoryNumber: updated.inventoryNumber,
|
||||||
|
|||||||
75
lib/auth.ts
Normal file
75
lib/auth.ts
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
// lib/auth.ts
|
||||||
|
'use server';
|
||||||
|
|
||||||
|
import { headers } from 'next/headers';
|
||||||
|
import { prisma } from './prisma';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Liefert die aktuelle User-ID oder null,
|
||||||
|
* falls kein User ermittelt werden kann.
|
||||||
|
*
|
||||||
|
* Reihenfolge:
|
||||||
|
* 1. HTTP-Header: x-user-id
|
||||||
|
* 2. HTTP-Header: x-user-email
|
||||||
|
* 3. Fallback: DEFAULT_USER_EMAIL env oder "user@domain.local"
|
||||||
|
*/
|
||||||
|
export async function getCurrentUserId(): Promise<string | null> {
|
||||||
|
const h = await headers();
|
||||||
|
|
||||||
|
const headerUserId = h.get('x-user-id');
|
||||||
|
const headerUserEmail = h.get('x-user-email');
|
||||||
|
|
||||||
|
// 1) Direkt über ID (Header)
|
||||||
|
if (headerUserId) {
|
||||||
|
const userById = await prisma.user.findUnique({
|
||||||
|
where: { id: headerUserId },
|
||||||
|
select: { id: true },
|
||||||
|
});
|
||||||
|
|
||||||
|
if (userById) {
|
||||||
|
return userById.id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2) Über Email (Header)
|
||||||
|
if (headerUserEmail) {
|
||||||
|
const userByEmail = await prisma.user.findUnique({
|
||||||
|
where: { email: headerUserEmail },
|
||||||
|
select: { id: true },
|
||||||
|
});
|
||||||
|
|
||||||
|
if (userByEmail) {
|
||||||
|
return userByEmail.id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3) Fallback: Standard-User (z.B. dein Test-User)
|
||||||
|
const fallbackEmail =
|
||||||
|
process.env.DEFAULT_USER_EMAIL ?? 'user@domain.local';
|
||||||
|
|
||||||
|
const fallbackUser = await prisma.user.findUnique({
|
||||||
|
where: { email: fallbackEmail },
|
||||||
|
select: { id: true },
|
||||||
|
});
|
||||||
|
|
||||||
|
return fallbackUser?.id ?? null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Optional: kompletten User holen (falls du später mehr brauchst)
|
||||||
|
*/
|
||||||
|
export async function getCurrentUser() {
|
||||||
|
const userId = await getCurrentUserId();
|
||||||
|
if (!userId) return null;
|
||||||
|
|
||||||
|
return prisma.user.findUnique({
|
||||||
|
where: { id: userId },
|
||||||
|
include: {
|
||||||
|
roles: {
|
||||||
|
include: {
|
||||||
|
role: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
BIN
prisma/dev.db
BIN
prisma/dev.db
Binary file not shown.
Loading…
x
Reference in New Issue
Block a user