diff --git a/.gitignore b/.gitignore index 0e3eb7ed..34a89ac2 100644 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,7 @@ node_modules .pnp.js migrations +mkcert # Local env files .env diff --git a/README.md b/README.md index 2c6898cb..47f603fb 100644 --- a/README.md +++ b/README.md @@ -99,3 +99,5 @@ LocalMachine RemoteSigned 4. http://localhost:8081/admin/category.php?category=authsettings -> Guest login button -> Hide 5. http://localhost:8081/admin/settings.php?section=sitepolicies -> emailchangeconfirmation -> False 6. Beim anlegen des Auth-Services Require Email verification deaktivieren +7. Beim erstellen der Role für API user: permission moodle/site:viewuseridentity geben +8. API user zu moodle kursen hinzufügen, um andere Nutzer hinzuzufügen diff --git a/apps/hub-server/.env.example b/apps/hub-server/.env.example new file mode 100644 index 00000000..67a41c4e --- /dev/null +++ b/apps/hub-server/.env.example @@ -0,0 +1,2 @@ +MOODLE_TOKEN= +MOODLE_URL= \ No newline at end of file diff --git a/apps/hub-server/helper/event.ts b/apps/hub-server/helper/event.ts new file mode 100644 index 00000000..11537fd7 --- /dev/null +++ b/apps/hub-server/helper/event.ts @@ -0,0 +1,11 @@ +import { Event, Participant } from "@repo/db"; + +export const participantCompleted = ( + event: Event, + participant: Participant, +) => { + if (event.finisherMoodleCourseId && !participant.finisherMoodleCurseCompleted) + return false; + if (event.hasPresenceEvents && !participant.attended) return false; + return true; +}; diff --git a/apps/hub-server/index.ts b/apps/hub-server/index.ts index 75efaea0..7cc534dc 100644 --- a/apps/hub-server/index.ts +++ b/apps/hub-server/index.ts @@ -1,2 +1,5 @@ +import "dotenv/config"; import "modules/chron"; + +// Add API eventually console.log("VAR hub Server started"); diff --git a/apps/hub-server/modules/chron.ts b/apps/hub-server/modules/chron.ts index afbbbc6b..adeabdff 100644 --- a/apps/hub-server/modules/chron.ts +++ b/apps/hub-server/modules/chron.ts @@ -1,6 +1,7 @@ import { getMoodleCourseCompletionStatus, getMoodleUserById } from "./moodle"; import { CronJob } from "cron"; import { prisma } from "@repo/db"; +import { participantCompleted } from "helper/event"; const syncMoodleIds = async () => { try { @@ -30,71 +31,84 @@ const syncMoodleIds = async () => { } }; -const updateParticipantMoodleResults = - (usage: "starter" | "finisher") => async () => { - const participantsMoodlePending = await prisma.participant.findMany({ - where: { - [usage === "starter" - ? "starterMoodleCourseCompleted" - : "finisherMoodleCourseCompleted"]: false, - Event: { - [usage === "starter" - ? "starterMoodleCourseId" - : "finisherMoodleCourseId"]: { - not: null, - }, +const updateParticipantMoodleResults = async () => { + const participantsMoodlePending = await prisma.participant.findMany({ + where: { + finisherMoodleCurseCompleted: false, + Event: { + finisherMoodleCourseId: { + not: null, }, }, - select: { - id: true, - Event: true, - User: true, - }, - }); + }, + include: { + Event: true, + User: true, + }, + }); + await Promise.all( + participantsMoodlePending.map(async (p) => { + if (!p.User) return; + if (!p.User.moodleId) return; - await Promise.all( - participantsMoodlePending.map(async (p) => { - if (!p.User) return; - if (!p.User.moodleId) return; + const quizzResult = await getMoodleCourseCompletionStatus( + p.User.moodleId.toString(), + p.Event.finisherMoodleCourseId!, + ); - const quizzResult = await getMoodleCourseCompletionStatus( - p.User.moodleId.toString(), - p.Event[ - usage === "starter" - ? "starterMoodleCourseId" - : "finisherMoodleCourseId" - ]!, - ); - p.Event.finisherMoodleCourseId; - if (quizzResult?.completionstatus?.completed === true) { + if (quizzResult?.completionstatus?.completed === true) { + await prisma.participant.update({ + where: { + id: p.id, + }, + data: { + finisherMoodleCurseCompleted: true, + statusLog: { + push: { + event: "Finisher course completed", + timestamp: new Date(), + user: "system", + }, + }, + }, + }); + if (participantCompleted(p.Event, p)) { + // Event is completed, give relating permissions + await prisma.user.update({ + where: { + id: p.userId, + }, + data: { + permissions: { + push: p.Event.finishedPermissions, + }, + badges: { + push: p.Event.finishedBadges, + }, + }, + }); await prisma.participant.update({ where: { id: p.id, }, data: { - [usage === "starter" - ? "starterMoodleCurseCompleted" - : "finisherMoodleCurseCompleted"]: true, - statusLog: { - push: { - event: "Starter course completed", - timestamp: new Date(), - user: "system", - }, - }, + finished: true, }, }); } - }), - ); - }; + } + }), + ); +}; CronJob.from({ cronTime: "0 * * * *", onTick: syncMoodleIds, start: true }); CronJob.from({ cronTime: "*/5 * * * *", onTick: async () => { - await updateParticipantMoodleResults("starter"); - await updateParticipantMoodleResults("finisher"); + console.log("Updating participant moodle results"); + await updateParticipantMoodleResults(); }, start: true, }); + +updateParticipantMoodleResults(); diff --git a/apps/hub-server/modules/moodle.ts b/apps/hub-server/modules/moodle.ts index c89aa4aa..fd3f733e 100644 --- a/apps/hub-server/modules/moodle.ts +++ b/apps/hub-server/modules/moodle.ts @@ -2,7 +2,7 @@ import axios from "axios"; export const getMoodleUserById = async (id: string) => { const { data: user } = await axios.get( - "https://moodle.virtualairrescue.com/webservice/rest/server.php", + `${process.env.MOODLE_URL}/webservice/rest/server.php`, { params: { wstoken: process.env.MOODLE_TOKEN, @@ -31,7 +31,7 @@ export const getMoodleUserById = async (id: string) => { export const getMoodleQuizResult = async (userId: string, quizId: string) => { const { data: quizzes } = await axios.get( - "https://moodle.virtualairrescue.com/webservice/rest/server.php", + `${process.env.MOODLE_URL}/webservice/rest/server.php`, { params: { wstoken: process.env.MOODLE_TOKEN, @@ -50,7 +50,7 @@ export const getMoodleCourseCompletionStatus = async ( courseId: string, ) => { const { data: completionStatus } = await axios.get( - "https://moodle.virtualairrescue.com/webservice/rest/server.php", + `${process.env.MOODLE_URL}/webservice/rest/server.php`, { params: { wstoken: process.env.MOODLE_TOKEN, @@ -61,29 +61,11 @@ export const getMoodleCourseCompletionStatus = async ( }, }, ); - return completionStatus; -}; - -export const enrollUserInCourse = async ( - courseid: number | string, - userid: number | string, -) => { - const { data: enrollmentResponse } = await axios.get( - "https://moodle.virtualairrescue.com/webservice/rest/server.php", - { - params: { - wstoken: process.env.MOODLE_TOKEN, - wsfunction: "enrol_manual_enrol_users", - moodlewsrestformat: "json", - enrolments: [ - { - roleid: 5, - userid, - courseid, - }, - ], - }, - }, - ); - return enrollmentResponse; + return completionStatus as { + completionstatus: { + completed: true; + aggregation: number; + }; + warnings: []; + }; }; diff --git a/apps/hub-server/package.json b/apps/hub-server/package.json index ce1401c7..d51f30fa 100644 --- a/apps/hub-server/package.json +++ b/apps/hub-server/package.json @@ -1,11 +1,15 @@ { "name": "hub-server", + "exports": { + "helpers": "./helper" + }, "scripts": { "dev": "nodemon", "build": "tsc" }, "devDependencies": { "@repo/db": "*", + "@repo/hub": "*", "@repo/typescript-config": "*", "@types/node": "^22.13.5", "concurrently": "^9.1.2", @@ -13,6 +17,7 @@ }, "dependencies": { "axios": "^1.7.9", - "cron": "^4.1.0" + "cron": "^4.1.0", + "dotenv": "^16.4.7" } } diff --git a/apps/hub/.env.example b/apps/hub/.env.example index ae7d750e..c66c3a3d 100644 --- a/apps/hub/.env.example +++ b/apps/hub/.env.example @@ -5,4 +5,6 @@ DISCORD_OAUTH_CLIENT_ID= DISCORD_OAUTH_SECRET= DISCORD_BOT_TOKEN= NEXT_PUBLIC_DISCORD_URL= -DISCORD_REDIRECT= \ No newline at end of file +DISCORD_REDIRECT= +MOODLE_TOKEN= +NEXT_PUBLIC_MOODLE_URL= \ No newline at end of file diff --git a/apps/hub/app/(app)/admin/event/_components/Form.tsx b/apps/hub/app/(app)/admin/event/_components/Form.tsx index e44eb1d6..59c97def 100644 --- a/apps/hub/app/(app)/admin/event/_components/Form.tsx +++ b/apps/hub/app/(app)/admin/event/_components/Form.tsx @@ -8,7 +8,7 @@ import { ParticipantOptionalDefaultsSchema, } from "@repo/db/zod"; import { set, useForm } from "react-hook-form"; -import { BADGES, Event, EventAppointment, User } from "@repo/db"; +import { BADGES, Event, EVENT_TYPE, PERMISSION } from "@repo/db"; import { Bot, Calendar, FileText, UserIcon } from "lucide-react"; import { Input } from "../../../../_components/ui/Input"; import { useRef, useState } from "react"; @@ -60,7 +60,6 @@ export const Form = ({ event }: { event?: Event }) => { eventId: event?.id, presenterId: values.presenterId, }); - console.log(createdAppointment); addParticipantModal.current?.close(); appointmentsTableRef.current?.refresh(); })} @@ -83,6 +82,7 @@ export const Form = ({ event }: { event?: Event }) => {