Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
440a650
Create checkin form submit endpoint
Mar 24, 2025
c8b1077
Finish hacker checkin form submit endpoint
May 3, 2025
d12fdac
Change checkin form spreadsheet
Sep 26, 2025
2cc2cee
api route for checking in teams
Oct 18, 2025
36f2517
Add devpost and discord tag field validation
Oct 31, 2025
6af09f6
Update db with devpost url
Nov 2, 2025
2d5bd65
Add url validation:
Nov 3, 2025
9508860
added team checkin form status to settings
Jan 7, 2026
b82b20c
remove workshops
Jan 13, 2026
2229ad1
more validation
Jan 14, 2026
f7d8d9a
test
Jan 14, 2026
794c97b
Updated email template dates and header logo
janekhuong Oct 24, 2025
700151f
Added hackboard role support
MikaVohl Nov 8, 2025
f4713e7
Revert "Added hackboard role support"
MikaVohl Nov 8, 2025
b31b2db
Feat/auto emails (#949)
janekhuong Nov 17, 2025
cdb74b1
Feat/create application review (#944)
JamieXiao Nov 18, 2025
98fa9ec
fixed merge issue with objectIDs
tektaxi Nov 18, 2025
f42363b
Added hackboard role support (#955)
MikaVohl Nov 18, 2025
743f70d
Updated account invitation email content
janekhuong Nov 18, 2025
e3b6f8d
Feat/assign reviewers (#959)
tektaxi Nov 20, 2025
878c83f
Added script for sending interest form emails (#954)
janekhuong Nov 20, 2025
d38d64c
quick fixes to assignReviewers (#961)
JamieXiao Nov 21, 2025
ea0f4c0
cut off to dec 1 11:59 pm
JamieXiao Dec 2, 2025
73c10fd
update cutoff
JamieXiao Dec 3, 2025
c7912e6
date fix??
tektaxi Dec 3, 2025
3502b5b
add hacker id to team api response schema
Nov 22, 2025
4a28bb4
new acceptance email
janekhuong Dec 14, 2025
3bbd1be
updated Marriott booking deadline
janekhuong Dec 15, 2025
9b150a2
round 2 acceptance email template
janekhuong Dec 28, 2025
4c28dbe
updated rejection email
janekhuong Jan 7, 2026
1e16280
updated wording
janekhuong Jan 8, 2026
a32e7a4
added volunteer link
janekhuong Jan 8, 2026
580a345
added applied status to validateStatus function
janekhuong Jan 9, 2026
4860145
overrode email template to send declined emails to hackers with appli…
janekhuong Jan 11, 2026
b4d7063
Finish hacker checkin form submit endpoint
May 3, 2025
01c5b76
format sheet name
Jan 15, 2026
4422f5c
remove checkin router dup
Jan 15, 2026
626da38
fix
Jan 16, 2026
d100111
Merge branch 'dev' into feat/team-checkin-submit
MubeenMohammed Jan 17, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions app.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ const searchRouter = require("./routes/api/search");
const settingsRouter = require("./routes/api/settings");
const volunteerRouter = require("./routes/api/volunteer");
const roleRouter = require("./routes/api/role");
const checkinRouter = require("./routes/api/checkin");
const emailsRouter = require("./routes/api/emails");

const app = express();
Expand Down Expand Up @@ -115,8 +116,12 @@ settingsRouter.activate(apiRouter);
Services.log.info("Settings router activated");
roleRouter.activate(apiRouter);
Services.log.info("Role router activated");
checkinRouter.activate(apiRouter);
Services.log.info("Checkin router activated");
emailsRouter.activate(apiRouter);
Services.log.info("Emails router activated");
checkinRouter.activate(apiRouter);
Services.log.info("Checkin router activated");

app.use("/", indexRouter);
app.use("/api", apiRouter);
Expand Down
37 changes: 37 additions & 0 deletions constants/checkin-options.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
// Team checkin form constants for Mchacks 13
"use strict";

const PRIZE_CATEGORIES = [
"Best Beginner Hack",
"Best Design",
"Chaotic Evil",
"Best Use of AI or AI Agents",
];

const SPONSOR_CHALLENGES = [
"HoloRay",
"Athena AI",
"Gumloop",
"National Bank",
"Tail'ed",
"BassiliChat AI",
"Dobson Center",
"Desjardins",
"NOVA",
"CSUS"
];

const MLH_CHALLENGES = [
"Best Use of ElevenLabs",
"Best Use of Gemini API",
"Best Use of MongoDB Atlas",
"Best Use of DigitalOcean",
"Best Use of Solana",
"Best Use of Auth0"
];

module.exports = {
PRIZE_CATEGORIES,
SPONSOR_CHALLENGES,
MLH_CHALLENGES
};
101 changes: 101 additions & 0 deletions controllers/checkin.controller.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
"use strict";

const Services = {
Sheets: require('../services/sheets.service'),
Hacker: require('../services/hacker.service'),
Team: require('../services/team.service'),
Account: require('../services/account.service')
};

/**
* @function submitCheckin
* @param {{body: {formData: Object}, user: {id: string}}} req
* @param {*} res
* @return {JSON} Success status
* @description Handles the check-in form submission and adds data to Google Sheets
* Automatically fetches team member emails from the logged-in user's team
*/
async function submitCheckin(req, res) {
try {
// Get logged-in hacker
const hacker = await Services.Hacker.findByAccountId(req.user.id);

if (!hacker) {
return res.status(404).json({
message: "Hacker not found",
data: {}
});
}

// Check hacker has a team
if (!hacker.teamId) {
return res.status(400).json({
message: "You must be part of a team to submit check-in",
data: {}
});
}

// Fetch team data
const team = await Services.Team.findById(hacker.teamId);

if (!team) {
return res.status(404).json({
message: "Team not found",
data: {}
});
}

// Fetch all team member emails
const teamMemberEmails = [];
for (const memberId of team.members) {
const memberHacker = await Services.Hacker.findById(memberId);
if (memberHacker) {
const memberAccount = await Services.Account.findById(memberHacker.accountId);
if (memberAccount) {
teamMemberEmails.push(memberAccount.email);
}
}
}

// Update team's devpostURL in the database if provided
if (req.body.formData.devpostLink) {
await Services.Team.updateOne(hacker.teamId, {
devpostURL: req.body.formData.devpostLink
});
}

// Prepare data for Google Sheets with team member emails
const teamIdString = team._id ? team._id.toString() : hacker.teamId.toString();

const checkinData = {
teamMember1: teamMemberEmails[0] || '',
teamMember2: teamMemberEmails[1] || '',
teamMember3: teamMemberEmails[2] || '',
teamMember4: teamMemberEmails[3] || '',
prizeCategories: req.body.formData.prizeCategories,
sponsorChallenges: req.body.formData.sponsorChallenges,
mlhChallenges: req.body.formData.mlhChallenges,
// workshopsAttended: req.body.formData.workshopsAttended,
discordTag: req.body.formData.discordTag,
devpostLink: req.body.formData.devpostLink,
teamId: teamIdString
};

await Services.Sheets.appendCheckinData(checkinData);

return res.status(200).json({
message: "Check-in data successfully submitted",
data: {}
});
} catch (error) {
console.error('Checkin submission error:', error);
return res.status(500).json({
message: "Error submitting check-in data",
data: {}
});
}
}

module.exports = {
submitCheckin
};
3 changes: 3 additions & 0 deletions cookies.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Netscape HTTP Cookie File
# https://curl.se/docs/http-cookies.html
# This file was generated by libcurl! Edit at your own risk.
31 changes: 31 additions & 0 deletions middlewares/settings.middleware.js
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,11 @@ async function updateSettings(req, res, next) {
* @description Confirms that openTime < closeTime < confirmTime
*/
function confirmValidPatch(req, res, next) {
if (!req.body.settingsDetails.openTime &&
!req.body.settingsDetails.closeTime &&
!req.body.settingsDetails.confirmTime) {
return next();
}
const openTime = new Date(req.body.settingsDetails.openTime);
const closeTime = new Date(req.body.settingsDetails.closeTime);
const confirmTime = new Date(req.body.settingsDetails.confirmTime);
Expand Down Expand Up @@ -126,9 +131,35 @@ async function confirmAppsOpen(req, res, next) {
}
}

/**
* @function confirmCheckinOpen
* @param {*} req
* @param {*} res
* @param {*} next
* @description Only succeeds if check-in is currently open
*/
async function confirmCheckinOpen(req, res, next) {
const settings = await Services.Settings.getSettings();
if (!settings) {
return next({
status: 500,
message: Constants.Error.GENERIC_500_MESSAGE
});
}
if (settings.checkinOpen) {
return next();
}

return next({
status: 403,
message: Constants.Error.SETTINGS_403_MESSAGE
});
}

module.exports = {
parsePatch: parsePatch,
confirmValidPatch: confirmValidPatch,
confirmCheckinOpen: Middleware.Util.asyncMiddleware(confirmCheckinOpen),
confirmAppsOpen: Middleware.Util.asyncMiddleware(confirmAppsOpen),
updateSettings: Middleware.Util.asyncMiddleware(updateSettings),
getSettings: Middleware.Util.asyncMiddleware(getSettings)
Expand Down
58 changes: 58 additions & 0 deletions middlewares/validators/checkin.validator.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
"use strict";

const { body } = require('express-validator');
const {
PRIZE_CATEGORIES,
SPONSOR_CHALLENGES,
MLH_CHALLENGES
} = require('../../constants/checkin-options');

/**
* Validator for check-in form submission
*/
const checkinValidator = [
body('formData.prizeCategories')
.isArray()
.withMessage('Prize categories must be an array')
.custom((values) =>
Array.isArray(values) &&
values.every((value) => typeof value === 'string' && PRIZE_CATEGORIES.includes(value))
)
.withMessage('Prize categories contain invalid selections'),
body('formData.sponsorChallenges')
.isArray()
.withMessage('Sponsor challenges must be an array')
.custom((values) =>
Array.isArray(values) &&
values.every((value) => typeof value === 'string' && SPONSOR_CHALLENGES.includes(value))
)
.withMessage('Sponsor challenges contain invalid selections'),
body('formData.mlhChallenges')
.isArray()
.withMessage('MLH challenges must be an array')
.custom((values) =>
Array.isArray(values) &&
values.every((value) => typeof value === 'string' && MLH_CHALLENGES.includes(value))
)
.withMessage('MLH challenges contain invalid selections'),
// body('formData.workshopsAttended').isArray().withMessage('Workshops attended must be an array'),
body('formData.discordTag').notEmpty().withMessage('Discord tag is required'),
body('formData.devpostLink')
.notEmpty()
.withMessage('Devpost link is required')
.bail()
.isURL({ require_protocol: true, protocols: ['http', 'https'] })
.withMessage('Devpost link must be a valid URL')
.bail()
.custom((value) => {
try {
const url = new URL(value);
return url.hostname === 'devpost.com' || url.hostname.endsWith('.devpost.com');
} catch (error) {
return false;
}
})
.withMessage('Devpost link must be a devpost.com URL')
];

module.exports = checkinValidator;
3 changes: 2 additions & 1 deletion middlewares/validators/settings.validator.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ module.exports = {
VALIDATOR.dateValidator("body", "openTime", true),
VALIDATOR.dateValidator("body", "closeTime", true),
VALIDATOR.dateValidator("body", "confirmTime", true),
VALIDATOR.booleanValidator("body", "isRemote", true)
VALIDATOR.booleanValidator("body", "isRemote", true),
VALIDATOR.booleanValidator("body", "checkinOpen", true),
]
};
4 changes: 4 additions & 0 deletions models/settings.model.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@ const settings = new mongoose.Schema({
isRemote: {
type: Boolean,
default: false
},
checkinOpen: {
type: Boolean,
default: false
}
});

Expand Down
Loading
Loading