diff --git a/src/pages/api/grading/index.ts b/src/pages/api/grading/index.ts index bc829e00..8b84f55a 100644 --- a/src/pages/api/grading/index.ts +++ b/src/pages/api/grading/index.ts @@ -15,7 +15,7 @@ import {Grading} from "@/interfaces"; import {getGroupsForUser} from "@/utils/groups.be"; import {uniq} from "lodash"; import {getUser} from "@/utils/users.be"; -import { getGradingSystem } from "@/utils/grading.be"; +import {getGradingSystem} from "@/utils/grading.be"; const db = getFirestore(app); @@ -56,7 +56,7 @@ async function post(req: NextApiRequest, res: NextApiResponse) { const participants = uniq(groups.flatMap((x) => x.participants)); const participantUsers = await Promise.all(participants.map(getUser)); - const corporateUsers = participantUsers.filter((x) => x.type === "corporate") as CorporateUser[]; + const corporateUsers = participantUsers.filter((x) => x?.type === "corporate") as CorporateUser[]; await Promise.all(corporateUsers.map(async (g) => await setDoc(doc(db, "grading", g.id), body))); } diff --git a/src/pages/api/make_user.ts b/src/pages/api/make_user.ts index a1956f74..025864d2 100644 --- a/src/pages/api/make_user.ts +++ b/src/pages/api/make_user.ts @@ -9,7 +9,7 @@ import {createUserWithEmailAndPassword, getAuth} from "firebase/auth"; import ShortUniqueId from "short-unique-id"; import {getUserCorporate, getUserGroups} from "@/utils/groups.be"; import {uniq} from "lodash"; -import {getUser} from "@/utils/users.be"; +import {getSpecificUsers, getUser} from "@/utils/users.be"; const DEFAULT_DESIRED_LEVELS = { reading: 9, @@ -32,9 +32,10 @@ export default withIronSessionApiRoute(handler, sessionOptions); const getUsersOfType = async (admin: string, type: Type) => { const groups = await getUserGroups(admin); - const users = await Promise.all(uniq(groups.flatMap((x) => x.participants)).map(getUser)); + const participants = groups.flatMap((x) => x.participants); + const users = await getSpecificUsers(participants); - return users.filter((x) => x.type === type).map((x) => x.id); + return users.filter((x) => x?.type === type).map((x) => x?.id); }; async function handler(req: NextApiRequest, res: NextApiResponse) { diff --git a/src/pages/api/register.ts b/src/pages/api/register.ts index 3dbee577..bc8e3ec2 100644 --- a/src/pages/api/register.ts +++ b/src/pages/api/register.ts @@ -4,13 +4,14 @@ import {app} from "@/firebase"; import {sessionOptions} from "@/lib/session"; import {withIronSessionApiRoute} from "iron-session/next"; import {getFirestore, doc, setDoc, query, collection, where, getDocs} from "firebase/firestore"; -import {CorporateInformation, DemographicInformation, Group, Type} from "@/interfaces/user"; +import {Code, CorporateInformation, DemographicInformation, Group, Type} from "@/interfaces/user"; import {addUserToGroupOnCreation} from "@/utils/registration"; import moment from "moment"; import {v4} from "uuid"; +import client from "@/lib/mongodb"; const auth = getAuth(app); -const db = getFirestore(app); +const db = client.db(process.env.MONGODB_DB); export default withIronSessionApiRoute(register, sessionOptions); @@ -45,24 +46,13 @@ async function registerIndividual(req: NextApiRequest, res: NextApiResponse) { code?: string; }; - const codeQuery = query(collection(db, "codes"), where("code", "==", code)); - const codeDocs = (await getDocs(codeQuery)).docs.filter((x) => !Object.keys(x.data()).includes("userId")); + const codeDoc = await db.collection("codes").findOne({code}); - if (code && code.length > 0 && codeDocs.length === 0) { + if (code && code.length > 0 && !!codeDoc) { res.status(400).json({error: "Invalid Code!"}); return; } - const codeData = - codeDocs.length > 0 - ? (codeDocs[0].data() as { - code: string; - type: Type; - creator?: string; - expiryDate: Date | null; - }) - : undefined; - createUserWithEmailAndPassword(auth, email.toLowerCase(), password) .then(async (userCredentials) => { const userId = userCredentials.user.uid; @@ -70,31 +60,32 @@ async function registerIndividual(req: NextApiRequest, res: NextApiResponse) { const user = { ...req.body, + id: userId, email: email.toLowerCase(), desiredLevels: DEFAULT_DESIRED_LEVELS, levels: DEFAULT_LEVELS, bio: "", - isFirstLogin: codeData ? codeData.type === "student" : true, + isFirstLogin: codeDoc ? codeDoc.type === "student" : true, profilePicture: "/defaultAvatar.png", focus: "academic", - type: email.endsWith("@ecrop.dev") ? "developer" : codeData ? codeData.type : "student", - subscriptionExpirationDate: codeData ? codeData.expiryDate : moment().subtract(1, "days").toISOString(), + type: email.endsWith("@ecrop.dev") ? "developer" : codeDoc ? codeDoc.type : "student", + subscriptionExpirationDate: codeDoc ? codeDoc.expiryDate : moment().subtract(1, "days").toISOString(), ...(passport_id ? {demographicInformation: {passport_id}} : {}), registrationDate: new Date().toISOString(), status: code ? "active" : "paymentDue", }; - await setDoc(doc(db, "users", userId), user); + await db.collection("users").insertOne(user); - if (codeDocs.length > 0 && codeData) { - await setDoc(codeDocs[0].ref, {userId: userId}, {merge: true}); - if (codeData.creator) await addUserToGroupOnCreation(userId, codeData.type, codeData.creator); + if (!!codeDoc) { + await db.collection("codes").updateOne({code: codeDoc.code}, {$set: {userId}}); + if (codeDoc.creator) await addUserToGroupOnCreation(userId, codeDoc.type, codeDoc.creator); } - req.session.user = {...user, id: userId}; + req.session.user = user; await req.session.save(); - res.status(200).json({user: {...user, id: userId}}); + res.status(200).json({user}); }) .catch((error) => { console.log(error); @@ -116,6 +107,7 @@ async function registerCorporate(req: NextApiRequest, res: NextApiResponse) { const user = { ...req.body, + id: userId, email: email.toLowerCase(), desiredLevels: DEFAULT_DESIRED_LEVELS, levels: DEFAULT_LEVELS, @@ -152,15 +144,13 @@ async function registerCorporate(req: NextApiRequest, res: NextApiResponse) { disableEditing: true, }; - await setDoc(doc(db, "users", userId), user); - await setDoc(doc(db, "groups", defaultTeachersGroup.id), defaultTeachersGroup); - await setDoc(doc(db, "groups", defaultStudentsGroup.id), defaultStudentsGroup); - await setDoc(doc(db, "groups", defaultCorporateGroup.id), defaultCorporateGroup); + await db.collection("users").insertOne(user); + await db.collection("groups").insertMany([defaultCorporateGroup, defaultStudentsGroup, defaultTeachersGroup]); - req.session.user = {...user, id: userId}; + req.session.user = user; await req.session.save(); - res.status(200).json({user: {...user, id: userId}}); + res.status(200).json({user}); }) .catch((error) => { console.log(error); diff --git a/src/utils/groups.be.ts b/src/utils/groups.be.ts index 269eded7..9d649897 100644 --- a/src/utils/groups.be.ts +++ b/src/utils/groups.be.ts @@ -1,75 +1,73 @@ import {app} from "@/firebase"; -import {CorporateUser, Group, MasterCorporateUser, StudentUser, TeacherUser} from "@/interfaces/user"; +import {CorporateUser, Group, MasterCorporateUser, StudentUser, TeacherUser, User} from "@/interfaces/user"; +import client from "@/lib/mongodb"; import {collection, doc, getDoc, getDocs, getFirestore, query, setDoc, where} from "firebase/firestore"; import moment from "moment"; import {getUser} from "./users.be"; import {getSpecificUsers} from "./users.be"; -const db = getFirestore(app); + +const db = client.db(process.env.MONGODB_DB); export const updateExpiryDateOnGroup = async (participantID: string, corporateID: string) => { - const corporateRef = await getDoc(doc(db, "users", corporateID)); - const participantRef = await getDoc(doc(db, "users", participantID)); - - if (!corporateRef.exists() || !participantRef.exists()) return; - - const corporate = { - ...corporateRef.data(), - id: corporateRef.id, - } as CorporateUser; - const participant = {...participantRef.data(), id: participantRef.id} as StudentUser | TeacherUser; + const corporate = await db.collection("users").findOne({id: corporateID}); + const participant = await db.collection("users").findOne({id: participantID}); + if (!corporate || !participant) return; if (corporate.type !== "corporate" || (participant.type !== "student" && participant.type !== "teacher")) return; - if (!corporate.subscriptionExpirationDate || !participant.subscriptionExpirationDate) { - return await setDoc(doc(db, "users", participant.id), {subscriptionExpirationDate: null}, {merge: true}); - } + if (!corporate.subscriptionExpirationDate || !participant.subscriptionExpirationDate) + return await db.collection("users").updateOne({id: participant.id}, {$set: {subscriptionExpirationDate: null}}); const corporateDate = moment(corporate.subscriptionExpirationDate); const participantDate = moment(participant.subscriptionExpirationDate); if (corporateDate.isAfter(participantDate)) - return await setDoc(doc(db, "users", participant.id), {subscriptionExpirationDate: corporateDate.toISOString()}, {merge: true}); + return await db.collection("users").updateOne({id: participant.id}, {$set: {subscriptionExpirationDate: corporateDate.toISOString()}}); return; }; export const getUserCorporate = async (id: string) => { const user = await getUser(id); + if (!user) return undefined; + if (["admin", "developer"].includes(user.type)) return undefined; if (user.type === "mastercorporate") return user; const groups = await getParticipantGroups(id); const admins = await Promise.all(groups.map((x) => x.admin).map(getUser)); - const corporates = admins.filter((x) => (user.type === "corporate" ? x.type === "mastercorporate" : x.type === "corporate")); + const corporates = admins + .filter((x) => (user.type === "corporate" ? x?.type === "mastercorporate" : x?.type === "corporate")) + .filter((x) => !!x) as User[]; if (corporates.length === 0) return undefined; return corporates.shift() as CorporateUser | MasterCorporateUser; }; export const getGroups = async () => { - const groupDocs = await getDocs(collection(db, "groups")); - return groupDocs.docs.map((x) => ({...x.data(), id: x.id})) as Group[]; + return await db.collection("groups").find({}).toArray(); }; export const getParticipantGroups = async (id: string) => { - const snapshot = await getDocs(query(collection(db, "groups"), where("participants", "array-contains", id))); - - const groups = snapshot.docs.map((doc) => ({ - id: doc.id, - ...doc.data(), - })) as Group[]; - - return groups; + return await db.collection("groups").find({participants: id}).toArray(); }; export const getUserGroups = async (id: string): Promise => { - const groupDocs = await getDocs(query(collection(db, "groups"), where("admin", "==", id))); - return groupDocs.docs.map((x) => ({...x.data(), id})) as Group[]; + return await db.collection("groups").find({admin: id}).toArray(); +}; + +export const getUsersGroups = async (ids: string[]) => { + return await db + .collection("groups") + .find({admin: {$in: ids}}) + .toArray(); }; export const getAllAssignersByCorporate = async (corporateID: string): Promise => { const groups = await getUserGroups(corporateID); - const groupUsers = (await Promise.all(groups.map(async (g) => await Promise.all(g.participants.map(getUser))))).flat(); + const groupUsers = (await Promise.all(groups.map(async (g) => await Promise.all(g.participants.map(getUser))))) + .flat() + .filter((x) => !!x) as User[]; const teacherPromises = await Promise.all( groupUsers.map(async (u) => u.type === "teacher" ? u.id : u.type === "corporate" ? [...(await getAllAssignersByCorporate(u.id)), u.id] : undefined, @@ -80,42 +78,19 @@ export const getAllAssignersByCorporate = async (corporateID: string): Promise { - try { - const queryConstraints = [ - ...(admin ? [where("admin", "==", admin)] : []), - ...(participant ? [where("participants", "array-contains", participant)] : []), - ]; - const snapshot = await getDocs(queryConstraints.length > 0 ? query(collection(db, "groups"), ...queryConstraints) : collection(db, "groups")); - const groups = snapshot.docs.map((doc) => ({ - id: doc.id, - ...doc.data(), - })) as Group[]; + if (admin && participant) return await db.collection("groups").find({admin, participant}).toArray(); - return groups; - } catch (e) { - console.error(e); - return []; - } + if (admin) return await getUserGroups(admin); + if (participant) return await getParticipantGroups(participant); + + return await getGroups(); }; export const getStudentGroupsForUsersWithoutAdmin = async (admin: string, participants: string[]) => { - try { - const queryConstraints = [ - ...(admin ? [where("admin", "!=", admin)] : []), - ...(participants ? [where("participants", "array-contains-any", participants)] : []), - where("name", "==", "Students"), - ]; - const snapshot = await getDocs(queryConstraints.length > 0 ? query(collection(db, "groups"), ...queryConstraints) : collection(db, "groups")); - const groups = snapshot.docs.map((doc) => ({ - id: doc.id, - ...doc.data(), - })) as Group[]; - - return groups; - } catch (e) { - console.error(e); - return []; - } + return await db + .collection("groups") + .find({...(admin ? {admin: {$ne: admin}} : {}), ...(participants ? {participants} : {})}) + .toArray(); }; export const getCorporateNameForStudent = async (studentID: string) => { @@ -123,7 +98,7 @@ export const getCorporateNameForStudent = async (studentID: string) => { if (groups.length === 0) return ""; const adminUserIds = [...new Set(groups.map((g) => g.admin))]; - const adminUsersData = await getSpecificUsers(adminUserIds); + const adminUsersData = (await getSpecificUsers(adminUserIds)).filter((x) => !!x) as User[]; if (adminUsersData.length === 0) return ""; const admins = adminUsersData.filter((x) => x.type === "corporate"); diff --git a/src/utils/registration.ts b/src/utils/registration.ts index 22954048..29a112a2 100644 --- a/src/utils/registration.ts +++ b/src/utils/registration.ts @@ -2,30 +2,28 @@ import {collection, doc, getDoc, getDocs, getFirestore, query, setDoc, where} fr import {app} from "@/firebase"; import {Group, Type, User} from "@/interfaces/user"; import {uuidv4} from "@firebase/util"; +import client from "@/lib/mongodb"; -const db = getFirestore(app); +const db = client.db(process.env.MONGODB_DB); export const addUserToGroupOnCreation = async (userId: string, type: Type, creatorId: string) => { - const creatorDoc = await getDoc(doc(db, "users", creatorId)); - if (!creatorDoc.exists()) return false; + const creator = await db.collection("users").findOne({id: creatorId}); + if (!creator) return false; - const creator = {...creatorDoc.data(), id: creatorDoc.id} as User; + const creatorGroup = await db.collection("groups").findOne({admin: creator.id, name: type === "student" ? "Students" : "Teachers"}); - const creatorGroupsDocs = await getDocs(query(collection(db, "groups"), where("admin", "==", creator.id))); - const typeGroup = creatorGroupsDocs.docs.find((x) => (x.data() as Group).name === (type === "student" ? "Students" : "Teachers")); - - if (typeGroup && typeGroup.exists()) { - await setDoc(typeGroup.ref, {participants: [...typeGroup.data().participants, userId]}, {merge: true}); + if (!!creatorGroup) { + await db.collection("groups").updateOne({id: creatorGroup.id}, {$set: {participants: [...creatorGroup.participants, userId]}}); return true; } const groupId = uuidv4(); - await setDoc(doc(db, "groups", groupId), { + await db.collection("groups").insertOne({ admin: creatorId, name: type === "student" ? "Students" : "Teachers", id: groupId, participants: [userId], disableEditing: true, - } as Group); + }); return true; }; diff --git a/src/utils/users.be.ts b/src/utils/users.be.ts index ee28955f..ea701a52 100644 --- a/src/utils/users.be.ts +++ b/src/utils/users.be.ts @@ -18,67 +18,46 @@ import { where, } from "firebase/firestore"; import {CorporateUser, Group, Type, User} from "@/interfaces/user"; -import {getGroupsForUser} from "./groups.be"; +import {getGroupsForUser, getParticipantGroups, getUserGroups, getUsersGroups} from "./groups.be"; import {last, uniq, uniqBy} from "lodash"; import {getUserCodes} from "./codes.be"; import moment from "moment"; -const db = getFirestore(app); +import client from "@/lib/mongodb"; + +const db = client.db(process.env.MONGODB_DB); export async function getUsers() { - const snapshot = await getDocs(collection(db, "users")); - - return snapshot.docs.map((doc) => ({ - ...doc.data(), - id: doc.id, - registrationDate: moment(doc.data().registrationDate).toISOString(), - })) as unknown as User[]; + return await db.collection("users").find({}).toArray(); } -export async function getUser(id: string) { - const userDoc = await getDoc(doc(db, "users", id)); - - return {...userDoc.data(), id, registrationDate: moment(userDoc.data()?.registrationDate).toISOString()} as unknown as User; +export async function getUser(id: string): Promise { + const user = await db.collection("users").findOne({id}); + return !!user ? user : undefined; } export async function getSpecificUsers(ids: string[]) { if (ids.length === 0) return []; - const snapshot = await getDocs(query(collection(db, "users"), where("id", "in", ids))); - - const groups = snapshot.docs.map((doc) => ({ - ...doc.data(), - id: doc.id, - registrationDate: moment(doc.data().registrationDate).toISOString(), - })) as unknown as User[]; - - return groups; + return await db + .collection("users") + .find({id: {$in: ids}}) + .toArray(); } export async function getLinkedUsers(userID?: string, userType?: Type, type?: Type, firstID?: string, lastID?: string, size?: number) { - const q = [ - ...(!!type ? [where("type", "==", type)] : []), - orderBy("registrationDate"), - ...(!!firstID && !lastID ? [endBefore(firstID)] : []), - ...(!!lastID && !firstID ? [startAfter(lastID)] : []), - ...(!!size ? [limit(size)] : []), - ]; - - const totalQ = [...(!!type ? [where("type", "==", type)] : []), orderBy(documentId())]; + const filters = { + ...(!!type ? {type} : {}), + }; if (!userID || userType === "admin" || userType === "developer") { - const snapshot = await getDocs(query(collection(db, "users"), ...q)); - const users = snapshot.docs.map((doc) => ({ - id: doc.id, - ...doc.data(), - })) as User[]; - - const total = await getCountFromServer(query(collection(db, "users"), ...totalQ)); - return {users, total: total.data().count}; + const users = await db.collection("users").find(filters).toArray(); + const total = await db.collection("users").countDocuments(filters); + return {users, total}; } - const adminGroups = await getGroupsForUser(userID); - const groups = await Promise.all(adminGroups.flatMap((x) => x.participants).map(async (x) => await getGroupsForUser(x))); - const belongingGroups = await getGroupsForUser(undefined, userID); + const adminGroups = await getUserGroups(userID); + const groups = await getUsersGroups(adminGroups.flatMap((x) => x.participants)); + const belongingGroups = await getParticipantGroups(userID); const participants = uniq([ ...adminGroups.flatMap((x) => x.participants), @@ -89,33 +68,13 @@ export async function getLinkedUsers(userID?: string, userType?: Type, type?: Ty // тип [FirebaseError: Invalid Query. A non-empty array is required for 'in' filters.] { if (participants.length === 0) return {users: [], total: 0}; - if (participants.length < 30) { - const snapshot = await getDocs(query(collection(db, "users"), ...[where(documentId(), "in", participants), ...q])); - const users = snapshot.docs.map((doc) => ({ - id: doc.id, - ...doc.data(), - })) as User[]; + const users = await db + .collection("users") + .find({...filters, id: {$in: participants}}) + .toArray(); + const total = await db.collection("users").countDocuments({...filters, id: {$in: participants}}); - const total = await getCountFromServer(query(collection(db, "users"), ...[where(documentId(), "in", participants), ...totalQ])); - - return { - users, - total: total.data().count, - }; - } - - const snapshot = await getDocs(query(collection(db, "users"), ...q)); - const users = snapshot.docs.map((doc) => ({ - id: doc.id, - ...doc.data(), - })) as User[]; - - const participantUsers = users.filter((x) => participants.includes(x.id)); - - return { - users: participantUsers, - total: participantUsers.length, - }; + return {users, total}; } export async function getUserBalance(user: User) { @@ -128,7 +87,7 @@ export async function getUserBalance(user: User) { if (user.type === "corporate") return participants.length + codes.filter((x) => !participants.includes(x.userId || "")).length; const participantUsers = await Promise.all(participants.map(getUser)); - const corporateUsers = participantUsers.filter((x) => x.type === "corporate") as CorporateUser[]; + const corporateUsers = participantUsers.filter((x) => x?.type === "corporate") as CorporateUser[]; return ( corporateUsers.reduce((acc, curr) => acc + curr.corporateInformation?.companyInformation?.userAmount || 0, 0) +