Merge pull request #165 from VAR-Virtual-Air-Rescue/staging

This commit was merged in pull request #165.
This commit is contained in:
PxlLoewe
2026-02-08 23:46:22 +01:00
committed by GitHub
20 changed files with 249 additions and 95 deletions

View File

@@ -3,6 +3,7 @@ import { io } from "index";
import cron from "node-cron"; import cron from "node-cron";
import { setUserStandardNamePermissions } from "routes/helper"; import { setUserStandardNamePermissions } from "routes/helper";
import { changeMemberRoles } from "routes/member"; import { changeMemberRoles } from "routes/member";
import client from "./discord";
const removeMission = async (id: number, reason: string) => { const removeMission = async (id: number, reason: string) => {
const log: MissionLog = { const log: MissionLog = {
@@ -121,6 +122,37 @@ const removeClosedMissions = async () => {
return removeMission(mission.id, "dem freimelden aller Stationen"); return removeMission(mission.id, "dem freimelden aller Stationen");
}); });
}; };
const syncDiscordImgUrls = async () => {
try {
const discordAccounts = await prisma.discordAccount.findMany({
where: {
updatedAt: {
lt: new Date(Date.now() - 7 * 24 * 60 * 60 * 1000),
},
User: {
isNot: null,
},
},
});
for (const account of discordAccounts) {
client.users.fetch(account.discordId).then((discordUser) => {
const nextAvatar = discordUser?.avatar ?? null;
if (typeof nextAvatar !== "string") return;
if (nextAvatar === account.avatar) return;
prisma.discordAccount.update({
where: {
id: account.id,
},
data: {
avatar: nextAvatar,
},
});
});
}
} catch (error) {}
};
const removeConnectedAircrafts = async () => { const removeConnectedAircrafts = async () => {
const connectedAircrafts = await prisma.connectedAircraft.findMany({ const connectedAircrafts = await prisma.connectedAircraft.findMany({
where: { where: {
@@ -216,7 +248,13 @@ const removePermissionsForBannedUsers = async () => {
} }
}; };
removePermissionsForBannedUsers(); cron.schedule("0 0 * * *", async () => {
try {
await syncDiscordImgUrls();
} catch (error) {
console.error("Error on daily cron job:", error);
}
});
cron.schedule("*/1 * * * *", async () => { cron.schedule("*/1 * * * *", async () => {
try { try {

View File

@@ -45,7 +45,7 @@ router.post("/rename", async (req: Request, res: Response) => {
console.log(`Member ${member.id} renamed to ${newName}`); console.log(`Member ${member.id} renamed to ${newName}`);
res.status(200).json({ message: "Member renamed successfully" }); res.status(200).json({ message: "Member renamed successfully" });
} catch (error) { } catch (error) {
console.error("Error renaming member:", error); console.error("Error renaming member:", (error as Error).message);
res.status(500).json({ error: "Failed to rename member" }); res.status(500).json({ error: "Failed to rename member" });
} }
}); });
@@ -84,7 +84,7 @@ const handleRoleChange = (action: "add" | "remove") => async (req: Request, res:
const result = await changeMemberRoles(memberId, roleIds, action); const result = await changeMemberRoles(memberId, roleIds, action);
res.status(200).json(result); res.status(200).json(result);
} catch (error) { } catch (error) {
console.error(`Error ${action}ing roles:`, error); console.error(`Error ${action}ing roles:`, (error as Error).message);
res.status(500).json({ error: `Failed to ${action} roles` }); res.status(500).json({ error: `Failed to ${action} roles` });
} }
}; };

View File

@@ -43,14 +43,20 @@ router.post("/admin-embed", async (req, res) => {
{ name: "angemeldet als", value: report.reportedUserRole, inline: true }, { name: "angemeldet als", value: report.reportedUserRole, inline: true },
{ {
name: "gemeldet von", name: "gemeldet von",
value: `${report.Sender?.firstname} ${report.Sender?.lastname} (${report.Sender?.publicId})`, value: report.Sender
? `${report.Sender?.firstname} ${report.Sender?.lastname} (${report.Sender?.publicId})`
: "System",
}, },
) )
.setFooter({ .setFooter({
text: "Bitte reagiere mit 🫡, wenn du den Report bearbeitet hast, oder mit ✅, wenn er abgeschlossen ist.", text: "Bitte reagiere mit 🫡, wenn du den Report bearbeitet hast, oder mit ✅, wenn er abgeschlossen ist.",
}) })
.setTimestamp(new Date(report.timestamp)) .setTimestamp(new Date(report.timestamp));
.setColor("DarkRed"); if (report.reviewed) {
embed.setColor("DarkGreen");
} else {
embed.setColor("DarkRed");
}
const reportsChannel = await client.channels.fetch(process.env.DISCORD_REPORT_CHANNEL!); const reportsChannel = await client.channels.fetch(process.env.DISCORD_REPORT_CHANNEL!);
if (!reportsChannel || !reportsChannel.isSendable()) { if (!reportsChannel || !reportsChannel.isSendable()) {
@@ -59,7 +65,9 @@ router.post("/admin-embed", async (req, res) => {
} }
const message = await reportsChannel.send({ embeds: [embed] }); const message = await reportsChannel.send({ embeds: [embed] });
message.react("🫡").catch(console.error); message.react("🫡").catch(console.error);
message.react("✅").catch(console.error); if (!report.reviewed) {
message.react("✅").catch(console.error);
}
res.json({ res.json({
message: "Report embed sent to Discord channel", message: "Report embed sent to Discord channel",
}); });

View File

@@ -1,4 +1,4 @@
import axios from "axios"; import axios, { AxiosError } from "axios";
const discordAxiosClient = axios.create({ const discordAxiosClient = axios.create({
baseURL: process.env.CORE_SERVER_URL, baseURL: process.env.CORE_SERVER_URL,
@@ -11,7 +11,10 @@ export const renameMember = async (memberId: string, newName: string) => {
newName, newName,
}) })
.catch((error) => { .catch((error) => {
console.error("Error renaming member:", error); console.error(
"Error renaming member:",
(error as AxiosError<{ error: string }>).response?.data.error || error.message,
);
}); });
}; };
@@ -22,7 +25,10 @@ export const addRolesToMember = async (memberId: string, roleIds: string[]) => {
roleIds, roleIds,
}) })
.catch((error) => { .catch((error) => {
console.error("Error adding roles to member:", error); console.error(
"Error adding roles to member:",
(error as AxiosError<{ error: string }>).response?.data.error || error.message,
);
}); });
}; };
@@ -33,7 +39,10 @@ export const removeRolesFromMember = async (memberId: string, roleIds: string[])
roleIds, roleIds,
}) })
.catch((error) => { .catch((error) => {
console.error("Error removing roles from member:", error); console.error(
"Error removing roles from member:",
(error as AxiosError<{ error: string }>).response?.data.error || error.message,
);
}); });
}; };
@@ -43,6 +52,9 @@ export const sendReportEmbed = async (reportId: number) => {
reportId, reportId,
}) })
.catch((error) => { .catch((error) => {
console.error("Error removing roles from member:", error); console.error(
"Error removing roles from member:",
(error as AxiosError<{ error: string }>).response?.data.error || error.message,
);
}); });
}; };

