remove lib/ and coffee-script compilation step, also remove "up" for development. Now only src/, coffee-script compiles on run-time, and will need to restart server each change

This commit is contained in:
Tyler Renelle
2012-11-15 10:49:51 -05:00
parent 5310bb0908
commit 6c2c5c55d8
11 changed files with 7 additions and 1293 deletions

View File

@@ -1,215 +0,0 @@
// Generated by CoffeeScript 1.4.0
module.exports = {
defaultTasks: [
{
type: 'habit',
text: '1h Productive Work',
notes: '<u>Habits: Constantly Track</u><br/>For some habits, it only makes sense to <b>gain</b> points (like this one).',
value: 0,
up: true,
down: false
}, {
type: 'habit',
text: 'Eat Junk Food',
notes: 'For others, it only makes sense to <b>lose</b> points',
value: 0,
up: false,
down: true
}, {
type: 'habit',
text: 'Take The Stairs',
notes: 'For the rest, both + and - make sense (stairs = gain, elevator = lose)',
value: 0,
up: true,
down: true
}, {
type: 'daily',
text: '1h Personal Project',
notes: '<u>Dailies: Complete Once a Day</u><br/>At the end of each day, non-completed Dailies dock you points.',
value: 0,
completed: false
}, {
type: 'daily',
text: 'Exercise',
notes: "If you are doing well, they turn green and are less valuable (experience, gold) and less damaging (HP). This means you can ease up on them for a bit.",
value: 3,
completed: false
}, {
type: 'daily',
text: '45m Reading',
notes: 'But if you are doing poorly, they turn red. The worse you do, the more valuable (exp, gold) and more damaging (HP) these goals become. This encourages you to focus on your shortcomings, the reds.',
value: -10,
completed: false
}, {
type: 'todo',
text: 'Call Mom',
notes: "<u>Todos: Complete Eventually</u><br/>Non-completed Todos won't hurt you, but they will become more valuable over time. This will encourage you to wrap up stale Todos.",
value: -3,
completed: false
}, {
type: 'reward',
text: '1 Episode of Game of Thrones',
notes: '<u>Rewards: Treat Yourself!</u><br/>As you complete goals, you earn gold to buy rewards. Buy them liberally - rewards are integral in forming good habits.',
value: 20
}, {
type: 'reward',
text: 'Cake',
notes: 'But only buy if you have enough gold - you lose HP otherwise.',
value: 10
}
],
tourSteps: [
{
element: "#avatar",
title: "Welcome to HabitRPG",
content: "Welcome to HabitRPG, a habit-tracker which treats your goals like a Role Playing Game."
}, {
element: "#bars",
title: "Acheive goals and level up",
content: "As you accomplish goals, you level up. If you fail your goals, you lose hit points. Lose all your HP and you die."
}, {
element: "ul.habits",
title: "Habits",
content: "Habits are goals that you constantly track.",
placement: "bottom"
}, {
element: "ul.dailys",
title: "Dailies",
content: "Dailies are goals that you want to complete once a day.",
placement: "bottom"
}, {
element: "ul.todos",
title: "Todos",
content: "Todos are one-off goals which need to be completed eventually.",
placement: "bottom"
}, {
element: "ul.rewards",
title: "Rewards",
content: "As you complete goals, you earn gold to buy rewards. Buy them liberally - rewards are integral in forming good habits.",
placement: "bottom"
}, {
element: "ul.habits li:first-child",
title: "Hover over comments",
content: "Different task-types have special properties. Hover over each task's comment for more information. When you're ready to get started, delete the existing tasks and add your own.",
placement: "right"
}
],
items: {
unlockedMessage: {
title: "Item Store Unlocked",
content: "Congradulations, you have unlocked the Item Store! You can now buy weapons, armor, potions, etc. Read each item's comment for more information."
},
weapon: [
{
type: 'weapon',
index: 0,
text: "Sword 1",
icon: "item-sword1",
notes: 'Training weapon.',
value: 0
}, {
type: 'weapon',
index: 1,
text: "Sword 2",
icon: 'item-sword2',
notes: 'Increases experience gain by 3%.',
value: 20
}, {
type: 'weapon',
index: 2,
text: "Axe",
icon: 'item-axe',
notes: 'Increases experience gain by 6%.',
value: 30
}, {
type: 'weapon',
index: 3,
text: "Morningstar",
icon: 'item-morningstar',
notes: 'Increases experience gain by 9%.',
value: 45
}, {
type: 'weapon',
index: 4,
text: "Blue Sword",
icon: 'item-bluesword',
notes: 'Increases experience gain by 12%.',
value: 65
}, {
type: 'weapon',
index: 5,
text: "Red Sword",
icon: 'item-redsword',
notes: 'Increases experience gain by 15%.',
value: 90
}, {
type: 'weapon',
index: 6,
text: "Golden Sword",
icon: 'item-goldensword',
notes: 'Increases experience gain by 18%.',
value: 120
}
],
armor: [
{
type: 'armor',
index: 0,
text: "Cloth Armor",
icon: 'item-clotharmor',
notes: 'Training armor.',
value: 0
}, {
type: 'armor',
index: 1,
text: "Leather Armor",
icon: 'item-leatherarmor',
notes: 'Decreases HP loss by 3%.',
value: 30
}, {
type: 'armor',
index: 2,
text: "Chain Mail",
icon: 'item-mailarmor',
notes: 'Decreases HP loss by 6%.',
value: 45
}, {
type: 'armor',
index: 3,
text: "Plate Mail",
icon: 'item-platearmor',
notes: 'Decreases HP loss by 9%.',
value: 65
}, {
type: 'armor',
index: 4,
text: "Red Armor",
icon: 'item-redarmor',
notes: 'Decreases HP loss by 12%.',
value: 90
}, {
type: 'armor',
index: 5,
text: "Golden Armor",
icon: 'item-goldenarmor',
notes: 'Decreases HP loss by 15%.',
value: 120
}
],
potion: {
type: 'potion',
text: "Potion",
notes: "Recover 15 HP",
value: 25,
icon: 'item-flask'
},
reroll: {
type: 'reroll',
text: "Re-Roll",
icon: 'favicon',
notes: "Resets your tasks. When you're struggling and everything's red, use for a clean slate.",
value: 0
}
}
};

