From a2156e00d56293c37298da3619b0b08f559e86eb Mon Sep 17 00:00:00 2001 From: David Huang Date: Sat, 10 Feb 2024 14:10:54 -0600 Subject: [PATCH 1/9] rough implementation for APIS --- pages/api/applications.ts | 60 +++++++++ pages/api/event/[id]/apply.ts | 119 ++++++++++++++++++ pages/api/event/[id]/submitted-application.ts | 66 ++++++++++ 3 files changed, 245 insertions(+) create mode 100644 pages/api/applications.ts create mode 100644 pages/api/event/[id]/apply.ts create mode 100644 pages/api/event/[id]/submitted-application.ts diff --git a/pages/api/applications.ts b/pages/api/applications.ts new file mode 100644 index 0000000..1a79229 --- /dev/null +++ b/pages/api/applications.ts @@ -0,0 +1,60 @@ +import dbConnect from '@/lib/dbConnect'; +import { ObjectId } from 'mongodb'; +import type { NextApiRequest, NextApiResponse } from 'next'; +import mongoose from 'mongoose'; +import VolunteerApplications from 'bookem-shared/src/models/VolunteerApplications'; +import { ApplicationResponseData } from 'bookem-shared/src/types/database'; +import ApplicationResponse from 'bookem-shared/src/models/ApplicationResponse'; +import VolunteerEvents from 'bookem-shared/src/models/VolunteerEvents'; +import { authOptions } from '@/pages/api/auth/[...nextauth]'; +import { getServerSession } from 'next-auth'; + +export default async function handler( + req: NextApiRequest, + res: NextApiResponse +) { + // Get session user + const session = await getServerSession(req, res, authOptions); + // for API testing only + // TODO extract this logic + // const session = { + // user: { + // id: '60f3f5f8f0f6f6f6f6f6f6f6', + // }, + // }; + + // Get request parameter + const { + query: { id }, + method, + } = req; + + switch (method) { + /** + * @route GET /api/applications + * @desc Get all applications to all applied events for a user + * @req event id, user in session + * @res list of applied events + */ + case 'GET': + try { + await dbConnect(); + await VolunteerEvents.find({}); + + // query volunteerApplication by event id attributes + const applicationResponses = await ApplicationResponse.find({ + userId: session.user.id, + }).populate('eventId'); + + // const appliedEvents = applicationResponses.map((applicationResponse) => { + // return applicationResponse.eventId; + // }; + + return res.status(200).json({ message: applicationResponses }); + } catch (error) { + console.error(error); + res.status(500).json({ message: error }); + } + break; + } +} diff --git a/pages/api/event/[id]/apply.ts b/pages/api/event/[id]/apply.ts new file mode 100644 index 0000000..6484b4c --- /dev/null +++ b/pages/api/event/[id]/apply.ts @@ -0,0 +1,119 @@ +import dbConnect from '@/lib/dbConnect'; +import { ObjectId } from 'mongodb'; +import type { NextApiRequest, NextApiResponse } from 'next'; +import mongoose from 'mongoose'; +import VolunteerApplications from 'bookem-shared/src/models/VolunteerApplications'; +import { ApplicationResponseData } from 'bookem-shared/src/types/database'; +import ApplicationResponse from 'bookem-shared/src/models/ApplicationResponse'; +import { authOptions } from '@/pages/api/auth/[...nextauth]'; +import { getServerSession } from 'next-auth'; + +export default async function handler( + req: NextApiRequest, + res: NextApiResponse +) { + // Get session user + const session = await getServerSession(req, res, authOptions); + // for API testing only + // TODO extract this logic + // const session = { + // user: { + // id: '60f3f5f8f0f6f6f6f6f6f6f6', + // }, + // } + + // Get request parameter + const { + query: { id }, + method, + } = req; + + switch (method) { + /** + * @route GET /api/event/[id]/apply + * @desc Return a list of application questions that this event needs + * @req event id, user in session + * @res list of application questions + */ + case 'GET': + try { + await dbConnect(); + + // TODO - refactor common id checking to a common util + if (!id) return res.status(400).json({ message: 'Missing id' }); + + // check if id is a valid mongoose id + if (!ObjectId.isValid(id as string)) + return res.status(400).json({ message: 'Invalid id' }); + + // query volunteerApplication by event id attributes + const volunteerApplication = await VolunteerApplications.findOne({ + eventId: id, + }); + + // TODO - refactor a common util for when something is not found + if (!volunteerApplication) { + return res + .status(404) + .json({ message: 'No application for the event found' }); + } + + const questions = volunteerApplication.questions; + + return res.status(200).json({ message: questions }); + } catch (error) { + console.error(error); + res.status(500).json({ message: error }); + } + break; + + /** + * @route POST /api/event/[id]/apply + * @desc Submit the answers to the questions this event needs + * @req event id, user in session + * @res Success message + */ + case 'POST': + try { + await dbConnect(); + + // start a try catch block to catch any errors in parsing the request body + // TODO maybe define a DTO for this + console.log(req.body); + const response = req.body as ApplicationResponseData; + const { answers } = response; + + + // Declare the following ops to be an atomic transaction + const mongoSession = await mongoose.startSession(); + await mongoSession.withTransaction(async () => { + // insert the response to applicationResponses data + const newResponse = new ApplicationResponse({ + userId: session.user.id, + status: 'pending', + eventId: id, + answers, + }); + + await newResponse.save() + + // use the id of the saved response to update the volunteerApplications data + await VolunteerApplications.updateOne( + { eventId: id }, + { + $push: { + responses: newResponse._id, + }, + } + ); + }); + + return res.status(200).json('Application Submitted'); + } catch (error: any) { + res.status(500).json({ message: error.message }); + console.error(error); + } + + break; + } +} diff --git a/pages/api/event/[id]/submitted-application.ts b/pages/api/event/[id]/submitted-application.ts new file mode 100644 index 0000000..421f24f --- /dev/null +++ b/pages/api/event/[id]/submitted-application.ts @@ -0,0 +1,66 @@ +import dbConnect from '@/lib/dbConnect'; +import { ObjectId } from 'mongodb'; +import type { NextApiRequest, NextApiResponse } from 'next'; +import ApplicationResponse from 'bookem-shared/src/models/ApplicationResponse'; +import { getServerSession } from 'next-auth'; +import { authOptions } from '@/pages/api/auth/[...nextauth]'; + +export default async function handler( + req: NextApiRequest, + res: NextApiResponse +) { + // Get session user + const session = await getServerSession(req, res, authOptions); + // for API testing only + // const session = { + // user: { + // id: '60f3f5f8f0f6f6f6f6f6f6f6', + // }, + // }; + + // Get request parameter + const { + query: { id }, + method, + } = req; + + switch (method) { + /** + * @route GET /api/event/[id]/submitted-application + * @desc Get submitted application + * @req event id, user in session + * @res list of application questions + */ + case 'GET': + try { + await dbConnect(); + + // TODO - refactor common id checking to a common util + if (!id) return res.status(400).json({ message: 'Missing id' }); + + // check if id is a valid mongoose id + if (!ObjectId.isValid(id as string)) + return res.status(400).json({ message: 'Invalid id' }); + + // query volunteerApplication by event id attributes + // using findOne - assuming there is only one application per event per user + const volunteerApplication = await ApplicationResponse.findOne({ + userId: session.user.id, + eventId: id, + }); + + // TODO - refactor a common util for when something is not found + if (!volunteerApplication) { + return res + .status(404) + .json({ message: 'No application for the event found' }); + } + + return res.status(200).json({ message: volunteerApplication }); + } catch (error) { + console.error(error); + res.status(500).json({ message: error }); + } + break; + } +} From 208ae13c2eb0bfa1e7c6aaa834bead7366e251be Mon Sep 17 00:00:00 2001 From: David Huang Date: Sat, 10 Feb 2024 15:33:12 -0600 Subject: [PATCH 2/9] fix error msg --- pages/api/applications.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pages/api/applications.ts b/pages/api/applications.ts index 1a79229..f3e0887 100644 --- a/pages/api/applications.ts +++ b/pages/api/applications.ts @@ -53,7 +53,7 @@ export default async function handler( return res.status(200).json({ message: applicationResponses }); } catch (error) { console.error(error); - res.status(500).json({ message: error }); + res.status(500).json({ message: error.message }); } break; } From 136b7088f5f8b9ae8d1162f1a2da8f84100419a1 Mon Sep 17 00:00:00 2001 From: David Huang Date: Sat, 10 Feb 2024 16:26:42 -0600 Subject: [PATCH 3/9] toggle middleware behavior for API testing without logging in --- middleware.ts | 24 ++++++++++++++++-------- utils/api-testing.ts | 15 +++++++++++++++ 2 files changed, 31 insertions(+), 8 deletions(-) create mode 100644 utils/api-testing.ts diff --git a/middleware.ts b/middleware.ts index 207a1bc..fa70add 100644 --- a/middleware.ts +++ b/middleware.ts @@ -1,3 +1,5 @@ +import { testingAPI } from './utils/api-testing'; + /** * THIS FILE HAS TO STAY HERE! * Use the default next-auth middleware pattern from https://next-auth.js.org/configuration/nextjs @@ -9,12 +11,18 @@ export { default } from 'next-auth/middleware'; /** * Configure which api routes to authenticate */ -export const config = { - // match all routes in /api except for the route /api/users/create - // the ?!(...) means match everything except for (...) - // adapted from: https://nextjs.org/docs/messages/invalid-route-source - matcher: ['/api/((?!users/create).*)'], -}; +// when in development, export an empty config +export const config = testingAPI + ? {} // Don't use authentication for tests + : { + // match all routes in /api except for the route /api/users/create + // the ?!(...) means match everything except for (...) + // adapted from: https://nextjs.org/docs/messages/invalid-route-source + matcher: ['/api/((?!users/create).*)'], + }; -// Comment everything and uncomment this to test -// export function middleware() {} +function middleware() {} +// when in development, export the empty middleware function +if (testingAPI) { + module.exports = { ...module.exports, middleware }; +} diff --git a/utils/api-testing.ts b/utils/api-testing.ts new file mode 100644 index 0000000..811a5e3 --- /dev/null +++ b/utils/api-testing.ts @@ -0,0 +1,15 @@ +// common utils for simple API testing in development phase + +// change this var to true when you are testing the API endpoints without logging in +export const testingAPI = false; + +const fakeUserSession = { + user: { + id: '65ae7bbd24dc37492f2581c0', + }, +}; + +// returns a fake user session when testing the API +export const makeSessionForAPITest = () => { + return testingAPI ? fakeUserSession : null; +}; From d799b561e714ce1c045f84d4dc736d367d21e608 Mon Sep 17 00:00:00 2001 From: David Huang Date: Sat, 10 Feb 2024 16:27:09 -0600 Subject: [PATCH 4/9] allow testing API endpoints with fake session in development --- pages/api/applications.ts | 15 +++------------ pages/api/event/[id]/apply.ts | 19 ++++--------------- pages/api/event/[id]/submitted-application.ts | 11 +++-------- 3 files changed, 10 insertions(+), 35 deletions(-) diff --git a/pages/api/applications.ts b/pages/api/applications.ts index f3e0887..5abf141 100644 --- a/pages/api/applications.ts +++ b/pages/api/applications.ts @@ -8,20 +8,15 @@ import ApplicationResponse from 'bookem-shared/src/models/ApplicationResponse'; import VolunteerEvents from 'bookem-shared/src/models/VolunteerEvents'; import { authOptions } from '@/pages/api/auth/[...nextauth]'; import { getServerSession } from 'next-auth'; +import { makeSessionForAPITest } from '@/utils/api-testing'; export default async function handler( req: NextApiRequest, res: NextApiResponse ) { // Get session user - const session = await getServerSession(req, res, authOptions); - // for API testing only - // TODO extract this logic - // const session = { - // user: { - // id: '60f3f5f8f0f6f6f6f6f6f6f6', - // }, - // }; + const session = + (await getServerSession(req, res, authOptions)) || makeSessionForAPITest(); // Get request parameter const { @@ -46,10 +41,6 @@ export default async function handler( userId: session.user.id, }).populate('eventId'); - // const appliedEvents = applicationResponses.map((applicationResponse) => { - // return applicationResponse.eventId; - // }; - return res.status(200).json({ message: applicationResponses }); } catch (error) { console.error(error); diff --git a/pages/api/event/[id]/apply.ts b/pages/api/event/[id]/apply.ts index 6484b4c..2619810 100644 --- a/pages/api/event/[id]/apply.ts +++ b/pages/api/event/[id]/apply.ts @@ -7,20 +7,15 @@ import { ApplicationResponseData } from 'bookem-shared/src/types/database'; import ApplicationResponse from 'bookem-shared/src/models/ApplicationResponse'; import { authOptions } from '@/pages/api/auth/[...nextauth]'; import { getServerSession } from 'next-auth'; +import { makeSessionForAPITest } from '@/utils/api-testing'; export default async function handler( req: NextApiRequest, res: NextApiResponse ) { // Get session user - const session = await getServerSession(req, res, authOptions); - // for API testing only - // TODO extract this logic - // const session = { - // user: { - // id: '60f3f5f8f0f6f6f6f6f6f6f6', - // }, - // } + let session = + (await getServerSession(req, res, authOptions)) || makeSessionForAPITest(); // Get request parameter const { @@ -39,7 +34,6 @@ export default async function handler( try { await dbConnect(); - // TODO - refactor common id checking to a common util if (!id) return res.status(400).json({ message: 'Missing id' }); // check if id is a valid mongoose id @@ -51,7 +45,6 @@ export default async function handler( eventId: id, }); - // TODO - refactor a common util for when something is not found if (!volunteerApplication) { return res .status(404) @@ -77,13 +70,9 @@ export default async function handler( try { await dbConnect(); - // start a try catch block to catch any errors in parsing the request body - // TODO maybe define a DTO for this - console.log(req.body); const response = req.body as ApplicationResponseData; const { answers } = response; - // Declare the following ops to be an atomic transaction const mongoSession = await mongoose.startSession(); await mongoSession.withTransaction(async () => { @@ -95,7 +84,7 @@ export default async function handler( answers, }); - await newResponse.save() + await newResponse.save(); // use the id of the saved response to update the volunteerApplications data await VolunteerApplications.updateOne( diff --git a/pages/api/event/[id]/submitted-application.ts b/pages/api/event/[id]/submitted-application.ts index 421f24f..afa53d8 100644 --- a/pages/api/event/[id]/submitted-application.ts +++ b/pages/api/event/[id]/submitted-application.ts @@ -4,19 +4,15 @@ import type { NextApiRequest, NextApiResponse } from 'next'; import ApplicationResponse from 'bookem-shared/src/models/ApplicationResponse'; import { getServerSession } from 'next-auth'; import { authOptions } from '@/pages/api/auth/[...nextauth]'; +import { makeSessionForAPITest } from '@/utils/api-testing'; export default async function handler( req: NextApiRequest, res: NextApiResponse ) { // Get session user - const session = await getServerSession(req, res, authOptions); - // for API testing only - // const session = { - // user: { - // id: '60f3f5f8f0f6f6f6f6f6f6f6', - // }, - // }; + let session = + (await getServerSession(req, res, authOptions)) || makeSessionForAPITest(); // Get request parameter const { @@ -35,7 +31,6 @@ export default async function handler( try { await dbConnect(); - // TODO - refactor common id checking to a common util if (!id) return res.status(400).json({ message: 'Missing id' }); // check if id is a valid mongoose id From b4599198596d1604839435e1e73bb116b5f2257c Mon Sep 17 00:00:00 2001 From: David Huang Date: Sat, 10 Feb 2024 16:34:36 -0600 Subject: [PATCH 5/9] return applied events in applications API not reponses --- pages/api/applications.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/pages/api/applications.ts b/pages/api/applications.ts index 5abf141..297c02d 100644 --- a/pages/api/applications.ts +++ b/pages/api/applications.ts @@ -40,8 +40,13 @@ export default async function handler( const applicationResponses = await ApplicationResponse.find({ userId: session.user.id, }).populate('eventId'); + + // get all the events that the user has applied to + const events = applicationResponses.map((response) => { + return response.eventId; + }); - return res.status(200).json({ message: applicationResponses }); + return res.status(200).json({ message: events }); } catch (error) { console.error(error); res.status(500).json({ message: error.message }); From 1b979b6835a7b5f2cd886fb6c159c647ae3be441 Mon Sep 17 00:00:00 2001 From: David Huang Date: Sat, 10 Feb 2024 16:39:25 -0600 Subject: [PATCH 6/9] format document --- pages/api/applications.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pages/api/applications.ts b/pages/api/applications.ts index 297c02d..861b15d 100644 --- a/pages/api/applications.ts +++ b/pages/api/applications.ts @@ -40,9 +40,9 @@ export default async function handler( const applicationResponses = await ApplicationResponse.find({ userId: session.user.id, }).populate('eventId'); - + // get all the events that the user has applied to - const events = applicationResponses.map((response) => { + const events = applicationResponses.map(response => { return response.eventId; }); From d687a57946ee26ef2abb7732485e806bb51da6d0 Mon Sep 17 00:00:00 2001 From: David Huang Date: Sat, 10 Feb 2024 16:42:37 -0600 Subject: [PATCH 7/9] add types --- pages/api/applications.ts | 2 +- pages/api/event/[id]/submitted-application.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pages/api/applications.ts b/pages/api/applications.ts index 861b15d..88192d9 100644 --- a/pages/api/applications.ts +++ b/pages/api/applications.ts @@ -47,7 +47,7 @@ export default async function handler( }); return res.status(200).json({ message: events }); - } catch (error) { + } catch (error: any) { console.error(error); res.status(500).json({ message: error.message }); } diff --git a/pages/api/event/[id]/submitted-application.ts b/pages/api/event/[id]/submitted-application.ts index afa53d8..c00ab06 100644 --- a/pages/api/event/[id]/submitted-application.ts +++ b/pages/api/event/[id]/submitted-application.ts @@ -52,7 +52,7 @@ export default async function handler( } return res.status(200).json({ message: volunteerApplication }); - } catch (error) { + } catch (error: any) { console.error(error); res.status(500).json({ message: error }); } From 750ae5dffd4bb09a5e603ca704b8edd21cc82655 Mon Sep 17 00:00:00 2001 From: David Huang Date: Sun, 11 Feb 2024 11:33:56 -0600 Subject: [PATCH 8/9] try reformat the config to pass Cypress E2E test try to fix: Error: [next-auth][error][NO_SECRET] https://next-auth.js.org/errors#no_secret --- middleware.ts | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/middleware.ts b/middleware.ts index fa70add..986cd6d 100644 --- a/middleware.ts +++ b/middleware.ts @@ -12,14 +12,12 @@ export { default } from 'next-auth/middleware'; * Configure which api routes to authenticate */ // when in development, export an empty config -export const config = testingAPI - ? {} // Don't use authentication for tests - : { - // match all routes in /api except for the route /api/users/create - // the ?!(...) means match everything except for (...) - // adapted from: https://nextjs.org/docs/messages/invalid-route-source - matcher: ['/api/((?!users/create).*)'], - }; +export const config = { + // match all routes in /api except for the route /api/users/create + // the ?!(...) means match everything except for (...) + // adapted from: https://nextjs.org/docs/messages/invalid-route-source + matcher: testingAPI ? [] : ['/api/((?!users/create).*)'], +}; function middleware() {} // when in development, export the empty middleware function From c3942369e52992f0dbc8c402170a88c943f9a078 Mon Sep 17 00:00:00 2001 From: David Huang Date: Sun, 11 Feb 2024 11:46:44 -0600 Subject: [PATCH 9/9] try to fix again --- middleware.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/middleware.ts b/middleware.ts index 986cd6d..a338410 100644 --- a/middleware.ts +++ b/middleware.ts @@ -11,12 +11,11 @@ export { default } from 'next-auth/middleware'; /** * Configure which api routes to authenticate */ -// when in development, export an empty config export const config = { // match all routes in /api except for the route /api/users/create // the ?!(...) means match everything except for (...) // adapted from: https://nextjs.org/docs/messages/invalid-route-source - matcher: testingAPI ? [] : ['/api/((?!users/create).*)'], + matcher: ['/api/((?!users/create).*)'], }; function middleware() {}