2017-03-06 22:09:50 +00:00
import moment from 'moment' ;
2016-09-16 15:18:07 +00:00
import common from '../../../common' ;
Automatically mute users who attempt to post a slur, fixes #8062 (#8177)
* Initial psuedo-code for checking for slurs in messages
* Initial working prototype for blocking posting of slurs. Moved check from group.js to the chat api. Still needs: to permanently revoke chat privileges, to notify the moderators, a better method for checking for the blacklisted words, and a way to get the real list of words to check.
* Permanently revoke chat privileges when attempting to post a slur.
* Removed console logs
* Fixing rebase
* Do not moderate private groups
* Moved slur check to a generic check for banned words function
* Moved list of slurs to a separate file, fixed misplacement of return in ContainsBannedWords() function
* Slurs are blocked in both public and private groups
* Added code to send a slack message for slurs
* Fixed formatting issues
* Incorporated tectContainsBannedWords() function from PR 8197, added an argument to specify the list of banned words to check
* Added initial tests for blocking slurs and revoking chat priviliges
* Uncommented line to save revoked privileges
* Check that privileges are revoked in private groups
* Moved code to email/slack mods to chat api file
* Switched to BadRequest instead of NotFound error
* Restore chat privileges after test
* Using official placeholder slur
* Fixed line to export sendSubscriptionNotification function for slack
* Replaced muteUser function in user methods with a single line in the chat controller file
* Reset chatRevoked flag to false in a single line
* Switched method of setting chatRevoked flag so that it is updated locally and in the database
* First attempt at the muteUser function: revokes user's chat privileges and notifies moderators
* Manual merge for cherry-pick
* Initial working prototype for blocking posting of slurs. Moved check from group.js to the chat api. Still needs: to permanently revoke chat privileges, to notify the moderators, a better method for checking for the blacklisted words, and a way to get the real list of words to check.
* Permanently revoke chat privileges when attempting to post a slur.
* Removed console logs
* Created report to be sent to moderators via email
* Do not moderate private groups
* Moved slur check to a generic check for banned words function
* Moved list of slurs to a separate file, fixed misplacement of return in ContainsBannedWords() function
* Slurs are blocked in both public and private groups
* Added code to send a slack message for slurs
* Fixed formatting issues
* Incorporated tectContainsBannedWords() function from PR 8197, added an argument to specify the list of banned words to check
* Added initial tests for blocking slurs and revoking chat priviliges
* Uncommented line to save revoked privileges
* Check that privileges are revoked in private groups
* Moved code to email/slack mods to chat api file
* Switched to BadRequest instead of NotFound error
* Restore chat privileges after test
* Using official placeholder slur
* Fixed line to export sendSubscriptionNotification function for slack
* Replaced muteUser function in user methods with a single line in the chat controller file
* Reset chatRevoked flag to false in a single line
* Switched method of setting chatRevoked flag so that it is updated locally and in the database
* Removed some code that got re-added after rebase
* Tests for automatic slur muting pass but are incomplete (do not check that chatRevoked flag is true)
* Moved list of banned slurs to server side
* Added warning to bannedSlurs file
* Test chat privileges revoked when posting slur in public chat
* Fix issues left over after rebase (I hope)
* Added code to test for revoked chat privileges after posting a slur in a private group
* Moved banned slur message into locales message
* Added new code to check for banned slurs (parallels banned words code)
* Fixed AUTHOR_MOTAL_URL in sendTxn for slur blocking
* Added tests that email sent on attempted slur in chat post
* Created context for slur-related-tests, fixed sandboxing of email. Successfully tests that email.sendTxn is called, but the email content test fails
* commented out slack (for now) and cleaned up tests of sending email
* Successfully tests that slur-report-to-mods email is sent
* Slack message is sent, and testing works, but some user variables seem to only work when found in chat.js and passed to slack
* Made some fixes for lint, but not sure what to do about the camel case requirement fail, since that's how they're defined in other slack calls
* Slack tests pass, skipped camelcase check around those code blocks
* Fixed InternalServerError caused by slack messaging
* Updated chat privileges revoked error
* fix(locale): typo correction
2017-07-19 21:06:15 +00:00
2016-06-07 14:14:19 +00:00
import {
chatDefaults ,
TAVERN _ID ,
2017-07-16 16:23:57 +00:00
model as Group ,
2016-06-07 14:14:19 +00:00
} from '../group' ;
Automatically mute users who attempt to post a slur, fixes #8062 (#8177)
* Initial psuedo-code for checking for slurs in messages
* Initial working prototype for blocking posting of slurs. Moved check from group.js to the chat api. Still needs: to permanently revoke chat privileges, to notify the moderators, a better method for checking for the blacklisted words, and a way to get the real list of words to check.
* Permanently revoke chat privileges when attempting to post a slur.
* Removed console logs
* Fixing rebase
* Do not moderate private groups
* Moved slur check to a generic check for banned words function
* Moved list of slurs to a separate file, fixed misplacement of return in ContainsBannedWords() function
* Slurs are blocked in both public and private groups
* Added code to send a slack message for slurs
* Fixed formatting issues
* Incorporated tectContainsBannedWords() function from PR 8197, added an argument to specify the list of banned words to check
* Added initial tests for blocking slurs and revoking chat priviliges
* Uncommented line to save revoked privileges
* Check that privileges are revoked in private groups
* Moved code to email/slack mods to chat api file
* Switched to BadRequest instead of NotFound error
* Restore chat privileges after test
* Using official placeholder slur
* Fixed line to export sendSubscriptionNotification function for slack
* Replaced muteUser function in user methods with a single line in the chat controller file
* Reset chatRevoked flag to false in a single line
* Switched method of setting chatRevoked flag so that it is updated locally and in the database
* First attempt at the muteUser function: revokes user's chat privileges and notifies moderators
* Manual merge for cherry-pick
* Initial working prototype for blocking posting of slurs. Moved check from group.js to the chat api. Still needs: to permanently revoke chat privileges, to notify the moderators, a better method for checking for the blacklisted words, and a way to get the real list of words to check.
* Permanently revoke chat privileges when attempting to post a slur.
* Removed console logs
* Created report to be sent to moderators via email
* Do not moderate private groups
* Moved slur check to a generic check for banned words function
* Moved list of slurs to a separate file, fixed misplacement of return in ContainsBannedWords() function
* Slurs are blocked in both public and private groups
* Added code to send a slack message for slurs
* Fixed formatting issues
* Incorporated tectContainsBannedWords() function from PR 8197, added an argument to specify the list of banned words to check
* Added initial tests for blocking slurs and revoking chat priviliges
* Uncommented line to save revoked privileges
* Check that privileges are revoked in private groups
* Moved code to email/slack mods to chat api file
* Switched to BadRequest instead of NotFound error
* Restore chat privileges after test
* Using official placeholder slur
* Fixed line to export sendSubscriptionNotification function for slack
* Replaced muteUser function in user methods with a single line in the chat controller file
* Reset chatRevoked flag to false in a single line
* Switched method of setting chatRevoked flag so that it is updated locally and in the database
* Removed some code that got re-added after rebase
* Tests for automatic slur muting pass but are incomplete (do not check that chatRevoked flag is true)
* Moved list of banned slurs to server side
* Added warning to bannedSlurs file
* Test chat privileges revoked when posting slur in public chat
* Fix issues left over after rebase (I hope)
* Added code to test for revoked chat privileges after posting a slur in a private group
* Moved banned slur message into locales message
* Added new code to check for banned slurs (parallels banned words code)
* Fixed AUTHOR_MOTAL_URL in sendTxn for slur blocking
* Added tests that email sent on attempted slur in chat post
* Created context for slur-related-tests, fixed sandboxing of email. Successfully tests that email.sendTxn is called, but the email content test fails
* commented out slack (for now) and cleaned up tests of sending email
* Successfully tests that slur-report-to-mods email is sent
* Slack message is sent, and testing works, but some user variables seem to only work when found in chat.js and passed to slack
* Made some fixes for lint, but not sure what to do about the camel case requirement fail, since that's how they're defined in other slack calls
* Slack tests pass, skipped camelcase check around those code blocks
* Fixed InternalServerError caused by slack messaging
* Updated chat privileges revoked error
* fix(locale): typo correction
2017-07-19 21:06:15 +00:00
2017-06-07 01:49:05 +00:00
import { defaults , map , flatten , flow , compact , uniq , partialRight } from 'lodash' ;
2017-01-04 14:27:54 +00:00
import { model as UserNotification } from '../userNotification' ;
2016-06-07 14:14:19 +00:00
import schema from './schema' ;
2018-04-08 14:27:03 +00:00
import payments from '../../libs/payments/payments' ;
import amazonPayments from '../../libs/payments/amazon' ;
import stripePayments from '../../libs/payments/stripe' ;
import paypalPayments from '../../libs/payments/paypal' ;
2016-06-07 14:14:19 +00:00
2017-06-21 19:28:12 +00:00
const daysSince = common . daysSince ;
2016-06-07 14:14:19 +00:00
schema . methods . isSubscribed = function isSubscribed ( ) {
2018-01-08 18:50:15 +00:00
const now = new Date ( ) ;
const plan = this . purchased . plan ;
2017-03-06 22:09:50 +00:00
return plan && plan . customerId && ( ! plan . dateTerminated || moment ( plan . dateTerminated ) . isAfter ( now ) ) ;
} ;
schema . methods . hasNotCancelled = function hasNotCancelled ( ) {
let plan = this . purchased . plan ;
return this . isSubscribed ( ) && ! plan . dateTerminated ;
2016-06-07 14:14:19 +00:00
} ;
// Get an array of groups ids the user is member of
schema . methods . getGroups = function getUserGroups ( ) {
2017-06-21 19:28:12 +00:00
let userGroups = this . guilds . slice ( 0 ) ; // clone this.guilds so we don't modify the original
2016-06-07 14:14:19 +00:00
if ( this . party . _id ) userGroups . push ( this . party . _id ) ;
userGroups . push ( TAVERN _ID ) ;
return userGroups ;
} ;
2017-06-07 01:49:05 +00:00
/* eslint-disable no-unused-vars */ // The checks below all get access to sndr and rcvr, but not all use both
const INTERACTION _CHECKS = Object . freeze ( {
always : [
// Revoked chat privileges block all interactions to prevent the evading of harassment protections
// See issue #7971 for some discussion
( sndr , rcvr ) => sndr . flags . chatRevoked && 'chatPrivilegesRevoked' ,
// Direct user blocks prevent all interactions
( sndr , rcvr ) => rcvr . inbox . blocks . includes ( sndr . _id ) && 'notAuthorizedToSendMessageToThisUser' ,
( sndr , rcvr ) => sndr . inbox . blocks . includes ( rcvr . _id ) && 'notAuthorizedToSendMessageToThisUser' ,
] ,
'send-private-message' : [
// Private messaging has an opt-out, which does not affect other interactions
( sndr , rcvr ) => rcvr . inbox . optOut && 'notAuthorizedToSendMessageToThisUser' ,
// We allow a player to message themselves so they can test how PMs work or send their own notes to themselves
] ,
'transfer-gems' : [
// Unlike private messages, gems can't be sent to oneself
( sndr , rcvr ) => rcvr . _id === sndr . _id && 'cannotSendGemsToYourself' ,
] ,
} ) ;
/* eslint-enable no-unused-vars */
export const KNOWN _INTERACTIONS = Object . freeze ( Object . keys ( INTERACTION _CHECKS ) . filter ( key => key !== 'always' ) ) ;
// Get an array of error message keys that would be thrown if the given interaction was attempted
schema . methods . getObjectionsToInteraction = function getObjectionsToInteraction ( interaction , receiver ) {
if ( ! KNOWN _INTERACTIONS . includes ( interaction ) ) {
throw new Error ( ` Unknown kind of interaction: " ${ interaction } ", expected one of ${ KNOWN _INTERACTIONS . join ( ', ' ) } ` ) ;
}
let sender = this ;
let checks = [
INTERACTION _CHECKS . always ,
INTERACTION _CHECKS [ interaction ] ,
] ;
let executeChecks = partialRight ( map , ( check ) => check ( sender , receiver ) ) ;
return flow (
flatten ,
executeChecks ,
compact , // Remove passed checks (passed checks return falsy; failed checks return message keys)
uniq
) ( checks ) ;
} ;
2016-12-16 00:36:54 +00:00
/ * *
2017-06-21 19:28:12 +00:00
* Sends a message to a this . Archives a copy in sender ' s inbox .
2016-12-16 00:36:54 +00:00
*
* @ param userToReceiveMessage The receiver
* @ param options
* @ param options . receiverMsg The message to send to the receiver
* @ param options . senderMsg The message to archive instead of receiverMsg
* @ return N / A
* /
schema . methods . sendMessage = async function sendMessage ( userToReceiveMessage , options ) {
2016-06-07 14:14:19 +00:00
let sender = this ;
2016-12-16 00:36:54 +00:00
let senderMsg = options . senderMsg || options . receiverMsg ;
2016-06-07 14:14:19 +00:00
2016-12-16 00:36:54 +00:00
common . refPush ( userToReceiveMessage . inbox . messages , chatDefaults ( options . receiverMsg , sender ) ) ;
2016-06-07 14:14:19 +00:00
userToReceiveMessage . inbox . newMessages ++ ;
userToReceiveMessage . _v ++ ;
userToReceiveMessage . markModified ( 'inbox.messages' ) ;
2018-01-31 10:55:39 +00:00
/ * @ T O D O d i s a b l e d u n t i l m o b i l e i s r e a d y
let excerpt ;
if ( ! options . receiverMsg ) {
excerpt = '' ;
} else if ( options . receiverMsg . length < 100 ) {
excerpt = options . receiverMsg ;
} else {
excerpt = options . receiverMsg . substring ( 0 , 100 ) ;
}
userToReceiveMessage . addNotification ( 'NEW_INBOX_MESSAGE' , {
sender : {
id : sender . _id ,
name : sender . profile . name ,
} ,
excerpt ,
messageId : newMessage . id ,
} ) ;
* /
2016-12-16 00:36:54 +00:00
common . refPush ( sender . inbox . messages , defaults ( { sent : true } , chatDefaults ( senderMsg , userToReceiveMessage ) ) ) ;
2016-06-07 14:14:19 +00:00
sender . markModified ( 'inbox.messages' ) ;
let promises = [ userToReceiveMessage . save ( ) , sender . save ( ) ] ;
2018-03-15 18:59:36 +00:00
await Promise . all ( promises ) ;
2016-06-07 14:14:19 +00:00
} ;
2017-01-04 14:27:54 +00:00
/ * *
* Creates a notification based on the input parameters and adds it to the local user notifications array .
* This does not save the notification to the database or interact with the database in any way .
*
2017-06-21 19:28:12 +00:00
* @ param type The type of notification to add to the this . Possible values are defined in the UserNotificaiton Schema
2017-01-04 14:27:54 +00:00
* @ param data The data to add to the notification
2018-01-31 10:55:39 +00:00
* @ param seen If the notification should be marked as seen
2017-01-04 14:27:54 +00:00
* /
2018-01-31 10:55:39 +00:00
schema . methods . addNotification = function addUserNotification ( type , data = { } , seen = false ) {
2016-06-07 14:14:19 +00:00
this . notifications . push ( {
type ,
data ,
2018-01-31 10:55:39 +00:00
seen ,
2016-06-07 14:14:19 +00:00
} ) ;
2016-08-04 17:56:00 +00:00
} ;
2017-01-04 14:27:54 +00:00
/ * *
* Creates a notification based on the type and data input parameters and saves that new notification
* to the database directly using an update statement . The local copy of these users are not updated by
* this operation . Use this function when you want to add a notification to a user ( s ) , but do not have
* the user document ( s ) opened .
*
* @ param query A Mongoose query defining the users to add the notification to .
2017-06-21 19:28:12 +00:00
* @ param type The type of notification to add to the this . Possible values are defined in the UserNotificaiton Schema
2017-01-04 14:27:54 +00:00
* @ param data The data to add to the notification
* /
2018-01-31 10:55:39 +00:00
schema . statics . pushNotification = async function pushNotification ( query , type , data = { } , seen = false ) {
let newNotification = new UserNotification ( { type , data , seen } ) ;
2018-02-04 12:28:05 +00:00
2017-01-04 14:27:54 +00:00
let validationResult = newNotification . validateSync ( ) ;
if ( validationResult ) {
throw validationResult ;
}
2018-02-04 12:28:05 +00:00
2018-01-31 10:55:39 +00:00
await this . update ( query , { $push : { notifications : newNotification . toObject ( ) } } , { multi : true } ) . exec ( ) ;
2017-01-04 14:27:54 +00:00
} ;
2018-05-28 11:38:59 +00:00
// Static method to add/remove properties to a JSON User object,
// For example for when the user is returned using `.lean()` and thus doesn't
// have access to any mongoose helper
schema . statics . transformJSONUser = function transformJSONUser ( jsonUser , addComputedStats = false ) {
// Add id property
jsonUser . id = jsonUser . _id ;
if ( addComputedStats ) this . addComputedStatsToJSONObj ( jsonUser . stats , jsonUser ) ;
} ;
2016-08-04 17:56:00 +00:00
// Add stats.toNextLevel, stats.maxMP and stats.maxHealth
2016-10-02 14:16:22 +00:00
// to a JSONified User stats object
2018-05-28 11:38:59 +00:00
schema . statics . addComputedStatsToJSONObj = function addComputedStatsToUserJSONObj ( userStatsJSON , user ) {
2017-06-21 19:28:12 +00:00
// NOTE: if an item is manually added to this.stats then
2016-08-04 17:56:00 +00:00
// common/fns/predictableRandom must be tweaked so the new item is not considered.
// Otherwise the client will have it while the server won't and the results will be different.
2018-05-28 11:38:59 +00:00
userStatsJSON . toNextLevel = common . tnl ( user . stats . lvl ) ;
userStatsJSON . maxHealth = common . maxHealth ;
userStatsJSON . maxMP = common . statsComputed ( user ) . maxMP ;
2016-10-02 14:16:22 +00:00
2018-05-28 11:38:59 +00:00
return userStatsJSON ;
2016-09-07 17:58:26 +00:00
} ;
2017-03-06 22:09:50 +00:00
2017-06-07 17:25:37 +00:00
/ * *
* Cancels a subscription .
*
* @ param options
* @ param options . user The user object who is purchasing
* @ param options . groupId The id of the group purchasing a subscription
* @ param options . headers The request headers ( only for Amazon subscriptions )
* @ param options . cancellationReason A text string to control sending an email
*
* @ return a Promise from api . cancelSubscription ( )
* /
2017-03-06 22:09:50 +00:00
// @TODO: There is currently a three way relation between the user, payment methods and the payment helper
// This creates some odd Dependency Injection issues. To counter that, we use the user as the third layer
// To negotiate between the payment providers and the payment helper (which probably has too many responsiblities)
// In summary, currently is is best practice to use this method to cancel a user subscription, rather than calling the
// payment helper.
2017-06-07 17:25:37 +00:00
schema . methods . cancelSubscription = async function cancelSubscription ( options = { } ) {
2017-03-06 22:09:50 +00:00
let plan = this . purchased . plan ;
2017-06-07 17:25:37 +00:00
options . user = this ;
2017-03-06 22:09:50 +00:00
if ( plan . paymentMethod === amazonPayments . constants . PAYMENT _METHOD ) {
2017-06-07 17:25:37 +00:00
return await amazonPayments . cancelSubscription ( options ) ;
2017-03-06 22:09:50 +00:00
} else if ( plan . paymentMethod === stripePayments . constants . PAYMENT _METHOD ) {
2017-06-07 17:25:37 +00:00
return await stripePayments . cancelSubscription ( options ) ;
2017-03-06 22:09:50 +00:00
} else if ( plan . paymentMethod === paypalPayments . constants . PAYMENT _METHOD ) {
2017-06-07 17:25:37 +00:00
return await paypalPayments . subscribeCancel ( options ) ;
2017-03-06 22:09:50 +00:00
}
2017-06-07 17:25:37 +00:00
// Android and iOS subscriptions cannot be cancelled by Habitica.
2017-03-06 22:09:50 +00:00
2017-06-07 17:25:37 +00:00
return await payments . cancelSubscription ( options ) ;
2017-03-06 22:09:50 +00:00
} ;
2017-06-21 19:28:12 +00:00
2017-07-16 16:23:57 +00:00
schema . methods . daysUserHasMissed = function daysUserHasMissed ( now , req = { } ) {
2017-06-21 19:28:12 +00:00
// If the user's timezone has changed (due to travel or daylight savings),
// cron can be triggered twice in one day, so we check for that and use
// both timezones to work out if cron should run.
// CDS = Custom Day Start time.
let timezoneOffsetFromUserPrefs = this . preferences . timezoneOffset ;
let timezoneOffsetAtLastCron = isFinite ( this . preferences . timezoneOffsetAtLastCron ) ? this . preferences . timezoneOffsetAtLastCron : timezoneOffsetFromUserPrefs ;
let timezoneOffsetFromBrowser = typeof req . header === 'function' && Number ( req . header ( 'x-user-timezoneoffset' ) ) ;
timezoneOffsetFromBrowser = isFinite ( timezoneOffsetFromBrowser ) ? timezoneOffsetFromBrowser : timezoneOffsetFromUserPrefs ;
// NB: All timezone offsets can be 0, so can't use `... || ...` to apply non-zero defaults
if ( timezoneOffsetFromBrowser !== timezoneOffsetFromUserPrefs ) {
// The user's browser has just told Habitica that the user's timezone has
// changed so store and use the new zone.
this . preferences . timezoneOffset = timezoneOffsetFromBrowser ;
timezoneOffsetFromUserPrefs = timezoneOffsetFromBrowser ;
}
// How many days have we missed using the user's current timezone:
let daysMissed = daysSince ( this . lastCron , defaults ( { now } , this . preferences ) ) ;
if ( timezoneOffsetAtLastCron !== timezoneOffsetFromUserPrefs ) {
2017-12-14 15:09:11 +00:00
// Give the user extra time based on the difference in timezones
if ( timezoneOffsetAtLastCron < timezoneOffsetFromUserPrefs ) {
const differenceBetweenTimezonesInMinutes = timezoneOffsetFromUserPrefs - timezoneOffsetAtLastCron ;
now = moment ( now ) . subtract ( differenceBetweenTimezonesInMinutes , 'minutes' ) ;
}
2017-06-21 19:28:12 +00:00
// Since cron last ran, the user's timezone has changed.
// How many days have we missed using the old timezone:
let daysMissedNewZone = daysMissed ;
let daysMissedOldZone = daysSince ( this . lastCron , defaults ( {
now ,
timezoneOffsetOverride : timezoneOffsetAtLastCron ,
} , this . preferences ) ) ;
if ( timezoneOffsetAtLastCron < timezoneOffsetFromUserPrefs ) {
// The timezone change was in the unsafe direction.
// E.g., timezone changes from UTC+1 (offset -60) to UTC+0 (offset 0).
// or timezone changes from UTC-4 (offset 240) to UTC-5 (offset 300).
// Local time changed from, for example, 03:00 to 02:00.
if ( daysMissedOldZone > 0 && daysMissedNewZone > 0 ) {
// Both old and new timezones indicate that we SHOULD run cron, so
// it is safe to do so immediately.
daysMissed = Math . min ( daysMissedOldZone , daysMissedNewZone ) ;
// use minimum value to be nice to user
} else if ( daysMissedOldZone > 0 ) {
// The old timezone says that cron should run; the new timezone does not.
// This should be impossible for this direction of timezone change, but
// just in case I'm wrong...
// TODO
// console.log("zone has changed - old zone says run cron, NEW zone says no - stop cron now only -- SHOULD NOT HAVE GOT TO HERE", timezoneOffsetAtLastCron, timezoneOffsetFromUserPrefs, now); // used in production for confirming this never happens
} else if ( daysMissedNewZone > 0 ) {
// The old timezone says that cron should NOT run -- i.e., cron has
// already run today, from the old timezone's point of view.
// The new timezone says that cron SHOULD run, but this is almost
// certainly incorrect.
// This happens when cron occurred at a time soon after the CDS. When
// you reinterpret that time in the new timezone, it looks like it
// was before the CDS, because local time has stepped backwards.
// To fix this, rewrite the cron time to a time that the new
// timezone interprets as being in today.
daysMissed = 0 ; // prevent cron running now
let timezoneOffsetDiff = timezoneOffsetAtLastCron - timezoneOffsetFromUserPrefs ;
// e.g., for dangerous zone change: 240 - 300 = -60 or -660 - -600 = -60
this . lastCron = moment ( this . lastCron ) . subtract ( timezoneOffsetDiff , 'minutes' ) ;
// NB: We don't change this.auth.timestamps.loggedin so that will still record the time that the previous cron actually ran.
// From now on we can ignore the old timezone:
this . preferences . timezoneOffsetAtLastCron = timezoneOffsetFromUserPrefs ;
} else {
// Both old and new timezones indicate that cron should
// NOT run.
daysMissed = 0 ; // prevent cron running now
}
} else if ( timezoneOffsetAtLastCron > timezoneOffsetFromUserPrefs ) {
daysMissed = daysMissedNewZone ;
// TODO: Either confirm that there is nothing that could possibly go wrong here and remove the need for this else branch, or fix stuff.
// There are probably situations where the Dailies do not reset early enough for a user who was expecting the zone change and wants to use all their Dailies immediately in the new zone;
// if so, we should provide an option for easy reset of Dailies (can't be automatic because there will be other situations where the user was not prepared).
}
}
return { daysMissed , timezoneOffsetFromUserPrefs } ;
} ;
2017-07-16 16:23:57 +00:00
2017-11-20 18:34:41 +00:00
async function getUserGroupData ( user ) {
const userGroups = user . getGroups ( ) ;
const groups = await Group
. find ( {
_id : { $in : userGroups } ,
} )
. select ( 'leaderOnly leader purchased' )
. exec ( ) ;
return groups ;
}
2017-07-16 16:23:57 +00:00
// Determine if the user can get gems: some groups restrict their members ability to obtain them.
// User is allowed to buy gems if no group has `leaderOnly.getGems` === true or if
// its the group leader
schema . methods . canGetGems = async function canObtainGems ( ) {
const user = this ;
const plan = user . purchased . plan ;
if ( ! user . isSubscribed ( ) || plan . customerId !== payments . constants . GROUP _PLAN _CUSTOMER _ID ) {
return true ;
}
2017-11-20 18:34:41 +00:00
const groups = await getUserGroupData ( user ) ;
2017-07-16 16:23:57 +00:00
return groups . every ( g => {
return ! g . isSubscribed ( ) || g . leader === user . _id || g . leaderOnly . getGems !== true ;
} ) ;
} ;
2017-11-20 18:34:41 +00:00
schema . methods . isMemberOfGroupPlan = async function isMemberOfGroupPlan ( ) {
const groups = await getUserGroupData ( this ) ;
return groups . every ( g => {
return g . isSubscribed ( ) ;
} ) ;
} ;
2017-12-11 17:48:17 +00:00
schema . methods . isAdmin = function isAdmin ( ) {
return this . contributor && this . contributor . admin ;
} ;