Difference between revisions of "WebFund 2014W: Assignment 2"

From Soma-notes
Jump to navigation Jump to search
 
(8 intermediate revisions by the same user not shown)
Line 1: Line 1:
'''This assignment is not yet finalized'''
The solutions to this assignment [http://homeostasis.scs.carleton.ca/~soma/webfund-2014w/adventure-demo-sol.zip are now available].


In this assignment you are to examine, modify, and answer questions regarding the [http://homeostasis.scs.carleton.ca/~soma/webfund-2014w/T5/adventure-demo.nomods.zip adventure-demo] sample node application.  The files of this application are also listed below.  Note that to run this application you'll first need to run <tt>node storeRooms.js</tt> to setup the rooms collection in MongoDB.
In this assignment you are to examine, modify, and answer questions regarding the [http://homeostasis.scs.carleton.ca/~soma/webfund-2014w/T5/adventure-demo.zip adventure-demo] ([http://homeostasis.scs.carleton.ca/~soma/webfund-2014w/T5/adventure-demo.nomods.zip without modules]) sample node application.  The files of this application are also listed below.  Note that to run this application you'll first need to run <tt>node storeRooms.js</tt> to setup the rooms collection in MongoDB.


Please submit your answers as a single zip file called "<username>-comp2406-assign2.zip" (where username is your MyCarletonOne username).  This zip file should unpack into a directory of the same name (minus the .zip extension of course).  This directory should contain:
Please submit your answers as a single zip file called "<username>-comp2406-assign2.zip" (where username is your MyCarletonOne username).  This zip file should unpack into a directory of the same name (minus the .zip extension of course).  This directory should contain:
Line 11: Line 11:
==Questions==
==Questions==


There are ?? points below in three parts.  Answer all questions.
There are 50 points below in two parts.  Answer all questions.


===Part A===
===Part A===
What happens when you change the following lines in <tt>adventure-demo</tt>?  Describe the change in behaviour as if each deletion is the '''only''' change made to the application.  If a runtime error is generated please describe the error and briefly explain why that error was produced. (two points each, ?? total)
#
===Part B===


Describe how to change the code to add the following features/change the following behaviour.  If the changes are small, specify the line(s) to changed.  If the changes are more substantial, you may just list the entire modified file.  These changes are cumulative, so the changes for the third question should take into account those made previously.
Describe how to change the code to add the following features/change the following behaviour.  If the changes are small, specify the line(s) to changed.  If the changes are more substantial, you may just list the entire modified file.  These changes are cumulative, so the changes for the third question should take into account those made previously.


# How can you change this app to list all of the currently logged in users on /users?
# (2 points) Make the secret room accessible by visiting "/secret".
# (4 points) Add a room to the game that is connected to the others so you can enter and exit it as you can the bridge, sickbay, and engineering.
# (4 points) Add a check to make sure that the player's name only contains alphanumeric characters.
# (10 points) Modify the start page (/) so that it lists the other players that are currently playing at the bottom, saying "Currently playing:" on one line and then a comma-separated list of players on the next.  Note that this list should just contain the players with active sessions, not those who have created accounts.  If no players are currently playing this part of the page should be omitted.  Like other data stored by this application, this information should be persistent across server restarts.


===Part C===
===Part B===


# (5 points) What is the difference between the Login and Register button on the initial screen?  Do they work the same way?
# (4 points) What is the key difference between the Start and Register button on the initial screen?  Do they work the same way?
# (4 points) MongoDB's "tables" are collections; they are grouped together into databases.  What MongoDB database is used by this application?  What collections?
# (4 points) MongoDB's "tables" are collections; they are grouped together into databases.  What MongoDB database is used by this application?  What collections?
# (2 points) How long before this app's session cookies expire?  How do you know?
# (2 points) How long before this app's session cookies expire?  How do you know?
Line 32: Line 31:
# (4 points) Why are there three arguments to the app.get()'s, rather than the previous two?  What does the third argument do?
# (4 points) Why are there three arguments to the app.get()'s, rather than the previous two?  What does the third argument do?
# (2 points) What is purpose of the call to <tt>playersCollection.update()</tt> (line 115 of routes/index.js)?
# (2 points) What is purpose of the call to <tt>playersCollection.update()</tt> (line 115 of routes/index.js)?
#  
# (6 points) When does <tt>makeRoomHandler()</tt> run? What does it return?  And when is its single argument accessed?
# The <tt>routes.register()</tt> has multiple nested functions. What do each of them do, and why are they nested the way they are?


==Source==
==Source==
Line 374: Line 372:
title: "Bridge",
title: "Bridge",
description: "You are on the Bridge.  There are big comfy chairs and a big screen here.",
description: "You are on the Bridge.  There are big comfy chairs and a big screen here.",
roomExits: ['sickbay'],
roomExits: ['sickbay']
     },     
     },     
     {
     {
Line 380: Line 378:
title: "Engineering",
title: "Engineering",
description: "You are in Engineering.  There are lots of funny instruments, many smaller screens, and kind of uncomfortable chairs.",
description: "You are in Engineering.  There are lots of funny instruments, many smaller screens, and kind of uncomfortable chairs.",
roomExits: ['sickbay'],
roomExits: ['sickbay']
     },
     },


Line 387: Line 385:
title: "Sickbay",
title: "Sickbay",
description: "You are in Sickbay.  It is in the center of the ship, the safest place there is.  There are lots of comfy beds here and blinky lights.",
description: "You are in Sickbay.  It is in the center of the ship, the safest place there is.  There are lots of comfy beds here and blinky lights.",
roomExits: ['engineering','bridge'],
roomExits: ['engineering','bridge']
     },
     },


