-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge branch 'main' into dev/jacob/s3-flexible
- Loading branch information
Showing
13 changed files
with
412 additions
and
257 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
import { Router } from "express"; | ||
import { StatusCodes } from "http-status-codes"; | ||
import { ScanValidator } from "./checkin-schema"; | ||
import RoleChecker from "../../middleware/role-checker"; | ||
import { Role } from "../auth/auth-models"; | ||
import { validateQrHash } from "./checkin-utils"; | ||
import { checkInUserToEvent } from "./checkin-utils"; | ||
|
||
const checkinRouter = Router(); | ||
|
||
checkinRouter.post( | ||
"/scan/staff", | ||
RoleChecker([Role.Enum.ADMIN]), | ||
async (req, res, next) => { | ||
try { | ||
const { eventId, qrCode } = ScanValidator.parse(req.body); | ||
console.log("Event ID:", eventId); | ||
|
||
const { userId, expTime } = validateQrHash(qrCode); | ||
|
||
if (Date.now() / 1000 > expTime) { | ||
return res | ||
.status(StatusCodes.UNAUTHORIZED) | ||
.json({ error: "QR code has expired" }); | ||
} | ||
|
||
await checkInUserToEvent(eventId, userId, true); | ||
|
||
return res.status(StatusCodes.OK).json(userId); | ||
} catch (error) { | ||
next(error); | ||
} | ||
} | ||
); | ||
|
||
export default checkinRouter; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
import { z } from "zod"; | ||
|
||
const ScanValidator = z.object({ | ||
eventId: z.string(), | ||
qrCode: z.string(), | ||
}); | ||
|
||
export { ScanValidator }; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,107 @@ | ||
import { Database } from "../../database"; | ||
import crypto from "crypto"; | ||
import { Config } from "../../config"; | ||
|
||
function getCurrentDay() { | ||
const currDate = new Date(); | ||
const dayString = new Intl.DateTimeFormat("en-US", { | ||
timeZone: "America/Chicago", | ||
weekday: "short", | ||
}).format(currDate); | ||
return dayString; | ||
} | ||
|
||
async function checkEventAndAttendeeExist(eventId: string, userId: string) { | ||
const [event, attendee] = await Promise.all([ | ||
Database.EVENTS.exists({ eventId }), | ||
Database.ATTENDEE.exists({ userId }), | ||
]); | ||
|
||
if (!event || !attendee) { | ||
throw new Error("Event or Attendee not found"); | ||
} | ||
|
||
return Promise.resolve(); | ||
} | ||
|
||
async function checkForDuplicateAttendance(eventId: string, userId: string) { | ||
const [isRepeatEvent, isRepeatAttendee] = await Promise.all([ | ||
Database.EVENTS_ATTENDANCE.exists({ eventId, attendees: userId }), | ||
Database.ATTENDEE_ATTENDANCE.exists({ | ||
userId, | ||
eventsAttended: eventId, | ||
}), | ||
]); | ||
|
||
if (isRepeatEvent || isRepeatAttendee) { | ||
throw new Error("Is Duplicate"); | ||
} | ||
} | ||
|
||
// Update attendee priority for the current day | ||
async function updateAttendeePriority(userId: string) { | ||
const day = getCurrentDay(); | ||
await Database.ATTENDEE.findOneAndUpdate( | ||
{ userId }, | ||
{ $set: { [`hasPriority.${day}`]: true } } | ||
); | ||
} | ||
|
||
async function updateAttendanceRecords(eventId: string, userId: string) { | ||
await Promise.all([ | ||
Database.EVENTS_ATTENDANCE.findOneAndUpdate( | ||
{ eventId }, | ||
{ $addToSet: { attendees: userId } }, | ||
{ new: true, upsert: true } | ||
), | ||
Database.ATTENDEE_ATTENDANCE.findOneAndUpdate( | ||
{ userId }, | ||
{ $addToSet: { eventsAttended: eventId } }, | ||
{ new: true, upsert: true } | ||
), | ||
]); | ||
} | ||
|
||
export async function checkInUserToEvent( | ||
eventId: string, | ||
userId: string, | ||
isCheckin: boolean = false | ||
) { | ||
await checkEventAndAttendeeExist(eventId, userId); | ||
await checkForDuplicateAttendance(eventId, userId); | ||
|
||
if (!isCheckin) { | ||
await updateAttendeePriority(userId); | ||
} | ||
|
||
await updateAttendanceRecords(eventId, userId); | ||
} | ||
|
||
export function generateQrHash(userId: string, expTime: number) { | ||
let hashStr = userId + "#" + expTime; | ||
const hashIterations = Config.QR_HASH_ITERATIONS; | ||
const hashSecret = Config.QR_HASH_SECRET; | ||
|
||
const hmac = crypto.createHmac("sha256", hashSecret); | ||
hashStr = hmac.update(hashStr).digest("hex"); | ||
|
||
for (let i = 0; i < hashIterations; i++) { | ||
const hash = crypto.createHash("sha256"); | ||
hashStr = hash.update(hashSecret + "#" + hashStr).digest("hex"); | ||
} | ||
|
||
return `${hashStr}#${expTime}#${userId}`; | ||
} | ||
|
||
export function validateQrHash(qrCode: string) { | ||
const parts = qrCode.split("#"); | ||
const userId = parts[2]; | ||
const expTime = parseInt(parts[1]); | ||
const generatedHash = generateQrHash(userId, expTime); | ||
|
||
if (generatedHash.split("#")[0] !== parts[0]) { | ||
throw new Error("Invalid QR code"); | ||
} | ||
|
||
return { userId, expTime }; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
import { z } from "zod"; | ||
|
||
const BatchResumeDownloadValidator = z.object({ | ||
userIds: z.string().array(), | ||
}); | ||
|
||
export default BatchResumeDownloadValidator; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.