View File

@@ -1,88 +0,0 @@
// Generated by CoffeeScript 1.4.0
var dayMapping, moment;
moment = require('moment');
module.exports.daysBetween = function(a, b) {
return Math.abs(moment(a).sod().diff(moment(b).sod(), 'days'));
};
module.exports.dayMapping = dayMapping = {
0: 'su',
1: 'm',
2: 't',
3: 'w',
4: 'th',
5: 'f',
6: 's',
7: 'su'
};
module.exports.viewHelpers = function(view) {
view.fn('taskClasses', function(type, completed, value, repeat) {
var classes;
classes = type;
if (completed || (repeat && repeat[dayMapping[moment().day()]] === false)) {
classes += " completed";
}
switch (false) {
case !(value < -8):
classes += ' color-worst';
break;
case !(value >= -8 && value < -5):
classes += ' color-worse';
break;
case !(value >= -5 && value < -1):
classes += ' color-bad';
break;
case !(value >= -1 && value < 1):
classes += ' color-neutral';
break;
case !(value >= 1 && value < 5):
classes += ' color-good';
break;
case !(value >= 5 && value < 10):
classes += ' color-better';
break;
case !(value >= 10):
classes += ' color-best';
}
return classes;
});
view.fn("percent", function(x, y) {
if (x === 0) {
x = 1;
}
return Math.round(x / y * 100);
});
view.fn("round", function(num) {
return Math.round(num);
});
view.fn("gold", function(num) {
if (num) {
return num.toFixed(1).split('.')[0];
} else {
return "0";
}
});
view.fn("silver", function(num) {
if (num) {
return num.toFixed(1).split('.')[1];
} else {
return "0";
}
});
view.fn("money", function(num) {
if (num) {
return num.toFixed(2);
} else {
return "0.00";
}
});
view.fn("lessThan", function(a, b) {
return a < b;
});
return view.fn("tokens", function(money) {
return money / 0.25;
});
};

View File

