Merge branch 'master' into production

This commit is contained in:
Nick Winter 2014-04-23 10:22:29 -07:00
commit 50095a1778
17 changed files with 102 additions and 37 deletions

View file

@ -68,7 +68,7 @@ expandFlattenedDelta = (delta, left, schema) ->
parentLeft = left parentLeft = left
parentSchema = schema parentSchema = schema
for key, i in delta.dataPath for key, i in delta.dataPath
# TODO: A more comprehensive way of getting child schemas # TODO: Better schema/json walking
childSchema = parentSchema?.items or parentSchema?.properties?[key] or {} childSchema = parentSchema?.items or parentSchema?.properties?[key] or {}
childLeft = parentLeft?[key] childLeft = parentLeft?[key]
humanKey = null humanKey = null

View file

@ -69,25 +69,18 @@ module.exports.thangNames = thangNames =
"Buffy" "Buffy"
"Allankrita" "Allankrita"
] ]
"Peasant": [ "Peasant M": [
"Yorik" "Yorik"
"Hector" "Hector"
"Thad" "Thad"
"Victor" "Victor"
"Lyle" "Lyle"
"Charles" "Charles"
"Mary"
"Brandy"
"Gwendolin"
"Tabitha"
"Regan"
"Yusef" "Yusef"
"Hingle" "Hingle"
"Azgot" "Azgot"
"Piers" "Piers"
"Carlton" "Carlton"
"Giselle"
"Bernadette"
"Hershell" "Hershell"
"Gawain" "Gawain"
"Durfkor" "Durfkor"
@ -99,6 +92,13 @@ module.exports.thangNames = thangNames =
"Icey" "Icey"
"Matilda" "Matilda"
"Mertia" "Mertia"
"Mary"
"Brandy"
"Gwendolin"
"Tabitha"
"Regan"
"Giselle"
"Bernadette"
] ]
"Archer F": [ "Archer F": [
"Phoebe" "Phoebe"

View file

@ -8,7 +8,7 @@ class Vector
a.copy()[name](b, useZ) a.copy()[name](b, useZ)
isVector: true isVector: true
apiProperties: ['x', 'y', 'magnitude', 'heading', 'distance', 'dot', 'equals', 'copy'] apiProperties: ['x', 'y', 'magnitude', 'heading', 'distance', 'dot', 'equals', 'copy', 'distanceSquared']
constructor: (@x=0, @y=0, @z=0) -> constructor: (@x=0, @y=0, @z=0) ->

View file

@ -1,11 +1,11 @@
module.exports = nativeDescription: "Nederlands (België)", englishDescription: "Dutch (Belgium)", translation: module.exports = nativeDescription: "Nederlands (België)", englishDescription: "Dutch (Belgium)", translation:
common: common:
loading: "Aan het laden..." loading: "Bezig met laden..."
saving: "Opslaan..." saving: "Opslaan..."
sending: "Verzenden..." sending: "Verzenden..."
send: "Verzend" send: "Verzend"
cancel: "Annuleren" cancel: "Annuleren"
save: "Opslagen" save: "Opslaan"
publish: "Publiceren" publish: "Publiceren"
create: "Creëer" create: "Creëer"
delay_1_sec: "1 seconde" delay_1_sec: "1 seconde"
@ -46,7 +46,7 @@ module.exports = nativeDescription: "Nederlands (België)", englishDescription:
employers: "Werkgevers" employers: "Werkgevers"
versions: versions:
save_version_title: "Nieuwe versie opslagen" save_version_title: "Nieuwe versie opslaan"
new_major_version: "Nieuwe hoofd versie" new_major_version: "Nieuwe hoofd versie"
cla_prefix: "Om bewerkingen op te slaan, moet je eerst akkoord gaan met onze" cla_prefix: "Om bewerkingen op te slaan, moet je eerst akkoord gaan met onze"
cla_url: "CLA" cla_url: "CLA"
@ -308,7 +308,7 @@ module.exports = nativeDescription: "Nederlands (België)", englishDescription:
editor: editor:
main_title: "CodeCombat Editors" main_title: "CodeCombat Editors"
main_description: "Maak je eigen levels, campagnes, eenheden en leermateriaal. Wij bieden alle programma's aan die u nodig heeft!" main_description: "Maak je eigen levels, campagnes, eenheden en leermateriaal. Wij bieden alle programma's aan die je nodig hebt!"
article_title: "Artikel Editor" article_title: "Artikel Editor"
article_description: "Schrijf artikels die spelers een overzicht geven over programmeer concepten die kunnen gebruikt worden over een variëteit van levels en campagnes." article_description: "Schrijf artikels die spelers een overzicht geven over programmeer concepten die kunnen gebruikt worden over een variëteit van levels en campagnes."
thang_title: "Thang Editor" thang_title: "Thang Editor"

View file

@ -1,11 +1,11 @@
module.exports = nativeDescription: "Nederlands (Nederland)", englishDescription: "Dutch (Netherlands)", translation: module.exports = nativeDescription: "Nederlands (Nederland)", englishDescription: "Dutch (Netherlands)", translation:
common: common:
loading: "Aan het laden..." loading: "Bezig met laden..."
saving: "Opslaan..." saving: "Opslaan..."
sending: "Verzenden..." sending: "Verzenden..."
send: "Verzend" send: "Verzend"
cancel: "Annuleren" cancel: "Annuleren"
save: "Opslagen" save: "Opslaan"
publish: "Publiceren" publish: "Publiceren"
create: "Creëer" create: "Creëer"
delay_1_sec: "1 seconde" delay_1_sec: "1 seconde"
@ -46,7 +46,7 @@ module.exports = nativeDescription: "Nederlands (Nederland)", englishDescription
employers: "Werkgevers" employers: "Werkgevers"
versions: versions:
save_version_title: "Nieuwe versie opslagen" save_version_title: "Nieuwe versie opslaan"
new_major_version: "Nieuwe hoofd versie" new_major_version: "Nieuwe hoofd versie"
cla_prefix: "Om bewerkingen op te slaan, moet je eerst akkoord gaan met onze" cla_prefix: "Om bewerkingen op te slaan, moet je eerst akkoord gaan met onze"
cla_url: "CLA" cla_url: "CLA"
@ -308,7 +308,7 @@ module.exports = nativeDescription: "Nederlands (Nederland)", englishDescription
editor: editor:
main_title: "CodeCombat Editors" main_title: "CodeCombat Editors"
main_description: "Maak je eigen levels, campagnes, eenheden en leermateriaal. Wij bieden alle programma's aan die u nodig heeft!" main_description: "Maak je eigen levels, campagnes, eenheden en leermateriaal. Wij bieden alle programma's aan die je nodig hebt!"
article_title: "Artikel Editor" article_title: "Artikel Editor"
article_description: "Schrijf artikels die spelers een overzicht geven over programmeer concepten die kunnen gebruikt worden over een variëteit van levels en campagnes." article_description: "Schrijf artikels die spelers een overzicht geven over programmeer concepten die kunnen gebruikt worden over een variëteit van levels en campagnes."
thang_title: "Thang Editor" thang_title: "Thang Editor"

View file

@ -1,11 +1,11 @@
module.exports = nativeDescription: "Nederlands", englishDescription: "Dutch", translation: module.exports = nativeDescription: "Nederlands", englishDescription: "Dutch", translation:
common: common:
loading: "Aan het laden..." loading: "Bezig met laden..."
saving: "Opslaan..." saving: "Opslaan..."
sending: "Verzenden..." sending: "Verzenden..."
send: "Verzend" send: "Verzend"
cancel: "Annuleren" cancel: "Annuleren"
save: "Opslagen" save: "Opslaan"
publish: "Publiceren" publish: "Publiceren"
create: "Creëer" create: "Creëer"
delay_1_sec: "1 seconde" delay_1_sec: "1 seconde"
@ -46,7 +46,7 @@ module.exports = nativeDescription: "Nederlands", englishDescription: "Dutch", t
employers: "Werkgevers" employers: "Werkgevers"
versions: versions:
save_version_title: "Nieuwe versie opslagen" save_version_title: "Nieuwe versie opslaan"
new_major_version: "Nieuwe hoofd versie" new_major_version: "Nieuwe hoofd versie"
cla_prefix: "Om bewerkingen op te slaan, moet je eerst akkoord gaan met onze" cla_prefix: "Om bewerkingen op te slaan, moet je eerst akkoord gaan met onze"
cla_url: "CLA" cla_url: "CLA"
@ -308,7 +308,7 @@ module.exports = nativeDescription: "Nederlands", englishDescription: "Dutch", t
editor: editor:
main_title: "CodeCombat Editors" main_title: "CodeCombat Editors"
main_description: "Maak je eigen levels, campagnes, eenheden en leermateriaal. Wij bieden alle programma's aan die u nodig heeft!" main_description: "Maak je eigen levels, campagnes, eenheden en leermateriaal. Wij bieden alle programma's aan die je nodig hebt!"
article_title: "Artikel Editor" article_title: "Artikel Editor"
article_description: "Schrijf artikels die spelers een overzicht geven over programmeer concepten die kunnen gebruikt worden over een variëteit van levels en campagnes." article_description: "Schrijf artikels die spelers een overzicht geven over programmeer concepten die kunnen gebruikt worden over een variëteit van levels en campagnes."
thang_title: "Thang Editor" thang_title: "Thang Editor"

View file

@ -133,6 +133,7 @@ class CocoModel extends Backbone.Model
schema ?= @schema() schema ?= @schema()
models = [] models = []
# TODO: Better schema/json walking
if $.isArray(data) and schema.items? if $.isArray(data) and schema.items?
for subData, i in data for subData, i in data
models = models.concat(@getReferencedModels(subData, schema.items, path+i+'/', shouldLoadProjection)) models = models.concat(@getReferencedModels(subData, schema.items, path+i+'/', shouldLoadProjection))
@ -227,4 +228,26 @@ class CocoModel extends Backbone.Model
watching: -> watching: ->
return me.id in (@get('watchers') or []) return me.id in (@get('watchers') or [])
populateI18N: (data, schema, path='') ->
# TODO: Better schema/json walking
sum = 0
data ?= $.extend true, {}, @attributes
schema ?= @schema() or {}
if schema.properties?.i18n and _.isPlainObject(data) and not data.i18n?
data.i18n = {}
sum += 1
if _.isPlainObject data
for key, value of data
numChanged = 0
numChanged = @populateI18N(value, childSchema, path+'/'+key) if childSchema = schema.properties?[key]
if numChanged and not path # should only do this for the root object
@set key, value
sum += numChanged
if schema.items and _.isArray data
sum += @populateI18N(value, schema.items, path+'/'+index) for value, index in data
sum
module.exports = CocoModel module.exports = CocoModel

View file

@ -68,6 +68,8 @@ block header
a(data-i18n="common.fork")#fork-level-start-button Fork a(data-i18n="common.fork")#fork-level-start-button Fork
li(class=anonymous ? "disabled": "") li(class=anonymous ? "disabled": "")
a(data-toggle="coco-modal", data-target="modal/revert", data-i18n="editor.revert")#revert-button Revert a(data-toggle="coco-modal", data-target="modal/revert", data-i18n="editor.revert")#revert-button Revert
li(class=anonymous ? "disabled": "")
a(data-i18n="editor.pop_i18n")#pop-level-i18n-button Populate i18n
li.divider li.divider
li.dropdown-header Info li.dropdown-header Info
li#level-history-button li#level-history-button

View file

@ -28,6 +28,7 @@ block content
th(data-i18n="employers.candidate_last_updated") Last Updated th(data-i18n="employers.candidate_last_updated") Last Updated
if me.isAdmin() if me.isAdmin()
th ✓? th ✓?
th ಥ_ಥ
tbody tbody
for candidate, index in candidates for candidate, index in candidates
@ -58,3 +59,7 @@ block content
td ✓ td ✓
else else
td ✗ td ✗
if profile.active
td ᶘ ᵒᴥᵒᶅ
else
td ⨂_⨂

View file

@ -233,6 +233,7 @@ class InternationalizationNode extends TreemaNode.nodeMap.object
type: "object" type: "object"
properties: {} properties: {}
} }
return i18nChildSchema unless @parent
unless @schema.props? unless @schema.props?
console.warn "i18n props array is empty! Filling with all parent properties by default" console.warn "i18n props array is empty! Filling with all parent properties by default"
@schema.props = (prop for prop,_ of @parent.schema.properties when prop isnt "i18n") @schema.props = (prop for prop,_ of @parent.schema.properties when prop isnt "i18n")

