rewrite & api: use mongoose populate to replace group.members array with

popualte member objects. this puts a load on the front-end, we'll need
to optimize! also added some duplicates sanitization.
This commit is contained in:
Tyler Renelle
2013-08-31 17:47:03 -04:00
parent 10ff61be91
commit 45383a9630
6 changed files with 96 additions and 88 deletions

View File

@@ -4,7 +4,9 @@ habitrpg
.controller("GroupsCtrl", ['$scope', '$rootScope', 'Groups', '$http', '$location', .controller("GroupsCtrl", ['$scope', '$rootScope', 'Groups', '$http', '$location',
function($scope, $rootScope, Groups) { function($scope, $rootScope, Groups) {
$scope.groups = Groups.query(); $scope.groups = Groups.query(function(groups){
$scope.members = groups.members;
});
$scope.party = true; $scope.party = true;
} }
]) ])

View File

@@ -18,34 +18,23 @@ var api = module.exports;
api.getGroups = function(req, res, next) { api.getGroups = function(req, res, next) {
var user = res.locals.user; var user = res.locals.user;
/*TODO should we support non-authenticated users? just for viewing public groups?*/ var usernameFields = 'auth.local.username auth.facebook.first_name auth.facebook.last_name auth.facebook.name auth.facebook.username';
return async.parallel({ // First get all groups
async.parallel({
party: function(cb) { party: function(cb) {
async.waterfall([ Group
function(cb2) { .findOne({type: 'party', members: {'$in': [user._id]}})
Group.findOne({type: 'party', members: {'$in': [user._id]}}, cb2); .populate({
}, function(party, cb2) { path: 'members',
var fields, query; //match: {_id: {$ne: user._id}}, //fixme this causes it to hang??
party = party.toJSON(); select: 'profile preferences items stats achievements party backer ' + usernameFields
query = {_id: { })
'$in': party.members, .exec(cb);
'$nin': [user._id]
}
};
fields = 'profile preferences items stats achievements party backer auth.local.username auth.facebook.first_name auth.facebook.last_name auth.facebook.name auth.facebook.username'.split(' ');
fields = _.reduce(fields, (function(m, k, v) {m[k] = 1;return m;}), {});
User.find(query, fields, function(err, members) {
party.members = members;
cb2(err, party);
});
}
], function(err, members) {
cb(err, members);
});
}, },
guilds: function(cb) { guilds: function(cb) {
Group.find({type: 'guild', members: {'$in': [user._id]}}, cb); Group.find({type: 'guild', members: {'$in': [user._id]}}).populate('members', usernameFields).exec(cb);
// Group.find({type: 'guild', members: {'$in': [user._id]}}, cb);
}, },
tavern: function(cb) { tavern: function(cb) {
Group.findOne({_id: 'habitrpg'}, cb); Group.findOne({_id: 'habitrpg'}, cb);
@@ -59,8 +48,13 @@ api.getGroups = function(req, res, next) {
members: 1 members: 1
}, cb); }, cb);
} }
}, function(err, results) { }, function(err, results){
if (err) return res.json(500, {err: err}); if (err) return res.json(500, {err: err});
// Remove self from party (see above failing `match` directive in `populate`
var i = _.findIndex(results.party.members, {_id:user._id});
if (~i) results.party.members.splice(i,1);
res.json(results); res.json(results);
}); })
}; };

View File

