From 0adf45c6adf2ed428cfbb0f051200074d0c3543f Mon Sep 17 00:00:00 2001 From: Joao Ramos Date: Tue, 12 Mar 2024 15:52:10 +0000 Subject: [PATCH] Added propagate status changes --- src/interfaces/user.ts | 3 +- src/pages/api/users/update.ts | 23 ++++++-- src/utils/propagate.user.changes.ts | 92 +++++++++++++++++++++++++++++ 3 files changed, 113 insertions(+), 5 deletions(-) create mode 100644 src/utils/propagate.user.changes.ts diff --git a/src/interfaces/user.ts b/src/interfaces/user.ts index bb1ae8ae..f74b42fc 100644 --- a/src/interfaces/user.ts +++ b/src/interfaces/user.ts @@ -2,6 +2,7 @@ import {Module} from "."; import {InstructorGender} from "./exam"; export type User = StudentUser | TeacherUser | CorporateUser | AgentUser | AdminUser | DeveloperUser; +export type UserStatus = "active" | "disabled" | "paymentDue"; export interface BasicUser { email: string; @@ -17,7 +18,7 @@ export interface BasicUser { isVerified: boolean; subscriptionExpirationDate?: null | Date; registrationDate?: Date; - status: "active" | "disabled" | "paymentDue"; + status: UserStatus; } export interface StudentUser extends BasicUser { diff --git a/src/pages/api/users/update.ts b/src/pages/api/users/update.ts index 9f194a88..0813c91e 100644 --- a/src/pages/api/users/update.ts +++ b/src/pages/api/users/update.ts @@ -12,6 +12,8 @@ import moment from "moment"; import ShortUniqueId from "short-unique-id"; import {Payment} from "@/interfaces/paypal"; import {toFixedNumber} from "@/utils/number"; +import { propagateStatusChange } from '@/utils/propagate.user.changes'; + const db = getFirestore(app); const auth = getAuth(app); @@ -74,12 +76,20 @@ async function handler(req: NextApiRequest, res: NextApiResponse) { return; } - const userRef = doc(db, "users", req.query.id ? (req.query.id as string) : req.session.user.id); + const queryId = req.query.id as string; + + const userRef = doc(db, "users", queryId ? (queryId as string) : req.session.user.id); const updatedUser = req.body as User & {password?: string; newPassword?: string}; - if (!!req.query.id) { + if (!!queryId) { const user = await setDoc(userRef, updatedUser, {merge: true}); await managePaymentRecords(updatedUser, updatedUser.id); + + if(updatedUser.status) { + // there's no await as this does not affect the user + propagateStatusChange(queryId, updatedUser.status); + } + res.status(200).json({ok: true}); return; } @@ -132,6 +142,11 @@ async function handler(req: NextApiRequest, res: NextApiResponse) { } } + if(updatedUser.status) { + // there's no await as this does not affect the user + propagateStatusChange(req.session.user.id, updatedUser.status); + } + delete updatedUser.password; delete updatedUser.newPassword; @@ -140,12 +155,12 @@ async function handler(req: NextApiRequest, res: NextApiResponse) { const docUser = await getDoc(doc(db, "users", req.session.user.id)); const user = docUser.data() as User; - if (!req.query.id) { + if (!queryId) { req.session.user = {...user, id: req.session.user.id}; await req.session.save(); } - await managePaymentRecords(user, req.query.id); + await managePaymentRecords(user, queryId); res.status(200).json({user}); } diff --git a/src/utils/propagate.user.changes.ts b/src/utils/propagate.user.changes.ts new file mode 100644 index 00000000..d168c178 --- /dev/null +++ b/src/utils/propagate.user.changes.ts @@ -0,0 +1,92 @@ +// updating specific user changes other users +// for example, updating the status of a corporate user should update the status of all users in the same corporate group +import { UserStatus, User } from "../interfaces/user"; +import { + getFirestore, + collection, + getDocs, + getDoc, + doc, + setDoc, + query, + where, +} from "firebase/firestore"; +import { app } from "@/firebase"; + +const db = getFirestore(app); + +export const propagateStatusChange = (userId: string, status: UserStatus) => + new Promise((resolve, reject) => { + getDoc(doc(db, "users", userId)) + .then((docUser) => { + const user = docUser.data() as User; + + // only update the status of the user's groups if the user is a corporate user + if (user.type === "corporate") { + getDocs( + query(collection(db, "groups"), where("admin", "==", userId)) + ).then(async (userGroupsRef) => { + const userGroups = userGroupsRef.docs.map((x) => x.data()); + + const targetUsers = [ + ...new Set( + userGroups.flatMap((g) => g.participants).filter((u) => u) + ), + ]; + + Promise.all( + targetUsers.map(async (targetUserId) => { + const ref = await getDoc(doc(db, "users", targetUserId)); + + if (!ref.exists()) return null; + + const data = ref.data() as User; + return { ...data, id: targetUserId }; + }) + ) + .then((data) => { + const filtered = data.filter((x) => { + if (x === null) return false; + if (x.status === status) return false; + if (x.type !== "student") return false; + return true; + }) as User[]; + + if (filtered.length === 0) { + return; + } + + Promise.all( + filtered.map((user: User) => + setDoc( + doc(db, "users", user.id), + { status }, + { merge: true } + ) + ) + ) + .then(() => { + resolve(true); + }) + .catch((err) => { + console.error(err); + reject(err); + }); + }) + .catch((err) => { + console.error(err); + reject(err); + }); + }); + return; + } + + const error = new Error("User is not a corporate user"); + console.error(error); + reject(error); + }) + .catch((err) => { + console.error(err); + reject(err); + }); + });