mirror of
https://github.com/codeninjasllc/codecombat.git
synced 2024-11-23 23:58:02 -05:00
Merge branch 'master' of https://github.com/codecombat/codecombat
This commit is contained in:
commit
ab77176ed0
23 changed files with 298 additions and 97 deletions
|
@ -323,7 +323,7 @@ module.exports = CocoSprite = class CocoSprite extends CocoClass
|
|||
if not action and @thang?.actionActivated and not @stopLogging
|
||||
console.error "action is", action, "for", @thang?.id, "from", @currentRootAction, @thang.action, @thang.getActionName?()
|
||||
@stopLogging = true
|
||||
@queueAction(action) if isDifferent or (@thang?.actionActivated and action.name isnt 'move')
|
||||
@queueAction(action) if action and (isDifferent or (@thang?.actionActivated and action.name isnt 'move'))
|
||||
@updateActionDirection()
|
||||
|
||||
determineAction: ->
|
||||
|
@ -332,8 +332,11 @@ module.exports = CocoSprite = class CocoSprite extends CocoClass
|
|||
action = thang.action if thang?.acts
|
||||
action ?= @currentRootAction.name if @currentRootAction?
|
||||
action ?= 'idle'
|
||||
action = null unless @actions[action]?
|
||||
return null unless action
|
||||
unless @actions[action]?
|
||||
@warnedFor ?= {}
|
||||
console.warn 'Cannot show action', action, 'for', @thangType.get('name'), 'because it DNE' unless @warnedFor[action]
|
||||
@warnedFor[action] = true
|
||||
return null
|
||||
action = 'break' if @actions.break? and @thang?.erroredOut
|
||||
action = 'die' if @actions.die? and thang?.health? and thang.health <= 0
|
||||
@actions[action]
|
||||
|
|
|
@ -119,6 +119,7 @@ class CocoModel extends Backbone.Model
|
|||
@set prop, defaultValue
|
||||
for prop, sch of @constructor.schema.properties or {}
|
||||
continue if @get(prop)?
|
||||
continue if prop is 'emails' # hack, defaults are handled through User.coffee's email-specific methods.
|
||||
#console.log "setting", prop, "to", sch.default, "from sch.default" if sch.default?
|
||||
@set prop, sch.default if sch.default?
|
||||
if @loaded
|
||||
|
|
|
@ -9,6 +9,7 @@ module.exports = class User extends CocoModel
|
|||
|
||||
initialize: ->
|
||||
super()
|
||||
@migrateEmails()
|
||||
|
||||
isAdmin: ->
|
||||
permissions = @attributes['permissions'] or []
|
||||
|
@ -43,3 +44,33 @@ module.exports = class User extends CocoModel
|
|||
)
|
||||
cache[id] = user
|
||||
user
|
||||
|
||||
getEnabledEmails: ->
|
||||
@migrateEmails()
|
||||
emails = _.clone(@get('emails')) or {}
|
||||
emails = _.defaults emails, @schema().properties.emails.default
|
||||
(emailName for emailName, emailDoc of emails when emailDoc.enabled)
|
||||
|
||||
setEmailSubscription: (name, enabled) ->
|
||||
newSubs = _.clone(@get('emails')) or {}
|
||||
(newSubs[name] ?= {}).enabled = enabled
|
||||
@set 'emails', newSubs
|
||||
|
||||
emailMap:
|
||||
announcement: 'generalNews'
|
||||
developer: 'archmageNews'
|
||||
tester: 'adventurerNews'
|
||||
level_creator: 'artisanNews'
|
||||
article_editor: 'scribeNews'
|
||||
translator: 'diplomatNews'
|
||||
support: 'ambassadorNews'
|
||||
notification: 'anyNotes'
|
||||
|
||||
migrateEmails: ->
|
||||
return if @attributes.emails or not @attributes.emailSubscriptions
|
||||
oldSubs = @get('emailSubscriptions') or []
|
||||
newSubs = {}
|
||||
newSubs[newSubName] = { enabled: oldSubName in oldSubs } for oldSubName, newSubName of @emailMap
|
||||
@set('emails', newSubs)
|
||||
|
||||
isEmailSubscriptionEnabled: (name) -> (@get('emails') or {})[name]?.enabled
|
||||
|
|
|
@ -20,7 +20,20 @@ UserSchema = c.object {},
|
|||
autocastDelay: {type: 'integer', 'default': 5000 }
|
||||
lastLevel: { type: 'string' }
|
||||
|
||||
emailSubscriptions: c.array {uniqueItems: true, 'default': ['announcement', 'notification']}, {'enum': emailSubscriptions}
|
||||
emailSubscriptions: c.array {uniqueItems: true}, {'enum': emailSubscriptions}
|
||||
emails: c.object {title: "Email Settings", default: {generalNews: {enabled:true}, anyNotes: {enabled:true}, recruitNotes: {enabled:true}}},
|
||||
# newsletters
|
||||
generalNews: { $ref: '#/definitions/emailSubscription' }
|
||||
adventurerNews: { $ref: '#/definitions/emailSubscription' }
|
||||
ambassadorNews: { $ref: '#/definitions/emailSubscription' }
|
||||
archmageNews: { $ref: '#/definitions/emailSubscription' }
|
||||
artisanNews: { $ref: '#/definitions/emailSubscription' }
|
||||
diplomatNews: { $ref: '#/definitions/emailSubscription' }
|
||||
scribeNews: { $ref: '#/definitions/emailSubscription' }
|
||||
|
||||
# notifications
|
||||
anyNotes: { $ref: '#/definitions/emailSubscription' } # overrides any other notifications settings
|
||||
recruitNotes: { $ref: '#/definitions/emailSubscription' }
|
||||
|
||||
# server controlled
|
||||
permissions: c.array {'default': []}, c.shortString()
|
||||
|
@ -99,4 +112,10 @@ UserSchema = c.object {},
|
|||
|
||||
c.extendBasicProperties UserSchema, 'user'
|
||||
|
||||
c.definitions =
|
||||
emailSubscription =
|
||||
enabled: {type: 'boolean'}
|
||||
lastSent: c.date()
|
||||
count: {type: 'integer'}
|
||||
|
||||
module.exports = UserSchema
|
||||
|
|
|
@ -43,6 +43,12 @@
|
|||
|
||||
.form
|
||||
max-width: 600px
|
||||
|
||||
#email-pane
|
||||
#specific-notification-settings
|
||||
padding-left: 20px
|
||||
margin-left: 20px
|
||||
border-left: 1px solid gray
|
||||
|
||||
#job-profile-view
|
||||
.profile-preview-button
|
||||
|
|
|
@ -67,16 +67,28 @@ block content
|
|||
p
|
||||
.form
|
||||
.form-group.checkbox
|
||||
label.control-label(for="email_announcement", data-i18n="account_settings.email_announcements") Announcements
|
||||
input#email_announcement(name="email_announcement", type="checkbox", checked=subs.announcement)
|
||||
label.control-label(for="email_generalNews", data-i18n="account_settings.email_announcements") Announcements
|
||||
input#email_generalNews(name="email_generalNews", type="checkbox", checked=subs.generalNews)
|
||||
span.help-block(data-i18n="account_settings.email_announcements_description") Get emails on the latest news and developments at CodeCombat.
|
||||
|
||||
|
||||
hr
|
||||
h4(data-i18n="account_settings.email_notifications") Notifications
|
||||
span Controls for transactional emails, ie emails specific to your account.
|
||||
|
||||
.form
|
||||
.form-group.checkbox
|
||||
label.control-label(for="email_notification", data-i18n="account_settings.email_notifications") Notifications
|
||||
input#email_notification(name="email_notification", type="checkbox", checked=subs.notification)
|
||||
span.help-block(data-i18n="account_settings.email_notifications_description") Get periodic notifications for your account.
|
||||
hr
|
||||
label.control-label(for="email_anyNotes", data-i18n="account_settings.any_notifications") Any Notifications
|
||||
input#email_anyNotes(name="email_anyNotes", type="checkbox", checked=subs.anyNotes)
|
||||
span.help-block(data-i18n="account_settings.email_any_notes_description") Disable to universally stop ALL notifications for this account.
|
||||
|
||||
fieldset#specific-notification-settings
|
||||
|
||||
.form-group.checkbox
|
||||
label.control-label(for="email_recruitNotes", data-i18n="account_settings.recruit_notes") Recruitment Opportunities
|
||||
input#email_recruitNotes(name="email_recruitNotes", type="checkbox", checked=subs.recruitNotes)
|
||||
span.help-block(data-i18n="account_settings.email_recruit_notes_description") If you play really well, we may contact you about getting you a (better) job.
|
||||
|
||||
hr
|
||||
|
||||
h4(data-i18n="account_settings.contributor_emails") Contributor Class Emails
|
||||
span(data-i18n="account_settings.contribute_prefix") We're looking for people to join our party! Check out the
|
||||
|
@ -85,63 +97,63 @@ block content
|
|||
|
||||
.form
|
||||
.form-group.checkbox
|
||||
label.control-label(for="email_developer")
|
||||
label.control-label(for="email_archmageNews")
|
||||
span(data-i18n="classes.archmage_title")
|
||||
| Archmage
|
||||
|
|
||||
span(data-i18n="classes.archmage_title_description")
|
||||
| (Coder)
|
||||
input#email_developer(name="email_developer", type="checkbox", checked=subs.developer)
|
||||
input#email_archmageNews(name="email_archmageNews", type="checkbox", checked=subs.archmageNews)
|
||||
span(data-i18n="contribute.archmage_subscribe_desc").help-block Get emails about general news and announcements about CodeCombat.
|
||||
|
||||
.form-group.checkbox
|
||||
label.control-label(for="email_level_creator")
|
||||
label.control-label(for="email_artisanNews")
|
||||
span(data-i18n="classes.artisan_title")
|
||||
| Artisan
|
||||
|
|
||||
span(data-i18n="classes.artisan_title_description")
|
||||
| (Level Builder)
|
||||
input#email_level_creator(name="email_level_creator", type="checkbox", checked=subs.level_creator)
|
||||
input#email_artisanNews(name="email_artisanNews", type="checkbox", checked=subs.artisanNews)
|
||||
span(data-i18n="contribute.artisan_subscribe_desc").help-block Get emails on level editor updates and announcements.
|
||||
|
||||
.form-group.checkbox
|
||||
label.control-label(for="email_tester")
|
||||
label.control-label(for="email_adventurerNews")
|
||||
span(data-i18n="classes.adventurer_title")
|
||||
| Adventurer
|
||||
|
|
||||
span(data-i18n="classes.adventurer_title_description")
|
||||
| (Level Playtester)
|
||||
input#email_tester(name="email_tester", type="checkbox", checked=subs.tester)
|
||||
input#email_adventurerNews(name="email_adventurerNews", type="checkbox", checked=subs.adventurerNews)
|
||||
span(data-i18n="contribute.adventurer_subscribe_desc").help-block Get emails when there are new levels to test.
|
||||
|
||||
.form-group.checkbox
|
||||
label.control-label(for="email_article_editor")
|
||||
label.control-label(for="email_scribeNews")
|
||||
span(data-i18n="classes.scribe_title")
|
||||
| Scribe
|
||||
|
|
||||
span(data-i18n="classes.scribe_title_description")
|
||||
| (Article Editor)
|
||||
input#email_article_editor(name="email_article_editor", type="checkbox", checked=subs.article_editor)
|
||||
input#email_scribeNews(name="email_scribeNews", type="checkbox", checked=subs.scribeNews)
|
||||
span(data-i18n="contribute.scribe_subscribe_desc").help-block Get emails about article writing announcements.
|
||||
|
||||
.form-group.checkbox
|
||||
label.control-label(for="email_translator")
|
||||
label.control-label(for="email_diplomatNews")
|
||||
span(data-i18n="classes.diplomat_title")
|
||||
| Diplomat
|
||||
|
|
||||
span(data-i18n="classes.diplomat_title_description")
|
||||
| (Translator)
|
||||
input#email_translator(name="email_translator", type="checkbox", checked=subs.translator)
|
||||
input#email_diplomatNews(name="email_diplomatNews", type="checkbox", checked=subs.diplomatNews)
|
||||
span(data-i18n="contribute.diplomat_subscribe_desc").help-block Get emails about i18n developments and, eventually, levels to translate.
|
||||
|
||||
.form-group.checkbox
|
||||
label.control-label(for="email_support")
|
||||
label.control-label(for="email_ambassadorNews")
|
||||
span(data-i18n="classes.ambassador_title")
|
||||
| Ambassador
|
||||
|
|
||||
span(data-i18n="classes.ambassador_title_description")
|
||||
| (Support)
|
||||
input#email_support(name="email_support", type="checkbox", checked=subs.support)
|
||||
input#email_ambassadorNews(name="email_ambassadorNews", type="checkbox", checked=subs.ambassadorNews)
|
||||
span(data-i18n="contribute.ambassador_subscribe_desc").help-block Get emails on support updates and multiplayer developments.
|
||||
|
||||
button.btn#toggle-all-button(data-i18n="account_settings.email_toggle") Toggle All
|
||||
|
|
|
@ -171,7 +171,7 @@ block content
|
|||
| We are trying to build a community, and every community needs a support team when
|
||||
| there are troubles. We have got chats, emails, and social networks so that our users
|
||||
| can get acquainted with the game. If you want to help people get involved, have fun,
|
||||
| and learn some programming, then this class is for you.
|
||||
| and learn some programming, then this c lass is for you.
|
||||
|
||||
a(href="/contribute/ambassador")
|
||||
p.lead(data-i18n="contribute.more_about_ambassador")
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
label.checkbox(for=contributorClassID).well
|
||||
input(type='checkbox', name=contributorClassID, id=contributorClassID)
|
||||
label.checkbox(for=contributorClassName).well
|
||||
input(type='checkbox', name=contributorClassName, id=contributorClassName)
|
||||
span(data-i18n="contribute.#{contributorClassName}_subscribe_desc")
|
||||
.saved-notification ✓ Saved
|
||||
|
||||
|
|
|
@ -65,20 +65,19 @@ module.exports = class SettingsView extends View
|
|||
c = super()
|
||||
return c unless me
|
||||
c.subs = {}
|
||||
c.subs[sub] = 1 for sub in c.me.get('emailSubscriptions') or ['announcement', 'notification', 'tester', 'level_creator', 'developer']
|
||||
c.subs[sub] = 1 for sub in c.me.getEnabledEmails()
|
||||
c.showsJobProfileTab = me.isAdmin() or me.get('jobProfile') or location.hash.search('job-profile-') isnt -1
|
||||
c
|
||||
|
||||
getSubscriptions: ->
|
||||
inputs = $('#email-pane input[type="checkbox"]', @$el)
|
||||
inputs = ($(i) for i in inputs)
|
||||
subs = (i.attr('name') for i in inputs when i.prop('checked'))
|
||||
subs = (s.replace('email_', '') for s in subs)
|
||||
subs
|
||||
inputs = ($(i) for i in $('#email-pane input[type="checkbox"].changed', @$el))
|
||||
emailNames = (i.attr('name').replace('email_', '') for i in inputs)
|
||||
enableds = (i.prop('checked') for i in inputs)
|
||||
_.zipObject emailNames, enableds
|
||||
|
||||
toggleEmailSubscriptions: =>
|
||||
subs = @getSubscriptions()
|
||||
$('#email-pane input[type="checkbox"]', @$el).prop('checked', not Boolean(subs.length))
|
||||
$('#email-pane input[type="checkbox"]', @$el).prop('checked', not _.any(_.values(subs))).addClass('changed')
|
||||
@save()
|
||||
|
||||
buildPictureTreema: ->
|
||||
|
@ -102,7 +101,8 @@ module.exports = class SettingsView extends View
|
|||
@trigger 'change'
|
||||
@$el.find('.gravatar-fallback').toggle not me.get 'photoURL'
|
||||
|
||||
save: ->
|
||||
save: (e) ->
|
||||
$(e.target).addClass('changed') if e
|
||||
forms.clearFormAlerts(@$el)
|
||||
@grabData()
|
||||
res = me.validate()
|
||||
|
@ -143,7 +143,8 @@ module.exports = class SettingsView extends View
|
|||
grabOtherData: ->
|
||||
me.set 'name', $('#name', @$el).val()
|
||||
me.set 'email', $('#email', @$el).val()
|
||||
me.set 'emailSubscriptions', @getSubscriptions()
|
||||
for emailName, enabled of @getSubscriptions()
|
||||
me.setEmailSubscription emailName, enabled
|
||||
me.set 'photoURL', @pictureTreema.get('/photoURL')
|
||||
|
||||
adminCheckbox = @$el.find('#admin')
|
||||
|
|
|
@ -21,30 +21,23 @@ module.exports = class ContributeClassView extends View
|
|||
super()
|
||||
@$el.find('.contributor-signup-anonymous').replaceWith(contributorSignupAnonymousTemplate(me: me))
|
||||
@$el.find('.contributor-signup').each ->
|
||||
context = me: me, contributorClassID: $(@).data('contributor-class-id'), contributorClassName: $(@).data('contributor-class-name')
|
||||
context = me: me, contributorClassName: $(@).data('contributor-class-name')
|
||||
$(@).replaceWith(contributorSignupTemplate(context))
|
||||
@$el.find('#contributor-list').replaceWith(contributorListTemplate(contributors: @contributors, contributorClassName: @contributorClassName))
|
||||
|
||||
checkboxes = @$el.find('input[type="checkbox"]').toArray()
|
||||
_.forEach checkboxes, (el) ->
|
||||
el = $(el)
|
||||
if el.attr('name') in me.get('emailSubscriptions')
|
||||
el.prop('checked', true)
|
||||
el.prop('checked', true) if me.isEmailSubscriptionEnabled(el.attr('name')+'News')
|
||||
|
||||
onCheckboxChanged: (e) ->
|
||||
el = $(e.target)
|
||||
checked = el.prop('checked')
|
||||
subscription = el.attr('name')
|
||||
subscriptions = me.get('emailSubscriptions') ? []
|
||||
if checked and not (subscription in subscriptions)
|
||||
subscriptions.push(subscription)
|
||||
if me.get 'anonymous'
|
||||
@openModalView new SignupModalView()
|
||||
if not checked
|
||||
subscriptions = _.without subscriptions, subscription
|
||||
|
||||
me.setEmailSubscription subscription+'News', checked
|
||||
me.save()
|
||||
@openModalView new SignupModalView() if me.get 'anonymous'
|
||||
el.parent().find('.saved-notification').finish().show('fast').delay(3000).fadeOut(2000)
|
||||
|
||||
me.set('emailSubscriptions', subscriptions)
|
||||
me.save()
|
||||
|
||||
contributors: []
|
||||
|
|
|
@ -32,13 +32,6 @@ module.exports = class EditorLevelView extends View
|
|||
'click #level-patch-button': 'startPatchingLevel'
|
||||
'click #level-watch-button': 'toggleWatchLevel'
|
||||
|
||||
subscriptions:
|
||||
'refresh-level-editor': 'rerenderAllViews'
|
||||
|
||||
rerenderAllViews: ->
|
||||
for view in [@thangsTab, @settingsTab, @scriptsTab, @componentsTab, @systemsTab, @patchesView]
|
||||
view.render()
|
||||
|
||||
constructor: (options, @levelID) ->
|
||||
super options
|
||||
@listenToOnce(@supermodel, 'loaded-all', @onAllLoaded)
|
||||
|
|
|
@ -11,8 +11,7 @@ module.exports = class DiplomatSuggestionView extends View
|
|||
"click #subscribe-button": "subscribeAsDiplomat"
|
||||
|
||||
subscribeAsDiplomat: ->
|
||||
currentSubscriptions = me.get("emailSubscriptions")
|
||||
me.set("emailSubscriptions", currentSubscriptions.concat ["translator"]) if "translator" not in currentSubscriptions
|
||||
me.setEmailSubscription 'diplomatNews', true
|
||||
me.save()
|
||||
$("#email_translator").prop("checked", 1)
|
||||
@hide()
|
||||
|
|
|
@ -48,15 +48,12 @@ module.exports = class SignupModalView extends View
|
|||
userObject = forms.formToObject @$el
|
||||
delete userObject.subscribe
|
||||
delete userObject["confirm-age"]
|
||||
for key, val of me.attributes when key in ["preferredLanguage", "testGroupNumber", "dateCreated", "wizardColor1", "name", "music", "volume", "emailSubscriptions"]
|
||||
for key, val of me.attributes when key in ["preferredLanguage", "testGroupNumber", "dateCreated", "wizardColor1", "name", "music", "volume", "emails"]
|
||||
userObject[key] ?= val
|
||||
subscribe = @$el.find('#signup-subscribe').prop('checked')
|
||||
userObject.emailSubscriptions ?= []
|
||||
if subscribe
|
||||
userObject.emailSubscriptions.push 'announcement' unless 'announcement' in userObject.emailSubscriptions
|
||||
userObject.emailSubscriptions.push 'notification' unless 'notification' in userObject.emailSubscriptions
|
||||
else
|
||||
userObject.emailSubscriptions = _.without (userObject.emailSubscriptions ? []), 'announcement', 'notification'
|
||||
userObject.emails ?= {}
|
||||
userObject.emails.generalNews ?= {}
|
||||
userObject.emails.generalNews.enabled = subscribe
|
||||
res = tv4.validateMultiple userObject, User.schema
|
||||
return forms.applyErrorsToForm(@$el, res.errors) unless res.valid
|
||||
window.tracker?.trackEvent 'Finished Signup'
|
||||
|
|
|
@ -65,7 +65,7 @@ collapseSessions = (sessionLists) ->
|
|||
|
||||
grabUser = (session, callback) ->
|
||||
findParameters = _id: session.creator
|
||||
selectString = 'email emailSubscriptions name jobProfile'
|
||||
selectString = 'email emailSubscriptions emails name jobProfile'
|
||||
query = User
|
||||
.findOne(findParameters)
|
||||
.select(selectString)
|
||||
|
|
35
scripts/mongodb/migrations/2014-02-22-migrate-emails.js
Normal file
35
scripts/mongodb/migrations/2014-02-22-migrate-emails.js
Normal file
|
@ -0,0 +1,35 @@
|
|||
// Did not migrate anonymous users because they get their properties setup on signup.
|
||||
|
||||
// migrate the most common subscription configs with mass update commands
|
||||
db.users.update({anonymous:false, emailSubscriptions:['announcement', 'notification'], emails:{$exists:false}}, {$set:{emails:{}}}, {multi:true});
|
||||
db.users.update({anonymous:false, emailSubscriptions:[], emails:{$exists:false}}, {$set:{emails:{anyNotes:{enabled:false}, generalNews:{enabled:false}}}}, {multi:true});
|
||||
|
||||
// migrate the rest one by one
|
||||
emailMap = {
|
||||
announcement: 'generalNews',
|
||||
developer: 'archmageNews',
|
||||
tester: 'adventurerNews',
|
||||
level_creator: 'artisanNews',
|
||||
article_editor: 'scribeNews',
|
||||
translator: 'diplomatNews',
|
||||
support: 'ambassadorNews',
|
||||
notification: 'anyNotes'
|
||||
};
|
||||
|
||||
db.users.find({anonymous:false, emails:{$exists:false}}).forEach(function(u) {
|
||||
emails = {anyNotes:{enabled:false}, generalNews:{enabled:false}};
|
||||
var oldEmailSubs = u.emailSubscriptions || ['notification', 'announcement'];
|
||||
for(var email in oldEmailSubs) {
|
||||
var oldEmailName = oldEmailSubs[email];
|
||||
var newEmailName = emailMap[oldEmailName];
|
||||
if(!newEmailName) {
|
||||
print('STOP, COULD NOT FIND EMAIL NAME', oldEmailName);
|
||||
return false;
|
||||
}
|
||||
emails[newEmailName] = {enabled:true};
|
||||
}
|
||||
u.emails = emails;
|
||||
db.users.save(u);
|
||||
});
|
||||
|
||||
// Done. No STOP error when this was run.
|
|
@ -2,14 +2,10 @@ config = require '../../server_config'
|
|||
|
||||
module.exports.MAILCHIMP_LIST_ID = 'e9851239eb'
|
||||
module.exports.MAILCHIMP_GROUP_ID = '4529'
|
||||
module.exports.MAILCHIMP_GROUP_MAP =
|
||||
announcement: 'Announcements'
|
||||
tester: 'Adventurers'
|
||||
level_creator: 'Artisans'
|
||||
developer: 'Archmages'
|
||||
article_editor: 'Scribes'
|
||||
translator: 'Diplomats'
|
||||
support: 'Ambassadors'
|
||||
|
||||
# these two need to be parallel
|
||||
module.exports.MAILCHIMP_GROUPS = ['Announcements', 'Adventurers', 'Artisans', 'Archmages', 'Scribes', 'Diplomats', 'Ambassadors']
|
||||
module.exports.NEWS_GROUPS = ['generalNews', 'adventurerNews', 'artisanNews', 'archmageNews', 'scribeNews', 'diplomatNews', 'ambassadorNews']
|
||||
|
||||
nodemailer = require 'nodemailer'
|
||||
module.exports.transport = nodemailer.createTransport "SMTP",
|
||||
|
|
|
@ -58,7 +58,7 @@ PatchHandler = class PatchHandler extends Handler
|
|||
log.error "Error sending patch created: could not find the loaded target on the patch object." unless doc.targetLoaded
|
||||
return unless doc.targetLoaded
|
||||
watchers = doc.targetLoaded.get('watchers') or []
|
||||
watchers = (w for w in watchers when not w.equals(editor.get('_id')))
|
||||
watchers = (w for w in watchers when not w.equals(req.user.get('_id')))
|
||||
return unless watchers?.length
|
||||
User.find({_id:{$in:watchers}}).select({email:1, name:1}).exec (err, watchers) =>
|
||||
for watcher in watchers
|
||||
|
|
|
@ -131,10 +131,14 @@ module.exports.setup = (app) ->
|
|||
if not user
|
||||
return errors.notFound res, "No user found with email '#{req.query.email}'"
|
||||
|
||||
user.set('emailSubscriptions', [])
|
||||
user.save (err) =>
|
||||
emails = _.clone(user.get('emails')) or {}
|
||||
emailSettings.enabled = false for emailSettings in _.values(emails)
|
||||
emails.generalNews ?= {}
|
||||
emails.generalNews.enabled = false
|
||||
emails.anyNotes ?= {}
|
||||
emails.anyNotes.enabled = false
|
||||
user.update {$set: {emails: emails, emailSubscriptions: []}}, {}, =>
|
||||
return errors.serverError res, 'Database failure.' if err
|
||||
|
||||
res.send "Unsubscribed #{req.query.email} from all CodeCombat emails. Sorry to see you go! <p><a href='/account/settings'>Account settings</a></p>"
|
||||
res.end()
|
||||
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
mail = require '../commons/mail'
|
||||
map = _.invert mail.MAILCHIMP_GROUP_MAP
|
||||
User = require '../users/User.coffee'
|
||||
errors = require '../commons/errors'
|
||||
#request = require 'request'
|
||||
|
@ -77,12 +76,13 @@ handleLadderUpdate = (req, res) ->
|
|||
sendLadderUpdateEmail result, now, daysAgo for result in results
|
||||
|
||||
sendLadderUpdateEmail = (session, now, daysAgo) ->
|
||||
User.findOne({_id: session.creator}).select("name email firstName lastName emailSubscriptions preferredLanguage").lean().exec (err, user) ->
|
||||
User.findOne({_id: session.creator}).select("name email firstName lastName emailSubscriptions emails preferredLanguage").lean().exec (err, user) ->
|
||||
if err
|
||||
log.error "Couldn't find user for #{session.creator} from session #{session._id}"
|
||||
return
|
||||
unless user.email and ('notification' in user.emailSubscriptions) and not session.unsubscribed
|
||||
log.info "Not sending email to #{user.email} #{user.name} because they only want emails about #{user.emailSubscriptions} - session unsubscribed: #{session.unsubscribed}"
|
||||
allowNotes = user.isEmailSubscriptionEnabled 'anyNotes'
|
||||
unless user.email and allowNotes and not session.unsubscribed
|
||||
log.info "Not sending email to #{user.email} #{user.name} because they only want emails about #{user.emailSubscriptions}, #{user.emails} - session unsubscribed: #{session.unsubscribed}"
|
||||
return
|
||||
unless session.levelName
|
||||
log.info "Not sending email to #{user.email} #{user.name} because the session had no levelName in it."
|
||||
|
@ -198,13 +198,11 @@ handleMailchimpWebHook = (req, res) ->
|
|||
return errors.serverError(res) if err
|
||||
res.end('Success')
|
||||
|
||||
module.exports.handleProfileUpdate = handleProfileUpdate = (user, post) ->
|
||||
mailchimpSubs = post.data.merges.INTERESTS.split(', ')
|
||||
|
||||
handleProfileUpdate = (user, post) ->
|
||||
groups = post.data.merges.INTERESTS.split(', ')
|
||||
groups = (map[g] for g in groups when map[g])
|
||||
otherSubscriptions = (g for g in user.get('emailSubscriptions') when not mail.MAILCHIMP_GROUP_MAP[g])
|
||||
groups = groups.concat otherSubscriptions
|
||||
user.set 'emailSubscriptions', groups
|
||||
for [mailchimpEmailGroup, emailGroup] in _.zip(mail.MAILCHIMP_GROUPS, mail.NEWS_GROUPS)
|
||||
user.setEmailSubscription emailGroup, mailchimpEmailGroup in mailchimpSubs
|
||||
|
||||
fname = post.data.merges.FNAME
|
||||
user.set('firstName', fname) if fname
|
||||
|
@ -217,7 +215,9 @@ handleProfileUpdate = (user, post) ->
|
|||
|
||||
# badLog("Updating user object to: #{JSON.stringify(user.toObject(), null, '\t')}")
|
||||
|
||||
handleUnsubscribe = (user) ->
|
||||
module.exports.handleUnsubscribe = handleUnsubscribe = (user) ->
|
||||
user.set 'emailSubscriptions', []
|
||||
for emailGroup in mail.NEWS_GROUPS
|
||||
user.setEmailSubscription emailGroup, false
|
||||
|
||||
# badLog("Unsubscribing user object to: #{JSON.stringify(user.toObject(), null, '\t')}")
|
||||
|
|
|
@ -16,6 +16,7 @@ UserSchema = new mongoose.Schema({
|
|||
UserSchema.pre('init', (next) ->
|
||||
return next() unless jsonschema.properties?
|
||||
for prop, sch of jsonschema.properties
|
||||
continue if prop is 'emails' # defaults may change, so don't carry them over just yet
|
||||
@set(prop, sch.default) if sch.default?
|
||||
@set('permissions', ['admin']) if not isProduction
|
||||
next()
|
||||
|
@ -23,27 +24,60 @@ UserSchema.pre('init', (next) ->
|
|||
|
||||
UserSchema.post('init', ->
|
||||
@set('anonymous', false) if @get('email')
|
||||
@currentSubscriptions = JSON.stringify(@get('emailSubscriptions'))
|
||||
)
|
||||
|
||||
UserSchema.methods.isAdmin = ->
|
||||
p = @get('permissions')
|
||||
return p and 'admin' in p
|
||||
|
||||
emailNameMap =
|
||||
generalNews: 'announcement'
|
||||
adventurerNews: 'tester'
|
||||
artisanNews: 'level_creator'
|
||||
archmageNews: 'developer'
|
||||
scribeNews: 'article_editor'
|
||||
diplomatNews: 'translator'
|
||||
ambassadorNews: 'support'
|
||||
anyNotes: 'notification'
|
||||
|
||||
UserSchema.methods.setEmailSubscription = (newName, enabled) ->
|
||||
oldSubs = _.clone @get('emailSubscriptions')
|
||||
if oldSubs and oldName = emailNameMap[newName]
|
||||
oldSubs = (s for s in oldSubs when s isnt oldName)
|
||||
oldSubs.push(oldName) if enabled
|
||||
@set('emailSubscriptions', oldSubs)
|
||||
|
||||
newSubs = _.clone(@get('emails') or jsonschema.properties.emails.default)
|
||||
newSubs[newName] ?= {}
|
||||
newSubs[newName].enabled = enabled
|
||||
@set('emails', newSubs)
|
||||
@newsSubsChanged = true if newName in mail.NEWS_GROUPS
|
||||
|
||||
UserSchema.methods.isEmailSubscriptionEnabled = (newName) ->
|
||||
emails = @get 'emails'
|
||||
if not emails
|
||||
oldSubs = @get('emailSubscriptions')
|
||||
oldName = emailNameMap[newName]
|
||||
return oldName and oldName in oldSubs if oldSubs
|
||||
emails ?= {}
|
||||
_.defaults emails, jsonschema.properties.emails.default
|
||||
return emails[newName]?.enabled
|
||||
|
||||
UserSchema.statics.updateMailChimp = (doc, callback) ->
|
||||
return callback?() unless isProduction
|
||||
return callback?() unless isProduction or GLOBAL.testing
|
||||
return callback?() if doc.updatedMailChimp
|
||||
return callback?() unless doc.get('email')
|
||||
existingProps = doc.get('mailChimp')
|
||||
emailChanged = (not existingProps) or existingProps?.email isnt doc.get('email')
|
||||
emailSubs = doc.get('emailSubscriptions')
|
||||
gm = mail.MAILCHIMP_GROUP_MAP
|
||||
newGroups = (gm[name] for name in emailSubs when gm[name]?)
|
||||
return callback?() unless emailChanged or doc.newsSubsChanged
|
||||
|
||||
newGroups = []
|
||||
for [mailchimpEmailGroup, emailGroup] in _.zip(mail.MAILCHIMP_GROUPS, mail.NEWS_GROUPS)
|
||||
newGroups.push(mailchimpEmailGroup) if doc.isEmailSubscriptionEnabled(emailGroup)
|
||||
|
||||
if (not existingProps) and newGroups.length is 0
|
||||
return callback?() # don't add totally unsubscribed people to the list
|
||||
subsChanged = doc.currentSubscriptions isnt JSON.stringify(emailSubs)
|
||||
return callback?() unless emailChanged or subsChanged
|
||||
|
||||
|
||||
params = {}
|
||||
params.id = mail.MAILCHIMP_LIST_ID
|
||||
params.email = if existingProps then {leid:existingProps.leid} else {email:doc.get('email')}
|
||||
|
|
|
@ -25,7 +25,7 @@ UserHandler = class UserHandler extends Handler
|
|||
|
||||
editableProperties: [
|
||||
'name', 'photoURL', 'password', 'anonymous', 'wizardColor1', 'volume',
|
||||
'firstName', 'lastName', 'gender', 'facebookID', 'gplusID', 'emailSubscriptions',
|
||||
'firstName', 'lastName', 'gender', 'facebookID', 'gplusID', 'emails',
|
||||
'testGroupNumber', 'music', 'hourOfCode', 'hourOfCodeComplete', 'preferredLanguage',
|
||||
'wizard', 'aceConfig', 'autocastDelay', 'lastLevel', 'jobProfile'
|
||||
]
|
||||
|
|
38
test/server/functional/mail.spec.coffee
Normal file
38
test/server/functional/mail.spec.coffee
Normal file
|
@ -0,0 +1,38 @@
|
|||
require '../common'
|
||||
mail = require '../../../server/routes/mail'
|
||||
User = require '../../../server/users/User'
|
||||
|
||||
testPost =
|
||||
data:
|
||||
email: 'scott@codecombat.com'
|
||||
id: '12345678'
|
||||
merges:
|
||||
INTERESTS: 'Announcements, Adventurers, Archmages, Scribes, Diplomats, Ambassadors, Artisans'
|
||||
FNAME: 'Scott'
|
||||
LNAME: 'Erickson'
|
||||
|
||||
describe 'handleProfileUpdate', ->
|
||||
it 'updates emails from the data passed in', (done) ->
|
||||
u = new User()
|
||||
mail.handleProfileUpdate(u, testPost)
|
||||
expect(u.isEmailSubscriptionEnabled('generalNews')).toBeTruthy()
|
||||
expect(u.isEmailSubscriptionEnabled('adventurerNews')).toBeTruthy()
|
||||
expect(u.isEmailSubscriptionEnabled('archmageNews')).toBeTruthy()
|
||||
expect(u.isEmailSubscriptionEnabled('scribeNews')).toBeTruthy()
|
||||
expect(u.isEmailSubscriptionEnabled('diplomatNews')).toBeTruthy()
|
||||
expect(u.isEmailSubscriptionEnabled('ambassadorNews')).toBeTruthy()
|
||||
expect(u.isEmailSubscriptionEnabled('artisanNews')).toBeTruthy()
|
||||
done()
|
||||
|
||||
describe 'handleUnsubscribe', ->
|
||||
it 'turns off all news and notifications', (done) ->
|
||||
u = new User({generalNews: {enabled:true}, archmageNews: {enabled:true}, anyNotes: {enabled:true}})
|
||||
mail.handleUnsubscribe(u)
|
||||
expect(u.isEmailSubscriptionEnabled('generalNews')).toBeFalsy()
|
||||
expect(u.isEmailSubscriptionEnabled('adventurerNews')).toBeFalsy()
|
||||
expect(u.isEmailSubscriptionEnabled('archmageNews')).toBeFalsy()
|
||||
expect(u.isEmailSubscriptionEnabled('scribeNews')).toBeFalsy()
|
||||
expect(u.isEmailSubscriptionEnabled('diplomatNews')).toBeFalsy()
|
||||
expect(u.isEmailSubscriptionEnabled('ambassadorNews')).toBeFalsy()
|
||||
expect(u.isEmailSubscriptionEnabled('artisanNews')).toBeFalsy()
|
||||
done()
|
|
@ -1,8 +1,47 @@
|
|||
require '../common'
|
||||
request = require 'request'
|
||||
User = require '../../../server/users/User'
|
||||
|
||||
urlUser = '/db/user'
|
||||
|
||||
describe 'Server user object', ->
|
||||
|
||||
it 'uses the schema defaults to fill in email preferences', (done) ->
|
||||
user = new User()
|
||||
expect(user.isEmailSubscriptionEnabled('generalNews')).toBeTruthy()
|
||||
expect(user.isEmailSubscriptionEnabled('anyNotes')).toBeTruthy()
|
||||
expect(user.isEmailSubscriptionEnabled('recruitNotes')).toBeTruthy()
|
||||
expect(user.isEmailSubscriptionEnabled('archmageNews')).toBeFalsy()
|
||||
done()
|
||||
|
||||
it 'uses old subs if they\'re around', (done) ->
|
||||
user = new User()
|
||||
user.set 'emailSubscriptions', ['tester']
|
||||
expect(user.isEmailSubscriptionEnabled('adventurerNews')).toBeTruthy()
|
||||
expect(user.isEmailSubscriptionEnabled('generalNews')).toBeFalsy()
|
||||
done()
|
||||
|
||||
it 'maintains the old subs list if it\'s around', (done) ->
|
||||
user = new User()
|
||||
user.set 'emailSubscriptions', ['tester']
|
||||
user.setEmailSubscription('artisanNews', true)
|
||||
expect(JSON.stringify(user.get('emailSubscriptions'))).toBe(JSON.stringify(['tester','level_creator']))
|
||||
done()
|
||||
|
||||
describe 'User.updateMailChimp', ->
|
||||
makeMC = (callback) ->
|
||||
GLOBAL.mc =
|
||||
lists:
|
||||
subscribe: callback
|
||||
|
||||
it 'uses emails to determine what to send to MailChimp', (done) ->
|
||||
makeMC (params) ->
|
||||
expect(JSON.stringify(params.merge_vars.groupings[0].groups)).toBe(JSON.stringify(['Announcements']))
|
||||
done()
|
||||
|
||||
user = new User({emailSubscriptions:['announcement'], email:'tester@gmail.com'})
|
||||
User.updateMailChimp(user)
|
||||
|
||||
describe 'POST /db/user', ->
|
||||
|
||||
it 'preparing test : clears the db first', (done) ->
|
||||
|
|
Loading…
Reference in a new issue