@@ -1,333 +0,0 @@
// Generated by CoffeeScript 1.4.0
var content, derby, get, helpers, moment, ready, schema, scoring, view, _, _ref;
derby = require('derby');
_ref = derby.createApp(module), get = _ref.get, view = _ref.view, ready = _ref.ready;
derby.use(require('derby-ui-boot'));
derby.use(require('../../ui'));
moment = require('moment');
content = require('./content');
scoring = require('./scoring');
schema = require('./schema');
helpers = require('./helpers');
helpers.viewHelpers(view);
_ = require('underscore');
get('/:uid?', function(page, model, _arg, next) {
var req, sess, uid;
uid = _arg.uid;
if (uid) {
if (require('derby-auth/node_modules/guid').isGuid(uid)) {
return page.redirect('/users/' + uid);
} else {
return next();
}
}
req = page._res.req;
if (req.headers['x-forwarded-proto'] !== 'https' && process.env.NODE_ENV === 'production') {
return page.redirect('https://' + req.headers.host + req.url);
}
sess = model.session;
if (sess.loggedIn) {
model.set('_loggedIn', true);
}
model.set('_userId', sess.userId);
return model.subscribe("users." + sess.userId, function(err, user) {
model.ref('_user', user);
user.setNull('balance', 2);
model.set('_items', {
armor: content.items.armor[parseInt(user.get('items.armor')) + 1],
weapon: content.items.weapon[parseInt(user.get('items.weapon')) + 1],
potion: content.items.potion,
reroll: content.items.reroll
});
model.refList("_habitList", "_user.tasks", "_user.habitIds");
model.refList("_dailyList", "_user.tasks", "_user.dailyIds");
model.refList("_todoList", "_user.tasks", "_user.todoIds");
model.refList("_completedList", "_user.tasks", "_user.completedIds");
model.refList("_rewardList", "_user.tasks", "_user.rewardIds");
_.each(['habitIds', 'dailyIds', 'todoIds', 'rewardIds'], function(path) {
return user.set(path, _.uniq(user.get(path), true));
});
model.fn('_user._tnl', '_user.stats.lvl', function(lvl) {
return (lvl * 100) / 5;
});
return page.render();
});
});
ready(function(model) {
var setupSortable, step, tour, type, _i, _j, _len, _len1, _ref1, _ref2;
scoring.setModel(model);
$('[rel=tooltip]').tooltip();
$('[rel=popover]').popover();
model.on('set', '*', function() {
$('[rel=tooltip]').tooltip();
return $('[rel=popover]').popover();
});
if (!(model.get('_mobileDevice') === true)) {
setupSortable = function(type) {
return $("ul." + type + "s").sortable({
dropOnEmpty: false,
cursor: "move",
items: "li",
opacity: 0.4,
scroll: true,
axis: 'y',
update: function(e, ui) {
var domId, id, item, to;
item = ui.item[0];
domId = item.id;
id = item.getAttribute('data-id');
to = $("ul." + type + "s").children().index(item);
return model.at("_" + type + "List").pass({
ignore: domId
}).move({
id: id
}, to);
}
});
};
_ref1 = ['habit', 'daily', 'todo', 'reward'];
for (_i = 0, _len = _ref1.length; _i < _len; _i++) {
type = _ref1[_i];
setupSortable(type);
}
}
tour = new Tour();
_ref2 = content.tourSteps;
for (_j = 0, _len1 = _ref2.length; _j < _len1; _j++) {
step = _ref2[_j];
tour.addStep({
element: step.element,
title: step.title,
content: step.content,
placement: step.placement
});
}
tour.start();
model.on('set', '_user.tasks.*.completed', function(i, completed, previous, isLocal, passed) {
var direction, from, fromIds, task, to, toIds, _ref3, _ref4;
if ((passed != null) && passed.cron) {
return;
}
direction = function() {
if (completed === true && previous === false) {
return 'up';
}
if (completed === false && previous === true) {
return 'down';
}
throw new Error("Direction neither 'up' nor 'down' on checkbox set.");
};
task = model.at("_user.tasks." + i);
scoring.score(i, direction());
if (task.get('type') === 'todo') {
_ref3 = direction() === 'up' ? ['todo', 'completed'] : ['completed', 'todo'], from = _ref3[0], to = _ref3[1];
_ref4 = ["_user." + from + "Ids", "_user." + to + "Ids"], from = _ref4[0], to = _ref4[1];
fromIds = model.get(from);
fromIds.splice(fromIds.indexOf(i), 1);
model.set(from, fromIds);
toIds = model.get(to);
toIds.push(i);
return model.set(to, toIds);
}
});
exports.addTask = function(e, el, next) {
var list, newModel, text;
type = $(el).attr('data-task-type');
list = model.at("_" + type + "List");
newModel = model.at('_new' + type.charAt(0).toUpperCase() + type.slice(1));
if (!(text = view.escapeHtml(newModel.get()))) {
return;
}
newModel.set('');
switch (type) {
case 'habit':
return list.push({
type: type,
text: text,
notes: '',
value: 0,
up: true,
down: true
});
case 'reward':
return list.push({
type: type,
text: text,
notes: '',
value: 20
});
case 'daily':
return list.push({
type: type,
text: text,
notes: '',
value: 0,
repeat: {
su: true,
m: true,
t: true,
w: true,
th: true,
f: true,
s: true
},
completed: false
});
case 'todo':
return list.push({
type: type,
text: text,
notes: '',
value: 0,
completed: false
});
}
};
exports.del = function(e, el) {
var history, result, task;
task = model.at(e.target);
history = task.get('history');
if (history && history.length > 2) {
if (task.get('value') < 0) {
result = confirm("Are you sure? Deleting this task will hurt you (to prevent deleting, then re-creating red tasks).");
if (result !== true) {
return;
} else {
task.set('type', 'habit');
scoring.score(task.get('id'), {
direction: 'down'
});
}
} else {
result = confirm("Are you sure you want to delete this task?");
if (result !== true) {
return;
}
}
}
$('[rel=tooltip]').tooltip('hide');
model.del('_user.tasks.' + task.get('id'));
return task.remove();
};
exports.clearCompleted = function(e, el) {
return _.each(model.get('_completedList'), function(task) {
model.del('_user.tasks.' + task.id);
return model.set('_user.completedIds', []);
});
};
exports.toggleDay = function(e, el) {
var task;
task = model.at(e.target);
if (/active/.test($(el).attr('class'))) {
return task.set('repeat.' + $(el).attr('data-day'), false);
} else {
return task.set('repeat.' + $(el).attr('data-day'), true);
}
};
exports.toggleTaskEdit = function(e, el) {
var hideId, toggleId;
hideId = $(el).attr('data-hide-id');
toggleId = $(el).attr('data-toggle-id');
$(document.getElementById(hideId)).hide();
return $(document.getElementById(toggleId)).toggle();
};
exports.toggleChart = function(e, el) {
var chart, chartSelector, data, date, hideSelector, historyPath, matrix, obj, options, readableDate, _k, _len2, _ref3;
hideSelector = $(el).attr('data-hide-id');
chartSelector = $(el).attr('data-toggle-id');
historyPath = $(el).attr('data-history-path');
$(document.getElementById(hideSelector)).hide();
$(document.getElementById(chartSelector)).toggle();
matrix = [['Date', 'Score']];
_ref3 = model.get(historyPath);
for (_k = 0, _len2 = _ref3.length; _k < _len2; _k++) {
obj = _ref3[_k];
date = new Date(obj.date);
readableDate = moment(date).format('MM/DD');
matrix.push([readableDate, obj.value]);
}
data = google.visualization.arrayToDataTable(matrix);
options = {
title: 'History',
backgroundColor: 'whiteSmoke'
};
chart = new google.visualization.LineChart(document.getElementById(chartSelector));
return chart.draw(data, options);
};
exports.buyItem = function(e, el, next) {
var hp, index, money, user, value, _ref3;
user = model.at('_user');
money = user.get('stats.money');
_ref3 = [$(el).attr('data-type'), $(el).attr('data-value'), $(el).attr('data-index')], type = _ref3[0], value = _ref3[1], index = _ref3[2];
if (money < value) {
return;
}
user.set('stats.money', money - value);
if (type === 'armor') {
user.set('items.armor', index);
return model.set('_items.armor', content.items.armor[parseInt(index) + 1]);
} else if (type === 'weapon') {
user.set('items.weapon', index);
return model.set('_items.weapon', content.items.weapon[parseInt(index) + 1]);
} else if (type === 'potion') {
hp = user.get('stats.hp');
hp += 15;
if (hp > 50) {
hp = 50;
}
return user.set('stats.hp', hp);
}
};
exports.score = function(e, el, next) {
var direction, task;
direction = $(el).attr('data-direction');
if (direction === 'true/') {
direction = 'up';
}
if (direction === 'false/') {
direction = 'down';
}
task = model.at($(el).parents('li')[0]);
return scoring.score(task.get('id'), direction);
};
exports.revive = function(e, el) {
var stats;
stats = model.at('_user.stats');
stats.set('hp', 50);
stats.set('lvl', 1);
stats.set('exp', 0);
stats.set('money', 0);
model.set('_user.items.armor', 0);
model.set('_user.items.weapon', 0);
model.set('_items.armor', content.items.armor[1]);
model.set('_items.weapon', content.items.weapon[1]);
return model.set('_user.balance', model.get('_user.balance') - 0.50);
};
exports.reset = function(e, el) {
model.set('_user.tasks', {});
_.each(['habit', 'daily', 'todo', 'completed', 'reward'], function(type) {
model.set("_user." + type + "Ids", []);
return model.refList("_" + type + "List", "_user.tasks", "_user." + type + "Ids");
});
model.set('_user.stats.hp', 50);
model.set('_user.stats.money', 0);
model.set('_user.stats.exp', 0);
return model.set('_user.stats.lvl', 1);
};
setTimeout(scoring.cron, 1);
setInterval(scoring.cron, 3600000);
return require('../server/private').app(exports, model);
});

