import { collection, getDocs, query, where, setDoc, doc, Firestore, getDoc, and } from "firebase/firestore"; import { groupBy, shuffle } from "lodash"; import { CEFRLevels, Difficulty, Exam, InstructorGender, SpeakingExam, Variant, WritingExam } from "@/interfaces/exam"; import { DeveloperUser, Stat, StudentUser, User } from "@/interfaces/user"; import { Module } from "@/interfaces"; import { getCorporateUser } from "@/resources/user"; import { getUserCorporate } from "./groups.be"; import { Db, ObjectId } from "mongodb"; import client from "@/lib/mongodb"; import { MODULE_ARRAY } from "./moduleUtils"; import { mapBy } from "."; import { getUser } from "./users.be"; const db = client.db(process.env.MONGODB_DB); export async function getSpecificExams(ids: string[]) { if (ids.length === 0) return []; const exams: Exam[] = ( await Promise.all( MODULE_ARRAY.flatMap( async (module) => await db .collection(module) .find({ id: { $in: ids } }) .toArray(), ), ) ).flat(); return exams; } export const getExam = async (module: Module, id: string) => { return await db.collection(module).findOne({ id }) ?? undefined }; export const getExamsByIds = async (ids: { module: Module; id: string }[]) => { const groupedByModule = groupBy(ids, "module"); const exams: Exam[] = ( await Promise.all( Object.keys(groupedByModule).map( async (m) => await db .collection(m) .find({ id: { $in: mapBy(groupedByModule[m], 'id') } }) .toArray(), ), ) ).flat(); return exams; }; export const getExams = async ( db: Db, module: Module, avoidRepeated: string, // added userId as due to assignments being set from the teacher to the student // we need to make sure we are serving exams not executed by the user and not // by the teacher that performed the request userId: string | undefined, variant?: Variant, instructorGender?: InstructorGender, ): Promise => { const allExams = await db .collection(module) .find({ isDiagnostic: false, }) .toArray(); const shuffledPublicExams = shuffle( allExams.map((doc) => ({ ...doc, module, })) as Exam[], ).filter((x) => !x.private); let exams: Exam[] = await filterByEntities(shuffledPublicExams, userId); exams = filterByVariant(exams, variant); exams = filterByInstructorGender(exams, instructorGender); exams = await filterByDifficulty(db, exams, module, userId); exams = await filterByPreference(db, exams, module, userId); if (avoidRepeated === "true") { const stats = await db .collection("stats") .find({ user: userId, }) .toArray(); const filteredExams = exams.filter((x) => !stats.map((s) => s.exam).includes(x.id)); return filteredExams.length > 0 ? filteredExams : exams; } return exams; }; const filterByInstructorGender = (exams: Exam[], instructorGender?: InstructorGender) => { if (!instructorGender || instructorGender === "varied") return exams; return exams.filter((e) => (e.module === "speaking" ? e.instructorGender === instructorGender : true)); }; const filterByVariant = (exams: Exam[], variant?: Variant) => { const filtered = variant && variant === "partial" ? exams.filter((x) => x.variant === "partial") : exams.filter((x) => x.variant !== "partial"); return filtered.length > 0 ? filtered : exams; }; const filterByEntities = async (exams: Exam[], userID?: string) => { if (!userID) return exams.filter((x) => !x.entities || x.entities.length === 0); const user = await getUser(userID) return await Promise.all( exams.filter(async (x) => { if (!x.entities) return true; if (x.entities.length === 0) return true; return mapBy(user?.entities || [], 'id').some(e => x.entities!.includes(e)) }), ); }; const filterByDifficulty = async (db: Db, exams: Exam[], module: Module, userID?: string) => { if (!userID) return exams; const user = await db.collection("users").findOne({ id: userID }); if (!user) return exams; const basicDifficulty = user.levels[module] <= 3 ? "easy" : user.levels[module] <= 6 ? "medium" : "hard"; let CEFRLevel: CEFRLevels; // Adjust the levels if necessary switch (user.levels[module]) { case 1: case 2: CEFRLevel = "A1"; break; case 3: case 4: CEFRLevel = "A2"; break; case 4: case 5: CEFRLevel = "B1"; break; case 6: CEFRLevel = "B2"; break; case 7: case 8: CEFRLevel = "C1"; break; case 9: CEFRLevel = "C2"; break; default: CEFRLevel = "B1"; } const filteredExams = exams.filter((exam) => exam.difficulty === basicDifficulty || exam.difficulty === CEFRLevel ); return filteredExams.length === 0 ? exams : filteredExams; }; const filterByPreference = async (db: Db, exams: Exam[], module: Module, userID?: string) => { if (!["speaking", "writing"].includes(module)) return exams; if (!userID) return exams; const user = await db.collection("users").findOne({ id: userID }); if (!user) return exams; if (!["developer", "student"].includes(user.type)) return exams; if (!user.preferredTopics || user.preferredTopics.length === 0) return exams; const userTopics = user.preferredTopics; const topicalExams = exams.filter((e) => { const exam = e as WritingExam | SpeakingExam; const topics = exam.exercises.map((x) => x.topic).filter((x) => !!x) as string[]; return topics.some((topic) => userTopics.map((x) => x.toLowerCase()).includes(topic.toLowerCase())); }); return topicalExams.length > 0 ? shuffle(topicalExams) : exams; };