Merge branch 'develop' into apiv2
Conflicts: src/server/apiv2.coffee
This commit is contained in:
@@ -222,10 +222,12 @@ module.exports.app = (appExports, model, app) ->
|
||||
|
||||
app.on 'render', (ctx) ->
|
||||
#restoreRefs(model)
|
||||
setupSortable(model)
|
||||
setupTooltips(model)
|
||||
setupTour(model)
|
||||
initStickyHeader(model) unless model.get('_mobileDevice')
|
||||
unless model.get('_mobileDevice')
|
||||
setupTooltips(model)
|
||||
initStickyHeader(model)
|
||||
setupSortable(model)
|
||||
setupTour(model)
|
||||
|
||||
$('.datepicker').datepicker({autoclose:true, todayBtn:true})
|
||||
.on 'changeDate', (ev) ->
|
||||
#for some reason selecting a date doesn't fire a change event on the field, meaning our changes aren't saved
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
_ = require 'lodash'
|
||||
helpers = require 'habitrpg-shared/script/helpers'
|
||||
|
||||
module.exports.app = (appExports, model) ->
|
||||
browser = require './browser'
|
||||
user = model.at '_user'
|
||||
|
||||
$('#profile-challenges-tab-link').on 'show', (e) ->
|
||||
_.each model.get('groups'), (g) ->
|
||||
_.each g.challenges, (chal) ->
|
||||
_.each ['habit','daily','todo'], (type) ->
|
||||
_.each chal["#{type}s"], (task) ->
|
||||
_.each chal.users, (member) ->
|
||||
if (history = member?["#{type}s"]?[task.id]?.history) and !!history
|
||||
data = google.visualization.arrayToDataTable _.map(history, (h)-> [h.date,h.value])
|
||||
options =
|
||||
backgroundColor: { fill:'transparent' }
|
||||
width: 150
|
||||
height: 50
|
||||
chartArea: width: '80%', height: '80%'
|
||||
axisTitlePosition: 'none'
|
||||
legend: position: 'bottom'
|
||||
hAxis: gridlines: color: 'transparent' # since you can't seem to *remove* gridlines...
|
||||
vAxis: gridlines: color: 'transparent'
|
||||
chart = new google.visualization.LineChart $(".challenge-#{chal.id}-member-#{member.id}-history-#{task.id}")[0]
|
||||
chart.draw(data, options)
|
||||
|
||||
|
||||
appExports.challengeCreate = (e,el) ->
|
||||
[type, gid] = [$(el).attr('data-type'), $(el).attr('data-gid')]
|
||||
model.set '_challenge.new',
|
||||
name: ''
|
||||
habits: []
|
||||
dailys: []
|
||||
todos: []
|
||||
rewards: []
|
||||
id: model.id()
|
||||
uid: user.get('id')
|
||||
user: helpers.username(model.get('_user.auth'), model.get('_user.profile.name'))
|
||||
group: {type, id:gid}
|
||||
timestamp: +new Date
|
||||
|
||||
appExports.challengeSave = ->
|
||||
gid = model.get('_challenge.new.group.id')
|
||||
model.unshift "groups.#{gid}.challenges", model.get('_challenge.new'), ->
|
||||
browser.growlNotification('Challenge Created','success')
|
||||
challengeDiscard()
|
||||
|
||||
appExports.toggleChallengeEdit = (e, el) ->
|
||||
path = "_editing.challenges.#{$(el).attr('data-id')}"
|
||||
model.set path, !model.get(path)
|
||||
|
||||
appExports.challengeDiscard = challengeDiscard = -> model.del '_challenge.new'
|
||||
|
||||
appExports.challengeSubscribe = (e) ->
|
||||
chal = e.get()
|
||||
|
||||
# Add challenge name as a tag for user
|
||||
tags = user.get('tags')
|
||||
unless tags and _.find(tags,{id: chal.id})
|
||||
model.push '_user.tags', {id: chal.id, name: chal.name, challenge: true}
|
||||
|
||||
tags = {}; tags[chal.id] = true
|
||||
# Add all challenge's tasks to user's tasks
|
||||
userChallenges = user.get('challenges')
|
||||
user.unshift('challenges', chal.id) unless userChallenges and (userChallenges.indexOf(chal.id) != -1)
|
||||
_.each ['habit', 'daily', 'todo', 'reward'], (type) ->
|
||||
_.each chal["#{type}s"], (task) ->
|
||||
task.tags = tags
|
||||
task.challenge = chal.id
|
||||
task.group = {id: chal.group.id, type: chal.group.type}
|
||||
model.push("_#{type}List", task)
|
||||
true
|
||||
|
||||
appExports.challengeUnsubscribe = (e) ->
|
||||
chal = e.get()
|
||||
i = user.get('challenges')?.indexOf chal.id
|
||||
user.remove("challenges.#{i}") if i? and i != -1
|
||||
_.each ['habit', 'daily', 'todo', 'reward'], (type) ->
|
||||
_.each chal["#{type}s"], (task) ->
|
||||
model.remove "_#{type}List", _.findIndex(model.get("_#{type}List",{id:task.id}))
|
||||
model.del "_user.tasks.#{task.id}"
|
||||
true
|
||||
@@ -14,9 +14,7 @@ module.exports.app = (appExports, model) ->
|
||||
model.set '_newTag', ''
|
||||
|
||||
appExports.toggleEditingTags = ->
|
||||
before = model.get('_editingTags')
|
||||
model.set '_editingTags', !before, ->
|
||||
location.reload() if before is true #when they're done, refresh the page
|
||||
model.set '_editingTags', !model.get('_editingTags')
|
||||
|
||||
appExports.clearFilters = ->
|
||||
user.set 'filters', {}
|
||||
|
||||
@@ -0,0 +1,178 @@
|
||||
_ = require('lodash')
|
||||
helpers = require('habitrpg-shared/script/helpers')
|
||||
|
||||
module.exports.app = (appExports, model, app) ->
|
||||
browser = require './browser'
|
||||
|
||||
_currentTime = model.at '_currentTime'
|
||||
_currentTime.setNull +new Date
|
||||
# Every 60 seconds, reset the current time so that the chat can update relative times
|
||||
setInterval (->_currentTime.set +new Date), 60000
|
||||
|
||||
user = model.at('_user')
|
||||
|
||||
appExports.groupCreate = (e,el) ->
|
||||
type = $(el).attr('data-type')
|
||||
newGroup =
|
||||
name: model.get("_new.group.name")
|
||||
description: model.get("_new.group.description")
|
||||
leader: user.get('id')
|
||||
members: [user.get('id')]
|
||||
type: type
|
||||
|
||||
# parties - free
|
||||
if type is 'party'
|
||||
return model.add 'groups', newGroup, ->location.reload()
|
||||
|
||||
# guilds - 4G
|
||||
unless user.get('balance') >= 1
|
||||
return $('#more-gems-modal').modal 'show'
|
||||
if confirm "Create Guild for 4 Gems?"
|
||||
newGroup.privacy = (model.get("_new.group.privacy") || 'public') if type is 'guild'
|
||||
newGroup.balance = 1 # they spent $ to open the guild, it goes into their guild bank
|
||||
model.add 'groups', newGroup, ->
|
||||
user.incr 'balance', -1, ->location.reload()
|
||||
|
||||
appExports.toggleGroupEdit = (e, el) ->
|
||||
path = "_editing.groups.#{$(el).attr('data-gid')}"
|
||||
model.set path, !model.get(path)
|
||||
|
||||
appExports.toggleLeaderMessageEdit = (e, el) ->
|
||||
path = "_editing.leaderMessage.#{$(el).attr('data-gid')}"
|
||||
model.set path, !model.get(path)
|
||||
|
||||
appExports.groupAddWebsite = (e, el) ->
|
||||
test = e.get()
|
||||
e.at().unshift 'websites', model.get('_newGroupWebsite')
|
||||
model.del '_newGroupWebsite'
|
||||
|
||||
appExports.groupInvite = (e,el) ->
|
||||
uid = model.get('_groupInvitee').replace(/[\s"]/g, '')
|
||||
model.set '_groupInvitee', ''
|
||||
return if _.isEmpty(uid)
|
||||
|
||||
model.query('users').publicInfo([uid]).fetch (err, profiles) ->
|
||||
throw err if err
|
||||
profile = profiles.at(0).get()
|
||||
return model.set("_groupError", "User with id #{uid} not found.") unless profile
|
||||
model.query('groups').withMember(uid).fetch (err, g) ->
|
||||
throw err if err
|
||||
group = e.get(); groups = g.get()
|
||||
{type, name} = group; gid = group.id
|
||||
groupError = (msg) -> model.set("_groupError", msg)
|
||||
invite = ->
|
||||
$.bootstrapGrowl "Invitation Sent."
|
||||
switch type
|
||||
when 'guild' then model.push "users.#{uid}.invitations.guilds", {id:gid, name}, ->location.reload()
|
||||
when 'party' then model.set "users.#{uid}.invitations.party", {id:gid, name}, ->location.reload()
|
||||
|
||||
switch type
|
||||
when 'guild'
|
||||
if profile.invitations?.guilds and _.find(profile.invitations.guilds, {id:gid})
|
||||
return groupError("User already invited to that group")
|
||||
else if uid in group.members
|
||||
return groupError("User already in that group")
|
||||
else invite()
|
||||
when 'party'
|
||||
if profile.invitations?.party
|
||||
return groupError("User already pending invitation.")
|
||||
else if _.find(groups, {type:'party'})
|
||||
return groupError("User already in a party.")
|
||||
else invite()
|
||||
|
||||
joinGroup = (gid) ->
|
||||
model.push("groups.#{gid}.members", user.get('id'), ->location.reload())
|
||||
|
||||
appExports.joinGroup = (e, el) -> joinGroup e.get('id')
|
||||
|
||||
appExports.acceptInvitation = (e,el) ->
|
||||
gid = e.get('id')
|
||||
if $(el).attr('data-type') is 'party'
|
||||
user.set 'invitations.party', null, ->joinGroup(gid)
|
||||
else
|
||||
e.at().remove ->joinGroup(gid)
|
||||
|
||||
appExports.rejectInvitation = (e, el) ->
|
||||
clear = -> browser.resetDom(model)
|
||||
if e.at().path().indexOf('party') != -1
|
||||
model.del e.at().path(), clear
|
||||
else e.at().remove clear
|
||||
|
||||
appExports.groupLeave = (e,el) ->
|
||||
if confirm("Leave this group, are you sure?") is true
|
||||
uid = user.get('id')
|
||||
group = model.at "groups.#{$(el).attr('data-id')}"
|
||||
index = group.get('members').indexOf(uid)
|
||||
if index != -1
|
||||
group.remove 'members', index, 1, ->
|
||||
updated = group.get()
|
||||
# last member out, delete the party
|
||||
if _.isEmpty(updated.members) and (updated.type is 'party')
|
||||
group.del ->location.reload()
|
||||
# assign new leader, so the party is editable #TODO allow old leader to assign new leader, this is just random
|
||||
else if (updated.leader is uid)
|
||||
group.set "leader", updated.members[0], ->location.reload()
|
||||
else location.reload()
|
||||
|
||||
###
|
||||
Chat Functionality
|
||||
###
|
||||
|
||||
model.on 'unshift', '_party.chat', -> $('.chat-message').tooltip()
|
||||
model.on 'unshift', '_habitrpg.chat', -> $('.chat-message').tooltip()
|
||||
|
||||
appExports.sendChat = (e,el) ->
|
||||
text = model.get '_chatMessage'
|
||||
# Check for non-whitespace characters
|
||||
return unless /\S/.test text
|
||||
|
||||
group = e.at()
|
||||
|
||||
# get rid of duplicate member ids - this is a weird place to put it, but works for now
|
||||
members = group.get('members'); uniqMembers = _.uniq(members)
|
||||
group.set('members', uniqMembers) if !_.isEqual(uniqMembers, members)
|
||||
|
||||
chat = group.at('chat')
|
||||
model.set('_chatMessage', '')
|
||||
|
||||
message =
|
||||
id: model.id()
|
||||
uuid: user.get('id')
|
||||
contributor: user.get('backer.contributor')
|
||||
npc: user.get('backer.npc')
|
||||
text: text
|
||||
user: helpers.username(model.get('_user.auth'), model.get('_user.profile.name'))
|
||||
timestamp: +new Date
|
||||
|
||||
# FIXME - sometimes racer will send many duplicates via chat.unshift. I think because it can't make connection, keeps
|
||||
# trying, but all attempts go through. Unfortunately we can't do chat.set without potentially clobbering other chatters
|
||||
messages = chat.get() or []
|
||||
messages.unshift(message)
|
||||
messages.splice(200)
|
||||
chat.set messages
|
||||
|
||||
type = $(el).attr('data-type')
|
||||
model.set '_user.party.lastMessageSeen', chat.get()[0].id if group.get('type') is 'party'
|
||||
|
||||
appExports.chatKeyup = (e, el, next) ->
|
||||
return next() unless e.keyCode is 13
|
||||
appExports.sendChat(e, el)
|
||||
|
||||
appExports.deleteChatMessage = (e) ->
|
||||
if confirm("Delete chat message?") is true
|
||||
e.at().remove() #requires the {#with}
|
||||
|
||||
app.on 'render', (ctx) ->
|
||||
$('#party-tab-link').on 'shown', (e) ->
|
||||
messages = model.get('_party.chat')
|
||||
return false unless messages?.length > 0
|
||||
model.set '_user.party.lastMessageSeen', messages[0].id
|
||||
|
||||
appExports.gotoPartyChat = ->
|
||||
model.set '_gamePane', true, ->
|
||||
$('#party-tab-link').tab('show')
|
||||
|
||||
appExports.assignGroupLeader = (e, el) ->
|
||||
newLeader = model.get('_new.groupLeader')
|
||||
if newLeader and (confirm("Assign new leader, you sure?") is true)
|
||||
e.at().set('leader', newLeader, ->browser.resetDom(model)) if newLeader
|
||||
+53
-18
@@ -30,12 +30,18 @@ algos = require 'habitrpg-shared/script/algos'
|
||||
setupSubscriptions = (page, model, params, next, cb) ->
|
||||
uuid = model.get('_userId') or model.session.userId # see http://goo.gl/TPYIt
|
||||
selfQ = model.query('users').withId(uuid) #keep this for later
|
||||
partyQ = model.query('parties').withMember(uuid)
|
||||
|
||||
partyQ.fetch (err, party) ->
|
||||
# Note: due to https://github.com/codeparty/racer/issues/57, this has to come at the very beginning. The more limited
|
||||
# the returned fields in motifs, the sooner they must come in fetch / subscribes.
|
||||
publicGroupsQuery = model.query('groups').publicGroups()
|
||||
myGroupsQuery = model.query('groups').withMember(uuid)
|
||||
model.fetch publicGroupsQuery, myGroupsQuery, (err, publicGroups, groups) ->
|
||||
return next(err) if err
|
||||
|
||||
finished = (descriptors, paths) ->
|
||||
# Add public "Tavern" guild in
|
||||
descriptors.unshift('groups.habitrpg'); paths.unshift('_habitRPG')
|
||||
|
||||
# Subscribe to each descriptor
|
||||
model.subscribe.apply model, descriptors.concat ->
|
||||
[err, refs] = [arguments[0], arguments]
|
||||
return next(err) if err
|
||||
@@ -45,20 +51,40 @@ setupSubscriptions = (page, model, params, next, cb) ->
|
||||
return page.redirect('/logout') #delete model.session.userId
|
||||
return cb()
|
||||
|
||||
# (1) Solo player
|
||||
return finished([selfQ, 'tavern'], ['_user', '_tavern']) unless party.get()
|
||||
# Get public groups first, order most-to-least # subscribers
|
||||
model.set '_publicGroups', _.sortBy(publicGroups.get(), (g) -> -_.size(g.members))
|
||||
|
||||
groupsObj = groups.get()
|
||||
|
||||
# (1) Solo player
|
||||
return finished([selfQ], ['_user']) if _.isEmpty(groupsObj)
|
||||
|
||||
## (2) Party or Guild has members, fetch those users too
|
||||
# Subscribe to the groups themselves. We separate them by _party, _guilds, and _habitRPG (the "global" guild).
|
||||
groupsInfo = _.reduce groupsObj, ((m,g)->
|
||||
if g.type is 'guild' then m.guildIds.push(g.id) else m.partyId = g.id
|
||||
m.members = m.members.concat(g.members)
|
||||
m
|
||||
), {guildIds:[], partyId:null, members:[]}
|
||||
|
||||
# Fetch, not subscribe. There's nothing dynamic we need from members, just the the Group (below) which includes chat, challenges, etc
|
||||
model.query('users').publicInfo(groupsInfo.members).fetch (err, members) ->
|
||||
return next(err) if err
|
||||
# we need _members as an object in the view, so we can iterate over _party.members as :id, and access _members[:id] for the info
|
||||
mObj = members.get()
|
||||
model.set "_members", _.object(_.pluck(mObj,'id'), mObj)
|
||||
model.set "_membersArray", mObj
|
||||
|
||||
## (2) Party has members, subscribe to those users too
|
||||
if m = party.get('members')
|
||||
# Fetch instead of subscribe. There's nothing dynamic we need from members just yet, they'll update _party instead.
|
||||
# This may change in the future.
|
||||
model.query('users').party(m).fetch (err, members) ->
|
||||
return next(err) if err
|
||||
model.ref '_partyMembers', members
|
||||
return finished([partyQ, selfQ, 'tavern'], ['_party', '_user', '_tavern'])
|
||||
else
|
||||
# Note - selfQ *must* come after membersQ in subscribe, otherwise _user will only get the fields restricted by party-members in store.coffee. Strang bug, but easy to get around
|
||||
return finished([partyQ, selfQ, 'tavern'], ['_party', '_user', '_tavern'])
|
||||
descriptors = [selfQ]; paths = ['_user']
|
||||
if groupsInfo.partyId
|
||||
descriptors.unshift model.query('groups').withIds(groupsInfo.partyId)
|
||||
paths.unshift '_party'
|
||||
unless _.isEmpty(groupsInfo.guildIds)
|
||||
descriptors.unshift model.query('groups').withIds(groupsInfo.guildIds)
|
||||
paths.unshift '_guilds'
|
||||
finished descriptors, paths
|
||||
|
||||
|
||||
# ========== ROUTES ==========
|
||||
|
||||
@@ -78,15 +104,13 @@ get '/', (page, model, params, next) ->
|
||||
# ========== CONTROLLER FUNCTIONS ==========
|
||||
|
||||
ready (model) ->
|
||||
exports.removeAt = (e) -> e.at().remove() # used for things like remove website, chat, etc
|
||||
|
||||
user = model.at('_user')
|
||||
misc.fixCorruptUser(model) # https://github.com/lefnire/habitrpg/issues/634
|
||||
|
||||
browser = require './browser'
|
||||
require('./tasks').app(exports, model)
|
||||
require('./items').app(exports, model)
|
||||
require('./party').app(exports, model, app)
|
||||
require('./groups').app(exports, model, app)
|
||||
require('./profile').app(exports, model)
|
||||
require('./pets').app(exports, model)
|
||||
require('../server/private').app(exports, model)
|
||||
@@ -94,6 +118,14 @@ ready (model) ->
|
||||
browser.app(exports, model, app)
|
||||
require('./unlock').app(exports, model)
|
||||
require('./filters').app(exports, model)
|
||||
require('./challenges').app(exports, model)
|
||||
|
||||
# used for things like remove website, chat, etc
|
||||
exports.removeAt = (e, el) ->
|
||||
if (confirmMessage = $(el).attr 'data-confirm')?
|
||||
return unless confirm(confirmMessage) is true
|
||||
e.at().remove()
|
||||
browser.resetDom(model) if $(el).attr('data-refresh')
|
||||
|
||||
###
|
||||
Cron
|
||||
@@ -102,7 +134,10 @@ ready (model) ->
|
||||
# habitrpg-shared/algos requires uObj.habits, uObj.dailys etc instead of uObj.tasks
|
||||
_.each ['habit','daily','todo','reward'], (type) -> uObj["#{type}s"] = _.where(uObj.tasks, {type}); true
|
||||
algos.cron uObj, {paths}
|
||||
# for new user, just set lastCron - no need to reset dom.
|
||||
# remember that the properties are set from uObj & paths AFTER the return of this callback
|
||||
return if _.isEmpty(paths) or (paths['lastCron'] and _.size(paths) is 1)
|
||||
# for everyone else, we need to reset dom - too many changes have been made and won't it breaks dom listeners.
|
||||
if lostHp = delete paths['stats.hp'] # we'll set this manually so we can get a cool animation
|
||||
setTimeout ->
|
||||
browser.resetDom(model)
|
||||
|
||||
+63
-11
@@ -18,9 +18,24 @@ module.exports.batchTxn = batchTxn = (model, cb, options) ->
|
||||
# pass true if we have levelled to supress xp notification
|
||||
unless _.isEmpty paths
|
||||
setOps = _.reduce paths, ((m,v,k)-> m[k] = helpers.dotGet(k,uObj);m), {}
|
||||
user.set "update__", setOps
|
||||
user.set "update__", setOps, options?.done
|
||||
ret
|
||||
|
||||
#TODO put this in habitrpg-shared
|
||||
###
|
||||
We can't always use refLists, but we often still need to get a positional path by id: eg, users.1234.tasks.5678.value
|
||||
For arrays (which use indexes, not id-paths), here's a helper function so we can run indexedPath('users',:user.id,'tasks',:task.id,'value)
|
||||
###
|
||||
indexedPath = ->
|
||||
_.reduce arguments, (m,v) =>
|
||||
return v if !m #first iteration
|
||||
return "#{m}.#{v}" if _.isString v #string paths
|
||||
return "#{m}." + _.findIndex(@model.get(m),v)
|
||||
, ''
|
||||
|
||||
taskInChallenge = (task) ->
|
||||
return undefined unless task?.challenge
|
||||
@model.at indexedPath.call(@, "groups.#{task.group.id}.challenges", {id:task.challenge}, "#{task.type}s", {id:task.id})
|
||||
|
||||
###
|
||||
algos.score wrapper for habitrpg-helpers to work in Derby. We need to do model.set() instead of simply setting the
|
||||
@@ -29,8 +44,8 @@ module.exports.batchTxn = batchTxn = (model, cb, options) ->
|
||||
perform the updates while tracking paths, then all the values at those paths
|
||||
###
|
||||
module.exports.score = (model, taskId, direction, allowUndo=false) ->
|
||||
#return setTimeout( (-> score(taskId, direction)), 500) if model._txnQueue.length > 0
|
||||
batchTxn model, (uObj, paths) ->
|
||||
drop = undefined
|
||||
delta = batchTxn model, (uObj, paths) ->
|
||||
tObj = uObj.tasks[taskId]
|
||||
|
||||
# Stuff for undo
|
||||
@@ -44,10 +59,32 @@ module.exports.score = (model, taskId, direction, allowUndo=false) ->
|
||||
|
||||
delta = algos.score(uObj, tObj, direction, {paths})
|
||||
model.set('_streakBonus', uObj._tmp.streakBonus) if uObj._tmp?.streakBonus
|
||||
if uObj._tmp?.drop and $?
|
||||
model.set '_drop', uObj._tmp.drop
|
||||
drop = uObj._tmp?.drop
|
||||
|
||||
# Update challenge statistics
|
||||
# FIXME put this in it's own batchTxn, make batchTxn model.at() ref aware (not just _user)
|
||||
# FIXME use reflists for users & challenges
|
||||
if (chalTask = taskInChallenge.call({model}, tObj)) and chalTask?.get()
|
||||
model._dontPersist = false
|
||||
chalTask.incr "value", delta
|
||||
chal = model.at indexedPath.call({model}, "groups.#{tObj.group.id}.challenges", {id:tObj.challenge})
|
||||
chalUser = -> indexedPath.call({model}, chal.path(), 'users', {id:uObj.id})
|
||||
cu = model.at chalUser()
|
||||
unless cu?.get()
|
||||
chal.push "users", {id: uObj.id, name: helpers.username(uObj.auth, uObj.profile?.name)}
|
||||
cu = model.at chalUser()
|
||||
else
|
||||
cu.set 'name', helpers.username(uObj.auth, uObj.profile?.name) # update their name incase it changed
|
||||
cu.set "#{tObj.type}s.#{tObj.id}",
|
||||
value: tObj.value
|
||||
history: tObj.history
|
||||
model._dontPersist = true
|
||||
, done:->
|
||||
if drop and $?
|
||||
model.set '_drop', drop
|
||||
$('#item-dropped-modal').modal 'show'
|
||||
delta
|
||||
|
||||
delta
|
||||
|
||||
###
|
||||
Make sure model.get() returns all properties, see https://github.com/codeparty/racer/issues/116
|
||||
@@ -76,14 +113,15 @@ module.exports.fixCorruptUser = (model) ->
|
||||
user.del("tasks.#{key}")
|
||||
delete tasks[key]
|
||||
true
|
||||
|
||||
resetDom = false
|
||||
batchTxn model, (uObj, paths, batch) ->
|
||||
|
||||
## fix https://github.com/lefnire/habitrpg/issues/1086
|
||||
uniqPets = _.uniq(uObj.items.pets)
|
||||
batch.set('items.pets', uniqPets) if !_.isEqual(uniqPets, uObj.items.pets)
|
||||
console.log {uniqPets, count:_.size(uniqPets)}
|
||||
|
||||
if uObj.invitations?.guilds
|
||||
uniqInvites = _.uniq(uObj.invitations.guilds)
|
||||
batch.set('invitations.guilds', uniqInvites) if !_.isEqual(uniqInvites, uObj.invitations.guilds)
|
||||
|
||||
## Task List Cleanup
|
||||
['habit','daily','todo','reward'].forEach (type) ->
|
||||
@@ -91,7 +129,7 @@ module.exports.fixCorruptUser = (model) ->
|
||||
# 1. remove duplicates
|
||||
# 2. restore missing zombie tasks back into list
|
||||
idList = uObj["#{type}Ids"]
|
||||
taskIds = _.pluck( _.where(tasks, {type:type}), 'id')
|
||||
taskIds = _.pluck( _.where(tasks, {type}), 'id')
|
||||
union = _.union idList, taskIds
|
||||
|
||||
# 2. remove empty (grey) tasks
|
||||
@@ -128,12 +166,14 @@ module.exports.viewHelpers = (view) ->
|
||||
view.fn 'int',
|
||||
get: (num) -> num
|
||||
set: (num) -> [parseInt(num)]
|
||||
view.fn 'indexedPath', indexedPath
|
||||
|
||||
|
||||
#iCal
|
||||
view.fn "encodeiCalLink", helpers.encodeiCalLink
|
||||
|
||||
#User
|
||||
view.fn "gems", (balance) -> return balance/0.25
|
||||
view.fn "gems", (balance) -> balance * 4
|
||||
view.fn "username", helpers.username
|
||||
view.fn "tnl", algos.tnl
|
||||
view.fn 'equipped', helpers.equipped
|
||||
@@ -162,3 +202,15 @@ module.exports.viewHelpers = (view) ->
|
||||
#Tags
|
||||
view.fn 'noTags', helpers.noTags
|
||||
view.fn 'appliedTags', helpers.appliedTags
|
||||
|
||||
#Challenges
|
||||
view.fn 'taskInChallenge', (task) ->
|
||||
taskInChallenge.call(@,task)?.get()
|
||||
view.fn 'taskAttrFromChallenge', (task, attr) ->
|
||||
taskInChallenge.call(@,task)?.get(attr)
|
||||
view.fn 'brokenChallengeLink', (task) ->
|
||||
task?.challenge and !(taskInChallenge.call(@,task)?.get())
|
||||
|
||||
view.fn 'challengeMemberScore', (member, tType, tid) ->
|
||||
Math.round(member["#{tType}s"]?[tid]?.value)
|
||||
|
||||
|
||||
@@ -1,147 +0,0 @@
|
||||
_ = require('lodash')
|
||||
helpers = require('habitrpg-shared/script/helpers')
|
||||
|
||||
module.exports.app = (appExports, model, app) ->
|
||||
browser = require './browser'
|
||||
|
||||
_currentTime = model.at '_currentTime'
|
||||
|
||||
_currentTime.setNull +new Date()
|
||||
|
||||
# Every 60 seconds, reset the current time so that the chat
|
||||
# can update relative times
|
||||
setInterval ->
|
||||
_currentTime.set +new Date()
|
||||
, 60000
|
||||
|
||||
user = model.at('_user')
|
||||
|
||||
model.on 'set', '_user.party.invitation', (after, before) ->
|
||||
if !before? and after? # they just got invited
|
||||
partyQ = model.query('parties').withId(after)
|
||||
partyQ.fetch (err, party) ->
|
||||
return next(err) if err
|
||||
model.ref '_party', party
|
||||
browser.resetDom(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}, ->
|
||||
window.location.reload true
|
||||
|
||||
appExports.partyInvite = ->
|
||||
id = model.get('_newPartyMember').replace(/[\s"]/g, '')
|
||||
return if _.isEmpty(id)
|
||||
|
||||
model.query('users').party([id]).fetch (err, users) ->
|
||||
throw err if err
|
||||
u = users.at(0).get()
|
||||
if !u?
|
||||
model.set "_partyError", "User with id #{id} not found."
|
||||
return
|
||||
else if u.party.current? or u.party.invitation?
|
||||
model.set "_partyError", "User already in a party or pending invitation."
|
||||
return
|
||||
else
|
||||
$.bootstrapGrowl "Invitation Sent."
|
||||
model.set "users.#{id}.party.invitation", model.get('_party.id'), -> window.location.reload()
|
||||
#model.set '_newPartyMember', ''
|
||||
#partySubscribe model
|
||||
|
||||
appExports.partyAccept = ->
|
||||
partyId = user.get('party.invitation')
|
||||
user.set 'party.invitation', null
|
||||
user.set 'party.current', partyId
|
||||
model.at("parties.#{partyId}.members").push user.get('id'), -> window.location.reload()
|
||||
# model.query('parties').withId(partyId).fetch (err, p) ->
|
||||
# members = p.get('members')
|
||||
# members.push user.get('id')
|
||||
# p.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
|
||||
browser.resetDom(model)
|
||||
|
||||
appExports.partyLeave = ->
|
||||
id = user.set 'party.current', null
|
||||
party = model.at '_party'
|
||||
members = party.get('members')
|
||||
index = members.indexOf(user.get('id'))
|
||||
party.remove 'members', index, 1, ->
|
||||
if members.length is 1 # # last member out, kill the party
|
||||
model.del "parties.#{id}", (-> window.location.reload true)
|
||||
else
|
||||
window.location.reload true
|
||||
|
||||
###
|
||||
Chat Functionality
|
||||
###
|
||||
|
||||
sendChat = (path, input) ->
|
||||
chat = model.at path
|
||||
text = model.get input
|
||||
# Check for non-whitespace characters
|
||||
return unless /\S/.test text
|
||||
model.set(input, '')
|
||||
|
||||
message =
|
||||
id: model.id()
|
||||
uuid: user.get('id')
|
||||
contributor: user.get('backer.contributor')
|
||||
npc: user.get('backer.npc')
|
||||
text: text
|
||||
user: helpers.username(model.get('_user.auth'), model.get('_user.profile.name'))
|
||||
timestamp: +new Date
|
||||
|
||||
# FIXME - sometimes racer will send many duplicates via chat.unshift. I think because it can't make connection, keeps
|
||||
# trying, but all attempts go through. Unfortunately we can't do chat.set without potentially clobbering other chatters,
|
||||
# and we can't make chat an object without using refLists. hack solution for now is to unshift, and if there are dupes
|
||||
# after we set to unique
|
||||
chat.unshift message, ->
|
||||
messages = chat.get() || []
|
||||
count = messages.length
|
||||
messages =_.uniq messages, true, ((m) -> m?.id) # get rid of dupes
|
||||
#There were a bunch of duplicates, let's clean it up
|
||||
if messages.length != count
|
||||
messages.splice(200)
|
||||
chat.set messages
|
||||
else
|
||||
chat.remove(200)
|
||||
|
||||
model.on 'unshift', '_party.chat', -> $('.chat-message').tooltip()
|
||||
model.on 'unshift', '_tavern.chat.messages', -> $('.chat-message').tooltip()
|
||||
|
||||
appExports.partySendChat = ->
|
||||
sendChat('_party.chat', '_chatMessage')
|
||||
model.set '_user.party.lastMessageSeen', model.get('_party.chat')[0].id
|
||||
|
||||
appExports.tavernSendChat = ->
|
||||
sendChat('_tavern.chat.messages', '_tavernMessage')
|
||||
|
||||
appExports.partyMessageKeyup = (e, el, next) ->
|
||||
return next() unless e.keyCode is 13
|
||||
appExports.partySendChat()
|
||||
|
||||
appExports.tavernMessageKeyup = (e, el, next) ->
|
||||
return next() unless e.keyCode is 13
|
||||
appExports.tavernSendChat()
|
||||
|
||||
appExports.deleteChatMessage = (e) ->
|
||||
if confirm("Delete chat message?") is true
|
||||
e.at().remove() #requires the {#with}
|
||||
|
||||
app.on 'render', (ctx) ->
|
||||
$('#party-tab-link').on 'shown', (e) ->
|
||||
messages = model.get('_party.chat')
|
||||
return false unless messages?.length > 0
|
||||
model.set '_user.party.lastMessageSeen', messages[0].id
|
||||
|
||||
appExports.gotoPartyChat = ->
|
||||
model.set '_gamePane', true, ->
|
||||
$('#party-tab-link').tab('show')
|
||||
+5
-6
@@ -25,12 +25,11 @@ module.exports.app = (appExports, model) ->
|
||||
return alert "You don't own that egg yet, complete more tasks!" if eggIdx is -1
|
||||
return alert "You already have that pet, hatch a different combo." if myPets and myPets.indexOf("#{egg.name}-#{hatchingPotionName}") != -1
|
||||
|
||||
user.push 'items.pets', egg.name + '-' + hatchingPotionName
|
||||
|
||||
eggs.splice eggIdx, 1
|
||||
myHatchingPotion.splice hatchingPotionIdx, 1
|
||||
user.set 'items.eggs', eggs
|
||||
user.set 'items.hatchingPotions', myHatchingPotion
|
||||
user.push 'items.pets', egg.name + '-' + hatchingPotionName, ->
|
||||
eggs.splice eggIdx, 1
|
||||
myHatchingPotion.splice hatchingPotionIdx, 1
|
||||
user.set 'items.eggs', eggs
|
||||
user.set 'items.hatchingPotions', myHatchingPotion
|
||||
|
||||
alert 'Your egg hatched! Visit your stable to equip your pet.'
|
||||
|
||||
|
||||
+28
-44
@@ -18,8 +18,9 @@ module.exports.app = (appExports, model) ->
|
||||
# Don't add a blank task; 20/02/13 Added a check for undefined value, more at issue #463 -lancemanfv
|
||||
return if /^(\s)*$/.test(text) || text == undefined
|
||||
|
||||
activeFilters = _.reduce user.get('filters'), ((memo,v,k) -> memo[k]=v if v;memo), {}
|
||||
newTask = {id: model.id(), type: type, text: text, notes: '', value: 0, tags: activeFilters}
|
||||
newTask = {id: model.id(), type, text, notes: '', value: 0}
|
||||
newTask.tags = _.reduce user.get('filters'), ((memo,v,k) -> memo[k]=v if v; memo), {}
|
||||
|
||||
switch type
|
||||
when 'habit'
|
||||
newTask = _.defaults {up: true, down: true}, newTask
|
||||
@@ -29,34 +30,14 @@ module.exports.app = (appExports, model) ->
|
||||
newTask = _.defaults {repeat:{su:true,m:true,t:true,w:true,th:true,f:true,s:true}, completed: false }, newTask
|
||||
when 'todo'
|
||||
newTask = _.defaults {completed: false }, newTask
|
||||
model.unshift "_#{type}List", newTask
|
||||
e.at().unshift newTask # e.at() in this case is the list, which was scoped here using {#with @list}...{/}
|
||||
newModel.set ''
|
||||
|
||||
appExports.del = (e) ->
|
||||
# Derby extends model.at to support creation from DOM nodes
|
||||
task = e.at()
|
||||
id = task.get('id')
|
||||
|
||||
history = task.get('history')
|
||||
if history and history.length > 2
|
||||
# prevent delete-and-recreate hack on red tasks
|
||||
if task.get('value') < 0
|
||||
if confirm("Are you sure? Deleting this task will hurt you (to prevent deleting, then re-creating red tasks).") is true
|
||||
task.set('type','habit') # hack to make sure it hits HP, instead of performing "undo checkbox"
|
||||
misc.score(model, id, 'down', true)
|
||||
else
|
||||
return # Cancel. Don't delete, don't hurt user
|
||||
|
||||
# prevent accidently deleting long-standing tasks
|
||||
else
|
||||
return unless confirm("Are you sure you want to delete this task?") is true
|
||||
|
||||
#TODO bug where I have to delete from _users.tasks AND _{type}List,
|
||||
# fix when query subscriptions implemented properly
|
||||
return unless confirm("Are you sure you want to delete this task?") is true
|
||||
$('[rel=tooltip]').tooltip('hide')
|
||||
|
||||
user.del('tasks.'+id)
|
||||
task.remove()
|
||||
user.del "tasks.#{e.get('id')}"
|
||||
e.at().remove()
|
||||
|
||||
|
||||
appExports.clearCompleted = (e, el) ->
|
||||
@@ -74,31 +55,34 @@ module.exports.app = (appExports, model) ->
|
||||
task.set('repeat.' + $(el).attr('data-day'), true)
|
||||
|
||||
appExports.toggleTaskEdit = (e, el) ->
|
||||
hideId = $(el).attr('data-hide-id')
|
||||
toggleId = $(el).attr('data-toggle-id')
|
||||
$(document.getElementById(hideId)).addClass('visuallyhidden')
|
||||
$(document.getElementById(toggleId)).toggleClass('visuallyhidden')
|
||||
id = e.get('id')
|
||||
[editPath, chartPath] = ["_tasks.editing.#{id}", "_page.charts.#{id}"]
|
||||
model.set editPath, !(model.get editPath)
|
||||
model.set chartPath, false
|
||||
|
||||
appExports.toggleChart = (e, el) ->
|
||||
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()
|
||||
id = $(el).attr('data-id')
|
||||
[historyPath, togglePath] = ['','']
|
||||
|
||||
switch id
|
||||
when 'exp'
|
||||
[togglePath, historyPath] = ['_page.charts.exp', '_user.history.exp']
|
||||
when 'todos'
|
||||
[togglePath, historyPath] = ['_page.charts.todos', '_user.history.todos']
|
||||
else
|
||||
[togglePath, historyPath] = ["_page.charts.#{id}", "_user.tasks.#{id}.history"]
|
||||
model.set "_tasks.editing.#{id}", false
|
||||
|
||||
history = model.get(historyPath)
|
||||
model.set togglePath, !(model.get togglePath)
|
||||
|
||||
matrix = [['Date', 'Score']]
|
||||
for obj in model.get(historyPath)
|
||||
date = +new Date(obj.date)
|
||||
readableDate = moment(date).format('MM/DD')
|
||||
matrix.push [ readableDate, obj.value ]
|
||||
_.each history, (obj) -> matrix.push([ moment(obj.date).format('MM/DD/YY'), obj.value ])
|
||||
data = google.visualization.arrayToDataTable matrix
|
||||
|
||||
options = {
|
||||
options =
|
||||
title: 'History'
|
||||
backgroundColor: { fill:'transparent' }
|
||||
}
|
||||
|
||||
chart = new google.visualization.LineChart(document.getElementById( chartSelector ))
|
||||
chart = new google.visualization.LineChart $(".#{id}-chart")[0]
|
||||
chart.draw(data, options)
|
||||
|
||||
appExports.todosShowRemaining = -> model.set '_showCompleted', false
|
||||
|
||||
@@ -75,9 +75,9 @@ module.exports.app = (appExports, model) ->
|
||||
user.on 'set', 'items.*', (after, before) ->
|
||||
return if user.get('achievements.ultimateGear')
|
||||
items = user.get('items')
|
||||
if parseInt(items.weapon) == 6 and parseInt(items.armor) == 5 and parseInt(items.head) == 5 and parseInt(items.shield) == 5
|
||||
dontPersist = model._dontPersist; model._dontPersist = false
|
||||
user.set 'achievements.ultimateGear', true, (-> model._dontPersist = dontPersist)
|
||||
if parseInt(items.weapon) >= 6 and parseInt(items.armor) >= 5 and parseInt(items.head) >= 5 and parseInt(items.shield) >= 5
|
||||
dontPersist = model._dontPersist; model._dontPersist = false
|
||||
user.set 'achievements.ultimateGear', true, (->model._dontPersist = dontPersist)
|
||||
$('#max-gear-achievement-modal').modal('show')
|
||||
|
||||
user.on 'set', 'tasks.*.streak', (id, after, before) ->
|
||||
|
||||
+80
-35
@@ -7,11 +7,16 @@ helpers = require 'habitrpg-shared/script/helpers'
|
||||
validator = require 'derby-auth/node_modules/validator'
|
||||
check = validator.check
|
||||
sanitize = validator.sanitize
|
||||
misc = require '../app/misc'
|
||||
utils = require 'derby-auth/utils'
|
||||
|
||||
NO_TOKEN_OR_UID = err: "You must include a token and uid (user id) in your request"
|
||||
NO_USER_FOUND = err: "No user found."
|
||||
|
||||
addTask = (user, task) ->
|
||||
task.type ?= 'habit'
|
||||
tid = user.add "tasks", task
|
||||
user.push "#{task.type}Ids", tid
|
||||
|
||||
# ---------- /api/v1 API ------------
|
||||
# Every url added beneath router is prefaced by /api/v1
|
||||
|
||||
@@ -42,7 +47,7 @@ auth = (req, res, next) ->
|
||||
return res.json err: err if err
|
||||
req.user = user
|
||||
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 _.isEmpty(req.userObj)
|
||||
req._isServer = true
|
||||
next()
|
||||
|
||||
@@ -92,6 +97,56 @@ router.put '/user', auth, (req, res) ->
|
||||
userObj.tasks = _.toArray(userObj.tasks) # FIXME figure out how we're going to consistently handle this. should always be array
|
||||
res.json 201, userObj
|
||||
|
||||
###
|
||||
POST /user/auth/local
|
||||
###
|
||||
router.post '/user/auth/local', (req, res) ->
|
||||
username = req.body.username
|
||||
password = req.body.password
|
||||
return res.json 401, err: 'No username or password' unless username and password
|
||||
|
||||
model = req.getModel()
|
||||
|
||||
q = model.query("users").withUsername(username)
|
||||
q.fetch (err, result1) ->
|
||||
return res.json 401, { err } if err
|
||||
u1 = result1.get()
|
||||
return res.json 401, err: 'Username not found' unless u1 # user not found
|
||||
|
||||
# We needed the whole user object first so we can get his salt to encrypt password comparison
|
||||
q = model.query("users").withLogin(username, utils.encryptPassword(password, u1.auth.local.salt))
|
||||
q.fetch (err, result2) ->
|
||||
return res.json 401, { err } if err
|
||||
|
||||
# joshua tree?
|
||||
u2 = result2.get()
|
||||
return res.json 401, err: 'Incorrect password' unless u2
|
||||
|
||||
res.json
|
||||
id: u2.id
|
||||
token: u2.apiToken
|
||||
|
||||
###
|
||||
POST /user/auth/facebook
|
||||
###
|
||||
router.post '/user/auth/facebook', (req, res) ->
|
||||
{facebook_id, email, name} = req.body
|
||||
return res.json 401, err: 'No facebook id provided' unless facebook_id
|
||||
model = req.getModel()
|
||||
q = model.query("users").withProvider('facebook', facebook_id)
|
||||
q.fetch (err, result) ->
|
||||
return res.json 401, { err } if err
|
||||
u = result.get()
|
||||
console.log {facebook_id, u}
|
||||
if u
|
||||
return res.json
|
||||
id: u.id
|
||||
token: u.apiToken
|
||||
else
|
||||
# FIXME: create a new user instead
|
||||
return res.json 403, err: "Please register with Facebook on https://habitrpg.com, then come back here and log in."
|
||||
|
||||
|
||||
###
|
||||
GET /user/task/:id
|
||||
###
|
||||
@@ -163,17 +218,17 @@ updateTasks = (tasks, user, model) ->
|
||||
if task.id
|
||||
if task.del
|
||||
user.del "tasks.#{task.id}"
|
||||
if task.type # TODO we should enforce they pass in type, so we can properly remove from idList
|
||||
i = model.get("_#{task.type}List").indexOf(task.id)
|
||||
model.remove("_#{task.type}List", i, 1) # doens't work when task.type isn't passed up
|
||||
|
||||
# Delete from id list, only if type is passed up
|
||||
# TODO we should enforce they pass in type, so we can properly remove from idList
|
||||
if task.type and ~(i = user.get("#{task.type}Ids").indexOf task.id)
|
||||
user.remove("#{task.type}Ids", i, 1)
|
||||
|
||||
task = deleted: true
|
||||
else
|
||||
user.set "tasks.#{task.id}", task
|
||||
else
|
||||
type = task.type || 'habit'
|
||||
model.ref '_user', user
|
||||
model.refList "_#{type}List", "_user.tasks", "_user.#{type}Ids"
|
||||
model.at("_#{type}List").push task
|
||||
addTask(user, task)
|
||||
tasks[idx] = task
|
||||
return tasks
|
||||
|
||||
@@ -187,31 +242,19 @@ router.post '/user/tasks', auth, (req, res) ->
|
||||
###
|
||||
router.post '/user/task', auth, validateTask, (req, res) ->
|
||||
task = req.task
|
||||
type = task.type
|
||||
|
||||
model = req.getModel()
|
||||
model.ref '_user', req.user
|
||||
model.refList "_#{type}List", "_user.tasks", "_user.#{type}Ids"
|
||||
model.at("_#{type}List").push task
|
||||
|
||||
addTask req.user, task
|
||||
res.json 201, task
|
||||
|
||||
###
|
||||
GET /user/tasks
|
||||
###
|
||||
router.get '/user/tasks', auth, (req, res) ->
|
||||
user = req.userObj
|
||||
return res.json 400, NO_USER_FOUND if !user || _.isEmpty(user)
|
||||
return res.json 400, NO_USER_FOUND if _.isEmpty(req.userObj)
|
||||
|
||||
model = req.getModel()
|
||||
model.ref '_user', req.user
|
||||
tasks = []
|
||||
types = ['habit','todo','daily','reward']
|
||||
if /^(habit|todo|daily|reward)$/.test req.query.type
|
||||
types = [req.query.type]
|
||||
for type in types
|
||||
model.refList "_#{type}List", "_user.tasks", "_user.#{type}Ids"
|
||||
tasks = tasks.concat model.get("_#{type}List")
|
||||
types =
|
||||
if /^(habit|todo|daily|reward)$/.test(req.query.type) then [req.query.type]
|
||||
else ['habit','todo','daily','reward']
|
||||
tasks = _.toArray (_.filter req.user.get('tasks'), (t)-> t.type in types)
|
||||
|
||||
res.json 200, tasks
|
||||
|
||||
@@ -230,9 +273,7 @@ scoreTask = (req, res, next) ->
|
||||
model = req.getModel()
|
||||
{user, userObj} = req
|
||||
|
||||
model.ref('_user', user)
|
||||
|
||||
existingTask = model.at "_user.tasks.#{taskId}"
|
||||
existingTask = user.at "tasks.#{taskId}"
|
||||
# TODO add service & icon to task
|
||||
# If task exists, set it's compltion
|
||||
if existingTask.get()
|
||||
@@ -253,12 +294,16 @@ scoreTask = (req, res, next) ->
|
||||
when 'daily', 'todo'
|
||||
task.completed = direction is 'up'
|
||||
|
||||
model.refList "_#{type}List", "_user.tasks", "_user.#{type}Ids"
|
||||
model.at("_#{type}List").push task
|
||||
addTask user, task
|
||||
|
||||
#FIXME
|
||||
delta = misc.score(model, taskId, direction)
|
||||
result = model.get '_user.stats'
|
||||
# TODO - could modify batchTxn to conform to this better
|
||||
uObj = req.user.get()
|
||||
tObj = uObj.tasks[taskId]
|
||||
paths = {}
|
||||
delta = algos.score(uObj, tObj, direction, {paths})
|
||||
_.each paths, (v,k) -> user.set(k,helpers.dotGet(k, uObj));true
|
||||
|
||||
result = uObj.stats
|
||||
result.delta = delta
|
||||
res.json result
|
||||
|
||||
|
||||
@@ -101,7 +101,6 @@ router.post '/', auth, (req, res) ->
|
||||
if (typeof oldValue == "number" || typeof oldValue == "string")
|
||||
user.set(action.path, action.value)
|
||||
|
||||
|
||||
user = misc.hydrate user.get()
|
||||
|
||||
#transform user structure FROM user.tasks{} + user.habitIds[] TO user.habits[] + user.todos[] etc.
|
||||
|
||||
@@ -23,9 +23,6 @@ racer.set('bundleTimeout', 40000)
|
||||
# racer.use(racer.logPlugin)
|
||||
# derby.use(derby.logPlugin)
|
||||
|
||||
# Infinite stack trace
|
||||
Error.stackTraceLimit = Infinity if process.env.NODE_ENV is 'development'
|
||||
|
||||
## SERVER CONFIGURATION ##
|
||||
|
||||
expressApp = express()
|
||||
|
||||
+53
-48
@@ -5,10 +5,15 @@ Setup read / write access
|
||||
@param store
|
||||
###
|
||||
|
||||
publicAccess = ->
|
||||
accept = arguments[arguments.length-2]
|
||||
#return err(derbyAuth.SESSION_INVALIDATED_ERROR) if derbyAuth.bustedSession(@)
|
||||
return accept(false) if derbyAuth.bustedSession(@)
|
||||
accept(true)
|
||||
|
||||
module.exports.customAccessControl = (store) ->
|
||||
userAccess(store)
|
||||
partySystem(store)
|
||||
tavernSystem(store)
|
||||
groupSystem(store)
|
||||
REST(store)
|
||||
|
||||
###
|
||||
@@ -43,7 +48,7 @@ userAccess = (store) ->
|
||||
return accept(false) # we can only manually set this stuff in the database
|
||||
|
||||
# public access to users.*.party.invitation (TODO, lock down a bit more)
|
||||
if attrPath is 'party.invitation'
|
||||
if attrPath.indexOf('invitations.') is 0
|
||||
return accept(true)
|
||||
|
||||
# Same session (user.id = this.session.userId)
|
||||
@@ -84,63 +89,63 @@ REST = (store) ->
|
||||
|
||||
|
||||
###
|
||||
Party permissions
|
||||
Party & Guild Permissions
|
||||
###
|
||||
partySystem = (store) ->
|
||||
store.query.expose "users", "party", (ids) ->
|
||||
groupSystem = (store) ->
|
||||
|
||||
###
|
||||
Public User Info
|
||||
###
|
||||
store.query.expose "users", "publicInfo", (ids) ->
|
||||
@where("id").within(ids)
|
||||
.only('stats',
|
||||
'items',
|
||||
'party',
|
||||
'invitations',
|
||||
'profile',
|
||||
'achievements',
|
||||
'backer',
|
||||
'preferences',
|
||||
'auth.local.username',
|
||||
'auth.facebook.displayName')
|
||||
store.queryAccess "users", "publicInfo", publicAccess
|
||||
|
||||
store.queryAccess "users", "party", (ids, accept, err) ->
|
||||
# return err(derbyAuth.SESSION_INVALIDATED_ERROR) if derbyAuth.bustedSession(@)
|
||||
return accept(false) if derbyAuth.bustedSession(@)
|
||||
accept(true) # no harm in public user stats
|
||||
###
|
||||
Read / Write groups, so they can create new groups
|
||||
###
|
||||
store.readPathAccess "groups.*", publicAccess
|
||||
store.writeAccess "*", "groups.*", publicAccess
|
||||
|
||||
store.query.expose "parties", "withId", (id) ->
|
||||
@where("id").equals(id).findOne()
|
||||
###
|
||||
Public HabitRPG Guild
|
||||
###
|
||||
store.readPathAccess 'groups.habitrpg', publicAccess
|
||||
store.writeAccess "*", "groups.habitrpg.chat.*", publicAccess
|
||||
store.writeAccess "*", "groups.habitrpg.challenges.*", publicAccess
|
||||
|
||||
store.queryAccess "parties", "withId", (id, accept, err) ->
|
||||
# return err(derbyAuth.SESSION_INVALIDATED_ERROR) if derbyAuth.bustedSession(@)
|
||||
return accept(false) if derbyAuth.bustedSession(@)
|
||||
accept(true)
|
||||
###
|
||||
Find group which has member by id
|
||||
###
|
||||
store.query.expose "groups", "withMember", (id, type) ->
|
||||
q = @where('members').contains([id]).only(['id', 'type', 'name', 'description', 'members' , 'privacy'])
|
||||
q = q.where('type').equals(type) if type?
|
||||
store.queryAccess 'groups', 'withMember', publicAccess
|
||||
|
||||
store.readPathAccess "parties.*", ->
|
||||
accept = arguments[arguments.length-2]
|
||||
accept(true)
|
||||
|
||||
store.writeAccess "*", "parties.*", ->
|
||||
accept = arguments[arguments.length-2]
|
||||
err = arguments[arguments.length - 1]
|
||||
# return err(derbyAuth.SESSION_INVALIDATED_ERROR) if derbyAuth.bustedSession(@)
|
||||
return accept(false) if derbyAuth.bustedSession(@)
|
||||
accept(true)
|
||||
|
||||
store.query.expose "parties", "withMember", (id) ->
|
||||
@where('members').contains([id]).findOne()
|
||||
|
||||
store.queryAccess 'parties', 'withMember', (id, accept, err) ->
|
||||
return accept(false) if derbyAuth.bustedSession(@)
|
||||
accept(true)
|
||||
|
||||
###
|
||||
LFG / tavern system
|
||||
###
|
||||
tavernSystem = (store) ->
|
||||
store.readPathAccess 'tavern', ->
|
||||
accept = arguments[arguments.length-2]
|
||||
return accept(false) if derbyAuth.bustedSession(@)
|
||||
accept(true)
|
||||
|
||||
store.writeAccess "*", "tavern.*", ->
|
||||
accept = arguments[arguments.length-2]
|
||||
return accept(false) if derbyAuth.bustedSession(@)
|
||||
accept(true)
|
||||
###
|
||||
Public Groups Info
|
||||
###
|
||||
store.query.expose "groups", "publicGroups", ->
|
||||
@where('privacy').equals('public')
|
||||
.where('type').equals('guild')
|
||||
.only(['id', 'type', 'name', 'description', 'members' , 'privacy'])
|
||||
store.queryAccess "groups", "publicGroups", publicAccess
|
||||
|
||||
###
|
||||
Fetch group info (ie, they just got invited)
|
||||
###
|
||||
store.query.expose "groups", "withIds", (ids) ->
|
||||
return unless ids #FIXME this is sometimes null when ids is array (guilds)
|
||||
if typeof ids is 'string'
|
||||
@where("id").equals(ids).findOne() # find a single group
|
||||
else
|
||||
@where("id").within(ids) # find multiple groups
|
||||
store.queryAccess "groups", "withIds", publicAccess
|
||||
Reference in New Issue
Block a user