View File

@@ -1,94 +0,0 @@
// Generated by CoffeeScript 1.4.0
var content, moment, userSchema, _;
content = require('./content');
moment = require('moment');
_ = require('underscore');
userSchema = {
balance: 2,
stats: {
money: 0,
exp: 0,
lvl: 1,
hp: 50
},
items: {
itemsEnabled: false,
armor: 0,
weapon: 0
},
tasks: {},
habitIds: [],
dailyIds: [],
todoIds: [],
completedIds: [],
rewardIds: []
};
module.exports.newUserObject = function() {
var guid, newUser, task, _i, _len, _ref;
newUser = require('clone')(userSchema, true);
_ref = content.defaultTasks;
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
task = _ref[_i];
guid = task.id = require('derby/node_modules/racer').uuid();
newUser.tasks[guid] = task;
switch (task.type) {
case 'habit':
newUser.habitIds.push(guid);
break;
case 'daily':
newUser.dailyIds.push(guid);
break;
case 'todo':
newUser.todoIds.push(guid);
break;
case 'reward':
newUser.rewardIds.push(guid);
}
}
return newUser;
};
module.exports.updateSchema = function(model) {
return;
return model.fetch('users', function(err, users) {
return _.each(users.get(), function(userObj) {
var daysOld, sameTasks, user, userPath;
userPath = "users." + userObj._id;
user = model.at(userPath);
if (userObj.lastCron == null) {
model.del(userPath);
return;
}
daysOld = helpers.daysBetween(new Date(), userObj.lastCron);
if (daysOld > 30) {
sameTasks = _.filter(require('./content').defaultTasks, function(defaultTask) {
var foundSame;
foundSame = _.find(userObj.tasks, function(userTask) {
return userTask.text === defaultTask.text;
});
return foundSame != null;
});
if (_.size(sameTasks) > 5) {
model.del(userPath);
return;
}
}
user.set('history', {
exp: [],
todos: []
});
return _.each(userObj.tasks, function(taskObj) {
var task;
task = user.at("tasks." + taskObj.id);
if (task.get("history")) {
return task.set("history", []);
}
});
});
});
};

