import {collection, getDocs, limit, orderBy, query, QueryConstraint, where} from "firebase/firestore"; import {getDownloadURL, listAll, ref, StorageReference} from "firebase/storage"; import {SetStateAction} from "react"; import {BillingPackage, CohortData, InstituteData, NotificationObject, PlacementListing, ProviderData, UserData, UserGroupData} from "../typeDefinitions"; import {db, storage} from "./firebaseConfig"; import FirebaseQuery from "./firebaseQuery"; import {pathToArr, sortByReverseStringLength} from "./util"; const firebaseQuery = new FirebaseQuery(); export const getStaffRolesForStudent = async (studentFields:string[], product:string, userObject:UserData) => { const roleWithStudent = await firebaseQuery.getDocsWhere( ["staffRoles"], where("filters.students", "array-contains", userObject.id)); const roleWithFilters = await studentFields.reduce(async (acc, field) => { if (!userObject.details[field as keyof typeof userObject]) { return acc; } const roles = await firebaseQuery.getDocsWhere( ["staffRoles"], where(`filters.fields.${field}`, "==", userObject.details[field as keyof typeof userObject])) as {[key:string]: unknown}; if (Object.keys(roles).length > 0) { acc = {...acc, ...roles}; } return acc; }, Promise.resolve({})); return {...roleWithFilters, ...roleWithStudent}; }; export const searchUsers = async (userType:"Staff"|"Students", name:string, user:UserData, lim=5, constraints:QueryConstraint[]=[]) => { if (name.length < 3) { return {}; } // This means we search for the longest string first, thereby probably getting the fewest results. const sortedNameList = sortByReverseStringLength(name.split(" ")); const unfilteredUsersByForename = await firebaseQuery.getDocsWhere( "users", [where("userType", "==", userType), where("oId", "==", user.oId), where("product", "==", user.product), where("status", "==", "active"), ...constraints, where("details.forename", ">=", sortedNameList[0]), where("details.forename", "<=", sortedNameList[0]+ "\uf8ff"), limit(lim)]) as {[key:string]: UserData}; const unfilteredUsersBySurname = await firebaseQuery.getDocsWhere( "users", [where("userType", "==", userType), where("product", "==", user.product), where("oId", "==", user.oId), where("status", "==", "active"), ...constraints, where("details.surname", ">=", sortedNameList[0]), where("details.surname", "<=", sortedNameList[0]+ "\uf8ff"), limit(lim)]) as {[key:string]: UserData}; let userResults = {...unfilteredUsersByForename, ...unfilteredUsersBySurname}; sortedNameList.shift(); sortedNameList.forEach((name) => { userResults = Object.fromEntries(Object.entries(userResults).filter(([, user]) => user.details.forename.includes(name) || user.details.surname.includes(name))); }); return userResults; }; type OldUserData = { userGroup?: string, cohort?: string, } export const getUser = async (uid: string, setState: SetStateAction) => { let oldUser:OldUserData = {}; let groupData = {}; let cohortData = {}; let packageData = {}; console.log("Fetching user"); firebaseQuery.documentSnapshot(["users", uid], async (user: UserData) => { console.log("user", user); if (user.userGroup && user.userGroup !== "admin") { // Always set the groupData, but only get it if the user group has changed.keyof if (oldUser?.userGroup !== user.userGroup) { groupData = await firebaseQuery.getDocData(["userGroups", user.userGroup]); } } user.groupData = groupData as UserGroupData; // if (user.product === "providers") { // // Always set the groupData, but only get it if the user group has changed.keyof // if (oldUser?.userGroup !== user.userGroup) { // const provider = await firebaseQuery.getDocData(["providers", user.oId]) as ProviderData; // packageData = await firebaseQuery.getDocData(["billing", provider.package]) as BillingPackage; // } // } user.packageData = packageData as BillingPackage; if (user.cohort) { // Always set the groupData, but only get it if the user group has changed.keyof if (oldUser?.cohort !== user.cohort) { cohortData = await firebaseQuery.getDocData(["cohorts", user.cohort]); } } user.cohortData = cohortData as CohortData; oldUser = user; setState(user) }); }; export const getNotifications = async (user: UserData, setState: SetStateAction) => { const updateNotifications = (notifications:{[key:string]: NotificationObject}, type:"user"|"org") => { setState((old:{[key:string]: NotificationObject}) => { const concatenatedNotifications = { ...old, ...Object.fromEntries( Object.entries(notifications) .map(([k, v]) => [k, {...v, notifType: type}]) ), }; return Object.fromEntries(Object.entries(concatenatedNotifications).sort(([, a], [, b]) => b.created.toMillis() - a.created.toMillis())); }); }; type NotifQuery = [string[], QueryConstraint|QueryConstraint[]]; const userNotifications:NotifQuery = [["users", user.id, "notifications"], [orderBy("created", "desc"), limit(10)]]; /* , limit(10) */ firebaseQuery.getDocsWhere(userNotifications[0], userNotifications[1]).then((s) => { updateNotifications(s as {[key:string]: NotificationObject}, "user"); }); if (user.oId) { let notificationsQuery:NotifQuery; if (user.userGroup === "admin") { notificationsQuery = [[user.product, user.oId, "notifications"], [orderBy("created", "desc"), limit(10)]]; /* , limit(10) */ } else { notificationsQuery = [[user.product, user.oId, "notifications"], [where("viewableBy", "array-contains-any", [user.id, user.userGroup]), orderBy("created", "desc")]]; /* , limit(10) */ } firebaseQuery.getDocsWhere(notificationsQuery[0], notificationsQuery[1]).then((s) => { updateNotifications(s as {[key:string]: NotificationObject}, "org"); }); } // Only gets notifications that user can view, either by ID or usergroup. Once user not in group, can no longer see notifications. // getCollectionSnapshot(notificationsQuery[0], setState, notificationsQuery[1]) }; export const getFiles = async (path: string[]|string) => { const files = await listAll(ref(storage, ...pathToArr(path))); return (await ((Array.from(files.items) as Array).reduce(async (acc, item) => { const url = await getDownloadURL(item); const entry = {name: item.name, url: url}; if (!(await acc).includes(entry)) { (await acc).push(entry); } return acc; }, Promise.resolve([] as Array<{name: string, url: string}>)))); }; export const getFormsFromId = async (path: string[]|string, forms: string[]) => { return (await (Array.from(forms).reduce(async (acc: {[k: string]: any}, item: string) => { const formData = typeof item === "object" ? item : await firebaseQuery.getDocData([...pathToArr(path), item]); console.log("formData", formData); acc.then((acc: {[k: string]: any}) => { acc[item] = formData; }); return acc; }, Promise.resolve({})))); }; export const getUserById = async (id: string, setState?: SetStateAction, getGroupData=true) => { if (!setState) { const user = await firebaseQuery.getDocData(["users", id]).catch(() => false) as UserData|false; if (!user) return; if (getGroupData) { user.groupData = user.userGroup && user.userGroup !== "admin" ? await firebaseQuery.getDocData(["userGroups", user.userGroup]) as UserGroupData : undefined } return user; } firebaseQuery.documentSnapshot(["users", id], async (user: UserData) => { if (getGroupData) { user.groupData = user.userGroup && user.userGroup !== "admin" ? await firebaseQuery.getDocData(["userGroups", user.userGroup]) as UserGroupData : undefined } setState(user); }); return; }; /* export const getUsersByType = async (q: Query, setState:SetStateAction, uid:string, lim:number, limitType?:"start"|"end") => { if (limitType === "end") { q = query(q, endBefore(uid), limitToLast(lim)); } else { q = query(q, startAfter(uid), limit(lim)); } return firebaseQuery.collectionSnapshot(q, setState); };*/ export const getPlacementbyId = async (pid: string, setState:SetStateAction) => { return firebaseQuery.documentSnapshot(["placements", pid], setState); }; export const getOrganisation = async (user: UserData, setOrg: (data: InstituteData|ProviderData) => void) => { return firebaseQuery.documentSnapshot([user.product, user.oId], setOrg); }; export const getPlacementsWhere = async ({w=[], oId, uid, raw=false}:{w:QueryConstraint[]|QueryConstraint, oId?:string, uid?:string, raw?:boolean}) => { return await firebaseQuery.getDocsWhere("placements", [...(oId ? [where("oId", "==", oId)] : []), ...(uid ? [where("uid", "==", uid)] : []), ...pathToArr(w)], raw); }; export const checkPlacementConflicts = async (instituteId:string, userGroupId:string) => { const q = query(collection(db, "placements"), where("oId", "==", instituteId), where("userGroup", "==", userGroupId)); return (await getDocs(q)).size; }; export const getPlacementListingsById = async (placementListings: string[]) => { const placements = await Promise.all(placementListings.map(async (listingId) => { return [listingId, await firebaseQuery.getDocData(["placementListings", listingId]).catch(() => false) as PlacementListing]; })) as [string, PlacementListing|false][]; return placements.filter(([_, v]) => v !== false) as [string, PlacementListing][]; }