MAC-Adresse
diff --git a/app/api/devices/[id]/route.ts b/app/api/devices/[id]/route.ts
index e4a8173..409f6b4 100644
--- a/app/api/devices/[id]/route.ts
+++ b/app/api/devices/[id]/route.ts
@@ -2,14 +2,19 @@
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
};
+
export async function GET(
_req: Request,
- ctx: { params: Promise }, // 👈 params ist ein Promise
+ ctx: RouteContext,
) {
- const { id } = await ctx.params; // 👈 Promise auflösen
+ // ⬇️ Promise auflösen
+ const { id } = await ctx.params;
if (!id) {
return NextResponse.json({ error: 'MISSING_ID' }, { status: 400 });
@@ -40,6 +45,7 @@ export async function GET(
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(),
@@ -53,14 +59,27 @@ export async function GET(
export async function PATCH(
req: Request,
- ctx: { params: Promise }, // 👈 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();
+ 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) {
@@ -79,9 +98,17 @@ export async function PATCH(
ipv6Address: body.ipv6Address,
macAddress: body.macAddress,
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 !== '') {
data.group = {
connectOrCreate: {
@@ -105,6 +132,7 @@ export async function PATCH(
data.location = { disconnect: true };
}
+ // Update durchführen (für "after"-Snapshot)
const updated = await prisma.device.update({
where: { inventoryNumber: id },
data,
@@ -114,33 +142,106 @@ export async function PATCH(
},
});
- const snapshot: Prisma.JsonObject = {
- 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(),
- };
+ const trackedFields = [
+ 'name',
+ 'manufacturer',
+ 'model',
+ 'serialNumber',
+ 'productNumber',
+ 'comment',
+ 'ipv4Address',
+ 'ipv6Address',
+ 'macAddress',
+ 'username',
+ 'passwordHash',
+ ] as const;
- await prisma.deviceHistory.create({
- data: {
- deviceId: updated.inventoryNumber,
- changeType: 'UPDATED',
- snapshot,
- changedById: null,
- },
- });
+ 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,
diff --git a/lib/auth.ts b/lib/auth.ts
new file mode 100644
index 0000000..5b7a55d
--- /dev/null
+++ b/lib/auth.ts
@@ -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 {
+ 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,
+ },
+ },
+ },
+ });
+}
diff --git a/prisma/dev.db b/prisma/dev.db
index 94528b7..17a57ea 100644
Binary files a/prisma/dev.db and b/prisma/dev.db differ