View File

@@ -1,340 +0,0 @@
// Generated by CoffeeScript 1.4.0
var MODIFIER, async, content, cron, expModifier, helpers, hpModifier, model, moment, score, setModel, setupNotifications, taskDeltaFormula, updateStats, user, _;
async = require('async');
moment = require('moment');
_ = require('underscore');
content = require('./content');
helpers = require('./helpers');
MODIFIER = .03;
user = void 0;
model = void 0;
setModel = function(m) {
model = m;
user = model.at('_user');
if (!model.get('_mobileDevice')) {
return setupNotifications();
}
};
setupNotifications = function() {
var statsNotification;
if (typeof jQuery === "undefined" || jQuery === null) {
return;
}
statsNotification = function(html, type) {
if (user.get('stats.lvl') === 0) {
return;
}
return $.bootstrapGrowl(html, {
type: type,
top_offset: 20,
align: 'right',
width: 250,
delay: 3000,
allow_dismiss: true,
stackup_spacing: 10
});
};
user.on('set', 'stats.hp', function(captures, args) {
var num, rounded;
num = captures - args;
rounded = Math.abs(num.toFixed(1));
if (num < 0) {
return statsNotification("<i class='icon-heart'></i>HP -" + rounded, 'error');
}
});
user.on('set', 'stats.money', function(captures, args) {
var num, rounded;
num = captures - args;
rounded = Math.abs(num.toFixed(1));
if (num < 0) {
return statsNotification("<i class='icon-star'></i>GP -" + rounded, 'success');
} else if (num > 0) {
num = Math.abs(num);
return statsNotification("<i class='icon-star'></i>Exp,GP +" + rounded, 'success');
}
});
return user.on('set', 'stats.lvl', function(captures, args) {
if (captures > args) {
return statsNotification('<i class="icon-chevron-up"></i> Level Up!', 'info');
}
});
};
/*
Calculates Exp & GP modification based on weapon & lvl
{value} task.value for gain
{modifiers} may manually pass in stats as {weapon, exp}. This is used for testing
*/
expModifier = function(value, modifiers) {
var dmg, lvl, modified, weapon;
if (modifiers == null) {
modifiers = {};
}
weapon = modifiers.weapon || user.get('items.weapon');
lvl = modifiers.lvl || user.get('stats.lvl');
dmg = weapon * MODIFIER;
dmg += (lvl - 1) * MODIFIER;
modified = value + (value * dmg);
return modified;
};
/*
Calculates HP-loss modification based on armor & lvl
{value} task.value which is hurting us
{modifiers} may manually pass in modifier as {armor, lvl}. This is used for testing
*/
hpModifier = function(value, modifiers) {
var ac, armor, lvl, modified;
if (modifiers == null) {
modifiers = {};
}
armor = modifiers.armor || user.get('items.armor');
lvl = modifiers.lvl || user.get('stats.lvl');
ac = armor * MODIFIER;
ac += (lvl - 1) * MODIFIER;
modified = value - (value * ac);
return modified;
};
/*
Calculates the next task.value based on direction
For negative values, use a line: something like y=-.1x+1
For positibe values, taper off with inverse log: y=.9^x
Would love to use inverse log for the whole thing, but after 13 fails it hits infinity. Revisit this formula later
{currentValue} the current value of the task, determines it's next value
{direction} 'up' or 'down'
*/
taskDeltaFormula = function(currentValue, direction) {
var delta, sign;
sign = direction === "up" ? 1 : -1;
delta = currentValue < 0 ? (-0.1 * currentValue + 1) * sign : (Math.pow(0.9, currentValue)) * sign;
return delta;
};
updateStats = function(stats) {
var money, tnl;
if (user.get('stats.lvl') === 0) {
return;
}
if (stats.hp != null) {
if (stats.hp <= 0) {
user.set('stats.lvl', 0);
user.set('stast.hp', 0);
return;
} else {
user.set('stats.hp', stats.hp);
}
}
if (stats.exp != null) {
tnl = user.get('_tnl');
if (stats.exp >= tnl) {
stats.exp -= tnl;
user.set('stats.lvl', user.get('stats.lvl') + 1);
user.set('stats.hp', 50);
}
if (!user.get('items.itemsEnabled') && stats.exp >= 15) {
user.set('items.itemsEnabled', true);
$('ul.items').popover({
title: content.items.unlockedMessage.title,
placement: 'left',
trigger: 'manual',
html: true,
content: "<div class='item-store-popover'> <img src='/img/BrowserQuest/chest.png' /> " + content.items.unlockedMessage.content + " <a href='#' onClick=\"$('ul.items').popover('hide');return false;\">[Close]</a> </div>"
});
$('ul.items').popover('show');
}
user.set('stats.exp', stats.exp);
}
if (stats.money != null) {
if (!(typeof money !== "undefined" && money !== null) || money < 0) {
money = 0.0;
}
return user.set('stats.money', stats.money);
}
};
score = function(taskId, direction, options) {
var adjustvalue, delta, exp, hp, lvl, modified, money, num, task, taskObj, taskPath, type, userObj, value, _ref, _ref1, _ref2;
if (options == null) {
options = {
cron: false,
times: 1
};
}
taskPath = "_user.tasks." + taskId;
_ref = [model.at(taskPath), model.get(taskPath)], task = _ref[0], taskObj = _ref[1];
type = taskObj.type, value = taskObj.value;
userObj = user.get();
if (!task) {
_ref1 = userObj.stats, money = _ref1.money, hp = _ref1.hp, exp = _ref1.exp;
if (direction === "up") {
modified = expModifier(1);
money += modified;
exp += modified;
} else {
modified = hpModifier(1);
hp -= modified;
}
updateStats({
hp: hp,
exp: exp,
money: money
});
return;
}
adjustvalue = type !== 'reward';
if ((type === 'habit') && (taskObj.up === false || taskObj.down === false)) {
adjustvalue = false;
}
delta = 0;
_.times(options.times, function(n) {
var nextDelta;
nextDelta = taskDeltaFormula(value, direction);
if (adjustvalue) {
value += nextDelta;
}
return delta += nextDelta;
});
if (type === 'habit') {
if (taskObj.value !== value) {
task.push('history', {
date: new Date(),
value: value
});
}
}
task.set('value', value);
if (options.cron) {
if (type === 'daily') {
return delta;
} else {
return 0;
}
}
_ref2 = userObj.stats, money = _ref2.money, hp = _ref2.hp, exp = _ref2.exp, lvl = _ref2.lvl;
if (type === 'reward') {
money -= task.get('value');
num = parseFloat(task.get('value')).toFixed(2);
if (money < 0) {
hp += money;
money = 0;
}
}
if ((delta > 0 || (type === 'daily' || type === 'todo')) && !options.cron) {
modified = expModifier(delta);
exp += modified;
money += modified;
} else if (type !== 'reward' && type !== 'todo') {
modified = hpModifier(delta);
hp += modified;
}
updateStats({
hp: hp,
exp: exp,
money: money
});
return delta;
};
cron = function() {
var daysPassed, hpTally, lastCron, tallyTask, tasks, today, todoTally;
today = new Date();
user.setNull('lastCron', today);
lastCron = user.get('lastCron');
daysPassed = helpers.daysBetween(today, lastCron);
if (daysPassed > 0) {
todoTally = 0;
hpTally = 0;
tallyTask = function(taskObj, callback) {
var absVal, completed, daysFailed, id, repeat, task, type, value;
id = taskObj.id, type = taskObj.type, completed = taskObj.completed, repeat = taskObj.repeat;
if (id == null) {
return callback('a task had a null id during cron, this should not be happening');
}
task = user.at("tasks." + id);
if (type === 'todo' || type === 'daily') {
if (!completed) {
daysFailed = daysPassed;
if (type === 'daily' && repeat) {
daysFailed = 0;
_.times(daysPassed, function(n) {
var thatDay;
thatDay = moment().subtract('days', n + 1);
if (repeat[helpers.dayMapping[thatDay.day()]] === true) {
return daysFailed++;
}
});
}
hpTally += score(id, 'down', {
cron: true,
times: daysFailed
});
}
value = task.get('value');
if (type === 'daily') {
task.push("history", {
date: today,
value: value
});
} else {
absVal = completed ? Math.abs(value) : value;
todoTally += absVal;
}
if (type === 'daily') {
task.pass({
cron: true
}).set('completed', false);
}
}
return callback();
};
tasks = _.toArray(user.get('tasks'));
return async.forEach(tasks, tallyTask, function(err) {
var expTally, lvl;
user.push('history.todos', {
date: today,
value: todoTally
});
expTally = user.get('stats.exp');
lvl = 0;
while (lvl < (user.get('stats.lvl') - 1)) {
lvl++;
expTally += (lvl * 100) / 5;
}
user.push('history.exp', {
date: today,
value: expTally
});
updateStats({
hp: user.get('stats.hp') + hpTally
});
return user.set('lastCron', today);
});
}
};
module.exports = {
setModel: setModel,
MODIFIER: MODIFIER,
score: score,
cron: cron,
expModifier: expModifier,
hpModifier: hpModifier,
taskDeltaFormula: taskDeltaFormula
};

