Skip to content
This repository was archived by the owner on Oct 14, 2025. It is now read-only.

Commit 2d9f93e

Browse files
connorffsyenkoakjadhav
authored
Add wallet QR code + update judge/mentor routes (#304)
* Digital IDs for Apple / Google Wallet (#300) * Add digital id download link * Add apple wallet button, improve id page * Add web version of QR code * Adjust styling slightly * Add Google Wallet integration * Add ID to dashboard (Admitted Stanford page) * Standardize naming * Adjust ID styling * Add ID to normal admitted page * Update Apple Wallet pass so it displays correctly on phones --------- Co-authored-by: Sabrina <59457991+syenko@users.noreply.github.com> * fix: update judge schema + interface * feat: create mentor schema + interface * feat: add judge + mentor routes * fix: change `hardwareList` from `string` to `string[]` * fix: use existing schemas + interfaces for judges and mentors * fix: add `dietaryRestrictions` to meal info * Add `tshirtSize` to check in info * Remove judging routes --------- Co-authored-by: Sabrina <59457991+syenko@users.noreply.github.com> Co-authored-by: Ameya Jadhav <jadhavameyak@gmail.com>
1 parent f606123 commit 2d9f93e

22 files changed

+4370
-418
lines changed

backend/index.ts

Lines changed: 36 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -60,12 +60,6 @@ import {
6060
} from "./routes/transportation_info";
6161
import { getUserResumes } from "./routes/user_resumes";
6262
import { importHacks } from "./routes/hacks/hacks_import";
63-
import {
64-
reviewNextHack,
65-
getJudgeLeaderboard,
66-
getJudgeStats,
67-
rateHack,
68-
} from "./routes/hacks/judging";
6963
import { getHackList, editHack } from "./routes/hacks/hacks_list";
7064
import { getJudgeList, editJudge } from "./routes/hacks/judges";
7165
import { getAnnouncements } from "./routes/announcements";
@@ -85,6 +79,7 @@ import {
8579
leaveTeam,
8680
getUserTeamData,
8781
} from "./routes/teams";
82+
import {getAppleId, getGoogleId} from "./routes/digital_id";
8883
import {
8984
uploadSponsorLogo,
9085
updateSponsor,
@@ -98,6 +93,20 @@ import {
9893
createAdmin
9994
} from "./routes/sponsors"
10095
import LiveNotificationsService from "./services/live_notifications";
96+
import {
97+
getJudgeForms,
98+
getJudgeMealInfo,
99+
setJudgeMealInfo,
100+
getJudgeCheckInInfo,
101+
setJudgeCheckInInfo
102+
} from "./routes/judge_forms";
103+
import {
104+
getMentorForms,
105+
getMentorMealInfo,
106+
setMentorMealInfo,
107+
getMentorCheckInInfo,
108+
setMentorCheckInInfo
109+
} from "./routes/mentor_forms";
101110

102111
// Start the notification service
103112
const notificationService = new LiveNotificationsService();
@@ -207,6 +216,14 @@ authenticatedRoute.get("/users/:userId/forms/workshop_info", getWorkshopList);
207216
authenticatedRoute.put("/users/:userId/forms/workshop_info", setWorkshopList);
208217
authenticatedRoute.put("/users/:userId/forms/add_teammate", addTeammate);
209218
authenticatedRoute.put("/users/:userId/forms/remove_teammate", removeTeammate);
219+
authenticatedRoute.get(
220+
"/users/:userId/:fullName/getAppleID",
221+
getAppleId
222+
);
223+
authenticatedRoute.get(
224+
"/users/:userId/:fullName/getGoogleID",
225+
getGoogleId
226+
);
210227

211228
// What permission should this one be?
212229
authenticatedRoute.get("/users/:userId/status", getApplicationStatus);
@@ -275,15 +292,19 @@ authenticatedRoute.get(
275292
reviewNextApplication
276293
);
277294

278-
// Judging routes:
279-
authenticatedRoute.get(
280-
"/judging/leaderboard",
281-
[judgeRoute],
282-
getJudgeLeaderboard
283-
);
284-
authenticatedRoute.get("/judging/stats", [judgeRoute], getJudgeStats);
285-
authenticatedRoute.post("/judging/rate", [judgeRoute], rateHack);
286-
authenticatedRoute.get("/judging/next_hack", [judgeRoute], reviewNextHack);
295+
// Judge form routes
296+
authenticatedRoute.get("/judges/:userId/forms", getJudgeForms);
297+
authenticatedRoute.get("/judges/:userId/forms/meal_info", getJudgeMealInfo);
298+
authenticatedRoute.put("/judges/:userId/forms/meal_info", setJudgeMealInfo);
299+
authenticatedRoute.get("/judges/:userId/forms/check_in_info", getJudgeCheckInInfo);
300+
authenticatedRoute.put("/judges/:userId/forms/check_in_info", setJudgeCheckInInfo);
301+
302+
// Mentor form routes
303+
authenticatedRoute.get("/mentors/:userId/forms", getMentorForms);
304+
authenticatedRoute.get("/mentors/:userId/forms/meal_info", getMentorMealInfo);
305+
authenticatedRoute.put("/mentors/:userId/forms/meal_info", setMentorMealInfo);
306+
authenticatedRoute.get("/mentors/:userId/forms/check_in_info", getMentorCheckInInfo);
307+
authenticatedRoute.put("/mentors/:userId/forms/check_in_info", setMentorCheckInInfo);
287308

288309
app.use("/api", apiRouter);
289310

backend/models/Application.d.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ export interface ITransportationInfo {
5555

5656
export interface IMealInfo {
5757
usedMeals?: [String];
58+
dietaryRestrictions?: String;
5859
}
5960

6061
export interface ITeamInfo {
@@ -69,6 +70,7 @@ export interface ICheckInInfo {
6970
checkedIn?: Boolean;
7071
checkedInBy?: String;
7172
checkedInAt?: Date;
73+
tshirtSize?: String;
7274
}
7375

7476
export interface IMeetInfo {
@@ -106,7 +108,7 @@ export interface IHardwareInfo {
106108
pendingReturn?: Boolean;
107109
returnedAt?: Date;
108110
returnedBy?: String;
109-
hardwareList?: String;
111+
hardwareList?: String[];
110112
}
111113

112114
export interface IApplication extends Document {

backend/models/Judge.ts

Lines changed: 30 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,42 @@
11
import mongoose from "mongoose";
22
import { Model, Schema } from "mongoose";
3+
import checkInSchema from "./checkInSchema";
4+
import mealInfoSchema from "./mealInfoSchema";
5+
import { ICheckInInfo } from "./Application.d";
6+
import { IMealInfo } from "./Application.d";
37

48
interface IJudge extends mongoose.Document {
59
_id: string,
6-
email: string,
7-
verticals: string[],
8-
floor: number
10+
year: string,
11+
forms: {
12+
application_info: {
13+
first_name: string,
14+
last_name: string
15+
},
16+
check_in_info: ICheckInInfo,
17+
meal_info: IMealInfo
18+
},
19+
user: {
20+
id: string,
21+
email: string
22+
}
923
}
1024

1125
const judgeSchema: Schema = new mongoose.Schema({
1226
"_id": String,
13-
"email": String,
14-
"verticals": [String],
15-
"floor": Number
27+
"year": String,
28+
"forms": {
29+
"application_info": {
30+
"first_name": String,
31+
"last_name": String
32+
},
33+
"check_in_info": checkInSchema,
34+
"meal_info": mealInfoSchema
35+
},
36+
"user": {
37+
"id": String,
38+
"email": String
39+
}
1640
});
1741

1842
const model: Model<IJudge> = mongoose.model("Judge", judgeSchema);

backend/models/Mentor.ts

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
import mongoose from "mongoose";
2+
import { Model, Schema } from "mongoose";
3+
import { ICheckInInfo } from "./Application.d";
4+
import { IMealInfo } from "./Application.d";
5+
import checkInSchema from "./checkInSchema";
6+
import mealInfoSchema from "./mealInfoSchema";
7+
8+
interface IMentor extends mongoose.Document {
9+
_id: string,
10+
year: string,
11+
forms: {
12+
application_info: {
13+
first_name: string,
14+
last_name: string
15+
},
16+
check_in_info: ICheckInInfo,
17+
meal_info: IMealInfo
18+
},
19+
user: {
20+
id: string,
21+
email: string
22+
}
23+
}
24+
25+
const mentorSchema: Schema = new mongoose.Schema({
26+
"_id": String,
27+
"year": String,
28+
"forms": {
29+
"application_info": {
30+
"first_name": String,
31+
"last_name": String
32+
},
33+
"check_in_info": checkInSchema,
34+
"meal_info": mealInfoSchema
35+
},
36+
"user": {
37+
"id": String,
38+
"email": String
39+
}
40+
});
41+
42+
const model: Model<IMentor> = mongoose.model("Mentor", mentorSchema);
43+
export default model;

backend/models/checkInSchema.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@ import { Schema } from "mongoose";
44
const checkInSchema: Schema = new mongoose.Schema({
55
checkedIn: Boolean,
66
checkedInBy: String,
7-
checkedInAt: Date
7+
checkedInAt: Date,
8+
tshirtSize: String
89
}, { _id: false });
910

1011
export default checkInSchema;

backend/models/hardwareInfoSchema.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ const hardwareInfoSchema: Schema = new mongoose.Schema({
77
pendingReturn: Boolean,
88
returnedAt: Date,
99
returnedBy: String,
10-
hardwareList: String
10+
hardwareList: [String]
1111
}, { _id: false });
1212

1313
export default hardwareInfoSchema;

backend/models/mealInfoSchema.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { Schema } from "mongoose";
33

44
const mealInfoSchema: Schema = new mongoose.Schema({
55
usedMeals: [String],
6+
dietaryRestrictions: String
67
}, { _id: false });
78

89
export default mealInfoSchema;

backend/routes/common.ts

Lines changed: 119 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,12 @@ import Application from "../models/Application";
22
import { IApplication } from "../models/Application.d";
33
import { Request, Response } from "express";
44
import { CognitoUser } from "../models/cognitoUser";
5-
import { STATUS, TYPE, TRANSPORTATION_STATUS } from "../constants";
6-
import { isEqual } from "lodash";
5+
import { STATUS } from "../constants";
76
import { injectDynamicApplicationContent } from "../utils/file_plugin";
87
import { ServerResponse } from "http";
98
import { Model } from "mongoose";
10-
import { prepopulateMeetInfo } from "./meet_info";
9+
import Judge from "../models/Judge";
10+
import Mentor from "../models/Mentor";
1111

1212
export function getDeadline(type) {
1313
switch (type) {
@@ -236,3 +236,119 @@ export function getGenericList(req: Request, res: Response, Model: Model<any>) {
236236
return res.status(400).json(err);
237237
});
238238
}
239+
240+
export async function getJudgeAttribute(
241+
req: Request,
242+
res: Response,
243+
getter: (e: any) => any
244+
) {
245+
// prevent non-admins from viewing judges
246+
const groups = res.locals.user["cognito:groups"] || [];
247+
const canView = groups.includes("admin") || groups.includes("organizers_current");
248+
if (!canView && res.locals.user.sub !== req.params.userId) {
249+
return res.status(403).send("You do not have access to view this judge");
250+
}
251+
252+
let judge = await Judge.findOne(
253+
{ "user.id": req.params.userId },
254+
{ __v: 0 },
255+
{ "treehacks:groups": res.locals.user["cognito:groups"] }
256+
);
257+
258+
if (!judge) {
259+
res.status(404).send("Judge not found.");
260+
} else {
261+
res.status(200).send(getter(judge));
262+
}
263+
}
264+
265+
export async function setJudgeAttribute(
266+
req: Request,
267+
res: Response,
268+
setter: (e: any) => any,
269+
getter: (e: any) => any = (e) => e
270+
) {
271+
// prevent non-admins from editing other judges
272+
const groups = res.locals.user["cognito:groups"] || [];
273+
const canEdit = groups.includes("admin") || groups.includes("organizers_current");
274+
if (!canEdit && res.locals.user.sub !== req.params.userId) {
275+
return res.status(403).send("You do not have access to edit this judge");
276+
}
277+
278+
const judge = await Judge.findOne(
279+
{ "user.id": req.params.userId },
280+
{ __v: 0 }
281+
);
282+
283+
if (!judge) {
284+
res.status(404).send("Judge not found.");
285+
return;
286+
}
287+
288+
let setResponse = setter(judge);
289+
if (setResponse instanceof ServerResponse) {
290+
return;
291+
}
292+
293+
await judge.save();
294+
295+
await getJudgeAttribute(req, res, getter);
296+
}
297+
298+
export async function getMentorAttribute(
299+
req: Request,
300+
res: Response,
301+
getter: (e: any) => any
302+
) {
303+
// prevent non-admins from viewing mentors
304+
const groups = res.locals.user["cognito:groups"] || [];
305+
const canView = groups.includes("admin") || groups.includes("organizers_current");
306+
if (!canView && res.locals.user.sub !== req.params.userId) {
307+
return res.status(403).send("You do not have access to view this mentor");
308+
}
309+
310+
let mentor = await Mentor.findOne(
311+
{ "user.id": req.params.userId },
312+
{ __v: 0 },
313+
{ "treehacks:groups": res.locals.user["cognito:groups"] }
314+
);
315+
316+
if (!mentor) {
317+
res.status(404).send("Mentor not found.");
318+
} else {
319+
res.status(200).send(getter(mentor));
320+
}
321+
}
322+
323+
export async function setMentorAttribute(
324+
req: Request,
325+
res: Response,
326+
setter: (e: any) => any,
327+
getter: (e: any) => any = (e) => e
328+
) {
329+
// prevent non-admins from editing other mentors
330+
const groups = res.locals.user["cognito:groups"] || [];
331+
const canEdit = groups.includes("admin") || groups.includes("organizers_current");
332+
if (!canEdit && res.locals.user.sub !== req.params.userId) {
333+
return res.status(403).send("You do not have access to edit this mentor");
334+
}
335+
336+
const mentor = await Mentor.findOne(
337+
{ "user.id": req.params.userId },
338+
{ __v: 0 }
339+
);
340+
341+
if (!mentor) {
342+
res.status(404).send("Mentor not found.");
343+
return;
344+
}
345+
346+
let setResponse = setter(mentor);
347+
if (setResponse instanceof ServerResponse) {
348+
return;
349+
}
350+
351+
await mentor.save();
352+
353+
await getMentorAttribute(req, res, getter);
354+
}

0 commit comments

Comments
 (0)