From b2df48079e59c99326732ed5431266a3a09dd915 Mon Sep 17 00:00:00 2001 From: Paul Hutchings <36493057+paulhutchings@users.noreply.github.com> Date: Thu, 21 Nov 2019 00:21:51 -0500 Subject: [PATCH 01/39] updated cache module --- lambda/scheduler/cache.js | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/lambda/scheduler/cache.js b/lambda/scheduler/cache.js index 219412d..b4c2d1f 100644 --- a/lambda/scheduler/cache.js +++ b/lambda/scheduler/cache.js @@ -5,11 +5,8 @@ */ module.exports = function(){ - if (!aws){ - var aws = require('aws-sdk'); - } - if (!documentclient){ + var aws = require('aws-sdk'); var documentclient = new aws.DynamoDB.DocumentClient(); } From c5aa76d72c5ba4d718ecee7d5f2c85a1e3e6a964 Mon Sep 17 00:00:00 2001 From: Christopher Bilger Date: Thu, 21 Nov 2019 09:38:27 -0500 Subject: [PATCH 02/39] Initial linter file to check directories for JS files to lint --- linter.sh | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100755 linter.sh diff --git a/linter.sh b/linter.sh new file mode 100755 index 0000000..9fc9d84 --- /dev/null +++ b/linter.sh @@ -0,0 +1,17 @@ +#!/bin/bash + +for f in $(find hello-world/ -name '*.js'); do + jshint $f +done + +for f in $(find lambda/ -name '*.js'); do + jshint $f +done + +for f in $(find s3/ -name '*.js'); do + jshint $f +done + +for f in $(find template/ -name '*.js'); do + jshint $f +done From ceb39dbd5c7fbef21f64785dbc339876e2e96138 Mon Sep 17 00:00:00 2001 From: Christopher Bilger Date: Thu, 21 Nov 2019 09:47:47 -0500 Subject: [PATCH 03/39] Formatted hello-world/ using jshint --- hello-world/cache.js | 31 ++++++++++++++++++------------- hello-world/index.js | 5 +++-- 2 files changed, 21 insertions(+), 15 deletions(-) diff --git a/hello-world/cache.js b/hello-world/cache.js index a68e5de..8156345 100644 --- a/hello-world/cache.js +++ b/hello-world/cache.js @@ -1,26 +1,31 @@ +/*jshint esversion: 8 */ + /** - * @module Cache - * Controls the state of global objects representing connections to AWS services that can be reused throughout invokations. + * @module Cache + * Controls the state of global objects representing connections to AWS services that can be reused throughout invokations. * If the variable does not exist in the namespace, it is instantiated and then reused upon subsequent executions in the same container. */ -module.exports = (params={S3:{endpoint: 'http://localhost:4572', s3ForcePathStyle: true, credentials: {accessKeyId: 'foo', secretAccessKey: 'bar'}}, - DocumentClient:{endpoint: 'http://localhost:4569'}, region: 'us-east-1'}) => { +var PARAMS = {S3: {endpoint: 'http://localhost:4572', s3ForcePathStyle: true, credentials: {accessKeyId: 'foo', secretAccessKey: 'bar'}}, + DocumentClient: {endpoint: 'http://localhost:4569'}, region: 'us-east-1'}; +module.exports = (params = PARAMS) => { + var aws = null; if (!aws){ - var aws = require('aws-sdk'); + aws = require('aws-sdk'); } + var documentclient = null; if (!documentclient){ - var documentclient = new aws.DynamoDB.DocumentClient(params.DocumentClient); + documentclient = new aws.DynamoDB.DocumentClient(params.DocumentClient); } + var s3 = null; if (!s3){ - var s3 = new aws.S3(params.S3); - } - - return { - S3: s3, - DocumentClient: documentclient + s3 = new aws.S3(params.S3); } -} \ No newline at end of file + return { + S3: s3, + DocumentClient: documentclient + }; +}; diff --git a/hello-world/index.js b/hello-world/index.js index 885e504..b33cbff 100644 --- a/hello-world/index.js +++ b/hello-world/index.js @@ -1,3 +1,5 @@ +/*jshint esversion: 8 */ + /** * Load the caching module if it doesn't exist in the namespace */ @@ -55,5 +57,4 @@ exports.handler = async function(event, context){ return error; } -} - +}; From 7828d957cc382d07d279943943e460264598138b Mon Sep 17 00:00:00 2001 From: Christopher Bilger Date: Thu, 21 Nov 2019 09:48:11 -0500 Subject: [PATCH 04/39] Formatted lambda/node_modules/banner/ using jshint --- lambda/node_modules/banner/index.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lambda/node_modules/banner/index.js b/lambda/node_modules/banner/index.js index c1c58af..e0161ae 100644 --- a/lambda/node_modules/banner/index.js +++ b/lambda/node_modules/banner/index.js @@ -1,3 +1,5 @@ +/*jshint esversion: 8*/ + const https = require('https'); const querystring = require('querystring'); @@ -194,4 +196,4 @@ async function promiseRequest(options, data=null){ }); } -module.exports = Banner; \ No newline at end of file +module.exports = Banner; From 93cdce1cd5bd3d3a721619630e93fd0234cb8136 Mon Sep 17 00:00:00 2001 From: Christopher Bilger Date: Thu, 21 Nov 2019 09:48:27 -0500 Subject: [PATCH 05/39] Formatted lambda/node_modules/models/ using jshint --- lambda/node_modules/models/index.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lambda/node_modules/models/index.js b/lambda/node_modules/models/index.js index 620d563..0c5ebc2 100644 --- a/lambda/node_modules/models/index.js +++ b/lambda/node_modules/models/index.js @@ -1,4 +1,4 @@ - +/*jshint esversion: 8*/ /** * @module @@ -26,7 +26,7 @@ module.exports = { 'campus': section.meetingsFaculty[0].meetingTime.campus, 'professor': this._getInstructor(section), 'classTimes': this._getClasstimes(section) - } + }; }); }, @@ -79,4 +79,4 @@ module.exports = { } } -} +}; From 388bc9552cc3358c53a18758fa8e52594ba35ea0 Mon Sep 17 00:00:00 2001 From: Christopher Bilger Date: Thu, 21 Nov 2019 09:48:41 -0500 Subject: [PATCH 06/39] Formatted lambda/test/ using jshint --- lambda/test/banner.js | 6 ++++-- lambda/test/models.js | 6 ++++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/lambda/test/banner.js b/lambda/test/banner.js index 944848e..17f39a6 100644 --- a/lambda/test/banner.js +++ b/lambda/test/banner.js @@ -1,3 +1,5 @@ +/*jshint esversion: 8*/ + var assert = require('assert'); var Banner = require('banner'); @@ -5,7 +7,7 @@ describe('Banner', async function () { /** * SETUP */ - const term = 202003 + const term = 202003; /** * BUILD() @@ -154,4 +156,4 @@ describe('Banner', async function () { assert(await banner.classSearch('CIS')); }); }); -}); \ No newline at end of file +}); diff --git a/lambda/test/models.js b/lambda/test/models.js index b6c45a9..505f573 100644 --- a/lambda/test/models.js +++ b/lambda/test/models.js @@ -1,3 +1,5 @@ +/*jshint esversion: 8*/ + var models = require('models'); var asssert = require('assert'); var fs = require ('fs'); @@ -7,7 +9,7 @@ var readFile = promisify(fs.readFile); describe('models', function() { describe('#BannerToDB.convert', function() { - var testData = {} + var testData = {}; before(async function(){ testData = JSON.parse(await readFile('./test/models.json')); }); @@ -16,4 +18,4 @@ describe('models', function() { testData.forEach(example => asssert.deepEqual(models.BannerToDB.convert([example.input]), [example.output])); }); }); -}); \ No newline at end of file +}); From e0271fb6ff4066526a60cfbadbd3f97b9b0bf14a Mon Sep 17 00:00:00 2001 From: Christopher Bilger Date: Thu, 21 Nov 2019 09:48:53 -0500 Subject: [PATCH 07/39] Formatted s3/clientScripts/ using jshint --- s3/clientScripts/api.js | 6 ++++-- s3/clientScripts/form.js | 9 ++++++--- s3/clientScripts/schedule.js | 21 ++++++++++++--------- s3/clientScripts/time.js | 12 +++++++----- 4 files changed, 29 insertions(+), 19 deletions(-) diff --git a/s3/clientScripts/api.js b/s3/clientScripts/api.js index 3324b37..e09f42a 100644 --- a/s3/clientScripts/api.js +++ b/s3/clientScripts/api.js @@ -1,3 +1,5 @@ +/*jshint esversion: 8*/ + /** * postClassTimes sends a POST request to get the schedules. * @param {Object} params : An object containing the following keys: @@ -10,7 +12,7 @@ */ async function postClassTimes(params) { let courses = params.courses || []; - let days = params.days || "MTWRF" + let days = params.days || "MTWRF"; let startTime = params.startTime || 0; let endTime = params.endTime || 2359; let campus = params.campus || "MN"; @@ -33,4 +35,4 @@ async function postClassTimes(params) { var results = await response.json(); return results; -} \ No newline at end of file +} diff --git a/s3/clientScripts/form.js b/s3/clientScripts/form.js index 9152fc3..c71f8fb 100644 --- a/s3/clientScripts/form.js +++ b/s3/clientScripts/form.js @@ -1,3 +1,6 @@ +/*jshint esversion: 8*/ +/*jshint -W083*/ + /** * * @param {Object} params : an Object containing the following keys: @@ -41,7 +44,7 @@ function Chips(params) { X ` - ).on('click', function() { + ).on('click', function() { availableTags.push(selectedTag); var i = selected.indexOf(selectedTag); selected.splice(i, 1); @@ -54,7 +57,7 @@ function Chips(params) { Chips.getSelected = () => { return selected; - } + }; return Chips; -} \ No newline at end of file +} diff --git a/s3/clientScripts/schedule.js b/s3/clientScripts/schedule.js index 74329e5..ec7cb54 100644 --- a/s3/clientScripts/schedule.js +++ b/s3/clientScripts/schedule.js @@ -1,3 +1,6 @@ +/*jshint esversion: 8*/ +/*jshint -W014*/ + var Schedule = {}; /** @@ -26,9 +29,9 @@ Schedule.init = () => { } // Create row divs. - for(var i = 8; i < 18; i++) { - let displayNum = i; - if(i > 12) { + for(var j = 8; j < 18; j++) { + let displayNum = j; + if(j > 12) { displayNum -= 12; } $('.schedule').append( @@ -39,7 +42,7 @@ Schedule.init = () => { ` ); } -} +}; /** * setSchedule adds classTime objects to the Schedule. @@ -81,9 +84,9 @@ Schedule.addClassTime = (divId, classTime) => { var rgb = [0, 0, 0]; - for (var i = 0; i < 3; i++) { - var value = (hash >> (i * 8)) & 255; - rgb[i] = value; + for (var j = 0; j < 3; j++) { + var value = (hash >> (j * 8)) & 255; + rgb[j] = value; } return `rgba(${rgb[0]}, ${rgb[1]}, ${rgb[2]}, 0.5)`; } @@ -163,7 +166,7 @@ Schedule.resetSchedule = (divId) => { "thursday": 0, "friday": 0 }; -} +}; /** * openClassModal replaces the text within a modal with the correct information and opens it. @@ -180,4 +183,4 @@ Schedule.openClassModal = (classTime) => {
    `); $('#classModal').modal(); -} \ No newline at end of file +}; diff --git a/s3/clientScripts/time.js b/s3/clientScripts/time.js index 96dda0f..d402c40 100644 --- a/s3/clientScripts/time.js +++ b/s3/clientScripts/time.js @@ -1,4 +1,6 @@ -var Time = {} +/*jshint esversion: 8*/ + +var Time = {}; Time.getPrintTime = (time) => { let hour = Time.getHour(time); @@ -16,17 +18,17 @@ Time.getPrintTime = (time) => { } return `${hour}:${mins}${period}`; -} +}; Time.getHour = (time) => { return Math.floor(time / 100); -} +}; Time.getMins = (time) => { return time % 100; -} +}; // getTimeDelta gets the time between two times in minutes. Time.getTimeDelta = (start, end) => { return (Time.getHour(end) - Time.getHour(start)) * 60 + (Time.getMins(end) - Time.getMins(start)) % 60; -} \ No newline at end of file +}; From 8271c6eeb5ab28d65bf829c4c4c115e6d5d62b1e Mon Sep 17 00:00:00 2001 From: Christopher Bilger Date: Thu, 21 Nov 2019 09:57:54 -0500 Subject: [PATCH 08/39] Made the linter bash script more modular --- linter.sh | 18 +++++------------- 1 file changed, 5 insertions(+), 13 deletions(-) diff --git a/linter.sh b/linter.sh index 9fc9d84..3d0fa79 100755 --- a/linter.sh +++ b/linter.sh @@ -1,17 +1,9 @@ #!/bin/bash -for f in $(find hello-world/ -name '*.js'); do - jshint $f -done - -for f in $(find lambda/ -name '*.js'); do - jshint $f -done - -for f in $(find s3/ -name '*.js'); do - jshint $f -done +directories="hello-world/ lambda/ s3/ templates/" -for f in $(find template/ -name '*.js'); do - jshint $f +for directory in $directories; do + for file in $(find $directory -name '*.js'); do + jshint $file + done done From 19bc85b712ba86e371064112592ae9d7a643885e Mon Sep 17 00:00:00 2001 From: Christopher Bilger Date: Fri, 22 Nov 2019 08:47:29 -0500 Subject: [PATCH 09/39] Updated the gitignore to include locally installed node modules --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index d9952f6..36e6bd8 100644 --- a/.gitignore +++ b/.gitignore @@ -7,3 +7,5 @@ lambda/node_modules/.bin hello-world/out.json test.js test.json +package-lock.json +node_modules/ \ No newline at end of file From 5f842ef3d9c61adbf62944ca8bfe6b492d3f3806 Mon Sep 17 00:00:00 2001 From: coizioc <36041795+coizioc@users.noreply.github.com> Date: Fri, 22 Nov 2019 12:17:14 -0500 Subject: [PATCH 10/39] Fixed bug where empty columns collapsed. --- s3/clientScripts/schedule.js | 3 +-- s3/styles/schedule.css | 27 +++++++++++++++++++++++++-- 2 files changed, 26 insertions(+), 4 deletions(-) diff --git a/s3/clientScripts/schedule.js b/s3/clientScripts/schedule.js index 74329e5..2f54c0a 100644 --- a/s3/clientScripts/schedule.js +++ b/s3/clientScripts/schedule.js @@ -123,8 +123,7 @@ Schedule.addBox = (divId, classTime, dayOfWeek) => { */ let verticalOffset = (Time.getHour(classTime.startTime) - 8) * 60 + Time.getMins(classTime.startTime) - - Schedule.dayOfWeekDivOffsets[dayOfWeek] - + 3; + - Schedule.dayOfWeekDivOffsets[dayOfWeek]; // We then update dayOfWeekDivOffsets with the current height. Schedule.dayOfWeekDivOffsets[dayOfWeek] += height; diff --git a/s3/styles/schedule.css b/s3/styles/schedule.css index a531922..f5033b7 100644 --- a/s3/styles/schedule.css +++ b/s3/styles/schedule.css @@ -26,10 +26,33 @@ position: relative; z-index: 500; float: left; - width: 20%; - margin-top: -30px; + width: 20%; + margin-top: -27px; } +/* +.monday { + left: 0; +} + +.tuesday { + left: 20%; +} + +.wednesday { + left: 40%; +} + +.thursday { + left: 60%; +} + +.friday { + left: 20%; +} +*/ + + .scheduleClassTime { position: relative; border-radius: 10px; From 91a8dd5a07a5f185026b89f82570f726d5096da8 Mon Sep 17 00:00:00 2001 From: Paul Hutchings <36493057+paulhutchings@users.noreply.github.com> Date: Tue, 26 Nov 2019 23:44:44 -0500 Subject: [PATCH 11/39] refactored banner module and tests --- lambda/node_modules/banner/index.js | 23 +++--- .../{ => node_modules/banner}/test/banner.js | 14 ++-- lambda/node_modules/conversions/index.js | 18 ++--- .../conversions/test/conversions.js | 30 ++++++++ .../conversions/test/conversions.json} | 72 +++++++++++++++++-- lambda/node_modules/scheduler/index.js | 5 +- lambda/test/models.js | 19 ----- 7 files changed, 128 insertions(+), 53 deletions(-) rename lambda/{ => node_modules/banner}/test/banner.js (92%) create mode 100644 lambda/node_modules/conversions/test/conversions.js rename lambda/{test/models.json => node_modules/conversions/test/conversions.json} (83%) delete mode 100644 lambda/test/models.js diff --git a/lambda/node_modules/banner/index.js b/lambda/node_modules/banner/index.js index c1c58af..8dd0ac6 100644 --- a/lambda/node_modules/banner/index.js +++ b/lambda/node_modules/banner/index.js @@ -14,22 +14,20 @@ const INSTR_MAX = 4000; * @class Class to interact with the SSB Server */ class Banner { - constructor(){ - this.SessionId = Date.now(); - } - - async build(term){ + constructor(term){ if (arguments.length < 1){ throw new Error('Must provide term to complete object construction'); } - await this._init(term); - return this; + this.SessionId = Date.now(); + this.Term = term; + //Set server for first classSearch + this.pending = this._init(); } - async _init(term){ + async _init(){ const data = querystring.stringify({ 'uniqueSessionId': this.SessionId, - 'term': term + 'term': this.Term }); const options = { @@ -41,7 +39,7 @@ class Banner { 'Content-Type': 'application/x-www-form-urlencoded' } }; - this.Term = term; + let res = await promiseRequest(options, data); this.Cookie = res.Response.headers['set-cookie']; } @@ -162,9 +160,10 @@ class Banner { 'Cookie': this.Cookie } }; + await this.pending; let res = await promiseRequest(options); - //Reset the server for the next class search request - this._init(this.Term); + //Reset server for next classSearch + this.pending = this._init(); return res.Data.data; } } diff --git a/lambda/test/banner.js b/lambda/node_modules/banner/test/banner.js similarity index 92% rename from lambda/test/banner.js rename to lambda/node_modules/banner/test/banner.js index 944848e..12e3e81 100644 --- a/lambda/test/banner.js +++ b/lambda/node_modules/banner/test/banner.js @@ -8,11 +8,11 @@ describe('Banner', async function () { const term = 202003 /** - * BUILD() + * CONSTRUCTOR */ - describe('#build(term)', function () { + describe('#constructor(term)', function () { it('Should throw an error when a term is not passed', function () { - assert.rejects(async () => new Banner().build, Error, 'Must provide term to complete object construction'); + assert.throws(() => new Banner(), Error, 'Must provide term to complete object construction'); }); }); @@ -21,8 +21,8 @@ describe('Banner', async function () { */ describe('#_init()', function () { it('Should set Banner.Cookie value', async function () { - let b = new Banner(); - await b._init(term); + let b = new Banner(term); + await b._init(); assert(banner.Cookie); }); }); @@ -30,7 +30,7 @@ describe('Banner', async function () { /** * SETUP */ - var banner = await new Banner().build(term); + var banner = await new Banner(term); /** * GET_TERMS() @@ -145,7 +145,7 @@ describe('Banner', async function () { * CLASS_SEARCH() */ describe('#classSearch(subjects)', function () { - this.timeout(10000); + this.timeout(15000); it('Should throw an error when a subject is not passed', function () { assert.rejects(async () => banner.classSearch, Error, 'Must provide subject'); }); diff --git a/lambda/node_modules/conversions/index.js b/lambda/node_modules/conversions/index.js index b1a354f..8599f16 100644 --- a/lambda/node_modules/conversions/index.js +++ b/lambda/node_modules/conversions/index.js @@ -15,9 +15,9 @@ module.exports = { 'currentWaitlist': section.waitCount, 'totalWaitlist': section.waitCapacity, 'isOpen': section.openSection, - 'section': Number(section.sequenceNumber), + 'section': section.sequenceNumber, 'courseName': `${section.subject} ${section.courseNumber}`, - 'campus': section.meetingsFaculty[0].meetingTime.campus, + 'campus': getCampus(section), 'professor': getInstructor(section), 'classTimes': getClasstimes(section) } @@ -42,14 +42,16 @@ module.exports = { }); } - /** - * Gets the name of the professor for the section - * @param {BannerSection} section - */ + function getCampus(section){ + return section.meetingsFaculty.length > 0 ? + section.meetingsFaculty[0].meetingTime.campus : + null; + } + function getInstructor(section){ return section.faculty.length > 0 ? - section.faculty.find(f => f.primaryIndicator).displayName : - null; + section.faculty.find(f => f.primaryIndicator).displayName : + null; } /** diff --git a/lambda/node_modules/conversions/test/conversions.js b/lambda/node_modules/conversions/test/conversions.js new file mode 100644 index 0000000..141a14c --- /dev/null +++ b/lambda/node_modules/conversions/test/conversions.js @@ -0,0 +1,30 @@ +var {BannerToDB} = require('conversions'); +var asssert = require('assert'); +var fs = require ('fs'); +var { promisify } = require('util'); +var readFile = promisify(fs.readFile); + +describe('conversions', function() { + + describe('#BannerToDB', function() { + var testData = {} + before(async function(){ + testData = JSON.parse(await readFile('./test/conversions.json')); + }); + + it('Should create multiple classtimes', function() { + let data = testData['multipleMeetingTimes']; + asssert.deepEqual(BannerToDB([data.input]), [data.output]); + }); + + it('Should create empty classtime array for meetingTimes with no days', function() { + let data = testData['meetingTimeNoDays']; + asssert.deepEqual(BannerToDB([data.input]), [data.output]); + }); + + it('Should handle empty meetingTime and faculty arrays', function() { + let data = testData['emptyMeetings']; + asssert.deepEqual(BannerToDB([data.input]), [data.output]); + }); + }); +}); \ No newline at end of file diff --git a/lambda/test/models.json b/lambda/node_modules/conversions/test/conversions.json similarity index 83% rename from lambda/test/models.json rename to lambda/node_modules/conversions/test/conversions.json index c5b4717..3b6982d 100644 --- a/lambda/test/models.json +++ b/lambda/node_modules/conversions/test/conversions.json @@ -1,5 +1,5 @@ -[ - { +{ + "multipleMeetingTimes": { "input": { "id": 487879, "term": "202003", @@ -156,14 +156,14 @@ "currentWaitlist": 0, "isOpen": true, "professor": "Eugene Kwatny", - "section": 2, + "section": "002", "title": "Introduction to Systems Programming and Operating Systems", "totalSeats": 30, "totalWaitlist": 100, "campus": "MN" } }, - { + "meetingTimeNoDays": { "input": { "id": 475368, "term": "202003", @@ -271,11 +271,71 @@ "currentWaitlist": 0, "isOpen": true, "professor": "Eric J. Schweingruber", - "section": 1, + "section": "001", "title": "Instrumental Ensemble", "totalSeats": 150, "totalWaitlist": 100, "campus": null } + + }, + "emptyMeetings": { + "input": { + "id": 492822, + "term": "202003", + "termDesc": "2020 Spring", + "courseReferenceNumber": "37231", + "partOfTerm": "1", + "courseNumber": "9998", + "subject": "CEE", + "subjectDescription": "Civil Engineering", + "sequenceNumber": "015", + "campusDescription": "Main", + "scheduleTypeDescription": "Dissertation Course", + "courseTitle": "Pre-Dissertation Research", + "creditHours": null, + "maximumEnrollment": 5, + "enrollment": 0, + "seatsAvailable": 5, + "waitCapacity": 100, + "waitCount": 0, + "waitAvailable": 100, + "crossList": null, + "crossListCapacity": null, + "crossListCount": null, + "crossListAvailable": null, + "creditHourHigh": 6, + "creditHourLow": 1, + "creditHourIndicator": "TO", + "openSection": true, + "linkIdentifier": null, + "isSectionLinked": false, + "subjectCourse": "CEE9998", + "faculty": [], + "meetingsFaculty": [], + "reservedSeatSummary": null, + "sectionAttributes": [], + "bookstores": [ + { + "url": "https://prd-wlssb.temple.edu/prod8/szp_reg_bookstore.p_ShowBookStoreLink_LuC?term_in=202003&camp_in=MN&dept_in=CEE&crse_in=9998&sect_in=015", + "label": "View Course Materials" + } + ], + "feeAmount": null + }, + "output": { + "classTimes": [], + "courseName": "CEE 9998", + "crn": 37231, + "currentSeats": 0, + "currentWaitlist": 0, + "isOpen": true, + "professor": null, + "section": "015", + "title": "Pre-Dissertation Research", + "totalSeats": 5, + "totalWaitlist": 100, + "campus": null + } } -] \ No newline at end of file +} \ No newline at end of file diff --git a/lambda/node_modules/scheduler/index.js b/lambda/node_modules/scheduler/index.js index 813b755..3fd7f4e 100644 --- a/lambda/node_modules/scheduler/index.js +++ b/lambda/node_modules/scheduler/index.js @@ -39,6 +39,9 @@ function hasClassTimeConflict(c1, c2){ return startConflict || endConflict; } +/** + * For now, it might seem redundent to put the scheduling algorithm into a class, but it will allow for more flexibility for any more sophisticated parameters we might add later on + */ class Scheduler{ constructor(){ @@ -111,7 +114,7 @@ class Scheduler{ let top = current.peek(); while (j < top.length) { let section = top[j]; - if (noConflict(section, temp)) { + if (section.classTimes.length == 0 || noConflict(section, temp)) { temp.push(section); index.push(j + 1); break; diff --git a/lambda/test/models.js b/lambda/test/models.js deleted file mode 100644 index b6c45a9..0000000 --- a/lambda/test/models.js +++ /dev/null @@ -1,19 +0,0 @@ -var models = require('models'); -var asssert = require('assert'); -var fs = require ('fs'); -var { promisify } = require('util'); -var readFile = promisify(fs.readFile); - -describe('models', function() { - - describe('#BannerToDB.convert', function() { - var testData = {} - before(async function(){ - testData = JSON.parse(await readFile('./test/models.json')); - }); - - it('Should match the database format', function() { - testData.forEach(example => asssert.deepEqual(models.BannerToDB.convert([example.input]), [example.output])); - }); - }); -}); \ No newline at end of file From 8ce837914b80a0e830274adf7bc5e303bdabe2bb Mon Sep 17 00:00:00 2001 From: Paul Hutchings <36493057+paulhutchings@users.noreply.github.com> Date: Tue, 26 Nov 2019 23:46:12 -0500 Subject: [PATCH 12/39] updated gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index d9952f6..326d020 100644 --- a/.gitignore +++ b/.gitignore @@ -7,3 +7,4 @@ lambda/node_modules/.bin hello-world/out.json test.js test.json +scratch.js From 670b4cbc2d0ed4a1f47f1474dfef595b0e3cfae8 Mon Sep 17 00:00:00 2001 From: Paul Hutchings <36493057+paulhutchings@users.noreply.github.com> Date: Tue, 26 Nov 2019 23:48:13 -0500 Subject: [PATCH 13/39] reorganized tests --- lambda/{node_modules/banner => }/test/banner.js | 0 lambda/{node_modules/conversions => }/test/conversions.js | 0 lambda/{node_modules/conversions => }/test/conversions.json | 0 3 files changed, 0 insertions(+), 0 deletions(-) rename lambda/{node_modules/banner => }/test/banner.js (100%) rename lambda/{node_modules/conversions => }/test/conversions.js (100%) rename lambda/{node_modules/conversions => }/test/conversions.json (100%) diff --git a/lambda/node_modules/banner/test/banner.js b/lambda/test/banner.js similarity index 100% rename from lambda/node_modules/banner/test/banner.js rename to lambda/test/banner.js diff --git a/lambda/node_modules/conversions/test/conversions.js b/lambda/test/conversions.js similarity index 100% rename from lambda/node_modules/conversions/test/conversions.js rename to lambda/test/conversions.js diff --git a/lambda/node_modules/conversions/test/conversions.json b/lambda/test/conversions.json similarity index 100% rename from lambda/node_modules/conversions/test/conversions.json rename to lambda/test/conversions.json From 4235709b053b14976a923086e1687aeb3a7602a1 Mon Sep 17 00:00:00 2001 From: Paul Hutchings <36493057+paulhutchings@users.noreply.github.com> Date: Wed, 27 Nov 2019 00:08:30 -0500 Subject: [PATCH 14/39] updated modules to use strict mode --- lambda/node_modules/banner/index.js | 2 ++ lambda/node_modules/conversions/index.js | 2 +- lambda/node_modules/scheduler/index.js | 2 ++ lambda/scheduler/cache.js | 2 ++ lambda/test/banner.js | 19 ++++++++++++++----- lambda/test/conversions.js | 2 ++ lambda/test/scheduler.js | 12 ++++++++++++ 7 files changed, 35 insertions(+), 6 deletions(-) create mode 100644 lambda/test/scheduler.js diff --git a/lambda/node_modules/banner/index.js b/lambda/node_modules/banner/index.js index 8dd0ac6..5207b24 100644 --- a/lambda/node_modules/banner/index.js +++ b/lambda/node_modules/banner/index.js @@ -1,3 +1,5 @@ +'use strict'; + const https = require('https'); const querystring = require('querystring'); diff --git a/lambda/node_modules/conversions/index.js b/lambda/node_modules/conversions/index.js index 8599f16..fd534d8 100644 --- a/lambda/node_modules/conversions/index.js +++ b/lambda/node_modules/conversions/index.js @@ -1,4 +1,4 @@ - +'use strict'; /** * @module diff --git a/lambda/node_modules/scheduler/index.js b/lambda/node_modules/scheduler/index.js index 3fd7f4e..f942aa1 100644 --- a/lambda/node_modules/scheduler/index.js +++ b/lambda/node_modules/scheduler/index.js @@ -1,3 +1,5 @@ +'use strict'; + /** * @function peek - Returns the last item in the array without removing it * @return {*} - The last item in the array diff --git a/lambda/scheduler/cache.js b/lambda/scheduler/cache.js index b4c2d1f..ceb4ed4 100644 --- a/lambda/scheduler/cache.js +++ b/lambda/scheduler/cache.js @@ -1,3 +1,5 @@ +'use strict'; + /** * @module Cache * Controls the state of global objects representing connections to AWS services that can be reused throughout invokations. diff --git a/lambda/test/banner.js b/lambda/test/banner.js index 12e3e81..c20cb9c 100644 --- a/lambda/test/banner.js +++ b/lambda/test/banner.js @@ -1,3 +1,5 @@ +'use strict'; + var assert = require('assert'); var Banner = require('banner'); @@ -127,17 +129,18 @@ describe('Banner', async function () { */ describe('#getInstructors()', function () { this.timeout(15000); + var data = null; it('Should not throw an error', async function () { assert.doesNotReject(banner.getInstructors()); }); it('Should not return void', async function () { - assert(await banner.getInstructors()); + data = await banner.getInstructors(); + assert(data); }); - it('Should return a non-empty array', async function () { - let terms = await banner.getInstructors(); - assert(terms.length > 0); + it('Should return a non-empty array', function () { + assert(data.length > 0); }); }); @@ -146,12 +149,18 @@ describe('Banner', async function () { */ describe('#classSearch(subjects)', function () { this.timeout(15000); + var data = null; it('Should throw an error when a subject is not passed', function () { assert.rejects(async () => banner.classSearch, Error, 'Must provide subject'); }); it('Should not return NULL', async function(){ - assert(await banner.classSearch('CIS')); + data = await banner.classSearch('CIS'); + assert(data); + }); + + it('Should return a non-empty array', function () { + assert(data.length > 0); }); }); }); \ No newline at end of file diff --git a/lambda/test/conversions.js b/lambda/test/conversions.js index 141a14c..92caa9c 100644 --- a/lambda/test/conversions.js +++ b/lambda/test/conversions.js @@ -1,3 +1,5 @@ +'use strict'; + var {BannerToDB} = require('conversions'); var asssert = require('assert'); var fs = require ('fs'); diff --git a/lambda/test/scheduler.js b/lambda/test/scheduler.js new file mode 100644 index 0000000..a29daaf --- /dev/null +++ b/lambda/test/scheduler.js @@ -0,0 +1,12 @@ +'use strict'; + +var assert = require('assert'); +var Scheduler = require('scheduler'); + +describe('#Scheduler', function(){ + var scheduler = new Scheduler(); + + it('Should not allow time conflicts', function(){ + + }); +}); \ No newline at end of file From 6c9859bf42da036c83a3cc2e519ef3ccd415faaa Mon Sep 17 00:00:00 2001 From: Paul Hutchings <36493057+paulhutchings@users.noreply.github.com> Date: Wed, 27 Nov 2019 11:00:10 -0500 Subject: [PATCH 15/39] added campus filtering in DB query --- lambda/node_modules/aws-sdk | 2 +- lambda/scheduler/index.js | 19 ++++++++++--------- 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/lambda/node_modules/aws-sdk b/lambda/node_modules/aws-sdk index 7f3daac..d843238 120000 --- a/lambda/node_modules/aws-sdk +++ b/lambda/node_modules/aws-sdk @@ -1 +1 @@ -../../../.npm-global/lib/node_modules/aws-sdk \ No newline at end of file +../../../../.npm-global/lib/node_modules/aws-sdk \ No newline at end of file diff --git a/lambda/scheduler/index.js b/lambda/scheduler/index.js index 0a3898c..decfab4 100644 --- a/lambda/scheduler/index.js +++ b/lambda/scheduler/index.js @@ -1,3 +1,4 @@ +'use strict'; if (!cache){ var cache = require('./cache'); @@ -13,14 +14,12 @@ exports.handler = async (event, context) => { console.log(`Received the following courses: ${courseList}`); try { - var data = await getSections(courseList); + var data = await getSections(courseList, params.campus); } catch (error) { console.log(`'Unable to get all courses from database: ${error}`); return { statusCode: '500', - body: JSON.stringify({ - Error: error - }), + body: "Unable to process request", headers: { 'Content-Type': 'application/json', 'Access-Control-Allow-Methods': '*', @@ -45,7 +44,7 @@ exports.handler = async (event, context) => { }; }; -async function getSections(courses){ +async function getSections(courses, campus){ let docClient = cache.DocumentClient; return Promise.all(courses.map(async course => { let params = { @@ -54,10 +53,12 @@ async function getSections(courses){ ExpressionAttributeNames : { "#courseName" : "courseName" }, - ExpressionAttributeValues : { - ":course" : course - - } + ExpressionAttributeValues : function(){ + let obj = {":course": course}; + campus.forEach(campus => obj[`:${campus}`] = campus); + return obj; + }(), + FilterExpression: `#campus IN (${campus.map(campus => `:${campus}`).join()})` }; return docClient.query(params).promise(); From 1b439f8c5e8dd1c0f449c9dfcc0a1f86a1f9d727 Mon Sep 17 00:00:00 2001 From: Paul Hutchings <36493057+paulhutchings@users.noreply.github.com> Date: Wed, 27 Nov 2019 11:11:32 -0500 Subject: [PATCH 16/39] updated upload script --- lambda/node_modules/conversions/index.js | 4 ++-- lambda/scheduler/upload.sh | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lambda/node_modules/conversions/index.js b/lambda/node_modules/conversions/index.js index fd534d8..c581b84 100644 --- a/lambda/node_modules/conversions/index.js +++ b/lambda/node_modules/conversions/index.js @@ -26,7 +26,7 @@ module.exports = { function getClasstimes(section){ let meetingTimes = section.meetingsFaculty.map(obj => obj.meetingTime); return meetingTimes.map(mt => convertMeetingTime(mt)) - .reduce((acc, val) => acc.concat(val), []); + .flat(); } function convertMeetingTime(meetingTime){ @@ -81,7 +81,7 @@ module.exports = { SchedulerToUI: function(schedules){ return schedules.map(schedule => schedule .map(section => convertSection(section) - .reduce((acc, val) => acc.concat(val), []))); + .flat())); function convertSection(section){ return section.classTimes.map(classTime => { diff --git a/lambda/scheduler/upload.sh b/lambda/scheduler/upload.sh index deceaa1..dfd3ebe 100755 --- a/lambda/scheduler/upload.sh +++ b/lambda/scheduler/upload.sh @@ -1,4 +1,4 @@ #!/bin/bash -zip -u function.zip index.js cache.js +zip -u function.zip index.js cache.js node_modules/conversions node_modules/scheduler aws lambda update-function-code --function-name scheduler --zip-file fileb://function.zip \ No newline at end of file From e8079055287bd6d7274ade0108fec2076e0cc69e Mon Sep 17 00:00:00 2001 From: Paul Hutchings <36493057+paulhutchings@users.noreply.github.com> Date: Wed, 27 Nov 2019 11:29:26 -0500 Subject: [PATCH 17/39] fixed error in db query --- lambda/scheduler/index.js | 3 ++- lambda/scheduler/upload.sh | 5 ++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/lambda/scheduler/index.js b/lambda/scheduler/index.js index decfab4..9c71336 100644 --- a/lambda/scheduler/index.js +++ b/lambda/scheduler/index.js @@ -51,7 +51,8 @@ async function getSections(courses, campus){ TableName : process.env.TABLENAME, KeyConditionExpression : "#courseName = :course", ExpressionAttributeNames : { - "#courseName" : "courseName" + "#courseName" : "courseName", + '#campus': 'campus' }, ExpressionAttributeValues : function(){ let obj = {":course": course}; diff --git a/lambda/scheduler/upload.sh b/lambda/scheduler/upload.sh index dfd3ebe..f6d8898 100755 --- a/lambda/scheduler/upload.sh +++ b/lambda/scheduler/upload.sh @@ -1,4 +1,7 @@ #!/bin/bash -zip -u function.zip index.js cache.js node_modules/conversions node_modules/scheduler +zip -u function.zip index.js cache.js +cd ../ +zip -u scheduler/function.zip node_modules/conversions/* node_modules/scheduler/* +cd scheduler aws lambda update-function-code --function-name scheduler --zip-file fileb://function.zip \ No newline at end of file From e07cd3ec2c2823aefb6847a5912563b1f5e77549 Mon Sep 17 00:00:00 2001 From: Paul Hutchings <36493057+paulhutchings@users.noreply.github.com> Date: Wed, 27 Nov 2019 11:39:46 -0500 Subject: [PATCH 18/39] fixed undeclared variables error --- lambda/node_modules/scheduler/index.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lambda/node_modules/scheduler/index.js b/lambda/node_modules/scheduler/index.js index f942aa1..a2ddd05 100644 --- a/lambda/node_modules/scheduler/index.js +++ b/lambda/node_modules/scheduler/index.js @@ -29,8 +29,8 @@ function hasTimeConflict(s1, s2){ } function hasSameDay(s1, s2){ - days1 = s1.classTimes.map(c => c.day); - days2 = s2.classTimes.map(c => c.day); + let days1 = s1.classTimes.map(c => c.day); + let days2 = s2.classTimes.map(c => c.day); return days1.some(a => days2.some(b => a === b)); } From a080ac6e6894dfe58120c639e5822bd179b6fccd Mon Sep 17 00:00:00 2001 From: Paul Hutchings <36493057+paulhutchings@users.noreply.github.com> Date: Wed, 27 Nov 2019 13:55:03 -0500 Subject: [PATCH 19/39] added lambdas for database update --- lambda/update-delegate/index.js | 31 +++++++++++++++++++++++++++++++ lambda/update-worker/index.js | 9 +++++++++ 2 files changed, 40 insertions(+) create mode 100644 lambda/update-delegate/index.js create mode 100644 lambda/update-worker/index.js diff --git a/lambda/update-delegate/index.js b/lambda/update-delegate/index.js new file mode 100644 index 0000000..37d0963 --- /dev/null +++ b/lambda/update-delegate/index.js @@ -0,0 +1,31 @@ +'use strict'; + +if (!cache){ + var cahce = require('./cache'); +} + +exports.handler = async (event, context) => { + let +} + +/** + * @async + * @function delegate - Invokes the worker lambda to process process the given subject + * @param {string} subject + */ +async function delegate(subject){ + const params = { + 'FunctionName': process.env.FUNCTION_NAME, + 'InvocationType': 'Event', + 'Payload': JSON.stringify({ + 'subject': subject + }) + }; + try { + //invoke worker lambda + console.log(`Invoking worker for ${subject}`); + return await lambda.invoke(params).promise(); + } catch (error) { + console.log(`Failed to invoke worker Lambda for ${subject}: ${error}`); + } +} \ No newline at end of file diff --git a/lambda/update-worker/index.js b/lambda/update-worker/index.js new file mode 100644 index 0000000..6c5f60b --- /dev/null +++ b/lambda/update-worker/index.js @@ -0,0 +1,9 @@ +'use strict'; + +if (!cache){ + var cache = require('./cache'); +} + +exports.handler = async (event, context) => { + +} \ No newline at end of file From bfd59091fa0472b4eafc33beefb844f20db47430 Mon Sep 17 00:00:00 2001 From: Paul Hutchings <36493057+paulhutchings@users.noreply.github.com> Date: Wed, 27 Nov 2019 14:01:24 -0500 Subject: [PATCH 20/39] generated cache modules for update lambdas --- lambda/update-delegate/cache.js | 19 +++++++++++++++++ lambda/update-worker/cache.js | 38 +++++++++++++++++++++++++++++++++ 2 files changed, 57 insertions(+) create mode 100644 lambda/update-delegate/cache.js create mode 100644 lambda/update-worker/cache.js diff --git a/lambda/update-delegate/cache.js b/lambda/update-delegate/cache.js new file mode 100644 index 0000000..061b0a7 --- /dev/null +++ b/lambda/update-delegate/cache.js @@ -0,0 +1,19 @@ +'use strict'; + +/** + * @module Cache + * Controls the state of global objects representing connections to AWS services that can be reused throughout invokations. + * If the variable does not exist in the namespace, it is instantiated and then reused upon subsequent executions in the same container. + */ +module.exports = function(){ + + if (!banner){ + var Banner = require('banner'); + var banner = new Banner(process.env.TERM); + } + + return { + Banner: banner, + } + +}(); \ No newline at end of file diff --git a/lambda/update-worker/cache.js b/lambda/update-worker/cache.js new file mode 100644 index 0000000..4f52f34 --- /dev/null +++ b/lambda/update-worker/cache.js @@ -0,0 +1,38 @@ +'use strict'; + +/** + * @module Cache + * Controls the state of global objects representing connections to AWS services that can be reused throughout invokations. + * If the variable does not exist in the namespace, it is instantiated and then reused upon subsequent executions in the same container. + */ +module.exports = function(){ + if (!AWS){ + var AWS = require('aws-sdk'); + } + + if (!documentclient){ + + var documentclient = new AWS.DynamoDB.DocumentClient(); + } + + if (!s3){ + var s3 = new AWS.S3(); + } + + if (!banner){ + var Banner = require('banner'); + var banner = new Banner(process.env.TERM); + } + + if (!conversions){ + var conversions = require('conversions'); + } + + return { + DocumentClient: documentclient, + Banner: banner, + BannerToDB: conversions.BannerToDB, + S3: s3 + } + +}(); \ No newline at end of file From e80ff3cdb55da0270b61c1ee5d0f2f14447f35e0 Mon Sep 17 00:00:00 2001 From: Paul Hutchings <36493057+paulhutchings@users.noreply.github.com> Date: Wed, 27 Nov 2019 14:07:57 -0500 Subject: [PATCH 21/39] drafted delegation lambda --- lambda/scheduler/index.js | 2 +- lambda/update-delegate/cache.js | 6 ++++++ lambda/update-delegate/index.js | 11 +++++++++-- lambda/update-worker/index.js | 2 +- 4 files changed, 17 insertions(+), 4 deletions(-) diff --git a/lambda/scheduler/index.js b/lambda/scheduler/index.js index 9c71336..6dd1e6c 100644 --- a/lambda/scheduler/index.js +++ b/lambda/scheduler/index.js @@ -45,7 +45,7 @@ exports.handler = async (event, context) => { }; async function getSections(courses, campus){ - let docClient = cache.DocumentClient; + const docClient = cache.DocumentClient; return Promise.all(courses.map(async course => { let params = { TableName : process.env.TABLENAME, diff --git a/lambda/update-delegate/cache.js b/lambda/update-delegate/cache.js index 061b0a7..35ec417 100644 --- a/lambda/update-delegate/cache.js +++ b/lambda/update-delegate/cache.js @@ -12,8 +12,14 @@ module.exports = function(){ var banner = new Banner(process.env.TERM); } + if (!lambda){ + var AWS = require('aws-sdk'); + var lambda = new AWS.Lambda(); + } + return { Banner: banner, + Lambda: lambda } }(); \ No newline at end of file diff --git a/lambda/update-delegate/index.js b/lambda/update-delegate/index.js index 37d0963..b7b9bb4 100644 --- a/lambda/update-delegate/index.js +++ b/lambda/update-delegate/index.js @@ -1,11 +1,17 @@ 'use strict'; if (!cache){ - var cahce = require('./cache'); + var cache = require('./cache'); } exports.handler = async (event, context) => { - let + const banner = cache.Banner; + try { + var subjects = await banner.getSubjects(); + await Promise.all(subjects.map(subject => delegate(subject))); + } catch (error) { + console.log(`Failed to fetch subjects from Banner: ${error}`); + } } /** @@ -14,6 +20,7 @@ exports.handler = async (event, context) => { * @param {string} subject */ async function delegate(subject){ + const lambda = cache.Lambda; const params = { 'FunctionName': process.env.FUNCTION_NAME, 'InvocationType': 'Event', diff --git a/lambda/update-worker/index.js b/lambda/update-worker/index.js index 6c5f60b..e183314 100644 --- a/lambda/update-worker/index.js +++ b/lambda/update-worker/index.js @@ -5,5 +5,5 @@ if (!cache){ } exports.handler = async (event, context) => { - + } \ No newline at end of file From da9e36acc56e39cf4e7083999b483ef0533d9607 Mon Sep 17 00:00:00 2001 From: Paul Hutchings <36493057+paulhutchings@users.noreply.github.com> Date: Wed, 27 Nov 2019 17:19:01 -0500 Subject: [PATCH 22/39] modified cache --- lambda/update-worker/cache.js | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/lambda/update-worker/cache.js b/lambda/update-worker/cache.js index 4f52f34..0ef3082 100644 --- a/lambda/update-worker/cache.js +++ b/lambda/update-worker/cache.js @@ -6,19 +6,12 @@ * If the variable does not exist in the namespace, it is instantiated and then reused upon subsequent executions in the same container. */ module.exports = function(){ - if (!AWS){ - var AWS = require('aws-sdk'); - } if (!documentclient){ - + var AWS = require('aws-sdk'); var documentclient = new AWS.DynamoDB.DocumentClient(); } - if (!s3){ - var s3 = new AWS.S3(); - } - if (!banner){ var Banner = require('banner'); var banner = new Banner(process.env.TERM); From 11770249976947e9bedfece682f27cf95396cfbe Mon Sep 17 00:00:00 2001 From: Paul Hutchings <36493057+paulhutchings@users.noreply.github.com> Date: Wed, 27 Nov 2019 19:54:34 -0500 Subject: [PATCH 23/39] refactored test suite --- lambda/node_modules/banner/index.js | 78 ++- lambda/node_modules/conversions/index.js | 8 +- lambda/test/banner.js | 76 ++- lambda/test/conversions.js | 31 +- lambda/test/conversions.json | 701 ++++++++++++----------- 5 files changed, 517 insertions(+), 377 deletions(-) diff --git a/lambda/node_modules/banner/index.js b/lambda/node_modules/banner/index.js index 5207b24..ee57dc6 100644 --- a/lambda/node_modules/banner/index.js +++ b/lambda/node_modules/banner/index.js @@ -10,7 +10,8 @@ const querystring = require('querystring'); const host = 'prd-xereg.temple.edu'; const basePath = '/StudentRegistrationSsb/ssb'; -const INSTR_MAX = 4000; +const INSTR_MAX = 3700; +const PAGE_SIZE = 100; /** * @class Class to interact with the SSB Server @@ -23,7 +24,7 @@ class Banner { this.SessionId = Date.now(); this.Term = term; //Set server for first classSearch - this.pending = this._init(); + this.reset = this._init(); } async _init(){ @@ -81,22 +82,25 @@ class Banner { return res.Data; } - async getInstructors(){ + async getInstructors(offset, max){ const path = '/classSearch/get_instructor'; - const params = querystring.stringify({ - offset: 1, - max: INSTR_MAX, - term: this.Term - }); - const options = { - method: 'GET', - hostname: host, - path: `${basePath}${path}?${params}`, - port: 443 - }; - - let res = await promiseRequest(options); - return res.Data; + const idxs = [...Array(INSTR_MAX / PAGE_SIZE).keys()].map(i => i + 1); + let res = await Promise.all(idxs.map(async idx => { + const params = querystring.stringify({ + offset: idx, + max: PAGE_SIZE, + term: this.Term + }); + const options = { + method: 'GET', + hostname: host, + path: `${basePath}${path}?${params}`, + port: 443 + }; + return promiseRequest(options); + })); + + return res.map(obj => obj.Data).flat(); } async getCampus(){ @@ -162,12 +166,48 @@ class Banner { 'Cookie': this.Cookie } }; - await this.pending; + await this.reset; let res = await promiseRequest(options); //Reset server for next classSearch - this.pending = this._init(); + this.reset = this._init(); return res.Data.data; } + + async catalogSearch(subject){ + if (arguments.length < 1){ + throw new Error('Must provide subject'); + } + const path = '/courseSearchResults'; + let params = { + txt_subject: subject, + txt_term: this.Term, + pageOffset: 0, + pageMaxSize: -1, + uniqueSessionId: this.SessionId + }; + params = querystring.stringify(params); + + const options = { + method: 'GET', + hostname: host, + path: `${basePath}${path}?${params}`, + port: 443, + headers: { + 'Cookie': this.Cookie + } + }; + await this.reset; + let res = await promiseRequest(options); + //Reset server for next classSearch + this.reset = this._init(); + return res.Data.data; + } + + async getAllCourses(){ + let subjects = (await this.getSubjects()).map(subj => subj.code); + let courses = await Promise.all(subjects.map(async subj => this.catalogSearch(subj))); + return courses.flat(); + } } /** diff --git a/lambda/node_modules/conversions/index.js b/lambda/node_modules/conversions/index.js index c581b84..52b5f22 100644 --- a/lambda/node_modules/conversions/index.js +++ b/lambda/node_modules/conversions/index.js @@ -50,8 +50,8 @@ module.exports = { function getInstructor(section){ return section.faculty.length > 0 ? - section.faculty.find(f => f.primaryIndicator).displayName : - null; + section.faculty.find(f => f.primaryIndicator).displayName : + null; } /** @@ -108,6 +108,10 @@ module.exports = { } }); } + }, + + ConvertBannerCatalog: function(courses){ + return courses.map(course => `${course.subject} ${course.courseNumber}`); } } diff --git a/lambda/test/banner.js b/lambda/test/banner.js index c20cb9c..da8143b 100644 --- a/lambda/test/banner.js +++ b/lambda/test/banner.js @@ -3,7 +3,7 @@ var assert = require('assert'); var Banner = require('banner'); -describe('Banner', async function () { +describe('Banner', function () { /** * SETUP */ @@ -25,14 +25,14 @@ describe('Banner', async function () { it('Should set Banner.Cookie value', async function () { let b = new Banner(term); await b._init(); - assert(banner.Cookie); + assert.strict(banner.Cookie); }); }); /** * SETUP */ - var banner = await new Banner(term); + var banner = new Banner(term); /** * GET_TERMS() @@ -43,12 +43,12 @@ describe('Banner', async function () { }); it('Should not return void', async function () { - assert(await banner.getTerms()); + assert.strict(await banner.getTerms()); }); it('Should return a non-empty array', async function () { let terms = await banner.getTerms(); - assert(terms.length > 0); + assert.strict(terms.length > 0); }); }); @@ -61,12 +61,12 @@ describe('Banner', async function () { }); it('Should not return void', async function () { - assert(await banner.getSubjects()); + assert.strict(await banner.getSubjects()); }); it('Should return a non-empty array', async function () { let terms = await banner.getSubjects(); - assert(terms.length > 0); + assert.strict(terms.length > 0); }); }); @@ -79,12 +79,12 @@ describe('Banner', async function () { }); it('Should not return void', async function () { - assert(await banner.getCampus()); + assert.strict(await banner.getCampus()); }); it('Should return a non-empty array', async function () { let terms = await banner.getCampus(); - assert(terms.length > 0); + assert.strict(terms.length > 0); }); }); @@ -97,12 +97,12 @@ describe('Banner', async function () { }); it('Should not return void', async function () { - assert(await banner.getColleges()); + assert.strict(await banner.getColleges()); }); it('Should return a non-empty array', async function () { let terms = await banner.getColleges(); - assert(terms.length > 0); + assert.strict(terms.length > 0); }); }); @@ -115,12 +115,12 @@ describe('Banner', async function () { }); it('Should not return void', async function () { - assert(await banner.getAttributes()); + assert.strict(await banner.getAttributes()); }); it('Should return a non-empty array', async function () { let terms = await banner.getAttributes(); - assert(terms.length > 0); + assert.strict(terms.length > 0); }); }); @@ -136,11 +136,11 @@ describe('Banner', async function () { it('Should not return void', async function () { data = await banner.getInstructors(); - assert(data); + assert.strict(data); }); it('Should return a non-empty array', function () { - assert(data.length > 0); + assert.strict(data.length > 0); }); }); @@ -148,7 +148,7 @@ describe('Banner', async function () { * CLASS_SEARCH() */ describe('#classSearch(subjects)', function () { - this.timeout(15000); + this.timeout(30000); var data = null; it('Should throw an error when a subject is not passed', function () { assert.rejects(async () => banner.classSearch, Error, 'Must provide subject'); @@ -156,11 +156,51 @@ describe('Banner', async function () { it('Should not return NULL', async function(){ data = await banner.classSearch('CIS'); - assert(data); + assert.strict(data); + }); + + it('Should return a non-empty array', function () { + assert.strict(data.length > 0); + }); + }); + + /** + * CATALOGSEARCH() + */ + describe('#catalogSearch(subjects)', function () { + this.timeout(30000); + var data = null; + it('Should throw an error when a subject is not passed', function () { + assert.rejects(async () => banner.catalogSearch, Error, 'Must provide subject'); + }); + + it('Should not return NULL', async function(){ + data = await banner.catalogSearch('CIS'); + assert.strict(data); + }); + + it('Should return a non-empty array', function () { + assert.strict(data.length > 0); + }); + }); + + /** + * GETALLCOURSES() + */ + describe('#getAllCourses()', function () { + this.timeout(30000); + var data = null; + it('Should not throw an error', function () { + assert.doesNotReject(async () => banner.getAllCourses()); + }); + + it('Should not return NULL', async function(){ + data = await banner.getAllCourses(); + assert.strict(data); }); it('Should return a non-empty array', function () { - assert(data.length > 0); + assert.strict(data.length > 0); }); }); }); \ No newline at end of file diff --git a/lambda/test/conversions.js b/lambda/test/conversions.js index 92caa9c..8f0a6db 100644 --- a/lambda/test/conversions.js +++ b/lambda/test/conversions.js @@ -1,32 +1,35 @@ 'use strict'; -var {BannerToDB} = require('conversions'); +var {BannerToDB, ConvertBannerCatalog} = require('conversions'); var asssert = require('assert'); var fs = require ('fs'); -var { promisify } = require('util'); -var readFile = promisify(fs.readFile); describe('conversions', function() { + var data = JSON.parse(fs.readFileSync('./test/conversions.json', 'utf8')); + console.log(data) describe('#BannerToDB', function() { - var testData = {} - before(async function(){ - testData = JSON.parse(await readFile('./test/conversions.json')); - }); - + let testData = data.BannerToDB; it('Should create multiple classtimes', function() { - let data = testData['multipleMeetingTimes']; - asssert.deepEqual(BannerToDB([data.input]), [data.output]); + let example = testData['multipleMeetingTimes']; + asssert.deepStrictEqual(BannerToDB([example.input]), [example.output]); }); it('Should create empty classtime array for meetingTimes with no days', function() { - let data = testData['meetingTimeNoDays']; - asssert.deepEqual(BannerToDB([data.input]), [data.output]); + let example = testData['meetingTimeNoDays']; + asssert.deepStrictEqual(BannerToDB([example.input]), [example.output]); }); it('Should handle empty meetingTime and faculty arrays', function() { - let data = testData['emptyMeetings']; - asssert.deepEqual(BannerToDB([data.input]), [data.output]); + let example = testData['emptyMeetings']; + asssert.deepStrictEqual(BannerToDB([example.input]), [example.output]); + }); + }); + + describe('#ConvertBannerCatalog', function() { + let testData = data.BannerCatalog; + it('Should return a concatenated string', function() { + asssert.deepStrictEqual(ConvertBannerCatalog([testData.input]), [testData.output]); }); }); }); \ No newline at end of file diff --git a/lambda/test/conversions.json b/lambda/test/conversions.json index 3b6982d..cd7d850 100644 --- a/lambda/test/conversions.json +++ b/lambda/test/conversions.json @@ -1,341 +1,394 @@ { - "multipleMeetingTimes": { + "SchedulerToUI": { + + }, + "DBToScheduler": { + + }, + "BannerCatalog": { "input": { - "id": 487879, - "term": "202003", - "termDesc": "2020 Spring", - "courseReferenceNumber": "30078", - "partOfTerm": "1", - "courseNumber": "3207", - "subject": "CIS", - "subjectDescription": "Computer & Information Science", - "sequenceNumber": "002", - "campusDescription": "Main", - "scheduleTypeDescription": "Lecture and Lab", - "courseTitle": "Introduction to Systems Programming and Operating Systems", - "creditHours": null, - "maximumEnrollment": 30, - "enrollment": 19, - "seatsAvailable": 11, - "waitCapacity": 100, - "waitCount": 0, - "waitAvailable": 100, - "crossList": "UR", - "crossListCapacity": 175, - "crossListCount": 63, - "crossListAvailable": 112, + "id": 8307, + "termEffective": "201436", + "courseNumber": "2101", + "subject": "ACCT", + "subjectCode": "ACCT", + "college": "Business & Mngmnt, Fox School", + "collegeCode": "BU", + "department": "Business:Accounting", + "departmentCode": "1502", + "courseTitle": "Financial Accounting", + "durationUnit": null, + "numberOfUnits": null, + "attributes": null, + "gradeModes": null, + "ceu": null, + "courseScheduleTypes": null, + "courseLevels": null, "creditHourHigh": null, - "creditHourLow": 4, + "creditHourLow": 3, "creditHourIndicator": null, - "openSection": true, - "linkIdentifier": null, - "isSectionLinked": false, - "subjectCourse": "CIS3207", - "faculty": [ - { - "bannerId": "903510150", - "category": null, - "class": "net.hedtech.banner.student.faculty.FacultyResultDecorator", - "courseReferenceNumber": "30078", - "displayName": "Eugene Kwatny", - "emailAddress": "gkwatny@temple.edu", - "primaryIndicator": true, - "term": "202003" - } - ], - "meetingsFaculty": [ - { - "category": "02", - "class": "net.hedtech.banner.student.schedule.SectionSessionDecorator", - "courseReferenceNumber": "30078", - "faculty": [], - "meetingTime": { - "beginTime": "1000", - "building": "SERC", - "buildingDescription": "Science Ed and Research Ctr", - "campus": "MN", - "campusDescription": "Main", + "lectureHourLow": null, + "lectureHourHigh": null, + "lectureHourIndicator": null, + "billHourLow": 3, + "billHourHigh": null, + "billHourIndicator": null, + "labHourLow": null, + "labHourHigh": null, + "labHourIndicator": null, + "otherHourLow": null, + "otherHourHigh": null, + "otherHourIndicator": null, + "description": null, + "subjectDescription": "Accounting", + "courseDescription": "Basic concepts and principles underlying the preparation and use of financial statements. Among the ", + "division": "Undergraduate", + "termStart": "200836", + "termEnd": "999999", + "preRequisiteCheckMethodCde": "C", + "anySections": null + }, + "output": "ACCT 2101" + }, + "BannerToDB": { + "multipleMeetingTimes": { + "input": { + "id": 487879, + "term": "202003", + "termDesc": "2020 Spring", + "courseReferenceNumber": "30078", + "partOfTerm": "1", + "courseNumber": "3207", + "subject": "CIS", + "subjectDescription": "Computer & Information Science", + "sequenceNumber": "002", + "campusDescription": "Main", + "scheduleTypeDescription": "Lecture and Lab", + "courseTitle": "Introduction to Systems Programming and Operating Systems", + "creditHours": null, + "maximumEnrollment": 30, + "enrollment": 19, + "seatsAvailable": 11, + "waitCapacity": 100, + "waitCount": 0, + "waitAvailable": 100, + "crossList": "UR", + "crossListCapacity": 175, + "crossListCount": 63, + "crossListAvailable": 112, + "creditHourHigh": null, + "creditHourLow": 4, + "creditHourIndicator": null, + "openSection": true, + "linkIdentifier": null, + "isSectionLinked": false, + "subjectCourse": "CIS3207", + "faculty": [ + { + "bannerId": "903510150", + "category": null, + "class": "net.hedtech.banner.student.faculty.FacultyResultDecorator", + "courseReferenceNumber": "30078", + "displayName": "Eugene Kwatny", + "emailAddress": "gkwatny@temple.edu", + "primaryIndicator": true, + "term": "202003" + } + ], + "meetingsFaculty": [ + { "category": "02", - "class": "net.hedtech.banner.general.overall.MeetingTimeDecorator", + "class": "net.hedtech.banner.student.schedule.SectionSessionDecorator", "courseReferenceNumber": "30078", - "creditHourSession": 0.0, - "endDate": "05/06/2020", - "endTime": "1150", - "friday": false, - "hoursWeek": 1.83, - "meetingScheduleType": "LL", - "meetingType": "LAB", - "meetingTypeDescription": "Laboratory", - "monday": false, - "room": "00206", - "saturday": false, - "startDate": "01/13/2020", - "sunday": false, - "term": "202003", - "thursday": false, - "tuesday": false, - "wednesday": true + "faculty": [], + "meetingTime": { + "beginTime": "1000", + "building": "SERC", + "buildingDescription": "Science Ed and Research Ctr", + "campus": "MN", + "campusDescription": "Main", + "category": "02", + "class": "net.hedtech.banner.general.overall.MeetingTimeDecorator", + "courseReferenceNumber": "30078", + "creditHourSession": 0.0, + "endDate": "05/06/2020", + "endTime": "1150", + "friday": false, + "hoursWeek": 1.83, + "meetingScheduleType": "LL", + "meetingType": "LAB", + "meetingTypeDescription": "Laboratory", + "monday": false, + "room": "00206", + "saturday": false, + "startDate": "01/13/2020", + "sunday": false, + "term": "202003", + "thursday": false, + "tuesday": false, + "wednesday": true + }, + "term": "202003" }, - "term": "202003" - }, - { - "category": "01", - "class": "net.hedtech.banner.student.schedule.SectionSessionDecorator", - "courseReferenceNumber": "30078", - "faculty": [], - "meetingTime": { - "beginTime": "1230", - "building": "SERC", - "buildingDescription": "Science Ed and Research Ctr", - "campus": "MN", - "campusDescription": "Main", + { "category": "01", - "class": "net.hedtech.banner.general.overall.MeetingTimeDecorator", + "class": "net.hedtech.banner.student.schedule.SectionSessionDecorator", "courseReferenceNumber": "30078", - "creditHourSession": 4.0, - "endDate": "05/06/2020", - "endTime": "1350", - "friday": false, - "hoursWeek": 2.66, - "meetingScheduleType": "LL", - "meetingType": "LEC", - "meetingTypeDescription": "Lecture", - "monday": false, - "room": "00116", - "saturday": false, - "startDate": "01/13/2020", - "sunday": false, - "term": "202003", - "thursday": true, - "tuesday": true, - "wednesday": false + "faculty": [], + "meetingTime": { + "beginTime": "1230", + "building": "SERC", + "buildingDescription": "Science Ed and Research Ctr", + "campus": "MN", + "campusDescription": "Main", + "category": "01", + "class": "net.hedtech.banner.general.overall.MeetingTimeDecorator", + "courseReferenceNumber": "30078", + "creditHourSession": 4.0, + "endDate": "05/06/2020", + "endTime": "1350", + "friday": false, + "hoursWeek": 2.66, + "meetingScheduleType": "LL", + "meetingType": "LEC", + "meetingTypeDescription": "Lecture", + "monday": false, + "room": "00116", + "saturday": false, + "startDate": "01/13/2020", + "sunday": false, + "term": "202003", + "thursday": true, + "tuesday": true, + "wednesday": false + }, + "term": "202003" + } + ], + "reservedSeatSummary": null, + "sectionAttributes": [], + "bookstores": [ + { + "url": "https://prd-wlssb.temple.edu/prod8/szp_reg_bookstore.p_ShowBookStoreLink_LuC?term_in=202003&camp_in=MN&dept_in=CIS&crse_in=3207&sect_in=002", + "label": "View Course Materials" + } + ], + "feeAmount": null + }, + "output": { + "classTimes": [ + { + "building": "SERC", + "day": "W", + "endTime": 1150, + "roomNumber": "00206", + "startTime": 1000, + "type": "LAB" }, - "term": "202003" - } - ], - "reservedSeatSummary": null, - "sectionAttributes": [], - "bookstores": [ - { - "url": "https://prd-wlssb.temple.edu/prod8/szp_reg_bookstore.p_ShowBookStoreLink_LuC?term_in=202003&camp_in=MN&dept_in=CIS&crse_in=3207&sect_in=002", - "label": "View Course Materials" - } - ], - "feeAmount": null + { + "building": "SERC", + "day": "T", + "endTime": 1350, + "roomNumber": "00116", + "startTime": 1230, + "type": "LEC" + }, + { + "building": "SERC", + "day": "R", + "endTime": 1350, + "roomNumber": "00116", + "startTime": 1230, + "type": "LEC" + } + ], + "courseName": "CIS 3207", + "crn": 30078, + "currentSeats": 19, + "currentWaitlist": 0, + "isOpen": true, + "professor": "Eugene Kwatny", + "section": "002", + "title": "Introduction to Systems Programming and Operating Systems", + "totalSeats": 30, + "totalWaitlist": 100, + "campus": "MN" + } }, - "output": { - "classTimes": [ - { - "building": "SERC", - "day": "W", - "endTime": 1150, - "roomNumber": "00206", - "startTime": 1000, - "type": "LAB" - }, - { - "building": "SERC", - "day": "T", - "endTime": 1350, - "roomNumber": "00116", - "startTime": 1230, - "type": "LEC" - }, - { - "building": "SERC", - "day": "R", - "endTime": 1350, - "roomNumber": "00116", - "startTime": 1230, - "type": "LEC" - } - ], - "courseName": "CIS 3207", - "crn": 30078, - "currentSeats": 19, - "currentWaitlist": 0, - "isOpen": true, - "professor": "Eugene Kwatny", - "section": "002", - "title": "Introduction to Systems Programming and Operating Systems", - "totalSeats": 30, - "totalWaitlist": 100, - "campus": "MN" - } - }, - "meetingTimeNoDays": { - "input": { - "id": 475368, - "term": "202003", - "termDesc": "2020 Spring", - "courseReferenceNumber": "1816", - "partOfTerm": "1", - "courseNumber": "4500", - "subject": "MUSC", - "subjectDescription": "Music", - "sequenceNumber": "001", - "campusDescription": "Main", - "scheduleTypeDescription": "Base Lecture", - "courseTitle": "Instrumental Ensemble", - "creditHours": null, - "maximumEnrollment": 150, - "enrollment": 86, - "seatsAvailable": 64, - "waitCapacity": 100, - "waitCount": 0, - "waitAvailable": 100, - "crossList": null, - "crossListCapacity": null, - "crossListCount": null, - "crossListAvailable": null, - "creditHourHigh": null, - "creditHourLow": 1, - "creditHourIndicator": null, - "openSection": true, - "linkIdentifier": null, - "isSectionLinked": false, - "subjectCourse": "MUSC4500", - "faculty": [ - { - "bannerId": "904549182", - "category": null, - "class": "net.hedtech.banner.student.faculty.FacultyResultDecorator", - "courseReferenceNumber": "1816", - "displayName": "Eric J. Schweingruber", - "emailAddress": "eschwein@temple.edu", - "primaryIndicator": true, - "term": "202003" - }, - { - "bannerId": "915313260", - "category": null, - "class": "net.hedtech.banner.student.faculty.FacultyResultDecorator", - "courseReferenceNumber": "1816", - "displayName": "Andreas Delfs", - "emailAddress": "tug29659@temple.edu", - "primaryIndicator": false, - "term": "202003" - } - ], - "meetingsFaculty": [ - { - "category": "01", - "class": "net.hedtech.banner.student.schedule.SectionSessionDecorator", - "courseReferenceNumber": "1816", - "faculty": [], - "meetingTime": { - "beginTime": null, - "building": null, - "buildingDescription": null, - "campus": null, - "campusDescription": null, - "category": "01", - "class": "net.hedtech.banner.general.overall.MeetingTimeDecorator", + "meetingTimeNoDays": { + "input": { + "id": 475368, + "term": "202003", + "termDesc": "2020 Spring", + "courseReferenceNumber": "1816", + "partOfTerm": "1", + "courseNumber": "4500", + "subject": "MUSC", + "subjectDescription": "Music", + "sequenceNumber": "001", + "campusDescription": "Main", + "scheduleTypeDescription": "Base Lecture", + "courseTitle": "Instrumental Ensemble", + "creditHours": null, + "maximumEnrollment": 150, + "enrollment": 86, + "seatsAvailable": 64, + "waitCapacity": 100, + "waitCount": 0, + "waitAvailable": 100, + "crossList": null, + "crossListCapacity": null, + "crossListCount": null, + "crossListAvailable": null, + "creditHourHigh": null, + "creditHourLow": 1, + "creditHourIndicator": null, + "openSection": true, + "linkIdentifier": null, + "isSectionLinked": false, + "subjectCourse": "MUSC4500", + "faculty": [ + { + "bannerId": "904549182", + "category": null, + "class": "net.hedtech.banner.student.faculty.FacultyResultDecorator", "courseReferenceNumber": "1816", - "creditHourSession": 0.0, - "endDate": "05/06/2020", - "endTime": null, - "friday": false, - "hoursWeek": 1.0, - "meetingScheduleType": "BAS", - "meetingType": "CLAS", - "meetingTypeDescription": "Class", - "monday": false, - "room": null, - "saturday": false, - "startDate": "01/13/2020", - "sunday": false, - "term": "202003", - "thursday": false, - "tuesday": false, - "wednesday": false + "displayName": "Eric J. Schweingruber", + "emailAddress": "eschwein@temple.edu", + "primaryIndicator": true, + "term": "202003" }, - "term": "202003" - } - ], - "reservedSeatSummary": null, - "sectionAttributes": [], - "bookstores": [ - { - "url": "https://prd-wlssb.temple.edu/prod8/szp_reg_bookstore.p_ShowBookStoreLink_LuC?term_in=202003&camp_in=MN&dept_in=MUSC&crse_in=4500&sect_in=001", - "label": "View Course Materials" - } - ], - "feeAmount": "$100.00" + { + "bannerId": "915313260", + "category": null, + "class": "net.hedtech.banner.student.faculty.FacultyResultDecorator", + "courseReferenceNumber": "1816", + "displayName": "Andreas Delfs", + "emailAddress": "tug29659@temple.edu", + "primaryIndicator": false, + "term": "202003" + } + ], + "meetingsFaculty": [ + { + "category": "01", + "class": "net.hedtech.banner.student.schedule.SectionSessionDecorator", + "courseReferenceNumber": "1816", + "faculty": [], + "meetingTime": { + "beginTime": null, + "building": null, + "buildingDescription": null, + "campus": null, + "campusDescription": null, + "category": "01", + "class": "net.hedtech.banner.general.overall.MeetingTimeDecorator", + "courseReferenceNumber": "1816", + "creditHourSession": 0.0, + "endDate": "05/06/2020", + "endTime": null, + "friday": false, + "hoursWeek": 1.0, + "meetingScheduleType": "BAS", + "meetingType": "CLAS", + "meetingTypeDescription": "Class", + "monday": false, + "room": null, + "saturday": false, + "startDate": "01/13/2020", + "sunday": false, + "term": "202003", + "thursday": false, + "tuesday": false, + "wednesday": false + }, + "term": "202003" + } + ], + "reservedSeatSummary": null, + "sectionAttributes": [], + "bookstores": [ + { + "url": "https://prd-wlssb.temple.edu/prod8/szp_reg_bookstore.p_ShowBookStoreLink_LuC?term_in=202003&camp_in=MN&dept_in=MUSC&crse_in=4500&sect_in=001", + "label": "View Course Materials" + } + ], + "feeAmount": "$100.00" + }, + "output": { + "classTimes": [], + "courseName": "MUSC 4500", + "crn": 1816, + "currentSeats": 86, + "currentWaitlist": 0, + "isOpen": true, + "professor": "Eric J. Schweingruber", + "section": "001", + "title": "Instrumental Ensemble", + "totalSeats": 150, + "totalWaitlist": 100, + "campus": null + } + }, - "output": { - "classTimes": [], - "courseName": "MUSC 4500", - "crn": 1816, - "currentSeats": 86, - "currentWaitlist": 0, - "isOpen": true, - "professor": "Eric J. Schweingruber", - "section": "001", - "title": "Instrumental Ensemble", - "totalSeats": 150, - "totalWaitlist": 100, - "campus": null - } - - }, - "emptyMeetings": { - "input": { - "id": 492822, - "term": "202003", - "termDesc": "2020 Spring", - "courseReferenceNumber": "37231", - "partOfTerm": "1", - "courseNumber": "9998", - "subject": "CEE", - "subjectDescription": "Civil Engineering", - "sequenceNumber": "015", - "campusDescription": "Main", - "scheduleTypeDescription": "Dissertation Course", - "courseTitle": "Pre-Dissertation Research", - "creditHours": null, - "maximumEnrollment": 5, - "enrollment": 0, - "seatsAvailable": 5, - "waitCapacity": 100, - "waitCount": 0, - "waitAvailable": 100, - "crossList": null, - "crossListCapacity": null, - "crossListCount": null, - "crossListAvailable": null, - "creditHourHigh": 6, - "creditHourLow": 1, - "creditHourIndicator": "TO", - "openSection": true, - "linkIdentifier": null, - "isSectionLinked": false, - "subjectCourse": "CEE9998", - "faculty": [], - "meetingsFaculty": [], - "reservedSeatSummary": null, - "sectionAttributes": [], - "bookstores": [ - { - "url": "https://prd-wlssb.temple.edu/prod8/szp_reg_bookstore.p_ShowBookStoreLink_LuC?term_in=202003&camp_in=MN&dept_in=CEE&crse_in=9998&sect_in=015", - "label": "View Course Materials" - } - ], - "feeAmount": null - }, - "output": { - "classTimes": [], - "courseName": "CEE 9998", - "crn": 37231, - "currentSeats": 0, - "currentWaitlist": 0, - "isOpen": true, - "professor": null, - "section": "015", - "title": "Pre-Dissertation Research", - "totalSeats": 5, - "totalWaitlist": 100, - "campus": null + "emptyMeetings": { + "input": { + "id": 492822, + "term": "202003", + "termDesc": "2020 Spring", + "courseReferenceNumber": "37231", + "partOfTerm": "1", + "courseNumber": "9998", + "subject": "CEE", + "subjectDescription": "Civil Engineering", + "sequenceNumber": "015", + "campusDescription": "Main", + "scheduleTypeDescription": "Dissertation Course", + "courseTitle": "Pre-Dissertation Research", + "creditHours": null, + "maximumEnrollment": 5, + "enrollment": 0, + "seatsAvailable": 5, + "waitCapacity": 100, + "waitCount": 0, + "waitAvailable": 100, + "crossList": null, + "crossListCapacity": null, + "crossListCount": null, + "crossListAvailable": null, + "creditHourHigh": 6, + "creditHourLow": 1, + "creditHourIndicator": "TO", + "openSection": true, + "linkIdentifier": null, + "isSectionLinked": false, + "subjectCourse": "CEE9998", + "faculty": [], + "meetingsFaculty": [], + "reservedSeatSummary": null, + "sectionAttributes": [], + "bookstores": [ + { + "url": "https://prd-wlssb.temple.edu/prod8/szp_reg_bookstore.p_ShowBookStoreLink_LuC?term_in=202003&camp_in=MN&dept_in=CEE&crse_in=9998&sect_in=015", + "label": "View Course Materials" + } + ], + "feeAmount": null + }, + "output": { + "classTimes": [], + "courseName": "CEE 9998", + "crn": 37231, + "currentSeats": 0, + "currentWaitlist": 0, + "isOpen": true, + "professor": null, + "section": "015", + "title": "Pre-Dissertation Research", + "totalSeats": 5, + "totalWaitlist": 100, + "campus": null + } } } } \ No newline at end of file From 12f3871b30ddbbe4b38e9d01bab87d8ec8f3f2c9 Mon Sep 17 00:00:00 2001 From: Paul Hutchings <36493057+paulhutchings@users.noreply.github.com> Date: Wed, 27 Nov 2019 21:23:46 -0500 Subject: [PATCH 24/39] added upload scripts for update lambdas --- lambda/node_modules/banner/index.js | 3 ++- lambda/scheduler/index.js | 2 +- lambda/update-delegate/index.js | 2 +- lambda/update-delegate/upload.sh | 7 ++++++ lambda/update-worker/cache.js | 3 +-- lambda/update-worker/index.js | 35 +++++++++++++++++++++++++++++ lambda/update-worker/upload.sh | 7 ++++++ 7 files changed, 54 insertions(+), 5 deletions(-) create mode 100755 lambda/update-delegate/upload.sh create mode 100755 lambda/update-worker/upload.sh diff --git a/lambda/node_modules/banner/index.js b/lambda/node_modules/banner/index.js index ee57dc6..235e916 100644 --- a/lambda/node_modules/banner/index.js +++ b/lambda/node_modules/banner/index.js @@ -157,6 +157,7 @@ class Banner { if (openOnly) params.chk_open_only = true; params = querystring.stringify(params); + await this.reset; const options = { method: 'GET', hostname: host, @@ -166,7 +167,7 @@ class Banner { 'Cookie': this.Cookie } }; - await this.reset; + let res = await promiseRequest(options); //Reset server for next classSearch this.reset = this._init(); diff --git a/lambda/scheduler/index.js b/lambda/scheduler/index.js index 6dd1e6c..9f2dd11 100644 --- a/lambda/scheduler/index.js +++ b/lambda/scheduler/index.js @@ -46,7 +46,7 @@ exports.handler = async (event, context) => { async function getSections(courses, campus){ const docClient = cache.DocumentClient; - return Promise.all(courses.map(async course => { + return Promise.all(courses.map(course => { let params = { TableName : process.env.TABLENAME, KeyConditionExpression : "#courseName = :course", diff --git a/lambda/update-delegate/index.js b/lambda/update-delegate/index.js index b7b9bb4..1efddc1 100644 --- a/lambda/update-delegate/index.js +++ b/lambda/update-delegate/index.js @@ -8,7 +8,7 @@ exports.handler = async (event, context) => { const banner = cache.Banner; try { var subjects = await banner.getSubjects(); - await Promise.all(subjects.map(subject => delegate(subject))); + await Promise.all(subjects.map(subject => delegate(subject.code))); } catch (error) { console.log(`Failed to fetch subjects from Banner: ${error}`); } diff --git a/lambda/update-delegate/upload.sh b/lambda/update-delegate/upload.sh new file mode 100755 index 0000000..e961a24 --- /dev/null +++ b/lambda/update-delegate/upload.sh @@ -0,0 +1,7 @@ +#!/bin/bash + +zip -u function.zip index.js cache.js +cd ../ +zip -u update-delegate/function.zip node_modules/banner/* +cd update-delegate +aws lambda update-function-code --function-name update_delegate --zip-file fileb://function.zip \ No newline at end of file diff --git a/lambda/update-worker/cache.js b/lambda/update-worker/cache.js index 0ef3082..7ae94c2 100644 --- a/lambda/update-worker/cache.js +++ b/lambda/update-worker/cache.js @@ -24,8 +24,7 @@ module.exports = function(){ return { DocumentClient: documentclient, Banner: banner, - BannerToDB: conversions.BannerToDB, - S3: s3 + BannerToDB: conversions.BannerToDB } }(); \ No newline at end of file diff --git a/lambda/update-worker/index.js b/lambda/update-worker/index.js index e183314..264ff1d 100644 --- a/lambda/update-worker/index.js +++ b/lambda/update-worker/index.js @@ -5,5 +5,40 @@ if (!cache){ } exports.handler = async (event, context) => { + const banner = cache.Banner; + const BannerToDB = cache.BannerToDB; + let subject = event.subject; + console.log(`Received ${subject}`); + let sections = await banner.classSearch(subject); + sections = BannerToDB(sections); + try { + await writeSections(sections); + } catch (error) { + console.log(`Did not successfully write all sections to database: ${error}`); + } +} + +async function writeSections(sections){ + const docClient = cache.DocumentClient; + let requests = sections.map(section => { + return { + 'PutRequest': { + 'Item': section + } + } + }); + let arrs = []; + while(requests.length > 0){ + arrs.push(requests.splice(0, 25)); + } + let pendingWrites = arrs.map(arr => { + return { + 'RequestItems': { + [process.env.TABLENAME]: arr + } + } + }); + + return Promise.all(pendingWrites.map(params => docClient.batchWrite(params).promise())); } \ No newline at end of file diff --git a/lambda/update-worker/upload.sh b/lambda/update-worker/upload.sh new file mode 100755 index 0000000..3b11f4a --- /dev/null +++ b/lambda/update-worker/upload.sh @@ -0,0 +1,7 @@ +#!/bin/bash + +zip -u function.zip index.js cache.js +cd ../ +zip -u update-worker/function.zip node_modules/conversions/* node_modules/banner/* +cd update-worker +aws lambda update-function-code --function-name update_worker --zip-file fileb://function.zip \ No newline at end of file From dff84d41486871cb33ba2c5cead6e5913831589e Mon Sep 17 00:00:00 2001 From: Paul Hutchings <36493057+paulhutchings@users.noreply.github.com> Date: Thu, 28 Nov 2019 09:36:13 -0500 Subject: [PATCH 25/39] Fixed bug that would allow undefined argument to constructor --- lambda/node_modules/banner/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lambda/node_modules/banner/index.js b/lambda/node_modules/banner/index.js index 235e916..5c339ce 100644 --- a/lambda/node_modules/banner/index.js +++ b/lambda/node_modules/banner/index.js @@ -18,7 +18,7 @@ const PAGE_SIZE = 100; */ class Banner { constructor(term){ - if (arguments.length < 1){ + if (arguments.length < 1 || term === undefined || term === null){ throw new Error('Must provide term to complete object construction'); } this.SessionId = Date.now(); From 472e4dd4265d460022239fd484bfe684e534f2dd Mon Sep 17 00:00:00 2001 From: Paul Hutchings <36493057+paulhutchings@users.noreply.github.com> Date: Thu, 28 Nov 2019 09:41:17 -0500 Subject: [PATCH 26/39] added open section ccondition to database query --- lambda/scheduler/index.js | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/lambda/scheduler/index.js b/lambda/scheduler/index.js index 9f2dd11..e990381 100644 --- a/lambda/scheduler/index.js +++ b/lambda/scheduler/index.js @@ -52,14 +52,18 @@ async function getSections(courses, campus){ KeyConditionExpression : "#courseName = :course", ExpressionAttributeNames : { "#courseName" : "courseName", - '#campus': 'campus' + '#campus': 'campus', + '#isOpen': 'isOpen' }, ExpressionAttributeValues : function(){ - let obj = {":course": course}; + let obj = { + ":course": course, + ':true': true + }; campus.forEach(campus => obj[`:${campus}`] = campus); return obj; }(), - FilterExpression: `#campus IN (${campus.map(campus => `:${campus}`).join()})` + FilterExpression: `#isOpen = :true AND #campus IN (${campus.map(campus => `:${campus}`).join()})` }; return docClient.query(params).promise(); From cec187952e29acb4ea4c32a692fec9eece625f36 Mon Sep 17 00:00:00 2001 From: Davis Samuel Date: Sat, 30 Nov 2019 21:50:57 -0500 Subject: [PATCH 27/39] Added filtering by day --- s3/index.html | 61 ++++++++++++++++++++++++++++++++++++++++++++- s3/styles/style.css | 16 ++++++++++-- 2 files changed, 74 insertions(+), 3 deletions(-) diff --git a/s3/index.html b/s3/index.html index 01a99fa..13b43ac 100644 --- a/s3/index.html +++ b/s3/index.html @@ -17,9 +17,26 @@
+ +
+ + +
- + + +