Line 394: Line 392:
title: "Secret Room",
title: "Secret Room",
description: "This is a secret room.  How did you get here?",
description: "This is a secret room.  How did you get here?",
roomExits: ['engineering', 'sickbay', 'bridge'],
roomExits: ['engineering', 'sickbay', 'bridge']
     },
     }
];
];



Latest revision as of 14:14, 12 February 2014

The solutions to this assignment are now available.

In this assignment you are to examine, modify, and answer questions regarding the adventure-demo (without modules) sample node application. The files of this application are also listed below. Note that to run this application you'll first need to run node storeRooms.js to setup the rooms collection in MongoDB.

Please submit your answers as a single zip file called "<username>-comp2406-assign2.zip" (where username is your MyCarletonOne username). This zip file should unpack into a directory of the same name (minus the .zip extension of course). This directory should contain:

  • your modified version of adventure-demo (one version with all code changes) and
  • a text file called "answers.txt" or "answers.pdf" at the top level (in text or PDF format, respectively) that contains the answers to the all of the questions, with the first four lines being "COMP 2406 Assignment 2", your name, student number, and the date. For questions requiring code changes (Part B), explain in your answers file which parts of the program were changed. You may wish to format answers.txt in Markdown to improve its appearance.

No other formats will be accepted. Submitting in another format will likely result in your assignment not being graded and you receiving no marks for this assignment. In particular do not submit an MS Word or OpenOffice file as your answers document!

Questions

There are 50 points below in two parts. Answer all questions.

Part A

Describe how to change the code to add the following features/change the following behaviour. If the changes are small, specify the line(s) to changed. If the changes are more substantial, you may just list the entire modified file. These changes are cumulative, so the changes for the third question should take into account those made previously.

  1. (2 points) Make the secret room accessible by visiting "/secret".
  2. (4 points) Add a room to the game that is connected to the others so you can enter and exit it as you can the bridge, sickbay, and engineering.
  3. (4 points) Add a check to make sure that the player's name only contains alphanumeric characters.
  4. (10 points) Modify the start page (/) so that it lists the other players that are currently playing at the bottom, saying "Currently playing:" on one line and then a comma-separated list of players on the next. Note that this list should just contain the players with active sessions, not those who have created accounts. If no players are currently playing this part of the page should be omitted. Like other data stored by this application, this information should be persistent across server restarts.

Part B

  1. (4 points) What is the key difference between the Start and Register button on the initial screen? Do they work the same way?
  2. (4 points) MongoDB's "tables" are collections; they are grouped together into databases. What MongoDB database is used by this application? What collections?
  3. (2 points) How long before this app's session cookies expire? How do you know?
  4. (4 points) Do sessions and user accounts persist across web application restarts? Why or why not?
  5. (4 points) In the POST function for /start, it processes a username and password supplied by the user. What object stores this information in node for our program to access? What line in app.js loads the code to provides the values for this object?
  6. (4 points) Why are there three arguments to the app.get()'s, rather than the previous two? What does the third argument do?
  7. (2 points) What is purpose of the call to playersCollection.update() (line 115 of routes/index.js)?
  8. (6 points) When does makeRoomHandler() run? What does it return? And when is its single argument accessed?

