diff --git a/.glitch-assets b/.glitch-assets new file mode 100644 index 00000000..e69de29b diff --git a/README.md b/README.md index 2c6e21e2..d8043599 100755 --- a/README.md +++ b/README.md @@ -1,89 +1,46 @@ Assignment 2 - Short Stack: Basic Two-tier Web Application using HTML/CSS/JS and Node.js === -Due: September 9th, by 11:59 AM. +https://paristhecity-a3-paris-lopez.glitch.me/ + +Based on my experience with creating math problems and designs for mathspring.org, an intelligent tutoring system, +I decided to create a login page and roster for a set of teachers. While there are many ways to elaborate on my +original plan, I did not have enough time to explore all of my options for this page. I would have liked to have built +a page where several users could log-in and look at their roster, as well as the grades that accompany each student. +The teacher would be able to remove students from their class and add new students. When the 'information' button +is clicked, the teacher can add grades and assignments to the student chosen. Preferably, there would have been +a navigation bar at the top of the page to switch between assignments and account information for the target student. +Currently, all the information is based on one account of a teacher, and if one were to login as the other +teacher (teacher1, teacher1), the tables would be occupied by only the students of the admin teacher. This was intentional +to get started. My plan was to add an attribute in each object to track the active person. This attribute would be +set to '0' when the user is not active, and '1' when either a teacher logs on or when a student is chosen and becomes +the 'active' teacher or student. This way all the student information can be gathered from the appropriate sources; +whether it is a list of students, or a single student. + +Because the database, password service, and server required a lot of time to get started, I found myself with less time +than intended to piece together my design. With the remaining time I was able to build a login page, as well +as the beginnings of a student roster and list of assignments. Students can be added and removed from the list, +and the more information button brings the user to the list of assignments. The teacher can add assignments as well +as remove them. + +Though, I was unable to complete more of my original ideas beyond these features, the web app still +provides a few options for the user and does so in a responsive and clear manner. + +Login information:
+Username: admin
+Password: admin -This assignment aims to introduce you to the concepts and practice involved in creating a prototype (i.e. not deployment ready) two-tiered web application. - -The baseline aims of this assignment involve creating an application that demonstrates the use of several specific pieces of HTML, CSS, JavaScript, and Node.js functionality. -Another aim of this assignment is to establish creative boundaries in which you and your partner can explore designing, implementing, and evaluating usable, useful, novel, and technically efficient web applications. - -Baseline Requirements ---- - -Note that there is a very large range of application areas and possibilities that meet these baseline requirements. -Games, internet of things, organizational tools, commerce, media - all are possibilities with a two-tiered form-focused web application. - -Do not limit yourselves to any of the examples given below. -Examples like the upcoming `efficiency_ratio` idea for the `cars` dataset are meant to be illustrative and easy to understand. -They are not intended to be sensible or useful ideas. - -Your application is required to implement the following functionalities: - -- a `Server` which not only serves files, but also maintains a tabular dataset with 3 or more fields related to your application -- a `Results` functionality which shows the entire dataset residing in the server's memory -- a `Form/Entry` functionality which allows a user to add, modify, or delete data items residing in the server's memory -- a `Server Logic` which, upon receiving new or modified "incoming" data, includes and uses a function that adds at least one additional derived field to this incoming data before integrating it with the existing dataset - - the `Derived field` for a new row of data must be computed based on fields already existing in the row. For example, a `cars` dataset with `year`, `horsepower`, and `fuel_efficiency` may create a new field `efficiency_ratio` by dividing `fuel_efficiency` by `horsepower` - -Your application is required to demonstrate the use of the following concepts: - -HTML: -- One or more [HTML Forms](https://developer.mozilla.org/en-US/docs/Learn/HTML/Forms), with any combination of form tags appropriate for the user input portion of the application - - Clarification: the results page can be implemented in any way. `
`s, `table`s, and `list`s are common choices - -CSS: -- CSS styling of the primary visual elements in the application -- Various CSS Selector functionality must be demonstrated: - - Element selectors - - ID selectors - - Class selectors -- CSS positioning and sizing of the primary visual elements in the application: - - CSS to cause at least one element to be horizontally centered on the page - - CSS to cause at least one pair of elements to appear side-by-side - - CSS defined in a maintainable, readable form, in external stylesheets - -JavaScript: -- At minimum, a small amount of front-end JavaScript to get / fetch data from the server; a sample is provided in this repository. - -Node.js: -- An HTTP Server that delivers all necessary files and data for the application. A starting point is provided in this repository. - -Deliverables ---- - -Do the following to complete this assignment: - -1. Fork the starting project code. This repo contains some starter code that may be used or discarded as needed. -2. Implement your project with the above requirements. -3. Test your project to make sure that when someone goes to your main page, it displays correctly. -4. Deploy your project to Glitch, and fill in the appropriate fields in your package.json file. -5. Ensure that your project has the proper naming scheme `a2-yourname` so we can find it. -6. Modify the Readme to the specifications below. -7. Create and submit a Pull Request to the original repo. Only one member needs to submit a pull request. - -Sample Readme (delete the above when you're ready to submit, and modify the below so with your links and descriptions) ---- - -## Your Web Application Title -Include a very brief summary of your project here. -Images are encouraged, along with concise, high-level text. - -Here is a sample formula for summarizing your activities, talk about: -- the domain area the project pertains to -- the main challenges or problems the application addresses -- the key innovations that make it possible to address the problem -- the main results of the implementation, does it really address the problem? -- any additional implications of the resulting application, or possibly areas for future work that have been discovered as part of the design and implementation activities - -(Note that when I use the above formula, I aim to have only one sentence per thought in order to remain concise.) - -http://a2-charlieroberts.glitch.me ## Technical Achievements -- **Tech Achievement 1**: Using a combination of... -- **Tech Achievement 2**: ... +- **Tech Achievement 1**: I completed the technical aspect of the assignment +by creating a lowdb database that stores various information and provides it to the web page when necessary. ### Design/Evaluation Achievements -- **Design Achievement 1**: Shown in `style.css`, the code... -- **Design Achievement 2**: We tested the application with n=X users, finding that... +- **Design Achievement 1**: Prior to the creation of the web application, I put great thought into my ideas and design. +I found the CSS Template to be greatly limiting, however I was still able to create a clean user interface appropriate +for its purpose. I prepared my web application with some simple drawings to sketch out my ideas on paper. With a +prototype in hand, I moved onto building the web app itself. + +- **Design Achievement 2**: I utilized a CSS template that provided a majority of the style and the functionality. I +also included a couple of JQuery scripts to make the removal of table rows easier and the connection between multiple +pages. diff --git a/db.json b/db.json new file mode 100644 index 00000000..4e6bc851 --- /dev/null +++ b/db.json @@ -0,0 +1,60 @@ +{ + "users": [ + { + "username": "admin", + "password": "admin", + "access": "1", + "students": [ + { + "first": "Jane", + "last": "Doe", + "active": 0, + "assignments": [ + { + "assignment": "HW1", + "grade": 30 + }, + { + "assignment": "HW2", + "grade": 90 + }, + { + "assignment": "Test", + "grade": 70 + } + ] + }, + { + "first": "John", + "last": "Smith", + "active": 0, + "assignments": [ + { + "assignment": "HW1", + "grade": 40 + }, + { + "assignment": "HW2", + "grade": 100 + }, + { + "assignment": "Test", + "grade": 90 + } + ] + }, + { + "first": "Paris", + "last": "Lopez", + "grade": 0 + } + ] + }, + { + "username": "teacher1", + "password": "teacher1", + "access": "1", + "students": [] + } + ] +} \ No newline at end of file diff --git a/package.json b/package.json index 988f135f..6ccf8d6e 100755 --- a/package.json +++ b/package.json @@ -1,12 +1,20 @@ { - "name": "", - "version": "", - "description": "", - "author": "", + "name": "app", + "version": "0.0.0", + "private": true, "scripts": { - "start": "node server.improved.js" + "start": "node server.js" }, "dependencies": { - "mime": "^2.4.4" + "cookie-parser": "^1.4.4", + "debug": "~2.6.9", + "express": "^4.16.4", + "http-errors": "~1.6.3", + "jade": "^1.11.0", + "lowdb": "^1.0.0", + "morgan": "~1.9.1", + "passport": "^0.4.0", + "passport-local": "^1.0.0", + "body-parser": "^1.19.0" } -} +} \ No newline at end of file diff --git a/public/css/style.css b/public/css/style.css deleted file mode 100755 index d5f842ab..00000000 --- a/public/css/style.css +++ /dev/null @@ -1 +0,0 @@ -/*Style your own assignment! This is fun! */ \ No newline at end of file diff --git a/public/homePage/home.css b/public/homePage/home.css new file mode 100644 index 00000000..7c851cd0 --- /dev/null +++ b/public/homePage/home.css @@ -0,0 +1,4 @@ +.container { + margin-top: 10px; +} + diff --git a/public/homePage/home.html b/public/homePage/home.html new file mode 100644 index 00000000..0e55de9b --- /dev/null +++ b/public/homePage/home.html @@ -0,0 +1,83 @@ + + + + + + + + + + + + + + + Class Roster + + + + +
+

+ Class Roster +

+
+
+
+ +
+
+ +
+
+ +
+
+
+
+ + +
+ + + + + + + + + + + + +
FirstLastFinal GradeMore InformationRemove Student
+
+ + + + + + + + + + + + + \ No newline at end of file diff --git a/public/homePage/home.js b/public/homePage/home.js new file mode 100644 index 00000000..20da78a6 --- /dev/null +++ b/public/homePage/home.js @@ -0,0 +1,122 @@ + +const occupyStudents = function( e ) { + fetch('/occupyStudents', { + method: 'GET', + headers: { 'Content-Type': 'application/json' } + }).then(function(response) { + console.log(response) + return response.json(); + }).then(function(data) { + let students = data; + let i + for(i = 0; i < students.length; i++) { + let student = students[i] + let assignments = student.assignments + var grade = gradeCalc(assignments) + fillStudentInfo(student.first, student.last, grade) + } + console.log(students) + }) +} + +const gradeCalc = function( assignments ) { + var sum = 0 + let i + if(assignments) { + for(i = 0; i < assignments.length; i++){ + sum += parseInt(assignments[i].grade) + } + return (sum/assignments.length).toFixed(2) + } + else + return 0 +} + + +const addStudent = function( e ) { + e.preventDefault() + console.log("Add Student") + + const firstName = document.querySelector('#first').value + const lastName = document.querySelector('#last').value + const grade = 0 + + const info = {first: firstName, last: lastName, grade: grade} + const body = JSON.stringify(info) + + fetch('/addStudent', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body + }).then(function( response ) { + console.log(response) + fillStudentInfo(firstName, lastName, grade); + }) + + return false; +} + +const removeStudent = function( first, last ) { + console.log("Remove Student") + const info = {first: first, last: last } + const body = JSON.stringify(info) + fetch('/deleteStudent', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body + }).then(function(response) { + console.log(response) + }) +} + +const getStudentInfo = function( ) { + console.log("Retrieve Student Information") + +} + + +const fillStudentInfo = function( firstName, lastName, grade ) { + var start = document.querySelector("tbody") + var row = document.createElement("tr") + + var cell_firstName = document.createElement("td") + cell_firstName.innerHTML = firstName + cell_firstName.setAttribute("scope", "row") + var cell_lastName = document.createElement("td") + cell_lastName.innerHTML = lastName + var cell_grade = document.createElement("td") + cell_grade.innerHTML = grade + var cell_info = document.createElement("td") + var cell_del = document.createElement("td") + + var info = document.createElement("button") + info.setAttribute('type', 'button') + info.setAttribute('class', 'btn btn-secondary info') + info.innerHTML = "Information" + cell_info.appendChild(info) + + var button = document.createElement("button") + button.setAttribute('type', 'delete') + button.setAttribute('class', 'btn btn-danger remove') + button.innerHTML = "Delete" + cell_del.appendChild(button) + + row.appendChild(cell_firstName) + row.appendChild(cell_lastName) + row.appendChild(cell_grade) + row.appendChild(cell_info) + row.appendChild(cell_del) + + start.appendChild(row) + +} + +window.onload = function() { + console.log("home.html: javascript loaded") + occupyStudents() + + const addButton = document.querySelector("#addStudent"); + addButton.onclick = addStudent + +} + diff --git a/public/index.css b/public/index.css new file mode 100644 index 00000000..549f73d3 --- /dev/null +++ b/public/index.css @@ -0,0 +1,5 @@ +.container { + width: 40%; + margin-top: 20px; +} + diff --git a/public/index.html b/public/index.html index c56d620e..36a0ccd6 100755 --- a/public/index.html +++ b/public/index.html @@ -1,41 +1,43 @@ - CS4241 Assignment 2 + - - -
- - -
- - + + Login Page + + - window.onload = function() { - const button = document.querySelector( 'button' ) - button.onclick = submit - } + +
+ +
+
+

+ Teacher Login +

+
+ +
+ + +
+
+ + +
+
 
+ +
+
+ - diff --git a/public/index.js b/public/index.js new file mode 100644 index 00000000..f5a8ec9d --- /dev/null +++ b/public/index.js @@ -0,0 +1,37 @@ + + +const loginAction = function( e ) { + e.preventDefault() + + const username = document.querySelector('#username').value + const password = document.querySelector('#password').value + const state = document.querySelector('#state') + + const body = { username: username, password: password }; + console.log(body); + fetch('/login', { + method: 'POST', + body : JSON.stringify(body), + headers: { 'Content-Type': 'application/json' } + }) + .then( response => { + console.log(response) + return response.json() + }) + .then( response => { + console.log(response) + location.href = './homePage/home.html' + }) + .catch(err => { + console.log(err) + state.innerHTML = "Incorrect Username or Password"; + }) + return false; +}; + +window.onload = function() { + console.log("index.html: javascript loaded") + + const loginButton = document.querySelector('#login'); + loginButton.onclick = loginAction; +} \ No newline at end of file diff --git a/public/infoPage/infoStudent.css b/public/infoPage/infoStudent.css new file mode 100644 index 00000000..679ecff4 --- /dev/null +++ b/public/infoPage/infoStudent.css @@ -0,0 +1,3 @@ +.container { + margin-top: 10px; +} diff --git a/public/infoPage/studentInfo.html b/public/infoPage/studentInfo.html new file mode 100644 index 00000000..a5c5b90a --- /dev/null +++ b/public/infoPage/studentInfo.html @@ -0,0 +1,75 @@ + + + + + + + + + + + + + + + + Class Roster + + +
+

+ Student +

+
+
+
+ +
+
+ +
+
+ +
+
+
+
+ +
+ + + + + + + + + + +
AssignmentGradeDelete
+
+ + + + + + + + + + + + + + \ No newline at end of file diff --git a/public/infoPage/studentInfo.js b/public/infoPage/studentInfo.js new file mode 100644 index 00000000..d6de1119 --- /dev/null +++ b/public/infoPage/studentInfo.js @@ -0,0 +1,89 @@ +const occupyAssignments = function( ) { + fetch('/occupyAssignments', { + method: 'GET', + headers: { 'Content-Type': 'application/json' } + }).then(function(response) { + console.log(response) + return response.json(); + }).then(function(data) { + let assignments = data; + let i + for(i = 0; i < assignments.length; i++) { + let assignment = assignments[i] + //let assignments = student.assignments + console.log(Object.values(assignment)) + fillAssignment( assignment.assignment, assignment.grade) + } + console.log(assignments) + }) +} + +const removeAssignment = function( assignment, grade ) { + console.log("Remove Assignment") + const info = {assignment: assignment, grade: grade } + const body = JSON.stringify(info) + fetch('/delAssignment', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body + }).then(function(response) { + console.log(response) + }) +} + + +const addAssignment = function( e ) { + e.preventDefault() + console.log("Add Assignment") + + const assignment = document.querySelector('#assignment').value + const grade = document.querySelector('#grade').value + + const info = {assignment: assignment, grade: grade} + const body = JSON.stringify(info) + + fetch('/addAssignment', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body + }).then(function( response ) { + console.log(response) + fillAssignment(assignment, grade); + }) + + return false; +} + +const fillAssignment = function( assignment, grade ) { + var start = document.querySelector("tbody") + var row = document.createElement("tr") + + var cell_assignment = document.createElement("td") + cell_assignment.innerHTML = assignment + cell_assignment.setAttribute("scope", "row") + var cell_grade = document.createElement("td") + cell_grade.innerHTML = grade + var cell_del = document.createElement("td") + + var button = document.createElement("button") + button.setAttribute('type', 'delete') + button.setAttribute('class', 'btn btn-danger remove') + button.innerHTML = "Delete" + cell_del.appendChild(button) + + row.appendChild(cell_assignment) + row.appendChild(cell_grade) + row.appendChild(cell_del) + + start.appendChild(row) + +} + +window.onload = function() { + console.log("home.html: javascript loaded") + occupyAssignments() + + const addButton = document.querySelector("#addAssignment"); + addButton.onclick = addAssignment + +} \ No newline at end of file diff --git a/public/js/scripts.js b/public/js/scripts.js deleted file mode 100755 index de052eae..00000000 --- a/public/js/scripts.js +++ /dev/null @@ -1,3 +0,0 @@ -// Add some Javascript code here, to run on the front end. - -console.log("Welcome to assignment 2!") \ No newline at end of file diff --git a/server.improved.js b/server.improved.js deleted file mode 100644 index 26673fc0..00000000 --- a/server.improved.js +++ /dev/null @@ -1,72 +0,0 @@ -const http = require( 'http' ), - fs = require( 'fs' ), - // IMPORTANT: you must run `npm install` in the directory for this assignment - // to install the mime library used in the following line of code - mime = require( 'mime' ), - dir = 'public/', - port = 3000 - -const appdata = [ - { 'model': 'toyota', 'year': 1999, 'mpg': 23 }, - { 'model': 'honda', 'year': 2004, 'mpg': 30 }, - { 'model': 'ford', 'year': 1987, 'mpg': 14} -] - -const server = http.createServer( function( request,response ) { - if( request.method === 'GET' ) { - handleGet( request, response ) - }else if( request.method === 'POST' ){ - handlePost( request, response ) - } -}) - -const handleGet = function( request, response ) { - const filename = dir + request.url.slice( 1 ) - - if( request.url === '/' ) { - sendFile( response, 'public/index.html' ) - }else{ - sendFile( response, filename ) - } -} - -const handlePost = function( request, response ) { - let dataString = '' - - request.on( 'data', function( data ) { - dataString += data - }) - - request.on( 'end', function() { - console.log( JSON.parse( dataString ) ) - - // ... do something with the data here!!! - - response.writeHead( 200, "OK", {'Content-Type': 'text/plain' }) - response.end() - }) -} - -const sendFile = function( response, filename ) { - const type = mime.getType( filename ) - - fs.readFile( filename, function( err, content ) { - - // if the error = null, then we've loaded the file successfully - if( err === null ) { - - // status code: https://httpstatuses.com - response.writeHeader( 200, { 'Content-Type': type }) - response.end( content ) - - }else{ - - // file not found, error code 404 - response.writeHeader( 404 ) - response.end( '404 Error: File Not Found' ) - - } - }) -} - -server.listen( process.env.PORT || port ) diff --git a/server.js b/server.js new file mode 100644 index 00000000..d7469e6e --- /dev/null +++ b/server.js @@ -0,0 +1,140 @@ +//express imports +const express = require('express') +const app = express() +const bodyParser = require('body-parser'); +const port = 3000 + +//passport imports +const passport = require('passport'); +const LocalStrategy = require('passport-local').Strategy; + +//lowdb imports +const low = require('lowdb') +const FileSync = require('lowdb/adapters/FileSync') +const adapter = new FileSync('db.json') +const db = low(adapter); + +const dir = "public/"; +app.use(express.static(dir)); +app.use(bodyParser.json()); + +//Teacher Users +db.defaults({ users: [] }).write(); +let users +const occupyUsers = function() { + users = []; + let i = 0; + while (true) { + let user = db.get(`users[${i}]`).value(); + if (user) users.push(user) + else break + i++; + } +}; +occupyUsers(); + +//Password Authentication +const strategy = function(username, password, done) { + const user = users.find(usr => usr.username === username); + if (user === undefined) + return done(null, false, { message: 'User Not Found' }); + else if (user.password === password) + return done(null, { username, password }); + else + return done(null, false, { message: 'Password Incorrect' }); +}; + +app.use(passport.initialize()); +app.use(passport.session()); +passport.use(new LocalStrategy(strategy)) + +passport.serializeUser((user, done) => done(null, user.username)) +passport.deserializeUser((username, done) => { + let user = users.find(usr => usr.username === username) + if (user !== undefined) { + done(null, user) + } else { + done(null, false, { message: 'User Not Found' }) + } +}) + + +//POST REQUESTS +app.post( '/login', + passport.authenticate('local'), + function( request, response ) { + const currentUser = users.find(usr => usr.username === request.user.username) + response.json({ status: true }) +}) + +app.post( '/addStudent', function( request, response ) { + db.get('users[0].students').push(request.body).write(); + occupyUsers() + response.writeHead(200, { 'Content-Type': 'application/json' }); + response.end(); +}) + +app.post( '/deleteStudent', function( request, response ) { + db.get('users[0].students').remove({ first: request.body.first, last: request.body.last }).write() + occupyUsers() + response.writeHead(200, { 'Content-Type': 'application/json' }); + response.end(); +}) + +app.post( '/addAssignment', function( request, response ) { + db.get('users[0].students[0].assignments').push(request.body).write(); + occupyUsers() + response.writeHead(200, { 'Content-Type': 'application/json' }); + response.end(); +}) + +app.post( '/delAssignment', function( request, response ) { + db.get('users[0].students[0].assignments').remove({ assignment: request.body.assignment, grade: request.body.grade }).write() + occupyUsers() + response.writeHead(200, { 'Content-Type': 'application/json' }); + response.end(); +}) + +//GET REQUESTS +app.get( '/occupyStudents', function( request, response ) { + var studentList = [] + let i + for(i = 0; i < users.length; i++){ + } + studentList = users[0].students + response.send(studentList) +}) + +app.get( '/occupyAssignments', function( request, response ) { + var assignmentList = [] + let i + for(i = 0; i < users.length; i++){ + } + assignmentList = users[0].students[0].assignments + response.send(assignmentList) +}) + + +/* +app.get('/refreshAll', function(request, response) { + response.send(allUsers); +}); + +app.post('/update', function(request, response) { + let body = request.body + + db.get('allUsers') + .find({ username: you.username }) + .assign({ + name: body.name, + age: body.age, + gender: body.gender, + hobby: body.hobby + }).write() + copyAllUsers() + response.writeHead(200, { 'Content-Type': 'application/json' }); + response.end(); +*/ + +app.listen(port, () => console.log('Example app listening on port ${port}!')) + diff --git a/shrinkwrap.yaml b/shrinkwrap.yaml new file mode 100644 index 00000000..80d7f8e1 --- /dev/null +++ b/shrinkwrap.yaml @@ -0,0 +1,736 @@ +dependencies: + cookie-parser: 1.4.4 + debug: 2.6.9 + express: 4.16.4 + http-errors: 1.6.3 + jade: 1.11.0 + morgan: 1.9.1 +packages: + /accepts/1.3.7: + dependencies: + mime-types: 2.1.24 + negotiator: 0.6.2 + dev: false + engines: + node: '>= 0.6' + resolution: + integrity: sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA== + /acorn-globals/1.0.9: + dependencies: + acorn: 2.7.0 + dev: false + resolution: + integrity: sha1-VbtemGkVB7dFedBRNBMhfDgMVM8= + /acorn/1.2.2: + dev: false + engines: + node: '>=0.4.0' + hasBin: true + resolution: + integrity: sha1-yM4n3grMdtiW0rH6099YjZ6C8BQ= + /acorn/2.7.0: + dev: false + engines: + node: '>=0.4.0' + hasBin: true + resolution: + integrity: sha1-q259nYhqrKiwhbwzEreaGYQz8Oc= + /align-text/0.1.4: + dependencies: + kind-of: 3.2.2 + longest: 1.0.1 + repeat-string: 1.6.1 + dev: false + engines: + node: '>=0.10.0' + resolution: + integrity: sha1-DNkKVhCT810KmSVsIrcGlDP60Rc= + /amdefine/1.0.1: + dev: false + engines: + node: '>=0.4.2' + resolution: + integrity: sha1-SlKCrBZHKek2Gbz9OtFR+BfOkfU= + /array-flatten/1.1.1: + dev: false + resolution: + integrity: sha1-ml9pkFGx5wczKPKgCJaLZOopVdI= + /asap/1.0.0: + dev: false + resolution: + integrity: sha1-sqRdpf36ILBJb8N2jMJ8EvqRan0= + /basic-auth/2.0.1: + dependencies: + safe-buffer: 5.1.2 + dev: false + engines: + node: '>= 0.8' + resolution: + integrity: sha512-NF+epuEdnUYVlGuhaxbbq+dvJttwLnGY+YixlXlME5KpQ5W3CnXA5cVTneY3SPbPDRkcjMbifrwmFYcClgOZeg== + /body-parser/1.18.3: + dependencies: + bytes: 3.0.0 + content-type: 1.0.4 + debug: 2.6.9 + depd: 1.1.2 + http-errors: 1.6.3 + iconv-lite: 0.4.23 + on-finished: 2.3.0 + qs: 6.5.2 + raw-body: 2.3.3 + type-is: 1.6.18 + dev: false + engines: + node: '>= 0.8' + resolution: + integrity: sha1-WykhmP/dVTs6DyDe0FkrlWlVyLQ= + /bytes/3.0.0: + dev: false + engines: + node: '>= 0.8' + resolution: + integrity: sha1-0ygVQE1olpn4Wk6k+odV3ROpYEg= + /camelcase/1.2.1: + dev: false + engines: + node: '>=0.10.0' + resolution: + integrity: sha1-m7UwTS4LVmmLLHWLCKPqqdqlijk= + /center-align/0.1.3: + dependencies: + align-text: 0.1.4 + lazy-cache: 1.0.4 + dev: false + engines: + node: '>=0.10.0' + resolution: + integrity: sha1-qg0yYptu6XIgBBHL1EYckHvCt60= + /character-parser/1.2.1: + dev: false + resolution: + integrity: sha1-wN3kqxgnE7kZuXCVmhI+zBow/NY= + /clean-css/3.4.28: + dependencies: + commander: 2.8.1 + source-map: 0.4.4 + dev: false + engines: + node: '>=0.10.0' + hasBin: true + resolution: + integrity: sha1-vxlF6C/ICPVWlebd6uwBQA79A/8= + /cliui/2.1.0: + dependencies: + center-align: 0.1.3 + right-align: 0.1.3 + wordwrap: 0.0.2 + dev: false + resolution: + integrity: sha1-S0dXYP+AJkx2LDoXGQMukcf+oNE= + /commander/2.6.0: + dev: false + engines: + node: '>= 0.6.x' + resolution: + integrity: sha1-nfflL7Kgyw+4kFjugMMQQiXzfh0= + /commander/2.8.1: + dependencies: + graceful-readlink: 1.0.1 + dev: false + engines: + node: '>= 0.6.x' + resolution: + integrity: sha1-Br42f+v9oMMwqh4qBy09yXYkJdQ= + /constantinople/3.0.2: + dependencies: + acorn: 2.7.0 + deprecated: Please update to at least constantinople 3.1.1 + dev: false + resolution: + integrity: sha1-S5RdmTeQe82Y7ldRIsOBdRZUQUE= + /content-disposition/0.5.2: + dev: false + engines: + node: '>= 0.6' + resolution: + integrity: sha1-DPaLud318r55YcOoUXjLhdunjLQ= + /content-type/1.0.4: + dev: false + engines: + node: '>= 0.6' + resolution: + integrity: sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA== + /cookie-parser/1.4.4: + dependencies: + cookie: 0.3.1 + cookie-signature: 1.0.6 + dev: false + engines: + node: '>= 0.8.0' + resolution: + integrity: sha512-lo13tqF3JEtFO7FyA49CqbhaFkskRJ0u/UAiINgrIXeRCY41c88/zxtrECl8AKH3B0hj9q10+h3Kt8I7KlW4tw== + /cookie-signature/1.0.6: + dev: false + resolution: + integrity: sha1-4wOogrNCzD7oylE6eZmXNNqzriw= + /cookie/0.3.1: + dev: false + engines: + node: '>= 0.6' + resolution: + integrity: sha1-5+Ch+e9DtMi6klxcWpboBtFoc7s= + /css-parse/1.0.4: + dev: false + resolution: + integrity: sha1-OLBQP7+dqfVOnB29pg4UXHcRe90= + /css-stringify/1.0.5: + dev: false + resolution: + integrity: sha1-sNBClG2ylTu50pKQCmy19tASIDE= + /css/1.0.8: + dependencies: + css-parse: 1.0.4 + css-stringify: 1.0.5 + dev: false + resolution: + integrity: sha1-k4aBHKgrzMnuf7WnMrHioxfIo+c= + /debug/2.6.9: + dependencies: + ms: 2.0.0 + dev: false + resolution: + integrity: sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA== + /decamelize/1.2.0: + dev: false + engines: + node: '>=0.10.0' + resolution: + integrity: sha1-9lNNFRSCabIDUue+4m9QH5oZEpA= + /depd/1.1.2: + dev: false + engines: + node: '>= 0.6' + resolution: + integrity: sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak= + /destroy/1.0.4: + dev: false + resolution: + integrity: sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA= + /ee-first/1.1.1: + dev: false + resolution: + integrity: sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0= + /encodeurl/1.0.2: + dev: false + engines: + node: '>= 0.8' + resolution: + integrity: sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k= + /escape-html/1.0.3: + dev: false + resolution: + integrity: sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg= + /etag/1.8.1: + dev: false + engines: + node: '>= 0.6' + resolution: + integrity: sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc= + /express/4.16.4: + dependencies: + accepts: 1.3.7 + array-flatten: 1.1.1 + body-parser: 1.18.3 + content-disposition: 0.5.2 + content-type: 1.0.4 + cookie: 0.3.1 + cookie-signature: 1.0.6 + debug: 2.6.9 + depd: 1.1.2 + encodeurl: 1.0.2 + escape-html: 1.0.3 + etag: 1.8.1 + finalhandler: 1.1.1 + fresh: 0.5.2 + merge-descriptors: 1.0.1 + methods: 1.1.2 + on-finished: 2.3.0 + parseurl: 1.3.3 + path-to-regexp: 0.1.7 + proxy-addr: 2.0.5 + qs: 6.5.2 + range-parser: 1.2.1 + safe-buffer: 5.1.2 + send: 0.16.2 + serve-static: 1.13.2 + setprototypeof: 1.1.0 + statuses: 1.4.0 + type-is: 1.6.18 + utils-merge: 1.0.1 + vary: 1.1.2 + dev: false + engines: + node: '>= 0.10.0' + resolution: + integrity: sha512-j12Uuyb4FMrd/qQAm6uCHAkPtO8FDTRJZBDd5D2KOL2eLaz1yUNdUB/NOIyq0iU4q4cFarsUCrnFDPBcnksuOg== + /finalhandler/1.1.1: + dependencies: + debug: 2.6.9 + encodeurl: 1.0.2 + escape-html: 1.0.3 + on-finished: 2.3.0 + parseurl: 1.3.3 + statuses: 1.4.0 + unpipe: 1.0.0 + dev: false + engines: + node: '>= 0.8' + resolution: + integrity: sha512-Y1GUDo39ez4aHAw7MysnUD5JzYX+WaIj8I57kO3aEPT1fFRL4sr7mjei97FgnwhAyyzRYmQZaTHb2+9uZ1dPtg== + /forwarded/0.1.2: + dev: false + engines: + node: '>= 0.6' + resolution: + integrity: sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ= + /fresh/0.5.2: + dev: false + engines: + node: '>= 0.6' + resolution: + integrity: sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac= + /graceful-readlink/1.0.1: + dev: false + resolution: + integrity: sha1-TK+tdrxi8C+gObL5Tpo906ORpyU= + /http-errors/1.6.3: + dependencies: + depd: 1.1.2 + inherits: 2.0.3 + setprototypeof: 1.1.0 + statuses: 1.5.0 + dev: false + engines: + node: '>= 0.6' + resolution: + integrity: sha1-i1VoC7S+KDoLW/TqLjhYC+HZMg0= + /iconv-lite/0.4.23: + dependencies: + safer-buffer: 2.1.2 + dev: false + engines: + node: '>=0.10.0' + resolution: + integrity: sha512-neyTUVFtahjf0mB3dZT77u+8O0QB89jFdnBkd5P1JgYPbPaia3gXXOVL2fq8VyU2gMMD7SaN7QukTB/pmXYvDA== + /inherits/2.0.3: + dev: false + resolution: + integrity: sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4= + /ipaddr.js/1.9.0: + dev: false + engines: + node: '>= 0.10' + resolution: + integrity: sha512-M4Sjn6N/+O6/IXSJseKqHoFc+5FdGJ22sXqnjTpdZweHK64MzEPAyQZyEU3R/KRv2GLoa7nNtg/C2Ev6m7z+eA== + /is-buffer/1.1.6: + dev: false + resolution: + integrity: sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w== + /is-promise/1.0.1: + dev: false + resolution: + integrity: sha1-MVc3YcBX4zwukaq56W2gjO++duU= + /is-promise/2.1.0: + dev: false + resolution: + integrity: sha1-eaKp7OfwlugPNtKy87wWwf9L8/o= + /jade/1.11.0: + dependencies: + character-parser: 1.2.1 + clean-css: 3.4.28 + commander: 2.6.0 + constantinople: 3.0.2 + jstransformer: 0.0.2 + mkdirp: 0.5.1 + transformers: 2.1.0 + uglify-js: 2.8.29 + void-elements: 2.0.1 + with: 4.0.3 + deprecated: 'Jade has been renamed to pug, please install the latest version of pug instead of jade' + dev: false + hasBin: true + resolution: + integrity: sha1-nIDlOMEtP7lcjZu5VZ+gzAQEBf0= + /jstransformer/0.0.2: + dependencies: + is-promise: 2.1.0 + promise: 6.1.0 + dev: false + resolution: + integrity: sha1-eq4pqQPRls+glz2IXT5HlH7Ndqs= + /kind-of/3.2.2: + dependencies: + is-buffer: 1.1.6 + dev: false + engines: + node: '>=0.10.0' + resolution: + integrity: sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ= + /lazy-cache/1.0.4: + dev: false + engines: + node: '>=0.10.0' + resolution: + integrity: sha1-odePw6UEdMuAhF07O24dpJpEbo4= + /longest/1.0.1: + dev: false + engines: + node: '>=0.10.0' + resolution: + integrity: sha1-MKCy2jj3N3DoKUoNIuZiXtd9AJc= + /media-typer/0.3.0: + dev: false + engines: + node: '>= 0.6' + resolution: + integrity: sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g= + /merge-descriptors/1.0.1: + dev: false + resolution: + integrity: sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E= + /methods/1.1.2: + dev: false + engines: + node: '>= 0.6' + resolution: + integrity: sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4= + /mime-db/1.40.0: + dev: false + engines: + node: '>= 0.6' + resolution: + integrity: sha512-jYdeOMPy9vnxEqFRRo6ZvTZ8d9oPb+k18PKoYNYUe2stVEBPPwsln/qWzdbmaIvnhZ9v2P+CuecK+fpUfsV2mA== + /mime-types/2.1.24: + dependencies: + mime-db: 1.40.0 + dev: false + engines: + node: '>= 0.6' + resolution: + integrity: sha512-WaFHS3MCl5fapm3oLxU4eYDw77IQM2ACcxQ9RIxfaC3ooc6PFuBMGZZsYpvoXS5D5QTWPieo1jjLdAm3TBP3cQ== + /mime/1.4.1: + dev: false + hasBin: true + resolution: + integrity: sha512-KI1+qOZu5DcW6wayYHSzR/tXKCDC5Om4s1z2QJjDULzLcmf3DvzS7oluY4HCTrc+9FiKmWUgeNLg7W3uIQvxtQ== + /minimist/0.0.8: + dev: false + resolution: + integrity: sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0= + /mkdirp/0.5.1: + dependencies: + minimist: 0.0.8 + dev: false + hasBin: true + resolution: + integrity: sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM= + /morgan/1.9.1: + dependencies: + basic-auth: 2.0.1 + debug: 2.6.9 + depd: 1.1.2 + on-finished: 2.3.0 + on-headers: 1.0.2 + dev: false + engines: + node: '>= 0.8.0' + resolution: + integrity: sha512-HQStPIV4y3afTiCYVxirakhlCfGkI161c76kKFca7Fk1JusM//Qeo1ej2XaMniiNeaZklMVrh3vTtIzpzwbpmA== + /ms/2.0.0: + dev: false + resolution: + integrity: sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g= + /negotiator/0.6.2: + dev: false + engines: + node: '>= 0.6' + resolution: + integrity: sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw== + /on-finished/2.3.0: + dependencies: + ee-first: 1.1.1 + dev: false + engines: + node: '>= 0.8' + resolution: + integrity: sha1-IPEzZIGwg811M3mSoWlxqi2QaUc= + /on-headers/1.0.2: + dev: false + engines: + node: '>= 0.8' + resolution: + integrity: sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA== + /optimist/0.3.7: + dependencies: + wordwrap: 0.0.3 + dev: false + resolution: + integrity: sha1-yQlBrVnkJzMokjB00s8ufLxuwNk= + /parseurl/1.3.3: + dev: false + engines: + node: '>= 0.8' + resolution: + integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ== + /path-to-regexp/0.1.7: + dev: false + resolution: + integrity: sha1-32BBeABfUi8V60SQ5yR6G/qmf4w= + /promise/2.0.0: + dependencies: + is-promise: 1.0.1 + dev: false + resolution: + integrity: sha1-RmSKqdYFr10ucMMCS/WUNtoCuA4= + /promise/6.1.0: + dependencies: + asap: 1.0.0 + dev: false + resolution: + integrity: sha1-LOcp9rlLRcJoka0GAsXJDgTG7vY= + /proxy-addr/2.0.5: + dependencies: + forwarded: 0.1.2 + ipaddr.js: 1.9.0 + dev: false + engines: + node: '>= 0.10' + resolution: + integrity: sha512-t/7RxHXPH6cJtP0pRG6smSr9QJidhB+3kXu0KgXnbGYMgzEnUxRQ4/LDdfOwZEMyIh3/xHb8PX3t+lfL9z+YVQ== + /qs/6.5.2: + dev: false + engines: + node: '>=0.6' + resolution: + integrity: sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA== + /range-parser/1.2.1: + dev: false + engines: + node: '>= 0.6' + resolution: + integrity: sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg== + /raw-body/2.3.3: + dependencies: + bytes: 3.0.0 + http-errors: 1.6.3 + iconv-lite: 0.4.23 + unpipe: 1.0.0 + dev: false + engines: + node: '>= 0.8' + resolution: + integrity: sha512-9esiElv1BrZoI3rCDuOuKCBRbuApGGaDPQfjSflGxdy4oyzqghxu6klEkkVIvBje+FF0BX9coEv8KqW6X/7njw== + /repeat-string/1.6.1: + dev: false + engines: + node: '>=0.10' + resolution: + integrity: sha1-jcrkcOHIirwtYA//Sndihtp15jc= + /right-align/0.1.3: + dependencies: + align-text: 0.1.4 + dev: false + engines: + node: '>=0.10.0' + resolution: + integrity: sha1-YTObci/mo1FWiSENJOFMlhSGE+8= + /safe-buffer/5.1.2: + dev: false + resolution: + integrity: sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== + /safer-buffer/2.1.2: + dev: false + resolution: + integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== + /send/0.16.2: + dependencies: + debug: 2.6.9 + depd: 1.1.2 + destroy: 1.0.4 + encodeurl: 1.0.2 + escape-html: 1.0.3 + etag: 1.8.1 + fresh: 0.5.2 + http-errors: 1.6.3 + mime: 1.4.1 + ms: 2.0.0 + on-finished: 2.3.0 + range-parser: 1.2.1 + statuses: 1.4.0 + dev: false + engines: + node: '>= 0.8.0' + resolution: + integrity: sha512-E64YFPUssFHEFBvpbbjr44NCLtI1AohxQ8ZSiJjQLskAdKuriYEP6VyGEsRDH8ScozGpkaX1BGvhanqCwkcEZw== + /serve-static/1.13.2: + dependencies: + encodeurl: 1.0.2 + escape-html: 1.0.3 + parseurl: 1.3.3 + send: 0.16.2 + dev: false + engines: + node: '>= 0.8.0' + resolution: + integrity: sha512-p/tdJrO4U387R9oMjb1oj7qSMaMfmOyd4j9hOFoxZe2baQszgHcSWjuya/CiT5kgZZKRudHNOA0pYXOl8rQ5nw== + /setprototypeof/1.1.0: + dev: false + resolution: + integrity: sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ== + /source-map/0.1.43: + dependencies: + amdefine: 1.0.1 + dev: false + engines: + node: '>=0.8.0' + resolution: + integrity: sha1-wkvBRspRfBRx9drL4lcbK3+eM0Y= + /source-map/0.4.4: + dependencies: + amdefine: 1.0.1 + dev: false + engines: + node: '>=0.8.0' + resolution: + integrity: sha1-66T12pwNyZneaAMti092FzZSA2s= + /source-map/0.5.7: + dev: false + engines: + node: '>=0.10.0' + resolution: + integrity: sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w= + /statuses/1.4.0: + dev: false + engines: + node: '>= 0.6' + resolution: + integrity: sha512-zhSCtt8v2NDrRlPQpCNtw/heZLtfUDqxBM1udqikb/Hbk52LK4nQSwr10u77iopCW5LsyHpuXS0GnEc48mLeew== + /statuses/1.5.0: + dev: false + engines: + node: '>= 0.6' + resolution: + integrity: sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow= + /transformers/2.1.0: + dependencies: + css: 1.0.8 + promise: 2.0.0 + uglify-js: 2.2.5 + deprecated: 'Deprecated, use jstransformer' + dev: false + resolution: + integrity: sha1-XSPLNVYd2F3Gf7hIIwm0fVPM6ac= + /type-is/1.6.18: + dependencies: + media-typer: 0.3.0 + mime-types: 2.1.24 + dev: false + engines: + node: '>= 0.6' + resolution: + integrity: sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g== + /uglify-js/2.2.5: + dependencies: + optimist: 0.3.7 + source-map: 0.1.43 + dev: false + engines: + node: '>=0.4.0' + hasBin: true + resolution: + integrity: sha1-puAqcNg5eSuXgEiLe4sYTAlcmcc= + /uglify-js/2.8.29: + dependencies: + source-map: 0.5.7 + yargs: 3.10.0 + dev: false + engines: + node: '>=0.8.0' + hasBin: true + optionalDependencies: + uglify-to-browserify: 1.0.2 + resolution: + integrity: sha1-KcVzMUgFe7Th913zW3qcty5qWd0= + /uglify-to-browserify/1.0.2: + dev: false + optional: true + resolution: + integrity: sha1-bgkk1r2mta/jSeOabWMoUKD4grc= + /unpipe/1.0.0: + dev: false + engines: + node: '>= 0.8' + resolution: + integrity: sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw= + /utils-merge/1.0.1: + dev: false + engines: + node: '>= 0.4.0' + resolution: + integrity: sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM= + /vary/1.1.2: + dev: false + engines: + node: '>= 0.8' + resolution: + integrity: sha1-IpnwLG3tMNSllhsLn3RSShj2NPw= + /void-elements/2.0.1: + dev: false + engines: + node: '>=0.10.0' + resolution: + integrity: sha1-wGavtYK7HLQSjWDqkjkulNXp2+w= + /window-size/0.1.0: + dev: false + engines: + node: '>= 0.8.0' + resolution: + integrity: sha1-VDjNLqk7IC76Ohn+iIeu58lPnJ0= + /with/4.0.3: + dependencies: + acorn: 1.2.2 + acorn-globals: 1.0.9 + dev: false + resolution: + integrity: sha1-7v0VTp550sjTQXtkeo8U2f7M4U4= + /wordwrap/0.0.2: + dev: false + engines: + node: '>=0.4.0' + resolution: + integrity: sha1-t5Zpu0LstAn4PVg8rVLKF+qhZD8= + /wordwrap/0.0.3: + dev: false + engines: + node: '>=0.4.0' + resolution: + integrity: sha1-o9XabNXAvAAI03I0u68b7WMFkQc= + /yargs/3.10.0: + dependencies: + camelcase: 1.2.1 + cliui: 2.1.0 + decamelize: 1.2.0 + window-size: 0.1.0 + dev: false + resolution: + integrity: sha1-9+572FfdfB0tOMDnTvvWgdFDH9E= +registry: 'https://registry.npmjs.org/' +shrinkwrapMinorVersion: 9 +shrinkwrapVersion: 3 +specifiers: + cookie-parser: ~1.4.4 + debug: ~2.6.9 + express: ~4.16.1 + http-errors: ~1.6.3 + jade: ~1.11.0 + morgan: ~1.9.1