View file

@ -40,6 +40,7 @@ module.exports = class ThangComponentEditView extends CocoView
@render() @render()
buildExtantComponentTreema: -> buildExtantComponentTreema: ->
new Level() # hack to load the schema
treemaOptions = treemaOptions =
supermodel: @supermodel supermodel: @supermodel
schema: Level.schema.properties.thangs.items.properties.components schema: Level.schema.properties.thangs.items.properties.components

View file

@ -31,6 +31,7 @@ module.exports = class EditorLevelView extends View
'click #patches-tab': -> @patchesView.load() 'click #patches-tab': -> @patchesView.load()
'click #level-patch-button': 'startPatchingLevel' 'click #level-patch-button': 'startPatchingLevel'
'click #level-watch-button': 'toggleWatchLevel' 'click #level-watch-button': 'toggleWatchLevel'
'click #pop-level-i18n-button': -> @level.populateI18N()
constructor: (options, @levelID) -> constructor: (options, @levelID) ->
super options super options

View file

@ -77,7 +77,8 @@ grabUser = (session, callback) ->
totalEmailsSent = 0 totalEmailsSent = 0
emailUser = (user, callback) -> emailUser = (user, callback) ->
return callback null, false if user.emails?.recruiting?.enabled is false # TODO: later, obey also "announcements" when that's untangled #return callback null, false if user.emails?.anyNotes?.enabled is false # TODO: later, uncomment to obey also "anyNotes" when that's untangled
return callback null, false if user.emails?.recruitNotes?.enabled is false
return callback null, false if user.email in alreadyEmailed return callback null, false if user.email in alreadyEmailed
return callback null, false if DEBUGGING and (totalEmailsSent > 1 or Math.random() > 0.1) return callback null, false if DEBUGGING and (totalEmailsSent > 1 or Math.random() > 0.1)
++totalEmailsSent ++totalEmailsSent

