completed user admin page
This commit is contained in:
@@ -14,7 +14,6 @@ export default async function RootLayout({
|
|||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
}>) {
|
}>) {
|
||||||
const session = await getServerSession();
|
const session = await getServerSession();
|
||||||
console.log(session);
|
|
||||||
if (!session) {
|
if (!session) {
|
||||||
redirect("/login");
|
redirect("/login");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,6 @@
|
|||||||
import { create } from "zustand";
|
import { create } from "zustand";
|
||||||
import { socket } from "../(dispatch)/socket";
|
import { socket } from "../(dispatch)/socket";
|
||||||
|
|
||||||
console.log("connectionStore");
|
|
||||||
|
|
||||||
interface ConnectionStore {
|
interface ConnectionStore {
|
||||||
isConnected: boolean;
|
isConnected: boolean;
|
||||||
connect: (uid: string) => Promise<void>;
|
connect: (uid: string) => Promise<void>;
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
API_PORT=
|
||||||
MOODLE_TOKEN=
|
MOODLE_TOKEN=
|
||||||
MOODLE_URL=
|
MOODLE_URL=
|
||||||
MAIL_SERVER=
|
MAIL_SERVER=
|
||||||
|
|||||||
@@ -1,5 +1,17 @@
|
|||||||
import "dotenv/config";
|
import "dotenv/config";
|
||||||
import "modules/chron";
|
import "modules/chron";
|
||||||
|
import express from "express";
|
||||||
|
import cors from "cors";
|
||||||
|
import router from "routes/router";
|
||||||
|
|
||||||
// Add API eventually
|
const app = express();
|
||||||
console.log("VAR hub Server started");
|
|
||||||
|
app.use(express.json());
|
||||||
|
app.use(cors());
|
||||||
|
|
||||||
|
app.use(router);
|
||||||
|
|
||||||
|
const port = process.env.API_PORT || 3003;
|
||||||
|
app.listen(port, () => {
|
||||||
|
console.log(`Server is running on port ${port}`);
|
||||||
|
});
|
||||||
|
|||||||
202
apps/hub-server/modules/mail-templates/PasswordChanged.tsx
Normal file
202
apps/hub-server/modules/mail-templates/PasswordChanged.tsx
Normal file
@@ -0,0 +1,202 @@
|
|||||||
|
import * as React from "react";
|
||||||
|
import { User } from "@repo/db";
|
||||||
|
import { Html, render } from "@react-email/components";
|
||||||
|
|
||||||
|
const styles = `
|
||||||
|
* {
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
a[x-apple-data-detectors] {
|
||||||
|
color: inherit !important;
|
||||||
|
text-decoration: inherit !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
#MessageViewBody a {
|
||||||
|
color: inherit;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
p {
|
||||||
|
line-height: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
.desktop_hide,
|
||||||
|
.desktop_hide table {
|
||||||
|
mso-hide: all;
|
||||||
|
display: none;
|
||||||
|
max-height: 0px;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.image_block img + div {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
sup,
|
||||||
|
sub {
|
||||||
|
font-size: 75%;
|
||||||
|
line-height: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.menu_block.desktop_hide .menu-links span {
|
||||||
|
mso-hide: all;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 700px) {
|
||||||
|
.desktop_hide table.icons-inner {
|
||||||
|
display: inline-block !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icons-inner {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icons-inner td {
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const Template = ({ user, password }: { user: User; password: string }) => (
|
||||||
|
<Html lang="de">
|
||||||
|
<meta content="text/html; charset=utf-8" httpEquiv="Content-Type" />
|
||||||
|
<meta content="width=device-width, initial-scale=1.0" name="viewport" />
|
||||||
|
<link
|
||||||
|
href="https://fonts.googleapis.com/css?family=Montserrat"
|
||||||
|
rel="stylesheet"
|
||||||
|
type="text/css"
|
||||||
|
/>
|
||||||
|
<style>{styles}</style>
|
||||||
|
<body style={{ backgroundColor: "#FFFFFF", margin: 0, padding: 0 }}>
|
||||||
|
<table width="100%">
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<table
|
||||||
|
align="center"
|
||||||
|
width="680"
|
||||||
|
style={{ margin: "0 auto", color: "#000000" }}
|
||||||
|
>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td style={{ textAlign: "center", paddingTop: "30px" }}>
|
||||||
|
<img
|
||||||
|
src={`${process.env.HUB_URL}/mail/var_logo.png`}
|
||||||
|
alt="Logo"
|
||||||
|
width="80"
|
||||||
|
style={{ display: "block", margin: "0 auto" }}
|
||||||
|
/>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td
|
||||||
|
style={{
|
||||||
|
textAlign: "center",
|
||||||
|
fontSize: "46px",
|
||||||
|
color: "#011936",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<strong>Passwort geändert</strong>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td
|
||||||
|
style={{
|
||||||
|
textAlign: "center",
|
||||||
|
fontSize: "30px",
|
||||||
|
color: "#011936",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Hallo {user.firstname},
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td
|
||||||
|
style={{
|
||||||
|
textAlign: "center",
|
||||||
|
fontSize: "18px",
|
||||||
|
color: "#011936",
|
||||||
|
padding: "20px",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Dein Passwort wurde erfolgreich geändert. Wenn du diese
|
||||||
|
Änderung nicht vorgenommen hast, kontaktiere bitte sofort
|
||||||
|
unseren Support.
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td
|
||||||
|
style={{
|
||||||
|
textAlign: "center",
|
||||||
|
fontSize: "18px",
|
||||||
|
color: "#011936",
|
||||||
|
padding: "20px",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Dein neues Passwort lautet: <strong>{password}</strong>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td style={{ textAlign: "center", paddingTop: "20px" }}>
|
||||||
|
<a
|
||||||
|
href="https://your-platform.com"
|
||||||
|
style={{
|
||||||
|
padding: "10px",
|
||||||
|
textDecoration: "none",
|
||||||
|
borderRadius: "20px",
|
||||||
|
color: "#011936",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Impressum
|
||||||
|
</a>
|
||||||
|
<span style={{ margin: "0 10px" }}>|</span>
|
||||||
|
<a
|
||||||
|
href="https://your-platform.com"
|
||||||
|
style={{
|
||||||
|
padding: "10px",
|
||||||
|
textDecoration: "none",
|
||||||
|
borderRadius: "20px",
|
||||||
|
color: "#011936",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Datenschutzerklärung
|
||||||
|
</a>
|
||||||
|
<span style={{ margin: "0 10px" }}>|</span>
|
||||||
|
<a
|
||||||
|
href="https://your-platform.com"
|
||||||
|
style={{
|
||||||
|
padding: "10px",
|
||||||
|
textDecoration: "none",
|
||||||
|
borderRadius: "20px",
|
||||||
|
color: "#011936",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Knowledgebase
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</body>
|
||||||
|
</Html>
|
||||||
|
);
|
||||||
|
|
||||||
|
export function renderPasswordChanged({
|
||||||
|
user,
|
||||||
|
password,
|
||||||
|
}: {
|
||||||
|
user: User;
|
||||||
|
password: string;
|
||||||
|
}) {
|
||||||
|
return render(<Template user={user} password={password} />);
|
||||||
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
import { Event, User } from "@repo/db";
|
import { Event, User } from "@repo/db";
|
||||||
import nodemailer from "nodemailer";
|
import nodemailer from "nodemailer";
|
||||||
import { renderCourseCompleted } from "./mail-templates/CourseCompleted";
|
import { renderCourseCompleted } from "./mail-templates/CourseCompleted";
|
||||||
|
import { renderPasswordChanged } from "./mail-templates/PasswordChanged";
|
||||||
|
|
||||||
let transporter: nodemailer.Transporter | null = null;
|
let transporter: nodemailer.Transporter | null = null;
|
||||||
|
|
||||||
@@ -42,18 +43,39 @@ export const sendCourseCompletedEmail = async (
|
|||||||
console.error("Transporter is not initialized");
|
console.error("Transporter is not initialized");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
transporter.sendMail(
|
sendMail(to, `Kurs ${event.name} erfolgreich abgeschlossen`, emailHtml);
|
||||||
{
|
|
||||||
from: process.env.MAIL_USER,
|
|
||||||
to,
|
|
||||||
subject: `Kurs ${event.name} erfolgreich abgeschlossen`,
|
|
||||||
html: emailHtml,
|
|
||||||
},
|
|
||||||
(error, info) => {
|
|
||||||
if (error) {
|
|
||||||
console.error("Error sending email:", error);
|
|
||||||
} else {
|
|
||||||
}
|
|
||||||
},
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const sendPasswordChanged = async (
|
||||||
|
to: string,
|
||||||
|
user: User,
|
||||||
|
password: string,
|
||||||
|
) => {
|
||||||
|
const emailHtml = await renderPasswordChanged({ user, password });
|
||||||
|
|
||||||
|
await sendMail(to, `Dein Passwort wurde geändert`, emailHtml);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const sendMail = async (to: string, subject: string, html: string) =>
|
||||||
|
new Promise<void>(async (resolve, reject) => {
|
||||||
|
if (!transporter) {
|
||||||
|
console.error("Transporter is not initialized");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await transporter.sendMail(
|
||||||
|
{
|
||||||
|
from: process.env.MAIL_USER,
|
||||||
|
to,
|
||||||
|
subject: subject,
|
||||||
|
html,
|
||||||
|
},
|
||||||
|
(error, info) => {
|
||||||
|
if (error) {
|
||||||
|
reject(error);
|
||||||
|
console.error("Error sending email:", error);
|
||||||
|
} else {
|
||||||
|
resolve();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|||||||
@@ -18,8 +18,10 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@react-email/components": "^0.0.33",
|
"@react-email/components": "^0.0.33",
|
||||||
"axios": "^1.7.9",
|
"axios": "^1.7.9",
|
||||||
|
"cors": "^2.8.5",
|
||||||
"cron": "^4.1.0",
|
"cron": "^4.1.0",
|
||||||
"dotenv": "^16.4.7",
|
"dotenv": "^16.4.7",
|
||||||
|
"express": "^4.21.2",
|
||||||
"nodemailer": "^6.10.0",
|
"nodemailer": "^6.10.0",
|
||||||
"react": "^19.0.0"
|
"react": "^19.0.0"
|
||||||
}
|
}
|
||||||
|
|||||||
56
apps/hub-server/routes/mail.ts
Normal file
56
apps/hub-server/routes/mail.ts
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
import { Router } from "express";
|
||||||
|
import { sendMail } from "modules/mail";
|
||||||
|
import { sendPasswordChanged, sendCourseCompletedEmail } from "modules/mail";
|
||||||
|
|
||||||
|
const router = Router();
|
||||||
|
|
||||||
|
router.post("/send", async (req, res) => {
|
||||||
|
console.log(req.body);
|
||||||
|
const { to, subject, html } = req.body;
|
||||||
|
|
||||||
|
try {
|
||||||
|
await sendMail(to, subject, html);
|
||||||
|
// Send email logic here
|
||||||
|
res.status(200).json({ message: "Email sent successfully" });
|
||||||
|
} catch (error) {
|
||||||
|
res.status(500).json({ error: "Failed to send email" });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
router.post("/template/:template", async (req, res) => {
|
||||||
|
const { template } = req.params;
|
||||||
|
const { to, data } = req.body;
|
||||||
|
if (!to || !data) {
|
||||||
|
res.status(400).json({ error: "Missing required fields" });
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!data.user) {
|
||||||
|
res.status(400).json({ error: "Missing user data" });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
console.log("template", template);
|
||||||
|
switch (template) {
|
||||||
|
case "password-change":
|
||||||
|
if (!data.password) {
|
||||||
|
res.status(400).json({ error: "Missing password data" });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await sendPasswordChanged(to, data.user, data.password);
|
||||||
|
break;
|
||||||
|
case "course-completed":
|
||||||
|
if (!data.event) {
|
||||||
|
res.status(400).json({ error: "Missing event data" });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await sendCourseCompletedEmail(to, data.user, data.event);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
res.status(400).json({ error: "Invalid template" });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
res.status(200).json({ message: "Email sent successfully" });
|
||||||
|
});
|
||||||
|
|
||||||
|
export default router;
|
||||||
8
apps/hub-server/routes/router.ts
Normal file
8
apps/hub-server/routes/router.ts
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
import { Router } from "express";
|
||||||
|
import mailRouter from "./mail";
|
||||||
|
|
||||||
|
const router = Router();
|
||||||
|
|
||||||
|
router.use("/mail", mailRouter);
|
||||||
|
|
||||||
|
export default router;
|
||||||
@@ -62,7 +62,6 @@ export default async () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const filteredEvents = events.filter((event) => {
|
const filteredEvents = events.filter((event) => {
|
||||||
console.log;
|
|
||||||
if (eventCompleted(event, event.participants[0])) return false;
|
if (eventCompleted(event, event.participants[0])) return false;
|
||||||
if (
|
if (
|
||||||
event.type === "OBLIGATED_COURSE" &&
|
event.type === "OBLIGATED_COURSE" &&
|
||||||
|
|||||||
@@ -61,9 +61,8 @@ export const AppointmentModal = ({
|
|||||||
</h3>
|
</h3>
|
||||||
<form
|
<form
|
||||||
onSubmit={appointmentForm.handleSubmit(async (values) => {
|
onSubmit={appointmentForm.handleSubmit(async (values) => {
|
||||||
console.log(values);
|
|
||||||
if (!event) return;
|
if (!event) return;
|
||||||
const createdAppointment = await upsertAppointment(values);
|
await upsertAppointment(values);
|
||||||
ref.current?.close();
|
ref.current?.close();
|
||||||
appointmentsTableRef.current?.refresh();
|
appointmentsTableRef.current?.refresh();
|
||||||
})}
|
})}
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
"use client";
|
"use client";
|
||||||
import { zodResolver } from "@hookform/resolvers/zod";
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
import { BADGES, User } from "@repo/db";
|
import { BADGES, PERMISSION, User } from "@repo/db";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { useForm } from "react-hook-form";
|
import { useForm } from "react-hook-form";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import { updateUser } from "../../../../settings/actions";
|
import { editUser, resetPassword } from "../../action";
|
||||||
import { toast } from "react-hot-toast";
|
import { toast } from "react-hot-toast";
|
||||||
import {
|
import {
|
||||||
PersonIcon,
|
PersonIcon,
|
||||||
@@ -18,36 +18,25 @@ import {
|
|||||||
} from "@radix-ui/react-icons";
|
} from "@radix-ui/react-icons";
|
||||||
import { Button } from "../../../../../_components/ui/Button";
|
import { Button } from "../../../../../_components/ui/Button";
|
||||||
import { Select } from "../../../../../_components/ui/Select";
|
import { Select } from "../../../../../_components/ui/Select";
|
||||||
|
import { UserSchema } from "@repo/db/zod";
|
||||||
|
import { useRouter } from "next/navigation";
|
||||||
|
|
||||||
interface ProfileFormProps {
|
interface ProfileFormProps {
|
||||||
user: User | null;
|
user: User;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ProfileForm: React.FC<ProfileFormProps> = ({ user }) => {
|
export const ProfileForm: React.FC<ProfileFormProps> = ({ user }) => {
|
||||||
const schema = z.object({
|
|
||||||
firstname: z.string().min(2).max(30),
|
|
||||||
lastname: z.string().min(2).max(30),
|
|
||||||
email: z.string().email({
|
|
||||||
message: "Bitte gebe eine gültige E-Mail Adresse ein",
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
const [isLoading, setIsLoading] = useState(false);
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
type IFormInput = z.infer<typeof schema>;
|
const form = useForm<User>({
|
||||||
|
defaultValues: user,
|
||||||
const form = useForm<IFormInput>({
|
resolver: zodResolver(UserSchema),
|
||||||
defaultValues: {
|
|
||||||
firstname: user?.firstname,
|
|
||||||
lastname: user?.lastname,
|
|
||||||
email: user?.email,
|
|
||||||
},
|
|
||||||
resolver: zodResolver(schema),
|
|
||||||
});
|
});
|
||||||
return (
|
return (
|
||||||
<form
|
<form
|
||||||
className="card-body"
|
className="card-body"
|
||||||
onSubmit={form.handleSubmit(async (values) => {
|
onSubmit={form.handleSubmit(async (values) => {
|
||||||
setIsLoading(true);
|
setIsLoading(true);
|
||||||
await updateUser(values);
|
await editUser(values.id, values);
|
||||||
form.reset(values);
|
form.reset(values);
|
||||||
setIsLoading(false);
|
setIsLoading(false);
|
||||||
toast.success("Deine Änderungen wurden gespeichert!", {
|
toast.success("Deine Änderungen wurden gespeichert!", {
|
||||||
@@ -114,13 +103,23 @@ export const ProfileForm: React.FC<ProfileFormProps> = ({ user }) => {
|
|||||||
<Select
|
<Select
|
||||||
isMulti
|
isMulti
|
||||||
form={form}
|
form={form}
|
||||||
name="finishedBadges"
|
name="badges"
|
||||||
label="Badges"
|
label="Badges"
|
||||||
options={Object.entries(BADGES).map(([key, value]) => ({
|
options={Object.entries(BADGES).map(([key, value]) => ({
|
||||||
label: value,
|
label: value,
|
||||||
value: key,
|
value: key,
|
||||||
}))}
|
}))}
|
||||||
/>
|
/>
|
||||||
|
<Select
|
||||||
|
isMulti
|
||||||
|
form={form}
|
||||||
|
name="permissions"
|
||||||
|
label="Permissions"
|
||||||
|
options={Object.entries(PERMISSION).map(([key, value]) => ({
|
||||||
|
label: value,
|
||||||
|
value: key,
|
||||||
|
}))}
|
||||||
|
/>
|
||||||
<div className="card-actions justify-center pt-6">
|
<div className="card-actions justify-center pt-6">
|
||||||
<Button
|
<Button
|
||||||
role="submit"
|
role="submit"
|
||||||
@@ -137,6 +136,8 @@ export const ProfileForm: React.FC<ProfileFormProps> = ({ user }) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const AdminForm: React.FC<ProfileFormProps> = ({ user }) => {
|
export const AdminForm: React.FC<ProfileFormProps> = ({ user }) => {
|
||||||
|
const router = useRouter();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="card-body">
|
<div className="card-body">
|
||||||
<h2 className="card-title">
|
<h2 className="card-title">
|
||||||
@@ -145,23 +146,59 @@ export const AdminForm: React.FC<ProfileFormProps> = ({ user }) => {
|
|||||||
<div className="text-left">
|
<div className="text-left">
|
||||||
<div className="card-actions pt-6">
|
<div className="card-actions pt-6">
|
||||||
<Button
|
<Button
|
||||||
role="submit"
|
onClick={async () => {
|
||||||
|
const { password } = await resetPassword(user.id);
|
||||||
|
toast.success(
|
||||||
|
`Neues Passwort
|
||||||
|
${password}, es wurde dem Nutzer an die E-Mail gesendet!`,
|
||||||
|
{
|
||||||
|
style: {
|
||||||
|
background: "var(--color-base-100)",
|
||||||
|
color: "var(--color-base-content)",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}}
|
||||||
className="btn-sm btn-wide btn-outline btn-success"
|
className="btn-sm btn-wide btn-outline btn-success"
|
||||||
>
|
>
|
||||||
<LockOpen1Icon /> Passwort zurücksetzen
|
<LockOpen1Icon /> Passwort zurücksetzen
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
{!user.isBanned && (
|
||||||
role="submit"
|
<Button
|
||||||
className="btn-sm btn-wide btn-outline btn-error"
|
onClick={async () => {
|
||||||
>
|
await editUser(user.id, { isBanned: true });
|
||||||
<HobbyKnifeIcon /> User Sperren
|
toast.success("Nutzer wurde gesperrt!", {
|
||||||
</Button>
|
style: {
|
||||||
<Button
|
background: "var(--color-base-100)",
|
||||||
role="submit"
|
color: "var(--color-base-content)",
|
||||||
className="btn-sm btn-wide btn-outline btn-warning"
|
},
|
||||||
>
|
});
|
||||||
<HeartIcon /> User Entperren
|
router.refresh();
|
||||||
</Button>
|
}}
|
||||||
|
role="submit"
|
||||||
|
className="btn-sm btn-wide btn-outline btn-error"
|
||||||
|
>
|
||||||
|
<HobbyKnifeIcon /> User Sperren
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
{user.isBanned && (
|
||||||
|
<Button
|
||||||
|
onClick={async () => {
|
||||||
|
await editUser(user.id, { isBanned: false });
|
||||||
|
toast.success("Nutzer wurde entsperrt!", {
|
||||||
|
style: {
|
||||||
|
background: "var(--color-base-100)",
|
||||||
|
color: "var(--color-base-content)",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
router.refresh();
|
||||||
|
}}
|
||||||
|
role="submit"
|
||||||
|
className="btn-sm btn-wide btn-outline btn-warning"
|
||||||
|
>
|
||||||
|
<HobbyKnifeIcon /> User Entperren
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,19 +1,18 @@
|
|||||||
import { PersonIcon } from "@radix-ui/react-icons";
|
import { PersonIcon } from "@radix-ui/react-icons";
|
||||||
import { PrismaClient, User } from "@repo/db";
|
import { PrismaClient, User } from "@repo/db";
|
||||||
import { AdminForm, ProfileForm } from "./_components/forms";
|
import { AdminForm, ProfileForm } from "./_components/forms";
|
||||||
|
import { Error } from "../../../../_components/Error";
|
||||||
|
|
||||||
export default async ({ params }: { params: { id: string } }) => {
|
export default async ({ params }: { params: { id: string } }) => {
|
||||||
const prisma = new PrismaClient();
|
const prisma = new PrismaClient();
|
||||||
const { id } = params;
|
const { id } = await params;
|
||||||
|
|
||||||
const user: User | null = await prisma.user.findUnique({
|
const user: User | null = await prisma.user.findUnique({
|
||||||
where: {
|
where: {
|
||||||
id: id,
|
id: id,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
if (!user) return <Error statusCode={404} title="User not found" />;
|
||||||
console.log(user);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="grid grid-cols-6 gap-4">
|
<div className="grid grid-cols-6 gap-4">
|
||||||
<div className="col-span-full">
|
<div className="col-span-full">
|
||||||
|
|||||||
35
apps/hub/app/(app)/admin/user/action.ts
Normal file
35
apps/hub/app/(app)/admin/user/action.ts
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
"use server";
|
||||||
|
import { PrismaClient } from "@prisma/client";
|
||||||
|
import { prisma, Prisma } from "@repo/db";
|
||||||
|
import bcrypt from "bcryptjs";
|
||||||
|
import { sendMailByTemplate } from "../../../../helper/mail";
|
||||||
|
|
||||||
|
export const editUser = async (id: string, data: Prisma.UserUpdateInput) => {
|
||||||
|
return await prisma.user.update({
|
||||||
|
where: {
|
||||||
|
id: id,
|
||||||
|
},
|
||||||
|
data,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const resetPassword = async (id: string) => {
|
||||||
|
const password = Math.random().toString(36).slice(-8);
|
||||||
|
const hashedPassword = await bcrypt.hash(password, 15);
|
||||||
|
|
||||||
|
const user = await prisma.user.update({
|
||||||
|
where: {
|
||||||
|
id: id,
|
||||||
|
},
|
||||||
|
data: {
|
||||||
|
password: hashedPassword,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
await sendMailByTemplate(user.email, "password-change", {
|
||||||
|
user: user,
|
||||||
|
password: password,
|
||||||
|
});
|
||||||
|
|
||||||
|
return { password };
|
||||||
|
};
|
||||||
@@ -103,13 +103,6 @@ const ModalBtn = ({
|
|||||||
? (selectedDate as any)?.Participants?.length + 1
|
? (selectedDate as any)?.Participants?.length + 1
|
||||||
: ownIndexInParticipantList + 1;
|
: ownIndexInParticipantList + 1;
|
||||||
|
|
||||||
console.log({
|
|
||||||
selectedDate,
|
|
||||||
ownPlaceInParticipantList,
|
|
||||||
ownIndexInParticipantList,
|
|
||||||
maxParticipants: event.maxParticipants,
|
|
||||||
});
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<button
|
<button
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ export const Register = () => {
|
|||||||
passwordConfirm: "",
|
passwordConfirm: "",
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
console.log(form.formState.errors);
|
|
||||||
return (
|
return (
|
||||||
<form
|
<form
|
||||||
className="card-body"
|
className="card-body"
|
||||||
|
|||||||
11
apps/hub/app/_components/Error.tsx
Normal file
11
apps/hub/app/_components/Error.tsx
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
export const Error = ({
|
||||||
|
statusCode,
|
||||||
|
title,
|
||||||
|
}: {
|
||||||
|
statusCode: number;
|
||||||
|
title: string;
|
||||||
|
}) => {
|
||||||
|
return <Error statusCode={404} title="User not found" />;
|
||||||
|
};
|
||||||
@@ -1,25 +1,43 @@
|
|||||||
import { ButtonHTMLAttributes, DetailedHTMLProps } from 'react';
|
import {
|
||||||
import { cn } from '../../../helper/cn';
|
ButtonHTMLAttributes,
|
||||||
|
DetailedHTMLProps,
|
||||||
|
useEffect,
|
||||||
|
useState,
|
||||||
|
} from "react";
|
||||||
|
import { cn } from "../../../helper/cn";
|
||||||
|
|
||||||
export const Button = ({
|
export const Button = ({
|
||||||
isLoading,
|
isLoading,
|
||||||
...props
|
...props
|
||||||
}: DetailedHTMLProps<
|
}: DetailedHTMLProps<
|
||||||
ButtonHTMLAttributes<HTMLButtonElement>,
|
ButtonHTMLAttributes<HTMLButtonElement>,
|
||||||
HTMLButtonElement
|
HTMLButtonElement
|
||||||
> & {
|
> & {
|
||||||
isLoading?: boolean;
|
isLoading?: boolean;
|
||||||
}) => {
|
}) => {
|
||||||
return (
|
const [isLoadingState, setIsLoadingState] = useState(isLoading);
|
||||||
<button
|
|
||||||
{...(props as any)}
|
useEffect(() => {
|
||||||
className={cn('btn', props.className)}
|
setIsLoadingState(isLoading);
|
||||||
disabled={isLoading || props.disabled}
|
}, [isLoading]);
|
||||||
>
|
|
||||||
{isLoading && (
|
return (
|
||||||
<span className="loading loading-spinner loading-sm"></span>
|
<button
|
||||||
)}
|
{...(props as any)}
|
||||||
{props.children as any}
|
className={cn("btn", props.className)}
|
||||||
</button>
|
disabled={isLoadingState || props.disabled}
|
||||||
);
|
onClick={async (e) => {
|
||||||
|
if (props.onClick) {
|
||||||
|
setIsLoadingState(true);
|
||||||
|
await props.onClick(e);
|
||||||
|
setIsLoadingState(false);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{isLoadingState && (
|
||||||
|
<span className="loading loading-spinner loading-sm"></span>
|
||||||
|
)}
|
||||||
|
{props.children as any}
|
||||||
|
</button>
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -76,11 +76,16 @@ const SelectCom = <T extends FieldValues>({
|
|||||||
<SelectTemplate
|
<SelectTemplate
|
||||||
onChange={(newValue: any) => {
|
onChange={(newValue: any) => {
|
||||||
if (Array.isArray(newValue)) {
|
if (Array.isArray(newValue)) {
|
||||||
form.setValue(name, newValue.map((v: any) => v.value) as any);
|
form.setValue(name, newValue.map((v: any) => v.value) as any, {
|
||||||
|
shouldDirty: true,
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
form.setValue(name, newValue.value);
|
form.setValue(name, newValue.value, {
|
||||||
|
shouldDirty: true,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
form.trigger(name);
|
form.trigger(name);
|
||||||
|
form.Dirty;
|
||||||
}}
|
}}
|
||||||
value={
|
value={
|
||||||
(inputProps as any)?.isMulti
|
(inputProps as any)?.isMulti
|
||||||
|
|||||||
@@ -24,9 +24,7 @@ export const GET = async (req: NextRequest) => {
|
|||||||
if (!user)
|
if (!user)
|
||||||
return NextResponse.json({ error: "User not found" }, { status: 404 });
|
return NextResponse.json({ error: "User not found" }, { status: 404 });
|
||||||
setTimeout(async () => {
|
setTimeout(async () => {
|
||||||
console.log("getting moodle ID");
|
|
||||||
const moodleUser = await getMoodleUserById(user.id);
|
const moodleUser = await getMoodleUserById(user.id);
|
||||||
console.log("got moodle ID", moodleUser.id);
|
|
||||||
await prisma.user.update({
|
await prisma.user.update({
|
||||||
where: {
|
where: {
|
||||||
id: user.id,
|
id: user.id,
|
||||||
|
|||||||
40
apps/hub/helper/mail.ts
Normal file
40
apps/hub/helper/mail.ts
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
export const sendMail = async (
|
||||||
|
email: string,
|
||||||
|
subject: string,
|
||||||
|
html: string,
|
||||||
|
) => {
|
||||||
|
await fetch(`${process.env.NEXT_PUBLIC_HUB_SERVER_URL}/mail/send`, {
|
||||||
|
method: "POST",
|
||||||
|
headers: { "Content-Type": "application/json" },
|
||||||
|
|
||||||
|
body: JSON.stringify({
|
||||||
|
to: email,
|
||||||
|
subject,
|
||||||
|
html,
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const sendMailByTemplate = async (
|
||||||
|
email: string,
|
||||||
|
template: "password-change" | "course-completed",
|
||||||
|
data: any,
|
||||||
|
) => {
|
||||||
|
try {
|
||||||
|
const res = await fetch(
|
||||||
|
`${process.env.NEXT_PUBLIC_HUB_SERVER_URL}/mail/template/${template}`,
|
||||||
|
{
|
||||||
|
method: "POST",
|
||||||
|
headers: { "Content-Type": "application/json" },
|
||||||
|
|
||||||
|
body: JSON.stringify({
|
||||||
|
to: email,
|
||||||
|
data,
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
console.log(res);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error sending mail:", error);
|
||||||
|
}
|
||||||
|
};
|
||||||
27
package-lock.json
generated
27
package-lock.json
generated
@@ -140,8 +140,10 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@react-email/components": "^0.0.33",
|
"@react-email/components": "^0.0.33",
|
||||||
"axios": "^1.7.9",
|
"axios": "^1.7.9",
|
||||||
|
"cors": "^2.8.5",
|
||||||
"cron": "^4.1.0",
|
"cron": "^4.1.0",
|
||||||
"dotenv": "^16.4.7",
|
"dotenv": "^16.4.7",
|
||||||
|
"express": "^4.21.2",
|
||||||
"nodemailer": "^6.10.0",
|
"nodemailer": "^6.10.0",
|
||||||
"react": "^19.0.0"
|
"react": "^19.0.0"
|
||||||
},
|
},
|
||||||
@@ -3798,6 +3800,7 @@
|
|||||||
"version": "3.1.0",
|
"version": "3.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz",
|
||||||
"integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==",
|
"integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==",
|
||||||
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"clean-stack": "^2.0.0",
|
"clean-stack": "^2.0.0",
|
||||||
@@ -3893,6 +3896,7 @@
|
|||||||
"version": "3.2.1",
|
"version": "3.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
|
||||||
"integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
|
"integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
|
||||||
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"color-convert": "^1.9.0"
|
"color-convert": "^1.9.0"
|
||||||
@@ -4236,6 +4240,7 @@
|
|||||||
"version": "1.0.2",
|
"version": "1.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
|
||||||
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
|
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
|
||||||
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/base64-js": {
|
"node_modules/base64-js": {
|
||||||
@@ -4358,6 +4363,7 @@
|
|||||||
"version": "1.1.11",
|
"version": "1.1.11",
|
||||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
|
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
|
||||||
"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
|
"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
|
||||||
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"balanced-match": "^1.0.0",
|
"balanced-match": "^1.0.0",
|
||||||
@@ -4569,6 +4575,7 @@
|
|||||||
"version": "2.4.2",
|
"version": "2.4.2",
|
||||||
"resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
|
"resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
|
||||||
"integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
|
"integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
|
||||||
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"ansi-styles": "^3.2.1",
|
"ansi-styles": "^3.2.1",
|
||||||
@@ -4667,6 +4674,7 @@
|
|||||||
"version": "2.2.0",
|
"version": "2.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz",
|
||||||
"integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==",
|
"integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==",
|
||||||
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=6"
|
"node": ">=6"
|
||||||
@@ -4835,6 +4843,7 @@
|
|||||||
"version": "1.9.3",
|
"version": "1.9.3",
|
||||||
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
|
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
|
||||||
"integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==",
|
"integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==",
|
||||||
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"color-name": "1.1.3"
|
"color-name": "1.1.3"
|
||||||
@@ -4844,6 +4853,7 @@
|
|||||||
"version": "1.1.3",
|
"version": "1.1.3",
|
||||||
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
|
||||||
"integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==",
|
"integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==",
|
||||||
|
"devOptional": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/color-string": {
|
"node_modules/color-string": {
|
||||||
@@ -4919,6 +4929,7 @@
|
|||||||
"version": "0.0.1",
|
"version": "0.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
|
||||||
"integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==",
|
"integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==",
|
||||||
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/concurrently": {
|
"node_modules/concurrently": {
|
||||||
@@ -6061,6 +6072,7 @@
|
|||||||
"version": "1.0.5",
|
"version": "1.0.5",
|
||||||
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
|
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
|
||||||
"integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==",
|
"integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==",
|
||||||
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=0.8.0"
|
"node": ">=0.8.0"
|
||||||
@@ -7096,6 +7108,7 @@
|
|||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
|
||||||
"integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==",
|
"integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==",
|
||||||
|
"dev": true,
|
||||||
"license": "ISC"
|
"license": "ISC"
|
||||||
},
|
},
|
||||||
"node_modules/fsevents": {
|
"node_modules/fsevents": {
|
||||||
@@ -7287,6 +7300,7 @@
|
|||||||
"resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
|
"resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
|
||||||
"integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==",
|
"integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==",
|
||||||
"deprecated": "Glob versions prior to v9 are no longer supported",
|
"deprecated": "Glob versions prior to v9 are no longer supported",
|
||||||
|
"dev": true,
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"fs.realpath": "^1.0.0",
|
"fs.realpath": "^1.0.0",
|
||||||
@@ -7536,6 +7550,7 @@
|
|||||||
"version": "3.0.0",
|
"version": "3.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
|
||||||
"integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==",
|
"integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==",
|
||||||
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=4"
|
"node": ">=4"
|
||||||
@@ -8091,6 +8106,7 @@
|
|||||||
"version": "4.0.0",
|
"version": "4.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz",
|
||||||
"integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==",
|
"integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==",
|
||||||
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=8"
|
"node": ">=8"
|
||||||
@@ -8101,6 +8117,7 @@
|
|||||||
"resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
|
"resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
|
||||||
"integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==",
|
"integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==",
|
||||||
"deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.",
|
"deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.",
|
||||||
|
"dev": true,
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"once": "^1.3.0",
|
"once": "^1.3.0",
|
||||||
@@ -8117,6 +8134,7 @@
|
|||||||
"version": "1.3.8",
|
"version": "1.3.8",
|
||||||
"resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz",
|
"resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz",
|
||||||
"integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==",
|
"integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==",
|
||||||
|
"dev": true,
|
||||||
"license": "ISC"
|
"license": "ISC"
|
||||||
},
|
},
|
||||||
"node_modules/inline-style-parser": {
|
"node_modules/inline-style-parser": {
|
||||||
@@ -8866,6 +8884,7 @@
|
|||||||
"version": "2.0.0",
|
"version": "2.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
|
||||||
"integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
|
"integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
|
||||||
|
"dev": true,
|
||||||
"license": "ISC"
|
"license": "ISC"
|
||||||
},
|
},
|
||||||
"node_modules/iterator.prototype": {
|
"node_modules/iterator.prototype": {
|
||||||
@@ -10560,6 +10579,7 @@
|
|||||||
"version": "3.1.2",
|
"version": "3.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
|
||||||
"integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
|
"integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
|
||||||
|
"dev": true,
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"brace-expansion": "^1.1.7"
|
"brace-expansion": "^1.1.7"
|
||||||
@@ -13582,6 +13602,7 @@
|
|||||||
"version": "1.4.0",
|
"version": "1.4.0",
|
||||||
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
|
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
|
||||||
"integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
|
"integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
|
||||||
|
"dev": true,
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"wrappy": "1"
|
"wrappy": "1"
|
||||||
@@ -13814,6 +13835,7 @@
|
|||||||
"version": "3.0.0",
|
"version": "3.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/p-map/-/p-map-3.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/p-map/-/p-map-3.0.0.tgz",
|
||||||
"integrity": "sha512-d3qXVTF/s+W+CdJ5A29wywV2n8CQQYahlgz2bFiA+4eVNJbHJodPZ+/gXwPGh0bOqA+j8S+6+ckmvLGPk1QpxQ==",
|
"integrity": "sha512-d3qXVTF/s+W+CdJ5A29wywV2n8CQQYahlgz2bFiA+4eVNJbHJodPZ+/gXwPGh0bOqA+j8S+6+ckmvLGPk1QpxQ==",
|
||||||
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"aggregate-error": "^3.0.0"
|
"aggregate-error": "^3.0.0"
|
||||||
@@ -13996,6 +14018,7 @@
|
|||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
|
||||||
"integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==",
|
"integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==",
|
||||||
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
@@ -16281,6 +16304,7 @@
|
|||||||
"version": "5.5.0",
|
"version": "5.5.0",
|
||||||
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
|
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
|
||||||
"integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
|
"integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
|
||||||
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"has-flag": "^3.0.0"
|
"has-flag": "^3.0.0"
|
||||||
@@ -17178,6 +17202,7 @@
|
|||||||
"version": "5.0.1",
|
"version": "5.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/validate-npm-package-name/-/validate-npm-package-name-5.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/validate-npm-package-name/-/validate-npm-package-name-5.0.1.tgz",
|
||||||
"integrity": "sha512-OljLrQ9SQdOUqTaQxqL5dEfZWrXExyyWsozYlAWFawPVNuD83igl7uJD2RTkNMbniIYgt8l81eCJGIdQF7avLQ==",
|
"integrity": "sha512-OljLrQ9SQdOUqTaQxqL5dEfZWrXExyyWsozYlAWFawPVNuD83igl7uJD2RTkNMbniIYgt8l81eCJGIdQF7avLQ==",
|
||||||
|
"dev": true,
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": "^14.17.0 || ^16.13.0 || >=18.0.0"
|
"node": "^14.17.0 || ^16.13.0 || >=18.0.0"
|
||||||
@@ -17353,6 +17378,7 @@
|
|||||||
"version": "2.0.2",
|
"version": "2.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
|
||||||
"integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
|
"integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
|
||||||
|
"dev": true,
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"isexe": "^2.0.0"
|
"isexe": "^2.0.0"
|
||||||
@@ -17525,6 +17551,7 @@
|
|||||||
"version": "1.0.2",
|
"version": "1.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
|
||||||
"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
|
"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
|
||||||
|
"dev": true,
|
||||||
"license": "ISC"
|
"license": "ISC"
|
||||||
},
|
},
|
||||||
"node_modules/ws": {
|
"node_modules/ws": {
|
||||||
|
|||||||
@@ -17,21 +17,21 @@ enum PERMISSION {
|
|||||||
}
|
}
|
||||||
|
|
||||||
model User {
|
model User {
|
||||||
id String @id @default(uuid())
|
id String @id @default(uuid())
|
||||||
publicId String @unique
|
publicId String @unique
|
||||||
firstname String
|
firstname String
|
||||||
lastname String
|
lastname String
|
||||||
email String @unique
|
email String @unique
|
||||||
password String
|
password String
|
||||||
vatsimCid Int? @map(name: "vatsim_cid")
|
vatsimCid Int? @map(name: "vatsim_cid")
|
||||||
moodleId Int? @map(name: "moodle_id")
|
moodleId Int? @map(name: "moodle_id")
|
||||||
emailVerified DateTime? @map(name: "email_verified")
|
emailVerified DateTime? @map(name: "email_verified")
|
||||||
image String?
|
image String?
|
||||||
badges BADGES[] @default([])
|
badges BADGES[] @default([])
|
||||||
permissions PERMISSION[] @default([])
|
permissions PERMISSION[] @default([])
|
||||||
createdAt DateTime @default(now()) @map(name: "created_at")
|
createdAt DateTime @default(now()) @map(name: "created_at")
|
||||||
updatedAt DateTime @default(now()) @map(name: "updated_at")
|
updatedAt DateTime @default(now()) @map(name: "updated_at")
|
||||||
|
isBanned Boolean @default(false) @map(name: "is_banned")
|
||||||
// relations:
|
// relations:
|
||||||
oauthTokens OAuthToken[]
|
oauthTokens OAuthToken[]
|
||||||
discordAccounts DiscordAccount[]
|
discordAccounts DiscordAccount[]
|
||||||
|
|||||||
Reference in New Issue
Block a user