2017-03-16 01:25:08 +08:00
|
|
|
import SimpleSchema from 'simpl-schema';
|
2016-12-21 12:04:43 +01:00
|
|
|
import Users from './collection.js';
|
2017-04-28 09:24:28 +09:00
|
|
|
import { Utils } from 'meteor/vulcan:lib'; // import from vulcan:lib because vulcan:core isn't loaded yet
|
2016-12-21 12:04:43 +01:00
|
|
|
|
2017-07-31 10:33:53 +01:00
|
|
|
///////////////////////////////////////
|
|
|
|
// Order for the Schema is as follows. Change as you see fit:
|
|
|
|
// 00.
|
|
|
|
// 10. Display Name
|
|
|
|
// 20. Email
|
|
|
|
// 30. Bio
|
|
|
|
// 40. Slug
|
|
|
|
// 50. Website
|
|
|
|
// 60. Twitter username
|
|
|
|
// 70.
|
|
|
|
// 80.
|
|
|
|
// 90.
|
|
|
|
// 100.
|
|
|
|
// Anything else..
|
|
|
|
///////////////////////////////////////
|
|
|
|
|
2016-06-23 15:00:58 +09:00
|
|
|
const adminGroup = {
|
|
|
|
name: "admin",
|
|
|
|
};
|
|
|
|
|
2017-01-16 12:32:28 +09:00
|
|
|
const ownsOrIsAdmin = (user, document) => {
|
|
|
|
return Users.owns(user, document) || Users.isAdmin(user);
|
|
|
|
};
|
|
|
|
|
2016-06-23 15:00:58 +09:00
|
|
|
/**
|
2016-11-08 18:22:17 +01:00
|
|
|
* @summary Users schema
|
2016-11-22 18:14:51 -05:00
|
|
|
* @type {Object}
|
2016-06-23 15:00:58 +09:00
|
|
|
*/
|
2016-12-08 23:48:16 +01:00
|
|
|
const schema = {
|
2016-11-08 18:22:17 +01:00
|
|
|
_id: {
|
|
|
|
type: String,
|
|
|
|
optional: true,
|
2016-12-06 10:55:47 +01:00
|
|
|
viewableBy: ['guests'],
|
2016-06-23 15:00:58 +09:00
|
|
|
},
|
2016-11-08 18:22:17 +01:00
|
|
|
username: {
|
|
|
|
type: String,
|
|
|
|
optional: true,
|
2016-12-06 10:55:47 +01:00
|
|
|
viewableBy: ['guests'],
|
2017-04-29 12:33:05 +09:00
|
|
|
insertableBy: ['guests'],
|
2017-04-30 20:36:14 +09:00
|
|
|
onInsert: user => {
|
2017-06-02 07:19:39 +09:00
|
|
|
if (user.services && user.services.twitter && user.services.twitter.screenName) {
|
2017-04-30 20:36:14 +09:00
|
|
|
return user.services.twitter.screenName;
|
|
|
|
}
|
2017-08-02 16:18:34 +09:00
|
|
|
},
|
|
|
|
searchable: true
|
2016-11-08 18:22:17 +01:00
|
|
|
},
|
|
|
|
emails: {
|
2017-03-16 01:25:08 +08:00
|
|
|
type: Array,
|
|
|
|
optional: true,
|
|
|
|
},
|
|
|
|
'emails.$': {
|
|
|
|
type: Object,
|
2016-11-30 10:21:25 +09:00
|
|
|
optional: true,
|
2016-06-23 15:00:58 +09:00
|
|
|
},
|
2016-11-08 18:22:17 +01:00
|
|
|
"emails.$.address": {
|
|
|
|
type: String,
|
|
|
|
regEx: SimpleSchema.RegEx.Email,
|
2016-11-30 10:21:25 +09:00
|
|
|
optional: true,
|
2016-11-08 18:22:17 +01:00
|
|
|
},
|
|
|
|
"emails.$.verified": {
|
|
|
|
type: Boolean,
|
2016-11-30 10:21:25 +09:00
|
|
|
optional: true,
|
2016-11-08 18:22:17 +01:00
|
|
|
},
|
|
|
|
createdAt: {
|
|
|
|
type: Date,
|
|
|
|
optional: true,
|
2017-08-20 15:23:06 +09:00
|
|
|
viewableBy: ['admins'],
|
2017-04-29 12:33:05 +09:00
|
|
|
onInsert: () => {
|
|
|
|
return new Date();
|
|
|
|
}
|
2016-11-08 18:22:17 +01:00
|
|
|
},
|
|
|
|
isAdmin: {
|
|
|
|
type: Boolean,
|
|
|
|
label: "Admin",
|
|
|
|
control: "checkbox",
|
|
|
|
optional: true,
|
2016-12-06 10:55:47 +01:00
|
|
|
insertableBy: ['admins'],
|
|
|
|
editableBy: ['admins'],
|
|
|
|
viewableBy: ['guests'],
|
2016-11-29 18:54:12 +09:00
|
|
|
group: adminGroup,
|
2017-04-28 09:24:28 +09:00
|
|
|
onInsert: user => {
|
|
|
|
// if this is not a dummy account, and is the first user ever, make them an admin
|
2017-04-29 12:33:05 +09:00
|
|
|
const realUsersCount = Users.find({'isDummy': {$ne: true}}).count();
|
|
|
|
return (!user.isDummy && realUsersCount === 0) ? true : false;
|
2017-04-28 09:24:28 +09:00
|
|
|
}
|
2016-11-08 18:22:17 +01:00
|
|
|
},
|
2017-10-05 09:14:42 +09:00
|
|
|
profile: {
|
|
|
|
type: Object,
|
|
|
|
optional: true,
|
|
|
|
blackbox: true,
|
|
|
|
insertableBy: ['guests'],
|
|
|
|
},
|
2017-08-20 15:23:06 +09:00
|
|
|
// // telescope-specific data, kept for backward compatibility and migration purposes
|
|
|
|
// telescope: {
|
|
|
|
// type: Object,
|
|
|
|
// blackbox: true,
|
|
|
|
// optional: true,
|
|
|
|
// },
|
2016-11-08 18:22:17 +01:00
|
|
|
services: {
|
|
|
|
type: Object,
|
|
|
|
optional: true,
|
2016-11-30 10:21:25 +09:00
|
|
|
blackbox: true,
|
2018-01-02 13:05:34 +09:00
|
|
|
viewableBy: ['guests'],
|
2016-11-08 18:22:17 +01:00
|
|
|
},
|
2016-06-23 15:00:58 +09:00
|
|
|
/**
|
|
|
|
The name displayed throughout the app. Can contain spaces and special characters, doesn't need to be unique
|
|
|
|
*/
|
2017-01-18 10:18:33 +09:00
|
|
|
displayName: {
|
2016-06-23 15:00:58 +09:00
|
|
|
type: String,
|
|
|
|
optional: true,
|
|
|
|
control: "text",
|
2016-12-06 10:55:47 +01:00
|
|
|
insertableBy: ['members'],
|
|
|
|
editableBy: ['members'],
|
|
|
|
viewableBy: ['guests'],
|
2017-07-31 10:33:53 +01:00
|
|
|
order: 10,
|
2017-04-28 09:24:28 +09:00
|
|
|
onInsert: (user, options) => {
|
2017-05-03 15:37:44 +09:00
|
|
|
const profileName = Utils.getNestedProperty(user, 'profile.name');
|
|
|
|
const twitterName = Utils.getNestedProperty(user, 'services.twitter.screenName');
|
|
|
|
const linkedinFirstName = Utils.getNestedProperty(user, 'services.linkedin.firstName');
|
|
|
|
if (profileName) return profileName;
|
|
|
|
if (twitterName) return twitterName;
|
|
|
|
if (linkedinFirstName) return `${linkedinFirstName} ${Utils.getNestedProperty(user, 'services.linkedin.lastName')}`;
|
|
|
|
if (user.username) return user.username;
|
|
|
|
return undefined;
|
2017-08-02 16:18:34 +09:00
|
|
|
},
|
|
|
|
searchable: true
|
2017-08-01 17:21:16 +09:00
|
|
|
},
|
|
|
|
/**
|
2017-07-31 10:33:53 +01:00
|
|
|
Bio (Markdown version)
|
|
|
|
*/
|
|
|
|
bio: {
|
|
|
|
type: String,
|
|
|
|
optional: true,
|
|
|
|
control: "textarea",
|
|
|
|
insertableBy: ['members'],
|
|
|
|
editableBy: ['members'],
|
|
|
|
viewableBy: ['guests'],
|
|
|
|
order: 30,
|
2017-08-02 16:18:34 +09:00
|
|
|
searchable: true
|
2016-06-23 15:00:58 +09:00
|
|
|
},
|
|
|
|
/**
|
|
|
|
The user's email. Modifiable.
|
|
|
|
*/
|
2017-01-18 10:18:33 +09:00
|
|
|
email: {
|
2016-06-23 15:00:58 +09:00
|
|
|
type: String,
|
|
|
|
optional: true,
|
|
|
|
regEx: SimpleSchema.RegEx.Email,
|
2017-05-01 10:49:27 +09:00
|
|
|
mustComplete: true,
|
2016-06-23 15:00:58 +09:00
|
|
|
control: "text",
|
2017-04-29 12:33:05 +09:00
|
|
|
insertableBy: ['guests'],
|
2016-12-06 10:55:47 +01:00
|
|
|
editableBy: ['members'],
|
2017-01-16 12:32:28 +09:00
|
|
|
viewableBy: ownsOrIsAdmin,
|
2017-07-31 10:33:53 +01:00
|
|
|
order: 20,
|
2017-04-29 12:33:05 +09:00
|
|
|
onInsert: (user) => {
|
2017-04-28 09:24:28 +09:00
|
|
|
// look in a few places for the user email
|
2017-05-03 13:04:33 +05:30
|
|
|
const meteorEmails = Utils.getNestedProperty(user, 'services.meteor-developer.emails');
|
|
|
|
const facebookEmail = Utils.getNestedProperty(user, 'services.facebook.email');
|
|
|
|
const githubEmail = Utils.getNestedProperty(user, 'services.github.email');
|
|
|
|
const googleEmail = Utils.getNestedProperty(user, 'services.google.email');
|
|
|
|
const linkedinEmail = Utils.getNestedProperty(user, 'services.linkedin.emailAddress');
|
|
|
|
|
2017-05-03 15:37:44 +09:00
|
|
|
if (meteorEmails) return _.findWhere(meteorEmails, { primary: true }).address;
|
|
|
|
if (facebookEmail) return facebookEmail;
|
|
|
|
if (githubEmail) return githubEmail;
|
|
|
|
if (googleEmail) return googleEmail;
|
|
|
|
if (linkedinEmail) return linkedinEmail;
|
|
|
|
return undefined;
|
2017-08-02 16:18:34 +09:00
|
|
|
},
|
|
|
|
searchable: true
|
2016-06-23 15:00:58 +09:00
|
|
|
// unique: true // note: find a way to fix duplicate accounts before enabling this
|
|
|
|
},
|
|
|
|
/**
|
|
|
|
A hash of the email, used for Gravatar // TODO: change this when email changes
|
|
|
|
*/
|
2017-01-18 10:18:33 +09:00
|
|
|
emailHash: {
|
2016-06-23 15:00:58 +09:00
|
|
|
type: String,
|
2016-10-30 18:25:51 +01:00
|
|
|
optional: true,
|
2016-12-06 10:55:47 +01:00
|
|
|
viewableBy: ['guests'],
|
2017-04-28 09:24:28 +09:00
|
|
|
onInsert: user => {
|
|
|
|
if (user.email) {
|
|
|
|
return Users.avatar.hash(user.email);
|
|
|
|
}
|
|
|
|
}
|
2016-06-23 15:00:58 +09:00
|
|
|
},
|
2017-05-01 10:49:27 +09:00
|
|
|
avatarUrl: {
|
|
|
|
type: String,
|
|
|
|
optional: true,
|
|
|
|
viewableBy: ['guests'],
|
|
|
|
onInsert: user => {
|
2017-05-03 15:37:44 +09:00
|
|
|
|
2017-05-03 14:42:39 +09:00
|
|
|
const twitterAvatar = Utils.getNestedProperty(user, 'services.twitter.profile_image_url_https');
|
2017-05-03 15:37:44 +09:00
|
|
|
const facebookId = Utils.getNestedProperty(user, 'services.facebook.id');
|
2017-05-03 13:04:33 +05:30
|
|
|
|
2017-05-03 14:42:39 +09:00
|
|
|
if (twitterAvatar) return twitterAvatar;
|
2017-05-03 15:37:44 +09:00
|
|
|
if (facebookId) return `https://graph.facebook.com/${facebookId}/picture?type=large`;
|
|
|
|
return undefined;
|
|
|
|
|
2017-07-08 11:36:27 +09:00
|
|
|
},
|
|
|
|
resolveAs: {
|
|
|
|
fieldName: 'avatarUrl',
|
|
|
|
type: 'String',
|
|
|
|
resolver: async (user, args, context) => {
|
|
|
|
if (user.avatarUrl) {
|
|
|
|
return user.avatarUrl;
|
|
|
|
} else {
|
|
|
|
// user has already been cleaned up by Users.restrictViewableFields, so we
|
|
|
|
// reload the full user object from the cache to access user.services
|
|
|
|
const fullUser = await Users.loader.load(user._id);
|
|
|
|
return Users.avatar.getUrl(fullUser);
|
|
|
|
}
|
|
|
|
}
|
2017-05-01 10:49:27 +09:00
|
|
|
}
|
|
|
|
},
|
2016-06-23 15:00:58 +09:00
|
|
|
/**
|
|
|
|
The HTML version of the bio field
|
|
|
|
*/
|
2017-01-18 10:18:33 +09:00
|
|
|
htmlBio: {
|
2016-06-23 15:00:58 +09:00
|
|
|
type: String,
|
|
|
|
optional: true,
|
2016-12-06 10:55:47 +01:00
|
|
|
viewableBy: ['guests'],
|
2016-06-23 15:00:58 +09:00
|
|
|
},
|
|
|
|
/**
|
|
|
|
The user's karma
|
|
|
|
*/
|
2017-01-18 10:18:33 +09:00
|
|
|
karma: {
|
2016-06-23 15:00:58 +09:00
|
|
|
type: Number,
|
2016-10-30 18:25:51 +01:00
|
|
|
optional: true,
|
2016-12-06 10:55:47 +01:00
|
|
|
viewableBy: ['guests'],
|
2016-06-23 15:00:58 +09:00
|
|
|
},
|
|
|
|
/**
|
|
|
|
The user's profile URL slug // TODO: change this when displayName changes
|
|
|
|
*/
|
2017-01-18 10:18:33 +09:00
|
|
|
slug: {
|
2016-06-23 15:00:58 +09:00
|
|
|
type: String,
|
2016-10-30 18:25:51 +01:00
|
|
|
optional: true,
|
2016-12-06 10:55:47 +01:00
|
|
|
viewableBy: ['guests'],
|
2017-07-31 10:33:53 +01:00
|
|
|
order: 40,
|
2017-04-28 09:24:28 +09:00
|
|
|
onInsert: user => {
|
|
|
|
// create a basic slug from display name and then modify it if this slugs already exists;
|
|
|
|
const basicSlug = Utils.slugify(user.displayName);
|
|
|
|
return Utils.getUnusedSlug(Users, basicSlug);
|
|
|
|
}
|
2016-06-23 15:00:58 +09:00
|
|
|
},
|
|
|
|
/**
|
2017-07-31 10:33:53 +01:00
|
|
|
A link to the user's homepage
|
|
|
|
*/
|
|
|
|
website: {
|
|
|
|
type: String,
|
|
|
|
regEx: SimpleSchema.RegEx.Url,
|
|
|
|
optional: true,
|
|
|
|
control: "text",
|
|
|
|
insertableBy: ['members'],
|
|
|
|
editableBy: ['members'],
|
|
|
|
viewableBy: ['guests'],
|
|
|
|
order: 50,
|
|
|
|
},
|
|
|
|
/**
|
2017-01-18 10:19:23 +09:00
|
|
|
The user's Twitter username
|
2016-06-23 15:00:58 +09:00
|
|
|
*/
|
2017-01-18 10:18:33 +09:00
|
|
|
twitterUsername: {
|
2016-06-23 15:00:58 +09:00
|
|
|
type: String,
|
|
|
|
optional: true,
|
|
|
|
control: "text",
|
2016-12-06 10:55:47 +01:00
|
|
|
insertableBy: ['members'],
|
|
|
|
editableBy: ['members'],
|
|
|
|
viewableBy: ['guests'],
|
2017-07-31 10:33:53 +01:00
|
|
|
order: 60,
|
2017-07-08 11:36:27 +09:00
|
|
|
resolveAs: {
|
|
|
|
type: 'String',
|
|
|
|
resolver: (user, args, context) => {
|
|
|
|
return context.Users.getTwitterName(context.Users.findOne(user._id));
|
|
|
|
},
|
|
|
|
},
|
2017-04-28 09:24:28 +09:00
|
|
|
onInsert: user => {
|
|
|
|
if (user.services && user.services.twitter && user.services.twitter.screenName) {
|
|
|
|
return user.services.twitter.screenName;
|
|
|
|
}
|
|
|
|
}
|
2016-06-23 15:00:58 +09:00
|
|
|
},
|
2016-07-19 17:30:59 +09:00
|
|
|
/**
|
|
|
|
Groups
|
|
|
|
*/
|
2017-01-18 10:18:33 +09:00
|
|
|
groups: {
|
2017-03-16 01:25:08 +08:00
|
|
|
type: Array,
|
2016-07-19 17:30:59 +09:00
|
|
|
optional: true,
|
|
|
|
control: "checkboxgroup",
|
2016-12-06 10:55:47 +01:00
|
|
|
insertableBy: ['admins'],
|
|
|
|
editableBy: ['admins'],
|
|
|
|
viewableBy: ['guests'],
|
2016-10-05 08:43:13 +02:00
|
|
|
form: {
|
2016-07-19 17:30:59 +09:00
|
|
|
options: function () {
|
2016-12-06 10:55:47 +01:00
|
|
|
const groups = _.without(_.keys(Users.groups), "guests", "members", "admins");
|
2016-07-19 17:30:59 +09:00
|
|
|
return groups.map(group => {return {value: group, label: group};});
|
|
|
|
}
|
|
|
|
},
|
|
|
|
},
|
2017-03-16 01:25:08 +08:00
|
|
|
'groups.$': {
|
|
|
|
type: String,
|
|
|
|
optional: true
|
2017-08-20 17:01:57 +09:00
|
|
|
},
|
|
|
|
|
|
|
|
// GraphQL only fields
|
|
|
|
|
2017-08-24 13:16:25 +09:00
|
|
|
pageUrl: {
|
2017-08-20 17:01:57 +09:00
|
|
|
type: String,
|
|
|
|
optional: true,
|
2018-01-02 13:05:34 +09:00
|
|
|
viewableBy: ['guests'],
|
2017-08-20 17:01:57 +09:00
|
|
|
resolveAs: {
|
|
|
|
type: 'String',
|
|
|
|
resolver: (user, args, context) => {
|
|
|
|
return Users.getProfileUrl(user, true);
|
|
|
|
},
|
|
|
|
}
|
2017-03-16 01:25:08 +08:00
|
|
|
}
|
2017-08-24 13:16:25 +09:00
|
|
|
|
2016-11-22 18:14:51 -05:00
|
|
|
};
|
2016-10-29 16:37:33 +09:00
|
|
|
|
2016-11-22 18:14:51 -05:00
|
|
|
export default schema;
|