From e4aae9804b28529d4f0770f934dc55c055d7c88c Mon Sep 17 00:00:00 2001
From: PxlLoewe <72106766+PxlLoewe@users.noreply.github.com>
Date: Fri, 30 Jan 2026 00:25:51 +0100
Subject: [PATCH] Continue Account log
---
apps/hub/app/(app)/admin/user/[id]/page.tsx | 59 ++++++++++++++++++-
apps/hub/app/(app)/events/page.tsx | 4 --
.../app/(app)/settings/_components/forms.tsx | 23 ++++++++
.../app/(auth)/login/_components/Login.tsx | 5 ++
.../app/(auth)/login/_components/action.ts | 53 +++++++++++++++++
apps/hub/helper/discord.ts | 10 ++++
packages/database/prisma/schema/log.prisma | 22 +++++++
.../migration.sql | 2 +
.../20260129221239_user_log/migration.sql | 16 +++++
.../20260129222950_add_browser/migration.sql | 16 +++++
.../20260129224931_ce_id/migration.sql | 2 +
.../migration.sql | 4 ++
.../database/prisma/schema/penalty.prisma | 15 ++---
packages/database/prisma/schema/report.prisma | 8 +--
packages/database/prisma/schema/user.prisma | 9 ++-
15 files changed, 224 insertions(+), 24 deletions(-)
create mode 100644 apps/hub/app/(auth)/login/_components/action.ts
create mode 100644 packages/database/prisma/schema/log.prisma
create mode 100644 packages/database/prisma/schema/migrations/20260129214442_fix_penalty_user_relation/migration.sql
create mode 100644 packages/database/prisma/schema/migrations/20260129221239_user_log/migration.sql
create mode 100644 packages/database/prisma/schema/migrations/20260129222950_add_browser/migration.sql
create mode 100644 packages/database/prisma/schema/migrations/20260129224931_ce_id/migration.sql
create mode 100644 packages/database/prisma/schema/migrations/20260129230237_profile_values/migration.sql
diff --git a/apps/hub/app/(app)/admin/user/[id]/page.tsx b/apps/hub/app/(app)/admin/user/[id]/page.tsx
index 76e57b3d..ca619cb6 100644
--- a/apps/hub/app/(app)/admin/user/[id]/page.tsx
+++ b/apps/hub/app/(app)/admin/user/[id]/page.tsx
@@ -1,5 +1,5 @@
import { PersonIcon } from "@radix-ui/react-icons";
-import { prisma } from "@repo/db";
+import { Log, prisma } from "@repo/db";
import {
AdminForm,
ConnectionHistory,
@@ -9,6 +9,8 @@ import {
} from "./_components/forms";
import { Error } from "../../../../_components/Error";
import { getUserPenaltys } from "@repo/shared-components";
+import { PaginatedTable } from "_components/PaginatedTable";
+import { ColumnDef } from "@tanstack/react-table";
export default async function Page({ params }: { params: Promise<{ id: string }> }) {
const { id } = await params;
@@ -35,6 +37,26 @@ export default async function Page({ params }: { params: Promise<{ id: string }>
});
}
+ const userLog = await prisma.log.findMany({
+ where: {
+ userId: user?.id,
+ },
+ });
+
+ const sameIpLogs = await prisma.log.findMany({
+ where: {
+ ip: {
+ in: userLog.map((log) => log.ip).filter((ip): ip is string => ip !== null),
+ },
+ userId: {
+ not: user?.id,
+ },
+ },
+ include: {
+ User: true,
+ },
+ });
+
const formerDiscordAccounts = await prisma.formerDiscordAccount.findMany({
where: {
userId: user?.id,
@@ -152,6 +174,41 @@ export default async function Page({ params }: { params: Promise<{ id: string }>
openTimebans={openTimeban}
/>
+
+
new Date(info.getValue()).toLocaleString("de-DE"),
+ },
+ {
+ header: "Aktion",
+ accessorKey: "action",
+ cell: ({ row }) => {
+ const action = row.original.type;
+
+ if (action !== "PROFILE_CHANGE") {
+ return action;
+ } else {
+ return `${row.original.field} von "${row.original.oldValue}" zu "${row.original.newValue}"`;
+ }
+ },
+ },
+ {
+ header: "IP-Adresse",
+ accessorKey: "ip",
+ },
+ {
+ header: "Gerät",
+ accessorKey: "browser",
+ },
+ ] as ColumnDef[]
+ }
+ />
+
diff --git a/apps/hub/app/(app)/events/page.tsx b/apps/hub/app/(app)/events/page.tsx
index fb67df39..2aa8a479 100644
--- a/apps/hub/app/(app)/events/page.tsx
+++ b/apps/hub/app/(app)/events/page.tsx
@@ -23,10 +23,6 @@ const page = async () => {
},
},
},
-
- orderBy: {
- id: "desc",
- },
});
return (
diff --git a/apps/hub/app/(app)/settings/_components/forms.tsx b/apps/hub/app/(app)/settings/_components/forms.tsx
index 64feabec..15399821 100644
--- a/apps/hub/app/(app)/settings/_components/forms.tsx
+++ b/apps/hub/app/(app)/settings/_components/forms.tsx
@@ -23,6 +23,7 @@ import toast from "react-hot-toast";
import { CircleAlert, Trash2 } from "lucide-react";
import { deleteUser, sendVerificationLink } from "(app)/admin/user/action";
import { setStandardName } from "../../../../helper/discord";
+import { logAction } from "(auth)/login/_components/action";
export const ProfileForm = ({
user,
@@ -101,6 +102,28 @@ export const ProfileForm = ({
userId: user.id,
});
}
+ if (user.firstname !== values.firstname) {
+ await logAction("PROFILE_CHANGE", {
+ field: "firstname",
+ oldValue: user.firstname,
+ newValue: values.firstname,
+ });
+ }
+ if (user.lastname !== values.lastname) {
+ await logAction("PROFILE_CHANGE", {
+ field: "lastname",
+ oldValue: user.lastname,
+ newValue: values.lastname,
+ });
+ }
+ if (user.email !== values.email) {
+ await logAction("PROFILE_CHANGE", {
+ field: "email",
+ oldValue: user.email,
+ newValue: values.email,
+ });
+ }
+
form.reset(values);
if (user.email !== values.email) {
await sendVerificationLink(user.id);
diff --git a/apps/hub/app/(auth)/login/_components/Login.tsx b/apps/hub/app/(auth)/login/_components/Login.tsx
index df02a2c2..f22db1d0 100644
--- a/apps/hub/app/(auth)/login/_components/Login.tsx
+++ b/apps/hub/app/(auth)/login/_components/Login.tsx
@@ -9,6 +9,7 @@ import { Toaster, toast } from "react-hot-toast";
import { z } from "zod";
import { Button } from "../../../_components/ui/Button";
import { useErrorBoundary } from "react-error-boundary";
+import { logAction } from "./action";
export const Login = () => {
const { showBoundary } = useErrorBoundary();
@@ -46,6 +47,10 @@ export const Login = () => {
});
return;
}
+
+ console.log("data", data);
+
+ await logAction("LOGIN");
redirect(searchParams.get("redirect") || "/");
} catch (error) {
showBoundary(error);
diff --git a/apps/hub/app/(auth)/login/_components/action.ts b/apps/hub/app/(auth)/login/_components/action.ts
new file mode 100644
index 00000000..fa38dd87
--- /dev/null
+++ b/apps/hub/app/(auth)/login/_components/action.ts
@@ -0,0 +1,53 @@
+"use server";
+import { LOG_TYPE, prisma } from "@repo/db";
+import { getServerSession } from "api/auth/[...nextauth]/auth";
+import { randomUUID } from "crypto";
+import { cookies, headers } from "next/headers";
+
+export async function getOrSetDeviceId() {
+ const store = await cookies();
+ let deviceId = store.get("device_id")?.value;
+
+ if (!deviceId) {
+ deviceId = randomUUID();
+ store.set("device_id", deviceId, {
+ httpOnly: true,
+ secure: true,
+ sameSite: "lax",
+ path: "/",
+ maxAge: 60 * 60 * 24 * 365, // 1 Jahr
+ });
+ }
+
+ return deviceId;
+}
+
+export const logAction = async (
+ type: LOG_TYPE,
+ otherValues?: {
+ field: string;
+ oldValue: string;
+ newValue: string;
+ },
+) => {
+ const headersList = await headers();
+ const user = await getServerSession();
+
+ console.log("headers");
+
+ const deviceId = await getOrSetDeviceId();
+
+ await prisma.log.create({
+ data: {
+ type,
+ browser: headersList.get("user-agent") || "unknown",
+ userId: user?.user.id,
+ deviceId: deviceId,
+ ip:
+ headersList.get("X-Forwarded-For") ||
+ headersList.get("Forwarded") ||
+ headersList.get("X-Real-IP"),
+ ...otherValues,
+ },
+ });
+};
diff --git a/apps/hub/helper/discord.ts b/apps/hub/helper/discord.ts
index a9a6b9d4..b432906b 100644
--- a/apps/hub/helper/discord.ts
+++ b/apps/hub/helper/discord.ts
@@ -38,6 +38,16 @@ export const removeRolesFromMember = async (memberId: string, roleIds: string[])
});
};
+export const sendReportEmbed = async (reportId: number) => {
+ discordAxiosClient
+ .post("/report/admin-embed", {
+ reportId,
+ })
+ .catch((error) => {
+ console.error("Error sending report embed:", error);
+ });
+};
+
export const setStandardName = async ({
memberId,
userId,
diff --git a/packages/database/prisma/schema/log.prisma b/packages/database/prisma/schema/log.prisma
new file mode 100644
index 00000000..59911faf
--- /dev/null
+++ b/packages/database/prisma/schema/log.prisma
@@ -0,0 +1,22 @@
+model Log {
+ id Int @id @default(autoincrement())
+ type LOG_TYPE
+ userId String?
+ browser String?
+ deviceId String?
+ ip String?
+ field String?
+ oldValue String?
+ newValue String?
+ timestamp DateTime @default(now())
+
+
+ User User? @relation(fields: [userId], references: [id], onDelete: Cascade)
+ @@map(name: "logs")
+}
+
+enum LOG_TYPE {
+ LOGIN
+ PROFILE_CHANGE
+ REGISTER
+}
\ No newline at end of file
diff --git a/packages/database/prisma/schema/migrations/20260129214442_fix_penalty_user_relation/migration.sql b/packages/database/prisma/schema/migrations/20260129214442_fix_penalty_user_relation/migration.sql
new file mode 100644
index 00000000..3360abb2
--- /dev/null
+++ b/packages/database/prisma/schema/migrations/20260129214442_fix_penalty_user_relation/migration.sql
@@ -0,0 +1,2 @@
+-- AlterTable
+ALTER TABLE "users" ADD COLUMN "is_deleted" BOOLEAN NOT NULL DEFAULT false;
diff --git a/packages/database/prisma/schema/migrations/20260129221239_user_log/migration.sql b/packages/database/prisma/schema/migrations/20260129221239_user_log/migration.sql
new file mode 100644
index 00000000..4cd2e37e
--- /dev/null
+++ b/packages/database/prisma/schema/migrations/20260129221239_user_log/migration.sql
@@ -0,0 +1,16 @@
+-- CreateEnum
+CREATE TYPE "LOG_TYLE" AS ENUM ('LOGIN', 'PROFILE_CHANGE');
+
+-- CreateTable
+CREATE TABLE "logs" (
+ "id" SERIAL NOT NULL,
+ "type" "LOG_TYLE" NOT NULL,
+ "userId" TEXT,
+ "ip" TEXT,
+ "timestamp" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
+
+ CONSTRAINT "logs_pkey" PRIMARY KEY ("id")
+);
+
+-- AddForeignKey
+ALTER TABLE "logs" ADD CONSTRAINT "logs_userId_fkey" FOREIGN KEY ("userId") REFERENCES "users"("id") ON DELETE CASCADE ON UPDATE CASCADE;
diff --git a/packages/database/prisma/schema/migrations/20260129222950_add_browser/migration.sql b/packages/database/prisma/schema/migrations/20260129222950_add_browser/migration.sql
new file mode 100644
index 00000000..3a0795ea
--- /dev/null
+++ b/packages/database/prisma/schema/migrations/20260129222950_add_browser/migration.sql
@@ -0,0 +1,16 @@
+/*
+ Warnings:
+
+ - Changed the type of `type` on the `logs` table. No cast exists, the column would be dropped and recreated, which cannot be done if there is data, since the column is required.
+
+*/
+-- CreateEnum
+CREATE TYPE "LOG_TYPE" AS ENUM ('LOGIN', 'PROFILE_CHANGE', 'REGISTER');
+
+-- AlterTable
+ALTER TABLE "logs" ADD COLUMN "browser" TEXT,
+DROP COLUMN "type",
+ADD COLUMN "type" "LOG_TYPE" NOT NULL;
+
+-- DropEnum
+DROP TYPE "LOG_TYLE";
diff --git a/packages/database/prisma/schema/migrations/20260129224931_ce_id/migration.sql b/packages/database/prisma/schema/migrations/20260129224931_ce_id/migration.sql
new file mode 100644
index 00000000..a29ac825
--- /dev/null
+++ b/packages/database/prisma/schema/migrations/20260129224931_ce_id/migration.sql
@@ -0,0 +1,2 @@
+-- AlterTable
+ALTER TABLE "logs" ADD COLUMN "deviceId" TEXT;
diff --git a/packages/database/prisma/schema/migrations/20260129230237_profile_values/migration.sql b/packages/database/prisma/schema/migrations/20260129230237_profile_values/migration.sql
new file mode 100644
index 00000000..fdeea722
--- /dev/null
+++ b/packages/database/prisma/schema/migrations/20260129230237_profile_values/migration.sql
@@ -0,0 +1,4 @@
+-- AlterTable
+ALTER TABLE "logs" ADD COLUMN "field" TEXT,
+ADD COLUMN "newValue" TEXT,
+ADD COLUMN "oldValue" TEXT;
diff --git a/packages/database/prisma/schema/penalty.prisma b/packages/database/prisma/schema/penalty.prisma
index 8a208b9a..743d16c2 100644
--- a/packages/database/prisma/schema/penalty.prisma
+++ b/packages/database/prisma/schema/penalty.prisma
@@ -1,12 +1,11 @@
-model AuditLog {
+model Penalty {
id Int @id @default(autoincrement())
userId String
createdUserId String?
reportId Int?
- // Generalized action type to cover penalties and user history events
- action AuditLogAction?
- reason String?
+ type PenaltyType
+ reason String
until DateTime?
suspended Boolean @default(false)
@@ -14,18 +13,14 @@ model AuditLog {
timestamp DateTime @default(now())
// relations:
- User User @relation(fields: [userId], references: [id], onDelete: Cascade)
+ User User @relation("User", fields: [userId], references: [id], onDelete: Cascade)
CreatedUser User? @relation("CreatedPenalties", fields: [createdUserId], references: [id])
Report Report? @relation(fields: [reportId], references: [id])
}
-enum AuditLogAction {
- // Penalty actions
+enum PenaltyType {
KICK
TIME_BAN
PERMISSIONS_REVOCED
BAN
- // User history events
- USER_DELETED
- USER_PROFILE_UPDATED
}
diff --git a/packages/database/prisma/schema/report.prisma b/packages/database/prisma/schema/report.prisma
index 33c0e9a8..9264f1e6 100644
--- a/packages/database/prisma/schema/report.prisma
+++ b/packages/database/prisma/schema/report.prisma
@@ -10,8 +10,8 @@ model Report {
reviewerUserId String?
// relations:
- Sender User? @relation("SentReports", fields: [senderUserId], references: [id])
- Reported User @relation("ReceivedReports", fields: [reportedUserId], references: [id], onDelete: Cascade)
- Reviewer User? @relation("ReviewedReports", fields: [reviewerUserId], references: [id])
- AuditLog AuditLog[]
+ Sender User? @relation("SentReports", fields: [senderUserId], references: [id])
+ Reported User @relation("ReceivedReports", fields: [reportedUserId], references: [id], onDelete: Cascade)
+ Reviewer User? @relation("ReviewedReports", fields: [reviewerUserId], references: [id])
+ Penalties Penalty[]
}
diff --git a/packages/database/prisma/schema/user.prisma b/packages/database/prisma/schema/user.prisma
index f8023cb3..45cf2206 100644
--- a/packages/database/prisma/schema/user.prisma
+++ b/packages/database/prisma/schema/user.prisma
@@ -82,14 +82,13 @@ model User {
ConnectedDispatcher ConnectedDispatcher[]
ConnectedAircraft ConnectedAircraft[]
PositionLog PositionLog[]
- Penaltys AuditLog[]
- CreatedAuditLogEntrys AuditLog[] @relation("CreatedAuditLogEntrys")
- Bookings Booking[]
- auditLogs AuditLog[]
+ Penaltys Penalty[] @relation("User")
+ CreatedPenalties Penalty[] @relation("CreatedPenalties")
+ Logs Log[]
+ Bookings Booking[]
DiscordAccount DiscordAccount?
FormerDiscordAccounts FormerDiscordAccount[]
- auditLogs AuditLog[]
@@map(name: "users")
}