View File

@@ -154,6 +154,15 @@ router.post("/:id/send-sds-message", async (req, res) => {
}, },
}); });
const user = await prisma.user.findFirst({
where: { publicId: sdsMessage.data.user.publicId, firstname: sdsMessage.data.user.firstname },
});
if (!user) {
res.status(404).json({ error: "User not found" });
return;
}
io.to( io.to(
sdsMessage.data.direction === "to-lst" ? "dispatchers" : `station:${sdsMessage.data.stationId}`, sdsMessage.data.direction === "to-lst" ? "dispatchers" : `station:${sdsMessage.data.stationId}`,
).emit(sdsMessage.data.direction === "to-lst" ? "notification" : "sds-status", { ).emit(sdsMessage.data.direction === "to-lst" ? "notification" : "sds-status", {
@@ -163,6 +172,7 @@ router.post("/:id/send-sds-message", async (req, res) => {
data: { data: {
aircraftId: parseInt(id), aircraftId: parseInt(id),
stationId: sdsMessage.data.stationId, stationId: sdsMessage.data.stationId,
userId: user.id,
}, },
} as NotificationPayload); } as NotificationPayload);

View File

@@ -3,8 +3,11 @@ import Link from "next/link";
import { prisma } from "@repo/db"; import { prisma } from "@repo/db";
import { ChangelogWrapper } from "_components/navbar/ChangelogWrapper"; import { ChangelogWrapper } from "_components/navbar/ChangelogWrapper";
import ModeSwitchDropdown from "_components/navbar/ModeSwitchDropdown"; import ModeSwitchDropdown from "_components/navbar/ModeSwitchDropdown";
import { getServerSession } from "api/auth/[...nextauth]/auth";
import AdminPanel from "_components/navbar/AdminPanel";
export default async function Navbar({ children }: { children: React.ReactNode }) { export default async function Navbar({ children }: { children: React.ReactNode }) {
const session = await getServerSession();
const latestChangelog = await prisma.changelog.findFirst({ const latestChangelog = await prisma.changelog.findFirst({
orderBy: { orderBy: {
createdAt: "desc", createdAt: "desc",
@@ -17,6 +20,7 @@ export default async function Navbar({ children }: { children: React.ReactNode }
<p className="text-xl font-semibold normal-case">VAR Operations Center</p> <p className="text-xl font-semibold normal-case">VAR Operations Center</p>
<ChangelogWrapper latestChangelog={latestChangelog} /> <ChangelogWrapper latestChangelog={latestChangelog} />
</div> </div>
{session?.user.permissions.includes("ADMIN_KICK") && <AdminPanel />}
</div> </div>
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
{children} {children}

View File

@@ -1,7 +1,7 @@
"use client"; "use client";
import { useEffect, useRef, useState } from "react"; import { useEffect, useRef, useState } from "react";
import { GearIcon } from "@radix-ui/react-icons"; import { GearIcon } from "@radix-ui/react-icons";
import { SettingsIcon, Volume2 } from "lucide-react"; import { Info, SettingsIcon, Volume2 } from "lucide-react";
import MicVolumeBar from "_components/MicVolumeIndication"; import MicVolumeBar from "_components/MicVolumeIndication";
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
import { editUserAPI, getUserAPI } from "_querys/user"; import { editUserAPI, getUserAPI } from "_querys/user";
@@ -27,7 +27,6 @@ export const SettingsBtn = () => {
onSuccess: async () => { onSuccess: async () => {
await queryClient.invalidateQueries({ queryKey: ["user", session.data?.user.id] }); await queryClient.invalidateQueries({ queryKey: ["user", session.data?.user.id] });
}, },
}); });
useEffect(() => { useEffect(() => {
@@ -82,10 +81,14 @@ export const SettingsBtn = () => {
useEffect(() => { useEffect(() => {
const setDevices = async () => { const setDevices = async () => {
if (typeof navigator !== "undefined" && navigator.mediaDevices?.enumerateDevices) { if (typeof navigator !== "undefined" && navigator.mediaDevices?.enumerateDevices) {
const stream = await navigator.mediaDevices.getUserMedia({ video: false, audio: true }); try {
const devices = await navigator.mediaDevices.enumerateDevices(); const stream = await navigator.mediaDevices.getUserMedia({ video: false, audio: true });
setInputDevices(devices.filter((d) => d.kind === "audioinput")); const devices = await navigator.mediaDevices.enumerateDevices();
stream.getTracks().forEach((track) => track.stop()); setInputDevices(devices.filter((d) => d.kind === "audioinput"));
stream.getTracks().forEach((track) => track.stop());
} catch (error) {
console.error("Error accessing media devices.", error);
}
} }
}; };
@@ -214,7 +217,18 @@ export const SettingsBtn = () => {
setSettingsPartial({ useHPGAsDispatcher: e.target.checked }); setSettingsPartial({ useHPGAsDispatcher: e.target.checked });
}} }}
/> />
HPG als Disponent verwenden HPG Validierung verwenden{" "}
<div
className="tooltip tooltip-warning"
data-tip="Achtung! Mit der Client Version v2.0.1.0 werden Einsätze auch ohne Validierung an die
HPG gesendet. Die Validierung über die HPG kann zu Verzögerungen bei der
Einsatzübermittlung führen, insbesondere wenn das HPG script den Einsatz nicht sofort
validieren kann. Es wird empfohlen, diese Option nur zu aktivieren, wenn es Probleme
mit der Einsatzübermittlung gibt oder wenn die HPG Validierung ausdrücklich gewünscht
wird."
>
<Info className="text-error" size={16} />
</div>
</div> </div>
<div className="modal-action flex justify-between"> <div className="modal-action flex justify-between">

View File

@@ -78,6 +78,7 @@ export const StatusToast = ({ event, t }: { event: StationStatus; t: Toast }) =>
}); });
}, },
}); });
console.log("Audio Room:", audioRoom, participants, livekitUser, event);
useEffect(() => { useEffect(() => {
let soundRef: React.RefObject<HTMLAudioElement | null> | null = null; let soundRef: React.RefObject<HTMLAudioElement | null> | null = null;
@@ -113,7 +114,6 @@ export const StatusToast = ({ event, t }: { event: StationStatus; t: Toast }) =>
}; };
}, [event.status, livekitUser?.roomName, audioRoom, t.id]); }, [event.status, livekitUser?.roomName, audioRoom, t.id]);
console.log(connectedAircraft, station);
if (!connectedAircraft || !station || !session.data) return null; if (!connectedAircraft || !station || !session.data) return null;
return ( return (
<BaseNotification> <BaseNotification>

View File

@@ -201,11 +201,23 @@ export const useAudioStore = create<TalkState>((set, get) => ({
}); });
} }
const inputStream = await navigator.mediaDevices.getUserMedia({ let inputStream = await navigator.mediaDevices
audio: { .getUserMedia({
deviceId: get().settings.micDeviceId ?? undefined, audio: {
}, deviceId: get().settings.micDeviceId ?? undefined,
}); },
})
.catch((e) => {
console.error("Konnte das Audio-Gerät nicht öffnen:", e);
return null;
});
if (!inputStream) {
inputStream = await navigator.mediaDevices.getUserMedia({ audio: true }).catch((e) => {
console.error("Konnte das Audio-Gerät nicht öffnen:", e);
return null;
});
}
if (!inputStream) throw new Error("Konnte das Audio-Gerät nicht öffnen");
// Funk-Effekt anwenden // Funk-Effekt anwenden
const radioStream = getRadioStream(inputStream, get().settings.micVolume); const radioStream = getRadioStream(inputStream, get().settings.micVolume);
if (!radioStream) throw new Error("Konnte Funkstream nicht erzeugen"); if (!radioStream) throw new Error("Konnte Funkstream nicht erzeugen");

View File

@@ -1,6 +1,5 @@
/// <reference types="next" /> /// <reference types="next" />
/// <reference types="next/image-types/global" /> /// <reference types="next/image-types/global" />
import "./.next/dev/types/routes.d.ts";
// NOTE: This file should not be edited // NOTE: This file should not be edited
// see https://nextjs.org/docs/app/api-reference/config/typescript for more information. // see https://nextjs.org/docs/app/api-reference/config/typescript for more information.

View File

@@ -84,6 +84,7 @@ const updateParticipantMoodleResults = async () => {
}; };
CronJob.from({ cronTime: "0 * * * *", onTick: syncMoodleIds, start: true }); CronJob.from({ cronTime: "0 * * * *", onTick: syncMoodleIds, start: true });
CronJob.from({ CronJob.from({
cronTime: "*/1 * * * *", cronTime: "*/1 * * * *",
onTick: async () => { onTick: async () => {

View File

@@ -5,6 +5,7 @@ import { renderPasswordChanged } from "./mail-templates/PasswordChanged";
import { renderVerificationCode } from "./mail-templates/ConfirmEmail"; import { renderVerificationCode } from "./mail-templates/ConfirmEmail";
import { renderBannNotice } from "modules/mail-templates/Bann"; import { renderBannNotice } from "modules/mail-templates/Bann";
import { renderTimeBanNotice } from "modules/mail-templates/TimeBann"; import { renderTimeBanNotice } from "modules/mail-templates/TimeBann";
import Mail from "nodemailer/lib/mailer";
let transporter: nodemailer.Transporter | null = null; let transporter: nodemailer.Transporter | null = null;
@@ -23,8 +24,9 @@ const initTransporter = () => {
pass: process.env.MAIL_PASSWORD, pass: process.env.MAIL_PASSWORD,
}, },
}); });
transporter.on("error", (err) => { transporter.on("error", (err) => {
console.error("Mail occurred:", err); console.error("Mail error:", err);
}); });
transporter.on("idle", () => { transporter.on("idle", () => {
console.log("Mail Idle"); console.log("Mail Idle");

View File

@@ -4,6 +4,7 @@ import { editReport } from "(app)/admin/report/actions";
import { zodResolver } from "@hookform/resolvers/zod"; import { zodResolver } from "@hookform/resolvers/zod";
import { Report as IReport, Prisma, User } from "@repo/db"; import { Report as IReport, Prisma, User } from "@repo/db";
import { ReportSchema, Report as IReportZod } from "@repo/db/zod"; import { ReportSchema, Report as IReportZod } from "@repo/db/zod";
import { cn } from "@repo/shared-components";
import { PaginatedTable } from "_components/PaginatedTable"; import { PaginatedTable } from "_components/PaginatedTable";
import { Button } from "_components/ui/Button"; import { Button } from "_components/ui/Button";
import { Switch } from "_components/ui/Switch"; import { Switch } from "_components/ui/Switch";
@@ -31,14 +32,23 @@ export const ReportSenderInfo = ({
</Link> </Link>
<span className="text-primary">{report.reportedUserRole}</span> <span className="text-primary">{report.reportedUserRole}</span>
</h2> </h2>
<div className="textarea w-full text-left">{report.text}</div> <div className="textarea w-full whitespace-pre-wrap text-left">{report.text}</div>
<Link {Sender ? (
href={`/admin/user/${Sender?.id}`} <Link
className="link link-hover text-right text-sm text-gray-600" href={`/admin/user/${Sender?.id}`}
> className={cn(
gemeldet von Nutzer {Sender?.firstname} {Sender?.lastname} ({Sender?.publicId}) am{" "} "link link-hover text-right text-sm text-gray-600",
{new Date(report.timestamp).toLocaleString()} !Sender && "text-green-300",
</Link> )}
>
gemeldet von Nutzer {Sender.firstname} {Sender.lastname} ({Sender.publicId}) am{" "}
{new Date(report.timestamp).toLocaleString()}
</Link>
) : (
<p className="text-right text-sm text-green-300">
gemeldet vom System am {new Date(report.timestamp).toLocaleString()}
</p>
)}
</div> </div>
); );
}; };

View File

@@ -25,8 +25,12 @@ export const reportColumns: ColumnDef<Report & { Sender?: User; Reported: User }
header: "Sender", header: "Sender",
cell: ({ row }) => { cell: ({ row }) => {
const user = row.original.Sender; const user = row.original.Sender;
if (!user) return "Unbekannt"; if (!user) return <p className="text-green-300">System</p>;
return `${user.firstname} ${user.lastname} (${user.publicId})`; return (
<Link
href={`/admin/user/${user.id}`}
>{`${user.firstname} ${user.lastname} (${user.publicId})`}</Link>
);
}, },
}, },
{ {

View File

@@ -47,30 +47,42 @@ export const AccountLog = ({ sameIPLogs, userId }: { sameIPLogs: Log[]; userId:
</div> </div>
} }
getFilter={(searchTerm) => { getFilter={(searchTerm) => {
return { if (onlyImportant) {
AND: [ return {
onlyImportant AND: [
? { { ip: { contains: searchTerm } },
type: { { browser: { contains: searchTerm } },
in: ["REGISTER", "PROFILE_CHANGE"], {
}, id: {
} in: sameIPLogs
: {}, .filter((log) => log.id.toString().includes(searchTerm))
{ .map((log) => log.id),
AND: [
{ ip: { contains: searchTerm } },
{ browser: { contains: searchTerm } },
{
id: {
in: sameIPLogs
.filter((log) => log.id.toString().includes(searchTerm))
.map((log) => log.id),
},
}, },
], },
}, ],
], } as Prisma.LogWhereInput;
} as Prisma.LogWhereInput; } else {
return {
OR: [
{
AND: [
{ ip: { contains: searchTerm } },
{ browser: { contains: searchTerm } },
{
id: {
in: sameIPLogs
.filter((log) => log.id.toString().includes(searchTerm))
.map((log) => log.id),
},
},
],
},
{
userId: userId,
},
],
} as Prisma.LogWhereInput;
}
}} }}
include={{ include={{
User: true, User: true,

View File

@@ -704,23 +704,15 @@ export const AdminForm = ({
<div role="alert" className="alert alert-warning alert-outline flex flex-col"> <div role="alert" className="alert alert-warning alert-outline flex flex-col">
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<TriangleAlert /> <TriangleAlert />
{openBans.map((ban) => ( <div>
<div key={ban.id}> <h3 className="text-lg font-semibold">Account gelöscht</h3>
<h3 className="text-lg font-semibold">Account gelöscht</h3> </div>
</div> <div>
))} <h3 className="text-lg font-semibold">
{openTimebans.map((timeban) => ( Dieser Account ist als gelöscht markiert, der Nutzer kann sich nicht mehr anmelden.
<div key={timeban.id}> </h3>
<h3 className="text-lg font-semibold"> </div>
Dieser Account ist als gelöscht markiert, der Nutzer kann sich nicht mehr
anmelden.
</h3>
</div>
))}
</div> </div>
<p className="text-sm text-gray-400">
Achtung! Die Strafe(n) sind aktiv, die Rechte des Nutzers müssen nicht angepasst werden!
</p>
</div> </div>
)} )}
{(user.CanonicalUser || (user.Duplicates && user.Duplicates.length > 0)) && ( {(user.CanonicalUser || (user.Duplicates && user.Duplicates.length > 0)) && (

View File

@@ -407,8 +407,11 @@ export const DeleteForm = ({
export const PasswordForm = (): React.JSX.Element => { export const PasswordForm = (): React.JSX.Element => {
const schema = z.object({ const schema = z.object({
password: z.string().min(2).max(30), password: z.string().min(2).max(30),
newPassword: z.string().min(2).max(30), newPassword: z
newPasswordConfirm: z.string().min(2).max(30), .string()
.min(8, { message: "Das Passwort muss mindestens 8 Zeichen lang sein" })
.max(30),
newPasswordConfirm: z.string().min(8).max(30),
}); });
const [isLoading, setIsLoading] = useState(false); const [isLoading, setIsLoading] = useState(false);
type IFormInput = z.infer<typeof schema>; type IFormInput = z.infer<typeof schema>;

View File

@@ -55,18 +55,45 @@ export const logAction = async (
}, },
}); });
if (existingLogs.length > 0 && user?.user.id) { if (existingLogs.length > 0 && user?.user.id) {
// Möglicherweise ein doppelter Account, Report erstellen const existingReport = await prisma.report.findFirst({
const report = await prisma.report.create({ where: {
data: { reportedUserId: user.user.id,
text: `Möglicher doppelter Account erkannt bei Login-Versuch.\n\nÜbereinstimmende Logs:\n${existingLogs reportedUserRole: {
.map((log) => `- Log ID: ${log.id}, IP: ${log.ip}, Zeitstempel: ${log.timestamp}`) contains: "Doppelter Account Verdacht",
.join("\n")}`, },
reportedUserId: user?.user.id,
reportedUserRole: "LOGIN - Doppelter Account Verdacht",
}, },
}); });
// keine Doppel-Reports für denselben Nutzer erstellen
if (!existingReport) {
// Möglicherweise ein doppelter Account, Report erstellen
const report = await prisma.report.create({
data: {
text: `Möglicher doppelter Account erkannt bei Login-Versuch.\n\nÜbereinstimmende Logs:\n${existingLogs
.map((log) => `- Log ID: ${log.id}, IP: ${log.ip}, Zeitstempel: ${log.timestamp}`)
.join("\n")}`,
reportedUserId: user?.user.id,
reportedUserRole: "LOGIN - Doppelter Account Verdacht",
},
});
await sendReportEmbed(report.id); await sendReportEmbed(report.id);
} else {
// Update report and send it again to Discord
const updatedReport = await prisma.report.update({
where: {
id: existingReport.id,
},
data: {
text: `Möglicher doppelter Account erkannt bei Login-Versuch.\n\nÜbereinstimmende Logs:\n${existingLogs
.map(
(log) =>
`- Log ID: ${log.id}, IP: ${log.ip}, Zeitstempel: ${new Date(log.timestamp).toLocaleString("de-DE")}`,
)
.join("\n")}`,
},
});
await sendReportEmbed(updatedReport.id);
}
} }
} }

View File

@@ -49,8 +49,8 @@ export const Register = () => {
.refine((val) => val.length === 0 || val.includes(" ") || /^[A-ZÄÖÜ]/.test(val), { .refine((val) => val.length === 0 || val.includes(" ") || /^[A-ZÄÖÜ]/.test(val), {
message: "Der Nachname muss mit einem Großbuchstaben beginnen", message: "Der Nachname muss mit einem Großbuchstaben beginnen",
}), }),
password: z.string().min(12, { password: z.string().min(8, {
message: "Das Passwort muss mindestens 12 Zeichen lang sein", message: "Das Passwort muss mindestens 8 Zeichen lang sein",
}), }),
passwordConfirm: z.string(), passwordConfirm: z.string(),
}) })