Source

app.js

/**
 * Module dependencies.
 */

var express = require('express');
var routes = require('./routes');
var path = require('path');
var fs = require('fs');

var http = require('https');  // note https, not http!
var MongoStore = require('connect-mongo')(express);

var app = express();

var options = {
  key: fs.readFileSync('keys/comp2406-private-key.pem'),
  cert: fs.readFileSync('keys/comp2406-cert.pem')
};

// all environments
app.set('port', process.env.PORT || 3000);
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'jade');
app.use(express.favicon());
app.use(express.logger('dev'));
app.use(express.json());
app.use(express.urlencoded());
app.use(express.methodOverride());

app.use(express.cookieParser('COMP 2406 adventure demo!'));
app.use(express.session({
    cookie: {maxAge: 60000 * 20} // 20 minutes
    , secret: "Shh... I'm a secret"
    , store: new MongoStore({db: "adventure-demo"})
}));

app.use(app.router);
app.use(express.static(path.join(__dirname, 'public')));

// set NODE_ENV environment variable to change from development mode
if ('development' == app.get('env')) {
  app.use(express.errorHandler());
}

app.get('/', routes.redirectLoggedIn, routes.index);
app.post("/register", routes.register);
app.post("/start", routes.start);
app.post("/quit", routes.quit);

var createRooms = function() {
    var i, theRoom;

    routes.getRooms().toArray(
	function(err, docs) {
	    if (err) {
		throw "Couldn't find active room list";
	    }
	    
	    var activeRooms = docs[0].activeRooms;

	    activeRooms.forEach(function(roomName) {
		console.log('Creating room: ' + roomName);
		app.get('/' + roomName,
			routes.makeRoomHandler(roomName));
	    });
	}
    );
}
routes.connectToDBs(createRooms);

http.createServer(options, app).listen(app.get('port'), function(){
  console.log('Express server listening on port ' + app.get('port') +
	      ' in ' + app.get('env') + ' mode.');
});


routes/index.js

var bcrypt = require("bcrypt");
var mc = require('mongodb').MongoClient;
var playersCollection;
var roomsCollection;

var connectToDBs = function(callback) {
    mc.connect('mongodb://localhost/adventure-demo', function(err, db) {
	if (err) {
	    throw err;
	}
	
	playersCollection = db.collection('players');
	roomsCollection = db.collection('rooms');

	callback();
    });
}

var index = function(req, res){
    res.render('index', { title: 'COMP 2406 Adventure Demo',
			  error: req.query.error });
}

var redirectLoggedIn = function(req, res, next) {
    if (req.session.player) {
        res.redirect("/" + req.session.player.room);
    } else {
        next();
    }
}

var redirectNotLoggedIn = function(req, res, next) {
    if (req.session.player) {
        next();
    } else {
        res.redirect("/");
    }
}

var register = function(req, res) {
    var playername = req.body.playername;
    var password = req.body.password;

    var addPlayer = function(err, players) {
	if(players.length!=0){
	    res.redirect("/?error=player already exists");	
	    return;
	}
	
	//generate a salt, with 10 rounds (2^10 iterations)
	bcrypt.genSalt(10, function(err, salt) {
	    bcrypt.hash(password, salt, function(err, hash) {
      		var newPlayer = {
      		    playername: playername,
      		    password: hash,
		    room: "bridge"
      		};
      		
      		playersCollection.insert(newPlayer, function(err, newPlayers){
		    if (err) {
			throw err;
		    } else {
      			res.render('registered', 
				   { playername: newPlayers[0].playername });
		    }
      		});    
	    });
	});	
    };	
    
    playersCollection.find({playername: playername}).toArray(addPlayer);
}

var start = function(req, res){
    var playername = req.body.playername;
    var password = req.body.password;

    playersCollection.findOne({playername: playername}, function(err, player){
	
	if (err || !player){
	    req.session.destroy(function(err) {
		res.redirect("/?error=invalid playername or password");	
	    });
	    return;
	}
	
	bcrypt.compare(password, player.password, function(err, authenticated){
	    if(authenticated){
		req.session.player = player;
		delete req.session.player._id;
		res.redirect("/" + player.room);
	    } else {
		req.session.destroy(function(err) {
		    res.redirect("/?error=invalid playername or password");
		});
	    }
	});
    });
}

