Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP

Comparing changes

Choose two branches to see what's changed or to start a new pull request. If you need to, you can also compare across forks.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also compare across forks.
base fork: akokobobo/crowdscribe
base: 0e04640cc6
...
head fork: akokobobo/crowdscribe
compare: e388925954
Checking mergeability… Don't worry, you can still create the pull request.
  • 2 commits
  • 11 files changed
  • 0 commit comments
  • 1 contributor
Commits on Jun 30, 2011
Adrian Fixed type 2eb4133
Commits on Jul 08, 2011
Adrian Added basic structure to models. Added bunch of code for rounds and p…
…osts. Created some quick test cases which mostly fail.
e388925
View
5 app.js
@@ -1,7 +1,7 @@
/**
* Module dependencies.
*/
-
+require('./nativeOverloads.js');
var express = require('express');
var redisModule = require('redis');
@@ -9,6 +9,9 @@ var redis = redisModule.createClient();
redis.select(1);
this.redis = redis;
+var test = require('./tests.js');
+test.start();
+
var app = module.exports = express.createServer();
// Configuration
View
57 db.js
@@ -1,43 +1,28 @@
-var app = require('../app');
+var app = require('./app.js');
var redis = app.redis;
-this.save = function(model, cb) {
- if(model.id() === null) {
- getIndex(model.name(), function(index) {
- model.setId(index);
- save(model, cb);
- });
- } else {
- save(model, cb);
- }
-}
-
-this.find = function() { }
-
-function save(model, cb) {
- redis.HMSET(
- model.name() + ':' + model.id(), //Key
- model.attr(), // Value
- cb //callback
- );
-}
+this.store = store;
+this.getUniqueId = getUniqueId;
-function getIndex(modelName, cb) {
- getOrCreateIndex(modelName, function(index) {
- updateIndexCount(modelName, index, cb);
+function getUniqueId(name, cb) {
+ var key = name+':index';
+ redis.get(key, function(e, r) {
+ var index = 1;
+ if(r !== null)
+ index = parseInt(r.toString()) + 1;
+
+ //record index, no worries about callback. Everything should go well ;)
+ store(key, index);
+ cb(index);
});
}
-function getOrCreateIndex(modelName, cb) {
- redis.get('index:' + modelName, function(e, r) {
- if(r !== null) {
- cb(parseInt(r.toString()) + 1);
- } else {
- cb(1);
- }
- });
+function store(key, value, cb) {
+ var valueType = (typeof value).toLowerCase();
+ if((typeof cb).toLowerCase() !== 'function') cb = function(){};
+
+ if(valueType === 'string' || valueType === 'number')
+ redis.set(key, value, function(e, r){ cb(r) });
+ else
+ redis.HMSET(key, value, function(e, r){ cb(r) });
}
-
-function updateIndexCount(modelName, index, cb) {
- redis.set("index:" + modelName, index, function() { cb(index); });
-}
View
66 models/base.js
@@ -1,49 +1,59 @@
+var db = require('../db.js');
/**
* Extends Base to model parameter.
*
*/
-this.extend = function(model, options) {
- var modelPrototype = model.prototype;
+module.exports.extend = function(model, options) {
+ //find attributes of the model
+ var modelAttrs = options['attrs'] || [];
- model.prototype = new Base(options);
- //attrs no longer needed
+ //key no longer need
delete options['attrs'];
+ //Copy base prototype into model
+ for(var method in Base.prototype)
+ model.prototype[method] = Base.prototype[method];
+
+ //Concat attributes specified by model and the Base _attr
+ if(modelAttrs && modelAttrs.length)
+ model.prototype._attrs = model.prototype._attrs.concat(modelAttrs).unique();
+
//overwrite all Base prototype functions defined by model.
- for(var i in modelPrototype) {
- model.prototype[i] = modelPrototype[i];
- }
+ for(var i in options)
+ model.prototype[i] = options[i];
}
-function Base(options) {
- if(options['attrs'])
- this._createAttrAccesors(options['attrs']);
-}
+function Base() {}
-Base.prototype._attrs = [];
+Base.prototype._attrs = ['id', 'name'];
Base.prototype.attributes = function(_attributes) {
+ if(_attributes === undefined) _attributes = {};
+
var attributes = {};
for(var i = 0; i < this._attrs.length; i++) {
- attributes[i] = this[i](_attributes[i]);
+ var attrName = this._attrs[i];
+ //create accessor if not present
+ if(typeof this[attrName] !== 'function') {
+ this._createAttrAccessor(attrName);
+ }
+
+ attributes[attrName] = this[attrName](_attributes[attrName]);
}
return attributes;
}
-Base.prototype._createAttrAccesors = function(attrs) {
+Base.prototype._createAttrAccessor = function(attributeName) {
var context = this;
- for(var i = 0; i < attrs.length; i++) {
- var attrName = attrs[i];
- //Creating accessor function
- this[attrName] = (function() {
+ if(typeof this[attributeName] !== 'function') {
+ this[attributeName] = (function() {
var _value;
return function(value) {
if(value !== undefined) _value = value;
return _value;
}
})();
- this._attrs.push(attrName);
}
}
@@ -52,6 +62,22 @@ Base.prototype.create = function() {
}
-Base.prototype.save = function() {
+Base.prototype.save = function(cb) {
+ var model = this;
+ if(this.id() === null || this.id() === undefined) {
+ //get unique id
+ db.getUniqueId(this.name(), function(id) {
+ model.id(id);
+
+ //recall model save method
+ model.save(cb);
+ });
+ } else {
+ if ((typeof cb).toLowerCase() !== 'function') cb = function() {};
+
+ db.store(this.name() + ':' + this.id(), this.attributes(), function(success) {
+ cb(success);
+ });
+ }
}
View
2  models/playerList.js
@@ -2,7 +2,7 @@
/**
* Creates an empty player list
*/
-this.create = function(maxPlayers) {
+module.exports.create = function(maxPlayers) {
return new PlayerList(maxPlayers);
}
View
99 models/post.js
@@ -1,99 +1,48 @@
-var db = require('../db');
-var MODEL_NAME = 'Post';
+var Base = require('./base.js');
/**
* Creates and Saves a post.
* @param {String} message
- * @param {Number} userId
- * @param {Number} storyId
+ * @param {Number} user id
* @param {Function} cb the new post is passed as argument to callback
*/
-this.create = function(message, userId, storyId, cb) {
- var post = new Post({message: message, userId: userId, storyId: storyId});
- post.save(function() {
+module.exports.create = function(message, user, cb) {
+ var post = new Post(message, user);
+ post.save(function(success) {
cb(post);
});
};
-/*
-function Post(attributes) {
- var _id = attributes['id'] || null;
- var _message = attributes['message'] || null;
- var _userId = attributes['userId'] || null;
- var _storyId = attributes['storyId'] || null;
- var _voteCount = 0;
-
- function id() { return id; }
- function message() { return _message; }
- function userId() { return _userId; }
- function storyId() { return _storyId; }
- function voteCount() { return _voteCount; }
- function name() { return MODEL_NAME; }
-
- var _submitedVotes = {};
- function vote(uId) {
- if(!hasVoted(uid)) {
- _submitedVotes[uid] = true;
- _voteCount++;
- }
- }
-
- //returns true is user id is not in the list of people who have already voted.
- //and user id is not the creator of the post
- function hasVoted(uid) {
- return (_submitedVotes[uid] === undefined && _submitedVotes[uid] !== userId());
- }
-
-
- function save(cb) {
- db.save(this, cb);
- }
-
- function attrs() {
- return {
- id: id(),
- userId: userId(),
- message: message(),
- storyId: storyId(),
- voteCount: voteCount()
- }
- }
-
- //Public Interface
- return {
- id: id,
- userId: userId,
- message: message,
- storyId: storyId,
- name: name,
- vote: vote,
- save: save,
- attrs: attrs,
- //to be used only when saving model
- setID: function(__id) { _id = __id; }
- }
-}*/
-function Post(message, user) {
- this.attributes({name: 'Post', message: message, user: user});
+function Post(message, userId) {
+ //set default attributes
+ this.attributes({name: 'Post', message: message, userId: userId});
}
Base.extend(Post, {
- attrs: ['user', 'story', 'message', 'name'],
+ attrs: ['userId', 'storyId', 'message'],
//Private Variables
_submitedVotes: {},
_voteCount: 0,
-
- vote: function(user) {
- if(!this.hasVoted(user)) {
- this._submitedVotes[user.id()] = user;
+ voteCount: function() { return this._voteCount; },
+ vote: function(userId) {
+ if(!this.hasVoted(userId)) {
+ this._submitedVotes[userId] = userId;
this._voteCount++;
+ return true;
}
+ return false;
},
- hasVoted: function(user) {
- return (_submitedVotes[user.id()] !== undefined && _submitedVotes[user.id()].id() !== this.user().id());
+ voterIds: function() {
+ var ids = [];
+ for(var id in this._submitedVotes) ids.push(parseInt(id));
+ return ids;
+ },
+ hasVoted: function(userId) {
+ if(userId === this.userId()) return true;
+ if(this._submitedVotes[userId] !== undefined) return true;
+ return false;
}
});
-
View
44 models/postCollection.js
@@ -0,0 +1,44 @@
+var Base = require('./base.js');
+var Post = require('./post.js');
+
+exports.create = function(cb) {
+ var postCollection = new PostCollection();
+ postCollection.save(function(success) {
+ cb(postCollection);
+ });
+}
+
+function PostCollection() {
+ this.attributes({name: 'PostCollection', count: 0});
+}
+
+Base.extend(PostCollection, {
+ attrs: ['count', 'postIds'],
+ _userPosts: {},
+ add: function(message, userId, cb) {
+ if(!this._hasPosted(userId)) {
+ var pcContext = this;
+ Post.create(message, userId, function(post) {
+ pcContext._userPosts[userId] = post;
+ cb(true);
+ });
+ } else cb(false);
+ },
+ find: function(postId) {
+ for(var i in this._userPosts) {
+ if(this._userPosts[i].id() == postId) return this._userPosts[i];
+ }
+ return null;
+ },
+ postIds: function() {
+ var postIds = [];
+ for(var i in this._userPosts) {
+ postIds.push(this._userPosts[i].id());
+ }
+ return postIds;
+ },
+ _hasPosted: function(userId) {
+ if(this._userPosts[userId] !== undefined) return true;
+ return false;
+ }
+});
View
121 models/roundSession.js
@@ -0,0 +1,121 @@
+var Base = require('./base.js');
+
+function now() { return (new Date()).getTime(); }
+
+module.exports.create = function(maxRounds, cb) {
+ cb(new RoundSession(maxRounds));
+}
+
+var STATE = {
+ IDLE: 1,
+ ROUND_STARTING: 2,
+ WAITING_FOR_POSTS: 3,
+ WAITING_FOR_VOTES: 4
+}
+
+var five = (5).seconds();
+var ten = (10).seconds();
+var thirty = (30).seconds();
+var minute = (30).minutes();
+var time = ten;
+var TIMERS = {
+ ROUND_STARTING: time,
+ POST: time, //1minute
+ VOTE: time //1minute
+};
+
+function RoundSession(maxRounds) {
+ this.attributes({max: maxRounds, current: 1});
+}
+
+
+Base.extend(RoundSession, {
+ attrs: ['max', 'current'],
+ _state: STATE.IDLE,
+ _update: function() {
+ //In case any of the state has expired, it will fall through the next case.
+ switch(this._state) {
+ case STATE.ROUND_STARTING:
+ //set round to 'waiting for posts' if 'round starting' has expired
+ if(this._roundStartingExpired())
+ this._setState(STATE.WAITING_FOR_POSTS);
+ else
+ break;
+ case STATE.WAITING_FOR_POSTS:
+ if(this._waitingForPostExpired())
+ this._setState(STATE.WAITING_FOR_VOTES);
+ else
+ break;
+ case STATE.WAITING_FOR_VOTES:
+ if(this._waitingForVoteExpired())
+ this._roundEnded();
+ else
+ break;
+ default:
+ break;
+ }
+ },
+ _roundEnded: function() {
+ this._setState(STATE.IDLE);
+ this.currentRound(this.currentRound() + 1);
+ },
+ _roundStartingExpired: function() { now() >= this._roundExpiration; },
+ _waitingForPostExpired: function() { now() >= this._postExpiration; },
+ _waitingForVoteExpired: function() { now() >= this._voteExpiration; },
+ _roundExpiration: 0,
+ _postExpiration: 0,
+ _voteExpiration: 0,
+ _calculateExpirations: function() {
+ var timeNow = now();
+ this._roundExpiration = timeNow + TIMERS.ROUND_STARTING;
+ this._postExpiration = this._roundExpiration + TIMERS.POST;
+ this._voteExpiration = this._postExpiration + TIMERS.VOTE;
+
+ },
+ //Returns remaining timer (in ms) of the current session
+ sessionEndsIn: function() {
+ var timer = null;
+ var timeNow = now();
+ switch(this._state) {
+ case STATE.ROUND_STARTING:
+ timer = this._roundExpiration - timeNow;
+ break;
+ case STATE.WAITING_FOR_POSTS:
+ timer = this._postExpiration - timeNow;
+ break;
+ case STATE.WAITING_FOR_VOTES:
+ timer = this._voteExpiration - timeNow;
+ break;
+ default:
+ break;
+ }
+ return timer;
+ },
+ start: function() {
+ //can only start if state is idle.
+ if(this.isIdle()) {
+ this._setState(STATE.ROUND_STARTING);
+ this._calculateExpirations();
+ }
+ },
+ state: function() { this._update(); return this._state; },
+ _setState: function(state) {
+ this._state = state;
+ },
+ isIdle: function() {
+ return this.state() === STATE.IDLE;
+ },
+ isStarting: function() {
+ return this.state() === STATE.ROUND_STARTING;
+ },
+ isWaitingForPosts: function() {
+ return this.state() === STATE.WAITING_FOR_POSTS;
+ },
+ isWaitingForVotes: function() {
+ return this.state() === STATE.WAITING_FOR_VOTES;
+ },
+ isOver: function() {
+ return this.isIdle() && this.currentRound() > this.maxRounds();
+ },
+ save: function() { /*This model does not save*/ }
+});
View
2  models/story.js
@@ -5,7 +5,7 @@ var MAX_MAX_PLAYERS = 20;
var defaultFirstPost = 'It was a dark and stormy night.';
var POST_CHAR_LIMIT = 500;
-this.create = function(maxPlayers, firstPost, name) {
+module.exports.create = function(maxPlayers, firstPost, name) {
}
View
13 models/user.js
@@ -0,0 +1,13 @@
+var Base = require('./base.js');
+
+module.exports.create = function() {
+
+}
+
+function User() {
+
+}
+
+Base.extend(User, {
+ attrs: ['username', 'password', 'token']
+});
View
25 nativeOverloads.js
@@ -0,0 +1,25 @@
+//Array Prototype
+
+Array.prototype.unique = function() {
+ for(var i = 0; i < this.length; i++) {
+ var current = this[i];
+ for(var j = i + 1; j < this.length; j++) {
+ var next = this[j];
+ if(current === next) {
+ this.splice(j, 1);
+ j--;
+ }
+ }
+ }
+ return this;
+}
+
+//Number Prototype
+
+Number.prototype.minutes = Number.prototype.minute = function () {
+ return this.seconds() * 60;
+}
+
+Number.prototype.seconds = Number.prototype.second = function () {
+ return this * 1000;
+}
View
88 tests.js
@@ -0,0 +1,88 @@
+var assert = require('assert');
+var redis = require('./app.js').redis;
+var Post = require('./models/post.js');
+var RoundSession = require('./models/roundSession.js');
+
+module.exports.start = function() {
+ testPosts(function() {
+ testRoundSession(function() {
+ title("TESTS FINISHED");
+ });
+ });
+
+}
+
+function testPosts(done) {
+ title("Testing POSTS...");
+ Post.create("Hello", 123, function(post) {
+ ok(post.id() > 0, "Id is " + post.id() + " > 0");
+ equal(post.message(), "Hello", "Message passed");
+ equal(post.userId(), 123, "UserId passed");
+ ok(post.vote(123) === false, "Post Owner Could not vote");
+ ok(post.vote(124), "Some other user could vote");
+ ok(post.vote(124) === false, "Same User Cannot vote twice");
+ equal(post.voteCount(), 1, "Vote count = 1");
+ equal(post.voterIds().length, 1, "Correct Amount of voters");
+ ok(post.voterIds()[0] === 124, "First voter is correct");
+
+ done();
+ });
+}
+
+function testRoundSession(done) {
+ title("Testing RoundSession...");
+ var maxRounds = 2;
+ RoundSession.create(maxRounds, function(round) {
+ equal(round.max(), maxRounds, "Max rounds " + round.max());
+ equal(round.current(), 1, "Current round is 1");
+ ok(round.isIdle(), "Round is IDLE");
+ round.start();
+ ok(round.isStarting(), "Round is Starting");
+ title("Session Ends in " + round.sessionEndsIn());
+ setTimeout(function() {
+ ok(round.isWaitingForPosts(), "Waiting for Posts");
+ title("Session Ends in " + round.sessionEndsIn());
+ setTimeout(function() {
+ ok(round.isWaitingForVotes(), "Waiting for Votes");
+ title("Session Ends in " + round.sessionEndsIn());
+
+ setTimeout(function() {
+ ok(round.isOver(), "All rounds are over");
+
+
+ done();
+ }, round.sessionEndsIn() + 10);
+
+ }, round.sessionEndsIn() + 10);
+ //safe milisecond
+ }, round.sessionEndsIn() + 10);
+
+ })
+}
+
+
+//SOME UTILITY FUNCTIONS
+function title(message) {
+ console.log(message + "\n");
+}
+
+function passTest(message) {
+ console.log('+\t' + message);
+}
+
+function failTest(message) {
+ console.log('-FAIL\t' + message);
+}
+
+function ok(test, message) {
+ if(test === true) {
+ passTest(message);
+ } else {
+ failTest(message);
+ }
+}
+
+function equal(actual, expected, message) {
+ if(actual === expected) passTest(message);
+ else failTest("Failed '" + message +"' Expected: " + expected + " Got: " + actual);
+}

No commit comments for this range

Something went wrong with that request. Please try again.