diff --git a/app.js b/app.js
index 7efd85cc..1bc2b6bb 100755
--- a/app.js
+++ b/app.js
@@ -8,7 +8,7 @@ const Services = {
log: require("./services/logger.service"),
db: require("./services/database.service"),
auth: require("./services/auth.service"),
- env: require("./services/env.service")
+ env: require("./services/env.service"),
};
const envLoadResult = Services.env.load(path.join(__dirname, "./.env"));
@@ -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 emailsRouter = require("./routes/api/emails");
const app = express();
Services.db.connect();
@@ -40,7 +41,7 @@ let corsOptions = {};
if (!Services.env.isProduction()) {
corsOptions = {
origin: [`http://${process.env.FRONTEND_ADDRESS_DEV}`],
- credentials: true
+ credentials: true,
};
} else {
corsOptions = {
@@ -48,34 +49,32 @@ if (!Services.env.isProduction()) {
const allowedOrigins = [
`https://${process.env.FRONTEND_ADDRESS_DEPLOY}`,
`https://${process.env.FRONTEND_ADDRESS_BETA}`,
- `https://docs.mchacks.ca`
+ `https://docs.mchacks.ca`,
];
const regex = /^https:\/\/dashboard-[\w-]+\.vercel\.app$/;
if (
allowedOrigins.includes(origin) || // Explicitly allowed origins
- regex.test(origin) // Matches dashboard subdomains
+ regex.test(origin) // Matches dashboard subdomains
) {
callback(null, true);
} else {
- callback(new Error('Not allowed by CORS'));
+ callback(new Error("Not allowed by CORS"));
}
},
- credentials: true
+ credentials: true,
};
}
-
-
app.use(cors(corsOptions));
app.use(Services.log.requestLogger);
app.use(Services.log.errorLogger);
app.use(express.json());
app.use(
express.urlencoded({
- extended: false
- })
+ extended: false,
+ }),
);
app.use(cookieParser());
//Cookie-based session tracking
@@ -86,8 +85,8 @@ app.use(
// Cookie Options
maxAge: 48 * 60 * 60 * 1000, //Logged in for 48 hours
sameSite: process.env.COOKIE_SAME_SITE,
- secureProxy: !Services.env.isTest()
- })
+ secureProxy: !Services.env.isTest(),
+ }),
);
app.use(passport.initialize());
app.use(passport.session()); //persistent login session
@@ -116,10 +115,10 @@ settingsRouter.activate(apiRouter);
Services.log.info("Settings router activated");
roleRouter.activate(apiRouter);
Services.log.info("Role router activated");
+emailsRouter.activate(apiRouter);
+Services.log.info("Emails router activated");
-apiRouter.use("/", indexRouter);
app.use("/", indexRouter);
-
app.use("/api", apiRouter);
//Custom error handler
@@ -140,10 +139,10 @@ app.use((err, req, res, next) => {
}
res.status(status).json({
message: message,
- data: errorContents
+ data: errorContents,
});
});
module.exports = {
- app: app
+ app: app,
};
diff --git a/assets/email/statusEmail/Accepted.hbs b/assets/email/statusEmail/Accepted.hbs
index 8e0bc5d3..e728a241 100644
--- a/assets/email/statusEmail/Accepted.hbs
+++ b/assets/email/statusEmail/Accepted.hbs
@@ -387,12 +387,12 @@
style="-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%;color:#4D4D4D;">
Congratulations, {{firstName}}! 🎉
- We’re thrilled to offer you a spot at McHacks! We can't wait to see what
+ We're thrilled to offer you a spot at McHacks! We can't wait to see what
you create with us this year.
Confirm your attendance on our hacker
- dashboard no later than January 21th at 11:59PM EST.
+ dashboard no later than January 13th at 11:59PM EST.
If you can no longer attend McHacks, please let us know as soon as
possible by withdrawing your application on our hacker
dashboard until the deadline on January
- 3rd at
+ style="-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%;font-weight: 700;">December
+ 20th at
11:59 PM ET.
In the meantime, follow us on
Hi, {{firstName}},
- Thanks for confirming your attendance for McHacks! We hope you’re just
+ Thanks for confirming your attendance for McHacks! We hope you're just
as excited as we are. Keep an eye out for our week-of email with more
details regarding McHacks. Happy hacking!
diff --git a/constants/routes.constant.js b/constants/routes.constant.js
index e880674d..0a246fe8 100644
--- a/constants/routes.constant.js
+++ b/constants/routes.constant.js
@@ -6,7 +6,7 @@
* ===***===***===***===***===***===***===***===***===
*
* If you are adding a route to this list, update this number
- * next avaiable createFromTime value: 168
+ * next avaiable createFromTime value: 170
*
* If you are deleting a route from this list, please add the ID to the list of 'reserved' IDs,
* so that we don't accidentally assign someone to a given ID.
@@ -20,189 +20,189 @@ const authRoutes = {
login: {
requestType: Constants.REQUEST_TYPES.POST,
uri: "/api/auth/login",
- _id: mongoose.Types.ObjectId.createFromTime(100)
+ _id: mongoose.Types.ObjectId.createFromTime(100),
},
logout: {
requestType: Constants.REQUEST_TYPES.POST,
uri: "/api/auth/logout",
- _id: mongoose.Types.ObjectId.createFromTime(101)
+ _id: mongoose.Types.ObjectId.createFromTime(101),
},
getSelfRoleBindindings: {
requestType: Constants.REQUEST_TYPES.GET,
uri: "/api/auth/rolebindings/" + Constants.ROLE_CATEGORIES.SELF,
- _id: mongoose.Types.ObjectId.createFromTime(102)
+ _id: mongoose.Types.ObjectId.createFromTime(102),
},
getAnyRoleBindings: {
requestType: Constants.REQUEST_TYPES.GET,
uri: "/api/auth/rolebindings/" + Constants.ROLE_CATEGORIES.ALL,
- _id: mongoose.Types.ObjectId.createFromTime(103)
+ _id: mongoose.Types.ObjectId.createFromTime(103),
},
changePassword: {
requestType: Constants.REQUEST_TYPES.PATCH,
uri: "/api/auth/password/change",
- _id: mongoose.Types.ObjectId.createFromTime(104)
- }
+ _id: mongoose.Types.ObjectId.createFromTime(104),
+ },
};
const accountRoutes = {
getSelf: {
requestType: Constants.REQUEST_TYPES.GET,
uri: "/api/account/self",
- _id: mongoose.Types.ObjectId.createFromTime(105)
+ _id: mongoose.Types.ObjectId.createFromTime(105),
},
getSelfById: {
requestType: Constants.REQUEST_TYPES.GET,
uri: "/api/account/" + Constants.ROLE_CATEGORIES.SELF,
- _id: mongoose.Types.ObjectId.createFromTime(106)
+ _id: mongoose.Types.ObjectId.createFromTime(106),
},
getAnyById: {
requestType: Constants.REQUEST_TYPES.GET,
uri: "/api/account/" + Constants.ROLE_CATEGORIES.ALL,
- _id: mongoose.Types.ObjectId.createFromTime(107)
+ _id: mongoose.Types.ObjectId.createFromTime(107),
},
post: {
requestType: Constants.REQUEST_TYPES.POST,
uri: "/api/account/",
- _id: mongoose.Types.ObjectId.createFromTime(108)
+ _id: mongoose.Types.ObjectId.createFromTime(108),
},
patchSelfById: {
requestType: Constants.REQUEST_TYPES.PATCH,
uri: "/api/account/" + Constants.ROLE_CATEGORIES.SELF,
- _id: mongoose.Types.ObjectId.createFromTime(109)
+ _id: mongoose.Types.ObjectId.createFromTime(109),
},
patchAnyById: {
requestType: Constants.REQUEST_TYPES.PATCH,
uri: "/api/account/" + Constants.ROLE_CATEGORIES.ALL,
- _id: mongoose.Types.ObjectId.createFromTime(110)
+ _id: mongoose.Types.ObjectId.createFromTime(110),
},
inviteAccount: {
requestType: Constants.REQUEST_TYPES.POST,
uri: "/api/account/invite",
- _id: mongoose.Types.ObjectId.createFromTime(111)
- }
+ _id: mongoose.Types.ObjectId.createFromTime(111),
+ },
};
const hackerRoutes = {
getSelf: {
requestType: Constants.REQUEST_TYPES.GET,
uri: "/api/hacker/self/",
- _id: mongoose.Types.ObjectId.createFromTime(112)
+ _id: mongoose.Types.ObjectId.createFromTime(112),
},
getSelfById: {
requestType: Constants.REQUEST_TYPES.GET,
uri: "/api/hacker/" + Constants.ROLE_CATEGORIES.SELF,
- _id: mongoose.Types.ObjectId.createFromTime(113)
+ _id: mongoose.Types.ObjectId.createFromTime(113),
},
getAnyById: {
requestType: Constants.REQUEST_TYPES.GET,
uri: "/api/hacker/" + Constants.ROLE_CATEGORIES.ALL,
- _id: mongoose.Types.ObjectId.createFromTime(114)
+ _id: mongoose.Types.ObjectId.createFromTime(114),
},
getSelfByEmail: {
requestType: Constants.REQUEST_TYPES.GET,
uri: "/api/hacker/email/" + Constants.ROLE_CATEGORIES.SELF,
- _id: mongoose.Types.ObjectId.createFromTime(115)
+ _id: mongoose.Types.ObjectId.createFromTime(115),
},
getAnyByEmail: {
requestType: Constants.REQUEST_TYPES.GET,
uri: "/api/hacker/email/" + Constants.ROLE_CATEGORIES.ALL,
- _id: mongoose.Types.ObjectId.createFromTime(116)
+ _id: mongoose.Types.ObjectId.createFromTime(116),
},
getSelfResumeById: {
requestType: Constants.REQUEST_TYPES.GET,
uri: "/api/hacker/resume/" + Constants.ROLE_CATEGORIES.SELF,
- _id: mongoose.Types.ObjectId.createFromTime(117)
+ _id: mongoose.Types.ObjectId.createFromTime(117),
},
getAnyResumeById: {
requestType: Constants.REQUEST_TYPES.GET,
uri: "/api/hacker/resume/" + Constants.ROLE_CATEGORIES.ALL,
- _id: mongoose.Types.ObjectId.createFromTime(118)
+ _id: mongoose.Types.ObjectId.createFromTime(118),
},
post: {
requestType: Constants.REQUEST_TYPES.POST,
uri: "/api/hacker/",
- _id: mongoose.Types.ObjectId.createFromTime(119)
+ _id: mongoose.Types.ObjectId.createFromTime(119),
},
postSelfResumeById: {
requestType: Constants.REQUEST_TYPES.POST,
uri: "/api/hacker/resume/" + Constants.ROLE_CATEGORIES.SELF,
- _id: mongoose.Types.ObjectId.createFromTime(120)
+ _id: mongoose.Types.ObjectId.createFromTime(120),
},
postAnyResumeById: {
requestType: Constants.REQUEST_TYPES.POST,
uri: "/api/hacker/resume/" + Constants.ROLE_CATEGORIES.ALL,
- _id: mongoose.Types.ObjectId.createFromTime(121)
+ _id: mongoose.Types.ObjectId.createFromTime(121),
},
patchSelfById: {
requestType: Constants.REQUEST_TYPES.PATCH,
uri: "/api/hacker/" + Constants.ROLE_CATEGORIES.SELF,
- _id: mongoose.Types.ObjectId.createFromTime(122)
+ _id: mongoose.Types.ObjectId.createFromTime(122),
},
patchAnyById: {
requestType: Constants.REQUEST_TYPES.PATCH,
uri: "/api/hacker/" + Constants.ROLE_CATEGORIES.ALL,
- _id: mongoose.Types.ObjectId.createFromTime(123)
+ _id: mongoose.Types.ObjectId.createFromTime(123),
},
patchAnyStatusById: {
requestType: Constants.REQUEST_TYPES.PATCH,
uri: "/api/hacker/status/" + Constants.ROLE_CATEGORIES.ALL,
- _id: mongoose.Types.ObjectId.createFromTime(124)
+ _id: mongoose.Types.ObjectId.createFromTime(124),
},
patchSelfStatusById: {
requestType: Constants.REQUEST_TYPES.PATCH,
uri: "/api/hacker/status/" + Constants.ROLE_CATEGORIES.SELF,
- _id: mongoose.Types.ObjectId.createFromTime(125)
+ _id: mongoose.Types.ObjectId.createFromTime(125),
},
patchSelfCheckInById: {
requestType: Constants.REQUEST_TYPES.PATCH,
uri: "/api/hacker/checkin/" + Constants.ROLE_CATEGORIES.SELF,
- _id: mongoose.Types.ObjectId.createFromTime(126)
+ _id: mongoose.Types.ObjectId.createFromTime(126),
},
patchAnyCheckInById: {
requestType: Constants.REQUEST_TYPES.PATCH,
uri: "/api/hacker/checkin/" + Constants.ROLE_CATEGORIES.ALL,
- _id: mongoose.Types.ObjectId.createFromTime(127)
+ _id: mongoose.Types.ObjectId.createFromTime(127),
},
patchSelfConfirmationById: {
requestType: Constants.REQUEST_TYPES.PATCH,
uri: "/api/hacker/confirmation/" + Constants.ROLE_CATEGORIES.SELF,
- _id: mongoose.Types.ObjectId.createFromTime(128)
+ _id: mongoose.Types.ObjectId.createFromTime(128),
},
patchAcceptHackerById: {
requestType: Constants.REQUEST_TYPES.PATCH,
uri: "/api/hacker/accept/" + Constants.ROLE_CATEGORIES.ALL,
- _id: mongoose.Types.ObjectId.createFromTime(129)
+ _id: mongoose.Types.ObjectId.createFromTime(129),
},
patchAcceptHackerByEmail: {
requestType: Constants.REQUEST_TYPES.PATCH,
uri: "/api/hacker/acceptEmail/" + Constants.ROLE_CATEGORIES.ALL,
- _id: mongoose.Types.ObjectId.createFromTime(130)
+ _id: mongoose.Types.ObjectId.createFromTime(130),
},
patchAcceptHackerByArrayOfIds: {
requestType: Constants.REQUEST_TYPES.PATCH,
uri: "/api/hacker/batchAccept",
- _id: mongoose.Types.ObjectId.createFromTime(165)
+ _id: mongoose.Types.ObjectId.createFromTime(165),
},
postAnySendWeekOfEmail: {
requestType: Constants.REQUEST_TYPES.POST,
uri: "/api/hacker/email/weekOf/" + Constants.ROLE_CATEGORIES.ALL,
- _id: mongoose.Types.ObjectId.createFromTime(131)
+ _id: mongoose.Types.ObjectId.createFromTime(131),
},
postSelfSendWeekOfEmail: {
requestType: Constants.REQUEST_TYPES.POST,
uri: "/api/hacker/email/weekOf/" + Constants.ROLE_CATEGORIES.SELF,
- _id: mongoose.Types.ObjectId.createFromTime(132)
+ _id: mongoose.Types.ObjectId.createFromTime(132),
},
postAnySendDayOfEmail: {
requestType: Constants.REQUEST_TYPES.POST,
uri: "/api/hacker/email/dayOf/" + Constants.ROLE_CATEGORIES.ALL,
- _id: mongoose.Types.ObjectId.createFromTime(133)
+ _id: mongoose.Types.ObjectId.createFromTime(133),
},
postSelfSendDayOfEmail: {
requestType: Constants.REQUEST_TYPES.POST,
uri: "/api/hacker/email/dayOf/" + Constants.ROLE_CATEGORIES.SELF,
- _id: mongoose.Types.ObjectId.createFromTime(134)
- }
+ _id: mongoose.Types.ObjectId.createFromTime(134),
+ },
// },
// postDiscord: {
// requestType: Constants.REQUEST_TYPES.POST,
@@ -215,179 +215,189 @@ const travelRoutes = {
getSelf: {
requestType: Constants.REQUEST_TYPES.GET,
uri: "/api/travel/self/",
- _id: mongoose.Types.ObjectId.createFromTime(135)
+ _id: mongoose.Types.ObjectId.createFromTime(135),
},
getSelfById: {
requestType: Constants.REQUEST_TYPES.GET,
uri: "/api/travel/" + Constants.ROLE_CATEGORIES.SELF,
- _id: mongoose.Types.ObjectId.createFromTime(136)
+ _id: mongoose.Types.ObjectId.createFromTime(136),
},
getAnyById: {
requestType: Constants.REQUEST_TYPES.GET,
uri: "/api/travel/" + Constants.ROLE_CATEGORIES.ALL,
- _id: mongoose.Types.ObjectId.createFromTime(137)
+ _id: mongoose.Types.ObjectId.createFromTime(137),
},
getSelfByEmail: {
requestType: Constants.REQUEST_TYPES.GET,
uri: "/api/travel/email/" + Constants.ROLE_CATEGORIES.SELF,
- _id: mongoose.Types.ObjectId.createFromTime(138)
+ _id: mongoose.Types.ObjectId.createFromTime(138),
},
getAnyByEmail: {
requestType: Constants.REQUEST_TYPES.GET,
uri: "/api/travel/email/" + Constants.ROLE_CATEGORIES.ALL,
- _id: mongoose.Types.ObjectId.createFromTime(139)
+ _id: mongoose.Types.ObjectId.createFromTime(139),
},
post: {
requestType: Constants.REQUEST_TYPES.POST,
uri: "/api/travel/",
- _id: mongoose.Types.ObjectId.createFromTime(140)
+ _id: mongoose.Types.ObjectId.createFromTime(140),
},
patchAnyStatusById: {
requestType: Constants.REQUEST_TYPES.PATCH,
uri: "/api/travel/status/" + Constants.ROLE_CATEGORIES.ALL,
- _id: mongoose.Types.ObjectId.createFromTime(141)
+ _id: mongoose.Types.ObjectId.createFromTime(141),
},
patchAnyOfferById: {
requestType: Constants.REQUEST_TYPES.PATCH,
uri: "/api/travel/offer/" + Constants.ROLE_CATEGORIES.ALL,
- _id: mongoose.Types.ObjectId.createFromTime(142)
- }
+ _id: mongoose.Types.ObjectId.createFromTime(142),
+ },
};
const sponsorRoutes = {
getSelf: {
requestType: Constants.REQUEST_TYPES.GET,
uri: "/api/sponsor/self/",
- _id: mongoose.Types.ObjectId.createFromTime(143)
+ _id: mongoose.Types.ObjectId.createFromTime(143),
},
getSelfById: {
requestType: Constants.REQUEST_TYPES.GET,
uri: "/api/sponsor/" + Constants.ROLE_CATEGORIES.SELF,
- _id: mongoose.Types.ObjectId.createFromTime(144)
+ _id: mongoose.Types.ObjectId.createFromTime(144),
},
getAnyById: {
requestType: Constants.REQUEST_TYPES.GET,
uri: "/api/sponsor/" + Constants.ROLE_CATEGORIES.ALL,
- _id: mongoose.Types.ObjectId.createFromTime(145)
+ _id: mongoose.Types.ObjectId.createFromTime(145),
},
post: {
requestType: Constants.REQUEST_TYPES.POST,
uri: "/api/sponsor/",
- _id: mongoose.Types.ObjectId.createFromTime(146)
+ _id: mongoose.Types.ObjectId.createFromTime(146),
},
patchSelfById: {
requestType: Constants.REQUEST_TYPES.PATCH,
uri: "/api/sponsor/" + Constants.ROLE_CATEGORIES.SELF,
- _id: mongoose.Types.ObjectId.createFromTime(147)
+ _id: mongoose.Types.ObjectId.createFromTime(147),
},
patchAnyById: {
requestType: Constants.REQUEST_TYPES.PATCH,
uri: "/api/sponsor/" + Constants.ROLE_CATEGORIES.ALL,
- _id: mongoose.Types.ObjectId.createFromTime(148)
- }
+ _id: mongoose.Types.ObjectId.createFromTime(148),
+ },
};
const teamRoutes = {
get: {
requestType: Constants.REQUEST_TYPES.GET,
uri: "/api/team/" + Constants.ROLE_CATEGORIES.ALL,
- _id: mongoose.Types.ObjectId.createFromTime(149)
+ _id: mongoose.Types.ObjectId.createFromTime(149),
},
post: {
requestType: Constants.REQUEST_TYPES.POST,
uri: "/api/team/",
- _id: mongoose.Types.ObjectId.createFromTime(150)
+ _id: mongoose.Types.ObjectId.createFromTime(150),
},
join: {
requestType: Constants.REQUEST_TYPES.PATCH,
uri: "/api/team/join/",
- _id: mongoose.Types.ObjectId.createFromTime(151)
+ _id: mongoose.Types.ObjectId.createFromTime(151),
},
patchSelfById: {
requestType: Constants.REQUEST_TYPES.PATCH,
uri: "/api/team/" + Constants.ROLE_CATEGORIES.SELF,
- _id: mongoose.Types.ObjectId.createFromTime(152)
+ _id: mongoose.Types.ObjectId.createFromTime(152),
},
patchAnyById: {
requestType: Constants.REQUEST_TYPES.PATCH,
uri: "/api/team/" + Constants.ROLE_CATEGORIES.ALL,
- _id: mongoose.Types.ObjectId.createFromTime(153)
+ _id: mongoose.Types.ObjectId.createFromTime(153),
},
leave: {
requestType: Constants.REQUEST_TYPES.PATCH,
uri: "/api/team/leave/",
- _id: mongoose.Types.ObjectId.createFromTime(154)
- }
+ _id: mongoose.Types.ObjectId.createFromTime(154),
+ },
};
const volunteerRoutes = {
getSelfById: {
requestType: Constants.REQUEST_TYPES.GET,
uri: "/api/volunteer/" + Constants.ROLE_CATEGORIES.SELF,
- _id: mongoose.Types.ObjectId.createFromTime(155)
+ _id: mongoose.Types.ObjectId.createFromTime(155),
},
getAnyById: {
requestType: Constants.REQUEST_TYPES.GET,
uri: "/api/volunteer/" + Constants.ROLE_CATEGORIES.ALL,
- _id: mongoose.Types.ObjectId.createFromTime(156)
+ _id: mongoose.Types.ObjectId.createFromTime(156),
},
post: {
requestType: Constants.REQUEST_TYPES.POST,
uri: "/api/volunteer/",
- _id: mongoose.Types.ObjectId.createFromTime(157)
- }
+ _id: mongoose.Types.ObjectId.createFromTime(157),
+ },
};
const roleRoutes = {
post: {
requestType: Constants.REQUEST_TYPES.POST,
uri: "/api/role/",
- _id: mongoose.Types.ObjectId.createFromTime(158)
- }
+ _id: mongoose.Types.ObjectId.createFromTime(158),
+ },
};
const searchRoutes = {
get: {
requestType: Constants.REQUEST_TYPES.GET,
uri: "/api/search/",
- _id: mongoose.Types.ObjectId.createFromTime(159)
- }
+ _id: mongoose.Types.ObjectId.createFromTime(159),
+ },
};
const staffRoutes = {
hackerStats: {
requestType: Constants.REQUEST_TYPES.GET,
uri: "/api/hacker/stats",
- _id: mongoose.Types.ObjectId.createFromTime(160)
+ _id: mongoose.Types.ObjectId.createFromTime(160),
},
postInvite: {
requestType: Constants.REQUEST_TYPES.POST,
uri: "/api/account/invite",
- _id: mongoose.Types.ObjectId.createFromTime(161)
+ _id: mongoose.Types.ObjectId.createFromTime(161),
},
getInvite: {
requestType: Constants.REQUEST_TYPES.GET,
uri: "/api/account/invite",
- _id: mongoose.Types.ObjectId.createFromTime(162)
+ _id: mongoose.Types.ObjectId.createFromTime(162),
},
postDiscord: {
requestType: Constants.REQUEST_TYPES.POST,
uri: "/api/hacker/discord",
- _id: mongoose.Types.ObjectId.createFromTime(167)
- }
+ _id: mongoose.Types.ObjectId.createFromTime(167),
+ },
+ postAutomatedStatusEmails: {
+ requestType: Constants.REQUEST_TYPES.POST,
+ uri: "/api/email/automated/status/:status",
+ _id: mongoose.Types.ObjectId.createFromTime(168),
+ },
+ getAutomatedStatusEmailCount: {
+ requestType: Constants.REQUEST_TYPES.GET,
+ uri: "/api/email/automated/status/:status/count",
+ _id: mongoose.Types.ObjectId.createFromTime(169),
+ },
};
const settingsRoutes = {
getSettings: {
requestType: Constants.REQUEST_TYPES.GET,
uri: "/api/settings",
- _id: mongoose.Types.ObjectId.createFromTime(163)
+ _id: mongoose.Types.ObjectId.createFromTime(163),
},
patchSettings: {
requestType: Constants.REQUEST_TYPES.PATCH,
uri: "/api/settings",
- _id: mongoose.Types.ObjectId.createFromTime(164)
- }
+ _id: mongoose.Types.ObjectId.createFromTime(164),
+ },
};
const allRoutes = {
@@ -401,7 +411,7 @@ const allRoutes = {
Role: roleRoutes,
Search: searchRoutes,
Settings: settingsRoutes,
- Staff: staffRoutes
+ Staff: staffRoutes,
};
/**
@@ -449,5 +459,5 @@ module.exports = {
settingsRoutes: settingsRoutes,
staffRoutes: staffRoutes,
allRoutes: allRoutes,
- listAllRoutes: listAllRoutes
+ listAllRoutes: listAllRoutes,
};
diff --git a/controllers/email.controller.js b/controllers/email.controller.js
new file mode 100644
index 00000000..25857cb2
--- /dev/null
+++ b/controllers/email.controller.js
@@ -0,0 +1,39 @@
+"use strict";
+
+const Constants = {
+ Success: require("../constants/success.constant"),
+ Error: require("../constants/error.constant"),
+};
+
+/**
+ * @function getStatusCount
+ * @param {{body: {count: number}}} req
+ * @param {*} res
+ * @return {JSON} Success status and count
+ * @description Returns the count of hackers with specified status
+ */
+function getStatusCount(req, res) {
+ return res.status(200).json({
+ message: "Successfully retrieved count",
+ data: { count: req.body.count },
+ });
+}
+
+/**
+ * @function sendAutomatedStatusEmails
+ * @param {{body: {results: {success: number, failed: number}}}} req
+ * @param {*} res
+ * @return {JSON} Success status and email results
+ * @description Returns the results of sending automated status emails
+ */
+function sendAutomatedStatusEmails(req, res) {
+ return res.status(200).json({
+ message: "Successfully sent emails",
+ data: req.body.results,
+ });
+}
+
+module.exports = {
+ getStatusCount,
+ sendAutomatedStatusEmails,
+};
diff --git a/middlewares/email.middleware.js b/middlewares/email.middleware.js
new file mode 100644
index 00000000..3ac0b573
--- /dev/null
+++ b/middlewares/email.middleware.js
@@ -0,0 +1,81 @@
+"use strict";
+
+const Services = {
+ AutomatedEmail: require("../services/automatedEmails.service"),
+};
+const Constants = {
+ Error: require("../constants/error.constant"),
+ General: require("../constants/general.constant"),
+};
+
+/**
+ * Middleware to validate status parameter
+ * @param {{params: {status: string}}} req
+ * @param {*} res
+ * @param {(err?)=>void} next
+ */
+function validateStatus(req, res, next) {
+ const { status } = req.params;
+ const validStatuses = [
+ Constants.General.HACKER_STATUS_ACCEPTED,
+ Constants.General.HACKER_STATUS_DECLINED,
+ ];
+
+ if (!validStatuses.includes(status)) {
+ return res.status(400).json({
+ message: "Invalid status",
+ data: {},
+ });
+ }
+
+ next();
+}
+
+/**
+ * Middleware to get count of hackers with specified status
+ * @param {{params: {status: string}}} req
+ * @param {*} res
+ * @param {(err?)=>void} next
+ */
+async function getStatusCount(req, res, next) {
+ const { status } = req.params;
+
+ try {
+ const count = await Services.AutomatedEmail.getStatusCount(status);
+ req.body.count = count;
+ next();
+ } catch (err) {
+ return res.status(500).json({
+ message: err.message,
+ data: {},
+ });
+ }
+}
+
+/**
+ * Middleware to send automated status emails
+ * @param {{params: {status: string}}} req
+ * @param {*} res
+ * @param {(err?)=>void} next
+ */
+async function sendAutomatedStatusEmails(req, res, next) {
+ const { status } = req.params;
+
+ try {
+ const results =
+ await Services.AutomatedEmail.sendAutomatedStatusEmails(status);
+ req.body.results = results;
+ next();
+ } catch (err) {
+ return res.status(500).json({
+ message: err.message,
+ data: {},
+ });
+ }
+}
+
+module.exports = {
+ validateStatus,
+ getStatusCount,
+ sendAutomatedStatusEmails,
+};
diff --git a/routes/api/emails.js b/routes/api/emails.js
new file mode 100644
index 00000000..3e55b5b5
--- /dev/null
+++ b/routes/api/emails.js
@@ -0,0 +1,72 @@
+"use strict";
+const express = require("express");
+
+const Middleware = {
+ Auth: require("../../middlewares/auth.middleware"),
+ Email: require("../../middlewares/email.middleware"),
+};
+
+const Controllers = {
+ Email: require("../../controllers/email.controller"),
+};
+
+module.exports = {
+ activate: function (apiRouter) {
+ const automatedEmailRouter = express.Router();
+
+ /**
+ * @api {get} /email/automated/status/:status/count Get count of hackers with specified status
+ * @apiName getStatusEmailCount
+ * @apiGroup Email
+ * @apiVersion 0.0.8
+ *
+ * @apiParam {string} status Status of hackers to count (Accepted/Declined)
+ *
+ * @apiSuccess {string} message Success message
+ * @apiSuccess {object} data Contains count of hackers
+ * @apiSuccessExample {object} Success-Response:
+ * {
+ * "message": "Successfully retrieved count",
+ * "data": {
+ * "count": 50
+ * }
+ * }
+ */
+ automatedEmailRouter.route("/automated/status/:status/count").get(
+ Middleware.Auth.ensureAuthenticated(),
+ // Middleware.Auth.ensureAuthorized(),
+ Middleware.Email.validateStatus,
+ Middleware.Email.getStatusCount,
+ Controllers.Email.getStatusCount,
+ );
+
+ /**
+ * @api {post} /email/automated/status/:status Send emails to all hackers with specified status
+ * @apiName sendAutomatedStatusEmails
+ * @apiGroup Email
+ * @apiVersion 0.0.8
+ *
+ * @apiParam {string} status Status of hackers to email (Accepted/Declined)
+ *
+ * @apiSuccess {string} message Success message
+ * @apiSuccess {object} data Contains counts of successful and failed emails
+ * @apiSuccessExample {object} Success-Response:
+ * {
+ * "message": "Successfully sent emails",
+ * "data": {
+ * "success": 50,
+ * "failed": 2
+ * }
+ * }
+ */
+ automatedEmailRouter.route("/automated/status/:status").post(
+ Middleware.Auth.ensureAuthenticated(),
+ // Middleware.Auth.ensureAuthorized(),
+ Middleware.Email.validateStatus,
+ Middleware.Email.sendAutomatedStatusEmails,
+ Controllers.Email.sendAutomatedStatusEmails,
+ );
+
+ apiRouter.use("/email", automatedEmailRouter);
+ },
+};
diff --git a/services/automatedEmails.service.js b/services/automatedEmails.service.js
new file mode 100644
index 00000000..815a5534
--- /dev/null
+++ b/services/automatedEmails.service.js
@@ -0,0 +1,82 @@
+"use strict";
+
+const Services = {
+ Email: require("./email.service"),
+ Hacker: require("./hacker.service"),
+ Logger: require("./logger.service"),
+};
+
+const TAG = "[AutomatedEmail.Service]";
+
+class AutomatedEmailService {
+ /**
+ * Get count of hackers with the given status
+ * @param {string} status - "Accepted", "Declined"
+ * @returns {Promise} Count of hackers with the status
+ */
+ async getStatusCount(status) {
+ try {
+ const hackers = await Services.Hacker.findByStatus(status);
+ if (!hackers || !Array.isArray(hackers)) {
+ return 0;
+ }
+ return hackers.length;
+ } catch (err) {
+ Services.Logger.error(`${TAG} Error in getStatusCount: ${err}`);
+ throw err;
+ }
+ }
+
+ /**
+ * Send status emails to all hackers with the given status
+ * @param {string} status - "Accepted", "Declined"
+ * @returns {Promise<{success: number, failed: number}>}
+ */
+ async sendAutomatedStatusEmails(status) {
+ const results = { success: 0, failed: 0 };
+ try {
+ const hackers = await Services.Hacker.findByStatus(status);
+
+ if (!hackers || !Array.isArray(hackers)) {
+ throw new Error(
+ `Expected array from findByStatus(${status}), got ${typeof hackers}`,
+ );
+ }
+
+ const emailPromises = hackers.map(async (hacker) => {
+ try {
+ await new Promise((resolve, reject) => {
+ Services.Email.sendStatusUpdate(
+ hacker.accountId.firstName,
+ hacker.accountId.email,
+ status,
+ (err) => {
+ if (err) {
+ reject(err);
+ } else {
+ resolve();
+ }
+ },
+ );
+ });
+ results.success++;
+ } catch (err) {
+ Services.Logger.error(
+ `${TAG} Failed to send ${status} email to ${hacker.accountId.email}: ${err}`,
+ );
+ results.failed++;
+ }
+ });
+
+ await Promise.all(emailPromises);
+ return results;
+ } catch (err) {
+ Services.Logger.error(
+ `${TAG} Error in sendAutomatedStatusEmails: ${err}`,
+ );
+ throw err;
+ }
+ }
+}
+
+module.exports = new AutomatedEmailService();
diff --git a/services/email.service.js b/services/email.service.js
index af9abde1..f8c15e1a 100644
--- a/services/email.service.js
+++ b/services/email.service.js
@@ -23,18 +23,19 @@ class EmailService {
//Silence all actual emails if we're testing
mailData.mailSettings = {
sandboxMode: {
- enable: true
- }
+ enable: true,
+ },
};
}
- return client.send(mailData, false)
- .then(response => {
- callback()
- return response
- })
- .catch(error => {
- callback(error)
+ return client
+ .send(mailData, false)
+ .then((response) => {
+ callback();
+ return response;
})
+ .catch((error) => {
+ callback(error);
+ });
}
/**
* Send separate emails to the list of users in mailData
@@ -42,14 +43,15 @@ class EmailService {
* @param {(err?)=>void} callback
*/
sendMultiple(mailData, callback = () => {}) {
- return client.sendMultiple(mailData)
- .then(response => {
- callback()
+ return client
+ .sendMultiple(mailData)
+ .then((response) => {
+ callback();
return response;
})
- .catch(error => {
- callback(error)
- })
+ .catch((error) => {
+ callback(error);
+ });
}
/**
* Send email with ticket.
@@ -61,17 +63,17 @@ class EmailService {
sendWeekOfEmail(firstName, recipient, ticket, callback) {
const handlebarsPath = path.join(
__dirname,
- `../assets/email/Ticket.hbs`
+ `../assets/email/Ticket.hbs`,
);
const html = this.renderEmail(handlebarsPath, {
firstName: firstName,
- ticket: ticket
+ ticket: ticket,
});
const mailData = {
to: recipient,
from: process.env.NO_REPLY_EMAIL,
subject: Constants.EMAIL_SUBJECTS[Constants.WEEK_OF],
- html: html
+ html: html,
};
this.send(mailData).then((response) => {
if (
@@ -93,16 +95,16 @@ class EmailService {
sendDayOfEmail(firstName, recipient, callback) {
const handlebarsPath = path.join(
__dirname,
- `../assets/email/Welcome.hbs`
+ `../assets/email/Welcome.hbs`,
);
const html = this.renderEmail(handlebarsPath, {
- firstName: firstName
+ firstName: firstName,
});
const mailData = {
to: recipient,
from: process.env.NO_REPLY_EMAIL,
subject: Constants.EMAIL_SUBJECTS[Constants.WEEK_OF],
- html: html
+ html: html,
};
this.send(mailData).then((response) => {
if (
@@ -119,15 +121,15 @@ class EmailService {
sendStatusUpdate(firstName, recipient, status, callback) {
const handlebarsPath = path.join(
__dirname,
- `../assets/email/statusEmail/${status}.hbs`
+ `../assets/email/statusEmail/${status}.hbs`,
);
const mailData = {
to: recipient,
from: process.env.NO_REPLY_EMAIL,
subject: Constants.EMAIL_SUBJECTS[status],
html: this.renderEmail(handlebarsPath, {
- firstName: firstName
- })
+ firstName: firstName,
+ }),
};
this.send(mailData).then((response) => {
if (
diff --git a/services/hacker.service.js b/services/hacker.service.js
index 37531700..d63e0bc4 100644
--- a/services/hacker.service.js
+++ b/services/hacker.service.js
@@ -37,10 +37,14 @@ function updateOne(id, hackerDetails) {
const TAG = `[Hacker Service # update ]:`;
const query = {
- _id: id
+ _id: id,
};
- return logger.logUpdate(TAG, "hacker", Hacker.findOneAndUpdate(query, hackerDetails, { new: true }));
+ return logger.logUpdate(
+ TAG,
+ "hacker",
+ Hacker.findOneAndUpdate(query, hackerDetails, { new: true }),
+ );
}
/**
@@ -67,7 +71,12 @@ async function findIds(queries) {
let ids = [];
for (const query of queries) {
- let currId = await logger.logQuery(TAG, "hacker", query, Hacker.findOne(query, "_id"));
+ let currId = await logger.logQuery(
+ TAG,
+ "hacker",
+ query,
+ Hacker.findOne(query, "_id"),
+ );
ids.push(currId);
}
return ids;
@@ -81,21 +90,45 @@ async function findIds(queries) {
function findByAccountId(accountId) {
const TAG = `[ Hacker Service # findByAccountId ]:`;
const query = {
- accountId: accountId
+ accountId: accountId,
};
return logger.logUpdate(TAG, "hacker", Hacker.findOne(query));
}
+/**
+ * Find all hackers with a specific status
+ * @param {string} status - The status to search for (e.g., "Accepted", "Declined")
+ * @return {Promise>} Array of hacker documents with the specified status
+ */
+async function findByStatus(status) {
+ const TAG = `[ Hacker Service # findByStatus ]:`;
+ const query = { status: status };
+
+ const result = await logger.logQuery(
+ TAG,
+ "hacker",
+ query,
+ Hacker.find(query).populate("accountId"),
+ );
+ // Always return an array
+ if (!Array.isArray(result)) {
+ return [];
+ }
+ return result;
+}
+
async function getStatsAllHackersCached() {
const TAG = `[ hacker Service # getStatsAll ]`;
if (cache.get(Constants.CACHE_KEY_STATS) !== null) {
logger.info(`${TAG} Getting cached stats`);
return cache.get(Constants.CACHE_KEY_STATS);
}
- const allHackers = await logger.logUpdate(TAG, "hacker", Hacker.find({})).populate({
- path: "accountId"
- });
+ const allHackers = await logger
+ .logUpdate(TAG, "hacker", Hacker.find({}))
+ .populate({
+ path: "accountId",
+ });
cache.put(Constants.CACHE_KEY_STATS, stats, Constants.CACHE_TIMEOUT_STATS); //set a time-out of 5 minutes
return getStats(allHackers);
}
@@ -106,7 +139,7 @@ async function getStatsAllHackersCached() {
*/
async function generateQRCode(str) {
const response = await QRCode.toDataURL(str, {
- scale: 4
+ scale: 4,
});
return response;
}
@@ -139,7 +172,7 @@ function getStats(hackers) {
dietaryRestrictions: {},
shirtSize: {},
age: {},
- applicationDate: {}
+ applicationDate: {},
};
hackers.forEach((hacker) => {
@@ -213,7 +246,9 @@ function getStats(hackers) {
// const age = hacker.accountId.getAge();
// stats.age[age] = stats.age[age] ? stats.age[age] + 1 : 1;
- stats.age[hacker.accountId.age] = stats.age[hacker.accountId.age] ? stats.age[age] + 1 : 1;
+ stats.age[hacker.accountId.age] = stats.age[hacker.accountId.age]
+ ? stats.age[age] + 1
+ : 1;
const applicationDate = hacker._id
.getTimestamp() //
@@ -235,8 +270,9 @@ module.exports = {
updateOne: updateOne,
findIds: findIds,
findByAccountId: findByAccountId,
+ findByStatus: findByStatus,
getStats: getStats,
getStatsAllHackersCached: getStatsAllHackersCached,
generateQRCode: generateQRCode,
- generateHackerViewLink: generateHackerViewLink
+ generateHackerViewLink: generateHackerViewLink,
};