View File

@@ -1,103 +0,0 @@
// Generated by CoffeeScript 1.4.0
var MongoStore, ONE_YEAR, app, auth, derby, everyauth, express, expressApp, gzippo, habitrpgMiddleware, http, options, path, priv, publicPath, racer, root, server, serverError, store, strategies;
http = require('http');
path = require('path');
express = require('express');
gzippo = require('gzippo');
derby = require('derby');
app = require('../app');
everyauth = require('everyauth');
serverError = require('./serverError');
MongoStore = require('connect-mongo')(express);
auth = require('derby-auth');
priv = require('./private');
racer = require('derby/node_modules/racer');
racer.io.set('transports', ['xhr-polling']);
if (process.env.NODE_ENV !== 'production') {
racer.use(racer.logPlugin);
derby.use(derby.logPlugin);
}
expressApp = express();
server = http.createServer(expressApp);
module.exports = server;
derby.use(require('racer-db-mongo'));
store = derby.createStore({
db: {
type: 'Mongo',
uri: process.env.NODE_DB_URI
},
listen: server
});
ONE_YEAR = 1000 * 60 * 60 * 24 * 365;
root = path.dirname(path.dirname(__dirname));
publicPath = path.join(root, 'public');
habitrpgMiddleware = function(req, res, next) {
var model;
model = req.getModel();
model.set('_mobileDevice', /Android|webOS|iPhone|iPad|iPod|BlackBerry/i.test(req.header('User-Agent')));
model.set('_nodeEnv', process.env.NODE_ENV);
return next();
};
strategies = {
facebook: {
strategy: require("passport-facebook").Strategy,
conf: {
clientID: process.env.FACEBOOK_KEY,
clientSecret: process.env.FACEBOOK_SECRET
}
}
};
options = {
domain: "http://localhost:3000",
allowPurl: true,
schema: require('../app/schema').newUserObject()
};
auth.init(expressApp, store, strategies, options);
expressApp.use(express.favicon()).use(gzippo.staticGzip(publicPath, {
maxAge: ONE_YEAR
})).use(express.compress()).use(express.bodyParser()).use(express.methodOverride()).use(express.cookieParser()).use(store.sessionMiddleware({
secret: process.env.SESSION_SECRET || 'YOUR SECRET HERE',
cookie: {
maxAge: ONE_YEAR
},
store: new MongoStore({
url: process.env.NODE_DB_URI
})
})).use(store.modelMiddleware()).use(priv.middleware).use(habitrpgMiddleware).use(auth.middleware()).use(app.router()).use(expressApp.router).use(serverError(root));
auth.routes();
priv.routes(expressApp);
require('./serverRoutes')(expressApp, root, derby);
expressApp.all('*', function(req) {
throw "404: " + req.url;
});

