Merge branch 'develop' of github.com:lefnire/habitrpg into api

This commit is contained in:
Daniel Saewitz 2013-02-20 20:05:06 -05:00
commit 905e728c70
9 changed files with 142 additions and 170 deletions

View file

@ -57,9 +57,6 @@ module.exports.app = (appExports, model) ->
batch.commit()
browser.resetDom(model)
appExports.closeKickstarterNofitication = (e, el) ->
user.set('flags.kickstarter', 'hide')
appExports.closeCelebrationNofitication = (e, el) ->
user.set('flags.celebrationEvent', 'hide')
@ -107,7 +104,6 @@ userSchema =
flags:
partyEnabled: false
itemsEnabled: false
kickstarter: 'show'
# ads: 'show' # added on registration
module.exports.newUserObject = ->

View file

@ -12,13 +12,10 @@ partyUnsubscribe = (model, cb) ->
cb()
###
Subscribe to the user, the users's party (meta), and the party's members. 3 subscriptions.
If the user is solo, just subscribe to the user. If in a an empty party, just subscribe to the party. If full party,
subscribe to everything.
Note a strange hack - later model.queries override previous model.queries'
returned fields. Aka, we need this here otherwise we only get the "public" fields for the current user, which
are defined in model.query('users').party(). So we need to subscribe to the main user last
Subscribe to the user, the users's party (meta info like party name, member ids, etc), and the party's members. 3 subscriptions.
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.
3) If full party, subscribe to everything.
###
module.exports.partySubscribe = partySubscribe = (model, cb) ->
@ -32,23 +29,30 @@ module.exports.partySubscribe = partySubscribe = (model, cb) ->
u = self.at(0)
uObj = u.get()
finished = ->
model.unsubscribe selfQ, ->
selfQ.subscribe (err, self) ->
model.ref '_user', self.at(0)
browser.resetDom(model) if window?
cb() if cb?
## (1) User is solo, just return that subscription
unless uObj.party?.current
unless uObj.party?.current?
model.ref '_user', u
browser.resetDom(model) if window?
return if cb then cb() else null
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
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
do the opposite - granting all the fields back to members. I dont' know what's going on here
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)
###
finished = ->
# model.unsubscribe selfQ, ->
selfQ.subscribe (err, self) ->
model.ref '_user', self.at(0)
cb()
# User in a party
partiesQ = model.query('parties').withId(uObj.party.current)
partiesQ.subscribe (err, res) ->
partiesQ.fetch (err, res) ->
throw err if err
p = res.at(0)
model.ref '_party', p
@ -68,7 +72,7 @@ module.exports.partySubscribe = partySubscribe = (model, cb) ->
else
## (3) Party has members, subscribe to those users too
membersQ = model.query('users').party(ids)
membersQ.subscribe (err, members) ->
membersQ.fetch (err, members) ->
throw err if err
model.ref '_partyMembers', members
finished()
@ -99,8 +103,9 @@ module.exports.app = (appExports, model) ->
appExports.partyCreate = ->
newParty = model.get("_newParty")
id = model.add 'parties', { name: newParty, leader: user.get('id'), members: [user.get('id')], invites:[] }
user.set 'party', {current: id, invitation: null, leader: true}
partySubscribe model, -> $('#party-modal').modal('show')
user.set 'party', {current: id, invitation: null, leader: true}, ->
window.location.reload true
# partySubscribe model, -> $('#party-modal').modal('show')
appExports.partyInvite = ->
id = model.get('_newPartyMember').replace(/[\s"]/g, '')
@ -123,18 +128,22 @@ module.exports.app = (appExports, model) ->
model.set "users.#{id}.party.invitation", p.get('id')
$.bootstrapGrowl "Invitation Sent."
$('#party-modal').modal('hide')
model.set '_newPartyMember', ''
partySubscribe model
model.set '_newPartyMember', '', -> window.location.reload true
#partySubscribe model
appExports.partyAccept = ->
user.set 'party.current', user.get('party.invitation')
partyId = user.get('party.invitation')
user.set 'party.invitation', null
setTimeout (-> window.location.reload true), 10
return
# FIXME This should handle not requiring a refresh, but alas.
partySubscribe model, ->
p = model.at('_party')
p.push 'members', user.get('id')
user.set 'party.current', partyId
# model.push "parties.#{partyId}.members", user.get('id'), -> #FIXME why this not working?
model.query('parties').withId(partyId).fetch (err, p) ->
members = p.at(0).get('members')
members.push user.get('id')
p.at(0).set 'members', members, ->
window.location.reload true
# partySubscribe model, ->
# p = model.at('_party')
# p.push 'members', user.get('id')
appExports.partyReject = ->
user.set 'party.invitation', null
@ -148,21 +157,16 @@ module.exports.app = (appExports, model) ->
members = p.get('members')
index = members.indexOf(user.get('id'))
members.splice(index,1)
p.set 'members', members
if (members.length == 0)
# last member out, kill the party
model.del "parties.#{id}"
model.set '_party', null
model.set '_partyMembers', null
setTimeout (-> window.location.reload true), 10
return
# FIXME This should handle not requiring a refresh, but alas.
partyUnsubscribe model, ->
selfQ = model.query('users').withId(model.get('_userId') or model.session.userId)
selfQ.subscribe (err, u) ->
model.ref '_user', u.at(0)
browser.resetDom model
#exports.partyDisband = ->
p.set 'members', members, ->
if (members.length == 0)
# last member out, kill the party
model.del "parties.#{id}", -> window.location.reload true
else
window.location.reload true
# model.set '_party', null
# model.set '_partyMembers', null
# partyUnsubscribe model, ->
# selfQ = model.query('users').withId(model.get('_userId') or model.session.userId)
# selfQ.subscribe (err, u) ->
# model.ref '_user', u.at(0)
# browser.resetDom model

View file

@ -3,16 +3,17 @@ path = require 'path'
express = require 'express'
gzippo = require 'gzippo'
derby = require 'derby'
racer = require 'racer'
auth = require 'derby-auth'
app = require '../app'
serverError = require './serverError'
MongoStore = require('connect-mongo')(express)
auth = require 'derby-auth'
priv = require './private'
habitrpgStore = require('./store')
habitrpgStore = require './store'
middleware = require './middleware'
## RACER CONFIGURATION ##
racer = require 'racer'
racer.io.set('transports', ['xhr-polling'])
racer.ioClient.set('reconnection limit', 300000) # max reconect timeout to 5 minutes
racer.set('bundleTimeout', 40000)
@ -26,7 +27,7 @@ expressApp = express()
server = http.createServer expressApp
module.exports = server
derby.use(require 'racer-db-mongo')
derby.use require('racer-db-mongo')
store = derby.createStore
db: {type: 'Mongo', uri: process.env.NODE_DB_URI, safe:true}
listen: server
@ -53,57 +54,27 @@ auth.store(store, habitrpgStore.customAccessControl)
mongo_store = new MongoStore {url: process.env.NODE_DB_URI}, ->
expressApp
#.use (req, res, next) ->
# if toobusy()
# return res.redirect 307, '/500.html'
# else
# next()
.use(express.favicon())
.use(express.favicon("#{publicPath}/favicon.ico"))
# Gzip static files and serve from memory
.use(gzippo.staticGzip publicPath, maxAge: ONE_YEAR)
.use(gzippo.staticGzip(publicPath, maxAge: ONE_YEAR))
# Gzip dynamically rendered content
.use(express.compress())
# Uncomment to add form data parsing support
.use(express.bodyParser())
.use(express.methodOverride())
# Uncomment and supply secret to add Derby session handling
# Derby session middleware creates req.session and socket.io sessions
.use(express.cookieParser())
.use(store.sessionMiddleware
secret: process.env.SESSION_SECRET || 'YOUR SECRET HERE'
cookie: {maxAge: TWO_WEEKS} # defaults to 2 weeks? aka, can delete this line?
cookie: { maxAge: TWO_WEEKS } # defaults to 2 weeks? aka, can delete this line?
store: mongo_store
)
#show splash page for newcomers
.use (req, res, next) ->
# This was an API call, not a page load
return next() if req.is('json')
if !req.session.userId? and !req.query?.play? and req.url == '/'
return res.redirect('/splash.html')
else
next()
# Show splash page for newcomers
.use(middleware.splash)
# Adds req.getModel method
.use(store.modelMiddleware())
# Middelware can be inserted after the modelMiddleware and before
# the app router to pass server accessible data to a model
.use(priv.middleware)
# HabitRPG Custom Middleware
.use (req, res, next) ->
model = req.getModel()
_view = model.get('_view') || {}
## Set _mobileDevice to true or false so view can exclude portions from mobile device
_view.mobileDevice = /Android|webOS|iPhone|iPad|iPod|BlackBerry/i.test(req.header 'User-Agent')
_view.nodeEnv = process.env.NODE_ENV
model.set '_view', _view
next()
.use(middleware.view)
.use(auth.middleware(strategies, options))
# Creates an express middleware from the app's routes
.use(app.router())
@ -111,10 +82,10 @@ mongo_store = new MongoStore {url: process.env.NODE_DB_URI}, ->
.use(require('./static').middleware)
.use(require('./deprecated').middleware)
.use(expressApp.router)
.use(serverError root)
.use(serverError(root))
priv.routes(expressApp)
# Errors
expressApp.all '*', (req) ->
throw "404: #{req.url}"
throw "404: #{req.url}"

View file

@ -0,0 +1,17 @@
module.exports.splash = (req, res, next) ->
# This was an API call, not a page load
return next() if req.is('json')
if !req.session.userId? and !req.query?.play?
res.redirect('/splash.html')
else
next()
module.exports.view = (req, res, next) ->
model = req.getModel()
_view = model.get('_view') || {}
## Set _mobileDevice to true or false so view can exclude portions from mobile device
_view.mobileDevice = /Android|webOS|iPhone|iPad|iPod|BlackBerry/i.test(req.header 'User-Agent')
_view.nodeEnv = process.env.NODE_ENV
model.set '_view', _view
next()

View file

@ -8,7 +8,7 @@ casper.start url
# ---------- Daily ------------
casper.then ->
helpers.reset()
helpers.addTasks()
helpers.addTasks(['daily'])
# Gained exp on +daily
casper.then ->
@ -27,15 +27,18 @@ casper.then ->
# ---------- Cron ------------
casper.then ->
helpers.reset()
helpers.addTasks()
helpers.addTasks(['daily'])
casper.then ->
helpers.cronBeforeAfter (model) ->
casper.then ->
#TODO make sure true for all dailies
dailyId = model.before._user.dailyIds[0]
casper.test.assert model.before._user.tasks[dailyId].value < model.after._user.tasks[dailyId].value, "daily:cron:daily gained value"
casper.test.assert model.before._user.stats.hp < model.after._user.stats.hp, 'daily:cron:hp lost value'
casper.test.assert model.before._user.stats.hp > model.after._user.stats.hp, 'daily:cron:hp lost value'
# Go through all the dailys, all of them are uncompleted, so should all get a negative value
casper.echo "Testing all uncompleted dailys after cron"
for id in model.before._user.dailyIds
casper.test.assertEquals model.before._user.tasks[id].value, 0, "daily:cron:daily value before was 0"
casper.test.assert model.after._user.tasks[id].value < 0, "daily:cron:daily value after is negative"
# ---------- Run ------------
casper.run ->

View file

@ -2,7 +2,7 @@ utils = require('utils')
module.exports = ->
SYNC_WAIT_TIME = 20
SYNC_WAIT_TIME = 40
baseUrl = 'http://localhost:3000'
@ -31,12 +31,13 @@ module.exports = ->
utils: utils
addTasks: ->
['habit', 'daily', 'todo', 'reward'].forEach (type) ->
# Add 15 of each task type
num = 0
casper.repeat 5, ->
casper.fill "form#new-#{type}", {'new-task': "#{type}-#{num}"} # why can't I use true here?
addTasks: (types = 'all', num = 5)->
if types == 'all'
types = ['habit', 'daily', 'todo', 'reward']
types.forEach (type) ->
i = 0
casper.repeat num, ->
casper.fill "form#new-#{type}", {'new-task': "#{type}-#{i}"} # why can't I use true here?
casper.click "form#new-#{type} input[type=submit]"
reset: ->
@ -58,7 +59,10 @@ module.exports = ->
casper.then -> done_cb(model)
runCron: ->
casper.evaluate -> window.DERBY.model.set('_user.lastCron', new Date('01/25/2013'))
casper.evaluate ->
yesterday = new Date()
yesterday.setDate(yesterday.getDate() - 1);
window.DERBY.app.model.set('_user.lastCron', yesterday)
casper.then -> casper.reload()
cronBeforeAfter: (cb) ->
@ -68,14 +72,13 @@ module.exports = ->
model.before = before
casper.then -> that.runCron()
casper.then ->
casper.wait 1050, -> # user's hp is updated after 1s for animation
casper.wait 1100, -> # user's hp is updated after 1s for animation
model.after = getModel()
casper.then ->
casper.test.assertEqual model.before._user.id, model.after._user.id, 'user id equal after cron'
casper.test.assertEqual model.before._user.tasks.length, model.after._user.tasks.length, "Didn't lose anything on cron"
cb(model)
register: ->
casper.fill 'form#derby-auth-register',
username: random
@ -90,13 +93,10 @@ module.exports = ->
password: random
, true
deleteOne: (type) ->
listType = if type == 'completed' then 'todo' else type
selector = ".#{type}s a[data-original-title=\"Delete\"]"
deleteOne: (list, typeSelector) ->
selector = "#{typeSelector} a[data-original-title=\"Delete\"]"
@modelBeforeAfter (-> casper.click selector), (model) ->
# utils.dump model
casper.test.assertEquals Object.keys(model.before._user.tasks).length - 1, Object.keys(model.after._user.tasks).length, "1 #{type} deleted from user.tasks"
casper.test.assertEquals model.before._user["#{listType}Ids"].length - 1, model.after._user["#{listType}Ids"].length, "1 #{type} deleted from user._typeIds"
casper.test.assertEquals model.before["_#{listType}List"].length - 1, model.after["_#{listType}List"].length, "1 #{type} deleted from _typeList"
casper.test.assertEquals Object.keys(model.before._user.tasks).length - 1, Object.keys(model.after._user.tasks).length, "1 #{typeSelector} deleted from user.tasks"
casper.test.assertEquals model.before._user["#{list}Ids"].length - 1, model.after._user["#{list}Ids"].length, "1 #{typeSelector} deleted from user._typeIds"
casper.test.assertEquals model.before["_#{list}List"].length - 1, model.after["_#{list}List"].length, "1 #{typeSelector} deleted from _typeList"
}

View file

@ -1,54 +1,57 @@
helper = new require('./test/casper/helpers')()
casper = helper.casper
utils = helper.utils
url = helper.url
helpers = new require('./test/casper/helpers')()
casper = helpers.casper
utils = helpers.utils
url = helpers.playUrl
casper.start url + '/?play=1'
casper.start url
# ---------- Todos ------------
casper.then ->
helper.reset()
helper.addTasks()
helpers.reset()
helpers.addTasks(['todo'])
# Gained exp on +daily
# Completing todo gives exp and gp
casper.then ->
helper.modelBeforeAfter (-> casper.click '.todos input[type="checkbox"]'), (model) ->
helpers.modelBeforeAfter (-> casper.click '.todos .uncompleted input[type="checkbox"]'), (model) ->
casper.test.assertEquals model.before._user.stats.hp, 50, 'todo:hp starts at 50'
casper.test.assertEquals model.before._user.stats.hp, model.after._user.stats.hp, '+todo =hp'
casper.test.assertEquals model.after._user.stats.exp, 1, '+todo exp=1'
casper.test.assertEquals model.after._user.stats.money, 1, '+todo gp=1'
casper.test.assertEquals model.after._user.stats.gp, 1, '+todo gp=1'
#FIXME before._user.stats not fully available until modified? Is this a Derby JIT caching mechanism?
#casper.test.assert model.before._user.stats.exp < model.after._user.stats.exp, '+todo +exp'
#casper.test.assert model.before._user.stats.money < model.after._user.stats.money, '+todo +money'
casper.test.assert model.before._user.stats.exp < model.after._user.stats.exp, '+todo +exp'
casper.test.assert model.before._user.stats.gp < model.after._user.stats.gp, '+todo +gp'
# -daily acts as undo
# Can delete completed
casper.then -> helpers.deleteOne('todo', '.todos .completed')
# Can delete uncompleted
casper.then -> helpers.deleteOne('todo', '.todos .uncompleted')
# Uncompleting subtracts exp and gp
casper.then ->
helper.modelBeforeAfter (-> casper.click '.completeds input[type="checkbox"]'), (model) ->
casper.click '.todos .uncompleted input[type="checkbox"]'
helpers.modelBeforeAfter (-> casper.click '.todos .completed input[type="checkbox"]'), (model) ->
casper.test.assertEquals model.before._user.stats.hp, model.after._user.stats.hp, '-todo =hp'
casper.test.assert model.before._user.stats.exp > model.after._user.stats.exp, '-todo -exp'
casper.test.assert model.before._user.stats.money > model.after._user.stats.money, '-todo -money'
utils.dump {before:model.before._user.stats, after:model.after._user.stats}
casper.test.assert model.before._user.stats.gp > model.after._user.stats.gp, '-todo -gp'
casper.then -> helper.deleteOne('todo')
casper.then -> helper.deleteOne('completed')
# ---------- Cron ------------
casper.then ->
helper.reset()
helper.addTasks()
helpers.reset()
helpers.addTasks(['todo'])
casper.then ->
helper.cronBeforeAfter (beforeAfter) ->
helpers.cronBeforeAfter (model) ->
casper.then ->
#TODO make sure true for all todos
todoId = beforeAfter.before.tasks.todo[0].id
# utils.dump
# dailyBefore:user.before.tasks[dailyId].value
# dailyAfter:user.before.tasks[dailyId].value
casper.test.assert beforeAfter.before.user.tasks[todoId].value < beforeAfter.after.user.tasks[todoId].value, "todo:cron:todo gained value"
casper.test.assertEqual beforeAfter.before.user.stats.hp, beforeAfter.after.user.stats.hp, 'todo:cron:hp no change'
casper.test.assertEqual model.before._user.stats.hp, model.after._user.stats.hp, 'todo:cron:hp no change'
# Go through all the todos, all of them are uncompleted, so should all get a negative value
casper.echo "Testing all uncompleted todos after cron"
for id in model.before._user.todoIds
casper.test.assertEquals model.before._user.tasks[id].value, 0, "todo:cron:todo value before was 0"
casper.test.assert model.after._user.tasks[id].value < 0, "todo:cron:todo value after is negative"
# ---------- Run ------------

View file

@ -3,27 +3,4 @@
<ul class="alert alert-error">
{#each _flash.error as :error}<li>{:error}</li>{/}
</ul>
{/}
{#if equal(_user.flags.kickstarter,'show')}
<div class='alert alert-success'>
<a x-bind="click:closeKickstarterNofitication" class=pull-right>[x]</a>
<table><tr>
<td>
Hi everyone, last stretch of the <a target="_blank" href="http://kck.st/XoA3Yg">Kickstarter</a>! Let's get a <a target="_blank" href="http://www.kickstarter.com/projects/lefnire/habitrpg-mobile/posts/403016">ton</a> of new features plus mobile apps. With ~25k registered users, if everyone backs $1 we can do it! Help spread the word:
</td>
<td>
<div class="addthis_toolbox addthis_default_style "
addthis:url="https://habitrpg.com"
addthis:title="HabitRPG - Gamify Your Life">
<a class="addthis_button_facebook"></a>
<a class="addthis_button_twitter" tw:via="habitrpg"></a>
<a class="addthis_button_compact"></a>
<script type="text/javascript">var addthis_config = {"data_track_addressbar":true};</script>
</div>
</td>
</tr></table>
</div>
{/}

View file

@ -31,6 +31,7 @@
<li><a target="_blank" href="https://trello.com/board/habitrpg/50e5d3684fe3a7266b0036d6">Features</a></li>
<li><a target="_blank" href="https://github.com/lefnire/habitrpg/wiki/FAQ">FAQ</a></li>
<li><a target="_blank" href="https://github.com/lefnire/habitrpg/wiki/API">API</a></li>
<li><a target="_blank" href="https://github.com/lefnire/habitrpg/wiki/Add-ons-and-Extensions">Add-ons / Extensions</a></li>
<li><a target="_blank" href="https://github.com/lefnire/habitrpg#contact">Contact</a></li>
<li><a href="/privacy">Privacy</a></li>
<li><a href="/terms">Terms</a></li>