Merge and fix tests

This commit is contained in:
Daniel Saewitz 2013-02-26 19:17:11 -05:00
commit 822f779812
8 changed files with 308 additions and 327 deletions

View file

@ -45,13 +45,13 @@ get '/', (page, model, params, next) ->
ready (model) -> ready (model) ->
user = model.at('_user') user = model.at('_user')
scoring.setModel(model) score = new scoring.Scoring(model)
#set cron immediately #set cron immediately
lastCron = user.get('lastCron') lastCron = user.get('lastCron')
user.set('lastCron', +new Date) if (!lastCron? or lastCron == 'new') user.set('lastCron', +new Date) if (!lastCron? or lastCron == 'new')
scoring.cron() score.cron()
character.app(exports, model) character.app(exports, model)
tasks.app(exports, model) tasks.app(exports, model)

View file

@ -16,31 +16,8 @@ partyUnsubscribe = (model, cb) ->
1) If the user is solo, just subscribe to the user. 1) If the user is solo, just subscribe to the user.
2) If in a an empty party, just subscribe to the user & party meta. 2) If in a an empty party, just subscribe to the user & party meta.
3) If full party, subscribe to everything. 3) If full party, subscribe to everything.
###
module.exports.partySubscribe = partySubscribe = (page, model, params, next, cb) ->
# unsubscribe from everything - we're starting over Note a strange hack - we subscribe to queries incrementally. First self, then party, then party members.
# partyUnsubscribe model, ->
# Restart subscription to the main user
selfQ = model.query('users').withId model.get('_userId') #or model.session.userId # see http://goo.gl/TPYIt
selfQ.subscribe (err, self) ->
return next(err) if err
u = self.at(0)
uObj = u.get()
# Attempted handling for 'party of undefined' error, which is caused by bustedSession (see derby-auth).
# Theoretically simply reloading the page should restore model.at('_userId') and the second load should work just fine
# bustedSession victims might hit a redirection loop if I'm wrong :/
return page.redirect('/') unless uObj
## (1) User is solo, just return that subscription
unless uObj.party?.current?
model.ref '_user', u
return cb()
###
Note this strange hack - we subscribe to queries incrementally. First self, then party, then party members.
Party members come with limited fields, so you can't hack their stuff. Strangely, subscribing to the members after Party members come with limited fields, so you can't hack their stuff. Strangely, subscribing to the members after
already subscribing to self limits self's fields to the fields which members are limited to. As a result, we have already subscribing to self limits self's fields to the fields which members are limited to. As a result, we have
to re-subscribe to self to get all the fields (otherwise everything breaks). Weirdly, this last subscription doesn't to re-subscribe to self to get all the fields (otherwise everything breaks). Weirdly, this last subscription doesn't
@ -48,40 +25,48 @@ module.exports.partySubscribe = partySubscribe = (page, model, params, next, cb)
Another issue: `model.unsubscribe(selfQ)` would seem to mitigate the above, so we at least don't have a stray Another issue: `model.unsubscribe(selfQ)` would seem to mitigate the above, so we at least don't have a stray
subscription floating around - but alas, it doesn't seem to work (or at least never calls the callback) subscription floating around - but alas, it doesn't seem to work (or at least never calls the callback)
### ###
finished = -> module.exports.partySubscribe = partySubscribe = (page, model, params, next, cb) ->
# model.unsubscribe selfQ, ->
selfQ.subscribe (err, self) -> # unsubscribe from everything - we're starting over
# partyUnsubscribe model, ->
# Restart subscription to the main user
selfQ = model.query('users').withId (model.get('_userId') or model.session.userId) # see http://goo.gl/TPYIt
selfQ.fetch (err, user) ->
return next(err) if err return next(err) if err
model.ref '_user', self.at(0) return next("User not found - this shouldn't be happening!") unless user.get()
finished = (descriptors, paths) ->
model.subscribe.apply model, descriptors.concat ->
[err, refs] = [arguments[0], arguments]
return next(err) if err
_.each paths, (path, idx) -> model.ref path, refs[idx+1]
cb() cb()
# User in a party
partiesQ = model.query('parties').withId(uObj.party.current)
partiesQ.fetch (err, res) ->
return next(err) if err
p = res.at(0)
model.ref '_party', p
ids = p.get('members')
# FIXME this is the kicker right here. This isn't getting triggered, and it's the reason why we have to refresh # Attempted handling for 'party of undefined' error, which is caused by bustedSession (see derby-auth).
# after every event. Get this working # Theoretically simply reloading the page should restore model.at('_userId') and the second load should work just fine
#p.on '*', 'members', (ids) -> # bustedSession victims might hit a redirection loop if I'm wrong :/
# console.log("members listener got called") # return page.redirect('/') unless uObj
# debugger
# membersSubscribe model, ids partyId = user.get('party.current')
# (1) Solo player
return finished([selfQ], ['_user']) unless partyId
# User in a party
partyQ = model.query('parties').withId(partyId)
partyQ.fetch (err, party) ->
return next(err) if err
members = party.get('members')
## (2) Party has no members, just subscribe to the party itself ## (2) Party has no members, just subscribe to the party itself
if _.isEmpty(ids) return finished([partyQ, selfQ], ['_party', '_user']) if _.isEmpty(members)
return finished()
else
## (3) Party has members, subscribe to those users too ## (3) Party has members, subscribe to those users too
membersQ = model.query('users').party(ids) membersQ = model.query('users').party(members)
membersQ.fetch (err, members) -> return finished [partyQ, membersQ, selfQ], ['_party', '_partyMembers', '_user']
return next(err) if err
model.ref '_partyMembers', members
finished()
module.exports.app = (appExports, model) -> module.exports.app = (appExports, model) ->
user = model.at('_user') user = model.at('_user')
@ -121,7 +106,7 @@ module.exports.app = (appExports, model) ->
query = model.query('users').party([id]) query = model.query('users').party([id])
model.fetch query, (err, res) -> model.fetch query, (err, res) ->
throw err if err throw err if err
u = res.at(0).get() u = res.get()
if !u?.id? if !u?.id?
model.set "_view.partyError", "User with id #{id} not found." model.set "_view.partyError", "User with id #{id} not found."
return return
@ -143,9 +128,9 @@ module.exports.app = (appExports, model) ->
user.set 'party.current', partyId user.set 'party.current', partyId
# model.push "parties.#{partyId}.members", user.get('id'), -> #FIXME why this not working? # model.push "parties.#{partyId}.members", user.get('id'), -> #FIXME why this not working?
model.query('parties').withId(partyId).fetch (err, p) -> model.query('parties').withId(partyId).fetch (err, p) ->
members = p.at(0).get('members') members = p.get('members')
members.push user.get('id') members.push user.get('id')
p.at(0).set 'members', members, -> p.set 'members', members, ->
window.location.reload true window.location.reload true
# partySubscribe model, -> # partySubscribe model, ->
# p = model.at('_party') # p = model.at('_party')
@ -174,5 +159,5 @@ module.exports.app = (appExports, model) ->
# partyUnsubscribe model, -> # partyUnsubscribe model, ->
# selfQ = model.query('users').withId model.get('_userId') #or model.session.userId # see http://goo.gl/TPYIt # selfQ = model.query('users').withId model.get('_userId') #or model.session.userId # see http://goo.gl/TPYIt
# selfQ.subscribe (err, u) -> # selfQ.subscribe (err, u) ->
# model.ref '_user', u.at(0) # model.ref '_user', u
# browser.resetDom model # browser.resetDom model