View file

@ -132,14 +132,24 @@ module.exports.setup = (app) ->
return errors.notFound res, "No user found with email '#{req.query.email}'" return errors.notFound res, "No user found with email '#{req.query.email}'"
emails = _.clone(user.get('emails')) or {} emails = _.clone(user.get('emails')) or {}
msg = ''
if req.query.recruitNotes
emails.recruitNotes ?= {}
emails.recruitNotes.enabled = false
msg = "Unsubscribed #{req.query.email} from recruiting emails."
else
msg = "Unsubscribed #{req.query.email} from all CodeCombat emails. Sorry to see you go!"
emailSettings.enabled = false for emailSettings in _.values(emails) emailSettings.enabled = false for emailSettings in _.values(emails)
emails.generalNews ?= {} emails.generalNews ?= {}
emails.generalNews.enabled = false emails.generalNews.enabled = false
emails.anyNotes ?= {} emails.anyNotes ?= {}
emails.anyNotes.enabled = false emails.anyNotes.enabled = false
user.update {$set: {emails: emails, emailSubscriptions: []}}, {}, =>
user.update {$set: {emails: emails}}, {}, =>
return errors.serverError res, 'Database failure.' if err 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.send msg + "<p><a href='/account/settings'>Account settings</a></p>"
res.end() res.end()
module.exports.loginUser = loginUser = (req, res, user, send=true, next=null) -> module.exports.loginUser = loginUser = (req, res, user, send=true, next=null) ->

