From 9abc321c3fc6299bc2397393bb2c73386ff606fe Mon Sep 17 00:00:00 2001 From: Sean Date: Sat, 13 Apr 2024 23:38:27 -0700 Subject: [PATCH] Make events pages work without auth --- src/lib/api/EventAPI.ts | 5 ++- src/lib/hoc/withAccessType.ts | 47 +++++++++++++++--------- src/pages/events.tsx | 30 ++++++++++----- src/pages/events/[uuid].tsx | 69 ++++++++++++++++++----------------- 4 files changed, 88 insertions(+), 63 deletions(-) diff --git a/src/lib/api/EventAPI.ts b/src/lib/api/EventAPI.ts index 0ba8ff21..131ff253 100644 --- a/src/lib/api/EventAPI.ts +++ b/src/lib/api/EventAPI.ts @@ -20,10 +20,11 @@ import axios from 'axios'; /** * Get a single event by UUID * @param uuid Search query uuid - * @param token Bearer token + * @param token Bearer token. Optional, but should be provided if you need to + * see attendance codes. * @returns Event info */ -export const getEvent = async (uuid: UUID, token: string): Promise => { +export const getEvent = async (uuid: UUID, token?: string): Promise => { const requestUrl = `${config.api.baseUrl}${config.api.endpoints.event.event}/${uuid}`; const response = await axios.get(requestUrl, { diff --git a/src/lib/hoc/withAccessType.ts b/src/lib/hoc/withAccessType.ts index 1f8b9fff..9e049e77 100644 --- a/src/lib/hoc/withAccessType.ts +++ b/src/lib/hoc/withAccessType.ts @@ -12,6 +12,33 @@ import type { } from 'next'; import { ParsedUrlQuery } from 'querystring'; +/** + * Tries to read the user object from the cookie. If the user object doesn't + * exist or is invalid, then it will try to fetch a new one with the auth token. + */ +export async function getCurrentUser( + { req, res }: Pick, + authToken: string +): Promise { + const userCookie = CookieService.getServerCookie(CookieType.USER, { req, res }); + let user: PrivateProfile | undefined; + + if (userCookie) { + // Standard flow will use the existing user cookie as src data unless it's corrupted or missing keys, then try to refresh user otherwise redirect on fail + try { + user = JSON.parse(userCookie); + } catch { + user = undefined; + } + } + + if (!user?.accessType) { + user = await UserAPI.getCurrentUserAndRefreshCookie(authToken, { req, res }); + } + + return user; +} + export type GetServerSidePropsWithAuth< Props extends { [key: string]: any } = { [key: string]: any }, Params extends ParsedUrlQuery = ParsedUrlQuery, @@ -42,7 +69,6 @@ export default function withAccessType( // Generate a new getServerSideProps function by taking the return value of the original function and appending the user prop onto it if the user cookie exists, otherwise force user to login page const modified: GetServerSideProps = async (context: GetServerSidePropsContext) => { const { req, res } = context; - const userCookie = CookieService.getServerCookie(CookieType.USER, { req, res }); const authTokenCookie = CookieService.getServerCookie(CookieType.ACCESS_TOKEN, { req, res }); const { homeRoute, loginRoute } = config; @@ -73,23 +99,8 @@ export default function withAccessType( return loginRedirect; } - let user: PrivateProfile | undefined; - let userAccessLevel: UserAccessType | undefined; - - if (userCookie) { - // Standard flow will use the existing user cookie as src data unless it's corrupted or missing keys, then try to refresh user otherwise redirect on fail - try { - user = JSON.parse(userCookie); - userAccessLevel = user?.accessType; - } catch { - user = undefined; - } - } - - if (!user || !userAccessLevel) { - user = await UserAPI.getCurrentUserAndRefreshCookie(authTokenCookie, { req, res }); - userAccessLevel = user.accessType; - } + const user = await getCurrentUser({ req, res }, authTokenCookie); + const userAccessLevel = user.accessType; // This block should be impossible to hit assuming the portal API doesn't go down if (!userAccessLevel) throw new Error('User access level is not defined'); diff --git a/src/pages/events.tsx b/src/pages/events.tsx index 26efee32..f902b174 100644 --- a/src/pages/events.tsx +++ b/src/pages/events.tsx @@ -3,9 +3,9 @@ import { DIVIDER } from '@/components/common/Dropdown'; import { EventDisplay } from '@/components/events'; import { config } from '@/lib'; import { EventAPI, UserAPI } from '@/lib/api'; -import withAccessType, { GetServerSidePropsWithAuth } from '@/lib/hoc/withAccessType'; +import { getCurrentUser } from '@/lib/hoc/withAccessType'; import useQueryState from '@/lib/hooks/useQueryState'; -import { PermissionService } from '@/lib/services'; +import { CookieService } from '@/lib/services'; import type { PublicAttendance, PublicEvent } from '@/lib/types/apiResponses'; import { FilterEventOptions, @@ -13,8 +13,10 @@ import { isValidCommunityFilter, isValidDateFilter, } from '@/lib/types/client'; +import { CookieType } from '@/lib/types/enums'; import { formatSearch, getDateRange, getYears } from '@/lib/utils'; import styles from '@/styles/pages/events.module.scss'; +import { GetServerSideProps } from 'next'; import { useMemo, useState } from 'react'; interface EventsPageProps { @@ -214,9 +216,13 @@ const EventsPage = ({ events, attendances, initialFilters }: EventsPageProps) => export default EventsPage; -const getServerSidePropsFunc: GetServerSidePropsWithAuth = async ({ query, authToken }) => { +export const getServerSideProps: GetServerSideProps = async ({ req, res, query }) => { + const authToken: string | null = + CookieService.getServerCookie(CookieType.ACCESS_TOKEN, { req, res }) ?? null; + const user = authToken !== null ? await getCurrentUser({ req, res }, authToken) : null; + const getEventsPromise = EventAPI.getAllEvents(); - const getAttendancesPromise = UserAPI.getAttendancesForCurrentUser(authToken); + const getAttendancesPromise = authToken ? UserAPI.getAttendancesForCurrentUser(authToken) : []; const [events, attendances] = await Promise.all([getEventsPromise, getAttendancesPromise]); @@ -229,10 +235,14 @@ const getServerSidePropsFunc: GetServerSidePropsWithAuth = async ({ query, authT const initialFilters = { community, date, attendance, search }; - return { props: { title: 'Events', events, attendances, initialFilters } }; + return { + props: { + title: 'Events', + events, + attendances, + initialFilters, + // For navbar + user, + }, + }; }; - -export const getServerSideProps = withAccessType( - getServerSidePropsFunc, - PermissionService.loggedInUser -); diff --git a/src/pages/events/[uuid].tsx b/src/pages/events/[uuid].tsx index 45af6ca8..b910232c 100644 --- a/src/pages/events/[uuid].tsx +++ b/src/pages/events/[uuid].tsx @@ -2,14 +2,17 @@ import { Typography } from '@/components/common'; import EventDetail from '@/components/events/EventDetail'; import { Feedback, FeedbackForm } from '@/components/feedback'; import { EventAPI, FeedbackAPI, UserAPI } from '@/lib/api'; -import { GetServerSidePropsWithAuth } from '@/lib/hoc/withAccessType'; +import { getCurrentUser } from '@/lib/hoc/withAccessType'; +import { CookieService } from '@/lib/services'; import type { PublicEvent, PublicFeedback } from '@/lib/types/apiResponses'; +import { CookieType } from '@/lib/types/enums'; import { formatEventDate } from '@/lib/utils'; import styles from '@/styles/pages/event.module.scss'; +import { GetServerSideProps } from 'next'; import { useMemo, useState } from 'react'; interface EventPageProps { - token: string; + token: string | null; event: PublicEvent; attended: boolean; feedback: PublicFeedback | null; @@ -28,7 +31,7 @@ const EventPage = ({ token, event, attended, feedback: initFeedback }: EventPage ); - } else if (started) { + } else if (started && token) { feedbackForm = ( ); @@ -44,36 +47,36 @@ const EventPage = ({ token, event, attended, feedback: initFeedback }: EventPage export default EventPage; -const getServerSidePropsFunc: GetServerSidePropsWithAuth = async ({ - params, - user, - authToken: token, -}) => { +export const getServerSideProps: GetServerSideProps = async ({ params, req, res }) => { const uuid = params?.uuid as string; - // try { - const [event, attendances, [feedback = null]] = await Promise.all([ - EventAPI.getEvent(uuid, token), - user ? UserAPI.getAttendancesForCurrentUser(token) : [], - user ? FeedbackAPI.getFeedback(token, { user: user.uuid, event: uuid }) : [], - ]); - return { - props: { - title: event.title, - description: `${formatEventDate(event.start, event.end, true)} at ${event.location}\n\n${ - event.description - }`, - previewImage: event.cover, - bigPreviewImage: true, - token, - event, - attended: attendances.some(attendance => attendance.event.uuid === uuid), - feedback, - }, - }; - // } catch { - // return { notFound: true }; - // } -}; + const token: string | null = + CookieService.getServerCookie(CookieType.ACCESS_TOKEN, { req, res }) ?? null; + const user = token !== null ? await getCurrentUser({ req, res }, token) : null; -export const getServerSideProps = getServerSidePropsFunc; + try { + const [event, attendances, [feedback = null]] = await Promise.all([ + EventAPI.getEvent(uuid, token), + token ? UserAPI.getAttendancesForCurrentUser(token) : [], + user ? FeedbackAPI.getFeedback(token, { user: user.uuid, event: uuid }) : [], + ]); + return { + props: { + title: event.title, + description: `${formatEventDate(event.start, event.end, true)} at ${event.location}\n\n${ + event.description + }`, + previewImage: event.cover, + bigPreviewImage: true, + token, + event, + attended: attendances.some(attendance => attendance.event.uuid === uuid), + feedback, + // For navbar + user, + }, + }; + } catch { + return { notFound: true }; + } +};