import {collection, getDocs, query, where, setDoc, doc, Firestore, getDoc, and} from "firebase/firestore"; import {shuffle} from "lodash"; import {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"; export const getExams = async ( db: Firestore, 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 moduleRef = collection(db, module); const q = query(moduleRef, and(where("isDiagnostic", "==", false), where("private", "!=", true))); const snapshot = await getDocs(q); const allExams = shuffle( snapshot.docs.map((doc) => ({ id: doc.id, ...doc.data(), module, })), ) as Exam[]; let exams: Exam[] = await filterByOwners(allExams, 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 statsQ = query(collection(db, "stats"), where("user", "==", userId)); const statsSnapshot = await getDocs(statsQ); const stats: Stat[] = statsSnapshot.docs.map((doc) => ({ id: doc.id, ...doc.data(), })) as unknown as Stat[]; 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 filterByOwners = async (exams: Exam[], userID?: string) => { if (!userID) return exams.filter((x) => !x.owners || x.owners.length === 0); return Promise.all( exams.filter(async (x) => { if (!x.owners) return true; if (x.owners.length === 0) return true; if (x.owners.includes(userID)) return true; const corporate = await getUserCorporate(userID); return !corporate ? false : x.owners.includes(corporate.id); }), ); }; const filterByDifficulty = async (db: Firestore, exams: Exam[], module: Module, userID?: string) => { if (!userID) return exams; const userRef = await getDoc(doc(db, "users", userID)); if (!userRef.exists()) return exams; const user = {...userRef.data(), id: userRef.id} as User; const difficulty = user.levels[module] <= 3 ? "easy" : user.levels[module] <= 6 ? "medium" : "hard"; const filteredExams = exams.filter((exam) => exam.difficulty === difficulty); return filteredExams.length === 0 ? exams : filteredExams; }; const filterByPreference = async (db: Firestore, exams: Exam[], module: Module, userID?: string) => { if (!["speaking", "writing"].includes(module)) return exams; if (!userID) return exams; const userRef = await getDoc(doc(db, "users", userID)); if (!userRef.exists()) return exams; const user = {...userRef.data(), id: userRef.id} as StudentUser | DeveloperUser; 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; };