From 37c3c6f7f402151caf8c428d11a513d98938306e Mon Sep 17 00:00:00 2001 From: Joao Ramos Date: Mon, 11 Mar 2024 17:00:38 +0000 Subject: [PATCH 1/5] Updated redirect implementation --- src/pages/action.tsx | 8 ++++---- src/pages/exam.tsx | 20 ++++++++------------ src/pages/exercises.tsx | 20 ++++++++------------ src/pages/generation.tsx | 20 ++++++++------------ src/pages/index.tsx | 20 ++++++++++++-------- src/pages/list/users.tsx | 11 ++++------- src/pages/login.tsx | 13 +++++-------- src/pages/payment-record.tsx | 26 +++++++++++--------------- src/pages/payment.tsx | 15 +++++++-------- src/pages/profile.tsx | 20 ++++++++------------ src/pages/record.tsx | 20 ++++++++------------ src/pages/settings.tsx | 20 ++++++++------------ src/pages/stats.tsx | 20 ++++++++------------ src/pages/tickets.tsx | 28 ++++++++++++---------------- 14 files changed, 111 insertions(+), 150 deletions(-) diff --git a/src/pages/action.tsx b/src/pages/action.tsx index bee4552a..c6cf8cbb 100644 --- a/src/pages/action.tsx +++ b/src/pages/action.tsx @@ -23,11 +23,11 @@ export function getServerSideProps({ res: any; }) { if (!query || !query.oobCode || !query.mode) { - res.setHeader("location", "/login"); - res.statusCode = 302; - res.end(); return { - props: {}, + redirect: { + destination: "/login", + permanent: false, + } }; } diff --git a/src/pages/exam.tsx b/src/pages/exam.tsx index 4d686e74..3f683604 100644 --- a/src/pages/exam.tsx +++ b/src/pages/exam.tsx @@ -10,24 +10,20 @@ export const getServerSideProps = withIronSessionSsr(({req, res}) => { const user = req.session.user; if (!user || !user.isVerified) { - res.setHeader("location", "/login"); - res.statusCode = 302; - res.end(); return { - props: { - user: null, - }, + redirect: { + destination: "/login", + permanent: false, + } }; } if (shouldRedirectHome(user)) { - res.setHeader("location", "/"); - res.statusCode = 302; - res.end(); return { - props: { - user: null, - }, + redirect: { + destination: "/", + permanent: false, + } }; } diff --git a/src/pages/exercises.tsx b/src/pages/exercises.tsx index f3bcfa84..9ab3b244 100644 --- a/src/pages/exercises.tsx +++ b/src/pages/exercises.tsx @@ -10,24 +10,20 @@ export const getServerSideProps = withIronSessionSsr(({req, res}) => { const user = req.session.user; if (!user || !user.isVerified) { - res.setHeader("location", "/login"); - res.statusCode = 302; - res.end(); return { - props: { - user: null, - }, + redirect: { + destination: "/login", + permanent: false, + } }; } if (shouldRedirectHome(user)) { - res.setHeader("location", "/"); - res.statusCode = 302; - res.end(); return { - props: { - user: null, - }, + redirect: { + destination: "/", + permanent: false, + } }; } diff --git a/src/pages/generation.tsx b/src/pages/generation.tsx index 58a5d641..b52f6ef6 100644 --- a/src/pages/generation.tsx +++ b/src/pages/generation.tsx @@ -26,24 +26,20 @@ export const getServerSideProps = withIronSessionSsr(({req, res}) => { const user = req.session.user; if (!user || !user.isVerified) { - res.setHeader("location", "/login"); - res.statusCode = 302; - res.end(); return { - props: { - user: null, - }, + redirect: { + destination: "/login", + permanent: false, + } }; } if (shouldRedirectHome(user) || user.type !== "developer") { - res.setHeader("location", "/"); - res.statusCode = 302; - res.end(); return { - props: { - user: null, - }, + redirect: { + destination: "/", + permanent: false, + } }; } diff --git a/src/pages/index.tsx b/src/pages/index.tsx index 02ff0a8e..59401b80 100644 --- a/src/pages/index.tsx +++ b/src/pages/index.tsx @@ -35,6 +35,7 @@ import Select from "react-select"; import {USER_TYPE_LABELS} from "@/resources/user"; export const getServerSideProps = withIronSessionSsr(({req, res}) => { + debugger; const user = req.session.user; const envVariables: {[key: string]: string} = {}; @@ -45,14 +46,11 @@ export const getServerSideProps = withIronSessionSsr(({req, res}) => { }); if (!user || !user.isVerified) { - res.setHeader("location", "/login"); - res.statusCode = 302; - res.end(); return { - props: { - user: null, - envVariables, - }, + redirect: { + destination: "/login", + permanent: false, + } }; } @@ -61,7 +59,13 @@ export const getServerSideProps = withIronSessionSsr(({req, res}) => { }; }, sessionOptions); -export default function Home({envVariables}: {envVariables: {[key: string]: string}}) { +interface Props { + user: any; + envVariables: {[key: string]: string}; +} +export default function Home(props: Props) { + const { envVariables} = props; + debugger; const [showDiagnostics, setShowDiagnostics] = useState(false); const [showDemographicInput, setShowDemographicInput] = useState(false); const [selectedScreen, setSelectedScreen] = useState("admin"); diff --git a/src/pages/list/users.tsx b/src/pages/list/users.tsx index 356ef810..3ec4c585 100644 --- a/src/pages/list/users.tsx +++ b/src/pages/list/users.tsx @@ -22,14 +22,11 @@ export const getServerSideProps = withIronSessionSsr(({req, res}) => { }); if (!user || !user.isVerified) { - res.setHeader("location", "/login"); - res.statusCode = 302; - res.end(); return { - props: { - user: null, - envVariables, - }, + redirect: { + destination: "/login", + permanent: false, + } }; } diff --git a/src/pages/login.tsx b/src/pages/login.tsx index 2a6990df..73582e1a 100644 --- a/src/pages/login.tsx +++ b/src/pages/login.tsx @@ -31,15 +31,12 @@ export const getServerSideProps = withIronSessionSsr(({ req, res }) => { }); if (user && user.isVerified) { - res.setHeader("location", "/"); - res.statusCode = 302; - res.end(); return { - props: { - user: null, - envVariables, - }, - }; + redirect: { + destination: "/", + permanent: false, + } + }; } return { diff --git a/src/pages/payment-record.tsx b/src/pages/payment-record.tsx index 941f9fab..e9368abf 100644 --- a/src/pages/payment-record.tsx +++ b/src/pages/payment-record.tsx @@ -42,25 +42,21 @@ export const getServerSideProps = withIronSessionSsr(({ req, res }) => { const user = req.session.user; if (!user || !user.isVerified) { - res.setHeader("location", "/login"); - res.statusCode = 302; - res.end(); return { - props: { - user: null, - }, - }; + redirect: { + destination: "/login", + permanent: false, + } + }; } if (shouldRedirectHome(user) || !["admin", "developer"].includes(user.type)) { - res.setHeader("location", "/"); - res.statusCode = 302; - res.end(); - return { - props: { - user: null, - }, - }; + return { + redirect: { + destination: "/", + permanent: false, + } + }; } return { diff --git a/src/pages/payment.tsx b/src/pages/payment.tsx index bb68b16c..0fcc3a56 100644 --- a/src/pages/payment.tsx +++ b/src/pages/payment.tsx @@ -7,6 +7,7 @@ import PaymentDue from "./(status)/PaymentDue"; import { useRouter } from "next/router"; export const getServerSideProps = withIronSessionSsr(({ req, res }) => { + debugger; const user = req.session.user; const envVariables: { [key: string]: string } = {}; @@ -17,15 +18,12 @@ export const getServerSideProps = withIronSessionSsr(({ req, res }) => { }); if (!user || !user.isVerified) { - res.setHeader("location", "/login"); - res.statusCode = 302; - res.end(); return { - props: { - user: null, - envVariables, - }, - }; + redirect: { + destination: "/login", + permanent: false, + } + }; } return { @@ -38,6 +36,7 @@ export default function Home({ }: { envVariables: { [key: string]: string }; }) { + debugger; const { user } = useUser({ redirectTo: "/login" }); const router = useRouter(); diff --git a/src/pages/profile.tsx b/src/pages/profile.tsx index e94d4026..68e652d2 100644 --- a/src/pages/profile.tsx +++ b/src/pages/profile.tsx @@ -37,24 +37,20 @@ export const getServerSideProps = withIronSessionSsr(({req, res}) => { const user = req.session.user; if (!user || !user.isVerified) { - res.setHeader("location", "/login"); - res.statusCode = 302; - res.end(); return { - props: { - user: null, - }, + redirect: { + destination: "/login", + permanent: false, + } }; } if (shouldRedirectHome(user)) { - res.setHeader("location", "/"); - res.statusCode = 302; - res.end(); return { - props: { - user: null, - }, + redirect: { + destination: "/", + permanent: false, + } }; } diff --git a/src/pages/record.tsx b/src/pages/record.tsx index d5c5d97f..c12888c6 100644 --- a/src/pages/record.tsx +++ b/src/pages/record.tsx @@ -30,24 +30,20 @@ export const getServerSideProps = withIronSessionSsr(({req, res}) => { const user = req.session.user; if (!user || !user.isVerified) { - res.setHeader("location", "/login"); - res.statusCode = 302; - res.end(); return { - props: { - user: null, - }, + redirect: { + destination: "/login", + permanent: false, + } }; } if (shouldRedirectHome(user)) { - res.setHeader("location", "/"); - res.statusCode = 302; - res.end(); return { - props: { - user: null, - }, + redirect: { + destination: "/", + permanent: false, + } }; } diff --git a/src/pages/settings.tsx b/src/pages/settings.tsx index eb1192b5..85cb5073 100644 --- a/src/pages/settings.tsx +++ b/src/pages/settings.tsx @@ -18,24 +18,20 @@ export const getServerSideProps = withIronSessionSsr(({req, res}) => { const user = req.session.user; if (!user || !user.isVerified) { - res.setHeader("location", "/login"); - res.statusCode = 302; - res.end(); return { - props: { - user: null, - }, + redirect: { + destination: "/login", + permanent: false, + } }; } if (shouldRedirectHome(user) || user.type !== "developer") { - res.setHeader("location", "/"); - res.statusCode = 302; - res.end(); return { - props: { - user: null, - }, + redirect: { + destination: "/", + permanent: false, + } }; } diff --git a/src/pages/stats.tsx b/src/pages/stats.tsx index ea8f9886..fdb36314 100644 --- a/src/pages/stats.tsx +++ b/src/pages/stats.tsx @@ -35,24 +35,20 @@ export const getServerSideProps = withIronSessionSsr(({req, res}) => { const user = req.session.user; if (!user || !user.isVerified) { - res.setHeader("location", "/login"); - res.statusCode = 302; - res.end(); return { - props: { - user: null, - }, + redirect: { + destination: "/login", + permanent: false, + } }; } if (shouldRedirectHome(user)) { - res.setHeader("location", "/"); - res.statusCode = 302; - res.end(); return { - props: { - user: null, - }, + redirect: { + destination: "/", + permanent: false, + } }; } diff --git a/src/pages/tickets.tsx b/src/pages/tickets.tsx index 21c6fd92..128058e4 100644 --- a/src/pages/tickets.tsx +++ b/src/pages/tickets.tsx @@ -35,28 +35,24 @@ export const getServerSideProps = withIronSessionSsr(({ req, res }) => { const user = req.session.user; if (!user || !user.isVerified) { - res.setHeader("location", "/login"); - res.statusCode = 302; - res.end(); - return { - props: { - user: null, - }, - }; + return { + redirect: { + destination: "/login", + permanent: false, + } + }; } if ( shouldRedirectHome(user) || ["admin", "developer", "agent"].includes(user.type) ) { - res.setHeader("location", "/"); - res.statusCode = 302; - res.end(); - return { - props: { - user: null, - }, - }; + return { + redirect: { + destination: "/", + permanent: false, + } + }; } return { From e2d5f6ac9d939dd76bf409e3ef8d32eaf879136e Mon Sep 17 00:00:00 2001 From: Joao Ramos Date: Mon, 11 Mar 2024 17:04:10 +0000 Subject: [PATCH 2/5] Removed debugger; --- src/pages/api/paypal/index.ts | 3 +++ src/pages/index.tsx | 2 -- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/pages/api/paypal/index.ts b/src/pages/api/paypal/index.ts index 9b01efe8..0f57adef 100644 --- a/src/pages/api/paypal/index.ts +++ b/src/pages/api/paypal/index.ts @@ -60,6 +60,9 @@ async function handler(req: NextApiRequest, res: NextApiResponse) { payment_source: { paypal: { email_address: req.session.user.email || "", + address: { + postal_code: "1234", + }, experience_context: { payment_method_preference: "IMMEDIATE_PAYMENT_REQUIRED", locale: "en-US", diff --git a/src/pages/index.tsx b/src/pages/index.tsx index 59401b80..b74662b4 100644 --- a/src/pages/index.tsx +++ b/src/pages/index.tsx @@ -35,7 +35,6 @@ import Select from "react-select"; import {USER_TYPE_LABELS} from "@/resources/user"; export const getServerSideProps = withIronSessionSsr(({req, res}) => { - debugger; const user = req.session.user; const envVariables: {[key: string]: string} = {}; @@ -65,7 +64,6 @@ interface Props { } export default function Home(props: Props) { const { envVariables} = props; - debugger; const [showDiagnostics, setShowDiagnostics] = useState(false); const [showDemographicInput, setShowDemographicInput] = useState(false); const [selectedScreen, setSelectedScreen] = useState("admin"); From 83e41737504836f9b16b10353109688880c35724 Mon Sep 17 00:00:00 2001 From: Joao Ramos Date: Mon, 11 Mar 2024 17:05:20 +0000 Subject: [PATCH 3/5] removed broken debugger --- src/pages/payment.tsx | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/pages/payment.tsx b/src/pages/payment.tsx index 0fcc3a56..f249b1c6 100644 --- a/src/pages/payment.tsx +++ b/src/pages/payment.tsx @@ -7,7 +7,6 @@ import PaymentDue from "./(status)/PaymentDue"; import { useRouter } from "next/router"; export const getServerSideProps = withIronSessionSsr(({ req, res }) => { - debugger; const user = req.session.user; const envVariables: { [key: string]: string } = {}; @@ -36,7 +35,6 @@ export default function Home({ }: { envVariables: { [key: string]: string }; }) { - debugger; const { user } = useUser({ redirectTo: "/login" }); const router = useRouter(); From d9b93a34704ff3044806d42471b7af1ab8d79560 Mon Sep 17 00:00:00 2001 From: Joao Ramos Date: Mon, 11 Mar 2024 17:13:48 +0000 Subject: [PATCH 4/5] Added default value for postal_code --- src/pages/api/paypal/index.ts | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/pages/api/paypal/index.ts b/src/pages/api/paypal/index.ts index 0f57adef..2875de4f 100644 --- a/src/pages/api/paypal/index.ts +++ b/src/pages/api/paypal/index.ts @@ -61,7 +61,15 @@ async function handler(req: NextApiRequest, res: NextApiResponse) { paypal: { email_address: req.session.user.email || "", address: { - postal_code: "1234", + address_line_1: "", + address_line_2: "", + admin_area_1: "", + admin_area_2: "", + // added default values as requsted by the client, using the default values recommended + // the paypal engineer, otherwise we would have to create something that would detect the location + // of the user and generate a valid postal code for that location... + country_code: "US", + postal_code: "94107", }, experience_context: { payment_method_preference: "IMMEDIATE_PAYMENT_REQUIRED", From 0adf45c6adf2ed428cfbb0f051200074d0c3543f Mon Sep 17 00:00:00 2001 From: Joao Ramos Date: Tue, 12 Mar 2024 15:52:10 +0000 Subject: [PATCH 5/5] 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); + }); + });