@@ -1,18 +1,10 @@
var GroupSchema, Schema, helpers, mongoose, _; var mongoose = require("mongoose");
var Schema = mongoose.Schema;
var helpers = require('habitrpg-shared/script/helpers');
var _ = require('lodash');
mongoose = require("mongoose"); var GroupSchema = new Schema({
_id: {type: String, 'default': helpers.uuid},
Schema = mongoose.Schema;
helpers = require('habitrpg-shared/script/helpers');
_ = require('lodash');
GroupSchema = new Schema({
_id: {
type: String,
'default': helpers.uuid
},
name: String, name: String,
description: String, description: String,
leader: { leader: {
@@ -43,7 +35,9 @@ GroupSchema = new Schema({
Number: Number, Number: Number,
'default': 0 'default': 0
}, },
websites: Array,
chat: Array, chat: Array,
/* /*
# [{ # [{
# timestamp: Date # timestamp: Date
@@ -58,10 +52,41 @@ GroupSchema = new Schema({
balance: Number, balance: Number,
logo: String, logo: String,
leaderMessage: String leaderMessage: String
}, { }, {strict: 'throw'});
strict: 'throw'
});
/**
* Derby duplicated stuff. This is a temporary solution, once we're completely off derby we'll run an mongo migration
* to remove duplicates, then take these fucntions out
*/
function removeDuplicates(doc){
// Remove duplicate members
if (doc.members) {
var uniqMembers = _.uniq(doc.members);
if (uniqMembers.length != doc.members.length) {
doc.members = uniqMembers;
}
}
if (doc.websites) {
var uniqWebsites = _.uniq(doc.websites);
if (uniqWebsites.length != doc.websites.length) {
doc.websites = uniqWebsites;
}
console.log(doc.websites);
}
}
GroupSchema.pre('save', function(next){
removeDuplicates(this);
next();
})
GroupSchema.methods.toJSON = function(){
var doc = this.toObject();
removeDuplicates(doc);
return doc;
}
module.exports.schema = GroupSchema; module.exports.schema = GroupSchema;
module.exports.model = mongoose.model("Group", GroupSchema); module.exports.model = mongoose.model("Group", GroupSchema);

View File

@@ -1,14 +1,9 @@
var Schema, UserSchema, helpers, mongoose, _; var mongoose = require("mongoose");
var Schema = mongoose.Schema;
var helpers = require('habitrpg-shared/script/helpers');
var _ = require('lodash');
mongoose = require("mongoose"); var UserSchema = new Schema({
Schema = mongoose.Schema;
helpers = require('habitrpg-shared/script/helpers');
_ = require('lodash');
UserSchema = new Schema({
_id: { _id: {
type: String, type: String,
'default': helpers.uuid 'default': helpers.uuid
@@ -274,20 +269,16 @@ UserSchema.post('init', function(doc) {
UserSchema.methods.toJSON = function() { UserSchema.methods.toJSON = function() {
var doc; var doc = this.toObject();
doc = this.toObject();
doc.id = doc._id; doc.id = doc._id;
_.each(['habit', 'daily', 'todo', 'reward'], function(type) { _.each(['habit', 'daily', 'todo', 'reward'], function(type) {
/* we use _.transform instead of a simple _.where in order to maintain sort-order*/ // we use _.transform instead of a simple _.where in order to maintain sort-order
doc["" + type + "s"] = _.transform(doc["" + type + "Ids"], function(result, tid) {
return doc["" + type + "s"] = _.transform(doc["" + type + "Ids"], function(result, tid) { result.push(doc.tasks[tid]);
return result.push(doc.tasks[tid]);
}); });
/*delete doc["#{type}Ids"]*/ //delete doc["#{type}Ids"]
}); });
/*delete doc.tasks*/ //delete doc.tasks
doc.filters = {}; doc.filters = {};
return doc; return doc;
}; };
@@ -296,16 +287,12 @@ UserSchema.methods.toJSON = function() {
# FIXME - since we're using special @post('init') above, we need to flag when the original path was modified. # FIXME - since we're using special @post('init') above, we need to flag when the original path was modified.
# Custom setter/getter virtuals? # Custom setter/getter virtuals?
*/ */
UserSchema.pre('save', function(next) { UserSchema.pre('save', function(next) {
this.markModified('tasks'); this.markModified('tasks');
/*our own version incrementer*/ //our own version incrementer
this._v++; this._v++;
return next(); next();
}); });
module.exports.schema = UserSchema; module.exports.schema = UserSchema;
module.exports.model = mongoose.model("User", UserSchema); module.exports.model = mongoose.model("User", UserSchema);

View File

@@ -1,8 +1,8 @@
a.pull-right.gem-wallet(ng-show='group.type=="guild" && group.id!="habitrpg"', rel='popover', data-trigger='hover', data-title='Guild Bank', data-content='Gems which your Guild leader can use for prizes in the upcoming <a target=_blank href="https://trello.com/card/challenges-individual-party-guild-public/50e5d3684fe3a7266b0036d6/58">Challenges</a> feature.', data-placement='bottom', data-html='true') a.pull-right.gem-wallet(rel='popover', data-trigger='hover', data-title='Guild Bank', data-content='Gems which your Guild leader can use for prizes in the upcoming <a target=_blank href="https://trello.com/card/challenges-individual-party-guild-public/50e5d3684fe3a7266b0036d6/58">Challenges</a> feature.', data-placement='bottom', data-html='true')
// <span class="task-action-btn tile flush bright add-gems-btn"></span> // <span class="task-action-btn tile flush bright add-gems-btn"></span>
span.task-action-btn.tile.flush.neutral span.task-action-btn.tile.flush.neutral
.Gems .Gems
| {{gems(group.balance)}} Guild Gems | {{group.balance / 4 | number:0 }} Guild Gems
.row-fluid .row-fluid
.span4 .span4
h3 {{group.name}} h3 {{group.name}}
@@ -43,24 +43,24 @@ a.pull-right.gem-wallet(ng-show='group.type=="guild" && group.id!="habitrpg"', r
accordion-group(heading='Members') accordion-group(heading='Members')
table.table.table-striped table.table.table-striped
tr(ng-repeat='memberId in group.members') tr(ng-repeat='member in group.members')
td td
// allow leaders to ban members // allow leaders to ban members
div(ng-show='and(equal(group.leader,_user.id),not(equal(_user.id,memberId)))') div(ng-show='group.leader == user.id && user.id!=member._id')
// {{#with group.members[$index]}} // {{#with group.members[$index]}}
a(x-bind='click:removeAt', data-refresh='true', data-confirm='Boot this member?') a(x-bind='click:removeAt', data-refresh='true', data-confirm='Boot this member?')
i.icon-ban-circle(rel='tooltip', title='Boot Member') i.icon-ban-circle(tooltip='Boot Member')
// {{/}} // {{/}}
a(data-toggle='modal', data-target='#avatar-modal-{{memberId}}') a(data-toggle='modal', data-target='#avatar-modal-{{member._id}}')
span(ng-class='{"badge badge-info": group.leader==memberId}') span(ng-class='{"badge badge-info": group.leader==member._id}')
| {{username(_members[memberId].auth, _members[memberId].profile.name)}} | {{username(member.auth, member.profile.name)}}
td td
| ({{memberId}}) | ({{member._id}})
// {#with group as :group} // {#with group as :group}
form.form-inline(x-bind='submit:groupInvite', data-type='{group.type}') form.form-inline(x-bind='submit:groupInvite', data-type='{{group.type}}')
.alert.alert-danger(ng-show='_groupError') {_groupError} .alert.alert-danger(ng-show='_groupError') {{_groupError}}
.control-group .control-group
input.input-medium(type='text', placeholder='User Id', value='{_groupInvitee}') input.input-medium(type='text', placeholder='User Id', value='{{_groupInvitee}}')
input.btn(type='submit', value='Invite') input.btn(type='submit', value='Invite')
// {/} // {/}
@@ -89,7 +89,7 @@ a.pull-right.gem-wallet(ng-show='group.type=="guild" && group.id!="habitrpg"', r
div(ng-show='_editing.leaderMessage[group.id]') div(ng-show='_editing.leaderMessage[group.id]')
a.pull-right(x-bind='click:toggleLeaderMessageEdit', data-gid='{{group.id}}') a.pull-right(x-bind='click:toggleLeaderMessageEdit', data-gid='{{group.id}}')
i.icon-ok i.icon-ok
textarea(cols='3', placeholder='Message from group leader') {group.leaderMessage} textarea(cols='3', placeholder='Message from group leader') {{group.leaderMessage}}
div(ng-hide='_editing.leaderMessage[group.id]') div(ng-hide='_editing.leaderMessage[group.id]')
a.btn.pull-right(x-bind='click:toggleLeaderMessageEdit', data-gid='{{group.id}}') Edit leader message a.btn.pull-right(x-bind='click:toggleLeaderMessageEdit', data-gid='{{group.id}}') Edit leader message
table(ng-show='group.leaderMessage') table(ng-show='group.leaderMessage')
@@ -100,7 +100,7 @@ a.pull-right.gem-wallet(ng-show='group.type=="guild" && group.id!="habitrpg"', r
.popover.static-popover.fade.right.in .popover.static-popover.fade.right.in
.arrow .arrow
h3.popover-title {{username(_members[group.leader].auth,_members[group.leader].profile.name)}} h3.popover-title {{username(_members[group.leader].auth,_members[group.leader].profile.name)}}
.popover-content {group.leaderMessage} .popover-content {{group.leaderMessage}}
h3 Chat h3 Chat
include ./chat-box include ./chat-box

View File

@@ -12,9 +12,9 @@ div(ng-controller='GroupsCtrl')
.tab-content .tab-content
#groups-party.tab-pane.active Party #groups-party.tab-pane.active Party
div(ng-show='groups.party.id', ng-controller='PartyCtrl') div(ng-show='group._id', ng-controller='PartyCtrl')
app:groups:group(group='{groups[_party.id]}') include ./group
div(ng-hide='groups.party.id') div(ng-hide='group._id')
div(ng-show='user.invitations.party') div(ng-show='user.invitations.party')
// #with required for the accept/reject buttons // #with required for the accept/reject buttons
// {#with _user.invitations.party as :party} // {#with _user.invitations.party as :party}