View File

@@ -1,9 +0,0 @@
// Generated by CoffeeScript 1.4.0
module.exports.middleware = function(req, res, next) {
return next();
};
module.exports.app = function(appExports, model) {};
module.exports.routes = function(expressApp) {};

View File

@@ -1,27 +0,0 @@
// Generated by CoffeeScript 1.4.0
var derby, isProduction;
derby = require('derby');
isProduction = derby.util.isProduction;
module.exports = function(root) {
var staticPages;
staticPages = derby.createStatic(root);
return function(err, req, res, next) {
var message, status;
if (err == null) {
return next();
}
console.log(err.stack ? err.stack : err);
message = err.message || err.toString();
status = parseInt(message);
if (status === 404) {
return staticPages.render('404', res, {
url: req.url
}, 404);
} else {
return res.send((400 <= status && status < 600) ? status : 500);
}
};
};

View File

@@ -1,72 +0,0 @@
// Generated by CoffeeScript 1.4.0
var scoring, _;
scoring = require('../app/scoring');
_ = require('underscore');
module.exports = function(expressApp, root, derby) {
var deprecatedMessage, staticPages;
staticPages = derby.createStatic(root);
expressApp.get('/privacy', function(req, res) {
return staticPages.render('privacy', res);
});
expressApp.get('/terms', function(req, res) {
return staticPages.render('terms', res);
});
deprecatedMessage = 'This REST resource is no longer supported, use /users/:uid/tasks/:taskId/:direction instead.';
expressApp.get('/:uid/up/:score?', function(req, res) {
return res.send(200, deprecatedMessage);
});
expressApp.get('/:uid/down/:score?', function(req, res) {
return res.send(200, deprecatedMessage);
});
return expressApp.post('/users/:uid/tasks/:taskId/:direction', function(req, res) {
var direction, icon, model, service, taskId, title, uid, _ref, _ref1;
_ref = req.params, uid = _ref.uid, taskId = _ref.taskId, direction = _ref.direction;
_ref1 = req.body, title = _ref1.title, service = _ref1.service, icon = _ref1.icon;
console.log({
params: req.params,
body: req.body
});
if (direction !== 'up' && direction !== 'down') {
return res.send(500, ":direction must be 'up' or 'down'");
}
model = req.getModel();
model.session.userId = uid;
return model.fetch("users." + uid, function(err, user) {
var delta, result, userObj;
if (err) {
return res.send(500, err);
}
userObj = user.get();
if (!(userObj && !_.isEmpty(userObj.stats))) {
console.log({
taskId: taskId,
direction: direction,
user: userObj,
error: 'non-user attempted to score'
});
return res.send(500, "User " + uid + " not found");
}
model.ref('_user', user);
if (!model.get("_user.tasks." + taskId)) {
model.refList("_habitList", "_user.tasks", "_user.habitIds");
model.at('_habitList').push({
id: taskId,
type: 'habit',
text: title || taskId,
value: 0,
up: true,
down: true,
notes: "This task was created by a third-party service. Feel free to edit, it won't harm the connection to that service. Additionally, multiple services may piggy-back off this task."
});
}
scoring.setModel(model);
delta = scoring.score(taskId, direction);
result = model.get('_user.stats');
result.delta = delta;
return res.send(result);
});
});
};

View File

@@ -17,12 +17,10 @@
"stripe": "*",
"async": "*",
"underscore": "*",
"clone": "*"
"clone": "*",
"coffee-script": "*"
},
"private": true,
"devDependencies": {
"coffee-script": ">=1.3.3"
},
"subdomain": "habitrpg",
"domains": [
"habitrpg.com",

View File

@@ -1,8 +1,5 @@
var port = process.env.PORT || 3000;
if (process.env.NODE_ENV === 'debug' || process.env.NODE_ENV === 'production') {
// "up" module interferes with node debug & node-inspector. also, nodejitsu (aka, 'production') doesn't like up
console.log('Running in ' + process.env.NODE_ENV + ': require("./lib/server").listen(' + port + ');');
require('./lib/server').listen(port);
} else {
require('derby').run(__dirname + '/lib/server', port);
}
require('coffee-script') // remove intermediate compilation requirement
require('./src/server').listen(process.env.PORT || 3000);
// Note: removed "up" module, which is default for development (but interferes with and production + PaaS)
// Restore to 5310bb0 if I want it back (see https://github.com/codeparty/derby/issues/165#issuecomment-10405693)