var quit = function(req, res){
    req.session.destroy(function(err){
	if(err){
            console.log("Error: %s", err);
	}
	res.redirect("/");
    });	
}

var makeRoomHandler = function(roomName) {
    handler = function(req, res) {
	if (req.session.player) {
	    var player = req.session.player;
	    player.room = roomName;
	    playersCollection.update({"playername": player.playername},
				     player, function(err, count) {
					 if (err) {
					     console.log(
						 "Couldn't save player state");
					 }
				     });
	    roomsCollection.findOne(
		{name: roomName},
		function(err, room) {
		    if (err || !room) {
			throw "Couldn't find room " + roomName;
		    }
		    res.render("room.jade", room);
		}
	    );
	} else {
            res.redirect("/");
	}
    }
    return handler;
}

var getRooms = function() {
    return roomsCollection.find({name: "roomList"});
}

exports.index = index;
exports.redirectLoggedIn = redirectLoggedIn;
exports.redirectNotLoggedIn = redirectNotLoggedIn;
exports.register = register;
exports.start = start;
exports.quit = quit;
exports.makeRoomHandler = makeRoomHandler;
exports.getRooms = getRooms;
exports.connectToDBs = connectToDBs;


views/layout.jade

doctype html
html
  head
    title= title
    script(src='/vendor/jquery/jquery.js')
    script('vendor/bootstrap/dist/js/bootstrap.js')
    link(rel='stylesheet', href='/vendor/bootstrap/dist/css/bootstrap.css')
    link(rel='stylesheet', href='/stylesheets/style.css')
    block header  
  body
    block content


views/index.jade

extends layout

block header
  script(src='/javascripts/home.js')

block content
  h1= title
  - if(error)
    div.alert-error #{error}
  p Please log in
  div
    form(action="/start", method="post")
        div.control-group.input-append
            input(type="text", name="playername")
            label.add-on(for="playername") Player Name
        div.control-group.input-append
            input(type="password", name="password")
            label.add-on(for="password") Password
            
        button(type="submit") Start
        button#register(type="button") Register


views/registered.jade

extends layout

block content
  h1 Registration Successful	
  p You have successfully registered player #{playername}
  form(action="/", method="get")
        button(type="submit") Return


views/room.jade

extends layout

block content
  h1= title
  p #{description}
  p Go to:
  ul
    each theExit in roomExits
      li
        a(href= theExit) #{theExit}
  form(action="/quit", method="post")
     button(type="submit") Quit


public/javascripts/home.js

$(function(){
	$("#register").on("click",function(){
		var $form = $("form");
		$form.attr("action","/register");
		$form.submit();
	});
});


storeRooms.js

// storeRooms.js

var mc = require('mongodb').MongoClient;

var rooms = [
    
    {
	name: "roomList",
	activeRooms: ['bridge', 'sickbay', 'engineering']
    },
    {   name: "bridge",
	title: "Bridge",
	description: "You are on the Bridge.  There are big comfy chairs and a big screen here.",
	roomExits: ['sickbay']
    },    
    {
	name: "engineering",
	title: "Engineering",
	description: "You are in Engineering.  There are lots of funny instruments, many smaller screens, and kind of uncomfortable chairs.",
	roomExits: ['sickbay']
    },

    {
	name: "sickbay",
	title: "Sickbay",
	description: "You are in Sickbay.  It is in the center of the ship, the safest place there is.  There are lots of comfy beds here and blinky lights.",
	roomExits: ['engineering','bridge']
    },

    {
	name: "secret",
	title: "Secret Room",
	description: "This is a secret room.  How did you get here?",
	roomExits: ['engineering', 'sickbay', 'bridge']
    }
];

mc.connect('mongodb://localhost/adventure-demo', function(err, db) {
    if (err) {
	throw err;
    }
    
    var roomsCollection = db.collection('rooms');

    roomsCollection.drop(function(err, count) {
	if (err) {
	    console.log("No collection to drop.");
	} else {
	    console.log("room collection dropped.");
	}
	roomsCollection.insert(rooms, function(err, rooms) {
	    if (err) {
		throw err;
	    }

	    rooms.forEach(function(room) {
		console.log("Added " + room.name);
	    });
	    db.close();
	});
    });
});