fixed moodle logic

This commit is contained in:
PxlLoewe
2025-06-26 13:40:33 -07:00
parent 8968bff1c5
commit 22a406c2d1
13 changed files with 127 additions and 95 deletions

View File

@@ -1,7 +1,8 @@
HUB_SERVER_PORT=3003 HUB_SERVER_PORT=3003
MOODLE_TOKEN=ac346f0324647b68488d13fd52a9bbe8 MOODLE_API_TOKEN=ac346f0324647b68488d13fd52a9bbe8
NEXT_PUBLIC_HUB_URL=http://localhost:3000 NEXT_PUBLIC_HUB_URL=http://localhost:3000
MOODLE_URL=http://localhost:8081 MOODLE_URL=http://localhost:8081
NEXT_PUBLIC_MOODLE_URL=
MAIL_SERVER="asmtp.mail.hostpoint.ch" MAIL_SERVER="asmtp.mail.hostpoint.ch"
MAIL_USER="noreply@virtualairrescue.com" MAIL_USER="noreply@virtualairrescue.com"
MAIL_PASSWORD="b7316PB8aDPCC%-&" MAIL_PASSWORD="b7316PB8aDPCC%-&"

View File

@@ -4,8 +4,12 @@ export const getMoodleUserById = async (id: string) => {
const { data: user } = await axios.get( const { data: user } = await axios.get(
`${process.env.NEXT_PUBLIC_MOODLE_URL}/webservice/rest/server.php`, `${process.env.NEXT_PUBLIC_MOODLE_URL}/webservice/rest/server.php`,
{ {
auth: {
username: "moodleuser",
password: "Xo1SXaLYBa7Yb6WW",
},
params: { params: {
wstoken: process.env.MOODLE_TOKEN, wstoken: process.env.MOODLE_API_TOKEN,
wsfunction: "core_user_get_users_by_field", wsfunction: "core_user_get_users_by_field",
moodlewsrestformat: "json", moodlewsrestformat: "json",
field: "idnumber", field: "idnumber",
@@ -33,8 +37,12 @@ export const getMoodleQuizResult = async (userId: string, quizId: string) => {
const { data: quizzes } = await axios.get( const { data: quizzes } = await axios.get(
`${process.env.NEXT_PUBLIC_MOODLE_URL}/webservice/rest/server.php`, `${process.env.NEXT_PUBLIC_MOODLE_URL}/webservice/rest/server.php`,
{ {
auth: {
username: "moodleuser",
password: "Xo1SXaLYBa7Yb6WW",
},
params: { params: {
wstoken: process.env.MOODLE_TOKEN, wstoken: process.env.MOODLE_API_TOKEN,
wsfunction: "mod_quiz_get_user_attempts", wsfunction: "mod_quiz_get_user_attempts",
moodlewsrestformat: "json", moodlewsrestformat: "json",
quizid: quizId, quizid: quizId,
@@ -49,8 +57,12 @@ export const getMoodleCourseCompletionStatus = async (userId: string, courseId:
const { data: completionStatus } = await axios.get( const { data: completionStatus } = await axios.get(
`${process.env.NEXT_PUBLIC_MOODLE_URL}/webservice/rest/server.php`, `${process.env.NEXT_PUBLIC_MOODLE_URL}/webservice/rest/server.php`,
{ {
auth: {
username: "moodleuser",
password: "Xo1SXaLYBa7Yb6WW",
},
params: { params: {
wstoken: process.env.MOODLE_TOKEN, wstoken: process.env.MOODLE_API_TOKEN,
wsfunction: "core_completion_get_course_completion_status", wsfunction: "core_completion_get_course_completion_status",
moodlewsrestformat: "json", moodlewsrestformat: "json",
userid: userId, userid: userId,

View File

@@ -25,7 +25,6 @@ router.post("/handle-participant-finished", async (req, res) => {
}, },
}, },
}); });
console.log("Handeling Participant-completed", participant?.User.publicId);
if (!participant) { if (!participant) {
res.status(404).json({ error: "Participant not found" }); res.status(404).json({ error: "Participant not found" });
return; return;
@@ -62,11 +61,16 @@ router.post("/check-moodle-results", async (req, res) => {
res.status(400).json({ error: "Teilnehmer hat keine Moodle-ID" }); res.status(400).json({ error: "Teilnehmer hat keine Moodle-ID" });
return; return;
} }
const quizzResult = await getMoodleCourseCompletionStatus( const courseStatus = await getMoodleCourseCompletionStatus(
participant.User.moodleId.toString(), participant.User.moodleId.toString(),
participant.Event.finisherMoodleCourseId!, participant.Event.finisherMoodleCourseId!,
); );
if (quizzResult?.completionstatus?.completed === true) {
if (courseStatus?.completionstatus?.completed === true) {
prisma.participant.update({
where: { id: participant.id },
data: { finisherMoodleCurseCompleted: true },
});
await handleParticipantFinished(participant.Event, participant, participant.User); await handleParticipantFinished(participant.Event, participant, participant.User);
res res
.status(200) .status(200)

View File

@@ -9,6 +9,6 @@ DISCORD_BOT_TOKEN=
NEXT_PUBLIC_DISCORD_URL= NEXT_PUBLIC_DISCORD_URL=
DISCORD_REDIRECT= DISCORD_REDIRECT=
MOODLE_PW=var-api-user-P1 MOODLE_PW=var-api-user-P1
MOODLE_TOKEN=ac346f0324647b68488d13fd52a9bbe8 MOODLE_API_TOKEN=ac346f0324647b68488d13fd52a9bbe8
NEXT_PUBLIC_MOODLE_URL=http://localhost:8081 NEXT_PUBLIC_MOODLE_URL=http://localhost:8081
NEXT_PUBLIC_DISPATCH_URL=http://localhost:3001 NEXT_PUBLIC_DISPATCH_URL=http://localhost:3001

View File

@@ -62,15 +62,6 @@ export const AppointmentModal = ({
timeCaption="Uhrzeit" timeCaption="Uhrzeit"
showTimeCaption showTimeCaption
/> />
{/* <Input
form={appointmentForm}
type="datetime-local"
label="Datum"
name="appointmentDate"
formOptions={{
valueAsDate: true,
}}
/> */}
<div> <div>
<PaginatedTable <PaginatedTable
hide={appointmentForm.watch("id") === undefined} hide={appointmentForm.watch("id") === undefined}

View File

@@ -55,23 +55,28 @@ export const ParticipantModal = ({ participantForm, ref }: ParticipantModalProps
<Button <Button
className="btn-sm" className="btn-sm"
onClick={async () => { onClick={async () => {
if (!participantForm.watch("id")) return; try {
if (!participantForm.watch("id")) return;
const participant = participantForm.getValues(); const participant = participantForm.getValues();
await handleParticipantFinished(participant.id.toString()).catch((e) => { await handleParticipantFinished(participant.id.toString()).catch((e) => {
const error = e as AxiosError; const error = e as AxiosError;
});
toast.success("Workflow erfolgreich ausgeführt");
router.refresh();
} catch (error) {
const e = error as AxiosError;
if ( if (
error.response?.data && e.response?.data &&
typeof error.response.data === "object" && typeof e.response.data === "object" &&
"error" in error.response.data "error" in e.response.data
) { ) {
toast.error(`Fehler: ${error.response.data.error}`); toast.error(`Fehler: ${e.response.data.error}`);
} else { } else {
toast.error("Unbekannter Fehler beim ausführen des Workflows"); toast.error("Unbekannter Fehler beim ausführen des Workflows");
} }
}); }
toast.success("Workflow erfolgreich ausgeführt");
router.refresh();
}} }}
> >
Event-abgeschlossen Workflow ausführen Event-abgeschlossen Workflow ausführen

View File

@@ -158,7 +158,7 @@ export const ProfileForm: React.FC<ProfileFormProps> = ({ user }: ProfileFormPro
form={form} form={form}
name="permissions" name="permissions"
label="Permissions" label="Permissions"
isDisabled={user.permissions.length > (session.data?.user?.permissions?.length ?? 0)} isDisabled={!session.data?.user.permissions.includes("ADMIN_USER_ADVANCED")}
options={Object.entries(PERMISSION).map(([key, value]) => ({ options={Object.entries(PERMISSION).map(([key, value]) => ({
label: value, label: value,
value: key, value: key,
@@ -325,6 +325,7 @@ export const ConnectionHistory: React.FC<{ user: User }> = ({ user }: { user: Us
export const UserPenalties = ({ user }: { user: User }) => { export const UserPenalties = ({ user }: { user: User }) => {
const createdUser = useSession().data?.user; const createdUser = useSession().data?.user;
const penaltyTable = useRef<PaginatedTableRef>(null); const penaltyTable = useRef<PaginatedTableRef>(null);
const session = useSession();
return ( return (
<div className="card-body"> <div className="card-body">
<h2 className="card-title flex justify-between"> <h2 className="card-title flex justify-between">
@@ -353,25 +354,27 @@ export const UserPenalties = ({ user }: { user: User }) => {
btnTip="Timeban hinzufügen" btnTip="Timeban hinzufügen"
showDatePicker={true} showDatePicker={true}
/> />
<PenaltyDropdown {session.data?.user.permissions.includes("ADMIN_USER_ADVANCED") && (
Icon={<LockKeyhole size={15} />} <PenaltyDropdown
onClick={async ({ reason }) => { Icon={<LockKeyhole size={15} />}
if (!reason) return toast.error("Bitte gib einen Grund für die Strafe an."); onClick={async ({ reason }) => {
if (!createdUser) if (!reason) return toast.error("Bitte gib einen Grund für die Strafe an.");
return toast.error("Du musst eingeloggt sein, um eine Strafe zu erstellen."); if (!createdUser)
await addPenalty({ return toast.error("Du musst eingeloggt sein, um eine Strafe zu erstellen.");
reason, await addPenalty({
type: "BAN", reason,
userId: user.id, type: "BAN",
createdUserId: createdUser.id, userId: user.id,
}); createdUserId: createdUser.id,
await editUser(user.id, { isBanned: true }); });
penaltyTable.current?.refresh(); await editUser(user.id, { isBanned: true });
toast.success("Ban wurde hinzugefügt!"); penaltyTable.current?.refresh();
}} toast.success("Ban wurde hinzugefügt!");
btnClassName="btn btn-outline btn-error tooltip-error" }}
btnTip="Rechte-entzug hinzufügen" btnClassName="btn btn-outline btn-error tooltip-error"
/> btnTip="Nutzerkonto sperren"
/>
)}
</div> </div>
</h2> </h2>
<PaginatedTable <PaginatedTable
@@ -484,7 +487,7 @@ export const AdminForm = ({
</Button> </Button>
</div> </div>
)} )}
{user.isBanned && ( {user.isBanned && session?.user.permissions.includes("ADMIN_USER_ADVANCED") && (
<Button <Button
onClick={async () => { onClick={async () => {
await editUser(user.id, { isBanned: false }); await editUser(user.id, { isBanned: false });

View File

@@ -1,5 +1,5 @@
"use client"; "use client";
import { useEffect } from "react"; import { use, useEffect } from "react";
import { CheckCircledIcon, CalendarIcon, EnterIcon } from "@radix-ui/react-icons"; import { CheckCircledIcon, CalendarIcon, EnterIcon } from "@radix-ui/react-icons";
import { Event, EventAppointment, Participant, User } from "@repo/db"; import { Event, EventAppointment, Participant, User } from "@repo/db";
import { cn } from "../../../../helper/cn"; import { cn } from "../../../../helper/cn";
@@ -87,6 +87,10 @@ const ModalBtn = ({
? (selectedDate as any)?.Participants?.length + 1 ? (selectedDate as any)?.Participants?.length + 1
: ownIndexInParticipantList + 1; : ownIndexInParticipantList + 1;
const missingRequirements =
event.requiredBadges?.length > 0 &&
!event.requiredBadges.some((badge) => user.badges.includes(badge));
return ( return (
<> <>
<button <button
@@ -96,14 +100,20 @@ const ModalBtn = ({
eventCompleted(event, participant) && "btn-success", eventCompleted(event, participant) && "btn-success",
)} )}
onClick={openModal} onClick={openModal}
disabled={eventCompleted(event, participant)} disabled={eventCompleted(event, participant) || missingRequirements}
> >
{participant && !eventCompleted(event, participant) && ( {missingRequirements && (
<>
<TriangleAlert className="h-6 w-6 shrink-0 stroke-current" fill="none" />
fehlende Anforderungen
</>
)}
{participant && !eventCompleted(event, participant) && !missingRequirements && (
<> <>
<EyeIcon /> Anzeigen <EyeIcon /> Anzeigen
</> </>
)} )}
{!participant && ( {!participant && !missingRequirements && (
<> <>
<EnterIcon /> Anmelden <EnterIcon /> Anmelden
</> </>
@@ -287,16 +297,20 @@ const MoodleCourseIndicator = ({
<button <button
className="btn btn-xs btn-info ml-2" className="btn btn-xs btn-info ml-2"
onClick={async () => { onClick={async () => {
await upsertParticipant({ try {
eventId: event.id, await upsertParticipant({
userId: user.id, eventId: event.id,
finisherMoodleCurseCompleted: false, userId: user.id,
}); finisherMoodleCurseCompleted: false,
});
if (user.moodleId) { if (user.moodleId) {
await inscribeToMoodleCourse(moodleCourseId, user.moodleId); await inscribeToMoodleCourse(moodleCourseId, user.moodleId);
}
window.open(courseUrl, "_blank");
} catch (error) {
console.log("Fehler beim öffnen des Moodle-Kurses", error);
} }
window.open(courseUrl, "_blank");
}} }}
> >
Zum Moodle Kurs Zum Moodle Kurs

View File

@@ -11,21 +11,12 @@ export default async function Page() {
const user = await prisma.user.findFirst({ const user = await prisma.user.findFirst({
where: { where: {
id: session.user.id, id: session.user.id,
Penaltys: {
some: {
until: {
gte: new Date(),
},
suspended: false,
},
},
}, },
include: { include: {
discordAccounts: true, discordAccounts: true,
Penaltys: true, Penaltys: true,
}, },
}); });
console.log("User", session, user);
const userPenaltys = await prisma.penalty.findMany({ const userPenaltys = await prisma.penalty.findMany({
where: { where: {
userId: session.user.id, userId: session.user.id,

View File

@@ -33,7 +33,7 @@ export const VerticalNav = async () => {
<li> <li>
<Link href="/logbook"> <Link href="/logbook">
<ReaderIcon /> <ReaderIcon />
Logbook Logbuch
</Link> </Link>
</li> </li>
<li> <li>

View File

@@ -5,6 +5,7 @@ import { DiscordAccount, prisma, User } from "@repo/db";
import bcrypt from "bcryptjs"; import bcrypt from "bcryptjs";
import oldUser from "./var.User.json"; import oldUser from "./var.User.json";
import { OldUser } from "../../../../types/oldUser"; import { OldUser } from "../../../../types/oldUser";
import { sendVerificationLink } from "(app)/admin/user/action";
export const options: AuthOptions = { export const options: AuthOptions = {
providers: [ providers: [
@@ -69,6 +70,7 @@ export const options: AuthOptions = {
}, },
}); });
} }
await sendVerificationLink(newUser.id);
return newUser; return newUser;
} }
} }

View File

@@ -25,6 +25,7 @@ export const GET = async (req: NextRequest) => {
if (!user) return NextResponse.json({ error: "User not found" }, { status: 404 }); if (!user) return NextResponse.json({ error: "User not found" }, { status: 404 });
setTimeout(async () => { setTimeout(async () => {
const moodleUser = await getMoodleUserById(user.id); const moodleUser = await getMoodleUserById(user.id);
await prisma.user.update({ await prisma.user.update({
where: { where: {
id: user.id, id: user.id,

View File

@@ -1,37 +1,44 @@
import axios from "axios"; import axios from "axios";
export const enrollUserInCourse = async ( export const enrollUserInCourse = async (courseid: number | string, userid: number | string) => {
courseid: number | string, try {
userid: number | string, const { data: enrollmentResponse } = await axios.get(
) => { `${process.env.NEXT_PUBLIC_MOODLE_URL}/webservice/rest/server.php`,
const { data: enrollmentResponse } = await axios.get( {
`${process.env.NEXT_PUBLIC_MOODLE_URL}/webservice/rest/server.php`, auth: {
{ username: "moodleuser",
params: { password: "Xo1SXaLYBa7Yb6WW",
wstoken: process.env.MOODLE_TOKEN, },
wsfunction: "enrol_manual_enrol_users", params: {
moodlewsrestformat: "json", wstoken: process.env.MOODLE_API_TOKEN,
enrolments: [ wsfunction: "enrol_manual_enrol_users",
{ moodlewsrestformat: "json",
roleid: 5, enrolments: [
userid: typeof userid === "string" ? parseInt(userid) : userid, {
courseid: roleid: 5,
typeof courseid === "string" ? parseInt(courseid) : courseid, userid: typeof userid === "string" ? parseInt(userid) : userid,
}, courseid: typeof courseid === "string" ? parseInt(courseid) : courseid,
], },
],
},
}, },
}, );
); return enrollmentResponse;
if (enrollmentResponse !== null) console.error(enrollmentResponse); } catch (error) {
return enrollmentResponse; return new Error("Failed to enroll user in course");
}
}; };
export const getMoodleUserById = async (id: string) => { export const getMoodleUserById = async (id: string) => {
const { data: user } = await axios.get( const { data: user } = await axios.get(
`${process.env.NEXT_PUBLIC_MOODLE_URL}/webservice/rest/server.php`, `${process.env.NEXT_PUBLIC_MOODLE_URL}/webservice/rest/server.php`,
{ {
auth: {
username: "moodleuser",
password: "Xo1SXaLYBa7Yb6WW",
},
params: { params: {
wstoken: process.env.MOODLE_TOKEN, wstoken: process.env.MOODLE_API_TOKEN,
wsfunction: "core_user_get_users_by_field", wsfunction: "core_user_get_users_by_field",
moodlewsrestformat: "json", moodlewsrestformat: "json",
field: "idnumber", field: "idnumber",
@@ -42,6 +49,7 @@ export const getMoodleUserById = async (id: string) => {
}, },
}, },
); );
console.log("Moodle User", user);
const u = user[0]; const u = user[0];
return ( return (
(u as { (u as {