View file

@ -47,7 +47,7 @@ UserSchema.methods.setEmailSubscription = (newName, enabled) ->
oldSubs.push(oldName) if enabled oldSubs.push(oldName) if enabled
@set('emailSubscriptions', oldSubs) @set('emailSubscriptions', oldSubs)
newSubs = _.clone(@get('emails') or jsonschema.properties.emails.default) newSubs = _.clone(@get('emails') or _.cloneDeep(jsonschema.properties.emails.default))
newSubs[newName] ?= {} newSubs[newName] ?= {}
newSubs[newName].enabled = enabled newSubs[newName].enabled = enabled
@set('emails', newSubs) @set('emails', newSubs)
@ -60,7 +60,7 @@ UserSchema.methods.isEmailSubscriptionEnabled = (newName) ->
oldName = emailNameMap[newName] oldName = emailNameMap[newName]
return oldName and oldName in oldSubs if oldSubs return oldName and oldName in oldSubs if oldSubs
emails ?= {} emails ?= {}
_.defaults emails, jsonschema.properties.emails.default _.defaults emails, _.cloneDeep(jsonschema.properties.emails.default)
return emails[newName]?.enabled return emails[newName]?.enabled
UserSchema.statics.updateMailChimp = (doc, callback) -> UserSchema.statics.updateMailChimp = (doc, callback) ->

