alpha-version-dce-check-in-api
Version:
API for taking attendance and checking people in.
1,350 lines (1,269 loc) • 41.4 kB
JavaScript
var __create = Object.create;
var __defProp = Object.defineProperty;
var __defProps = Object.defineProperties;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropDescs = Object.getOwnPropertyDescriptors;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __getOwnPropSymbols = Object.getOwnPropertySymbols;
var __getProtoOf = Object.getPrototypeOf;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __propIsEnum = Object.prototype.propertyIsEnumerable;
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
var __spreadValues = (a, b) => {
for (var prop in b || (b = {}))
if (__hasOwnProp.call(b, prop))
__defNormalProp(a, prop, b[prop]);
if (__getOwnPropSymbols)
for (var prop of __getOwnPropSymbols(b)) {
if (__propIsEnum.call(b, prop))
__defNormalProp(a, prop, b[prop]);
}
return a;
};
var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b));
var __objRest = (source, exclude) => {
var target = {};
for (var prop in source)
if (__hasOwnProp.call(source, prop) && exclude.indexOf(prop) < 0)
target[prop] = source[prop];
if (source != null && __getOwnPropSymbols)
for (var prop of __getOwnPropSymbols(source)) {
if (exclude.indexOf(prop) < 0 && __propIsEnum.call(source, prop))
target[prop] = source[prop];
}
return target;
};
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
// If the importer is in node compatibility mode or this is not an ESM
// file that has been converted to a CommonJS file using a Babel-
// compatible transform (i.e. "__esModule" has not been set), then set
// "default" to the CommonJS "module.exports" for node compatibility.
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
mod
));
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
var __async = (__this, __arguments, generator) => {
return new Promise((resolve, reject) => {
var fulfilled = (value) => {
try {
step(generator.next(value));
} catch (e) {
reject(e);
}
};
var rejected = (value) => {
try {
step(generator.throw(value));
} catch (e) {
reject(e);
}
};
var step = (x) => x.done ? resolve(x.value) : Promise.resolve(x.value).then(fulfilled, rejected);
step((generator = generator.apply(__this, __arguments)).next());
});
};
// src/index.ts
var src_exports = {};
__export(src_exports, {
addCheckInRoutes: () => addCheckInRoutes
});
module.exports = __toCommonJS(src_exports);
// src/shared/constants/STUDENT_PATH_PREFIX.ts
var STUDENT_PATH_PREFIX = "/api/DCHUI";
var STUDENT_PATH_PREFIX_default = STUDENT_PATH_PREFIX;
// src/shared/constants/TTM_PATH_PREFIX.ts
var TTM_PATH_PREFIX = "/api/ttm/DCHUI";
var TTM_PATH_PREFIX_default = TTM_PATH_PREFIX;
// src/initCollections.ts
var import_dce_live_grouper = require("dce-live-grouper");
var groupCollection;
var occurrenceCollection;
var initCollections = (CollectionCons) => {
occurrenceCollection = new CollectionCons(
"OccurrenceDocument",
{
uniqueIndexKey: "id",
indexKeys: [
"courseId",
"ihid"
]
}
);
const {
eventCollection
} = (0, import_dce_live_grouper.initGrouper)(CollectionCons);
groupCollection = eventCollection;
};
var getOccurrenceCollection = () => {
if (!occurrenceCollection) {
throw new Error("Occurrence collection has not been initiated.");
}
return occurrenceCollection;
};
// src/initLogger.ts
var logger;
var initLogger = (logAction, logError) => {
logger = logger != null ? logger : {
logAction,
logError
};
};
// src/addRoutes/addStudentRoutes/index.ts
var import_express2 = __toESM(require("express"));
// src/addRoutes/addStudentRoutes/addAttendanceRoutes/index.ts
var import_express = __toESM(require("express"));
// src/addRoutes/addStudentRoutes/addAttendanceRoutes/addCheckInPageRoute.ts
var import_ejs = __toESM(require("ejs"));
var import_dce_live_grouper2 = require("dce-live-grouper");
// dce-reactkit/helpers/getLaunchInfo.ts
var getLaunchInfo = (req) => {
if (req && req.session && req.session.launchInfo) {
return {
launched: true,
launchInfo: req.session.launchInfo
};
}
return {
launched: false,
launchInfo: void 0
};
};
var getLaunchInfo_default = getLaunchInfo;
// dce-reactkit/types/ReactKitErrorCode.ts
var ReactKitErrorCode = /* @__PURE__ */ ((ReactKitErrorCode2) => {
ReactKitErrorCode2["NoResponse"] = "DRK1";
ReactKitErrorCode2["NoCode"] = "DRK2";
ReactKitErrorCode2["SessionExpired"] = "DRK3";
ReactKitErrorCode2["MissingParameter"] = "DRK4";
ReactKitErrorCode2["InvalidParameter"] = "DRK5";
ReactKitErrorCode2["WrongCourse"] = "DRK6";
ReactKitErrorCode2["NoCACCLSendRequestFunction"] = "DRK7";
ReactKitErrorCode2["NoCACCLGetLaunchInfoFunction"] = "DRK8";
ReactKitErrorCode2["NotTTM"] = "DRK9";
ReactKitErrorCode2["NotAdmin"] = "DRK10";
ReactKitErrorCode2["NotAllowedToReviewLogs"] = "DRK11";
ReactKitErrorCode2["ThemeCheckedBeforeReactKitReady"] = "DRK12";
ReactKitErrorCode2["SessionExpiredMessageGottenBeforeReactKitReady"] = "DRK13";
return ReactKitErrorCode2;
})(ReactKitErrorCode || {});
var ReactKitErrorCode_default = ReactKitErrorCode;
// dce-reactkit/types/ParamType.ts
var ParamType = /* @__PURE__ */ ((ParamType2) => {
ParamType2["Boolean"] = "boolean";
ParamType2["BooleanOptional"] = "boolean-optional";
ParamType2["Float"] = "float";
ParamType2["FloatOptional"] = "float-optional";
ParamType2["Int"] = "int";
ParamType2["IntOptional"] = "int-optional";
ParamType2["JSON"] = "json";
ParamType2["JSONOptional"] = "json-optional";
ParamType2["String"] = "string";
ParamType2["StringOptional"] = "string-optional";
return ParamType2;
})(ParamType || {});
var ParamType_default = ParamType;
// dce-reactkit/helpers/handleError.ts
var handleError = (res, error) => {
let message;
if (error && error.message) {
message = error.message || "An unknown error occurred.";
} else if (typeof error === "string") {
message = error.trim().length > 0 ? error : "An unknown error occurred.";
} else {
message = "An unknown error occurred.";
}
const code = error.code || ReactKitErrorCode_default.NoCode;
const status = error.status || 500;
res.status(status).json({
// Error message
message,
// Error code
code,
// Success = false flag so client can detect server-side errors
success: false
});
return void 0;
};
var handleError_default = handleError;
// dce-reactkit/helpers/handleSuccess.ts
var handleSuccess = (res, body) => {
res.json({
// Include the body as a parameter
body,
// Success = true flag so client can detect successful responses
success: true
});
return void 0;
};
var handleSuccess_default = handleSuccess;
// dce-reactkit/html/genErrorPage.ts
var genErrorPage = (opts = {}) => {
var _a, _b, _c, _d;
const title = (_a = opts.title) != null ? _a : "An Error Occurred";
const pageTitle = (_b = opts.pageTitle) != null ? _b : title;
const description = (_c = opts.description) != null ? _c : "An unknown server error occurred. Please contact support.";
const code = (_d = opts.code) != null ? _d : ReactKitErrorCode_default.NoCode;
return `
<head>
<!-- Metadata -->
<meta
name="viewport"
content="width=device-width, height=device-height, initial-scale=1.0, minimum-scale=1.0"
>
<!-- Title -->
<title>${pageTitle}</title>
<!-- Bootstrap -->
<link
rel="stylesheet"
href="https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.2.1/css/bootstrap.min.css"
integrity="sha512-siwe/oXMhSjGCwLn+scraPOWrJxHlUgMBMZXdPe2Tnk3I0x3ESCoLz7WZ5NTH6SZrywMY+PB1cjyqJ5jAluCOg=="
crossorigin="anonymous"
referrerpolicy="no-referrer"
/>
<script
src="https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.2.1/js/bootstrap.min.js"
integrity="sha512-vyRAVI0IEm6LI/fVSv/Wq/d0KUfrg3hJq2Qz5FlfER69sf3ZHlOrsLriNm49FxnpUGmhx+TaJKwJ+ByTLKT+Yg=="
crossorigin="anonymous"
referrerpolicy="no-referrer"
></script>
<!-- FontAwesome -->
<link
rel="stylesheet"
href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.2.0/css/all.min.css"
integrity="sha512-xh6O/CkQoPOWDdYTDqeRdPCVd1SpvCA9XXcUnZS2FmJNp1coAFzvtCN9BmamE+4aHK8yyUHUSCcJHgXloTyT2A=="
crossorigin="anonymous"
referrerpolicy="no-referrer"
/>
<!-- Style -->
<style>
.DCEReactKit-pop-in {
animation-name: DCEReactKit-pop-in;
animation-duration: 0.5s;
animation-iteration-count: 1;
animation-timing-function: ease-out;
animation-fill-mode: both;
transform-origin: center;
}
@keyframes DCEReactKit-pop-in {
0% {
opacity: 0;
transform: scale(0.9);
}
100% {
opacity: 1;
transform: scale(1);
}
}
.DCEReactKit-slide-in {
animation-name: DCEReactKit-slide-in;
animation-duration: 1s;
animation-iteration-count: 1;
animation-timing-function: ease-out;
animation-fill-mode: both;
animation-delay: 0.2s;
}
@keyframes DCEReactKit-slide-in {
0% {
opacity: 0;
transform: translate(0, 0.3em);
}
100% {
opacity: 1;
transform: translate(0, 0);
}
}
</style>
</head>
<!-- Body -->
<body class="bg-dark text-center pt-3 ps-3 pe-3">
<!-- Alert -->
<div
class="DCEReactKit-pop-in alert alert-warning d-inline-block"
style="width: 50em; max-width: 100%"
>
<!-- Title -->
<h2>
<i class="me-1 fa-solid fa-triangle-exclamation"></i>
${title}
</h2>
<!-- Description -->
<div>
${description}
</div>
</div>
<!-- Error Code -->
<div class="DCEReactKit-slide-in text-light">
<strong>
Error Code:
</strong>
${code}
</div>
</body>
`;
};
var genErrorPage_default = genErrorPage;
// dce-reactkit/html/genInfoPage.ts
var genInfoPage = (opts) => {
const {
title,
body
} = opts;
return `
<head>
<!-- Metadata -->
<meta
name="viewport"
content="width=device-width, height=device-height, initial-scale=1.0, minimum-scale=1.0"
>
<!-- Title -->
<title>${title}</title>
<!-- Bootstrap -->
<link
rel="stylesheet"
href="https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.2.1/css/bootstrap.min.css"
integrity="sha512-siwe/oXMhSjGCwLn+scraPOWrJxHlUgMBMZXdPe2Tnk3I0x3ESCoLz7WZ5NTH6SZrywMY+PB1cjyqJ5jAluCOg=="
crossorigin="anonymous"
referrerpolicy="no-referrer"
/>
<script
src="https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.2.1/js/bootstrap.min.js"
integrity="sha512-vyRAVI0IEm6LI/fVSv/Wq/d0KUfrg3hJq2Qz5FlfER69sf3ZHlOrsLriNm49FxnpUGmhx+TaJKwJ+ByTLKT+Yg=="
crossorigin="anonymous"
referrerpolicy="no-referrer"
></script>
<!-- FontAwesome -->
<link
rel="stylesheet"
href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.2.0/css/all.min.css"
integrity="sha512-xh6O/CkQoPOWDdYTDqeRdPCVd1SpvCA9XXcUnZS2FmJNp1coAFzvtCN9BmamE+4aHK8yyUHUSCcJHgXloTyT2A=="
crossorigin="anonymous"
referrerpolicy="no-referrer"
/>
<!-- Style -->
<style>
.DCEReactKit-pop-in {
animation-name: DCEReactKit-pop-in;
animation-duration: 0.5s;
animation-iteration-count: 1;
animation-timing-function: ease-out;
animation-fill-mode: both;
transform-origin: center;
}
@keyframes DCEReactKit-pop-in {
0% {
opacity: 0;
transform: scale(0.9);
}
100% {
opacity: 1;
transform: scale(1);
}
}
</style>
</head>
<!-- Body -->
<body class="bg-dark text-center pt-3 ps-3 pe-3">
<!-- Alert -->
<div
class="DCEReactKit-pop-in alert alert-info d-inline-block"
style="width: 50em; max-width: 100%"
>
<!-- Title -->
<h2>
<i class="me-1 fa-solid fa-circle-info"></i>
${title}
</h2>
<!-- Body -->
<div>
${body}
</div>
</div>
</body>
`;
};
var genInfoPage_default = genInfoPage;
// dce-reactkit/helpers/genRouteHandler.ts
var genRouteHandler = (opts) => {
return (req, res, next) => __async(void 0, null, function* () {
var _a, _b, _c;
const output = {};
const paramList = Object.entries((_a = opts.paramTypes) != null ? _a : {});
for (let i = 0; i < paramList.length; i++) {
const [name, type] = paramList[i];
const value = req.params[name] || req.query[name] || req.body[name];
if (type === ParamType_default.Boolean || type === ParamType_default.BooleanOptional) {
if (value === void 0) {
if (type === ParamType_default.BooleanOptional) {
output[name] = void 0;
} else {
return handleError_default(
res,
{
message: `Parameter ${name} is required, but it was not included.`,
code: ReactKitErrorCode_default.MissingParameter,
status: 422
}
);
}
} else {
const simpleVal = String(value).trim().toLowerCase();
output[name] = [
"true",
"yes",
"y",
"1",
"t"
].indexOf(simpleVal) >= 0;
}
} else if (type === ParamType_default.Float || type === ParamType_default.FloatOptional) {
if (value === void 0) {
if (type === ParamType_default.FloatOptional) {
output[name] = void 0;
} else {
return handleError_default(
res,
{
message: `Parameter ${name} is required, but it was not included.`,
code: ReactKitErrorCode_default.MissingParameter,
status: 422
}
);
}
} else if (!Number.isNaN(Number.parseFloat(String(value)))) {
output[name] = Number.parseFloat(String(value));
} else {
return handleError_default(
res,
{
message: `Request data was malformed: ${name} was not a valid float.`,
code: ReactKitErrorCode_default.InvalidParameter,
status: 422
}
);
}
} else if (type === ParamType_default.Int || type === ParamType_default.IntOptional) {
if (value === void 0) {
if (type === ParamType_default.IntOptional) {
output[name] = void 0;
} else {
return handleError_default(
res,
{
message: `Parameter ${name} is required, but it was not included.`,
code: ReactKitErrorCode_default.MissingParameter,
status: 422
}
);
}
} else if (!Number.isNaN(Number.parseInt(String(value), 10))) {
output[name] = Number.parseInt(String(value), 10);
} else {
return handleError_default(
res,
{
message: `Request data was malformed: ${name} was not a valid int.`,
code: ReactKitErrorCode_default.InvalidParameter,
status: 422
}
);
}
} else if (type === ParamType_default.JSON || type === ParamType_default.JSONOptional) {
if (value === void 0) {
if (type === ParamType_default.JSONOptional) {
output[name] = void 0;
} else {
return handleError_default(
res,
{
message: `Parameter ${name} is required, but it was not included.`,
code: ReactKitErrorCode_default.MissingParameter,
status: 422
}
);
}
} else {
try {
output[name] = JSON.parse(String(value));
} catch (err) {
return handleError_default(
res,
{
message: `Request data was malformed: ${name} was not a valid JSON payload.`,
code: ReactKitErrorCode_default.InvalidParameter,
status: 422
}
);
}
}
} else if (type === ParamType_default.String || type === ParamType_default.StringOptional) {
if (value === void 0) {
if (type === ParamType_default.StringOptional) {
output[name] = void 0;
} else {
return handleError_default(
res,
{
message: `Parameter ${name} is required, but it was not included.`,
code: ReactKitErrorCode_default.MissingParameter,
status: 422
}
);
}
} else {
output[name] = value;
}
} else {
return handleError_default(
res,
{
message: `An internal error occurred: we could not determine the type of ${name}.`,
code: ReactKitErrorCode_default.InvalidParameter,
status: 422
}
);
}
}
const { launched, launchInfo } = getLaunchInfo_default(req);
if (
// Not launched
(!launched || !launchInfo) && !opts.skipSessionCheck
) {
return handleError_default(
res,
{
message: "Your session has expired. Please refresh the page and try again.",
code: ReactKitErrorCode_default.SessionExpired,
status: 401
}
);
}
if (
// User information is incomplete
(!launchInfo || !launchInfo.userId || !launchInfo.userFirstName || !launchInfo.userLastName || launchInfo.notInCourse && !launchInfo.isAdmin || !launchInfo.isTTM && !launchInfo.isLearner && !launchInfo.isAdmin) && !opts.skipSessionCheck
) {
return handleError_default(
res,
{
message: "Your session was invalid. Please refresh the page and try again.",
code: ReactKitErrorCode_default.SessionExpired,
status: 401
}
);
}
output.userId = launchInfo ? launchInfo.userId : void 0;
output.userFirstName = launchInfo ? launchInfo.userFirstName : void 0;
output.userLastName = launchInfo ? launchInfo.userLastName : void 0;
output.userEmail = launchInfo ? launchInfo.userEmail : void 0;
output.userAvatarURL = launchInfo ? (_b = launchInfo.userImage) != null ? _b : "http://www.gravatar.com/avatar/?d=identicon" : void 0;
output.isLearner = launchInfo ? !!launchInfo.isLearner : void 0;
output.isTTM = launchInfo ? !!launchInfo.isTTM : void 0;
output.isAdmin = launchInfo ? !!launchInfo.isAdmin : void 0;
output.courseId = launchInfo ? (_c = output.courseId) != null ? _c : launchInfo.courseId : void 0;
output.courseName = launchInfo ? launchInfo.contextLabel : void 0;
Object.keys(req.session).forEach((propName) => {
if (output[propName] !== void 0) {
return;
}
const value = req.session[propName];
if (typeof value === "string" || typeof value === "boolean" || typeof value === "number") {
output[propName] = value;
}
});
if (output.courseId && launchInfo && launchInfo.courseId && output.courseId !== launchInfo.courseId && !output.isTTM && !output.isAdmin) {
return handleError_default(
res,
{
message: "You switched sessions by opening this app in another tab. Please refresh the page and try again.",
code: ReactKitErrorCode_default.WrongCourse,
status: 401
}
);
}
if (
// This is a TTM endpoint
req.path.startsWith("/api/ttm") && // User is not a TTM
(!output.isTTM && !output.isAdmin)
) {
return handleError_default(
res,
{
message: "This action is only allowed if you are a teaching team member for the course. Please go back to Canvas, log in as a teaching team member, and try again.",
code: ReactKitErrorCode_default.NotTTM,
status: 401
}
);
}
if (
// This is an admin endpoint
req.path.startsWith("/api/admin") && !output.isAdmin
) {
return handleError_default(
res,
{
message: "This action is only allowed if you are a Canvas admin. Please go back to Canvas, log in as an admin, and try again.",
code: ReactKitErrorCode_default.NotAdmin,
status: 401
}
);
}
let responseSent = false;
const redirect = (pathOrURL) => {
responseSent = true;
res.redirect(pathOrURL);
};
const send = (text, status = 200) => {
responseSent = true;
res.status(status).send(text);
};
const renderErrorPage = (renderOpts = {}) => {
var _a2;
const html = genErrorPage_default(renderOpts);
send(html, (_a2 = renderOpts.status) != null ? _a2 : 500);
};
const renderInfoPage = (renderOpts) => {
const html = genInfoPage_default(renderOpts);
send(html, 200);
};
try {
const results = yield opts.handler({
params: output,
req,
send,
next: () => {
responseSent = true;
next();
},
redirect,
renderErrorPage,
renderInfoPage
});
if (!responseSent) {
return handleSuccess_default(res, results != null ? results : void 0);
}
} catch (err) {
if (!responseSent) {
handleError_default(res, err);
return;
}
console.log("Error occurred but could not be sent to client because a response was already sent:", err);
}
});
};
var genRouteHandler_default = genRouteHandler;
// src/shared/types/AssignToGroups.ts
var AssignToGroups = /* @__PURE__ */ ((AssignToGroups2) => {
AssignToGroups2["Always"] = "Always";
AssignToGroups2["Never"] = "Never";
AssignToGroups2["Ask"] = "Ask";
return AssignToGroups2;
})(AssignToGroups || {});
var AssignToGroups_default = AssignToGroups;
// src/shared/types/AttendanceMethod.ts
var AttendanceMethod = /* @__PURE__ */ ((AttendanceMethod2) => {
AttendanceMethod2["InPerson"] = "in-person";
AttendanceMethod2["Live"] = "live";
AttendanceMethod2["Async"] = "async";
AttendanceMethod2["Excused"] = "excused";
return AttendanceMethod2;
})(AttendanceMethod || {});
var AttendanceMethod_default = AttendanceMethod;
// src/shared/types/ErrorCode.ts
var ErrorCode = /* @__PURE__ */ ((ErrorCode2) => {
ErrorCode2["InvalidURL"] = "DCHUI1";
ErrorCode2["EventNotFound"] = "DCHUI2";
ErrorCode2["EventNotHeldInPerson"] = "DCHUI3";
ErrorCode2["OccurrenceNotSetUpToday"] = "DCHUI4";
ErrorCode2["UnknownCheckInError"] = "DCHUI5";
return ErrorCode2;
})(ErrorCode || {});
var ErrorCode_default = ErrorCode;
// src/shared/helpers/toStringMMDDYY.ts
var toStringMMDDYY = (mmddyy) => {
const {
year,
month,
day
} = mmddyy;
return `${year}.${month}.${day}`;
};
var toStringMMDDYY_default = toStringMMDDYY;
// src/shared/helpers/genOccurrenceId.ts
var genOccurrenceId = (occurrence) => {
const _a = occurrence, {
courseId,
ihid
} = _a, mmddyy = __objRest(_a, [
"courseId",
"ihid"
]);
const formattedDate = toStringMMDDYY_default(mmddyy);
return `${courseId}-${ihid}-O${formattedDate}`;
};
var genOccurrenceId_default = genOccurrenceId;
// src/shared/helpers/getTimeInfoInET.ts
var getTimeInfoInET = (dateOrTimestamp) => {
let d;
if (!dateOrTimestamp) {
d = /* @__PURE__ */ new Date();
} else if (typeof dateOrTimestamp === "number") {
d = new Date(dateOrTimestamp);
} else {
d = dateOrTimestamp;
}
const str = d.toLocaleString(
"en-US",
// Using US encoding (it's the only one installed on containers)
{ timeZone: "America/New_York" }
// Force EST timezone
);
const [dateStr, timeStr] = str.split(", ");
const [monthStr, dayStr, yearStr] = dateStr.split("/");
const [hourStr, minStr, ending] = timeStr.split(":");
const timestamp = d.getTime();
const year = Number.parseInt(yearStr, 10);
const month = Number.parseInt(monthStr, 10);
const day = Number.parseInt(dayStr, 10);
const minute = Number.parseInt(minStr, 10);
const hour12 = Number.parseInt(hourStr, 10);
const isAM = ending.toLowerCase().includes("am");
const isPM = !isAM;
let hour = hour12;
if (isPM && hour12 !== 12) {
hour += 12;
} else if (isAM && hour12 === 12) {
hour = 0;
}
return {
timestamp,
year,
month,
day,
hour,
hour12,
isPM,
minute
};
};
var getTimeInfoInET_default = getTimeInfoInET;
// src/shared/helpers/createOccurrence.ts
var createOccurrence = (occurrence) => __async(void 0, null, function* () {
const occurrenceCollection2 = getOccurrenceCollection();
yield occurrenceCollection2.insert(__spreadValues({
id: genOccurrenceId_default(occurrence)
}, occurrence));
});
var createOccurrence_default = createOccurrence;
// src/shared/helpers/findOccurrence.ts
var findOccurrence = (occurrenceId) => __async(void 0, null, function* () {
const occurrenceCollection2 = getOccurrenceCollection();
const occurrenceDocs = yield occurrenceCollection2.find({ id: occurrenceId });
if (occurrenceDocs.length === 0) {
return null;
}
const _a = occurrenceDocs[0], { id } = _a, occurrence = __objRest(_a, ["id"]);
return occurrence;
});
var findOccurrence_default = findOccurrence;
// src/addRoutes/addStudentRoutes/addAttendanceRoutes/checkInEJSTemplate/HTML.ts
var HTML = `
<body class="bg-success">
<div class="outer-container">
<div class="container">
<h2> Welcome, <span class="fw-bold"><%= userFirstName %></span>! </h2>
<div
class="PopSuccessMark-outer-container bg-white"
aria-label="checkmark indicating success"
>
<div
class="PopSuccessMark-check-stroke-1 bg-success"
></div>
<div
class="PopSuccessMark-check-stroke-2 bg-success"
></div>
</div>
<% if (groupNum !== undefined) { %>
<h2> Group <%= groupNum %> </h2>
<% } %>
</div>
</div>
</body>
`;
var HTML_default = HTML;
// src/addRoutes/addStudentRoutes/addAttendanceRoutes/checkInEJSTemplate/STYLE.ts
var checkMarkWidthVmin = 30;
var STYLE = `
<!-- Import FontAwesome -->
<link
rel="stylesheet"
href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css"
>
<!-- Import Bootstrap -->
<link
href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css"
rel="stylesheet"
integrity="sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC"
crossorigin="anonymous"
>
<!-- Custom styles -->
<style>
h2 {
font-size: 8vmin ;
}
.outer-container {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
min-height: 100vh;
}
.container {
text-align: center;
color: white;
}
.PopSuccessMark-outer-container {
position: relative;
display: inline-block;
border-radius: 50%;
animation-name: PopSuccessMark-outer-container;
animation-duration: 0.8s;
animation-fill-mode: both;
animation-iteration-count: 1;
animation-timing-function: ease-out;
width: ${checkMarkWidthVmin}vmin;
height: ${checkMarkWidthVmin}vmin;
}
@keyframes PopSuccessMark-outer-container {
0% {
opacity: 0;
transform: scale(1.5);
filter: saturate(0);
}
80.7% {
opacity: 1;
transform: scale(1);
filter: saturate(0);
}
100% {
opacity: 1;
transform: scale(1);
filter: saturate(1);
}
}
.PopSuccessMark-check-stroke-1 {
position: absolute;
left: 20%;
top: 36%;
border-radius: ${checkMarkWidthVmin / 5}vmin;
display: inline-block;
height: 16%;
width: 35%;
transform-origin: left;
animation-name: PopSuccessMark-check-stroke-1;
animation-duration: 0.3s;
animation-delay: 0.3s;
animation-fill-mode: both;
animation-iteration-count: 1;
animation-timing-function: ease-in;
}
@keyframes PopSuccessMark-check-stroke-1 {
0% {
transform: rotate(45deg) scaleX(0);
}
100% {
transform: rotate(45deg) scaleX(1);
}
}
.PopSuccessMark-check-stroke-2 {
position: absolute;
left: 35%;
top: 63%;
border-radius: ${checkMarkWidthVmin / 5}vmin;
display: inline-block;
height: 16%;
width: 60%;
transform-origin: left;
animation-name: PopSuccessMark-check-stroke-2;
animation-duration: 0.3s;
animation-delay: 0.6s;
animation-fill-mode: both;
animation-iteration-count: 1;
animation-timing-function: ease-out;
}
@keyframes PopSuccessMark-check-stroke-2 {
0% {
transform: rotate(-40deg) scaleX(0);
}
100% {
transform: rotate(-40deg) scaleX(1);
}
}
</style>
`;
var STYLE_default = STYLE;
// src/addRoutes/addStudentRoutes/addAttendanceRoutes/checkInEJSTemplate/index.ts
var checkInEJSTemplate = `
<html>
<head>
${STYLE_default}
</head>
${HTML_default}
</html>
`;
var checkInEJSTemplate_default = checkInEJSTemplate;
// src/addRoutes/addStudentRoutes/addAttendanceRoutes/addCheckInPageRoute.ts
var addCheckInPageRoute = (router, recordAttendance, getCourseEvent) => {
router.get(
"/checkin/:ihid",
genRouteHandler_default({
paramTypes: {
ihid: ParamType_default.String
},
handler: (_0) => __async(void 0, [_0], function* ({ params, send, renderErrorPage }) {
const {
ihid,
courseId,
userId,
userFirstName,
userLastName,
isLearner
} = params;
try {
const {
day,
month,
year
} = getTimeInfoInET_default();
const occurrenceBasicInfo = {
courseId,
ihid,
day,
month,
year
};
const occurrenceId = genOccurrenceId_default(occurrenceBasicInfo);
let occurrence = yield findOccurrence_default(occurrenceId);
if (!occurrence) {
const event = yield getCourseEvent({
courseId,
ihid,
requireNotArchived: true
});
if (!event) {
return renderErrorPage({
title: "Could not find event",
description: "Could not find event. Please double-check your URL or contact your instructor to set it up.",
code: ErrorCode_default.EventNotFound
});
}
if (!event.inPersonConfig) {
return renderErrorPage({
title: "Event Not Held In Person",
description: "This event is not held in person, so you cannot check in. Please contact your instructor if you think this is wrong.",
code: ErrorCode_default.EventNotHeldInPerson
});
}
if (event.inPersonConfig.assignToGroups !== AssignToGroups_default.Never) {
return renderErrorPage({
title: "Event Not Started Yet",
description: "This event has not started yet, so you cannot check in. Please wait for your instructor to initialize check in.",
code: ErrorCode_default.OccurrenceNotSetUpToday
});
}
occurrence = __spreadProps(__spreadValues({}, occurrenceBasicInfo), {
grouperEnabled: false
});
yield createOccurrence_default(occurrence);
}
let groupNum;
if (occurrence.grouperEnabled) {
const mmddyyStr = toStringMMDDYY_default({
day,
month,
year
});
const grouperRequest = {
courseId,
eventId: `${ihid}-O${mmddyyStr}`,
studentId: userId
};
const groupAssignment = yield (0, import_dce_live_grouper2.getGroupAssignment)(grouperRequest);
groupNum = groupAssignment.groupNum;
}
const attendanceEntry = {
userId,
userFirstName,
userLastName,
courseId,
ihid,
isLearner,
method: AttendanceMethod_default.InPerson,
groupNum
};
yield recordAttendance(attendanceEntry);
const page = import_ejs.default.render(
checkInEJSTemplate_default,
{
userFirstName,
groupNum
}
);
return send(page);
} catch (err) {
console.log("Error:", err);
return renderErrorPage({
title: "An Unknown Error Occurred",
description: "We couldn't check you in because an unknown error occurred. Try again. If the issue persists, contact your instructor.",
code: ErrorCode_default.UnknownCheckInError
});
}
})
})
);
};
var addCheckInPageRoute_default = addCheckInPageRoute;
// src/addRoutes/addStudentRoutes/addAttendanceRoutes/addGetRosterRoute.ts
var canvasUserToUser = (canvasUser) => {
const {
id: userId,
first_name: userFirstName,
last_name: userLastName
} = canvasUser;
return {
userId,
userFirstName,
userLastName
};
};
var addGetRosterRoute = (router, getCanvasAPI) => {
router.get(
"/students",
genRouteHandler_default({
handler: () => __async(void 0, null, function* () {
const canvas = getCanvasAPI();
const canvasUserList = yield canvas.course.listStudents();
const userList = canvasUserList.map(canvasUserToUser);
return userList;
})
})
);
};
var addGetRosterRoute_default = addGetRosterRoute;
// src/addRoutes/addStudentRoutes/addAttendanceRoutes/index.ts
var addAttendanceRoutes = (opts) => {
const {
router,
recordAttendance,
getCanvasAPI,
getCourseEvent
} = opts;
const attendanceRouter = import_express.default.Router();
addGetRosterRoute_default(attendanceRouter, getCanvasAPI);
addCheckInPageRoute_default(attendanceRouter, recordAttendance, getCourseEvent);
router.use("/attendance", attendanceRouter);
};
var addAttendanceRoutes_default = addAttendanceRoutes;
// src/addRoutes/addStudentRoutes/index.ts
var addStudentRoutes = (recordAttendance, getCanvasAPI, getCourseEvent) => {
const router = import_express2.default.Router();
addAttendanceRoutes_default({
router,
recordAttendance,
getCanvasAPI,
getCourseEvent
});
return router;
};
var addStudentRoutes_default = addStudentRoutes;
// src/addRoutes/addTTMRoutes/index.ts
var import_express5 = __toESM(require("express"));
// src/addRoutes/addTTMRoutes/addGrouperRoutes/index.ts
var import_express3 = __toESM(require("express"));
// src/addRoutes/addTTMRoutes/addGrouperRoutes/addGetEventRoute.ts
var import_dce_live_grouper3 = require("dce-live-grouper");
var addGetEventRoute = (router) => {
router.get(
"/event",
genRouteHandler_default({
paramTypes: {
courseId: ParamType_default.Int,
eventId: ParamType_default.String
},
handler: (_0) => __async(void 0, [_0], function* ({ params }) {
const {
courseId,
eventId
} = params;
return (0, import_dce_live_grouper3.getEvent)(courseId, eventId);
})
})
);
};
var addGetEventRoute_default = addGetEventRoute;
// src/addRoutes/addTTMRoutes/addGrouperRoutes/addCreateEventRoute.ts
var import_dce_live_grouper4 = require("dce-live-grouper");
var addCreateEventRoute = (router) => {
router.post(
"/event",
genRouteHandler_default({
paramTypes: {
createEventBody: ParamType_default.JSON
},
handler: (_0) => __async(void 0, [_0], function* ({ params }) {
const { createEventBody } = params;
return (0, import_dce_live_grouper4.createEvent)(createEventBody);
})
})
);
};
var addCreateEventRoute_default = addCreateEventRoute;
// src/addRoutes/addTTMRoutes/addGrouperRoutes/addGetGroupAssignmentRoute.ts
var import_dce_live_grouper5 = require("dce-live-grouper");
var addGetGroupAssignmentRoute = (router) => {
router.post(
"/group-assignment",
genRouteHandler_default({
paramTypes: {
grouperRequest: ParamType_default.JSON
},
handler: (_0) => __async(void 0, [_0], function* ({ params }) {
const { grouperRequest } = params;
return (0, import_dce_live_grouper5.getGroupAssignment)(grouperRequest);
})
})
);
};
var addGetGroupAssignmentRoute_default = addGetGroupAssignmentRoute;
// src/addRoutes/addTTMRoutes/addGrouperRoutes/index.ts
var addGrouperRoutes = (router) => {
const grouperRouter = import_express3.default.Router();
addGetEventRoute_default(grouperRouter);
addCreateEventRoute_default(grouperRouter);
addGetGroupAssignmentRoute_default(grouperRouter);
router.use("/grouper", grouperRouter);
};
var addGrouperRoutes_default = addGrouperRoutes;
// src/addRoutes/addTTMRoutes/addOccurrenceRoutes/index.ts
var import_express4 = __toESM(require("express"));
// src/addRoutes/addTTMRoutes/addOccurrenceRoutes/addGetOccurrenceRoute.ts
var addGetOccurrenceRoute = (router) => {
router.get(
"",
genRouteHandler_default({
paramTypes: {
occurrenceId: ParamType_default.String
},
handler: (_0) => __async(void 0, [_0], function* ({ params }) {
const { occurrenceId } = params;
const occurrence = yield findOccurrence_default(occurrenceId);
return occurrence;
})
})
);
};
var addGetOccurrenceRoute_default = addGetOccurrenceRoute;
// src/addRoutes/addTTMRoutes/addOccurrenceRoutes/addCreateOccurrenceRoute.ts
var addCreateOccurrenceRoute = (router) => {
router.post(
"",
genRouteHandler_default({
paramTypes: {
occurrence: ParamType_default.JSON
},
handler: (_0) => __async(void 0, [_0], function* ({ params }) {
const { occurrence } = params;
yield createOccurrence_default(occurrence);
})
})
);
};
var addCreateOccurrenceRoute_default = addCreateOccurrenceRoute;
// src/addRoutes/addTTMRoutes/addOccurrenceRoutes/addGetSeriesListRoute.ts
var addGetSeriesListRoute = (router) => {
router.get(
"/series-list",
genRouteHandler_default({
paramTypes: {
courseId: ParamType_default.Int,
ihid: ParamType_default.String
},
handler: (_0) => __async(void 0, [_0], function* ({ params }) {
const {
courseId,
ihid
} = params;
const occurrenceCollection2 = getOccurrenceCollection();
const seriesDocList = yield occurrenceCollection2.find({
courseId,
ihid
});
const seriesList = seriesDocList.filter((doc) => {
return doc.grouperEnabled;
}).map((doc) => {
const _a = doc, {
id
} = _a, occ = __objRest(_a, [
"id"
]);
return occ;
});
return seriesList;
})
})
);
};
var addGetSeriesListRoute_default = addGetSeriesListRoute;
// src/addRoutes/addTTMRoutes/addOccurrenceRoutes/index.ts
var addOccurrenceRoutes = (router) => {
const occurrenceRouter = import_express4.default.Router();
addGetOccurrenceRoute_default(occurrenceRouter);
addCreateOccurrenceRoute_default(occurrenceRouter);
addGetSeriesListRoute_default(occurrenceRouter);
router.use("/occurrence", occurrenceRouter);
};
var addOccurrenceRoutes_default = addOccurrenceRoutes;
// src/addRoutes/addTTMRoutes/index.ts
var addTTMRoutes = () => {
const router = import_express5.default.Router();
addOccurrenceRoutes_default(router);
addGrouperRoutes_default(router);
return router;
};
var addTTMRoutes_default = addTTMRoutes;
// src/index.ts
var addCheckInRoutes = (opts) => {
const {
app,
getCanvasAPI,
CollectionCons,
recordAttendance,
getCourseEvent,
logAction,
logError
} = opts;
initCollections(CollectionCons);
initLogger(logAction, logError);
const ttmRouter = addTTMRoutes_default();
app.use(TTM_PATH_PREFIX_default, ttmRouter);
const studentRouter = addStudentRoutes_default(
recordAttendance,
getCanvasAPI,
getCourseEvent
);
app.use(STUDENT_PATH_PREFIX_default, studentRouter);
};
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
addCheckInRoutes
});