summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--week-7/Final3/final3-validate.js1
-rw-r--r--week-7/Final3/package.json12
-rw-r--r--week-7/Final4/blog/README.md14
-rw-r--r--week-7/Final4/blog/app.js27
-rw-r--r--week-7/Final4/blog/package.json22
-rw-r--r--week-7/Final4/blog/posts.js133
-rw-r--r--week-7/Final4/blog/routes/content.js214
-rw-r--r--week-7/Final4/blog/routes/error.js9
-rw-r--r--week-7/Final4/blog/routes/index.js47
-rw-r--r--week-7/Final4/blog/routes/session.js170
-rw-r--r--week-7/Final4/blog/sessions.js65
-rw-r--r--week-7/Final4/blog/users.js75
-rw-r--r--week-7/Final4/blog/views/blog_template.html40
-rw-r--r--week-7/Final4/blog/views/entry_template.html58
-rw-r--r--week-7/Final4/blog/views/error_template.html12
-rw-r--r--week-7/Final4/blog/views/login.html47
-rw-r--r--week-7/Final4/blog/views/newpost_template.html29
-rw-r--r--week-7/Final4/blog/views/signup.html75
-rw-r--r--week-7/Final4/blog/views/welcome.html28
-rw-r--r--week-7/Final4/validate/final4-validate.js1
-rw-r--r--week-7/Final4/validate/package.json13
21 files changed, 1092 insertions, 0 deletions
diff --git a/week-7/Final3/final3-validate.js b/week-7/Final3/final3-validate.js
new file mode 100644
index 0000000..d7945ed
--- /dev/null
+++ b/week-7/Final3/final3-validate.js
@@ -0,0 +1 @@
+var _0x7ca2=["\x4D\x6F\x6E\x67\x6F\x43\x6C\x69\x65\x6E\x74","\x6D\x6F\x6E\x67\x6F\x64\x62","\x63\x72\x79\x70\x74\x6F","\x63\x6F\x6D\x6D\x61\x6E\x64\x65\x72","\x49\x66\x20\x79\x6F\x75\x20\x61\x72\x65\x20\x6C\x6F\x6F\x6B\x69\x6E\x67\x20\x61\x74\x20\x74\x68\x69\x73\x20\x74\x68\x65\x6E\x20\x53\x48\x41\x4D\x45\x20\x4F\x4E\x20\x59\x4F\x55","\x61\x72\x67\x76","\x70\x61\x72\x73\x65","\x2D\x70\x2C\x20\x2D\x2D\x70\x6F\x72\x74\x20\x5B\x70\x6F\x72\x74\x5D","\x4D\x6F\x6E\x67\x6F\x44\x42\x20\x70\x6F\x72\x74\x20\x74\x6F\x20\x63\x6F\x6E\x6E\x65\x63\x74\x20\x74\x6F\x2E\x20\x20\x44\x65\x66\x61\x75\x6C\x74\x20\x69\x73\x20\x27\x32\x37\x30\x31\x37\x27","\x6F\x70\x74\x69\x6F\x6E","\x2D\x68\x2C\x20\x2D\x2D\x68\x6F\x73\x74\x20\x5B\x68\x6F\x73\x74\x5D","\x4D\x6F\x6E\x67\x6F\x44\x42\x20\x68\x6F\x73\x74\x20\x74\x6F\x20\x63\x6F\x6E\x6E\x65\x63\x74\x20\x74\x6F\x2E\x20\x20\x44\x65\x66\x61\x75\x6C\x74\x20\x69\x73\x20\x27\x6C\x6F\x63\x61\x6C\x68\x6F\x73\x74\x27","\x6C\x6F\x63\x61\x6C\x68\x6F\x73\x74","\x2D\x63\x2C\x20\x2D\x2D\x63\x6F\x6C\x6C\x65\x63\x74\x69\x6F\x6E\x20\x5B\x63\x6F\x6C\x6C\x65\x63\x74\x69\x6F\x6E\x5D","\x43\x6F\x6C\x6C\x65\x63\x74\x69\x6F\x6E\x20\x63\x6F\x6E\x74\x61\x69\x6E\x69\x6E\x67\x20\x77\x65\x61\x74\x68\x65\x72\x20\x64\x61\x74\x61\x2E\x20\x20\x44\x65\x66\x61\x75\x6C\x74\x20\x69\x73\x20\x27\x6D\x65\x73\x73\x61\x67\x65\x73\x27","\x6D\x65\x73\x73\x61\x67\x65\x73","\x2D\x64\x2C\x20\x2D\x2D\x64\x62\x20\x5B\x64\x61\x74\x61\x62\x61\x73\x65\x5D","\x44\x61\x74\x61\x62\x61\x73\x65\x20\x63\x6F\x6E\x74\x61\x69\x6E\x69\x6E\x67\x20\x77\x65\x61\x74\x68\x65\x72\x20\x64\x61\x74\x61\x2E\x20\x20\x44\x65\x66\x61\x75\x6C\x74\x20\x69\x73\x20\x27\x65\x6E\x72\x6F\x6E\x27","\x65\x6E\x72\x6F\x6E","\x6D\x6F\x6E\x67\x6F\x64\x62\x3A\x2F\x2F","\x68\x6F\x73\x74","\x3A","\x70\x6F\x72\x74","\x2F","\x64\x62","\x63\x61\x6E\x27\x74\x20\x63\x6F\x6E\x6E\x65\x63\x74\x20\x74\x6F\x20\x4D\x6F\x6E\x67\x6F\x44\x42\x20\x75\x73\x69\x6E\x67\x3A\x20\x6D\x6F\x6E\x67\x6F\x64\x62\x3A\x2F\x2F","\x2E\x20\x20\x49\x73\x20\x69\x74\x20\x72\x75\x6E\x6E\x69\x6E\x67\x3F","\x6C\x6F\x67","\x63\x6F\x6C\x6C\x65\x63\x74\x69\x6F\x6E","\x6D\x72\x70\x6F\x74\x61\x74\x6F\x68\x65\x61\x64\x40\x6D\x6F\x6E\x67\x6F\x64\x62\x2E\x63\x6F\x6D","\x3C\x38\x31\x34\x37\x33\x30\x38\x2E\x31\x30\x37\x35\x38\x35\x31\x30\x34\x32\x33\x33\x35\x2E\x4A\x61\x76\x61\x4D\x61\x69\x6C\x2E\x65\x76\x61\x6E\x73\x40\x74\x68\x79\x6D\x65\x3E","\x46\x61\x69\x6C\x65\x64\x20\x74\x6F\x20\x71\x75\x65\x72\x79\x20\x4D\x6F\x6E\x67\x6F\x44\x42\x2E\x20\x20\x49\x73\x20\x69\x74\x20\x72\x75\x6E\x6E\x69\x6E\x67\x3F","\x53\x6F\x72\x72\x79\x2C\x20\x62\x75\x74\x20\x49\x20\x63\x6F\x75\x6C\x64\x20\x6E\x6F\x74\x20\x66\x69\x6E\x64\x20","\x20\x69\x6E\x20\x74\x68\x65\x20\x68\x65\x61\x64\x65\x72\x73\x2E\x54\x6F\x20\x6C\x69\x73\x74\x73","\x53\x6F\x72\x72\x79\x2C\x20\x62\x75\x74\x20\x49\x20\x66\x6F\x75\x6E\x64\x20","\x20\x69\x6E\x20\x6D\x75\x6C\x74\x69\x70\x6C\x65\x20\x64\x6F\x63\x75\x6D\x65\x6E\x74\x73\x2E\x20\x59\x6F\x75\x20\x73\x68\x6F\x75\x6C\x64\x20\x70\x72\x6F\x62\x61\x62\x6C\x79\x20\x72\x65\x2D\x69\x6D\x70\x6F\x72\x74\x20\x74\x68\x65\x20\x64\x61\x74\x61\x73\x65\x74\x2E","\x75\x6E\x64\x65\x66\x69\x6E\x65\x64","\x49\x20\x74\x68\x6F\x75\x67\x68\x74\x20\x74\x68\x65\x72\x65\x20\x77\x61\x73\x20\x61\x20\x64\x6F\x63\x2C\x20\x61\x6E\x64\x20\x74\x68\x65\x6E\x20\x74\x68\x65\x72\x65\x20\x77\x61\x73\x20\x6E\x6F\x6E\x65\x2E\x20\x41\x72\x65\x20\x79\x6F\x75\x20\x6D\x6F\x64\x69\x66\x69\x6E\x67\x20\x74\x68\x65\x20\x64\x61\x74\x61\x73\x65\x74\x20\x77\x68\x69\x6C\x65\x20\x49\x20\x61\x6D\x20\x72\x75\x6E\x6E\x69\x6E\x67\x3F","\x68\x65\x61\x64\x65\x72\x73","\x4D\x65\x73\x73\x61\x67\x65\x2D\x49\x44","\x44\x6F\x63\x75\x6D\x65\x6E\x74\x20\x73\x74\x72\x75\x63\x74\x75\x72\x65\x20\x69\x73\x20\x6E\x6F\x74\x20\x63\x6F\x72\x72\x65\x63\x74\x2E\x20\x20\x43\x61\x6E\x6E\x6F\x74\x20\x66\x69\x6E\x64\x20\x22\x68\x65\x61\x64\x65\x72\x73\x2E\x4D\x65\x73\x73\x61\x67\x65\x2D\x49\x44\x22\x20\x66\x69\x65\x6C\x64\x2E","\x59\x6F\x75\x20\x73\x68\x6F\x75\x6C\x64\x20\x70\x72\x6F\x62\x61\x62\x6C\x79\x20\x72\x65\x2D\x69\x6D\x70\x6F\x72\x74\x20\x74\x68\x65\x20\x64\x61\x74\x61\x73\x65\x74\x2E","\x64\x69\x72","\x46\x6F\x75\x6E\x64\x20\x61\x20\x64\x6F\x63\x75\x6D\x65\x6E\x74\x20\x77\x69\x74\x68\x20","\x20\x62\x75\x74\x20\x74\x68\x65\x20\x4D\x65\x73\x73\x61\x67\x65\x2D\x49\x44\x20\x69\x73\x20\x6E\x6F\x74\x20\x63\x6F\x72\x72\x65\x63\x74\x2E","\x4D\x65\x73\x73\x61\x67\x65\x2D\x49\x44\x20\x69\x73\x20","\x66\x69\x6E\x64\x4F\x6E\x65","\x63\x6F\x75\x6E\x74","\x66\x69\x6E\x64","\x57\x65\x6C\x63\x6F\x6D\x65\x20\x74\x6F\x20\x74\x68\x65\x20\x46\x69\x6E\x61\x6C\x20\x45\x78\x61\x6D\x20\x51\x33\x20\x43\x68\x65\x63\x6B\x65\x72\x2E\x20\x4D\x79\x20\x6A\x6F\x62\x20\x69\x73\x20\x74\x6F\x20\x6D\x61\x6B\x65\x20\x73\x75\x72\x65\x20\x79\x6F\x75\x20\x63\x6F\x72\x72\x65\x63\x74\x6C\x79\x20\x75\x70\x64\x61\x74\x65\x64\x20\x74\x68\x65\x20\x64\x6F\x63\x75\x6D\x65\x6E\x74","\x46\x61\x69\x6C\x65\x64\x20\x56\x61\x6C\x69\x64\x61\x74\x69\x6F\x6E\x3A\x20\x54\x68\x65\x20\x64\x6F\x63\x75\x6D\x65\x6E\x74\x20\x77\x61\x73\x20\x6E\x6F\x74\x20\x75\x70\x64\x61\x74\x65\x64\x20\x63\x6F\x72\x72\x65\x63\x74\x6C\x79","\x63\x6C\x6F\x73\x65","\x46\x69\x6E\x61\x6C\x20\x45\x78\x61\x6D\x20\x51\x33\x20\x56\x61\x6C\x69\x64\x61\x74\x65\x64\x20\x73\x75\x63\x63\x65\x73\x73\x66\x75\x6C\x6C\x79\x21","\x61\x65\x73\x32\x35\x36","\x63\x72\x65\x61\x74\x65\x44\x65\x63\x69\x70\x68\x65\x72","\x68\x65\x78","\x75\x74\x66\x38","\x75\x70\x64\x61\x74\x65","\x66\x69\x6E\x61\x6C","\x59\x6F\x75\x72\x20\x76\x61\x6C\x69\x64\x61\x74\x69\x6F\x6E\x20\x63\x6F\x64\x65\x20\x69\x73\x3A\x20","\x37\x35\x39\x31\x31\x33\x30\x38\x33\x33\x63\x30\x34\x30\x62\x39\x34\x32\x38\x30\x30\x33\x35\x30\x36\x32\x37\x62\x65\x32\x31\x34\x39\x37\x33\x62\x66\x63\x31\x33\x32\x35\x39\x39\x64\x37\x33\x62\x61\x66\x65\x63\x31\x33\x37\x65\x30\x34\x39\x63\x35\x33\x34\x39","\x37\x34\x37\x42\x65\x6F\x54\x53\x63\x47\x4C\x64\x4C\x53\x56\x34\x46\x64\x76\x54","\x63\x6F\x6E\x6E\x65\x63\x74"];var MongoClient=require(_0x7ca2[1])[_0x7ca2[0]];var crypto=require(_0x7ca2[2]);var program=require(_0x7ca2[3]);var reprimand=_0x7ca2[4];program[_0x7ca2[9]](_0x7ca2[16],_0x7ca2[17],_0x7ca2[18])[_0x7ca2[9]](_0x7ca2[13],_0x7ca2[14],_0x7ca2[15])[_0x7ca2[9]](_0x7ca2[10],_0x7ca2[11],_0x7ca2[12])[_0x7ca2[9]](_0x7ca2[7],_0x7ca2[8],27017)[_0x7ca2[6]](process[_0x7ca2[5]]);MongoClient[_0x7ca2[62]](_0x7ca2[19]+program[_0x7ca2[20]]+_0x7ca2[21]+program[_0x7ca2[22]]+_0x7ca2[23]+program[_0x7ca2[24]],function (_0x8755x5,_0x8755x6){if(_0x8755x5){console[_0x7ca2[27]](_0x7ca2[25]+program[_0x7ca2[20]]+_0x7ca2[21]+program[_0x7ca2[22]]+_0x7ca2[23]+program[_0x7ca2[24]]+_0x7ca2[26]);throw _0x8755x5;} ;var _0x8755x7=_0x8755x6[_0x7ca2[28]](program[_0x7ca2[28]]);function _0x8755x8(_0x8755x9){var _0x8755xa=_0x7ca2[29];var _0x8755xb=_0x7ca2[30];_0x8755x7[_0x7ca2[48]]({"\x68\x65\x61\x64\x65\x72\x73\x2E\x54\x6F":_0x8755xa})[_0x7ca2[47]](function (_0x8755x5,_0x8755xc){if(_0x8755x5){console[_0x7ca2[27]](_0x7ca2[31]);throw _0x8755x5;} ;if(_0x8755xc<1){console[_0x7ca2[27]](_0x7ca2[32]+_0x8755xa+_0x7ca2[33]);return _0x8755x9(false);} ;if(_0x8755xc>1){console[_0x7ca2[27]](_0x7ca2[34]+_0x8755xa+_0x7ca2[35]);return _0x8755x9(false);} ;_0x8755x7[_0x7ca2[46]]({"\x68\x65\x61\x64\x65\x72\x73\x2E\x54\x6F":_0x8755xa},function (_0x8755x5,_0x8755xd){if(_0x8755x5){console[_0x7ca2[27]](_0x7ca2[31]);throw _0x8755x5;} ;if(!_0x8755xd||_0x8755xd===null|| typeof _0x8755xd===_0x7ca2[36]){console[_0x7ca2[27]](_0x7ca2[37]);return _0x8755x9(false);} ;if( typeof _0x8755xd[_0x7ca2[38]]===_0x7ca2[36]|| typeof _0x8755xd[_0x7ca2[38]][_0x7ca2[39]]===_0x7ca2[36]){console[_0x7ca2[27]](_0x7ca2[40]);console[_0x7ca2[27]](_0x7ca2[41]);console[_0x7ca2[42]](_0x8755xd);return _0x8755x9(false);} ;if(_0x8755xd[_0x7ca2[38]][_0x7ca2[39]]!=_0x8755xb){console[_0x7ca2[27]](_0x7ca2[43]+_0x8755xa+_0x7ca2[44]);console[_0x7ca2[27]](_0x7ca2[45],_0x8755xd[_0x7ca2[38]][_0x7ca2[39]]);return _0x8755x9(false);} ;return _0x8755x9(true);} );} );} ;console[_0x7ca2[27]](_0x7ca2[49]);_0x8755x8(function (_0x8755xe){if(!_0x8755xe){console[_0x7ca2[27]](_0x7ca2[50]);return _0x8755x6[_0x7ca2[51]]();} ;console[_0x7ca2[27]](_0x7ca2[52]);function _0x8755xf(_0x8755x10,_0x8755x11){var _0x8755x12=_0x7ca2[53];var _0x8755x13=crypto[_0x7ca2[54]](_0x8755x12,_0x8755x11);var _0x8755x14=_0x8755x13[_0x7ca2[57]](_0x8755x10,_0x7ca2[55],_0x7ca2[56])+_0x8755x13[_0x7ca2[58]](_0x7ca2[56]);return _0x8755x14;} ;console[_0x7ca2[27]](_0x7ca2[59]+_0x8755xf(_0x7ca2[60],_0x7ca2[61]));_0x8755x6[_0x7ca2[51]]();} );} );
diff --git a/week-7/Final3/package.json b/week-7/Final3/package.json
new file mode 100644
index 0000000..29f86b5
--- /dev/null
+++ b/week-7/Final3/package.json
@@ -0,0 +1,12 @@
+{
+ "name": "question-3",
+ "version": "0.0.0",
+ "description": "final exam question 3 validator",
+ "main": "final3-validate.js",
+ "dependencies": {
+ "commander": "~2.0.0",
+ "mongodb": "~1.3.19"
+ },
+ "license": "BSD",
+ "private": true
+}
diff --git a/week-7/Final4/blog/README.md b/week-7/Final4/blog/README.md
new file mode 100644
index 0000000..863910e
--- /dev/null
+++ b/week-7/Final4/blog/README.md
@@ -0,0 +1,14 @@
+Blog project for M101JS
+
+./app.js - entry point
+./package.json - npm package description
+./routes/ - Application routes
+./posts.js - Posts Data Access Helper
+./sessions.js - Sessions Data Access Helper
+./users.js - Users Data Access Helper
+./views/ - html templates
+
+Getting started
+
+npm install
+node app.js
diff --git a/week-7/Final4/blog/app.js b/week-7/Final4/blog/app.js
new file mode 100644
index 0000000..f773876
--- /dev/null
+++ b/week-7/Final4/blog/app.js
@@ -0,0 +1,27 @@
+var express = require('express')
+ , app = express() // Web framework to handle routing requests
+ , cons = require('consolidate') // Templating library adapter for Express
+ , MongoClient = require('mongodb').MongoClient // Driver for connecting to MongoDB
+ , routes = require('./routes'); // Routes for our application
+
+MongoClient.connect('mongodb://localhost:27017/blog', function(err, db) {
+ "use strict";
+ if(err) throw err;
+
+ // Register our templating engine
+ app.engine('html', cons.swig);
+ app.set('view engine', 'html');
+ app.set('views', __dirname + '/views');
+
+ // Express middleware to populate 'req.cookies' so we can access cookies
+ app.use(express.cookieParser());
+
+ // Express middleware to populate 'req.body' so we can access POST variables
+ app.use(express.bodyParser());
+
+ // Application routes
+ routes(app, db);
+
+ app.listen(3000);
+ console.log('Express server listening on port 3000');
+});
diff --git a/week-7/Final4/blog/package.json b/week-7/Final4/blog/package.json
new file mode 100644
index 0000000..093690d
--- /dev/null
+++ b/week-7/Final4/blog/package.json
@@ -0,0 +1,22 @@
+{
+ "name": "blog",
+ "version": "0.0.0",
+ "description": "Blog Project for M101JS",
+ "main": "app.js",
+ "dependencies": {
+ "bcrypt-nodejs": "~0.0.3",
+ "consolidate": "~0.9.1",
+ "express": ">=3.x",
+ "mongodb": "~1.3.9",
+ "swig": "~0.14.0",
+ "validator": "~1.1.3"
+ },
+ "devDependencies": {},
+ "scripts": {
+ "test": "echo \"Error: no test specified\" && exit 1"
+ },
+ "repository": "",
+ "author": "Shaun Verch <shaun.verch@10gen.com>",
+ "license": "BSD",
+ "private": true
+}
diff --git a/week-7/Final4/blog/posts.js b/week-7/Final4/blog/posts.js
new file mode 100644
index 0000000..0769fcf
--- /dev/null
+++ b/week-7/Final4/blog/posts.js
@@ -0,0 +1,133 @@
+/* The PostsDAO must be constructed with a connected database object */
+function PostsDAO(db) {
+ "use strict";
+
+ /* If this constructor is called without the "new" operator, "this" points
+ * to the global object. Log a warning and call it correctly. */
+ if (false === (this instanceof PostsDAO)) {
+ console.log('Warning: PostsDAO constructor called without "new" operator');
+ return new PostsDAO(db);
+ }
+
+ var posts = db.collection("posts");
+
+ this.insertEntry = function (title, body, tags, author, callback) {
+ "use strict";
+ console.log("inserting blog entry" + title + body);
+
+ // fix up the permalink to not include whitespace
+ var permalink = title.replace( /\s/g, '_' );
+ permalink = permalink.replace( /\W/g, '' );
+
+ // Build a new post
+ var post = {"title": title,
+ "author": author,
+ "body": body,
+ "permalink":permalink,
+ "tags": tags,
+ "comments": [],
+ "date": new Date()}
+
+ // now insert the post
+ posts.insert(post, function (err, result) {
+ "use strict";
+
+ if (err) return callback(err, null);
+
+ console.log("Inserted new post");
+ callback(err, permalink);
+ });
+ }
+
+ this.getPosts = function(num, callback) {
+ "use strict";
+
+ posts.find().sort('date', -1).limit(num).toArray(function(err, items) {
+ "use strict";
+
+ if (err) return callback(err, null);
+
+ console.log("Found " + items.length + " posts");
+
+ callback(err, items);
+ });
+ }
+
+ this.getPostsByTag = function(tag, num, callback) {
+ "use strict";
+
+ posts.find({ tags : tag }).sort('date', -1).limit(num).toArray(function(err, items) {
+ "use strict";
+
+ if (err) return callback(err, null);
+
+ console.log("Found " + items.length + " posts");
+
+ callback(err, items);
+ });
+ }
+
+ this.getPostByPermalink = function(permalink, callback) {
+ "use strict";
+ posts.findOne({'permalink': permalink}, function(err, post) {
+ "use strict";
+
+ if (err) return callback(err, null);
+
+ // XXX: Look here for final exam to see where we store "num_likes"
+
+ // fix up likes values. set to zero if data is not present
+ if (typeof post.comments === 'undefined') {
+ post.comments = [];
+ }
+
+ // Each comment document in a post should have a "num_likes" entry, so we have to
+ // iterate all the comments in the post to make sure that is the case
+ for (var i = 0; i < post.comments.length; i++) {
+ if (typeof post.comments[i].num_likes === 'undefined') {
+ post.comments[i].num_likes = 0;
+ }
+ post.comments[i].comment_ordinal = i;
+ }
+ callback(err, post);
+ });
+ }
+
+ this.addComment = function(permalink, name, email, body, callback) {
+ "use strict";
+
+ var comment = {'author': name, 'body': body}
+
+ if (email != "") {
+ comment['email'] = email
+ }
+
+ posts.update({'permalink': permalink}, {'$push': {'comments': comment}}, function(err, numModified) {
+ "use strict";
+
+ if (err) return callback(err, null);
+
+ callback(err, numModified);
+ });
+ }
+
+ this.incrementLikes = function(permalink, comment_ordinal, callback) {
+ "use strict";
+
+ // The "comment_ordinal" argument specifies which comment in the post we are looking at
+ // Here is an example of how to build a selector with the 'comment_ordinal' variable
+ // We have to do it this way because a literal object with variables in field names such as:
+ // { 'comments.' + comment_ordinal + '.author' : 'Frank' } is illegal Javascript.
+ var selector_example = {};
+ var comment_ordinal_example = 0;
+ selector_example['comments.' + comment_ordinal_example + '.author'] = 'Frank';
+ // Now selector_example = { 'comments.0.author' : 'Frank' } which is a selector for the
+ // string 'Frank' in the 'author' field of the first element of the 'comments' array (which
+ // is zero indexed).
+
+ // TODO: Final exam question - Increment the number of likes
+ callback(Error("incrementLikes NYI"), null);
+ }
+}
+
+module.exports.PostsDAO = PostsDAO;
diff --git a/week-7/Final4/blog/routes/content.js b/week-7/Final4/blog/routes/content.js
new file mode 100644
index 0000000..6e7491c
--- /dev/null
+++ b/week-7/Final4/blog/routes/content.js
@@ -0,0 +1,214 @@
+var PostsDAO = require('../posts').PostsDAO
+ , sanitize = require('validator').sanitize; // Helper to sanitize form input
+
+/* The ContentHandler must be constructed with a connected db */
+function ContentHandler (db) {
+ "use strict";
+
+ var posts = new PostsDAO(db);
+
+ this.displayMainPage = function(req, res, next) {
+ "use strict";
+
+ posts.getPosts(10, function(err, results) {
+ "use strict";
+
+ if (err) return next(err);
+
+ return res.render('blog_template', {
+ title: 'blog homepage',
+ username: req.username,
+ myposts: results
+ });
+ });
+ }
+
+ this.displayMainPageByTag = function(req, res, next) {
+ "use strict";
+
+ var tag = req.params.tag;
+
+ posts.getPostsByTag(tag, 10, function(err, results) {
+ "use strict";
+
+ if (err) return next(err);
+
+ return res.render('blog_template', {
+ title: 'blog homepage',
+ username: req.username,
+ myposts: results
+ });
+ });
+ }
+
+ this.displayPostByPermalink = function(req, res, next) {
+ "use strict";
+
+ var permalink = req.params.permalink;
+
+ posts.getPostByPermalink(permalink, function(err, post) {
+ "use strict";
+
+ if (err) return next(err);
+
+ if (!post) return res.redirect("/post_not_found");
+
+ // init comment form fields for additional comment
+ var comment = {'name': req.username, 'body': "", 'email': ""}
+
+ return res.render('entry_template', {
+ title: 'blog post',
+ username: req.username,
+ post: post,
+ comment: comment,
+ errors: ""
+ });
+ });
+ }
+
+ this.handleNewComment = function(req, res, next) {
+ "use strict";
+ var name = req.body.commentName;
+ var email = req.body.commentEmail;
+ var body = req.body.commentBody;
+ var permalink = req.body.permalink;
+
+ // Override the comment with our actual user name if found
+ if (req.username) {
+ name = req.username;
+ }
+
+ if (!name || !body) {
+ // user did not fill in enough information
+
+ posts.getPostByPermalink(permalink, function(err, post) {
+ "use strict";
+
+ if (err) return next(err);
+
+ if (!post) return res.redirect("/post_not_found");
+
+ // init comment form fields for additional comment
+ var comment = {'name': name, 'body': "", 'email': ""}
+
+ var errors = "Post must contain your name and an actual comment."
+ return res.render('entry_template', {
+ title: 'blog post',
+ username: req.username,
+ post: post,
+ comment: comment,
+ errors: errors
+ });
+ });
+
+ return;
+ }
+
+ // even if there is no logged in user, we can still post a comment
+ posts.addComment(permalink, name, email, body, function(err, updated) {
+ "use strict";
+
+ if (err) return next(err);
+
+ if (updated == 0) return res.redirect("/post_not_found");
+
+ return res.redirect("/post/" + permalink);
+ });
+ }
+
+ this.displayPostNotFound = function(req, res, next) {
+ "use strict";
+ return res.send('Sorry, post not found', 404);
+ }
+
+ this.displayNewPostPage = function(req, res, next) {
+ "use strict";
+
+ if (!req.username) return res.redirect("/login");
+
+ return res.render('newpost_template', {
+ subject: "",
+ body: "",
+ errors: "",
+ tags: "",
+ username: req.username
+ });
+ }
+
+ function extract_tags(tags) {
+ "use strict";
+
+ var cleaned = [];
+
+ var tags_array = tags.split(',');
+
+ for (var i = 0; i < tags_array.length; i++) {
+ if ((cleaned.indexOf(tags_array[i]) == -1) && tags_array[i] != "") {
+ cleaned.push(tags_array[i].replace(/\s/g,''));
+ }
+ }
+
+ return cleaned
+ }
+
+ this.handleNewPost = function(req, res, next) {
+ "use strict";
+
+ var title = req.body.subject
+ var post = req.body.body
+ var tags = req.body.tags
+
+ if (!req.username) return res.redirect("/signup");
+
+ if (!title || !post) {
+ var errors = "Post must contain a title and blog entry";
+ return res.render("newpost_template", {subject:title, username:req.username, body:post, tags:tags, errors:errors});
+ }
+
+ var tags_array = extract_tags(tags)
+
+ // looks like a good entry, insert it escaped
+ var escaped_post = sanitize(post).escape();
+
+ // substitute some <br> for the paragraph breaks
+ var formatted_post = escaped_post.replace(/\r?\n/g,'<br>');
+
+ posts.insertEntry(title, formatted_post, tags_array, req.username, function(err, permalink) {
+ "use strict";
+
+ if (err) return next(err);
+
+ // now redirect to the blog permalink
+ return res.redirect("/post/" + permalink)
+ });
+ }
+
+ this.handleLike = function(req, res, next) {
+ "use strict";
+
+ var permalink = req.body.permalink;
+ permalink = sanitize(permalink).escape();
+
+ var comment_ordinal = req.body.comment_ordinal;
+
+ posts.getPostByPermalink(permalink, function(err, post) {
+ "use strict";
+
+ if (err) return next(err);
+
+ if (!post) return res.redirect("/post_not_found");
+
+ // it all looks good. increment the ordinal
+ posts.incrementLikes(permalink, comment_ordinal, function(err, post) {
+ "use strict";
+
+ if (err) return next(err);
+
+ // now redirect to the blog permalink
+ return res.redirect("/post/" + permalink)
+ });
+ });
+ }
+}
+
+module.exports = ContentHandler;
diff --git a/week-7/Final4/blog/routes/error.js b/week-7/Final4/blog/routes/error.js
new file mode 100644
index 0000000..f50f2b6
--- /dev/null
+++ b/week-7/Final4/blog/routes/error.js
@@ -0,0 +1,9 @@
+// Error handling middleware
+
+exports.errorHandler = function(err, req, res, next) {
+ "use strict";
+ console.error(err.message);
+ console.error(err.stack);
+ res.status(500);
+ res.render('error_template', { error: err });
+}
diff --git a/week-7/Final4/blog/routes/index.js b/week-7/Final4/blog/routes/index.js
new file mode 100644
index 0000000..4586e7b
--- /dev/null
+++ b/week-7/Final4/blog/routes/index.js
@@ -0,0 +1,47 @@
+var SessionHandler = require('./session')
+ , ContentHandler = require('./content')
+ , ErrorHandler = require('./error').errorHandler;
+
+module.exports = exports = function(app, db) {
+
+ var sessionHandler = new SessionHandler(db);
+ var contentHandler = new ContentHandler(db);
+
+ // Middleware to see if a user is logged in
+ app.use(sessionHandler.isLoggedInMiddleware);
+
+ // The main page of the blog
+ app.get('/', contentHandler.displayMainPage);
+
+ // The main page of the blog, filtered by tag
+ app.get('/tag/:tag', contentHandler.displayMainPageByTag);
+
+ // A single post, which can be commented on
+ app.get("/post/:permalink", contentHandler.displayPostByPermalink);
+ app.post('/newcomment', contentHandler.handleNewComment);
+ app.get("/post_not_found", contentHandler.displayPostNotFound);
+
+ // Displays the form allowing a user to add a new post. Only works for logged in users
+ app.get('/newpost', contentHandler.displayNewPostPage);
+ app.post('/newpost', contentHandler.handleNewPost);
+
+ // Used to process a like on a blog post
+ app.post('/like', contentHandler.handleLike);
+
+ // Login form
+ app.get('/login', sessionHandler.displayLoginPage);
+ app.post('/login', sessionHandler.handleLoginRequest);
+
+ // Logout page
+ app.get('/logout', sessionHandler.displayLogoutPage);
+
+ // Welcome page
+ app.get("/welcome", sessionHandler.displayWelcomePage);
+
+ // Signup form
+ app.get('/signup', sessionHandler.displaySignupPage);
+ app.post('/signup', sessionHandler.handleSignup);
+
+ // Error handling middleware
+ app.use(ErrorHandler);
+}
diff --git a/week-7/Final4/blog/routes/session.js b/week-7/Final4/blog/routes/session.js
new file mode 100644
index 0000000..73fe6d9
--- /dev/null
+++ b/week-7/Final4/blog/routes/session.js
@@ -0,0 +1,170 @@
+var UsersDAO = require('../users').UsersDAO
+ , SessionsDAO = require('../sessions').SessionsDAO;
+
+/* The SessionHandler must be constructed with a connected db */
+function SessionHandler (db) {
+ "use strict";
+
+ var users = new UsersDAO(db);
+ var sessions = new SessionsDAO(db);
+
+ this.isLoggedInMiddleware = function(req, res, next) {
+ var session_id = req.cookies.session;
+ sessions.getUsername(session_id, function(err, username) {
+ "use strict";
+
+ if (!err && username) {
+ req.username = username;
+ }
+ return next();
+ });
+ }
+
+ this.displayLoginPage = function(req, res, next) {
+ "use strict";
+ return res.render("login", {username:"", password:"", login_error:""})
+ }
+
+ this.handleLoginRequest = function(req, res, next) {
+ "use strict";
+
+ var username = req.body.username;
+ var password = req.body.password;
+
+ console.log("user submitted username: " + username + " pass: " + password);
+
+ users.validateLogin(username, password, function(err, user) {
+ "use strict";
+
+ if (err) {
+ if (err.no_such_user) {
+ return res.render("login", {username:username, password:"", login_error:"No such user"});
+ }
+ else if (err.invalid_password) {
+ return res.render("login", {username:username, password:"", login_error:"Invalid password"});
+ }
+ else {
+ // Some other kind of error
+ return next(err);
+ }
+ }
+
+ sessions.startSession(user['_id'], function(err, session_id) {
+ "use strict";
+
+ if (err) return next(err);
+
+ res.cookie('session', session_id);
+ return res.redirect('/welcome');
+ });
+ });
+ }
+
+ this.displayLogoutPage = function(req, res, next) {
+ "use strict";
+
+ var session_id = req.cookies.session;
+ sessions.endSession(session_id, function (err) {
+ "use strict";
+
+ // Even if the user wasn't logged in, redirect to home
+ res.cookie('session', '');
+ return res.redirect('/');
+ });
+ }
+
+ this.displaySignupPage = function(req, res, next) {
+ "use strict";
+ res.render("signup", {username:"", password:"",
+ password_error:"",
+ email:"", username_error:"", email_error:"",
+ verify_error :""});
+ }
+
+ function validateSignup(username, password, verify, email, errors) {
+ "use strict";
+ var USER_RE = /^[a-zA-Z0-9_-]{3,20}$/;
+ var PASS_RE = /^.{3,20}$/;
+ var EMAIL_RE = /^[\S]+@[\S]+\.[\S]+$/;
+
+ errors['username_error'] = "";
+ errors['password_error'] = "";
+ errors['verify_error'] = "";
+ errors['email_error'] = "";
+
+ if (!USER_RE.test(username)) {
+ errors['username_error'] = "invalid username. try just letters and numbers";
+ return false;
+ }
+ if (!PASS_RE.test(password)) {
+ errors['password_error'] = "invalid password.";
+ return false;
+ }
+ if (password != verify) {
+ errors['verify_error'] = "password must match";
+ return false;
+ }
+ if (email != "") {
+ if (!EMAIL_RE.test(email)) {
+ errors['email_error'] = "invalid email address";
+ return false;
+ }
+ }
+ return true;
+ }
+
+ this.handleSignup = function(req, res, next) {
+ "use strict";
+
+ var email = req.body.email
+ var username = req.body.username
+ var password = req.body.password
+ var verify = req.body.verify
+
+ // set these up in case we have an error case
+ var errors = {'username': username, 'email': email}
+ if (validateSignup(username, password, verify, email, errors)) {
+ users.addUser(username, password, email, function(err, user) {
+ "use strict";
+
+ if (err) {
+ // this was a duplicate
+ if (err.code == '11000') {
+ errors['username_error'] = "Username already in use. Please choose another";
+ return res.render("signup", errors);
+ }
+ // this was a different error
+ else {
+ return next(err);
+ }
+ }
+
+ sessions.startSession(user['_id'], function(err, session_id) {
+ "use strict";
+
+ if (err) return next(err);
+
+ res.cookie('session', session_id);
+ return res.redirect('/welcome');
+ });
+ });
+ }
+ else {
+ console.log("user did not validate");
+ return res.render("signup", errors);
+ }
+ }
+
+ this.displayWelcomePage = function(req, res, next) {
+ "use strict";
+
+ if (!req.username) {
+ console.log("welcome: can't identify user...redirecting to signup");
+ return res.redirect("/signup");
+ }
+
+ return res.render("welcome", {'username':req.username})
+ }
+}
+
+module.exports = SessionHandler;
diff --git a/week-7/Final4/blog/sessions.js b/week-7/Final4/blog/sessions.js
new file mode 100644
index 0000000..3af169f
--- /dev/null
+++ b/week-7/Final4/blog/sessions.js
@@ -0,0 +1,65 @@
+var crypto = require('crypto');
+
+/* The SessionsDAO must be constructed with a connected database object */
+function SessionsDAO(db) {
+ "use strict";
+
+ /* If this constructor is called without the "new" operator, "this" points
+ * to the global object. Log a warning and call it correctly. */
+ if (false === (this instanceof SessionsDAO)) {
+ console.log('Warning: SessionsDAO constructor called without "new" operator');
+ return new SessionsDAO(db);
+ }
+
+ var sessions = db.collection("sessions");
+
+ this.startSession = function(username, callback) {
+ "use strict";
+
+ // Generate session id
+ var current_date = (new Date()).valueOf().toString();
+ var random = Math.random().toString();
+ var session_id = crypto.createHash('sha1').update(current_date + random).digest('hex');
+
+ // Create session document
+ var session = {'username': username, '_id': session_id}
+
+ // Insert session document
+ sessions.insert(session, function (err, result) {
+ "use strict";
+ callback(err, session_id);
+ });
+ }
+
+ this.endSession = function(session_id, callback) {
+ "use strict";
+ // Remove session document
+ sessions.remove({ '_id' : session_id }, function (err, numRemoved) {
+ "use strict";
+ callback(err);
+ });
+ }
+ this.getUsername = function(session_id, callback) {
+ "use strict";
+
+ if (!session_id) {
+ callback(Error("Session not set"), null);
+ return;
+ }
+
+ sessions.findOne({ '_id' : session_id }, function(err, session) {
+ "use strict";
+
+ if (err) return callback(err, null);
+
+ if (!session) {
+ callback(new Error("Session: " + session + " does not exist"), null);
+ return;
+ }
+
+ callback(null, session.username);
+ });
+ }
+}
+
+module.exports.SessionsDAO = SessionsDAO;
diff --git a/week-7/Final4/blog/users.js b/week-7/Final4/blog/users.js
new file mode 100644
index 0000000..5960dbd
--- /dev/null
+++ b/week-7/Final4/blog/users.js
@@ -0,0 +1,75 @@
+var bcrypt = require('bcrypt-nodejs');
+
+/* The UsersDAO must be constructed with a connected database object */
+function UsersDAO(db) {
+ "use strict";
+
+ /* If this constructor is called without the "new" operator, "this" points
+ * to the global object. Log a warning and call it correctly. */
+ if (false === (this instanceof UsersDAO)) {
+ console.log('Warning: UsersDAO constructor called without "new" operator');
+ return new UsersDAO(db);
+ }
+
+ var users = db.collection("users");
+
+ this.addUser = function(username, password, email, callback) {
+ "use strict";
+
+ // Generate password hash
+ var salt = bcrypt.genSaltSync();
+ var password_hash = bcrypt.hashSync(password, salt);
+
+ // Create user document
+ var user = {'_id': username, 'password': password_hash};
+
+ // Add email if set
+ if (email != "") {
+ user['email'] = email;
+ }
+
+ users.insert(user, function (err, result) {
+ "use strict";
+
+ if (!err) {
+ console.log("Inserted new user");
+ return callback(null, result[0]);
+ }
+
+ return callback(err, null);
+ });
+ }
+
+ this.validateLogin = function(username, password, callback) {
+ "use strict";
+
+ // Callback to pass to MongoDB that validates a user document
+ function validateUserDoc(err, user) {
+ "use strict";
+
+ if (err) return callback(err, null);
+
+ if (user) {
+ if (bcrypt.compareSync(password, user.password)) {
+ callback(null, user);
+ }
+ else {
+ var invalid_password_error = new Error("Invalid password");
+ // Set an extra field so we can distinguish this from a db error
+ invalid_password_error.invalid_password = true;
+ callback(invalid_password_error, null);
+ }
+ }
+ else {
+ var no_such_user_error = new Error("User: " + user + " does not exist");
+ // Set an extra field so we can distinguish this from a db error
+ no_such_user_error.no_such_user = true;
+ callback(no_such_user_error, null);
+ }
+ }
+
+ users.findOne({ '_id' : username }, validateUserDoc);
+ }
+}
+
+module.exports.UsersDAO = UsersDAO;
diff --git a/week-7/Final4/blog/views/blog_template.html b/week-7/Final4/blog/views/blog_template.html
new file mode 100644
index 0000000..6af803d
--- /dev/null
+++ b/week-7/Final4/blog/views/blog_template.html
@@ -0,0 +1,40 @@
+<!DOCTYPE html>
+<html>
+<head>
+<title>My Blog</title>
+</head>
+<body>
+
+{% if username %}
+Welcome {{username}} <a href="/logout">Logout</a> | <a href="/newpost">New Post</a><p>
+{% else %}
+You are not logged in! <a href="/login">Login</a> | <a href="/signup">Sign Up</a><p>
+{% endif %}
+
+<h1>My Blog</h1>
+
+{% for post in myposts %}
+<h2><a href="/post/{{post['permalink']}}">{{post['title']}}</a></h2>
+Posted {{post['date']}} <i>By {{post['author']}}</i><br>
+Comments:
+<a href="/post/{{post['permalink']}}">{{post['comments']|length}}</a>
+<hr>
+{% autoescape false %}
+{{post['body']}}
+{% endautoescape %}
+<p>
+<p>
+<em>Filed Under</em>:
+{% for tag in post.tags %}
+ {% if loop.first %}
+ <a href="/tag/{{tag}}">{{tag}}</a>
+ {% else %}
+ , <a href="/tag/{{tag}}">{{tag}}</a>
+ {% endif %}
+{% endfor %}
+{% endfor %}
+<p>
+</body>
+</html>
+
+
diff --git a/week-7/Final4/blog/views/entry_template.html b/week-7/Final4/blog/views/entry_template.html
new file mode 100644
index 0000000..27a0789
--- /dev/null
+++ b/week-7/Final4/blog/views/entry_template.html
@@ -0,0 +1,58 @@
+<!doctype HTML>
+<html
+<head>
+<title>
+Blog Post
+</title>
+</head>
+<body>
+{% if username %}
+Welcome {{username}} <a href="/logout">Logout</a> | <a href="/newpost">New Post</a><p>
+{% else %}
+You are not logged in! <a href="/login">Login</a> | <a href="/signup">Sign Up</a><p>
+{% endif %}
+<a href="/">Blog Home</a><br><br>
+
+<h2>{{post['title']}}</h2>
+Posted {{post['date']}}<i> By {{post['author']}}</i><br>
+<hr>
+{% autoescape false %}
+{{post['body']}}
+{% endautoescape %}
+<p>
+<em>Filed Under</em>:
+{% for tag in post.tags %}
+ {% if loop.first %}
+ <a href="/tag/{{tag}}">{{tag}}</a>
+ {% else %}
+ , <a href="/tag/{{tag}}">{{tag}}</a>
+ {% endif %}
+{% endfor %}
+<p>
+Comments:
+<ul>
+{% for comment in post.comments %}
+<form action="/like" method="POST">
+<input type="hidden" name="permalink", value="{{post['permalink']}}">
+<input type="hidden" name="comment_ordinal", value="{{loop.index0}}">
+Author: {{comment['author']}}<br>
+Likes: {{comment['num_likes']}} <input type="submit" value="Like"></form><br>
+{{comment['body']}}<br>
+<hr>
+{% endfor %}
+<h3>Add a comment</h3>
+<form action="/newcomment" method="POST">
+<input type="hidden" name="permalink", value="{{post['permalink']}}">
+<h4>{{errors}}</h4>
+<b>Name</b> (required)<br>
+<input type="text" name="commentName" size="60" value="{{comment['name']}}"><br>
+<b>Email</b> (optional)<br>
+<input type="text" name="commentEmail" size="60" value="{{comment['email']}}"><br>
+<b>Comment</b><br>
+<textarea name="commentBody" cols="60" rows="10">{{comment['body']}}</textarea><br>
+<input type="submit" value="Submit">
+</ul>
+</body>
+</html>
+
+
diff --git a/week-7/Final4/blog/views/error_template.html b/week-7/Final4/blog/views/error_template.html
new file mode 100644
index 0000000..c37339f
--- /dev/null
+++ b/week-7/Final4/blog/views/error_template.html
@@ -0,0 +1,12 @@
+<!doctype HTML>
+<html>
+<head>
+<title>Internal Error</title>
+</head>
+<body>
+
+Oops..<br>
+{{error}}
+</body>
+</html>
+
diff --git a/week-7/Final4/blog/views/login.html b/week-7/Final4/blog/views/login.html
new file mode 100644
index 0000000..4041d5c
--- /dev/null
+++ b/week-7/Final4/blog/views/login.html
@@ -0,0 +1,47 @@
+<!DOCTYPE html>
+
+<html>
+ <head>
+ <title>Login</title>
+ <style type="text/css">
+ .label {text-align: right}
+ .error {color: red}
+ </style>
+
+ </head>
+
+ <body>
+ <h2>Login</h2>
+ <form method="post">
+ <table>
+ <tr>
+ <td class="label">
+ Username
+ </td>
+ <td>
+ <input type="text" name="username" value="{{username}}">
+ </td>
+ <td class="error">
+ </td>
+ </tr>
+
+ <tr>
+ <td class="label">
+ Password
+ </td>
+ <td>
+ <input type="password" name="password" value="">
+ </td>
+ <td class="error">
+ {{login_error}}
+
+ </td>
+ </tr>
+
+ </table>
+
+ <input type="submit">
+ </form>
+ </body>
+
+</html>
diff --git a/week-7/Final4/blog/views/newpost_template.html b/week-7/Final4/blog/views/newpost_template.html
new file mode 100644
index 0000000..bd67f9c
--- /dev/null
+++ b/week-7/Final4/blog/views/newpost_template.html
@@ -0,0 +1,29 @@
+<!doctype HTML>
+<html>
+<head>
+<title>Create a new post</title>
+</head>
+<body>
+
+{% if username %}
+Welcome {{username}} <a href="/logout">Logout</a><p>
+{% else %}
+You are not logged in! <a href="/login">Login</a> | <a href="/signup">Sign Up</a><p>
+{% endif %}
+<a href="/">Blog Home</a><br><br>
+
+<form action="/newpost" method="POST">
+{{errors}}
+<h2>Title</h2>
+<input type="text" name="subject" size="120" value="{{subject}}"><br>
+<h2>Blog Entry<h2>
+<textarea name="body" cols="120" rows="20">{{body}}</textarea><br>
+<h2>Tags</h2>
+Comma separated, please<br>
+<input type="text" name="tags" size="120" value="{{tags}}"><br>
+<p>
+<input type="submit" value="Submit">
+
+</body>
+</html>
+
diff --git a/week-7/Final4/blog/views/signup.html b/week-7/Final4/blog/views/signup.html
new file mode 100644
index 0000000..76bee7f
--- /dev/null
+++ b/week-7/Final4/blog/views/signup.html
@@ -0,0 +1,75 @@
+<!DOCTYPE html>
+
+<html>
+ <head>
+ <title>Sign Up</title>
+ <style type="text/css">
+ .label {text-align: right}
+ .error {color: red}
+ </style>
+
+ </head>
+
+ <body>
+ Already a user? <a href="/login">Login</a><p>
+ <h2>Signup</h2>
+ <form method="post">
+ <table>
+ <tr>
+ <td class="label">
+ Username
+ </td>
+ <td>
+ <input type="text" name="username" value="{{username}}">
+ </td>
+ <td class="error">
+ {{username_error}}
+
+ </td>
+ </tr>
+
+ <tr>
+ <td class="label">
+ Password
+ </td>
+ <td>
+ <input type="password" name="password" value="">
+ </td>
+ <td class="error">
+ {{password_error}}
+
+ </td>
+ </tr>
+
+ <tr>
+ <td class="label">
+ Verify Password
+ </td>
+ <td>
+ <input type="password" name="verify" value="">
+ </td>
+ <td class="error">
+ {{verify_error}}
+
+ </td>
+ </tr>
+
+ <tr>
+ <td class="label">
+ Email (optional)
+ </td>
+ <td>
+ <input type="text" name="email" value="{{email}}">
+ </td>
+ <td class="error">
+ {{email_error}}
+
+ </td>
+ </tr>
+ </table>
+
+ <input type="submit">
+ </form>
+ </body>
+
+</html>
diff --git a/week-7/Final4/blog/views/welcome.html b/week-7/Final4/blog/views/welcome.html
new file mode 100644
index 0000000..64f5929
--- /dev/null
+++ b/week-7/Final4/blog/views/welcome.html
@@ -0,0 +1,28 @@
+<!DOCTYPE html>
+
+<html>
+ <head>
+ <title>Welcome</title>
+ <style type="text/css">
+ .label {text-align: right}
+ .error {color: red}
+ </style>
+
+ </head>
+
+ <body>
+ Welcome {{username}}
+<p>
+<ul>
+<li><a href="/">Goto Blog Home</a></li>
+<li>
+<a href="/logout">Logout</a>
+</li>
+<li>
+<a href="/newpost">Create a New Post</a>
+</li>
+
+
+ </body>
+
+</html>
diff --git a/week-7/Final4/validate/final4-validate.js b/week-7/Final4/validate/final4-validate.js
new file mode 100644
index 0000000..2bef8ef
--- /dev/null
+++ b/week-7/Final4/validate/final4-validate.js
@@ -0,0 +1 @@
+var _0xc070=["\x72\x65\x71\x75\x65\x73\x74","\x62\x63\x72\x79\x70\x74\x2D\x6E\x6F\x64\x65\x6A\x73","\x63\x6F\x6D\x6D\x61\x6E\x64\x65\x72","\x63\x72\x79\x70\x74\x6F","\x49\x66\x20\x79\x6F\x75\x20\x61\x72\x65\x20\x6C\x6F\x6F\x6B\x69\x6E\x67\x20\x61\x74\x20\x74\x68\x69\x73\x20\x74\x68\x65\x6E\x20\x53\x48\x41\x4D\x45\x20\x4F\x4E\x20\x59\x4F\x55","\x61\x72\x67\x76","\x70\x61\x72\x73\x65","\x2D\x70\x2C\x20\x2D\x2D\x70\x6F\x72\x74\x20\x5B\x70\x6F\x72\x74\x5D","\x57\x65\x62\x73\x65\x72\x76\x65\x72\x20\x75\x72\x6C\x2E\x20\x20\x44\x65\x66\x61\x75\x6C\x74\x20\x69\x73\x20\x27\x33\x30\x30\x30\x27","\x6F\x70\x74\x69\x6F\x6E","\x2D\x68\x2C\x20\x2D\x2D\x68\x6F\x73\x74\x20\x5B\x68\x6F\x73\x74\x5D","\x57\x65\x62\x73\x65\x72\x76\x65\x72\x20\x68\x6F\x73\x74\x2E\x20\x20\x44\x65\x66\x61\x75\x6C\x74\x20\x69\x73\x20\x27\x6C\x6F\x63\x61\x6C\x68\x6F\x73\x74\x27","\x6C\x6F\x63\x61\x6C\x68\x6F\x73\x74","\x68\x74\x74\x70\x3A\x2F\x2F","\x68\x6F\x73\x74","\x3A","\x70\x6F\x72\x74","\x2F","\x54\x72\x79\x69\x6E\x67\x20\x74\x6F\x20\x66\x65\x74\x63\x68\x20\x62\x6C\x6F\x67\x20\x68\x6F\x6D\x65\x70\x61\x67\x65\x20\x66\x6F\x72\x20\x75\x72\x6C\x20","\x6C\x6F\x67","\x46\x61\x69\x6C\x65\x64\x20\x74\x6F\x20\x63\x6F\x6E\x6E\x65\x63\x74\x20\x74\x6F\x20\x62\x6C\x6F\x67\x20\x61\x74\x20\x27","\x27\x3A\x20","\x3C\x61\x20\x68\x72\x65\x66\x3D\x22\x28\x2F\x70\x6F\x73\x74\x2F\x5B\x5E\x22\x5D\x2B\x29\x22\x77\x2A\x3F\x3E","\x6D\x61\x74\x63\x68","\x48\x6D\x6D\x2C\x20\x63\x61\x6E\x27\x74\x20\x73\x65\x65\x6D\x20\x74\x6F\x20\x66\x69\x6E\x64\x20\x61\x20\x70\x6F\x73\x74\x2E\x20\x49\x73\x20\x74\x68\x65\x20\x62\x6C\x6F\x67\x20\x70\x6F\x70\x75\x6C\x61\x74\x65\x64\x20\x77\x69\x74\x68\x20\x70\x6F\x73\x74\x73\x3F","\x57\x68\x65\x6E\x20\x77\x65\x20\x74\x72\x69\x65\x64\x20\x74\x6F\x20\x72\x65\x61\x64\x20\x74\x68\x65\x20\x62\x6C\x6F\x67\x20\x69\x6E\x64\x65\x78\x20\x61\x74\x20","\x20\x68\x65\x72\x65\x20\x69\x73\x20\x77\x68\x61\x74\x20\x77\x65\x20\x67\x6F\x74\x3A","\x46\x61\x69\x6C\x65\x64\x20\x74\x6F\x20\x76\x61\x6C\x69\x64\x61\x74\x65\x20\x62\x6C\x6F\x67\x20\x6C\x69\x6B\x65\x73","\x67\x65\x74","\x54\x72\x79\x69\x6E\x67\x20\x74\x6F\x20\x67\x72\x61\x62\x20\x74\x68\x65\x20\x6E\x75\x6D\x62\x65\x72\x20\x6F\x66\x20\x6C\x69\x6B\x65\x73\x20\x66\x6F\x72\x20\x75\x72\x6C\x20","\x4C\x69\x6B\x65\x73\x3A\x20\x73\x2A\x28\x5B\x30\x2D\x39\x5D\x2B\x29\x73\x2A","\x43\x61\x6E\x27\x74\x20\x66\x65\x74\x63\x68\x20\x74\x68\x65\x20\x6C\x69\x6B\x65\x20\x76\x61\x6C\x75\x65\x20\x66\x6F\x72\x20\x74\x68\x65\x20\x66\x69\x72\x73\x74\x20\x63\x6F\x6D\x6D\x65\x6E\x74\x2E\x20\x50\x65\x72\x68\x61\x70\x73\x20\x74\x68\x65\x20\x62\x6C\x6F\x67\x20\x65\x6E\x74\x72\x79\x20\x68\x61\x73\x20\x6E\x6F\x20\x63\x6F\x6D\x6D\x65\x6E\x74\x73\x3F","\x57\x68\x65\x6E\x20\x77\x65\x20\x74\x72\x69\x65\x64\x20\x74\x6F\x20\x72\x65\x61\x64\x20\x74\x68\x65\x20\x62\x6C\x6F\x67\x20\x70\x65\x72\x6D\x61\x6C\x69\x6E\x6B\x20\x61\x74\x20","\x20\x68\x65\x72\x65\x20\x69\x73\x20\x77\x68\x61\x74\x20\x77\x65\x20\x67\x6F\x74","\x54\x72\x79\x69\x6E\x67\x20\x74\x6F\x20\x69\x6E\x63\x72\x65\x6D\x65\x6E\x74\x20\x74\x68\x65\x20\x6E\x75\x6D\x62\x65\x72\x20\x6F\x66\x20\x6C\x69\x6B\x65\x73\x20\x66\x6F\x72\x20\x70\x6F\x73\x74\x3A\x20","\x2F\x6C\x69\x6B\x65","\x5B\x5E\x2F\x5D\x2B\x2F\x28\x5B\x5E\x2F\x5D\x2B\x29","\x49\x6E\x74\x65\x72\x6E\x61\x6C\x20\x45\x72\x72\x6F\x72\x3A\x20\x49\x6E\x76\x61\x6C\x69\x64\x20\x70\x65\x72\x6D\x61\x6C\x69\x6E\x6B\x3A\x20","\x66\x6F\x72\x6D","\x70\x6F\x73\x74","\x42\x6C\x6F\x67\x20\x64\x69\x64\x20\x6E\x6F\x74\x20\x76\x61\x6C\x69\x64\x61\x74\x65\x20\x64\x75\x65\x20\x74\x6F\x20\x65\x72\x72\x6F\x72\x20\x66\x65\x74\x63\x68\x69\x6E\x67\x20\x62\x6C\x6F\x67\x20\x68\x6F\x6D\x65\x70\x61\x67\x65\x21","\x42\x6C\x6F\x67\x20\x64\x69\x64\x20\x6E\x6F\x74\x20\x76\x61\x6C\x69\x64\x61\x74\x65\x20\x64\x75\x65\x20\x74\x6F\x20\x65\x72\x72\x6F\x72\x20\x6C\x6F\x63\x61\x74\x69\x6E\x67\x20\x6C\x69\x6B\x65\x73\x21","\x42\x6C\x6F\x67\x20\x64\x69\x64\x20\x6E\x6F\x74\x20\x76\x61\x6C\x69\x64\x61\x74\x65\x20\x64\x75\x65\x20\x74\x6F\x20\x65\x72\x72\x6F\x72\x20\x63\x6C\x69\x63\x6B\x69\x6E\x67\x20\x6F\x6E\x20\x6C\x69\x6B\x65\x21","\x42\x6C\x6F\x67\x20\x64\x69\x64\x20\x6E\x6F\x74\x20\x76\x61\x6C\x69\x64\x61\x74\x65\x3A\x20\x6C\x69\x6B\x65\x73\x20\x6E\x6F\x74\x20\x69\x6E\x63\x72\x65\x6D\x65\x6E\x74\x65\x64\x20\x63\x6F\x72\x72\x65\x63\x74\x6C\x79\x2E","\x4F\x6C\x64\x20\x76\x61\x6C\x75\x65\x3A\x20","\x4E\x65\x77\x20\x76\x61\x6C\x75\x65\x3A\x20","\x53\x75\x63\x63\x65\x73\x73\x66\x75\x6C\x6C\x79\x20\x63\x6C\x69\x63\x6B\x65\x64\x20\x6C\x69\x6B\x65","\x42\x6C\x6F\x67\x20\x76\x61\x6C\x69\x64\x61\x74\x65\x64\x20\x73\x75\x63\x63\x65\x73\x73\x66\x75\x6C\x6C\x79\x21","\x61\x65\x73\x32\x35\x36","\x63\x72\x65\x61\x74\x65\x44\x65\x63\x69\x70\x68\x65\x72","\x68\x65\x78","\x75\x74\x66\x38","\x75\x70\x64\x61\x74\x65","\x66\x69\x6E\x61\x6C","\x59\x6F\x75\x72\x20\x76\x61\x6C\x69\x64\x61\x74\x69\x6F\x6E\x20\x63\x6F\x64\x65\x20\x69\x73\x3A\x20","\x37\x64\x65\x61\x33\x65\x33\x61\x32\x39\x31\x31\x66\x37\x66\x33\x37\x36\x32\x61\x63\x61\x64\x38\x38\x33\x61\x66\x66\x61\x64\x62\x39\x38\x33\x30\x38\x61\x30\x39\x36\x39\x63\x64\x31\x64\x36\x31\x32\x63\x34\x63\x64\x64\x33\x64\x65\x62\x35\x38\x66\x35\x63\x37","\x67\x6F\x78\x7A\x46\x62\x74\x30\x35\x63\x42\x49\x30\x72\x39\x6F\x4B\x47\x54\x59"];var request=require(_0xc070[0]);var bcrypt=require(_0xc070[1]);var program=require(_0xc070[2]);var crypto=require(_0xc070[3]);var reprimand=_0xc070[4];program[_0xc070[9]](_0xc070[10],_0xc070[11],_0xc070[12])[_0xc070[9]](_0xc070[7],_0xc070[8],3000)[_0xc070[6]](process[_0xc070[5]]);function fetchBlogHomePage(_0x1b31x7){var _0x1b31x8=_0xc070[13]+program[_0xc070[14]]+_0xc070[15]+program[_0xc070[16]]+_0xc070[17];console[_0xc070[19]](_0xc070[18]+_0x1b31x8);request[_0xc070[28]](_0x1b31x8,function (_0x1b31x9,_0x1b31xa,_0x1b31xb){if(_0x1b31x9){return _0x1b31x7(Error(_0xc070[20]+program[_0xc070[14]]+_0xc070[15]+program[_0xc070[16]]+_0xc070[21]+_0x1b31x9.toString()));} ;var _0x1b31xc=_0x1b31xb[_0xc070[23]](_0xc070[22]);if(_0x1b31xc===null){console[_0xc070[19]](_0xc070[24]);console[_0xc070[19]](_0xc070[25]+_0x1b31x8+_0xc070[26]);console[_0xc070[19]](_0x1b31xb);return _0x1b31x7(Error(_0xc070[27]));} ;return _0x1b31x7(null,_0x1b31xc[1]);} );} ;function fetchLikes(_0x1b31xe,_0x1b31x7){var _0x1b31x8=_0xc070[13]+program[_0xc070[14]]+_0xc070[15]+program[_0xc070[16]]+_0x1b31xe;console[_0xc070[19]](_0xc070[29]+_0x1b31x8);request[_0xc070[28]](_0x1b31x8,function (_0x1b31x9,_0x1b31xa,_0x1b31xb){if(_0x1b31x9){return _0x1b31x7(Error(_0xc070[20]+program[_0xc070[14]]+_0xc070[15]+program[_0xc070[16]]+_0xc070[21]+_0x1b31x9.toString()));} ;var _0x1b31xc=_0x1b31xb[_0xc070[23]](_0xc070[30]);if(_0x1b31xc===null){console[_0xc070[19]](_0xc070[31]);console[_0xc070[19]](_0xc070[32]+_0x1b31x8+_0xc070[33]);console[_0xc070[19]](_0x1b31xb);return _0x1b31x7(Error(_0xc070[27]));} ;return _0x1b31x7(null,_0x1b31xc[1]);} );} ;function clickOnLike(_0x1b31x10,_0x1b31x7){console[_0xc070[19]](_0xc070[34]+_0x1b31x10);var _0x1b31x8=_0xc070[13]+program[_0xc070[14]]+_0xc070[15]+program[_0xc070[16]]+_0xc070[35];var _0x1b31xc=_0x1b31x10[_0xc070[23]](_0xc070[36]);if(_0x1b31xc===null){console[_0xc070[19]](_0xc070[37]+_0x1b31x10);return _0x1b31x7(Error(_0xc070[27]));} ;request[_0xc070[39]](_0x1b31x8,function (_0x1b31x9,_0x1b31xa,_0x1b31xb){if(_0x1b31x9){return _0x1b31x7(Error(_0xc070[20]+program[_0xc070[14]]+_0xc070[15]+program[_0xc070[16]]+_0xc070[21]+_0x1b31x9.toString()));} ;return _0x1b31x7(null);} )[_0xc070[38]]({"\x70\x65\x72\x6D\x61\x6C\x69\x6E\x6B":_0x1b31xc[1],"\x63\x6F\x6D\x6D\x65\x6E\x74\x5F\x6F\x72\x64\x69\x6E\x61\x6C":0});} ;fetchBlogHomePage(function (_0x1b31x11,_0x1b31xe){if(_0x1b31x11){console[_0xc070[19]](_0xc070[40]);return ;} ;fetchLikes(_0x1b31xe,function (_0x1b31x11,_0x1b31x12){if(_0x1b31x11){console[_0xc070[19]](_0xc070[41]);return ;} ;clickOnLike(_0x1b31xe,function (_0x1b31x11){if(_0x1b31x11){console[_0xc070[19]](_0xc070[42]);return ;} ;fetchLikes(_0x1b31xe,function (_0x1b31x11,_0x1b31x13){if(_0x1b31x11){console[_0xc070[19]](_0xc070[41]);return ;} else {if((+_0x1b31x12+1)!=_0x1b31x13){console[_0xc070[19]](_0xc070[43]);console[_0xc070[19]](_0xc070[44]+_0x1b31x12);console[_0xc070[19]](_0xc070[45]+_0x1b31x13);return ;} else {console[_0xc070[19]](_0xc070[46]);console[_0xc070[19]](_0xc070[47]);function _0x1b31x14(_0x1b31x15,_0x1b31x16){var _0x1b31x17=_0xc070[48];var _0x1b31x18=crypto[_0xc070[49]](_0x1b31x17,_0x1b31x16);var _0x1b31x19=_0x1b31x18[_0xc070[52]](_0x1b31x15,_0xc070[50],_0xc070[51])+_0x1b31x18[_0xc070[53]](_0xc070[51]);return _0x1b31x19;} ;console[_0xc070[19]](_0xc070[54]+_0x1b31x14(_0xc070[55],_0xc070[56]));} ;} ;} );} );} );} );
diff --git a/week-7/Final4/validate/package.json b/week-7/Final4/validate/package.json
new file mode 100644
index 0000000..d8d8cbb
--- /dev/null
+++ b/week-7/Final4/validate/package.json
@@ -0,0 +1,13 @@
+{
+ "name": "validate",
+ "version": "0.0.0",
+ "description": "Final exam question 4 validation script",
+ "main": "final4-validate.js",
+ "dependencies": {
+ "bcrypt-nodejs": "~0.0.3",
+ "commander": "~2.0.0",
+ "request": "~2.27.0"
+ },
+ "license": "BSD",
+ "private": true
+}