summaryrefslogtreecommitdiff
path: root/week-2
diff options
context:
space:
mode:
authormo khan <mo@mokhan.ca>2015-01-18 22:48:26 -0700
committermo khan <mo@mokhan.ca>2015-01-18 22:48:26 -0700
commitb50664795c3f4b6c672c5b1dd7248180fd096322 (patch)
tree8a23c44af4c09f61963caf6c9e421a214e190ce2 /week-2
parentfbf1ae7f45148a180083ef79d6ccf2983660e4d1 (diff)
add hw2-3 package.
Diffstat (limited to 'week-2')
-rw-r--r--week-2/hw2-3/blog/README.md13
-rw-r--r--week-2/hw2-3/blog/app.js27
-rw-r--r--week-2/hw2-3/blog/package.json22
-rw-r--r--week-2/hw2-3/blog/posts.js89
-rw-r--r--week-2/hw2-3/blog/routes/content.js187
-rw-r--r--week-2/hw2-3/blog/routes/error.js9
-rw-r--r--week-2/hw2-3/blog/routes/index.js44
-rw-r--r--week-2/hw2-3/blog/routes/session.js170
-rw-r--r--week-2/hw2-3/blog/sessions.js65
-rw-r--r--week-2/hw2-3/blog/users.js68
-rw-r--r--week-2/hw2-3/blog/views/blog_template.html40
-rw-r--r--week-2/hw2-3/blog/views/entry_template.html54
-rw-r--r--week-2/hw2-3/blog/views/error_template.html12
-rw-r--r--week-2/hw2-3/blog/views/login.html47
-rw-r--r--week-2/hw2-3/blog/views/newpost_template.html29
-rw-r--r--week-2/hw2-3/blog/views/signup.html75
-rw-r--r--week-2/hw2-3/blog/views/welcome.html28
-rw-r--r--week-2/hw2-3/validate/README.md10
-rw-r--r--week-2/hw2-3/validate/package.json20
-rw-r--r--week-2/hw2-3/validate/validate.js1
20 files changed, 1010 insertions, 0 deletions
diff --git a/week-2/hw2-3/blog/README.md b/week-2/hw2-3/blog/README.md
new file mode 100644
index 0000000..fd885ed
--- /dev/null
+++ b/week-2/hw2-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-2/hw2-3/blog/app.js b/week-2/hw2-3/blog/app.js
new file mode 100644
index 0000000..f773876
--- /dev/null
+++ b/week-2/hw2-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(3000);
+ console.log('Express server listening on port 3000');
+});
diff --git a/week-2/hw2-3/blog/package.json b/week-2/hw2-3/blog/package.json
new file mode 100644
index 0000000..bdec04a
--- /dev/null
+++ b/week-2/hw2-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.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-2/hw2-3/blog/posts.js b/week-2/hw2-3/blog/posts.js
new file mode 100644
index 0000000..4cae444
--- /dev/null
+++ b/week-2/hw2-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 Not Yet Implemented!"), 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 Not Yet Implemented!"), null);
+ }
+}
+
+module.exports.PostsDAO = PostsDAO;
diff --git a/week-2/hw2-3/blog/routes/content.js b/week-2/hw2-3/blog/routes/content.js
new file mode 100644
index 0000000..abdada8
--- /dev/null
+++ b/week-2/hw2-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-2/hw2-3/blog/routes/error.js b/week-2/hw2-3/blog/routes/error.js
new file mode 100644
index 0000000..f50f2b6
--- /dev/null
+++ b/week-2/hw2-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-2/hw2-3/blog/routes/index.js b/week-2/hw2-3/blog/routes/index.js
new file mode 100644
index 0000000..7fe6535
--- /dev/null
+++ b/week-2/hw2-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-2/hw2-3/blog/routes/session.js b/week-2/hw2-3/blog/routes/session.js
new file mode 100644
index 0000000..73fe6d9
--- /dev/null
+++ b/week-2/hw2-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-2/hw2-3/blog/sessions.js b/week-2/hw2-3/blog/sessions.js
new file mode 100644
index 0000000..3af169f
--- /dev/null
+++ b/week-2/hw2-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-2/hw2-3/blog/users.js b/week-2/hw2-3/blog/users.js
new file mode 100644
index 0000000..9a93882
--- /dev/null
+++ b/week-2/hw2-3/blog/users.js
@@ -0,0 +1,68 @@
+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;
+ }
+
+ // TODO: hw2.3
+ callback(Error("addUser Not Yet Implemented!"), 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);
+ }
+ }
+
+ // TODO: hw2.3
+ callback(Error("validateLogin Not Yet Implemented!"), null);
+ }
+}
+
+module.exports.UsersDAO = UsersDAO;
diff --git a/week-2/hw2-3/blog/views/blog_template.html b/week-2/hw2-3/blog/views/blog_template.html
new file mode 100644
index 0000000..6af803d
--- /dev/null
+++ b/week-2/hw2-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-2/hw2-3/blog/views/entry_template.html b/week-2/hw2-3/blog/views/entry_template.html
new file mode 100644
index 0000000..3f5d2a4
--- /dev/null
+++ b/week-2/hw2-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-2/hw2-3/blog/views/error_template.html b/week-2/hw2-3/blog/views/error_template.html
new file mode 100644
index 0000000..c37339f
--- /dev/null
+++ b/week-2/hw2-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-2/hw2-3/blog/views/login.html b/week-2/hw2-3/blog/views/login.html
new file mode 100644
index 0000000..4041d5c
--- /dev/null
+++ b/week-2/hw2-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-2/hw2-3/blog/views/newpost_template.html b/week-2/hw2-3/blog/views/newpost_template.html
new file mode 100644
index 0000000..bd67f9c
--- /dev/null
+++ b/week-2/hw2-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-2/hw2-3/blog/views/signup.html b/week-2/hw2-3/blog/views/signup.html
new file mode 100644
index 0000000..76bee7f
--- /dev/null
+++ b/week-2/hw2-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-2/hw2-3/blog/views/welcome.html b/week-2/hw2-3/blog/views/welcome.html
new file mode 100644
index 0000000..64f5929
--- /dev/null
+++ b/week-2/hw2-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-2/hw2-3/validate/README.md b/week-2/hw2-3/validate/README.md
new file mode 100644
index 0000000..87d21b6
--- /dev/null
+++ b/week-2/hw2-3/validate/README.md
@@ -0,0 +1,10 @@
+Validation script for hw2-3
+
+To run:
+
+npm install
+node validate.js
+
+To see extra options (if you are running the blog or mongodb on different hosts or ports):
+
+node validate.js --help
diff --git a/week-2/hw2-3/validate/package.json b/week-2/hw2-3/validate/package.json
new file mode 100644
index 0000000..77e5b6c
--- /dev/null
+++ b/week-2/hw2-3/validate/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
+}
diff --git a/week-2/hw2-3/validate/validate.js b/week-2/hw2-3/validate/validate.js
new file mode 100644
index 0000000..6ab1488
--- /dev/null
+++ b/week-2/hw2-3/validate/validate.js
@@ -0,0 +1 @@
+var _0x2d80=["\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\x33\x30\x30\x30\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","\x75\x73\x65\x72\x73","\x63\x6F\x6C\x6C\x65\x63\x74\x69\x6F\x6E","\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","\x66\x6F\x72\x6D","\x68\x74\x74\x70\x3A\x2F\x2F","\x68\x6F\x73\x74","\x3A","\x70\x6F\x72\x74","\x2F\x73\x69\x67\x6E\x75\x70","\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","\x63\x6F\x6F\x6B\x69\x65\x73","\x70\x6F\x73\x74","\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","\x6C\x6F\x67","\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\x6C\x6F\x67\x67\x69\x6E\x67\x20\x6F\x75\x74\x21","\x53\x75\x63\x63\x65\x73\x73\x66\x75\x6C\x6C\x79\x20\x6C\x6F\x67\x67\x65\x64\x20\x6F\x75\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\x63\x72\x65\x61\x74\x69\x6E\x67\x20\x6C\x6F\x67\x69\x6E\x20\x73\x65\x73\x73\x69\x6F\x6E\x21","\x53\x75\x63\x63\x65\x73\x73\x66\x75\x6C\x6C\x79\x20\x6C\x6F\x67\x67\x65\x64\x20\x69\x6E","\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","\x66\x30\x33\x63\x64\x64\x62\x30\x35\x36\x34\x35\x35\x65\x63\x33\x62\x61\x66\x62\x30\x65\x30\x39\x32\x66\x30\x33\x33\x39\x30\x30\x61\x31\x61\x63\x66\x33\x31\x38\x36\x35\x38\x30\x66\x38\x36\x38\x37\x37\x64\x33\x34\x39\x35\x66\x31\x63\x34\x31\x38\x37\x35\x36","\x50\x32\x38\x31\x55\x6D\x47\x68\x71\x78\x31\x4E\x69\x47\x71\x4F\x67\x4D\x38\x37","\x63\x6F\x6E\x6E\x65\x63\x74"];var request=require(_0x2d80[0]);var bcrypt=require(_0x2d80[1]);var MongoClient=require(_0x2d80[3])[_0x2d80[2]];var program=require(_0x2d80[4]);var crypto=require(_0x2d80[5]);var reprimand=_0x2d80[6];program[_0x2d80[12]](_0x2d80[15],_0x2d80[16],_0x2d80[17])[_0x2d80[12]](_0x2d80[13],_0x2d80[14],3000)[_0x2d80[12]](_0x2d80[9],_0x2d80[10],_0x2d80[11])[_0x2d80[8]](process[_0x2d80[7]]);MongoClient[_0x2d80[79]](program[_0x2d80[18]],function (_0x9596x7,_0x9596x8){if(_0x9596x7){throw _0x9596x7;} ;var _0x9596x9=_0x9596x8[_0x2d80[20]](_0x2d80[19]);var _0x9596xa=_0x9596x8[_0x2d80[20]](_0x2d80[21]);var _0x9596xb=request[_0x2d80[22]]();request=request[_0x2d80[23]]({"\x6A\x61\x72":_0x9596xb});function _0x9596xc(_0x9596xd){var _0x9596xe=_0x2d80[24];_0x9596xd=_0x9596xd?_0x9596xd:32;var _0x9596xf=_0x2d80[25];for(var _0x9596x10=0;_0x9596x10<_0x9596xd;_0x9596x10++){var _0x9596x11=Math[_0x2d80[28]](Math[_0x2d80[26]]()*_0x9596xe[_0x2d80[27]]);_0x9596xf+=_0x9596xe[_0x2d80[29]](_0x9596x11,_0x9596x11+1);} ;return _0x9596xf;} ;function _0x9596x12(_0x9596x13,_0x9596x14,_0x9596x15,_0x9596x16){_0x9596x9[_0x2d80[37]]({"\x5F\x69\x64":_0x9596x13},function (_0x9596x7,_0x9596x17){_0x2d80[30];if(_0x9596x7){return _0x9596x16(_0x9596x7,null);} ;if(_0x9596x17){if(bcrypt[_0x2d80[32]](_0x9596x14,_0x9596x17[_0x2d80[31]])){if(_0x9596x17[_0x2d80[33]]!=_0x9596x15){_0x9596x16( new Error(_0x2d80[34]));} else {_0x9596x16(null);} ;} else {_0x9596x16( new Error(_0x2d80[35]));} ;} else {_0x9596x16( new Error(_0x2d80[36]));} ;} );} ;function _0x9596x18(_0x9596x13,_0x9596x19,_0x9596x16){_0x2d80[30];var _0x9596x1a;for(var _0x9596x10=0;_0x9596x10<_0x9596x19[_0x2d80[27]];_0x9596x10++){if(_0x9596x19[_0x9596x10][_0x2d80[38]]===_0x2d80[39]){_0x9596x1a=_0x9596x19[_0x9596x10][_0x2d80[40]];} ;} ;if(!_0x9596x1a){_0x9596x16(Error(_0x2d80[41]));return ;} ;_0x9596xa[_0x2d80[37]]({"\x5F\x69\x64":_0x9596x1a},function (_0x9596x7,_0x9596x1b){if(_0x9596x7){return _0x9596x16(_0x9596x7);} ;if(!_0x9596x1b){_0x9596x16( new Error(_0x2d80[42]));return ;} ;if(_0x9596x1b[_0x2d80[43]]!=_0x9596x13){_0x9596x16( new Error(_0x2d80[44]));return ;} ;_0x9596x16(null);} );} ;function _0x9596x1c(_0x9596x1a,_0x9596x16){_0x2d80[30];_0x9596xa[_0x2d80[37]]({"\x5F\x69\x64":_0x9596x1a},function (_0x9596x7,_0x9596x1b){if(_0x9596x7){return _0x9596x16(_0x9596x7);} ;if(_0x9596x1b){_0x9596x16( new Error(_0x2d80[45]));return ;} ;_0x9596x16(null);} );} ;function _0x9596x1d(_0x9596x13,_0x9596x14,_0x9596x15,_0x9596x16){request[_0x2d80[55]](_0x2d80[47]+program[_0x2d80[48]]+_0x2d80[49]+program[_0x2d80[50]]+_0x2d80[51],function (_0x9596x1e,_0x9596x1f,_0x9596x20){if(_0x9596x1e){return _0x9596x16(Error(_0x2d80[52]+program[_0x2d80[48]]+_0x2d80[49]+program[_0x2d80[50]]+_0x2d80[53]+_0x9596x1e.toString()));} ;_0x9596x12(_0x9596x13,_0x9596x14,_0x9596x15,function (_0x9596x7){if(_0x9596x7){return _0x9596x16(_0x9596x7);} ;_0x9596x18(_0x9596x13,_0x9596xb[_0x2d80[54]],_0x9596x16);} );} )[_0x2d80[46]]({"\x75\x73\x65\x72\x6E\x61\x6D\x65":_0x9596x13,"\x70\x61\x73\x73\x77\x6F\x72\x64":_0x9596x14,"\x76\x65\x72\x69\x66\x79":_0x9596x14,"\x65\x6D\x61\x69\x6C":_0x9596x15});} ;function _0x9596x21(_0x9596x13,_0x9596x14,_0x9596x16){request[_0x2d80[55]](_0x2d80[47]+program[_0x2d80[48]]+_0x2d80[49]+program[_0x2d80[50]]+_0x2d80[56],function (_0x9596x1e,_0x9596x1f,_0x9596x20){if(_0x9596x1e){return _0x9596x16(Error(_0x2d80[52]+program[_0x2d80[48]]+_0x2d80[49]+program[_0x2d80[50]]+_0x2d80[53]+_0x9596x1e.toString()));} ;_0x9596x18(_0x9596x13,_0x9596xb[_0x2d80[54]],_0x9596x16);} )[_0x2d80[46]]({"\x75\x73\x65\x72\x6E\x61\x6D\x65":_0x9596x13,"\x70\x61\x73\x73\x77\x6F\x72\x64":_0x9596x14});} ;function _0x9596x22(_0x9596x16){var _0x9596x1a;for(var _0x9596x10=0;_0x9596x10<_0x9596xb[_0x2d80[54]][_0x2d80[27]];_0x9596x10++){if(_0x9596xb[_0x2d80[54]][_0x9596x10][_0x2d80[38]]===_0x2d80[39]){_0x9596x1a=_0x9596xb[_0x2d80[54]][_0x9596x10][_0x2d80[40]];} ;} ;request(_0x2d80[47]+program[_0x2d80[48]]+_0x2d80[49]+program[_0x2d80[50]]+_0x2d80[57],function (_0x9596x1e,_0x9596x1f,_0x9596x20){if(_0x9596x1e){return _0x9596x16(Error(_0x2d80[52]+program[_0x2d80[48]]+_0x2d80[49]+program[_0x2d80[50]]+_0x2d80[53]+_0x9596x1e.toString()));} ;_0x9596x1c(_0x9596x1a,_0x9596x16);} );} ;var _0x9596x13=_0x9596xc(20);var _0x9596x14=_0x9596xc(10);var _0x9596x15=_0x9596xc(10)+_0x2d80[58]+_0x9596xc(5)+_0x2d80[59]+_0x9596xc(3);_0x9596x1d(_0x9596x13,_0x9596x14,_0x9596x15,function (_0x9596x7){if(_0x9596x7){console[_0x2d80[61]](_0x2d80[60]);console[_0x2d80[61]](_0x9596x7[_0x2d80[62]]);return _0x9596x8[_0x2d80[63]]();} ;console[_0x2d80[61]](_0x2d80[64]);_0x9596x22(function (_0x9596x7){if(_0x9596x7){console[_0x2d80[61]](_0x2d80[65]);console[_0x2d80[61]](_0x9596x7[_0x2d80[62]]);return _0x9596x8[_0x2d80[63]]();} ;console[_0x2d80[61]](_0x2d80[66]);_0x9596x21(_0x9596x13,_0x9596x14,function (_0x9596x7){if(_0x9596x7){console[_0x2d80[61]](_0x2d80[67]);console[_0x2d80[61]](_0x9596x7[_0x2d80[62]]);return _0x9596x8[_0x2d80[63]]();} else {console[_0x2d80[61]](_0x2d80[68]);console[_0x2d80[61]](_0x2d80[69]);function _0x9596x23(_0x9596x24,_0x9596x25){var _0x9596x26=_0x2d80[70];var _0x9596x27=crypto[_0x2d80[71]](_0x9596x26,_0x9596x25);var _0x9596x28=_0x9596x27[_0x2d80[74]](_0x9596x24,_0x2d80[72],_0x2d80[73])+_0x9596x27[_0x2d80[75]](_0x2d80[73]);return _0x9596x28;} ;console[_0x2d80[61]](_0x2d80[76]+_0x9596x23(_0x2d80[77],_0x2d80[78]));} ;_0x9596x8[_0x2d80[63]]();} );} );} );} );