From 8d58cab1ce9283ec513b5fb66121070d1cf9f937 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=C3=ABl=20Heres?= Date: Sat, 11 Apr 2015 12:53:35 +0200 Subject: [PATCH 01/17] Some small optimizations for publications highlights / places --- web/packages/account/account/account.jsdoc | 52 +++++++--------- .../collection-deals/deals/publish.js | 19 +++--- .../highlights/publish.js | 21 +++---- web/packages/collection-highlights/package.js | 6 +- .../collection-places/places/publish.js | 15 ++--- web/packages/collection-users/package.js | 6 +- .../collection-users/users/helpers.jsdoc | 59 +++++++++---------- .../collection-users/users/publish.js | 36 +++++------ 8 files changed, 104 insertions(+), 110 deletions(-) diff --git a/web/packages/account/account/account.jsdoc b/web/packages/account/account/account.jsdoc index 7772d0f..c1eb405 100644 --- a/web/packages/account/account/account.jsdoc +++ b/web/packages/account/account/account.jsdoc @@ -6,21 +6,21 @@ Account = {}; * @summary Attach or move user to some city. Users can move thereself to another city when they are not fully registered. Otherwise only ambassadors have the permissions to move users. * @locus Server * @param {String} hackerId The *userId* of the user that will be attached to the given city. - * @param {String} city The *cityKey* of the city where user will be attached to. + * @param {String} city The *cityKey* of the city where user will be attached to. */ Account.moveUserToCity = function(hackerId, city) { // called from Methods.js var hacker = Users.findOne(hackerId); - + if (!hacker) - throw new Meteor.Error(500, "no such user"); + throw new Meteor.Error(500, "no such user"); if (!_.contains(City.identifiers(), city)) - throw new Meteor.Error(500, "no valid city"); + throw new Meteor.Error(500, "no valid city"); // check permissions var userId = this.userId || Meteor.userId(); var me = userId === hackerId; - + var allowed = function() { if (me && hacker.isAccessDenied) return true; @@ -33,10 +33,10 @@ Account.moveUserToCity = function(hackerId, city) { // called from Methods.js throw new Meteor.Error(500, 'not allowed'); if (!hacker.isAccessDenied && me) { - if (ambPerm) - throw new Meteor.Error(500, "can't move yourself"); - else - throw new Meteor.Error(500, "cite move not allowed", 'already registered at some city'); + if (ambPerm) + throw new Meteor.Error(500, "can't move yourself"); + else + throw new Meteor.Error(500, "cite move not allowed", 'already registered at some city'); } return true; @@ -80,7 +80,7 @@ Account.verifyInvitation = function(phrase) { * @param {Number} phrase The *invitationPhrase* of the user who sent the invitation. * @param {String} userId The userId of the user who request access to the site. */ -Account.verifyInvitationOfUser = function(phrase, userId) { +Account.verifyInvitationOfUser = function(phrase, userId) { check(phrase, Number); check(userId, String); @@ -109,14 +109,14 @@ Account.verifyInvitationOfUser = function(phrase, userId) { if (broadcastUser.invitations < 1) throw new Meteor.Error(200, "limit", "Invitation limit reached."); - + // invitation valid // insert invitation couple in database Invitations.insert({ broadcastUser: broadcastUser._id, receivingUser: userId - }); + }); // decrement broadcast user's unused invitations Meteor.users.update(broadcastUser._id, {$inc: {invitations: -1}}); @@ -141,7 +141,7 @@ Account.forceInvitationOfUser = function(userId) { Meteor.users.update(userId, {$unset: {isUninvited: true}}); // check if user now get access to the website - Account.requestAccessOfUser(userId); + Account.requestAccessOfUser(userId); } @@ -151,14 +151,14 @@ Account.forceInvitationOfUser = function(userId) { * @locus Server * @param {String} userId The userId of the user who will get access to the site. */ -Account.requestAccessOfUser = function(userId) { +Account.requestAccessOfUser = function(userId) { var user = Meteor.users.findOne(userId); if (!user) throw new Meteor.Error(500, "Unknow user"); - if (!user.city) - throw new Meteor.Error(500, "User isn't attached to some city."); + if (!user.city) + throw new Meteor.Error(500, "User isn't attached to some city."); if (user.isUninvited) throw new Meteor.Error(500, "notInvited", "User hasn't used an invitation code."); @@ -173,7 +173,7 @@ Account.requestAccessOfUser = function(userId) { // execute these commands if user had previously no access if (user.isAccessDenied === true) { - + // set signup date if (!user.accessAt) Meteor.users.update(userId, {$set: {accessAt: new Date()}}); @@ -190,7 +190,7 @@ Account.requestAccessOfUser = function(userId) { /** - * @summary User let us know that he is ready filling in his profile. + * @summary User let us know that he is ready filling in his profile. * Check if all required properties are filled in. * @locus Server */ @@ -238,7 +238,7 @@ Account.forceEmailVerification = function(userId) { if (!_.contains(emails, user.profile.email)) throw new Meteor.Error(500, "E-mail not registered", "E-mail address not present in user's list of addresses.") if (_.findWhere(user.emails, {address: user.profile.email, verified: true})) - throw new Meteor.Error(500, "E-mail already verified") + throw new Meteor.Error(500, "E-mail already verified") // force verification Users.update({_id: userId, "emails.address": user.profile.email}, {$set: {"emails.$.verified": true}}); @@ -271,7 +271,7 @@ Account.sendVerificationEmail = function(userId) { // make a user visible for others -// by default a user is hidden at time of signup +// by default a user is hidden at time of signup // until he is allowed to access the site. // also admins are hidden var requestVisibility = function() { @@ -281,7 +281,7 @@ var requestVisibility = function() { requestVisibilityOfUser(Meteor.userId()); } -// when this function is called, is must already be verified that +// when this function is called, is must already be verified that // the user is allowed to do this operation var requestVisibilityOfUser = function(userId) { var user = Meteor.users.findOne(userId); @@ -296,13 +296,3 @@ var requestVisibilityOfUser = function(userId) { // make user visible to other users Meteor.users.update(userId, {$unset: {isHidden: true}}); } - - - - - - - - - - diff --git a/web/packages/collection-deals/deals/publish.js b/web/packages/collection-deals/deals/publish.js index 3da21ef..2bbc75c 100644 --- a/web/packages/collection-deals/deals/publish.js +++ b/web/packages/collection-deals/deals/publish.js @@ -1,10 +1,11 @@ +var options = {fields: Query.fieldsArray(UserFields.permissionDeps)}; // Only publish deals for the city the user is visiting Meteor.publish("deals", function (city) { - var user = Users.findOne(this.userId); + var user = Users.findOne(this.userId, options); - if(!user || !Users.allowedAccess(user._id)) - return []; + if(!user || !Users.allowedAccess(user)) + return []; if (city === 'all' && Users.isAdmin(user)) return Deals.find({}); @@ -17,13 +18,13 @@ Meteor.publish("deals", function (city) { // Only publish sortings for the city the user is visiting Meteor.publish("dealsSort", function (city) { - var user = Users.findOne(this.userId); - - if(!user || !Users.allowedAccess(user._id)) - return []; + var user = Users.findOne(this.userId, options); + + if(!user || !Users.allowedAccess(user)) + return []; if (user.city === city || Users.isAdmin(user)) - return DealsSort.find({city: city}); + return DealsSort.find({city: city}); return []; -}); \ No newline at end of file +}); diff --git a/web/packages/collection-highlights/highlights/publish.js b/web/packages/collection-highlights/highlights/publish.js index 7f4b79f..4e7107e 100644 --- a/web/packages/collection-highlights/highlights/publish.js +++ b/web/packages/collection-highlights/highlights/publish.js @@ -1,29 +1,30 @@ +var options = {fields: Query.fieldsArray(UserFields.permissionDeps)}; // Only publish highlights for the city the user is visiting Meteor.publish("highlights", function (city) { - var user = Users.findOne(this.userId); + var user = Users.findOne(this.userId, options); - if(!user || !Users.allowedAccess(user._id)) - return []; + if(!user || !Users.allowedAccess(user)) + return []; if (city === 'all' && Users.isAdmin(user)) - return Highlights.find({}); + return Highlights.find({}); if (user.city === city || Users.isAdmin(user)) - return Highlights.find({$or: [{private: false}, {city: city}]}); + return Highlights.find({$or: [{private: false}, {city: city}]}); return []; }); // Only publish sortings for the city the user is visiting Meteor.publish("highlightsSort", function (city) { - var user = Users.findOne(this.userId); + var user = Users.findOne(this.userId, options); - if(!user || !Users.allowedAccess(user._id)) - return []; + if(!user || !Users.allowedAccess(user)) + return []; if (user.city === city || Users.isAdmin(user)) - return HighlightsSort.find({city: city}); + return HighlightsSort.find({city: city}); return []; -}); \ No newline at end of file +}); diff --git a/web/packages/collection-highlights/package.js b/web/packages/collection-highlights/package.js index b66f8bf..e482e00 100644 --- a/web/packages/collection-highlights/package.js +++ b/web/packages/collection-highlights/package.js @@ -6,16 +6,16 @@ Package.describe({ Package.onUse(function(api) { api.use('hckrs:docs'); - + api.use('base'); api.use('collection-users'); - + api.addFiles('highlights/collection.jsdoc'); api.addFiles('highlights/schema.js'); api.addFiles('highlights/allow-deny.js'); api.addFiles('highlights/publish.js', 'server'); api.addFiles('highlights/methods.js', 'server'); - + api.export([ 'Highlights', 'HighlightsSort', diff --git a/web/packages/collection-places/places/publish.js b/web/packages/collection-places/places/publish.js index 5e31564..cf88c94 100644 --- a/web/packages/collection-places/places/publish.js +++ b/web/packages/collection-places/places/publish.js @@ -1,15 +1,16 @@ +var options = {fields: Query.fieldsArray(UserFields.permissionDeps)}; Meteor.publish("places", function (city) { - var user = Users.findOne(this.userId); + var user = Users.findOne(this.userId, options); - if(!user || !Users.allowedAccess(user._id)) - return []; + if(!user || !Users.allowedAccess(user)) + return []; if (city === 'all' && Users.isAdmin(user)) - return Places.find({}); - + return Places.find({}); + if (user.city === city || Users.isAdmin(user)) - return Places.find({$or: [{private: false}, {city: city}]}); + return Places.find({$or: [{private: false}, {city: city}]}); return []; -}); \ No newline at end of file +}); diff --git a/web/packages/collection-users/package.js b/web/packages/collection-users/package.js index 4c443c5..4387da0 100644 --- a/web/packages/collection-users/package.js +++ b/web/packages/collection-users/package.js @@ -6,7 +6,7 @@ Package.describe({ Package.onUse(function(api) { api.use('hckrs:docs'); - + api.use('base'); api.use('collection-base'); api.use('cities'); @@ -24,9 +24,9 @@ Package.onUse(function(api) { api.addFiles('users/hooks.js', 'server'); api.addFiles('users/tmpl-helpers.js', 'client'); - api.imply('collection-base'); + api.imply('collection-base'); api.export([ - 'Users', + 'Users', 'UserFields', 'HACKING_OPTIONS', 'HACKING_VALUES', 'HACKING', 'AVAILABLE_OPTIONS', 'AVAILABLE_VALUES', 'AVAILABLE', 'MAILING_OPTIONS', 'MAILING_VALUES', diff --git a/web/packages/collection-users/users/helpers.jsdoc b/web/packages/collection-users/users/helpers.jsdoc index b58ccf5..e204771 100644 --- a/web/packages/collection-users/users/helpers.jsdoc +++ b/web/packages/collection-users/users/helpers.jsdoc @@ -4,32 +4,32 @@ /** - * @summary Get property from the specified user, without reactive + * @summary Get property from the specified user, without reactive * dependencies on the other user properties. * @locus Anywhere * @param {String|Object} user userId or user object * @param {String} field Name of the property that you want to obtain * @param {Object} [opts] Options passed to `cursor.findOne(..., options)` - * @example + * @example * ``` * Users.prop('srX87t29FW7CCYbjd', 'city'); // 'utrecht' * ``` */ Users.prop = function(user, field, options) { - + // memorized var prop = _.isObject(user) ? Object.property(user, field) : undefined; if (!_.isUndefined(prop)) return prop; - + var userId = _.isObject(user) ? user._id : user; var opt = {fields: _.object([field], [true])}; user = Meteor.users.findOne(userId, _.extend(options || {}, opt)); - return Object.property(user, field); + return Object.property(user, field); } /** - * @summary Get multiple properties from the specified user, without reactive + * @summary Get multiple properties from the specified user, without reactive * dependencies on the other properties. * @locus Anywhere * @param {String|Object} user userId or user object @@ -52,16 +52,16 @@ Users.props = function(user, fields, options) { var userId = _.isObject(user) ? user._id : user; var opt = {fields: _.object(fields, _.map(fields, function() { return true; }))}; - return Meteor.users.findOne(userId, _.extend(options || {}, opt)); + return Meteor.users.findOne(userId, _.extend(options || {}, opt)); } /** - * @summary Get property from current logged in user, without reactive + * @summary Get property from current logged in user, without reactive * dependencies on the other properties. * @locus Anywhere * @param {String} field Name of the property that you want to obtain * @param {Object} [opts] Options passed to `cursor.findOne(..., options)` - * @example + * @example * ``` * Users.myProp('city'); // 'utrecht' * ``` @@ -71,14 +71,14 @@ Users.myProp = function(field, options) { } /** - * @summary Get multiple properties from current logged in user, without reactive + * @summary Get multiple properties from current logged in user, without reactive * dependencies on the other properties. * @locus Anywhere * @param {String[]} fields Names of the properties that you want to obtain * @param {Object} [opts] Options passed to `cursor.find(..., options)` * @example * ``` - * Users.myProps(['city', 'isAdmin']); + * Users.myProps(['city', 'isAdmin']); * //=> {_id: "srX87t29FW7CCYbjd", city: 'utrecht', isAdmin: true} * ``` */ @@ -92,15 +92,15 @@ Users.myProps = function(fields, options) { /* permission helpers */ /** - * @summary Check if the given user is an admin. - * + * @summary Check if the given user is an admin. + * * * If no user is spcified, the logged in user will be used instead. - * + * * @locus Anywhere * @param {String|Object} [user] Some userId or user object. * @example * ``` - * Users.isAdmin("srX87t29FW7CCYbjd"); + * Users.isAdmin("srX87t29FW7CCYbjd"); * //=> true * ``` */ @@ -120,7 +120,7 @@ Users.isAdmin = function(user) { * @param {String} [city] City identifier. * @example * ``` - * Users.isAmbassador("srX87t29FW7CCYbjd", "utrecht"); + * Users.isAmbassador("srX87t29FW7CCYbjd", "utrecht"); * //=> false * ``` */ @@ -145,7 +145,7 @@ Users.isAmbassador = function(user, city) { * @example * ``` * var doc = {city: "utrecht", userId: "srX87t29FW7CCYbjd"}; - * Users.isOwner("srX87t29FW7CCYbjd", doc); + * Users.isOwner("srX87t29FW7CCYbjd", doc); * //=> true * ``` */ @@ -215,7 +215,7 @@ Users.hasOwnerPermission = function(user, doc) { * @summary If Users.isAdminPermission() return false this function will trhow an error. * * * If no user is spcified, the logged in user will be used instead. - * + * * @locus Anywhere * @param {String|Object} [user] Some userId or user object. */ @@ -229,7 +229,7 @@ Users.checkAdminPermission = function(user) { * * * If no user is specified, the logged in user will be used instead. * * If no city is specified, the current city (of logged in user) will be used instead. - * + * * @locus Anywhere * @param {String|Object} [user] Some userId or user object. * @param {String} [city] City identifier. @@ -243,17 +243,16 @@ Users.checkAmbassadorPermission = function(user, city) { * @summary Check if the user has access to the site, by checking the **isAccessDenied** property. * * The **isAccessDenied** property can be false if: - * + * * * The user isn't invited. * * The user has an incomplete profile. * * The user hasn't validate his email address. * * The user is blocked. - * + * * @locus Anywhere - * @param {String} [user] Some userId. + * @param {Object} [user] Some user. */ -Users.allowedAccess = function(userId) { - var user = Users.findOne(userId); +Users.allowedAccess = function(user) { return user && user.isAccessDenied != true; } @@ -290,7 +289,7 @@ Users.userIsForeign = function(user) { var _userRanks = {}; /** - * @summary Get the user rank. Ranks are numbers in ascending order starting from 1, + * @summary Get the user rank. Ranks are numbers in ascending order starting from 1, * given to every user within a city, based on their registration date. * @locus Anywhere * @param {String|Object} [user] Some userId or user object. @@ -311,7 +310,7 @@ Users.userRank = function(user) { } /** - * @summary Display representation of user's *picture label* which will be visible in the profile picture of this user. + * @summary Display representation of user's *picture label* which will be visible in the profile picture of this user. * The label will contain of one of the following information: * * * **merged with #id**, if user account is merged with an other one. @@ -321,7 +320,7 @@ Users.userRank = function(user) { * * ***city name***, if user belongs to an other city. * * ***staff title***, in the case this user is an ambassador. * * otherwise the user rank (***#id***) is shown. - * + * * @locus Anywhere * @param {String|Object} [user] Some userId or user object. */ @@ -351,8 +350,8 @@ Users.userPictureLabel = function(user) { * * If user hasn't access to the site, **no access** (orange). * * If user isn't visible for normal users, **hidden** (orange). * * If user is an admin, **admin** (green). - * * If user is an ambassador, **ambassador** (green). - * + * * If user is an ambassador, **ambassador** (green). + * * @locus Anywhere * @param {String|Object} [user] Some userId or user object. */ @@ -430,7 +429,7 @@ Users.userSocialName = function(user, service) { /* find users */ /** - * @summary Get the user corresponding to the given unique bithash representation. + * @summary Get the user corresponding to the given unique bithash representation. * The bitHash is used in URL's to indentify some user's profile page. * @locus Anywhere * @param {String} bitHash A bitHash representation string for some user. diff --git a/web/packages/collection-users/users/publish.js b/web/packages/collection-users/users/publish.js index d49bfc2..7a682cf 100644 --- a/web/packages/collection-users/users/publish.js +++ b/web/packages/collection-users/users/publish.js @@ -11,6 +11,23 @@ // and the privacy settings of the published user docs. // this will be handled by the filterUserFields function +// exported user fields +UserFields = { + // properties of the current logged in user that determine the user's permissions (in filterUserFields()). + // When one of these properties changes it affects the visible fields of other users. + // So when an dependency changes, we have to republish the whole collection to test + // each user doc against the new permission rights. + // Only first-level properties are allowed, such as 'profile', but NOT 'profile.name' + permissionDeps: [ + '_id', + 'isAccessDenied', + 'isAdmin', + 'city', + 'currentCity', + 'isAmbassador' + ] +} + var userFieldsGlobal = [ "city", "currentCity", @@ -70,21 +87,6 @@ var allUserFields = _.union( userFieldsCurrentUser ); -// properties of the current logged in user that determine the user's permissions (in filterUserFields()). -// When one of these properties changes it affects the visible fields of other users. -// So when an dependency changes, we have to republish the whole collection to test -// each user doc against the new permission rights. -// Only first-level properties are allowed, such as 'profile', but NOT 'profile.name' -var permissionDeps = [ - '_id', - 'isAccessDenied', - 'isAdmin', - 'city', - 'currentCity', - 'isAmbassador' -]; - - // publish all fields of the logged in user Meteor.publish('currentUser', function() { var fields = { @@ -100,7 +102,7 @@ Meteor.publish('users', function() { var queryOptions = {fields: Query.fieldsArray(allUserFields)}; // initial permissions (can be changed below) - var permissions = Users.findOne(this.userId, {fields: Query.fieldsArray(permissionDeps)}) || {}; + var permissions = Users.findOne(this.userId, {fields: Query.fieldsArray(UserFields.permissionDeps)}) || {}; // observe docs changes and push the changes to the client // only include fields that are allowed to publish, this can vary between users @@ -129,7 +131,7 @@ Meteor.publish('users', function() { // Check if depended permissions fields from current user changes // if so, we have to republish the whole user collection if (self.userId) - var myObserver = Users.find({_id: self.userId}, {fields: Query.fieldsArray(permissionDeps)}).observe({'changed': republish}); + var myObserver = Users.find({_id: self.userId}, {fields: Query.fieldsArray(UserFields.permissionDeps)}).observe({'changed': republish}); // handlers From 06d40484c91e0155af176fc60c3cb2ba68d5a714 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=C3=ABl=20Heres?= Date: Sun, 12 Apr 2015 09:57:20 +0200 Subject: [PATCH 02/17] Use null publication for current user. --- web/Routes.js | 15 --------------- web/packages/collection-users/users/publish.js | 2 +- 2 files changed, 1 insertion(+), 16 deletions(-) diff --git a/web/Routes.js b/web/Routes.js index 06baebc..7926b0d 100644 --- a/web/Routes.js +++ b/web/Routes.js @@ -116,7 +116,6 @@ FrontpageController = DefaultController.extend({ template: 'frontpage', waitOn: function() { return [ - Meteor.subscribe('currentUser'), Meteor.subscribe('staff'), Meteor.subscribe('ambassadors') ]; @@ -132,7 +131,6 @@ AgendaController = DefaultController.extend({ template: 'agenda', waitOn: function () { return [ - Meteor.subscribe('currentUser'), Meteor.subscribe('users') // XXX be more precise ]; } @@ -142,7 +140,6 @@ BooksController = DefaultController.extend({ template: 'books', waitOn: function () { return [ - Meteor.subscribe('currentUser'), Meteor.subscribe('users') // XXX be more precise ]; } @@ -153,7 +150,6 @@ DealsController = DefaultController.extend({ waitOn: function () { var city = Url.city(); return [ - Meteor.subscribe('currentUser'), Meteor.subscribe('users') // XXX be more precise ].concat(!city ? [] : [ Meteor.subscribe('deals', city), @@ -166,7 +162,6 @@ HackerController = DefaultController.extend({ template: 'hacker', waitOn: function () { return [ - Meteor.subscribe('currentUser'), Meteor.subscribe('users') // XXX be more precise ]; }, @@ -185,7 +180,6 @@ HackersController = DefaultController.extend({ template: 'hackers', waitOn: function () { return [ - Meteor.subscribe('currentUser'), Meteor.subscribe('invitations'), Meteor.subscribe('users') // XXX be more precise ]; @@ -198,7 +192,6 @@ HighlightsController = DefaultController.extend({ waitOn: function() { var city = Url.city(); return [ - Meteor.subscribe('currentUser'), Meteor.subscribe('users') // XXX be more precise ].concat(!city ? [] : [ Meteor.subscribe('highlights', city), @@ -225,7 +218,6 @@ InvitationsController = DefaultController.extend({ template: 'invitations', waitOn: function () { return [ - Meteor.subscribe('currentUser'), Meteor.subscribe('invitations'), Meteor.subscribe('users') // XXX be more precise ]; @@ -237,7 +229,6 @@ MapController = DefaultController.extend({ waitOn: function () { var city = Url.city(); return [ - Meteor.subscribe('currentUser'), Meteor.subscribe('users') // XXX be more precise ].concat(!city ? [] : [ Meteor.subscribe('places', city), @@ -325,7 +316,6 @@ AdminDealsController = DefaultAdminController.extend({ var city = Session.get('currentCity'); var isAdmin = Users.hasAdminPermission(); return [ - Meteor.subscribe('currentUser'), Meteor.subscribe('deals', isAdmin ? 'all' : city), Meteor.subscribe('users') // XXX be more precise ]; @@ -336,7 +326,6 @@ AdminEmailTemplatesController = DefaultAdminController.extend({ template: 'admin_emailTemplates', waitOn: function () { return [ - Meteor.subscribe('currentUser'), Meteor.subscribe('emailTemplates'), Meteor.subscribe('users') // XXX be more precise ]; @@ -350,7 +339,6 @@ AdminGrowthController = DefaultAdminController.extend({ }, waitOn: function () { return [ - Meteor.subscribe('currentUser'), // load all github users from the selected city Meteor.subscribe('growthGithub', AdminGrowth.getCity()), Meteor.subscribe('emailTemplates'), @@ -363,7 +351,6 @@ AdminHackersController = DefaultAdminController.extend({ template: 'admin_hackers', waitOn: function () { return [ - Meteor.subscribe('currentUser'), Meteor.subscribe('users') // XXX be more precise ]; } @@ -375,7 +362,6 @@ AdminHighlightsController = DefaultAdminController.extend({ var city = Session.get('currentCity'); var isAdmin = Users.hasAdminPermission(); return [ - Meteor.subscribe('currentUser'), Meteor.subscribe('highlights', isAdmin ? 'all' : city), Meteor.subscribe('users') // XXX be more precise ]; @@ -389,7 +375,6 @@ AdminPlacesController = DefaultAdminController.extend({ var city = Session.get('currentCity'); var isAdmin = Users.hasAdminPermission(); return [ - Meteor.subscribe('currentUser'), Meteor.subscribe('places', isAdmin ? 'all' : city), Meteor.subscribe('users') // XXX be more precise ]; diff --git a/web/packages/collection-users/users/publish.js b/web/packages/collection-users/users/publish.js index 7a682cf..7e3a417 100644 --- a/web/packages/collection-users/users/publish.js +++ b/web/packages/collection-users/users/publish.js @@ -88,7 +88,7 @@ var allUserFields = _.union( ); // publish all fields of the logged in user -Meteor.publish('currentUser', function() { +Meteor.publish(null, function() { var fields = { "services": 0 } From 4acdf1e4df15c48e5aff5e2fb0f758117432730e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=C3=ABl=20Heres?= Date: Sun, 12 Apr 2015 10:37:36 +0200 Subject: [PATCH 03/17] only publish users in current city --- web/Routes.js | 22 ++++++++++++------- .../collection-users/users/publish.js | 18 ++++++++++++--- web/packages/collection-users/users/schema.js | 7 +----- web/server/Subscriptions.js | 5 ----- 4 files changed, 30 insertions(+), 22 deletions(-) diff --git a/web/Routes.js b/web/Routes.js index 7926b0d..88763bf 100644 --- a/web/Routes.js +++ b/web/Routes.js @@ -129,9 +129,11 @@ FrontpageController = DefaultController.extend({ AgendaController = DefaultController.extend({ template: 'agenda', + waitOn: function () { + var city = Url.city(); return [ - Meteor.subscribe('users') // XXX be more precise + Meteor.subscribe('users', city) ]; } }); @@ -139,8 +141,9 @@ AgendaController = DefaultController.extend({ BooksController = DefaultController.extend({ template: 'books', waitOn: function () { + var city = Url.city(); return [ - Meteor.subscribe('users') // XXX be more precise + Meteor.subscribe('users', city) ]; } }); @@ -150,7 +153,7 @@ DealsController = DefaultController.extend({ waitOn: function () { var city = Url.city(); return [ - Meteor.subscribe('users') // XXX be more precise + Meteor.subscribe('users', city) ].concat(!city ? [] : [ Meteor.subscribe('deals', city), Meteor.subscribe('dealsSort', city) @@ -161,8 +164,9 @@ DealsController = DefaultController.extend({ HackerController = DefaultController.extend({ template: 'hacker', waitOn: function () { + var city = Url.city(); return [ - Meteor.subscribe('users') // XXX be more precise + Meteor.subscribe('users', city) ]; }, onBeforeAction: function() { @@ -179,9 +183,10 @@ HackerController = DefaultController.extend({ HackersController = DefaultController.extend({ template: 'hackers', waitOn: function () { + var city = Url.city(); return [ Meteor.subscribe('invitations'), - Meteor.subscribe('users') // XXX be more precise + Meteor.subscribe('users', city) ]; } }); @@ -192,7 +197,7 @@ HighlightsController = DefaultController.extend({ waitOn: function() { var city = Url.city(); return [ - Meteor.subscribe('users') // XXX be more precise + Meteor.subscribe('users', city) ].concat(!city ? [] : [ Meteor.subscribe('highlights', city), Meteor.subscribe('highlightsSort', city) @@ -217,9 +222,10 @@ HighlightsController = DefaultController.extend({ InvitationsController = DefaultController.extend({ template: 'invitations', waitOn: function () { + var city = Url.city(); return [ Meteor.subscribe('invitations'), - Meteor.subscribe('users') // XXX be more precise + Meteor.subscribe('users', city) ]; } }); @@ -229,7 +235,7 @@ MapController = DefaultController.extend({ waitOn: function () { var city = Url.city(); return [ - Meteor.subscribe('users') // XXX be more precise + Meteor.subscribe('users', city) ].concat(!city ? [] : [ Meteor.subscribe('places', city), Meteor.subscribe('mapHackersLocations', {excludeCity: city}) // load anonym location data of all users world wide (XXX TODO: async) diff --git a/web/packages/collection-users/users/publish.js b/web/packages/collection-users/users/publish.js index 7e3a417..c011559 100644 --- a/web/packages/collection-users/users/publish.js +++ b/web/packages/collection-users/users/publish.js @@ -97,17 +97,29 @@ Meteor.publish(null, function() { // publish specific fields of all users -Meteor.publish('users', function() { +Meteor.publish('users', function(city) { var self = this; var queryOptions = {fields: Query.fieldsArray(allUserFields)}; // initial permissions (can be changed below) var permissions = Users.findOne(this.userId, {fields: Query.fieldsArray(UserFields.permissionDeps)}) || {}; + var selector = {}; + + if(!permissions || !Users.allowedAccess(permissions)) + return []; + + if (typeof city === 'string' && (permissions.city === city || Users.isAdmin(permissions))) { + selector.city = city; + } + else { // not allowed to see users of city + return []; + } + // observe docs changes and push the changes to the client // only include fields that are allowed to publish, this can vary between users // and will be handled by the filterUserFields() function. - var observer = Users.find({}, queryOptions).observe({ + var observer = Users.find(selector, queryOptions).observe({ added: function(doc) { self.added('users', doc._id, excludeUserFields(permissions, doc, false)); }, @@ -123,7 +135,7 @@ Meteor.publish('users', function() { // because permission of current user have changed var republish = function(newPermissions) { permissions = newPermissions; // ! set new permissions - Users.find({}, queryOptions).forEach(function(doc) { + Users.find(selector, queryOptions).forEach(function(doc) { self.changed('users', doc._id, excludeUserFields(permissions, doc, false)); }); } diff --git a/web/packages/collection-users/users/schema.js b/web/packages/collection-users/users/schema.js index 5998de5..336e13f 100644 --- a/web/packages/collection-users/users/schema.js +++ b/web/packages/collection-users/users/schema.js @@ -102,6 +102,7 @@ Schemas.User = new SimpleSchema([Schemas.default, { "city": { // the city where this hacker is registered to (lowercase) type: String, + index: 1, allowedValues: City.identifiers() }, "currentCity": { // the city this (admin) user is visiting @@ -214,9 +215,3 @@ Users._transform = function(u) { u.bitHash = Url.bitHash(u.globalId); return u; } - - - - - - diff --git a/web/server/Subscriptions.js b/web/server/Subscriptions.js index 72a5568..0f447ea 100644 --- a/web/server/Subscriptions.js +++ b/web/server/Subscriptions.js @@ -63,8 +63,3 @@ Meteor.publish('inviteBroadcastUser', function(inviteBitHash) { var fields = Query.fieldsArray(_.union(userFields, ['invitationPhrase'])); return !invitePhrase ? [] : Users.find({invitationPhrase: invitePhrase}, {fields: fields, limit: 1}); }); - - - - - From a1fc728e45ead1fd5a30d114b690ff7723216f9d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jarno=20Le=20Cont=C3=A9?= Date: Mon, 2 Nov 2015 20:44:35 +0100 Subject: [PATCH 04/17] rollback last commit --- web/packages/growth/growth/crawler-server.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/web/packages/growth/growth/crawler-server.js b/web/packages/growth/growth/crawler-server.js index f184f58..1d04b7b 100644 --- a/web/packages/growth/growth/crawler-server.js +++ b/web/packages/growth/growth/crawler-server.js @@ -35,8 +35,12 @@ var fetchGithubUsersInCity = function(city) { var forEachQueriedUser = function(query, page, iterator, cb) { - // if (page > 10) - // return cb && cb(); + // Github only present the first 1000 search results. + // We should stop after retrieving 1000 results. + // A better way is to make the search more specific to find all users in a city. + // For example we can alphabetic search usernames, so we can get 1000 results per letter. + if (page > 10) + return cb && cb(); console.log('fetch github search', query, page); From d58b478c72ba81b7eaf0926f22a02f727002f27d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jarno=20Le=20Cont=C3=A9?= Date: Mon, 2 Nov 2015 20:45:50 +0100 Subject: [PATCH 05/17] bugfix: admin tables; because of updated package reactive-table. --- web/client/admin/admin.js | 5 +++ web/client/admin/deals/admin_deals.js | 6 +-- .../emailTemplates/admin_emailTemplates.js | 6 +-- web/client/admin/growth/admin_growth.js | 37 ++++++++++--------- web/client/admin/hackers/admin_hackers.js | 10 ++--- .../admin/highlights/admin_highlights.js | 4 +- web/client/admin/places/admin_places.js | 6 +-- 7 files changed, 40 insertions(+), 34 deletions(-) diff --git a/web/client/admin/admin.js b/web/client/admin/admin.js index d374a02..bcf50ef 100644 --- a/web/client/admin/admin.js +++ b/web/client/admin/admin.js @@ -42,12 +42,14 @@ Field.fn.avatar = function(val) { /* DATA FIELD templates */ Field.edit = { + fieldId: 'id', key: 'id', label: 'edit', tmpl: Template.reactiveTable_editButton } Field.date = { + fieldId: 'createdAt', key: 'createdAt', label: 'date', sortByValue: true, @@ -56,6 +58,7 @@ Field.date = { } Field.city = { + fieldId: 'city', key: 'city', label: 'city', fn: function(val, obj) { @@ -65,6 +68,7 @@ Field.city = { } Field.private = { + fieldId: 'private', key: 'private', label: 'private', fn: function(val, obj) { @@ -74,6 +78,7 @@ Field.private = { } Field.url = { + fieldId: 'url', key: 'url', label: 'url', fn: function(val, obj) { diff --git a/web/client/admin/deals/admin_deals.js b/web/client/admin/deals/admin_deals.js index 71e3db0..92d9d6f 100644 --- a/web/client/admin/deals/admin_deals.js +++ b/web/client/admin/deals/admin_deals.js @@ -13,10 +13,10 @@ Template.admin_deals.helpers({ Field.date, Field.city, Field.private, - 'title', - 'description', + { fieldId: 'title', key: 'title', label: 'title'}, + { fieldId: 'description', key: 'description', label: 'description'}, Field.url, - 'code' + { fieldId: 'code', key: 'code', label: 'code'}, ], } } diff --git a/web/client/admin/emailTemplates/admin_emailTemplates.js b/web/client/admin/emailTemplates/admin_emailTemplates.js index e2b391e..201b7f1 100644 --- a/web/client/admin/emailTemplates/admin_emailTemplates.js +++ b/web/client/admin/emailTemplates/admin_emailTemplates.js @@ -12,9 +12,9 @@ Template.admin_emailTemplates.helpers({ showFilter: false, rowsPerPage: 500, fields: [ - 'groups', - 'subject', - 'identifier', + { fieldId: 'groups', key: 'groups', label: 'groups'}, + { fieldId: 'subject', key: 'subject', label: 'subject'}, + { fieldId: 'identifier', key: 'identifier', label: 'identifier'}, Field.edit, ], } diff --git a/web/client/admin/growth/admin_growth.js b/web/client/admin/growth/admin_growth.js index 843515d..de60768 100644 --- a/web/client/admin/growth/admin_growth.js +++ b/web/client/admin/growth/admin_growth.js @@ -22,25 +22,26 @@ var state = new State('adminGrowth', { var fields = function() { return [ Field.city, - { key: 'createdAt', label: 'since', sortByValue: true, fn: Field.fn.date }, - { key: 'avatarUrl', label: '#', fn: function(avatarUrl, obj) { return Safe.string(''); }}, + { fieldId: 'createdAt', key: 'createdAt', label: 'since', sortByValue: true, fn: Field.fn.date }, + { fieldId: 'avatarUrl', key: 'avatarUrl', label: '#', fn: function(avatarUrl, obj) { return Safe.string(''); }}, // 'name', - 'followers', - 'following', - 'repos', - 'gists', - { key: 'username', label: 'username', sortByValue: true, fn: function(username) { return Safe.url("https://github.com/"+username, {text: username}); }}, - { key: 'hireable', label: 'hire', fn: Field.fn.bool }, - { key: 'biography', label: 'bio', fn: function(bio) { return bio ? Safe.string('') : ''; } }, - { key: 'company', label: 'comp.', fn: function(company) { return company ? Safe.string('') : ''; } }, - { key: 'location', label: 'loc.', fn: function(location) { return location ? Safe.string('') : ''; } }, - - { key: 'website', label: 'site', sortByValue: true, fn: function(url) { return url ? Safe.url(url, {text: ''}) : ''; }}, - { key: 'email', label: 'email', sortByValue: true, fn: Field.fn.email }, - { key: 'invitedAt', label: 'invited', sortByValue: true, fn: Field.fn.date}, - { key: 'open', label: 'open', sortByValue: true, fn: Field.fn.bool}, - { key: 'clicks', label: 'clicks', sortByValue: true}, - { key: 'signupAt', label: 'singup', sortByValue: true, fn: Field.fn.date}, + { fieldId: 'followers', key: 'followers', label: 'followers'}, + { fieldId: 'following', key: 'following', label: 'following'}, + { fieldId: 'repos', key: 'repos', label: 'repos'}, + { fieldId: 'gists', key: 'gists', label: 'gists'}, + + { fieldId: 'username', key: 'username', label: 'username', sortByValue: true, fn: function(username) { return Safe.url("https://github.com/"+username, {text: username}); }}, + { fieldId: 'hireable', key: 'hireable', label: 'hire', fn: Field.fn.bool }, + { fieldId: 'biography', key: 'biography', label: 'bio', fn: function(bio) { return bio ? Safe.string('') : ''; } }, + { fieldId: 'company', key: 'company', label: 'comp.', fn: function(company) { return company ? Safe.string('') : ''; } }, + { fieldId: 'location', key: 'location', label: 'loc.', fn: function(location) { return location ? Safe.string('') : ''; } }, + + { fieldId: 'website', key: 'website', label: 'site', sortByValue: true, fn: function(url) { return url ? Safe.url(url, {text: ''}) : ''; }}, + { fieldId: 'email', key: 'email', label: 'email', sortByValue: true, fn: Field.fn.email }, + { fieldId: 'invitedAt', key: 'invitedAt', label: 'invited', sortByValue: true, fn: Field.fn.date}, + { fieldId: 'open', key: 'open', label: 'open', sortByValue: true, fn: Field.fn.bool}, + { fieldId: 'clicks', key: 'clicks', label: 'clicks', sortByValue: true}, + { fieldId: 'signupAt', key: 'signupAt', label: 'singup', sortByValue: true, fn: Field.fn.date}, ]; }; diff --git a/web/client/admin/hackers/admin_hackers.js b/web/client/admin/hackers/admin_hackers.js index b4fbff3..2657641 100644 --- a/web/client/admin/hackers/admin_hackers.js +++ b/web/client/admin/hackers/admin_hackers.js @@ -15,11 +15,11 @@ Template.admin_hackers.helpers({ fields: [ Field.date, Field.city, - { key: 'globalId', label: '#', sortByValue: true, fn: function(val, obj) { return Safe.url(Users.userProfileUrl(obj), {text: Users.userRank(obj) ? '#'+Users.userRank(obj) : '-'}); } }, - { key: 'profile.name', label: 'name', sortByValue: true, fn: function(val, obj) { return Safe.url(Users.userProfileUrl(obj), {text: val || '-'}); } }, - { key: 'profile.email', label: 'e-mail', sortByValue: true, fn: Field.fn.email }, - { key: 'invitations', label: 'free invites' }, - { key: '_id', label: 'status', + { fieldId: 'globalId', key: 'globalId', label: '#', sortByValue: true, fn: function(val, obj) { return Safe.url(Users.userProfileUrl(obj), {text: Users.userRank(obj) ? '#'+Users.userRank(obj) : '-'}); } }, + { fieldId: 'profile.name', key: 'profile.name', label: 'name', sortByValue: true, fn: function(val, obj) { return Safe.url(Users.userProfileUrl(obj), {text: val || '-'}); } }, + { fieldId: 'profile.email', key: 'profile.email', label: 'e-mail', sortByValue: true, fn: Field.fn.email }, + { fieldId: 'invitations', key: 'invitations', label: 'free invites' }, + { fieldId: '_id', key: '_id', label: 'status', fn: function(val, obj) { var labels = Users.userStatusLabel(obj); var makeLabel = function(label) { return ''+label.text+''; } diff --git a/web/client/admin/highlights/admin_highlights.js b/web/client/admin/highlights/admin_highlights.js index 1745149..4479a43 100644 --- a/web/client/admin/highlights/admin_highlights.js +++ b/web/client/admin/highlights/admin_highlights.js @@ -13,8 +13,8 @@ Template.admin_highlights.helpers({ Field.date, Field.city, Field.private, - 'title', - 'subtitle', + {fieldId: 'title', key: 'title', label: 'title'}, + {fieldId: 'subtitle', key: 'subtitle', label: 'subtitle'}, Field.url, ], } diff --git a/web/client/admin/places/admin_places.js b/web/client/admin/places/admin_places.js index aaef602..c7be0b9 100644 --- a/web/client/admin/places/admin_places.js +++ b/web/client/admin/places/admin_places.js @@ -13,9 +13,9 @@ Template.admin_places.helpers({ Field.date, Field.city, Field.private, - 'type', - 'title', - 'description', + {fieldId: 'type', key: 'type', label: 'type'}, + {fieldId: 'title', key: 'title', label: 'title'}, + {fieldId: 'description', key: 'description', label: 'description'}, Field.url, ], } From 13f1dc04d0bfcdf3d648aafc7faf02db79a9e08a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jarno=20Le=20Cont=C3=A9?= Date: Mon, 2 Nov 2015 21:25:24 +0100 Subject: [PATCH 06/17] bugfix: admin email templates; because of updated package autoform. --- .../emailTemplates/admin_emailTemplates.html | 20 +++++------ .../email-templates/constants.js | 12 +++---- .../email-templates/schema.js | 33 ++++++++++--------- 3 files changed, 34 insertions(+), 31 deletions(-) diff --git a/web/client/admin/emailTemplates/admin_emailTemplates.html b/web/client/admin/emailTemplates/admin_emailTemplates.html index 89feeba..b191b47 100644 --- a/web/client/admin/emailTemplates/admin_emailTemplates.html +++ b/web/client/admin/emailTemplates/admin_emailTemplates.html @@ -1,22 +1,22 @@