diff --git a/middleware.ts b/middleware.ts index 207a1bc..a338410 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 @@ -16,5 +18,8 @@ export const config = { 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/pages/api/applications.ts b/pages/api/applications.ts new file mode 100644 index 0000000..88192d9 --- /dev/null +++ b/pages/api/applications.ts @@ -0,0 +1,56 @@ +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'; +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)) || makeSessionForAPITest(); + + // 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'); + + // get all the events that the user has applied to + const events = applicationResponses.map(response => { + return response.eventId; + }); + + return res.status(200).json({ message: events }); + } catch (error: any) { + console.error(error); + res.status(500).json({ message: error.message }); + } + break; + } +} diff --git a/pages/api/event/[id]/apply.ts b/pages/api/event/[id]/apply.ts new file mode 100644 index 0000000..2619810 --- /dev/null +++ b/pages/api/event/[id]/apply.ts @@ -0,0 +1,108 @@ +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'; +import { makeSessionForAPITest } from '@/utils/api-testing'; + +export default async function handler( + req: NextApiRequest, + res: NextApiResponse +) { + // Get session user + let session = + (await getServerSession(req, res, authOptions)) || makeSessionForAPITest(); + + // 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(); + + 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, + }); + + 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(); + + 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..c00ab06 --- /dev/null +++ b/pages/api/event/[id]/submitted-application.ts @@ -0,0 +1,61 @@ +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]'; +import { makeSessionForAPITest } from '@/utils/api-testing'; + +export default async function handler( + req: NextApiRequest, + res: NextApiResponse +) { + // Get session user + let session = + (await getServerSession(req, res, authOptions)) || makeSessionForAPITest(); + + // 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(); + + 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: any) { + console.error(error); + res.status(500).json({ message: error }); + } + break; + } +} 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; +};