CHanged Event admin layout
This commit is contained in:
@@ -1,2 +1,6 @@
|
||||
MOODLE_TOKEN=
|
||||
MOODLE_URL=
|
||||
MAIL_SERVER=
|
||||
MAIL_USER=
|
||||
MAIL_PASSWORD=
|
||||
MAIL_PORT=
|
||||
@@ -2,6 +2,8 @@ import { getMoodleCourseCompletionStatus, getMoodleUserById } from "./moodle";
|
||||
import { CronJob } from "cron";
|
||||
import { prisma } from "@repo/db";
|
||||
import { eventCompleted } from "@repo/ui/helper";
|
||||
import { sendCourseCompletedEmail } from "modules/mail";
|
||||
import { handleParticipantFinished } from "modules/event";
|
||||
|
||||
const syncMoodleIds = async () => {
|
||||
try {
|
||||
@@ -57,7 +59,7 @@ const updateParticipantMoodleResults = async () => {
|
||||
);
|
||||
|
||||
if (quizzResult?.completionstatus?.completed === true) {
|
||||
await prisma.participant.update({
|
||||
return prisma.participant.update({
|
||||
where: {
|
||||
id: p.id,
|
||||
},
|
||||
@@ -65,7 +67,7 @@ const updateParticipantMoodleResults = async () => {
|
||||
finisherMoodleCurseCompleted: true,
|
||||
statusLog: {
|
||||
push: {
|
||||
event: "Finisher course completed",
|
||||
event: "Moodle-Kurs abgeschlossen",
|
||||
timestamp: new Date(),
|
||||
user: "system",
|
||||
},
|
||||
@@ -77,33 +79,40 @@ const updateParticipantMoodleResults = async () => {
|
||||
);
|
||||
};
|
||||
|
||||
export const checkedFinishedParticipants = async () => {
|
||||
export const checkFinishedParticipants = async () => {
|
||||
console.log("Checking finished participants");
|
||||
const participantsPending = await prisma.participant.findMany({
|
||||
where: {
|
||||
finished: false,
|
||||
completetionWorkflowFinished: false,
|
||||
},
|
||||
include: {
|
||||
Event: true,
|
||||
User: true,
|
||||
},
|
||||
});
|
||||
|
||||
participantsPending.forEach(async (p) => {
|
||||
if (!p.User) return;
|
||||
if (!p.User.moodleId) return;
|
||||
|
||||
const completed = eventCompleted(p.Event, p);
|
||||
|
||||
if (!completed) return;
|
||||
handleParticipantFinished(p.Event, p, p.User);
|
||||
});
|
||||
};
|
||||
|
||||
CronJob.from({ cronTime: "0 * * * *", onTick: syncMoodleIds, start: true });
|
||||
CronJob.from({
|
||||
cronTime: "*/5 * * * *",
|
||||
cronTime: "*/1 * * * *",
|
||||
onTick: async () => {
|
||||
console.log("Updating participant moodle results");
|
||||
await updateParticipantMoodleResults();
|
||||
await checkFinishedParticipants();
|
||||
},
|
||||
start: true,
|
||||
});
|
||||
|
||||
updateParticipantMoodleResults();
|
||||
const debug = async () => {
|
||||
await updateParticipantMoodleResults();
|
||||
await checkFinishedParticipants();
|
||||
};
|
||||
|
||||
debug();
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { Event, Participant, prisma, User } from "@repo/db";
|
||||
import { sendCourseCompletedEmail } from "modules/mail";
|
||||
|
||||
export const handleParticipantFinished = async (
|
||||
event: Event,
|
||||
@@ -11,9 +12,6 @@ export const handleParticipantFinished = async (
|
||||
},
|
||||
});
|
||||
|
||||
//TODO: Send Discord Message
|
||||
//TODO: Send Email
|
||||
|
||||
await prisma.user.update({
|
||||
where: {
|
||||
id: user.id,
|
||||
@@ -22,19 +20,24 @@ export const handleParticipantFinished = async (
|
||||
badges: {
|
||||
push: event.finishedBadges,
|
||||
},
|
||||
permissions: event.finishedPermissions,
|
||||
permissions: {
|
||||
push: event.finishedPermissions,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
//TODO: Send Discord Message
|
||||
await sendCourseCompletedEmail(user.email, user, event);
|
||||
|
||||
await prisma.participant.update({
|
||||
where: {
|
||||
id: participant.id,
|
||||
},
|
||||
data: {
|
||||
finished: true,
|
||||
completetionWorkflowFinished: true,
|
||||
statusLog: {
|
||||
push: {
|
||||
event: "Event finished",
|
||||
event: "Berechtigungen und Badges vergeben",
|
||||
timestamp: new Date(),
|
||||
user: "system",
|
||||
},
|
||||
|
||||
21
apps/hub-server/modules/mail-templates/CourseCompleted.tsx
Normal file
21
apps/hub-server/modules/mail-templates/CourseCompleted.tsx
Normal file
@@ -0,0 +1,21 @@
|
||||
import * as React from "react";
|
||||
import { Event, User } from "@repo/db";
|
||||
|
||||
import { Html, Button, render } from "@react-email/components";
|
||||
|
||||
const Template = ({ event, user }: { user: User; event: Event }) => (
|
||||
<Html lang="en">
|
||||
<p>You completed the Course {event.name}</p>
|
||||
<p>Congratulation</p>
|
||||
</Html>
|
||||
);
|
||||
|
||||
export function renderCourseCompleted({
|
||||
user,
|
||||
event,
|
||||
}: {
|
||||
user: User;
|
||||
event: Event;
|
||||
}) {
|
||||
return render(<Template event={event} user={user} />);
|
||||
}
|
||||
59
apps/hub-server/modules/mail.ts
Normal file
59
apps/hub-server/modules/mail.ts
Normal file
@@ -0,0 +1,59 @@
|
||||
import { Event, User } from "@repo/db";
|
||||
import nodemailer from "nodemailer";
|
||||
import { renderCourseCompleted } from "./mail-templates/CourseCompleted";
|
||||
|
||||
let transporter: nodemailer.Transporter | null = null;
|
||||
|
||||
const initTransporter = () => {
|
||||
if (!process.env.MAIL_SERVER)
|
||||
return console.error("MAIL_SERVER is not defined");
|
||||
if (!process.env.MAIL_PORT) return console.error("MAIL_PORT is not defined");
|
||||
if (!process.env.MAIL_USER) return console.error("MAIL_USER is not defined");
|
||||
if (!process.env.MAIL_PASSWORD)
|
||||
return console.error("MAIL_PASSWORD is not defined");
|
||||
|
||||
transporter = nodemailer.createTransport({
|
||||
host: process.env.MAIL_SERVER,
|
||||
port: parseInt(process.env.MAIL_PORT),
|
||||
secure: true, // true for 465, false for other ports
|
||||
auth: {
|
||||
user: process.env.MAIL_USER,
|
||||
pass: process.env.MAIL_PASSWORD,
|
||||
},
|
||||
});
|
||||
transporter.on("error", (err) => {
|
||||
console.error("Mail occurred:", err);
|
||||
});
|
||||
transporter.on("idle", () => {
|
||||
console.log("Mail Idle");
|
||||
});
|
||||
};
|
||||
|
||||
initTransporter();
|
||||
|
||||
export const sendCourseCompletedEmail = async (
|
||||
to: string,
|
||||
user: User,
|
||||
event: Event,
|
||||
) => {
|
||||
const emailHtml = await renderCourseCompleted({ user, event });
|
||||
|
||||
if (!transporter) {
|
||||
console.error("Transporter is not initialized");
|
||||
return;
|
||||
}
|
||||
transporter.sendMail(
|
||||
{
|
||||
from: process.env.MAIL_USER,
|
||||
to,
|
||||
subject: `Congratulations ${user.firstname} on completing ${event.name}`,
|
||||
html: emailHtml,
|
||||
},
|
||||
(error, info) => {
|
||||
if (error) {
|
||||
console.error("Error sending email:", error);
|
||||
} else {
|
||||
}
|
||||
},
|
||||
);
|
||||
};
|
||||
@@ -11,12 +11,16 @@
|
||||
"@repo/db": "*",
|
||||
"@repo/typescript-config": "*",
|
||||
"@types/node": "^22.13.5",
|
||||
"@types/nodemailer": "^6.4.17",
|
||||
"concurrently": "^9.1.2",
|
||||
"typescript": "latest"
|
||||
},
|
||||
"dependencies": {
|
||||
"@react-email/components": "^0.0.33",
|
||||
"axios": "^1.7.9",
|
||||
"cron": "^4.1.0",
|
||||
"dotenv": "^16.4.7"
|
||||
"dotenv": "^16.4.7",
|
||||
"nodemailer": "^6.10.0",
|
||||
"react": "^19.0.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,8 +3,9 @@
|
||||
"compilerOptions": {
|
||||
"outDir": "dist",
|
||||
"allowImportingTsExtensions": false,
|
||||
"baseUrl": "."
|
||||
"baseUrl": ".",
|
||||
"jsx": "react"
|
||||
},
|
||||
"include": ["**/*.ts", "./index.ts"],
|
||||
"exclude": ["node_modules", "dist"]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,33 +22,37 @@ import { Switch } from "../../../../_components/ui/Switch";
|
||||
interface AppointmentModalProps {
|
||||
event?: Event;
|
||||
ref: RefObject<HTMLDialogElement | null>;
|
||||
participantModal: RefObject<HTMLDialogElement | null>;
|
||||
appointmentsTableRef: React.RefObject<PaginatedTableRef>;
|
||||
appointmentForm: UseFormReturn<
|
||||
EventAppointmentOptionalDefaults,
|
||||
any,
|
||||
undefined
|
||||
>;
|
||||
participantForm: UseFormReturn<Participant, any, undefined>;
|
||||
}
|
||||
|
||||
export const AppointmentModal = ({
|
||||
event,
|
||||
ref,
|
||||
participantModal,
|
||||
appointmentsTableRef,
|
||||
appointmentForm,
|
||||
participantForm,
|
||||
}: AppointmentModalProps) => {
|
||||
const { data: session } = useSession();
|
||||
|
||||
const participantTableRef = useRef<PaginatedTableRef>(null);
|
||||
const participantForm = useForm<Participant>({
|
||||
resolver: zodResolver(ParticipantOptionalDefaultsSchema),
|
||||
});
|
||||
|
||||
return (
|
||||
<dialog ref={ref} className="modal">
|
||||
<div className="modal-box">
|
||||
<form method="dialog">
|
||||
{/* if there is a button in form, it will close the modal */}
|
||||
<button className="btn btn-sm btn-circle btn-ghost absolute right-2 top-2">
|
||||
<button
|
||||
className="btn btn-sm btn-circle btn-ghost absolute right-2 top-2"
|
||||
onClick={() => ref.current?.close()}
|
||||
>
|
||||
✕
|
||||
</button>
|
||||
</form>
|
||||
@@ -57,6 +61,7 @@ export const AppointmentModal = ({
|
||||
</h3>
|
||||
<form
|
||||
onSubmit={appointmentForm.handleSubmit(async (values) => {
|
||||
console.log(values);
|
||||
if (!event) return;
|
||||
const createdAppointment = await upsertAppointment(values);
|
||||
ref.current?.close();
|
||||
@@ -75,8 +80,8 @@ export const AppointmentModal = ({
|
||||
}}
|
||||
/> */}
|
||||
<div>
|
||||
{appointmentForm.watch("id") && (
|
||||
<PaginatedTable
|
||||
hide={appointmentForm.watch("id") === undefined}
|
||||
ref={participantTableRef}
|
||||
columns={[
|
||||
{
|
||||
@@ -95,13 +100,13 @@ export const AppointmentModal = ({
|
||||
<button
|
||||
onClick={() => {
|
||||
participantForm.reset(row.original);
|
||||
participantModal.current?.showModal();
|
||||
}}
|
||||
className="btn btn-outline btn-sm"
|
||||
>
|
||||
anzeigen
|
||||
</button>
|
||||
{!row.original.attended &&
|
||||
event?.hasPresenceEvents && (
|
||||
{!row.original.attended && event?.hasPresenceEvents && (
|
||||
<button
|
||||
type="button"
|
||||
onSubmit={() => {}}
|
||||
@@ -150,56 +155,8 @@ export const AppointmentModal = ({
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
<div className="modal-action"></div>
|
||||
</form>
|
||||
{participantForm.watch("id") && (
|
||||
<form
|
||||
onSubmit={participantForm.handleSubmit(async (data) => {
|
||||
await upsertParticipant({
|
||||
...data,
|
||||
statusLog: data.statusLog as Prisma.InputJsonValue[],
|
||||
});
|
||||
participantTableRef.current?.refresh();
|
||||
})}
|
||||
className="space-y-1"
|
||||
>
|
||||
<h1 className="text-2xl">Teilnehmer bearbeiten</h1>
|
||||
<Switch
|
||||
form={participantForm}
|
||||
name="appointmentCancelled"
|
||||
label="Termin abgesagt"
|
||||
/>
|
||||
<Switch
|
||||
form={participantForm}
|
||||
name="finisherMoodleCurseCompleted"
|
||||
label="Abschluss-Moodle kurs abgeschlossen"
|
||||
/>
|
||||
{event?.hasPresenceEvents && (
|
||||
<Switch
|
||||
form={participantForm}
|
||||
name="attended"
|
||||
label="An Presenstermin teilgenommen"
|
||||
/>
|
||||
)}
|
||||
<div className="flex flex-col">
|
||||
<h3 className="text-xl">Verlauf</h3>
|
||||
{participantForm.watch("statusLog").map((s) => {
|
||||
return (
|
||||
<div
|
||||
className="flex justify-between"
|
||||
key={(s as any).timestamp}
|
||||
>
|
||||
<p>{(s as any).event}</p>
|
||||
<p>{new Date((s as any).timestamp).toLocaleString()}</p>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
<Button>Speichern</Button>
|
||||
</form>
|
||||
)}
|
||||
</div>
|
||||
</dialog>
|
||||
);
|
||||
|
||||
@@ -17,7 +17,7 @@ import {
|
||||
prisma,
|
||||
Prisma,
|
||||
} from "@repo/db";
|
||||
import { Bot, Calendar, FileText, UserIcon } from "lucide-react";
|
||||
import { Bot, Calendar, FileText, User, UserIcon } from "lucide-react";
|
||||
import { Input } from "../../../../_components/ui/Input";
|
||||
import { useRef, useState } from "react";
|
||||
import {
|
||||
@@ -39,6 +39,7 @@ import { Select } from "../../../../_components/ui/Select";
|
||||
import { useSession } from "next-auth/react";
|
||||
import { MarkdownEditor } from "../../../../_components/ui/MDEditor";
|
||||
import { AppointmentModal } from "./AppointmentModal";
|
||||
import { ParticipantModal } from "./ParticipantModal";
|
||||
|
||||
export const Form = ({ event }: { event?: Event }) => {
|
||||
const { data: session } = useSession();
|
||||
@@ -53,17 +54,28 @@ export const Form = ({ event }: { event?: Event }) => {
|
||||
presenterId: session?.user?.id,
|
||||
},
|
||||
});
|
||||
const participantForm = useForm<Participant>({
|
||||
resolver: zodResolver(ParticipantOptionalDefaultsSchema),
|
||||
});
|
||||
const appointmentsTableRef = useRef<PaginatedTableRef>(null);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [deleteLoading, setDeleteLoading] = useState(false);
|
||||
const appointmentModal = useRef<HTMLDialogElement>(null);
|
||||
const participantModal = useRef<HTMLDialogElement>(null);
|
||||
|
||||
return (
|
||||
<>
|
||||
<AppointmentModal
|
||||
participantModal={participantModal}
|
||||
participantForm={participantForm}
|
||||
appointmentForm={appointmentForm}
|
||||
ref={appointmentModal}
|
||||
appointmentsTableRef={appointmentsTableRef}
|
||||
event={event}
|
||||
/>
|
||||
<ParticipantModal
|
||||
participantForm={participantForm}
|
||||
ref={participantModal}
|
||||
/>
|
||||
<form
|
||||
onSubmit={form.handleSubmit(async (values) => {
|
||||
@@ -164,6 +176,8 @@ export const Form = ({ event }: { event?: Event }) => {
|
||||
appointmentModal.current?.showModal();
|
||||
appointmentForm.reset({
|
||||
id: undefined,
|
||||
eventId: event.id,
|
||||
presenterId: session?.user?.id,
|
||||
});
|
||||
}}
|
||||
>
|
||||
@@ -238,6 +252,62 @@ export const Form = ({ event }: { event?: Event }) => {
|
||||
</div>
|
||||
</div>
|
||||
) : null}
|
||||
{!form.watch("hasPresenceEvents") ? (
|
||||
<div className="card bg-base-200 shadow-xl col-span-6">
|
||||
<div className="card-body">
|
||||
<PaginatedTable
|
||||
leftOfSearch={
|
||||
<h2 className="card-title">
|
||||
<Calendar className="w-5 h-5" /> Teilnehmer
|
||||
</h2>
|
||||
}
|
||||
searchFields={["User.firstname", "User.lastname"]}
|
||||
ref={appointmentsTableRef}
|
||||
prismaModel={"participant"}
|
||||
filter={{
|
||||
eventId: event?.id,
|
||||
}}
|
||||
include={{
|
||||
User: true,
|
||||
}}
|
||||
columns={[
|
||||
{
|
||||
header: "Vorname",
|
||||
accessorKey: "User.firstname",
|
||||
},
|
||||
{
|
||||
header: "Nachname",
|
||||
accessorKey: "User.lastname",
|
||||
},
|
||||
{
|
||||
header: "Moodle Kurs abgeschlossen",
|
||||
accessorKey: "finisherMoodleCurseCompleted",
|
||||
},
|
||||
{
|
||||
header: "Aktionen",
|
||||
cell: ({ row }) => {
|
||||
return (
|
||||
<div className="flex gap-2">
|
||||
<button
|
||||
onSubmit={() => false}
|
||||
type="button"
|
||||
onClick={() => {
|
||||
participantForm.reset(row.original);
|
||||
participantModal.current?.showModal();
|
||||
}}
|
||||
className="btn btn-sm btn-outline"
|
||||
>
|
||||
Bearbeiten
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
) : null}
|
||||
<div className="card bg-base-200 shadow-xl col-span-6">
|
||||
<div className="card-body ">
|
||||
<div className="flex w-full gap-4">
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
import { Switch } from "../../../../_components/ui/Switch";
|
||||
import { Button } from "../../../../_components/ui/Button";
|
||||
import { Participant, Prisma } from "@repo/db";
|
||||
import { UseFormReturn } from "react-hook-form";
|
||||
import { upsertParticipant } from "../../../events/actions";
|
||||
import { RefObject } from "react";
|
||||
|
||||
interface ParticipantModalProps {
|
||||
participantForm: UseFormReturn<Participant>;
|
||||
ref: RefObject<HTMLDialogElement | null>;
|
||||
}
|
||||
|
||||
export const ParticipantModal = ({
|
||||
participantForm,
|
||||
ref,
|
||||
}: ParticipantModalProps) => {
|
||||
return (
|
||||
<dialog className="modal" ref={ref}>
|
||||
<div className="modal-box">
|
||||
<form method="dialog">
|
||||
<button className="btn btn-sm btn-circle btn-ghost absolute right-2 top-2">
|
||||
✕
|
||||
</button>
|
||||
</form>
|
||||
<h1 className="text-2xl">Teilnehmer bearbeiten</h1>
|
||||
<form
|
||||
onSubmit={participantForm.handleSubmit(async (data) => {
|
||||
await upsertParticipant({
|
||||
...data,
|
||||
statusLog: data.statusLog as Prisma.InputJsonValue[],
|
||||
});
|
||||
participantForm.reset();
|
||||
ref.current?.close();
|
||||
})}
|
||||
className="space-y-1"
|
||||
>
|
||||
<Switch
|
||||
form={participantForm}
|
||||
name="appointmentCancelled"
|
||||
label="Termin abgesagt"
|
||||
/>
|
||||
<Switch
|
||||
form={participantForm}
|
||||
name="finisherMoodleCurseCompleted"
|
||||
label="Abschluss-Moodle-Kurs abgeschlossen"
|
||||
/>
|
||||
<Switch
|
||||
form={participantForm}
|
||||
name="completetionWorkflowFinished"
|
||||
label="Abgeschlossen (E-Mail-Benachrichtigung senden)"
|
||||
/>
|
||||
<div className="flex flex-col">
|
||||
<h3 className="text-xl">Verlauf</h3>
|
||||
{participantForm.watch("statusLog")?.map((s) => (
|
||||
<div className="flex justify-between" key={(s as any).timestamp}>
|
||||
<p>{(s as any).event}</p>
|
||||
<p>{new Date((s as any).timestamp).toLocaleString()}</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<Button type="submit">Speichern</Button>
|
||||
</form>
|
||||
</div>
|
||||
</dialog>
|
||||
);
|
||||
};
|
||||
@@ -120,6 +120,7 @@ const ModalBtn = ({
|
||||
<div className="flex items-center gap-2 justify-center">
|
||||
<CalendarIcon />
|
||||
{!!dates.length && (
|
||||
// TODO: Prevent users from selecting an appointment that is full
|
||||
<Select
|
||||
form={selectAppointmentForm as any}
|
||||
options={dates.map((date) => ({
|
||||
@@ -180,10 +181,10 @@ const ModalBtn = ({
|
||||
>
|
||||
absagen
|
||||
</button>
|
||||
</div>
|
||||
<p className="mt-3 text-center">
|
||||
Bitte finde dich an diesem Termin in unserem Discord ein.
|
||||
</p>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
@@ -251,7 +252,7 @@ const MoodleCourseIndicator = ({
|
||||
event: Event;
|
||||
}) => {
|
||||
const courseUrl = `${process.env.NEXT_PUBLIC_MOODLE_URL}/course/view.php?id=${moodleCourseId}`;
|
||||
if (!participant || (event.hasPresenceEvents && !participant?.attended))
|
||||
if (event.hasPresenceEvents && !participant?.attended)
|
||||
return (
|
||||
<p className="py-4 flex items-center gap-2 justify-center">
|
||||
<Clock10Icon className="text-error" />
|
||||
|
||||
@@ -25,6 +25,7 @@ interface PaginatedTableProps<TData>
|
||||
leftOfSearch?: React.ReactNode;
|
||||
rightOfSearch?: React.ReactNode;
|
||||
leftOfPagination?: React.ReactNode;
|
||||
hide?: boolean;
|
||||
ref?: Ref<PaginatedTableRef>;
|
||||
}
|
||||
|
||||
@@ -39,6 +40,7 @@ export function PaginatedTable<TData>({
|
||||
leftOfSearch,
|
||||
rightOfSearch,
|
||||
leftOfPagination,
|
||||
hide,
|
||||
...restProps
|
||||
}: PaginatedTableProps<TData>) {
|
||||
const [data, setData] = useState<TData[]>([]);
|
||||
@@ -111,19 +113,23 @@ export function PaginatedTable<TData>({
|
||||
)}
|
||||
<div className="flex justify-center">{rightOfSearch}</div>
|
||||
</div>
|
||||
{!hide && (
|
||||
<SortableTable
|
||||
data={data}
|
||||
prismaModel={prismaModel}
|
||||
showEditButton={showEditButton}
|
||||
{...restProps}
|
||||
/>
|
||||
)}
|
||||
<div className="flex items-between">
|
||||
{leftOfPagination}
|
||||
{!hide && (
|
||||
<Pagination
|
||||
totalPages={Math.ceil(total / rowsPerPage)}
|
||||
page={page}
|
||||
setPage={setPage}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -23,10 +23,11 @@ export const GET = async (req: NextRequest) => {
|
||||
});
|
||||
if (!user)
|
||||
return NextResponse.json({ error: "User not found" }, { status: 404 });
|
||||
|
||||
setTimeout(async () => {
|
||||
console.log("getting moodle ID");
|
||||
const moodleUser = await getMoodleUserById(user.id);
|
||||
prisma.user.update({
|
||||
console.log("got moodle ID", moodleUser.id);
|
||||
await prisma.user.update({
|
||||
where: {
|
||||
id: user.id,
|
||||
},
|
||||
@@ -35,8 +36,6 @@ export const GET = async (req: NextRequest) => {
|
||||
},
|
||||
});
|
||||
|
||||
if (user.moodleId) return;
|
||||
|
||||
const participatingEvents = await prisma.participant.findMany({
|
||||
where: {
|
||||
userId: user.id,
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
"@next-auth/prisma-adapter": "^1.0.7",
|
||||
"@repo/db": "*",
|
||||
"@repo/ui": "*",
|
||||
"@tanstack/react-query": "^5.67.2",
|
||||
"@tanstack/react-table": "^8.20.6",
|
||||
"@uiw/react-md-editor": "^4.0.5",
|
||||
"axios": "^1.7.9",
|
||||
|
||||
@@ -21,6 +21,17 @@ services:
|
||||
- "8080:80"
|
||||
depends_on:
|
||||
- postgres
|
||||
redis:
|
||||
container_name: redis
|
||||
image: docker.io/bitnami/redis:7.4
|
||||
environment:
|
||||
# ALLOW_EMPTY_PASSWORD is recommended only for development.
|
||||
- ALLOW_EMPTY_PASSWORD=yes
|
||||
- REDIS_DISABLE_COMMANDS=FLUSHDB,FLUSHALL
|
||||
ports:
|
||||
- "6379:6379"
|
||||
volumes:
|
||||
- "redis_data:/bitnami/redis/data"
|
||||
|
||||
moodle_database:
|
||||
container_name: moodle_database
|
||||
@@ -62,3 +73,5 @@ volumes:
|
||||
moodle_data:
|
||||
moodle_database:
|
||||
moodle_moodledata:
|
||||
redis_data:
|
||||
driver: local
|
||||
|
||||
538
package-lock.json
generated
538
package-lock.json
generated
@@ -95,6 +95,7 @@
|
||||
"@next-auth/prisma-adapter": "^1.0.7",
|
||||
"@repo/db": "*",
|
||||
"@repo/ui": "*",
|
||||
"@tanstack/react-query": "^5.67.2",
|
||||
"@tanstack/react-table": "^8.20.6",
|
||||
"@uiw/react-md-editor": "^4.0.5",
|
||||
"axios": "^1.7.9",
|
||||
@@ -137,14 +138,18 @@
|
||||
},
|
||||
"apps/hub-server": {
|
||||
"dependencies": {
|
||||
"@react-email/components": "^0.0.33",
|
||||
"axios": "^1.7.9",
|
||||
"cron": "^4.1.0",
|
||||
"dotenv": "^16.4.7"
|
||||
"dotenv": "^16.4.7",
|
||||
"nodemailer": "^6.10.0",
|
||||
"react": "^19.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@repo/db": "*",
|
||||
"@repo/typescript-config": "*",
|
||||
"@types/node": "^22.13.5",
|
||||
"@types/nodemailer": "^6.4.17",
|
||||
"concurrently": "^9.1.2",
|
||||
"typescript": "latest"
|
||||
}
|
||||
@@ -2096,6 +2101,286 @@
|
||||
"react": "^16.x || ^17.x || ^18.x || ^19.0.0 || ^19.0.0-rc"
|
||||
}
|
||||
},
|
||||
"node_modules/@react-email/body": {
|
||||
"version": "0.0.11",
|
||||
"resolved": "https://registry.npmjs.org/@react-email/body/-/body-0.0.11.tgz",
|
||||
"integrity": "sha512-ZSD2SxVSgUjHGrB0Wi+4tu3MEpB4fYSbezsFNEJk2xCWDBkFiOeEsjTmR5dvi+CxTK691hQTQlHv0XWuP7ENTg==",
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"react": "^18.0 || ^19.0 || ^19.0.0-rc"
|
||||
}
|
||||
},
|
||||
"node_modules/@react-email/button": {
|
||||
"version": "0.0.19",
|
||||
"resolved": "https://registry.npmjs.org/@react-email/button/-/button-0.0.19.tgz",
|
||||
"integrity": "sha512-HYHrhyVGt7rdM/ls6FuuD6XE7fa7bjZTJqB2byn6/oGsfiEZaogY77OtoLL/mrQHjHjZiJadtAMSik9XLcm7+A==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=18.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^18.0 || ^19.0 || ^19.0.0-rc"
|
||||
}
|
||||
},
|
||||
"node_modules/@react-email/code-block": {
|
||||
"version": "0.0.11",
|
||||
"resolved": "https://registry.npmjs.org/@react-email/code-block/-/code-block-0.0.11.tgz",
|
||||
"integrity": "sha512-4D43p+LIMjDzm66gTDrZch0Flkip5je91mAT7iGs6+SbPyalHgIA+lFQoQwhz/VzHHLxuD0LV6gwmU/WUQ2WEg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"prismjs": "1.29.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^18.0 || ^19.0 || ^19.0.0-rc"
|
||||
}
|
||||
},
|
||||
"node_modules/@react-email/code-inline": {
|
||||
"version": "0.0.5",
|
||||
"resolved": "https://registry.npmjs.org/@react-email/code-inline/-/code-inline-0.0.5.tgz",
|
||||
"integrity": "sha512-MmAsOzdJpzsnY2cZoPHFPk6uDO/Ncpb4Kh1hAt9UZc1xOW3fIzpe1Pi9y9p6wwUmpaeeDalJxAxH6/fnTquinA==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=18.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^18.0 || ^19.0 || ^19.0.0-rc"
|
||||
}
|
||||
},
|
||||
"node_modules/@react-email/column": {
|
||||
"version": "0.0.13",
|
||||
"resolved": "https://registry.npmjs.org/@react-email/column/-/column-0.0.13.tgz",
|
||||
"integrity": "sha512-Lqq17l7ShzJG/d3b1w/+lVO+gp2FM05ZUo/nW0rjxB8xBICXOVv6PqjDnn3FXKssvhO5qAV20lHM6S+spRhEwQ==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=18.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^18.0 || ^19.0 || ^19.0.0-rc"
|
||||
}
|
||||
},
|
||||
"node_modules/@react-email/components": {
|
||||
"version": "0.0.33",
|
||||
"resolved": "https://registry.npmjs.org/@react-email/components/-/components-0.0.33.tgz",
|
||||
"integrity": "sha512-/GKdT3YijT1iEWPAXF644jr12w5xVgzUr0zlbZGt2KOkGeFHNZUCL5UtRopmnjrH/Fayf8Gjv6q/4E2cZgDtdQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@react-email/body": "0.0.11",
|
||||
"@react-email/button": "0.0.19",
|
||||
"@react-email/code-block": "0.0.11",
|
||||
"@react-email/code-inline": "0.0.5",
|
||||
"@react-email/column": "0.0.13",
|
||||
"@react-email/container": "0.0.15",
|
||||
"@react-email/font": "0.0.9",
|
||||
"@react-email/head": "0.0.12",
|
||||
"@react-email/heading": "0.0.15",
|
||||
"@react-email/hr": "0.0.11",
|
||||
"@react-email/html": "0.0.11",
|
||||
"@react-email/img": "0.0.11",
|
||||
"@react-email/link": "0.0.12",
|
||||
"@react-email/markdown": "0.0.14",
|
||||
"@react-email/preview": "0.0.12",
|
||||
"@react-email/render": "1.0.5",
|
||||
"@react-email/row": "0.0.12",
|
||||
"@react-email/section": "0.0.16",
|
||||
"@react-email/tailwind": "1.0.4",
|
||||
"@react-email/text": "0.0.11"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^18.0 || ^19.0 || ^19.0.0-rc"
|
||||
}
|
||||
},
|
||||
"node_modules/@react-email/container": {
|
||||
"version": "0.0.15",
|
||||
"resolved": "https://registry.npmjs.org/@react-email/container/-/container-0.0.15.tgz",
|
||||
"integrity": "sha512-Qo2IQo0ru2kZq47REmHW3iXjAQaKu4tpeq/M8m1zHIVwKduL2vYOBQWbC2oDnMtWPmkBjej6XxgtZByxM6cCFg==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=18.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^18.0 || ^19.0 || ^19.0.0-rc"
|
||||
}
|
||||
},
|
||||
"node_modules/@react-email/font": {
|
||||
"version": "0.0.9",
|
||||
"resolved": "https://registry.npmjs.org/@react-email/font/-/font-0.0.9.tgz",
|
||||
"integrity": "sha512-4zjq23oT9APXkerqeslPH3OZWuh5X4crHK6nx82mVHV2SrLba8+8dPEnWbaACWTNjOCbcLIzaC9unk7Wq2MIXw==",
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"react": "^18.0 || ^19.0 || ^19.0.0-rc"
|
||||
}
|
||||
},
|
||||
"node_modules/@react-email/head": {
|
||||
"version": "0.0.12",
|
||||
"resolved": "https://registry.npmjs.org/@react-email/head/-/head-0.0.12.tgz",
|
||||
"integrity": "sha512-X2Ii6dDFMF+D4niNwMAHbTkeCjlYYnMsd7edXOsi0JByxt9wNyZ9EnhFiBoQdqkE+SMDcu8TlNNttMrf5sJeMA==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=18.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^18.0 || ^19.0 || ^19.0.0-rc"
|
||||
}
|
||||
},
|
||||
"node_modules/@react-email/heading": {
|
||||
"version": "0.0.15",
|
||||
"resolved": "https://registry.npmjs.org/@react-email/heading/-/heading-0.0.15.tgz",
|
||||
"integrity": "sha512-xF2GqsvBrp/HbRHWEfOgSfRFX+Q8I5KBEIG5+Lv3Vb2R/NYr0s8A5JhHHGf2pWBMJdbP4B2WHgj/VUrhy8dkIg==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=18.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^18.0 || ^19.0 || ^19.0.0-rc"
|
||||
}
|
||||
},
|
||||
"node_modules/@react-email/hr": {
|
||||
"version": "0.0.11",
|
||||
"resolved": "https://registry.npmjs.org/@react-email/hr/-/hr-0.0.11.tgz",
|
||||
"integrity": "sha512-S1gZHVhwOsd1Iad5IFhpfICwNPMGPJidG/Uysy1AwmspyoAP5a4Iw3OWEpINFdgh9MHladbxcLKO2AJO+cA9Lw==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=18.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^18.0 || ^19.0 || ^19.0.0-rc"
|
||||
}
|
||||
},
|
||||
"node_modules/@react-email/html": {
|
||||
"version": "0.0.11",
|
||||
"resolved": "https://registry.npmjs.org/@react-email/html/-/html-0.0.11.tgz",
|
||||
"integrity": "sha512-qJhbOQy5VW5qzU74AimjAR9FRFQfrMa7dn4gkEXKMB/S9xZN8e1yC1uA9C15jkXI/PzmJ0muDIWmFwatm5/+VA==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=18.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^18.0 || ^19.0 || ^19.0.0-rc"
|
||||
}
|
||||
},
|
||||
"node_modules/@react-email/img": {
|
||||
"version": "0.0.11",
|
||||
"resolved": "https://registry.npmjs.org/@react-email/img/-/img-0.0.11.tgz",
|
||||
"integrity": "sha512-aGc8Y6U5C3igoMaqAJKsCpkbm1XjguQ09Acd+YcTKwjnC2+0w3yGUJkjWB2vTx4tN8dCqQCXO8FmdJpMfOA9EQ==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=18.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^18.0 || ^19.0 || ^19.0.0-rc"
|
||||
}
|
||||
},
|
||||
"node_modules/@react-email/link": {
|
||||
"version": "0.0.12",
|
||||
"resolved": "https://registry.npmjs.org/@react-email/link/-/link-0.0.12.tgz",
|
||||
"integrity": "sha512-vF+xxQk2fGS1CN7UPQDbzvcBGfffr+GjTPNiWM38fhBfsLv6A/YUfaqxWlmL7zLzVmo0K2cvvV9wxlSyNba1aQ==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=18.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^18.0 || ^19.0 || ^19.0.0-rc"
|
||||
}
|
||||
},
|
||||
"node_modules/@react-email/markdown": {
|
||||
"version": "0.0.14",
|
||||
"resolved": "https://registry.npmjs.org/@react-email/markdown/-/markdown-0.0.14.tgz",
|
||||
"integrity": "sha512-5IsobCyPkb4XwnQO8uFfGcNOxnsg3311GRXhJ3uKv51P7Jxme4ycC/MITnwIZ10w2zx7HIyTiqVzTj4XbuIHbg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"md-to-react-email": "5.0.5"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^18.0 || ^19.0 || ^19.0.0-rc"
|
||||
}
|
||||
},
|
||||
"node_modules/@react-email/preview": {
|
||||
"version": "0.0.12",
|
||||
"resolved": "https://registry.npmjs.org/@react-email/preview/-/preview-0.0.12.tgz",
|
||||
"integrity": "sha512-g/H5fa9PQPDK6WUEG7iTlC19sAktI23qyoiJtMLqQiXFCfWeQMhqjLGKeLSKkfzszqmfJCjZtpSiKtBoOdxp3Q==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=18.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^18.0 || ^19.0 || ^19.0.0-rc"
|
||||
}
|
||||
},
|
||||
"node_modules/@react-email/render": {
|
||||
"version": "1.0.5",
|
||||
"resolved": "https://registry.npmjs.org/@react-email/render/-/render-1.0.5.tgz",
|
||||
"integrity": "sha512-CA69HYXPk21HhtAXATIr+9JJwpDNmAFCvdMUjWmeoD1+KhJ9NAxusMRxKNeibdZdslmq3edaeOKGbdQ9qjK8LQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"html-to-text": "9.0.5",
|
||||
"prettier": "3.4.2",
|
||||
"react-promise-suspense": "0.3.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^18.0 || ^19.0 || ^19.0.0-rc",
|
||||
"react-dom": "^18.0 || ^19.0 || ^19.0.0-rc"
|
||||
}
|
||||
},
|
||||
"node_modules/@react-email/row": {
|
||||
"version": "0.0.12",
|
||||
"resolved": "https://registry.npmjs.org/@react-email/row/-/row-0.0.12.tgz",
|
||||
"integrity": "sha512-HkCdnEjvK3o+n0y0tZKXYhIXUNPDx+2vq1dJTmqappVHXS5tXS6W5JOPZr5j+eoZ8gY3PShI2LWj5rWF7ZEtIQ==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=18.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^18.0 || ^19.0 || ^19.0.0-rc"
|
||||
}
|
||||
},
|
||||
"node_modules/@react-email/section": {
|
||||
"version": "0.0.16",
|
||||
"resolved": "https://registry.npmjs.org/@react-email/section/-/section-0.0.16.tgz",
|
||||
"integrity": "sha512-FjqF9xQ8FoeUZYKSdt8sMIKvoT9XF8BrzhT3xiFKdEMwYNbsDflcjfErJe3jb7Wj/es/lKTbV5QR1dnLzGpL3w==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=18.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^18.0 || ^19.0 || ^19.0.0-rc"
|
||||
}
|
||||
},
|
||||
"node_modules/@react-email/tailwind": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/@react-email/tailwind/-/tailwind-1.0.4.tgz",
|
||||
"integrity": "sha512-tJdcusncdqgvTUYZIuhNC6LYTfL9vNTSQpwWdTCQhQ1lsrNCEE4OKCSdzSV3S9F32pi0i0xQ+YPJHKIzGjdTSA==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=18.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^18.0 || ^19.0 || ^19.0.0-rc"
|
||||
}
|
||||
},
|
||||
"node_modules/@react-email/text": {
|
||||
"version": "0.0.11",
|
||||
"resolved": "https://registry.npmjs.org/@react-email/text/-/text-0.0.11.tgz",
|
||||
"integrity": "sha512-a7nl/2KLpRHOYx75YbYZpWspUbX1DFY7JIZbOv5x0QU8SvwDbJt+Hm01vG34PffFyYvHEXrc6Qnip2RTjljNjg==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=18.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^18.0 || ^19.0 || ^19.0.0-rc"
|
||||
}
|
||||
},
|
||||
"node_modules/@repo/db": {
|
||||
"resolved": "packages/database",
|
||||
"link": true
|
||||
@@ -2124,6 +2409,19 @@
|
||||
"integrity": "sha512-kkKUDVlII2DQiKy7UstOR1ErJP8kUKAQ4oa+SQtM0K+lPdmmjj0YnnxBgtTVYH7mUKtbsxeFC9y0AmK7Yb78/A==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@selderee/plugin-htmlparser2": {
|
||||
"version": "0.11.0",
|
||||
"resolved": "https://registry.npmjs.org/@selderee/plugin-htmlparser2/-/plugin-htmlparser2-0.11.0.tgz",
|
||||
"integrity": "sha512-P33hHGdldxGabLFjPPpaTxVolMrzrcegejx+0GxjrIb9Zv48D8yAIA/QTDR2dFl7Uz7urX8aX6+5bCZslr+gWQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"domhandler": "^5.0.3",
|
||||
"selderee": "^0.11.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://ko-fi.com/killymxi"
|
||||
}
|
||||
},
|
||||
"node_modules/@swc/counter": {
|
||||
"version": "0.1.3",
|
||||
"resolved": "https://registry.npmjs.org/@swc/counter/-/counter-0.1.3.tgz",
|
||||
@@ -2381,6 +2679,32 @@
|
||||
"node": "^10 || ^12 || >=14"
|
||||
}
|
||||
},
|
||||
"node_modules/@tanstack/query-core": {
|
||||
"version": "5.67.2",
|
||||
"resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.67.2.tgz",
|
||||
"integrity": "sha512-+iaFJ/pt8TaApCk6LuZ0WHS/ECVfTzrxDOEL9HH9Dayyb5OVuomLzDXeSaI2GlGT/8HN7bDGiRXDts3LV+u6ww==",
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/tannerlinsley"
|
||||
}
|
||||
},
|
||||
"node_modules/@tanstack/react-query": {
|
||||
"version": "5.67.2",
|
||||
"resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.67.2.tgz",
|
||||
"integrity": "sha512-6Sa+BVNJWhAV4QHvIqM73norNeGRWGC3ftN0Ix87cmMvI215I1wyJ44KUTt/9a0V9YimfGcg25AITaYVel71Og==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@tanstack/query-core": "5.67.2"
|
||||
},
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/tannerlinsley"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^18 || ^19"
|
||||
}
|
||||
},
|
||||
"node_modules/@tanstack/react-table": {
|
||||
"version": "8.20.6",
|
||||
"resolved": "https://registry.npmjs.org/@tanstack/react-table/-/react-table-8.20.6.tgz",
|
||||
@@ -2655,6 +2979,16 @@
|
||||
"undici-types": "~6.19.2"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/nodemailer": {
|
||||
"version": "6.4.17",
|
||||
"resolved": "https://registry.npmjs.org/@types/nodemailer/-/nodemailer-6.4.17.tgz",
|
||||
"integrity": "sha512-I9CCaIp6DTldEg7vyUTZi8+9Vo0hi1/T8gv3C89yk1rSAAzoKQ8H8ki/jBYJSFoH/BisgLP8tkZMlQ91CIquww==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/parse-json": {
|
||||
"version": "4.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.2.tgz",
|
||||
@@ -4570,6 +4904,15 @@
|
||||
"integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/deepmerge": {
|
||||
"version": "4.3.1",
|
||||
"resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz",
|
||||
"integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/defaults": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.4.tgz",
|
||||
@@ -4751,6 +5094,61 @@
|
||||
"csstype": "^3.0.2"
|
||||
}
|
||||
},
|
||||
"node_modules/dom-serializer": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz",
|
||||
"integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"domelementtype": "^2.3.0",
|
||||
"domhandler": "^5.0.2",
|
||||
"entities": "^4.2.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/cheeriojs/dom-serializer?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/domelementtype": {
|
||||
"version": "2.3.0",
|
||||
"resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz",
|
||||
"integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/fb55"
|
||||
}
|
||||
],
|
||||
"license": "BSD-2-Clause"
|
||||
},
|
||||
"node_modules/domhandler": {
|
||||
"version": "5.0.3",
|
||||
"resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz",
|
||||
"integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==",
|
||||
"license": "BSD-2-Clause",
|
||||
"dependencies": {
|
||||
"domelementtype": "^2.3.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/fb55/domhandler?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/domutils": {
|
||||
"version": "3.2.2",
|
||||
"resolved": "https://registry.npmjs.org/domutils/-/domutils-3.2.2.tgz",
|
||||
"integrity": "sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==",
|
||||
"license": "BSD-2-Clause",
|
||||
"dependencies": {
|
||||
"dom-serializer": "^2.0.0",
|
||||
"domelementtype": "^2.3.0",
|
||||
"domhandler": "^5.0.3"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/fb55/domutils?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/dot-case": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/dot-case/-/dot-case-2.1.1.tgz",
|
||||
@@ -6886,6 +7284,22 @@
|
||||
"react-is": "^16.7.0"
|
||||
}
|
||||
},
|
||||
"node_modules/html-to-text": {
|
||||
"version": "9.0.5",
|
||||
"resolved": "https://registry.npmjs.org/html-to-text/-/html-to-text-9.0.5.tgz",
|
||||
"integrity": "sha512-qY60FjREgVZL03vJU6IfMV4GDjGBIoOyvuFdpBDIX9yTlDw0TjxVBQp+P8NvpdIXNJvfWBTNul7fsAQJq2FNpg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@selderee/plugin-htmlparser2": "^0.11.0",
|
||||
"deepmerge": "^4.3.1",
|
||||
"dom-serializer": "^2.0.0",
|
||||
"htmlparser2": "^8.0.2",
|
||||
"selderee": "^0.11.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14"
|
||||
}
|
||||
},
|
||||
"node_modules/html-url-attributes": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/html-url-attributes/-/html-url-attributes-3.0.1.tgz",
|
||||
@@ -6906,6 +7320,25 @@
|
||||
"url": "https://github.com/sponsors/wooorm"
|
||||
}
|
||||
},
|
||||
"node_modules/htmlparser2": {
|
||||
"version": "8.0.2",
|
||||
"resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-8.0.2.tgz",
|
||||
"integrity": "sha512-GYdjWKDkbRLkZ5geuHs5NY1puJ+PXwP7+fHPRz06Eirsb9ugf6d8kkXav6ADhcODhFFPMIXyxkxSuMf3D6NCFA==",
|
||||
"funding": [
|
||||
"https://github.com/fb55/htmlparser2?sponsor=1",
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/fb55"
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"domelementtype": "^2.3.0",
|
||||
"domhandler": "^5.0.3",
|
||||
"domutils": "^3.0.1",
|
||||
"entities": "^4.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/http-proxy-agent": {
|
||||
"version": "7.0.2",
|
||||
"resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz",
|
||||
@@ -7987,6 +8420,15 @@
|
||||
"node": ">=0.10"
|
||||
}
|
||||
},
|
||||
"node_modules/leac": {
|
||||
"version": "0.6.0",
|
||||
"resolved": "https://registry.npmjs.org/leac/-/leac-0.6.0.tgz",
|
||||
"integrity": "sha512-y+SqErxb8h7nE/fiEX07jsbuhrpO9lL8eca7/Y1nuWV2moNlXhyd59iDGcRf6moVyDMbmTNzL40SUyrFU/yDpg==",
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"url": "https://ko-fi.com/killymxi"
|
||||
}
|
||||
},
|
||||
"node_modules/leaflet": {
|
||||
"version": "1.9.4",
|
||||
"resolved": "https://registry.npmjs.org/leaflet/-/leaflet-1.9.4.tgz",
|
||||
@@ -8405,6 +8847,18 @@
|
||||
"url": "https://github.com/sponsors/wooorm"
|
||||
}
|
||||
},
|
||||
"node_modules/marked": {
|
||||
"version": "7.0.4",
|
||||
"resolved": "https://registry.npmjs.org/marked/-/marked-7.0.4.tgz",
|
||||
"integrity": "sha512-t8eP0dXRJMtMvBojtkcsA7n48BkauktUKzfkPSCq85ZMTJ0v76Rke4DYz01omYpPTUh4p/f7HePgRo3ebG8+QQ==",
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"marked": "bin/marked.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 16"
|
||||
}
|
||||
},
|
||||
"node_modules/math-intrinsics": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
|
||||
@@ -8413,6 +8867,18 @@
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/md-to-react-email": {
|
||||
"version": "5.0.5",
|
||||
"resolved": "https://registry.npmjs.org/md-to-react-email/-/md-to-react-email-5.0.5.tgz",
|
||||
"integrity": "sha512-OvAXqwq57uOk+WZqFFNCMZz8yDp8BD3WazW1wAKHUrPbbdr89K9DWS6JXY09vd9xNdPNeurI8DU/X4flcfaD8A==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"marked": "7.0.4"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^18.0 || ^19.0"
|
||||
}
|
||||
},
|
||||
"node_modules/mdast-util-find-and-replace": {
|
||||
"version": "3.0.2",
|
||||
"resolved": "https://registry.npmjs.org/mdast-util-find-and-replace/-/mdast-util-find-and-replace-3.0.2.tgz",
|
||||
@@ -9666,6 +10132,15 @@
|
||||
"integrity": "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/nodemailer": {
|
||||
"version": "6.10.0",
|
||||
"resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-6.10.0.tgz",
|
||||
"integrity": "sha512-SQ3wZCExjeSatLE/HBaXS5vqUOQk6GtBdIIKxiFdmm01mOQZX/POJkO3SUX1wDiYcwUOJwT23scFSC9fY2H8IA==",
|
||||
"license": "MIT-0",
|
||||
"engines": {
|
||||
"node": ">=6.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/npm": {
|
||||
"version": "11.1.0",
|
||||
"resolved": "https://registry.npmjs.org/npm/-/npm-11.1.0.tgz",
|
||||
@@ -12615,6 +13090,19 @@
|
||||
"url": "https://github.com/inikulin/parse5?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/parseley": {
|
||||
"version": "0.12.1",
|
||||
"resolved": "https://registry.npmjs.org/parseley/-/parseley-0.12.1.tgz",
|
||||
"integrity": "sha512-e6qHKe3a9HWr0oMRVDTRhKce+bRO8VGQR3NyVwcjwrbhMmFCX9KszEV35+rn4AdilFAq9VPxP/Fe1wC9Qjd2lw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"leac": "^0.6.0",
|
||||
"peberminta": "^0.9.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://ko-fi.com/killymxi"
|
||||
}
|
||||
},
|
||||
"node_modules/pascal-case": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/pascal-case/-/pascal-case-2.0.1.tgz",
|
||||
@@ -12674,6 +13162,15 @@
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/peberminta": {
|
||||
"version": "0.9.0",
|
||||
"resolved": "https://registry.npmjs.org/peberminta/-/peberminta-0.9.0.tgz",
|
||||
"integrity": "sha512-XIxfHpEuSJbITd1H3EeQwpcZbTLHc+VVr8ANI9t5sit565tsI4/xK3KWTUFE2e6QiangUkh3B0jihzmGnNrRsQ==",
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"url": "https://ko-fi.com/killymxi"
|
||||
}
|
||||
},
|
||||
"node_modules/picocolors": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
|
||||
@@ -12857,7 +13354,6 @@
|
||||
"version": "3.4.2",
|
||||
"resolved": "https://registry.npmjs.org/prettier/-/prettier-3.4.2.tgz",
|
||||
"integrity": "sha512-e9MewbtFo+Fevyuxn/4rrcDAaq0IYxPGLvObpQjiZBMAzB9IGmzlnG9RZy3FFas+eBMu2vA0CszMeduow5dIuQ==",
|
||||
"dev": true,
|
||||
"bin": {
|
||||
"prettier": "bin/prettier.cjs"
|
||||
},
|
||||
@@ -12902,6 +13398,15 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/prismjs": {
|
||||
"version": "1.29.0",
|
||||
"resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.29.0.tgz",
|
||||
"integrity": "sha512-Kx/1w86q/epKcmte75LNrEoT+lX8pBpavuAbvJWRXar7Hz8jrtF+e3vY751p0R8H9HdArwaCTNDDzHg/ScJK1Q==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/prop-types": {
|
||||
"version": "15.8.1",
|
||||
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz",
|
||||
@@ -13013,6 +13518,7 @@
|
||||
"version": "19.0.0",
|
||||
"resolved": "https://registry.npmjs.org/react/-/react-19.0.0.tgz",
|
||||
"integrity": "sha512-V8AVnmPIICiWpGfm6GLzCR/W5FXLchHop40W4nXBmdlEceh16rCN8O8LNWm5bh5XUX91fh7KpA+W0TgMKmgTpQ==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
@@ -13126,6 +13632,21 @@
|
||||
"react": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/react-promise-suspense": {
|
||||
"version": "0.3.4",
|
||||
"resolved": "https://registry.npmjs.org/react-promise-suspense/-/react-promise-suspense-0.3.4.tgz",
|
||||
"integrity": "sha512-I42jl7L3Ze6kZaq+7zXWSunBa3b1on5yfvUW6Eo/3fFOj6dZ5Bqmcd264nJbTK/gn1HjjILAjSwnZbV4RpSaNQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"fast-deep-equal": "^2.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/react-promise-suspense/node_modules/fast-deep-equal": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz",
|
||||
"integrity": "sha512-bCK/2Z4zLidyB4ReuIsvALH6w31YfAQDmXMqMx6FyfHqvBxtjC0eRumeSu4Bs3XtXwpyIywtSTrVT99BxY1f9w==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/react-select": {
|
||||
"version": "5.10.0",
|
||||
"resolved": "https://registry.npmjs.org/react-select/-/react-select-5.10.0.tgz",
|
||||
@@ -13784,6 +14305,18 @@
|
||||
"integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/selderee": {
|
||||
"version": "0.11.0",
|
||||
"resolved": "https://registry.npmjs.org/selderee/-/selderee-0.11.0.tgz",
|
||||
"integrity": "sha512-5TF+l7p4+OsnP8BCCvSyZiSPc4x4//p5uPwK8TCnVPJYRmU2aYKMpOXvw8zM5a5JvuuCGN1jmsMwuU2W02ukfA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"parseley": "^0.12.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://ko-fi.com/killymxi"
|
||||
}
|
||||
},
|
||||
"node_modules/semver": {
|
||||
"version": "7.6.3",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz",
|
||||
@@ -15625,6 +16158,7 @@
|
||||
"react-dom": "^19.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@repo/db": "*",
|
||||
"@repo/eslint-config": "*",
|
||||
"@repo/typescript-config": "*",
|
||||
"@turbo/gen": "^1.12.4",
|
||||
|
||||
@@ -22,7 +22,7 @@ model Participant {
|
||||
finisherMoodleCurseCompleted Boolean @default(false)
|
||||
attended Boolean @default(false)
|
||||
appointmentCancelled Boolean @default(false)
|
||||
finished Boolean @default(false)
|
||||
completetionWorkflowFinished Boolean @default(false)
|
||||
eventAppointmentId Int?
|
||||
statusLog Json[] @default([])
|
||||
eventId Int
|
||||
|
||||
Reference in New Issue
Block a user