diff --git a/src/app.ts b/src/app.ts index 06c9cf2..480b95a 100644 --- a/src/app.ts +++ b/src/app.ts @@ -21,6 +21,7 @@ import s3Router from "./services/s3/s3-router"; import statsRouter from "./services/stats/stats-router"; import subscriptionRouter from "./services/subscription/subscription-router"; import speakersRouter from "./services/speakers/speakers-router"; +import puzzlebangRouter from "./services/puzzlebang/puzzlebang-router"; AWS.config.update({ region: Config.S3_REGION, @@ -49,6 +50,7 @@ app.use("/auth", databaseMiddleware, authRouter); app.use("/checkin", databaseMiddleware, checkinRouter); app.use("/events", databaseMiddleware, eventsRouter); app.use("/notifications", databaseMiddleware, notificationsRouter); +app.use("/puzzlebang", databaseMiddleware, puzzlebangRouter); app.use("/registration", databaseMiddleware, registrationRouter); app.use("/s3", databaseMiddleware, s3Router); app.use("/stats", databaseMiddleware, statsRouter); diff --git a/src/database.ts b/src/database.ts index 263cf74..28fc12e 100644 --- a/src/database.ts +++ b/src/database.ts @@ -1,11 +1,7 @@ import mongoose, { Schema, Document } from "mongoose"; -import { - AttendeeSchema, - AttendeeValidator, -} from "./services/attendee/attendee-schema"; import { AttendeeAttendanceSchema, - AttendeeAttendanceValidator, + AttendeeSchema, } from "./services/attendee/attendee-schema"; import { EventSchema, @@ -80,11 +76,10 @@ export const Database = { EventAttendanceSchema, EventAttendanceValidator ), - ATTENDEE: initializeModel("attendee", AttendeeSchema, AttendeeValidator), - ATTENDEE_ATTENDANCE: initializeModel( + ATTENDEE: mongoose.model("attendee", AttendeeSchema), + ATTENDEE_ATTENDANCE: mongoose.model( "attendee_attendance", - AttendeeAttendanceSchema, - AttendeeAttendanceValidator + AttendeeAttendanceSchema ), SUBSCRIPTIONS: initializeModel( "subscriptions", diff --git a/src/middleware/role-checker.ts b/src/middleware/role-checker.ts index cdd58ab..0febf9a 100644 --- a/src/middleware/role-checker.ts +++ b/src/middleware/role-checker.ts @@ -48,6 +48,13 @@ export default function RoleChecker( return next(); } + // PuzzleBang JWT can access puzzlebang endpoints + if (requiredRoles.includes(Role.Enum.PUZZLEBANG)) { + if (userRoles.includes(Role.Enum.PUZZLEBANG)) { + return next(); + } + } + // Corporate role can access corporate only endpoints if (requiredRoles.includes(Role.Enum.CORPORATE)) { if (userRoles.includes(Role.Enum.CORPORATE)) { diff --git a/src/services/attendee/attendee-router.ts b/src/services/attendee/attendee-router.ts index bb3a140..d90ea39 100644 --- a/src/services/attendee/attendee-router.ts +++ b/src/services/attendee/attendee-router.ts @@ -1,14 +1,14 @@ import { Router } from "express"; import { StatusCodes } from "http-status-codes"; -import { AttendeeValidator, EventIdValidator } from "./attendee-schema"; +import { + AttendeeCreateValidator, + EventIdValidator, +} from "./attendee-validators"; import { Database } from "../../database"; import RoleChecker from "../../middleware/role-checker"; import { Role } from "../auth/auth-models"; -import dotenv from "dotenv"; import { generateQrHash } from "../checkin/checkin-utils"; -dotenv.config(); - const attendeeRouter = Router(); // Favorite an event for an attendee @@ -98,7 +98,7 @@ attendeeRouter.get( // Create a new attendee attendeeRouter.post("/", async (req, res, next) => { try { - const attendeeData = AttendeeValidator.parse(req.body); + const attendeeData = AttendeeCreateValidator.parse(req.body); const attendee = new Database.ATTENDEE(attendeeData); await attendee.save(); diff --git a/src/services/attendee/attendee-schema.ts b/src/services/attendee/attendee-schema.ts index bd10f88..247d94f 100644 --- a/src/services/attendee/attendee-schema.ts +++ b/src/services/attendee/attendee-schema.ts @@ -1,37 +1,4 @@ import { Schema } from "mongoose"; -import { z } from "zod"; - -// Zod schema for attendee -export const AttendeeValidator = z.object({ - userId: z.string(), - name: z.string(), - email: z.string().email(), - events: z.array(z.string()).default([]), - dietaryRestrictions: z.string().array(), - allergies: z.string().array(), - hasCheckedIn: z.boolean().default(false), - points: z.number().min(0).default(0), - foodWave: z.number().int().min(0).default(0), - hasPriority: z - .object({ - Mon: z.boolean().default(false), - Tue: z.boolean().default(false), - Wed: z.boolean().default(false), - Thu: z.boolean().default(false), - Fri: z.boolean().default(false), - Sat: z.boolean().default(false), - Sun: z.boolean().default(false), - }) - .default({ - Mon: false, - Tue: false, - Wed: false, - Thu: false, - Fri: false, - Sat: false, - Sun: false, - }), -}); // Mongoose schema for attendee export const AttendeeSchema = new Schema({ @@ -68,6 +35,7 @@ export const AttendeeSchema = new Schema({ }, }, favorites: [{ type: String }], + puzzlesCompleted: [{ type: String, default: [] }], }); export const AttendeeAttendanceSchema = new Schema({ @@ -78,15 +46,3 @@ export const AttendeeAttendanceSchema = new Schema({ }, eventsAttended: [{ type: String, ref: "Event", required: true }], }); - -export const AttendeeAttendanceValidator = z.object({ - userId: z.string(), - eventsAttended: z.array(z.string()), -}); - -export const EventIdValidator = z.object({ - eventId: z.string().uuid(), -}); - -// Partial schema for attendee filter -export const PartialAttendeeValidator = AttendeeValidator.partial(); diff --git a/src/services/attendee/attendee-validators.ts b/src/services/attendee/attendee-validators.ts new file mode 100644 index 0000000..fe3ea9f --- /dev/null +++ b/src/services/attendee/attendee-validators.ts @@ -0,0 +1,14 @@ +import { z } from "zod"; + +// Zod schema for attendee +export const AttendeeCreateValidator = z.object({ + userId: z.string(), + name: z.string(), + email: z.string().email(), + dietaryRestrictions: z.string().array(), + allergies: z.string().array(), +}); + +export const EventIdValidator = z.object({ + eventId: z.string().uuid(), +}); diff --git a/src/services/auth/auth-models.ts b/src/services/auth/auth-models.ts index 335126f..f2fc551 100644 --- a/src/services/auth/auth-models.ts +++ b/src/services/auth/auth-models.ts @@ -1,6 +1,12 @@ import { z } from "zod"; -export const Role = z.enum(["USER", "STAFF", "ADMIN", "CORPORATE"]); +export const Role = z.enum([ + "USER", + "STAFF", + "ADMIN", + "CORPORATE", + "PUZZLEBANG", +]); export const JwtPayloadValidator = z.object({ userId: z.string(), diff --git a/src/services/puzzlebang/puzzlebang-router.ts b/src/services/puzzlebang/puzzlebang-router.ts new file mode 100644 index 0000000..a4f1a7b --- /dev/null +++ b/src/services/puzzlebang/puzzlebang-router.ts @@ -0,0 +1,40 @@ +import { Router } from "express"; +import { StatusCodes } from "http-status-codes"; +import { Database } from "../../database"; +import RoleChecker from "../../middleware/role-checker"; +import { Role } from "../auth/auth-models"; +import { PuzzlebangCompleteRequestValidator } from "./puzzlebang-validators"; + +const puzzlebangRouter = Router(); + +puzzlebangRouter.post( + "/", + RoleChecker([Role.Enum.PUZZLEBANG]), + async (req, res, next) => { + try { + const requestInfo = PuzzlebangCompleteRequestValidator.parse( + req.body + ); + + const attendeeData = await Database.ATTENDEE.findOneAndUpdate( + { email: requestInfo.email }, + { $addToSet: { puzzlesCompleted: requestInfo.puzzleId } }, + { new: false } + ); + + if (!attendeeData) { + return res.sendStatus(StatusCodes.NOT_FOUND); + } + + if (attendeeData.puzzlesCompleted.includes(requestInfo.puzzleId)) { + return res.sendStatus(StatusCodes.UNAUTHORIZED); + } + + return res.sendStatus(StatusCodes.OK); + } catch (error) { + next(error); + } + } +); + +export default puzzlebangRouter; diff --git a/src/services/puzzlebang/puzzlebang-validators.ts b/src/services/puzzlebang/puzzlebang-validators.ts new file mode 100644 index 0000000..3b862b9 --- /dev/null +++ b/src/services/puzzlebang/puzzlebang-validators.ts @@ -0,0 +1,7 @@ +import { z } from "zod"; + +export const PuzzlebangCompleteRequestValidator = z.object({ + // userId: z.string(), + email: z.string().email(), + puzzleId: z.string(), +}); diff --git a/src/services/registration/registration-router.ts b/src/services/registration/registration-router.ts index 5ddfd0b..7b89b3e 100644 --- a/src/services/registration/registration-router.ts +++ b/src/services/registration/registration-router.ts @@ -7,7 +7,7 @@ import { import { Database } from "../../database"; import RoleChecker from "../../middleware/role-checker"; import { Role } from "../auth/auth-models"; -import { AttendeeValidator } from "../attendee/attendee-schema"; +import { AttendeeCreateValidator } from "../attendee/attendee-validators"; import { registrationExists } from "./registration-utils"; const registrationRouter = Router(); @@ -79,7 +79,7 @@ registrationRouter.post("/submit", RoleChecker([]), async (req, res, next) => { { upsert: true } ); - const attendeeData = AttendeeValidator.parse(registrationData); + const attendeeData = AttendeeCreateValidator.parse(registrationData); await Database.ATTENDEE.findOneAndUpdate( { userId: payload.userId }, diff --git a/src/services/registration/registration-schema.ts b/src/services/registration/registration-schema.ts index 892b4b6..ac76ade 100644 --- a/src/services/registration/registration-schema.ts +++ b/src/services/registration/registration-schema.ts @@ -6,7 +6,7 @@ const RegistrationValidator = z.object({ userId: z.coerce.string().regex(/user[0-9]*/), name: z.string(), email: z.string().email(), - university: z.string().nonempty(), + university: z.string(), graduation: z.string().nullable().optional(), major: z.string().nullable().optional(), dietaryRestrictions: z.string().array(),