View File

@@ -1,6 +1,6 @@
import axios from "axios"; import axios from "axios";
import { NextRequest, NextResponse } from "next/server"; import { NextRequest, NextResponse } from "next/server";
import { DiscordAccount, prisma } from "@repo/db"; import { prisma } from "@repo/db";
import { getServerSession } from "../auth/[...nextauth]/auth"; import { getServerSession } from "../auth/[...nextauth]/auth";
import { setStandardName } from "../../../helper/discord"; import { setStandardName } from "../../../helper/discord";
import { getUserPenaltys } from "@repo/shared-components"; import { getUserPenaltys } from "@repo/shared-components";
@@ -52,8 +52,7 @@ export const GET = async (req: NextRequest) => {
}, },
}); });
const discordObject = { const discordData = {
userId: session.user.id,
accessToken: authData.access_token, accessToken: authData.access_token,
refreshToken: authData.refresh_token, refreshToken: authData.refresh_token,
discordId: discordUser.id, discordId: discordUser.id,
@@ -63,12 +62,19 @@ export const GET = async (req: NextRequest) => {
globalName: discordUser.global_name || discordUser.username, globalName: discordUser.global_name || discordUser.username,
verified: discordUser.verified, verified: discordUser.verified,
tokenType: authData.token_type, tokenType: authData.token_type,
} as DiscordAccount; };
await prisma.discordAccount.upsert({ await prisma.discordAccount.upsert({
where: { discordId: discordUser.id }, where: { discordId: discordUser.id },
update: discordObject, // Updates if found update: discordData, // Updates if found
create: discordObject, // Creates if not found create: {
...discordData,
User: {
connect: {
id: session.user.id,
},
},
}, // Creates if not found
}); });
const user = await prisma.user.findUnique({ const user = await prisma.user.findUnique({
where: { id: session.user.id }, where: { id: session.user.id },