summaryrefslogtreecommitdiff
path: root/week-3
diff options
context:
space:
mode:
authormo khan <mo@mokhan.ca>2015-01-24 20:21:36 -0700
committermo khan <mo@mokhan.ca>2015-01-24 20:21:36 -0700
commite3f19022ebae4ce898fc76c84180fc35611b4930 (patch)
tree37e0de995fe6b5928afec9c6b7f31f910608c537 /week-3
parent20a40444ddea212c4ee4d90902f2ed65b5bc8dc9 (diff)
add handouts.
Diffstat (limited to 'week-3')
-rw-r--r--week-3/hw3-2and3-3/blog/README.md13
-rw-r--r--week-3/hw3-2and3-3/blog/app.js27
-rw-r--r--week-3/hw3-2and3-3/blog/package.json22
-rw-r--r--week-3/hw3-2and3-3/blog/posts.js89
-rw-r--r--week-3/hw3-2and3-3/blog/routes/content.js187
-rw-r--r--week-3/hw3-2and3-3/blog/routes/error.js9
-rw-r--r--week-3/hw3-2and3-3/blog/routes/index.js44
-rw-r--r--week-3/hw3-2and3-3/blog/routes/session.js170
-rw-r--r--week-3/hw3-2and3-3/blog/sessions.js65
-rw-r--r--week-3/hw3-2and3-3/blog/users.js75
-rw-r--r--week-3/hw3-2and3-3/blog/views/blog_template.html40
-rw-r--r--week-3/hw3-2and3-3/blog/views/entry_template.html54
-rw-r--r--week-3/hw3-2and3-3/blog/views/error_template.html12
-rw-r--r--week-3/hw3-2and3-3/blog/views/login.html47
-rw-r--r--week-3/hw3-2and3-3/blog/views/newpost_template.html29
-rw-r--r--week-3/hw3-2and3-3/blog/views/signup.html75
-rw-r--r--week-3/hw3-2and3-3/blog/views/welcome.html28
-rw-r--r--week-3/hw3-2validate/hw3-2validate.js1
-rw-r--r--week-3/hw3-2validate/package.json20
19 files changed, 1007 insertions, 0 deletions
diff --git a/week-3/hw3-2and3-3/blog/README.md b/week-3/hw3-2and3-3/blog/README.md
new file mode 100644
index 0000000..fd885ed
--- /dev/null
+++ b/week-3/hw3-2and3-3/blog/README.md
@@ -0,0 +1,13 @@
+Blog project for M101JS
+
+./app.js - entry point
+./package.json - npm package description
+./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-3/hw3-2and3-3/blog/app.js b/week-3/hw3-2and3-3/blog/app.js
new file mode 100644
index 0000000..a4a95e5
--- /dev/null
+++ b/week-3/hw3-2and3-3/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(8082);
+ console.log('Express server listening on port 8082');
+});
diff --git a/week-3/hw3-2and3-3/blog/package.json b/week-3/hw3-2and3-3/blog/package.json
new file mode 100644
index 0000000..55fbc8f
--- /dev/null
+++ b/week-3/hw3-2and3-3/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.5",
+ "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-3/hw3-2and3-3/blog/posts.js b/week-3/hw3-2and3-3/blog/posts.js
new file mode 100644
index 0000000..28593ce
--- /dev/null
+++ b/week-3/hw3-2and3-3/blog/posts.js
@@ -0,0 +1,89 @@
+/* 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
+ // hw3.2 TODO
+ callback(Error("insertEntry NYI"), null);
+ }
+
+ 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);
+
+ callback(err, post);
+ });
+ }
+
+ this.addComment = function(permalink, name, email, body, callback) {
+ "use strict";
+
+ var comment = {'author': name, 'body': body}
+
+ if (email != "") {
+ comment['email'] = email
+ }
+
+ // hw3.3 TODO
+ callback(Error("addComment NYI"), null);
+ }
+}
+
+module.exports.PostsDAO = PostsDAO;
diff --git a/week-3/hw3-2and3-3/blog/routes/content.js b/week-3/hw3-2and3-3/blog/routes/content.js
new file mode 100644
index 0000000..abdada8
--- /dev/null
+++ b/week-3/hw3-2and3-3/blog/routes/content.js
@@ -0,0 +1,187 @@
+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)
+ });
+ }
+}
+
+module.exports = ContentHandler;
diff --git a/week-3/hw3-2and3-3/blog/routes/error.js b/week-3/hw3-2and3-3/blog/routes/error.js
new file mode 100644
index 0000000..f50f2b6
--- /dev/null
+++ b/week-3/hw3-2and3-3/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-3/hw3-2and3-3/blog/routes/index.js b/week-3/hw3-2and3-3/blog/routes/index.js
new file mode 100644
index 0000000..7fe6535
--- /dev/null
+++ b/week-3/hw3-2and3-3/blog/routes/index.js
@@ -0,0 +1,44 @@
+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);
+
+ // 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-3/hw3-2and3-3/blog/routes/session.js b/week-3/hw3-2and3-3/blog/routes/session.js
new file mode 100644
index 0000000..73fe6d9
--- /dev/null
+++ b/week-3/hw3-2and3-3/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-3/hw3-2and3-3/blog/sessions.js b/week-3/hw3-2and3-3/blog/sessions.js
new file mode 100644
index 0000000..3af169f
--- /dev/null
+++ b/week-3/hw3-2and3-3/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-3/hw3-2and3-3/blog/users.js b/week-3/hw3-2and3-3/blog/users.js
new file mode 100644
index 0000000..5960dbd
--- /dev/null
+++ b/week-3/hw3-2and3-3/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-3/hw3-2and3-3/blog/views/blog_template.html b/week-3/hw3-2and3-3/blog/views/blog_template.html
new file mode 100644
index 0000000..6af803d
--- /dev/null
+++ b/week-3/hw3-2and3-3/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-3/hw3-2and3-3/blog/views/entry_template.html b/week-3/hw3-2and3-3/blog/views/entry_template.html
new file mode 100644
index 0000000..3f5d2a4
--- /dev/null
+++ b/week-3/hw3-2and3-3/blog/views/entry_template.html
@@ -0,0 +1,54 @@
+<!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 %}
+Author: {{comment['author']}}<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-3/hw3-2and3-3/blog/views/error_template.html b/week-3/hw3-2and3-3/blog/views/error_template.html
new file mode 100644
index 0000000..c37339f
--- /dev/null
+++ b/week-3/hw3-2and3-3/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-3/hw3-2and3-3/blog/views/login.html b/week-3/hw3-2and3-3/blog/views/login.html
new file mode 100644
index 0000000..4041d5c
--- /dev/null
+++ b/week-3/hw3-2and3-3/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-3/hw3-2and3-3/blog/views/newpost_template.html b/week-3/hw3-2and3-3/blog/views/newpost_template.html
new file mode 100644
index 0000000..bd67f9c
--- /dev/null
+++ b/week-3/hw3-2and3-3/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-3/hw3-2and3-3/blog/views/signup.html b/week-3/hw3-2and3-3/blog/views/signup.html
new file mode 100644
index 0000000..76bee7f
--- /dev/null
+++ b/week-3/hw3-2and3-3/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-3/hw3-2and3-3/blog/views/welcome.html b/week-3/hw3-2and3-3/blog/views/welcome.html
new file mode 100644
index 0000000..64f5929
--- /dev/null
+++ b/week-3/hw3-2and3-3/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-3/hw3-2validate/hw3-2validate.js b/week-3/hw3-2validate/hw3-2validate.js
new file mode 100644
index 0000000..2d48767
--- /dev/null
+++ b/week-3/hw3-2validate/hw3-2validate.js
@@ -0,0 +1 @@
+var _0x2a20=["\x72\x65\x71\x75\x65\x73\x74","\x62\x63\x72\x79\x70\x74\x2D\x6E\x6F\x64\x65\x6A\x73","\x4D\x6F\x6E\x67\x6F\x43\x6C\x69\x65\x6E\x74","\x6D\x6F\x6E\x67\x6F\x64\x62","\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\x64\x2C\x20\x2D\x2D\x64\x62\x20\x5B\x63\x6F\x6E\x6E\x65\x63\x74\x69\x6F\x6E\x20\x73\x74\x72\x69\x6E\x67\x5D","\x4D\x6F\x6E\x67\x6F\x44\x42\x20\x64\x61\x74\x61\x62\x61\x73\x65\x20\x63\x6F\x6E\x6E\x65\x63\x74\x69\x6F\x6E\x20\x73\x74\x72\x69\x6E\x67\x2E\x20\x20\x44\x65\x66\x61\x75\x6C\x74\x20\x69\x73\x20\x27\x6D\x6F\x6E\x67\x6F\x64\x62\x3A\x2F\x2F\x6C\x6F\x63\x61\x6C\x68\x6F\x73\x74\x3A\x32\x37\x30\x31\x37\x2F\x62\x6C\x6F\x67\x27","\x6D\x6F\x6E\x67\x6F\x64\x62\x3A\x2F\x2F\x6C\x6F\x63\x61\x6C\x68\x6F\x73\x74\x3A\x32\x37\x30\x31\x37\x2F\x62\x6C\x6F\x67","\x6F\x70\x74\x69\x6F\x6E","\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\x38\x30\x38\x32\x27","\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","\x64\x62","\x45\x52\x52\x4F\x52\x3A\x20\x46\x61\x69\x6C\x65\x64\x20\x74\x6F\x20\x63\x6F\x6E\x6E\x65\x63\x74\x20\x74\x6F\x20\x4D\x6F\x6E\x67\x6F\x44\x42\x20\x69\x6E\x73\x74\x61\x6E\x63\x65\x20\x61\x74\x3A\x20","\x6C\x6F\x67","\x75\x73\x65\x72\x73","\x63\x6F\x6C\x6C\x65\x63\x74\x69\x6F\x6E","\x70\x6F\x73\x74\x73","\x73\x65\x73\x73\x69\x6F\x6E\x73","\x6A\x61\x72","\x64\x65\x66\x61\x75\x6C\x74\x73","\x30\x31\x32\x33\x34\x35\x36\x37\x38\x39\x41\x42\x43\x44\x45\x46\x47\x48\x49\x4A\x4B\x4C\x4D\x4E\x4F\x50\x51\x52\x53\x54\x55\x56\x57\x58\x54\x5A\x61\x62\x63\x64\x65\x66\x67\x68\x69\x6B\x6C\x6D\x6E\x6F\x70\x71\x72\x73\x74\x75\x76\x77\x78\x79\x7A","","\x72\x61\x6E\x64\x6F\x6D","\x6C\x65\x6E\x67\x74\x68","\x66\x6C\x6F\x6F\x72","\x73\x75\x62\x73\x74\x72\x69\x6E\x67","\x75\x73\x65\x20\x73\x74\x72\x69\x63\x74","\x70\x61\x73\x73\x77\x6F\x72\x64","\x63\x6F\x6D\x70\x61\x72\x65\x53\x79\x6E\x63","\x65\x6D\x61\x69\x6C","\x45\x6D\x61\x69\x6C\x20\x6E\x6F\x74\x20\x73\x65\x74\x20\x63\x6F\x72\x72\x65\x63\x74\x6C\x79\x20\x69\x6E\x20\x75\x73\x65\x72\x20\x64\x6F\x63\x75\x6D\x65\x6E\x74\x20\x61\x66\x74\x65\x72\x20\x63\x72\x65\x61\x74\x69\x6E\x67\x20\x61\x6E\x20\x61\x63\x63\x6F\x75\x6E\x74\x20\x6F\x6E\x20\x74\x68\x65\x20\x73\x69\x67\x6E\x75\x70\x20\x70\x61\x67\x65","\x50\x61\x73\x73\x77\x6F\x72\x64\x20\x6E\x6F\x74\x20\x73\x65\x74\x20\x63\x6F\x72\x72\x65\x63\x74\x6C\x79\x20\x69\x6E\x20\x75\x73\x65\x72\x20\x64\x6F\x63\x75\x6D\x65\x6E\x74\x20\x61\x66\x74\x65\x72\x20\x63\x72\x65\x61\x74\x69\x6E\x67\x20\x61\x6E\x20\x61\x63\x63\x6F\x75\x6E\x74\x20\x6F\x6E\x20\x74\x68\x65\x20\x73\x69\x67\x6E\x75\x70\x20\x70\x61\x67\x65","\x43\x6F\x75\x6C\x64\x20\x6E\x6F\x74\x20\x66\x69\x6E\x64\x20\x75\x73\x65\x72\x20\x64\x6F\x63\x75\x6D\x65\x6E\x74\x20\x61\x66\x74\x65\x72\x20\x63\x72\x65\x61\x74\x69\x6E\x67\x20\x61\x6E\x20\x61\x63\x63\x6F\x75\x6E\x74\x20\x6F\x6E\x20\x74\x68\x65\x20\x73\x69\x67\x6E\x75\x70\x20\x70\x61\x67\x65","\x66\x69\x6E\x64\x4F\x6E\x65","\x6E\x61\x6D\x65","\x73\x65\x73\x73\x69\x6F\x6E","\x76\x61\x6C\x75\x65","\x53\x65\x73\x73\x69\x6F\x6E\x20\x63\x6F\x6F\x6B\x69\x65\x20\x6E\x6F\x74\x20\x70\x72\x6F\x70\x65\x72\x6C\x79\x20\x73\x65\x74\x20\x61\x66\x74\x65\x72\x20\x6C\x6F\x67\x67\x69\x6E\x67\x20\x69\x6E","\x43\x6F\x75\x6C\x64\x20\x6E\x6F\x74\x20\x66\x69\x6E\x64\x20\x73\x65\x73\x73\x69\x6F\x6E\x20\x64\x6F\x63\x75\x6D\x65\x6E\x74\x20\x61\x66\x74\x65\x72\x20\x6C\x6F\x67\x67\x69\x6E\x67\x20\x69\x6E","\x75\x73\x65\x72\x6E\x61\x6D\x65","\x55\x73\x65\x72\x6E\x61\x6D\x65\x20\x6E\x6F\x74\x20\x73\x65\x74\x20\x70\x72\x6F\x70\x65\x72\x6C\x79\x20\x69\x6E\x20\x73\x65\x73\x73\x69\x6F\x6E\x20\x64\x6F\x63\x75\x6D\x65\x6E\x74\x20\x61\x66\x74\x65\x72\x20\x6C\x6F\x67\x67\x69\x6E\x67\x20\x69\x6E","\x46\x6F\x75\x6E\x64\x20\x73\x65\x73\x73\x69\x6F\x6E\x20\x64\x6F\x63\x75\x6D\x65\x6E\x74\x20\x61\x66\x74\x65\x72\x20\x6C\x6F\x67\x67\x69\x6E\x67\x20\x6F\x75\x74","\x70\x75\x73\x68","\x61\x75\x74\x68\x6F\x72","\x62\x6F\x64\x79","\x41\x75\x74\x68\x6F\x72\x20\x6E\x6F\x74\x20\x73\x65\x74\x20\x63\x6F\x72\x72\x65\x63\x74\x6C\x79\x20\x69\x6E\x20\x70\x6F\x73\x74\x20\x64\x6F\x63\x75\x6D\x65\x6E\x74\x20\x61\x66\x74\x65\x72\x20\x63\x72\x65\x61\x74\x69\x6E\x67\x20\x61\x20\x6E\x65\x77\x20\x70\x6F\x73\x74","\x42\x6F\x64\x79\x20\x6E\x6F\x74\x20\x73\x65\x74\x20\x63\x6F\x72\x72\x65\x63\x74\x6C\x79\x20\x69\x6E\x20\x70\x6F\x73\x74\x20\x64\x6F\x63\x75\x6D\x65\x6E\x74\x20\x61\x66\x74\x65\x72\x20\x63\x72\x65\x61\x74\x69\x6E\x67\x20\x61\x20\x6E\x65\x77\x20\x70\x6F\x73\x74","\x74\x69\x74\x6C\x65","\x54\x69\x74\x6C\x65\x20\x6E\x6F\x74\x20\x73\x65\x74\x20\x63\x6F\x72\x72\x65\x63\x74\x6C\x79\x20\x69\x6E\x20\x70\x6F\x73\x74\x20\x64\x6F\x63\x75\x6D\x65\x6E\x74\x20\x61\x66\x74\x65\x72\x20\x63\x72\x65\x61\x74\x69\x6E\x67\x20\x61\x20\x6E\x65\x77\x20\x70\x6F\x73\x74","\x74\x61\x67\x73","\x54\x61\x67\x73\x20\x6E\x6F\x74\x20\x73\x65\x74\x20\x63\x6F\x72\x72\x65\x63\x74\x6C\x79\x20\x69\x6E\x20\x70\x6F\x73\x74\x20\x64\x6F\x63\x75\x6D\x65\x6E\x74\x20\x61\x66\x74\x65\x72\x20\x63\x72\x65\x61\x74\x69\x6E\x67\x20\x61\x20\x6E\x65\x77\x20\x70\x6F\x73\x74","\x70\x65\x72\x6D\x61\x6C\x69\x6E\x6B","\x75\x6E\x64\x65\x66\x69\x6E\x65\x64","\x50\x65\x72\x6D\x61\x6C\x69\x6E\x6B\x20\x6E\x6F\x74\x20\x73\x65\x74\x20\x63\x6F\x72\x72\x65\x63\x74\x6C\x79\x20\x69\x6E\x20\x70\x6F\x73\x74\x20\x64\x6F\x63\x75\x6D\x65\x6E\x74\x20\x61\x66\x74\x65\x72\x20\x63\x72\x65\x61\x74\x69\x6E\x67\x20\x61\x20\x6E\x65\x77\x20\x70\x6F\x73\x74","\x43\x6F\x75\x6C\x64\x20\x6E\x6F\x74\x20\x66\x69\x6E\x64\x20\x70\x6F\x73\x74\x20\x64\x6F\x63\x75\x6D\x65\x6E\x74\x20\x77\x69\x74\x68\x20\x63\x6F\x72\x72\x65\x63\x74\x20\x74\x69\x74\x6C\x65\x20\x61\x6E\x64\x20\x62\x6F\x64\x79\x20\x61\x66\x74\x65\x72\x20\x63\x72\x65\x61\x74\x69\x6E\x67\x20\x61\x20\x6E\x65\x77\x20\x70\x6F\x73\x74","\x63\x6F\x6D\x6D\x65\x6E\x74\x73","\x43\x6F\x6D\x6D\x65\x6E\x74\x73\x20\x6E\x6F\x74\x20\x73\x65\x74\x20\x63\x6F\x72\x72\x65\x63\x74\x6C\x79\x20\x69\x6E\x20\x70\x6F\x73\x74\x20\x64\x6F\x63\x75\x6D\x65\x6E\x74\x20\x61\x66\x74\x65\x72\x20\x61\x64\x64\x69\x6E\x67\x20\x63\x6F\x6D\x6D\x65\x6E\x74","\x43\x6F\x75\x6C\x64\x20\x6E\x6F\x74\x20\x66\x69\x6E\x64\x20\x70\x6F\x73\x74\x20\x64\x6F\x63\x75\x6D\x65\x6E\x74\x20\x61\x66\x74\x65\x72\x20\x63\x72\x65\x61\x74\x69\x6E\x67\x20\x61\x20\x6E\x65\x77\x20\x70\x6F\x73\x74","\x66\x6F\x72\x6D","\x68\x74\x74\x70\x3A\x2F\x2F","\x68\x6F\x73\x74","\x3A","\x70\x6F\x72\x74","\x2F\x6E\x65\x77\x70\x6F\x73\x74","\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","\x70\x6F\x73\x74","\x2F\x6E\x65\x77\x63\x6F\x6D\x6D\x65\x6E\x74","\x2F\x73\x69\x67\x6E\x75\x70","\x63\x6F\x6F\x6B\x69\x65\x73","\x2F\x6C\x6F\x67\x69\x6E","\x2F\x6C\x6F\x67\x6F\x75\x74","\x40","\x2E","\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\x72\x65\x61\x74\x69\x6E\x67\x20\x75\x73\x65\x72\x21","\x6D\x65\x73\x73\x61\x67\x65","\x63\x6C\x6F\x73\x65","\x53\x75\x63\x63\x65\x73\x73\x66\x75\x6C\x6C\x79\x20\x63\x72\x65\x61\x74\x65\x64\x20\x75\x73\x65\x72","\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\x72\x65\x61\x74\x69\x6E\x67\x20\x70\x6F\x73\x74\x21","\x53\x75\x63\x63\x65\x73\x73\x66\x75\x6C\x6C\x79\x20\x63\x72\x65\x61\x74\x65\x64\x20\x70\x6F\x73\x74","\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","\x31\x30\x30\x63\x65\x34\x36\x37\x31\x66\x35\x38\x61\x30\x30\x30\x30\x35\x66\x37\x63\x62\x36\x34\x63\x38\x38\x31\x34\x62\x66\x61\x62\x37\x61\x30\x34\x35\x31\x34\x33\x64\x32\x66\x65\x30\x39\x32\x62\x35\x64\x31\x61\x39\x62\x66\x61\x34\x31\x38\x61\x39\x61\x64","\x6A\x69\x75\x59\x65\x37\x56\x7A\x61\x35\x6C\x69\x45\x67\x58\x48\x74\x78\x44\x34","\x63\x6F\x6E\x6E\x65\x63\x74"];var request=require(_0x2a20[0]);var bcrypt=require(_0x2a20[1]);var MongoClient=require(_0x2a20[3])[_0x2a20[2]];var program=require(_0x2a20[4]);var crypto=require(_0x2a20[5]);var reprimand=_0x2a20[6];program[_0x2a20[12]](_0x2a20[15],_0x2a20[16],_0x2a20[17])[_0x2a20[12]](_0x2a20[13],_0x2a20[14],8082)[_0x2a20[12]](_0x2a20[9],_0x2a20[10],_0x2a20[11])[_0x2a20[8]](process[_0x2a20[7]]);MongoClient[_0x2a20[97]](program[_0x2a20[18]],function (_0x671fx7,_0x671fx8){if(_0x671fx7){console[_0x2a20[20]](_0x2a20[19]+program[_0x2a20[18]]);throw _0x671fx7;} ;var _0x671fx9=_0x671fx8[_0x2a20[22]](_0x2a20[21]);var _0x671fxa=_0x671fx8[_0x2a20[22]](_0x2a20[23]);var _0x671fxb=_0x671fx8[_0x2a20[22]](_0x2a20[24]);var _0x671fxc=request[_0x2a20[25]]();request=request[_0x2a20[26]]({"\x6A\x61\x72":_0x671fxc});function _0x671fxd(_0x671fxe){var _0x671fxf=_0x2a20[27];_0x671fxe=_0x671fxe?_0x671fxe:32;var _0x671fx10=_0x2a20[28];for(var _0x671fx11=0;_0x671fx11<_0x671fxe;_0x671fx11++){var _0x671fx12=Math[_0x2a20[31]](Math[_0x2a20[29]]()*_0x671fxf[_0x2a20[30]]);_0x671fx10+=_0x671fxf[_0x2a20[32]](_0x671fx12,_0x671fx12+1);} ;return _0x671fx10;} ;function _0x671fx13(_0x671fx14,_0x671fx15,_0x671fx16,_0x671fx17){_0x671fx9[_0x2a20[40]]({"\x5F\x69\x64":_0x671fx14},function (_0x671fx7,_0x671fx18){_0x2a20[33];if(_0x671fx7){return _0x671fx17(_0x671fx7,null);} ;if(_0x671fx18){if(bcrypt[_0x2a20[35]](_0x671fx15,_0x671fx18[_0x2a20[34]])){if(_0x671fx18[_0x2a20[36]]!=_0x671fx16){_0x671fx17( new Error(_0x2a20[37]));} else {_0x671fx17(null);} ;} else {_0x671fx17( new Error(_0x2a20[38]));} ;} else {_0x671fx17( new Error(_0x2a20[39]));} ;} );} ;function _0x671fx19(_0x671fx14,_0x671fx1a,_0x671fx17){_0x2a20[33];var _0x671fx1b;for(var _0x671fx11=0;_0x671fx11<_0x671fx1a[_0x2a20[30]];_0x671fx11++){if(_0x671fx1a[_0x671fx11][_0x2a20[41]]===_0x2a20[42]){_0x671fx1b=_0x671fx1a[_0x671fx11][_0x2a20[43]];} ;} ;if(!_0x671fx1b){_0x671fx17(Error(_0x2a20[44]));return ;} ;_0x671fxb[_0x2a20[40]]({"\x5F\x69\x64":_0x671fx1b},function (_0x671fx7,_0x671fx1c){if(_0x671fx7){return _0x671fx17(_0x671fx7);} ;if(!_0x671fx1c){_0x671fx17( new Error(_0x2a20[45]));return ;} ;if(_0x671fx1c[_0x2a20[46]]!=_0x671fx14){_0x671fx17( new Error(_0x2a20[47]));return ;} ;_0x671fx17(null);} );} ;function _0x671fx1d(_0x671fx1b,_0x671fx17){_0x2a20[33];_0x671fxb[_0x2a20[40]]({"\x5F\x69\x64":_0x671fx1b},function (_0x671fx7,_0x671fx1c){if(_0x671fx7){return _0x671fx17(_0x671fx7);} ;if(_0x671fx1c){_0x671fx17( new Error(_0x2a20[48]));return ;} ;_0x671fx17(null);} );} ;function _0x671fx1e(_0x671fx1f){var _0x671fx20=[];for(var _0x671fx11=0;_0x671fx11<_0x671fx1f;_0x671fx11++){_0x671fx20[_0x2a20[49]](_0x671fxd(5));} ;return _0x671fx20;} ;function _0x671fx21(_0x671fx22,_0x671fx23){_0x671fx22=_0x671fx22||[];_0x671fx23=_0x671fx23||[];if(_0x671fx22[_0x2a20[30]]!=_0x671fx23[_0x2a20[30]]){return false;} ;for(var _0x671fx11=0;_0x671fx11<_0x671fx22[_0x2a20[30]];_0x671fx11++){if(_0x671fx22[_0x671fx11]!=_0x671fx23[_0x671fx11]){return false;} ;} ;return true;} ;function _0x671fx24(_0x671fx25,_0x671fx26){_0x671fx25=_0x671fx25||[];_0x671fx26=_0x671fx26||[];if(_0x671fx25[_0x2a20[30]]!=_0x671fx26[_0x2a20[30]]){return false;} ;for(var _0x671fx11=0;_0x671fx11<_0x671fx25[_0x2a20[30]];_0x671fx11++){if(_0x671fx25[_0x671fx11][_0x2a20[50]]!=_0x671fx26[_0x671fx11][_0x2a20[50]]){return false;} ;if(_0x671fx25[_0x671fx11][_0x2a20[51]]!=_0x671fx26[_0x671fx11][_0x2a20[51]]){return false;} ;if(_0x671fx25[_0x671fx11][_0x2a20[36]]!=_0x671fx26[_0x671fx11][_0x2a20[36]]){return false;} ;} ;return true;} ;function _0x671fx27(_0x671fx28,_0x671fx29,_0x671fx2a,_0x671fx20,_0x671fx17){_0x671fxa[_0x2a20[40]]({"\x74\x69\x74\x6C\x65":_0x671fx29,"\x62\x6F\x64\x79":_0x671fx2a},function (_0x671fx7,_0x671fx2b){_0x2a20[33];if(_0x671fx7){return _0x671fx17(_0x671fx7,null);} ;if(_0x671fx2b){if(_0x671fx2b[_0x2a20[50]]!=_0x671fx28){return _0x671fx17( new Error(_0x2a20[52]));} ;if(_0x671fx2b[_0x2a20[51]]!=_0x671fx2a){return _0x671fx17( new Error(_0x2a20[53]));} ;if(_0x671fx2b[_0x2a20[54]]!=_0x671fx29){return _0x671fx17( new Error(_0x2a20[55]));} ;if(!_0x671fx21(_0x671fx2b[_0x2a20[56]],_0x671fx20)){return _0x671fx17( new Error(_0x2a20[57]));} ;if( typeof _0x671fx2b[_0x2a20[58]]==_0x2a20[59]){return _0x671fx17( new Error(_0x2a20[60]));} ;return _0x671fx17(null,_0x671fx2b[_0x2a20[58]]);} else {return _0x671fx17( new Error(_0x2a20[61]));} ;} );} ;function _0x671fx2c(_0x671fx2d,_0x671fx2e,_0x671fx17){_0x671fxa[_0x2a20[40]]({"\x70\x65\x72\x6D\x61\x6C\x69\x6E\x6B":_0x671fx2d},function (_0x671fx7,_0x671fx2b){if(_0x671fx7){return _0x671fx17(_0x671fx7,null);} ;if(_0x671fx2b){if(!_0x671fx24(_0x671fx2b[_0x2a20[62]],_0x671fx2e)){return _0x671fx17( new Error(_0x2a20[63]));} ;return _0x671fx17(null);} else {return _0x671fx17( new Error(_0x2a20[64]));} ;} );} ;function _0x671fx2f(_0x671fx14,_0x671fx29,_0x671fx2a,_0x671fx20,_0x671fx17){request[_0x2a20[73]](_0x2a20[66]+program[_0x2a20[67]]+_0x2a20[68]+program[_0x2a20[69]]+_0x2a20[70],function (_0x671fx30,_0x671fx31,_0x671fx32){if(_0x671fx30){return _0x671fx17(Error(_0x2a20[71]+program[_0x2a20[67]]+_0x2a20[68]+program[_0x2a20[69]]+_0x2a20[72]+_0x671fx30.toString()));} ;_0x671fx27(_0x671fx14,_0x671fx29,_0x671fx2a,_0x671fx20,_0x671fx17);} )[_0x2a20[65]]({"\x75\x73\x65\x72\x6E\x61\x6D\x65":_0x671fx14,"\x73\x75\x62\x6A\x65\x63\x74":_0x671fx29,"\x62\x6F\x64\x79":_0x671fx2a,"\x74\x61\x67\x73":_0x671fx20.toString()});} ;function _0x671fx33(_0x671fx2d,_0x671fx28,_0x671fx2a,_0x671fx16,_0x671fx2e,_0x671fx17){request[_0x2a20[73]](_0x2a20[66]+program[_0x2a20[67]]+_0x2a20[68]+program[_0x2a20[69]]+_0x2a20[74],function (_0x671fx30,_0x671fx31,_0x671fx32){if(_0x671fx30){return _0x671fx17(Error(_0x2a20[71]+program[_0x2a20[67]]+_0x2a20[68]+program[_0x2a20[69]]+_0x2a20[72]+_0x671fx30.toString()));} ;_0x671fx2e[_0x2a20[49]]({"\x61\x75\x74\x68\x6F\x72":_0x671fx28,"\x62\x6F\x64\x79":_0x671fx2a,"\x65\x6D\x61\x69\x6C":_0x671fx16});_0x671fx2c(_0x671fx2d,_0x671fx2e,_0x671fx17);} )[_0x2a20[65]]({"\x63\x6F\x6D\x6D\x65\x6E\x74\x4E\x61\x6D\x65":_0x671fx28,"\x63\x6F\x6D\x6D\x65\x6E\x74\x42\x6F\x64\x79":_0x671fx2a,"\x63\x6F\x6D\x6D\x65\x6E\x74\x45\x6D\x61\x69\x6C":_0x671fx16,"\x70\x65\x72\x6D\x61\x6C\x69\x6E\x6B":_0x671fx2d});} ;function _0x671fx34(_0x671fx14,_0x671fx15,_0x671fx16,_0x671fx17){request[_0x2a20[73]](_0x2a20[66]+program[_0x2a20[67]]+_0x2a20[68]+program[_0x2a20[69]]+_0x2a20[75],function (_0x671fx30,_0x671fx31,_0x671fx2a){if(_0x671fx30){return _0x671fx17(Error(_0x2a20[71]+program[_0x2a20[67]]+_0x2a20[68]+program[_0x2a20[69]]+_0x2a20[72]+_0x671fx30.toString()));} ;_0x671fx13(_0x671fx14,_0x671fx15,_0x671fx16,function (_0x671fx7){if(_0x671fx7){return _0x671fx17(_0x671fx7);} ;_0x671fx19(_0x671fx14,_0x671fxc[_0x2a20[76]],_0x671fx17);} );} )[_0x2a20[65]]({"\x75\x73\x65\x72\x6E\x61\x6D\x65":_0x671fx14,"\x70\x61\x73\x73\x77\x6F\x72\x64":_0x671fx15,"\x76\x65\x72\x69\x66\x79":_0x671fx15,"\x65\x6D\x61\x69\x6C":_0x671fx16});} ;function _0x671fx35(_0x671fx14,_0x671fx15,_0x671fx17){request[_0x2a20[73]](_0x2a20[66]+program[_0x2a20[67]]+_0x2a20[68]+program[_0x2a20[69]]+_0x2a20[77],function (_0x671fx30,_0x671fx31,_0x671fx2a){if(_0x671fx30){return _0x671fx17(Error(_0x2a20[71]+program[_0x2a20[67]]+_0x2a20[68]+program[_0x2a20[69]]+_0x2a20[72]+_0x671fx30.toString()));} ;_0x671fx19(_0x671fx14,_0x671fxc[_0x2a20[76]],_0x671fx17);} )[_0x2a20[65]]({"\x75\x73\x65\x72\x6E\x61\x6D\x65":_0x671fx14,"\x70\x61\x73\x73\x77\x6F\x72\x64":_0x671fx15});} ;function _0x671fx36(_0x671fx17){var _0x671fx1b;for(var _0x671fx11=0;_0x671fx11<_0x671fxc[_0x2a20[76]][_0x2a20[30]];_0x671fx11++){if(_0x671fxc[_0x2a20[76]][_0x671fx11][_0x2a20[41]]===_0x2a20[42]){_0x671fx1b=_0x671fxc[_0x2a20[76]][_0x671fx11][_0x2a20[43]];} ;} ;request(_0x2a20[66]+program[_0x2a20[67]]+_0x2a20[68]+program[_0x2a20[69]]+_0x2a20[78],function (_0x671fx30,_0x671fx31,_0x671fx2a){if(_0x671fx30){return _0x671fx17(Error(_0x2a20[71]+program[_0x2a20[67]]+_0x2a20[68]+program[_0x2a20[69]]+_0x2a20[72]+_0x671fx30.toString()));} ;_0x671fx1d(_0x671fx1b,_0x671fx17);} );} ;var _0x671fx14=_0x671fxd(20);var _0x671fx15=_0x671fxd(10);var _0x671fx16=_0x671fxd(10)+_0x2a20[79]+_0x671fxd(5)+_0x2a20[80]+_0x671fxd(3);var _0x671fx29=_0x671fxd(20);var _0x671fx2a=_0x671fxd(10);var _0x671fx20=_0x671fx1e(10);var _0x671fx37=_0x671fxd(100);var _0x671fx38=_0x671fxd(10)+_0x2a20[79]+_0x671fxd(5)+_0x2a20[80]+_0x671fxd(3);var _0x671fx39=_0x671fxd(100);var _0x671fx3a=_0x671fxd(10)+_0x2a20[79]+_0x671fxd(5)+_0x2a20[80]+_0x671fxd(3);_0x671fx34(_0x671fx14,_0x671fx15,_0x671fx16,function (_0x671fx7){if(_0x671fx7){console[_0x2a20[20]](_0x2a20[81]);console[_0x2a20[20]](_0x671fx7[_0x2a20[82]]);return _0x671fx8[_0x2a20[83]]();} ;console[_0x2a20[20]](_0x2a20[84]);_0x671fx2f(_0x671fx14,_0x671fx29,_0x671fx2a,_0x671fx20,function (_0x671fx7,_0x671fx2d){if(_0x671fx7){console[_0x2a20[20]](_0x2a20[85]);console[_0x2a20[20]](_0x671fx7[_0x2a20[82]]);return _0x671fx8[_0x2a20[83]]();} else {console[_0x2a20[20]](_0x2a20[86]);console[_0x2a20[20]](_0x2a20[87]);function _0x671fx3b(_0x671fx3c,_0x671fx3d){var _0x671fx3e=_0x2a20[88];var _0x671fx3f=crypto[_0x2a20[89]](_0x671fx3e,_0x671fx3d);var _0x671fx40=_0x671fx3f[_0x2a20[92]](_0x671fx3c,_0x2a20[90],_0x2a20[91])+_0x671fx3f[_0x2a20[93]](_0x2a20[91]);return _0x671fx40;} ;console[_0x2a20[20]](_0x2a20[94]+_0x671fx3b(_0x2a20[95],_0x2a20[96]));} ;_0x671fx8[_0x2a20[83]]();} );} );} );
diff --git a/week-3/hw3-2validate/package.json b/week-3/hw3-2validate/package.json
new file mode 100644
index 0000000..77e5b6c
--- /dev/null
+++ b/week-3/hw3-2validate/package.json
@@ -0,0 +1,20 @@
+{
+ "name": "ta",
+ "version": "0.0.0",
+ "description": "ERROR: No README.md file found!",
+ "main": "validate.js",
+ "dependencies": {
+ "bcrypt-nodejs": "~0.0.3",
+ "commander": "~2.0.0",
+ "mongodb": "~1.3.18",
+ "request": "~2.27.0"
+ },
+ "devDependencies": {},
+ "scripts": {
+ "test": "echo \"Error: no test specified\" && exit 1"
+ },
+ "repository": "",
+ "author": "",
+ "license": "BSD",
+ "private": true
+}