View file

@ -235,9 +235,10 @@ UserHandler = class UserHandler extends Handler
getCandidates: (req, res) -> getCandidates: (req, res) ->
authorized = req.user.isAdmin() or ('employer' in req.user.get('permissions')) authorized = req.user.isAdmin() or ('employer' in req.user.get('permissions'))
since = (new Date((new Date()) - 2 * 30.4 * 86400 * 1000)).toISOString() since = (new Date((new Date()) - 2 * 30.4 * 86400 * 1000)).toISOString()
query = {'jobProfile.active': true, 'jobProfile.updated': {$gt: since}} #query = {'jobProfile.active': true, 'jobProfile.updated': {$gt: since}}
#query = {'jobProfile.updated': {$gt: since}} query = {'jobProfile.updated': {$gt: since}}
query.jobProfileApproved = true unless req.user.isAdmin() query.jobProfileApproved = true unless req.user.isAdmin()
query['jobProfile.active'] = true unless req.user.isAdmin()
selection = 'jobProfile' selection = 'jobProfile'
selection += ' email' if authorized selection += ' email' if authorized
selection += ' jobProfileApproved' if req.user.isAdmin() selection += ' jobProfileApproved' if req.user.isAdmin()
@ -251,7 +252,7 @@ UserHandler = class UserHandler extends Handler
fields = if authorized then ['jobProfile', 'jobProfileApproved', 'photoURL', '_id'] else ['jobProfile'] fields = if authorized then ['jobProfile', 'jobProfileApproved', 'photoURL', '_id'] else ['jobProfile']
obj = _.pick document.toObject(), fields obj = _.pick document.toObject(), fields
obj.photoURL ||= obj.jobProfile.photoURL if authorized obj.photoURL ||= obj.jobProfile.photoURL if authorized
subfields = ['country', 'city', 'lookingFor', 'jobTitle', 'skills', 'experience', 'updated'] subfields = ['country', 'city', 'lookingFor', 'jobTitle', 'skills', 'experience', 'updated', 'active']
if authorized if authorized
subfields = subfields.concat ['name'] subfields = subfields.concat ['name']
obj.jobProfile = _.pick obj.jobProfile, subfields obj.jobProfile = _.pick obj.jobProfile, subfields

View file

@ -1,5 +1,6 @@
require '../common' require '../common'
request = require 'request' request = require 'request'
User = require '../../../server/users/User'
urlLogin = getURL('/auth/login') urlLogin = getURL('/auth/login')
urlReset = getURL('/auth/reset') urlReset = getURL('/auth/reset')
@ -16,7 +17,8 @@ describe '/auth/whoami', ->
describe '/auth/login', -> describe '/auth/login', ->
it 'clears Users first', (done) -> it 'clears Users first', (done) ->
User.remove {}, (err) -> clearModels [User], (err) ->
throw err if err
request.get getURL('/auth/whoami'), -> request.get getURL('/auth/whoami'), ->
throw err if err throw err if err
done() done()
@ -134,3 +136,21 @@ describe '/auth/reset', ->
form = req.form() form = req.form()
form.append('username', 'scott@gmail.com') form.append('username', 'scott@gmail.com')
form.append('password', 'nada') form.append('password', 'nada')
describe '/auth/unsubscribe', ->
it 'clears Users first', (done) ->
clearModels [User], (err) ->
throw err if err
request.get getURL('/auth/whoami'), ->
throw err if err
done()
it 'removes just recruitment emails if you include ?recruitNotes=1', (done) ->
loginJoe (joe) ->
url = getURL('/auth/unsubscribe?recruitNotes=1&email='+joe.get('email'))
request.get url, (error, response) ->
expect(response.statusCode).toBe(200)
user = User.findOne(joe.get('_id')).exec (err, user) ->
expect(user.get('emails').recruitNotes.enabled).toBe(false)
expect(user.isEmailSubscriptionEnabled('generalNews')).toBeTruthy()
done()