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
MOODLE_TOKEN=ac346f0324647b68488d13fd52a9bbe8
MOODLE_API_TOKEN=ac346f0324647b68488d13fd52a9bbe8
NEXT_PUBLIC_HUB_URL=http://localhost:3000
MOODLE_URL=http://localhost:8081
NEXT_PUBLIC_MOODLE_URL=
MAIL_SERVER="asmtp.mail.hostpoint.ch"
MAIL_USER="noreply@virtualairrescue.com"
MAIL_PASSWORD="b7316PB8aDPCC%-&"

View File

@@ -4,8 +4,12 @@ export const getMoodleUserById = async (id: string) => {
const { data: user } = await axios.get(
`${process.env.NEXT_PUBLIC_MOODLE_URL}/webservice/rest/server.php`,
{
auth: {
username: "moodleuser",
password: "Xo1SXaLYBa7Yb6WW",
},
params: {
wstoken: process.env.MOODLE_TOKEN,
wstoken: process.env.MOODLE_API_TOKEN,
wsfunction: "core_user_get_users_by_field",
moodlewsrestformat: "json",
field: "idnumber",
@@ -33,8 +37,12 @@ export const getMoodleQuizResult = async (userId: string, quizId: string) => {
const { data: quizzes } = await axios.get(
`${process.env.NEXT_PUBLIC_MOODLE_URL}/webservice/rest/server.php`,
{
auth: {
username: "moodleuser",
password: "Xo1SXaLYBa7Yb6WW",
},
params: {
wstoken: process.env.MOODLE_TOKEN,
wstoken: process.env.MOODLE_API_TOKEN,
wsfunction: "mod_quiz_get_user_attempts",
moodlewsrestformat: "json",
quizid: quizId,
@@ -49,8 +57,12 @@ export const getMoodleCourseCompletionStatus = async (userId: string, courseId:
const { data: completionStatus } = await axios.get(
`${process.env.NEXT_PUBLIC_MOODLE_URL}/webservice/rest/server.php`,
{
auth: {
username: "moodleuser",
password: "Xo1SXaLYBa7Yb6WW",
},
params: {
wstoken: process.env.MOODLE_TOKEN,
wstoken: process.env.MOODLE_API_TOKEN,
wsfunction: "core_completion_get_course_completion_status",
moodlewsrestformat: "json",
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) {
res.status(404).json({ error: "Participant not found" });
return;
@@ -62,11 +61,16 @@ router.post("/check-moodle-results", async (req, res) => {
res.status(400).json({ error: "Teilnehmer hat keine Moodle-ID" });
return;
}
const quizzResult = await getMoodleCourseCompletionStatus(
const courseStatus = await getMoodleCourseCompletionStatus(
participant.User.moodleId.toString(),
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);
res
.status(200)

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,5 +1,5 @@
"use client";
import { useEffect } from "react";
import { use, useEffect } from "react";
import { CheckCircledIcon, CalendarIcon, EnterIcon } from "@radix-ui/react-icons";
import { Event, EventAppointment, Participant, User } from "@repo/db";
import { cn } from "../../../../helper/cn";
@@ -87,6 +87,10 @@ const ModalBtn = ({
? (selectedDate as any)?.Participants?.length + 1
: ownIndexInParticipantList + 1;
const missingRequirements =
event.requiredBadges?.length > 0 &&
!event.requiredBadges.some((badge) => user.badges.includes(badge));
return (
<>
<button
@@ -96,14 +100,20 @@ const ModalBtn = ({
eventCompleted(event, participant) && "btn-success",
)}
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
</>
)}
{!participant && (
{!participant && !missingRequirements && (
<>
<EnterIcon /> Anmelden
</>
@@ -287,16 +297,20 @@ const MoodleCourseIndicator = ({
<button
className="btn btn-xs btn-info ml-2"
onClick={async () => {
await upsertParticipant({
eventId: event.id,
userId: user.id,
finisherMoodleCurseCompleted: false,
});
try {
await upsertParticipant({
eventId: event.id,
userId: user.id,
finisherMoodleCurseCompleted: false,
});
if (user.moodleId) {
await inscribeToMoodleCourse(moodleCourseId, user.moodleId);
if (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

View File

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

View File

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

View File

@@ -5,6 +5,7 @@ import { DiscordAccount, prisma, User } from "@repo/db";
import bcrypt from "bcryptjs";
import oldUser from "./var.User.json";
import { OldUser } from "../../../../types/oldUser";
import { sendVerificationLink } from "(app)/admin/user/action";
export const options: AuthOptions = {
providers: [
@@ -69,6 +70,7 @@ export const options: AuthOptions = {
},
});
}
await sendVerificationLink(newUser.id);
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 });
setTimeout(async () => {
const moodleUser = await getMoodleUserById(user.id);
await prisma.user.update({
where: {
id: user.id,

View File

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