View file

@ -5,21 +5,18 @@ helpers = require './helpers'
browser = require './browser' browser = require './browser'
character = require './character' character = require './character'
items = require './items' items = require './items'
MODIFIER = .02 # each new level, armor, weapon add 2% modifier (this number may change)
user = undefined
model = undefined
# This is required by all the functions, make sure it's set before anythign else is called module.exports.Scoring = (model) ->
setModel = (m) ->
model = m
user = model.at('_user')
### MODIFIER = .02 # each new level, armor, weapon add 2% modifier (this mechanism will change)
user = model.at '_user'
###
Calculates Exp & GP modification based on weapon & lvl Calculates Exp & GP modification based on weapon & lvl
{value} task.value for gain {value} task.value for gain
{modifiers} may manually pass in stats as {weapon, exp}. This is used for testing {modifiers} may manually pass in stats as {weapon, exp}. This is used for testing
### ###
expModifier = (value, modifiers = {}) -> expModifier = (value, modifiers = {}) ->
weapon = modifiers.weapon || user.get('items.weapon') weapon = modifiers.weapon || user.get('items.weapon')
lvl = modifiers.lvl || user.get('stats.lvl') lvl = modifiers.lvl || user.get('stats.lvl')
dmg = items.items.weapon[weapon].modifier # each new weapon increases exp gain dmg = items.items.weapon[weapon].modifier # each new weapon increases exp gain
@ -27,12 +24,12 @@ expModifier = (value, modifiers = {}) ->
modified = value + (value * dmg) modified = value + (value * dmg)
return modified return modified
### ###
Calculates HP-loss modification based on armor & lvl Calculates HP-loss modification based on armor & lvl
{value} task.value which is hurting us {value} task.value which is hurting us
{modifiers} may manually pass in modifier as {armor, lvl}. This is used for testing {modifiers} may manually pass in modifier as {armor, lvl}. This is used for testing
### ###
hpModifier = (value, modifiers = {}) -> hpModifier = (value, modifiers = {}) ->
armor = modifiers.armor || user.get('items.armor') armor = modifiers.armor || user.get('items.armor')
head = modifiers.head || user.get('items.head') head = modifiers.head || user.get('items.head')
shield = modifiers.shield || user.get('items.shield') shield = modifiers.shield || user.get('items.shield')
@ -42,25 +39,25 @@ hpModifier = (value, modifiers = {}) ->
modified = value - (value * ac) modified = value - (value * ac)
return modified return modified
### ###
Calculates the next task.value based on direction Calculates the next task.value based on direction
For negative values, use a line: something like y=-.1x+1 For negative values, use a line: something like y=-.1x+1
For positibe values, taper off with inverse log: y=.9^x 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 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 {currentValue} the current value of the task, determines it's next value
{direction} 'up' or 'down' {direction} 'up' or 'down'
### ###
taskDeltaFormula = (currentValue, direction) -> taskDeltaFormula = (currentValue, direction) ->
sign = if (direction == "up") then 1 else -1 sign = if (direction == "up") then 1 else -1
delta = if (currentValue < 0) then (( -0.1 * currentValue + 1 ) * sign) else (( Math.pow(0.9,currentValue) ) * sign) delta = if (currentValue < 0) then (( -0.1 * currentValue + 1 ) * sign) else (( Math.pow(0.9,currentValue) ) * sign)
return delta return delta
### ###
Updates user stats with new stats. Handles death, leveling up, etc Updates user stats with new stats. Handles death, leveling up, etc
{stats} new stats {stats} new stats
{update} if aggregated changes, pass in userObj as update. otherwise commits will be made immediately {update} if aggregated changes, pass in userObj as update. otherwise commits will be made immediately
### ###
updateStats = (newStats, batch) -> updateStats = (newStats, batch) ->
obj = batch.obj() obj = batch.obj()
# if user is dead, dont do anything # if user is dead, dont do anything
@ -105,11 +102,11 @@ updateStats = (newStats, batch) ->
gp = 0.0 if (!gp? or gp<0) gp = 0.0 if (!gp? or gp<0)
obj.stats.gp = newStats.gp obj.stats.gp = newStats.gp
# {taskId} task you want to score # {taskId} task you want to score
# {direction} 'up' or 'down' # {direction} 'up' or 'down'
# {times} # times to call score on this task (1 unless cron, usually) # {times} # times to call score on this task (1 unless cron, usually)
# {update} if we're running updates en-mass (eg, cron on server) pass in userObj # {update} if we're running updates en-mass (eg, cron on server) pass in userObj
score = (taskId, direction, times, batch, cron) -> score = (taskId, direction, times, batch, cron) ->
commit = false commit = false
unless batch? unless batch?
@ -197,15 +194,15 @@ score = (taskId, direction, times, batch, cron) ->
newStats = _.clone batch.obj().stats newStats = _.clone batch.obj().stats
_.each Object.keys(origStats), (key) -> obj.stats[key] = origStats[key] _.each Object.keys(origStats), (key) -> obj.stats[key] = origStats[key]
batch.setStats(newStats) batch.setStats(newStats)
# batch.setStats() # batch.setStats()
batch.commit() batch.commit()
return delta return delta
### ###
At end of day, add value to all incomplete Daily & Todo tasks (further incentive) At end of day, add value to all incomplete Daily & Todo tasks (further incentive)
For incomplete Dailys, deduct experience For incomplete Dailys, deduct experience
### ###
cron = () -> cron = () ->
today = +new Date today = +new Date
daysPassed = helpers.daysBetween(today, user.get('lastCron')) daysPassed = helpers.daysBetween(today, user.get('lastCron'))
if daysPassed > 0 if daysPassed > 0
@ -264,8 +261,7 @@ cron = () ->
setTimeout (-> user.set 'stats.hp', hpAfter), 1000 # animate hp loss setTimeout (-> user.set 'stats.hp', hpAfter), 1000 # animate hp loss
module.exports = { return {
setModel: setModel
MODIFIER: MODIFIER MODIFIER: MODIFIER
score: score score: score
cron: cron cron: cron
@ -274,4 +270,4 @@ module.exports = {
expModifier: expModifier expModifier: expModifier
hpModifier: hpModifier hpModifier: hpModifier
taskDeltaFormula: taskDeltaFormula taskDeltaFormula: taskDeltaFormula
} }

View file

@ -26,6 +26,7 @@ module.exports.view = (view) ->
module.exports.app = (appExports, model) -> module.exports.app = (appExports, model) ->
user = model.at('_user') user = model.at('_user')
score = new scoring.Scoring(model)
user.on 'set', 'tasks.*.completed', (i, completed, previous, isLocal, passed) -> user.on 'set', 'tasks.*.completed', (i, completed, previous, isLocal, passed) ->
return if passed? && passed.cron # Don't do this stuff on cron return if passed? && passed.cron # Don't do this stuff on cron
@ -36,7 +37,7 @@ module.exports.app = (appExports, model) ->
# Score the user based on todo task # Score the user based on todo task
task = user.at("tasks.#{i}") task = user.at("tasks.#{i}")
scoring.score(i, direction()) score.score(i, direction())
appExports.addTask = (e, el, next) -> appExports.addTask = (e, el, next) ->
type = $(el).attr('data-task-type') type = $(el).attr('data-task-type')
@ -81,7 +82,7 @@ module.exports.app = (appExports, model) ->
return # Cancel. Don't delete, don't hurt user return # Cancel. Don't delete, don't hurt user
else else
task.set('type','habit') # hack to make sure it hits HP, instead of performing "undo checkbox" task.set('type','habit') # hack to make sure it hits HP, instead of performing "undo checkbox"
scoring.score(id, direction:'down') score.score(id, direction:'down')
# prevent accidently deleting long-standing tasks # prevent accidently deleting long-standing tasks
else else
@ -171,4 +172,4 @@ module.exports.app = (appExports, model) ->
direction = 'up' if direction == 'true/' direction = 'up' if direction == 'true/'
direction = 'down' if direction == 'false/' direction = 'down' if direction == 'false/'
task = model.at $(el).parents('li')[0] task = model.at $(el).parents('li')[0]
scoring.score(task.get('id'), direction) score.score(task.get('id'), direction)

View file

@ -30,7 +30,6 @@ auth = (req, res, next) ->
query.fetch (err, user) -> query.fetch (err, user) ->
return res.json err: err if err return res.json err: err if err
user = user.at(0)
req.user = user req.user = user
req.userObj = user.get() req.userObj = user.get()
return res.json 401, NO_USER_FOUND if !req.userObj || _.isEmpty(req.userObj) return res.json 401, NO_USER_FOUND if !req.userObj || _.isEmpty(req.userObj)
@ -109,7 +108,7 @@ router.get '/users/:uid/calendar.ics', (req, res) ->
query = model.query('users').withIdAndToken(uid, apiToken) query = model.query('users').withIdAndToken(uid, apiToken)
query.fetch (err, result) -> query.fetch (err, result) ->
return res.send(400, err) if err return res.send(400, err) if err
tasks = result.at(0).get('tasks') tasks = result.get('tasks')
# tasks = result[0].tasks # tasks = result[0].tasks
tasksWithDates = _.filter tasks, (task) -> !!task.date tasksWithDates = _.filter tasks, (task) -> !!task.date
return res.send(400, "No events found") if _.isEmpty(tasksWithDates) return res.send(400, "No events found") if _.isEmpty(tasksWithDates)

View file

@ -12,9 +12,9 @@ module.exports.app = (appExports, model) ->
token = (res) -> token = (res) ->
console.log(res); console.log(res);
$.ajax({ $.ajax({
type:"POST", type: "POST",
url:"/charge", url: "/charge",
data:res data: res
}).success -> }).success ->
window.location.href = "/" window.location.href = "/"
.error (err) -> .error (err) ->

View file

@ -74,7 +74,7 @@ REST = (store) ->
store.query.expose "users", "withIdAndToken", (uid, token) -> store.query.expose "users", "withIdAndToken", (uid, token) ->
@byId(uid) @byId(uid)
.where('apiToken').equals(token) .where('apiToken').equals(token)
.one .findOne()
store.queryAccess "users", "withIdAndToken", (uid, token, accept, err) -> store.queryAccess "users", "withIdAndToken", (uid, token, accept, err) ->
return accept(true) if uid && token return accept(true) if uid && token
@ -101,6 +101,7 @@ partySystem = (store) ->
store.query.expose "parties", "withId", (id) -> store.query.expose "parties", "withId", (id) ->
@where("id").equals(id) @where("id").equals(id)
.findOne()
store.queryAccess "parties", "withId", (id, accept, err) -> store.queryAccess "parties", "withId", (id, accept, err) ->
# return err(derbyAuth.SESSION_INVALIDATED_ERROR) if derbyAuth.bustedSession(@) # return err(derbyAuth.SESSION_INVALIDATED_ERROR) if derbyAuth.bustedSession(@)
return accept(false) if derbyAuth.bustedSession(@) return accept(false) if derbyAuth.bustedSession(@)

View file

@ -119,7 +119,7 @@ describe 'API', ->
expect(res.statusCode).to.be 201 expect(res.statusCode).to.be 201
expect(res.body.id).not.to.be.empty() expect(res.body.id).not.to.be.empty()
# Ensure that user owns the newly created object # Ensure that user owns the newly created object
expect(user.at(0).get().tasks[res.body.id]).to.be.an('object') expect(user.get().tasks[res.body.id]).to.be.an('object')
done() done()
it 'PUT /api/v1/task/:id', (done) -> it 'PUT /api/v1/task/:id', (done) ->
@ -128,11 +128,12 @@ describe 'API', ->
.set('Accept', 'application/json') .set('Accept', 'application/json')
.set('X-API-User', currentUser.id) .set('X-API-User', currentUser.id)
.set('X-API-Key', currentUser.apiToken) .set('X-API-Key', currentUser.apiToken)
.send(title: 'a new title') .send(title: 'a new title',text: 'hi')
.end (res) -> .end (res) ->
expect(res.body.err).to.be undefined expect(res.body.err).to.be undefined
expect(res.statusCode).to.be 200 expect(res.statusCode).to.be 200
currentUser.tasks[tid].title = 'a new title' currentUser.tasks[tid].title = 'a new title'
currentUser.tasks[tid].text = 'hi'
expect(res.body).to.eql currentUser.tasks[tid] expect(res.body).to.eql currentUser.tasks[tid]
done() done()
@ -146,8 +147,7 @@ describe 'API', ->
query.fetch (err, user) -> query.fetch (err, user) ->
expect(res.body.err).to.be undefined expect(res.body.err).to.be undefined
expect(res.statusCode).to.be 200 expect(res.statusCode).to.be 200
currentUser = user.at(0).get() model.ref '_user', user
model.ref '_user', user.at(0)
tasks = [] tasks = []
for type in ['habit','todo','daily','reward'] for type in ['habit','todo','daily','reward']
model.refList "_#{type}List", "_user.tasks", "_user.#{type}Ids" model.refList "_#{type}List", "_user.tasks", "_user.#{type}Ids"
@ -169,8 +169,7 @@ describe 'API', ->
query.fetch (err, user) -> query.fetch (err, user) ->
expect(res.body.err).to.be undefined expect(res.body.err).to.be undefined
expect(res.statusCode).to.be 200 expect(res.statusCode).to.be 200
currentUser = user.at(0).get() model.ref '_user', user
model.ref '_user', user.at(0)
model.refList "_todoList", "_user.tasks", "_user.todoIds" model.refList "_todoList", "_user.tasks", "_user.todoIds"
tasks = model.get("_todoList") tasks = model.get("_todoList")
# Ensure that user owns the tasks # Ensure that user owns the tasks