UNPKG

alpha-version-dce-check-in-api

Version:

API for taking attendance and checking people in.

1,350 lines (1,269 loc) 41.4 kB
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 !important; } .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 = `<!DOCTYPE html> <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 });