Merge branch 'master' into production

This commit is contained in:
Nick Winter 2016-01-18 16:09:25 -08:00
commit 9ed201c910
26 changed files with 106 additions and 521 deletions

View file

@ -1037,6 +1037,7 @@
ambassador_title: "Ambassador" ambassador_title: "Ambassador"
ambassador_title_description: "(Support)" ambassador_title_description: "(Support)"
ambassador_summary: "Tame our forum users and provide direction for those with questions. Our ambassadors represent CodeCombat to the world." ambassador_summary: "Tame our forum users and provide direction for those with questions. Our ambassadors represent CodeCombat to the world."
teacher_title: "Teacher"
editor: editor:
main_title: "CodeCombat Editors" main_title: "CodeCombat Editors"
@ -1193,6 +1194,7 @@
ambassador_join_note_strong: "Note" ambassador_join_note_strong: "Note"
ambassador_join_note_desc: "One of our top priorities is to build multiplayer where players having difficulty solving levels can summon higher level wizards to help them. This will be a great way for ambassadors to do their thing. We'll keep you posted!" ambassador_join_note_desc: "One of our top priorities is to build multiplayer where players having difficulty solving levels can summon higher level wizards to help them. This will be a great way for ambassadors to do their thing. We'll keep you posted!"
ambassador_subscribe_desc: "Get emails on support updates and multiplayer developments." ambassador_subscribe_desc: "Get emails on support updates and multiplayer developments."
teacher_subscribe_desc: "Get emails on updates and announcements for teachers."
changes_auto_save: "Changes are saved automatically when you toggle checkboxes." changes_auto_save: "Changes are saved automatically when you toggle checkboxes."
diligent_scribes: "Our Diligent Scribes:" diligent_scribes: "Our Diligent Scribes:"
powerful_archmages: "Our Powerful Archmages:" powerful_archmages: "Our Powerful Archmages:"

View file

@ -79,6 +79,7 @@ _.extend UserSchema.properties,
archmageNews: {$ref: '#/definitions/emailSubscription'} archmageNews: {$ref: '#/definitions/emailSubscription'}
artisanNews: {$ref: '#/definitions/emailSubscription'} artisanNews: {$ref: '#/definitions/emailSubscription'}
diplomatNews: {$ref: '#/definitions/emailSubscription'} diplomatNews: {$ref: '#/definitions/emailSubscription'}
teacherNews: {$ref: '#/definitions/emailSubscription'}
scribeNews: {$ref: '#/definitions/emailSubscription'} scribeNews: {$ref: '#/definitions/emailSubscription'}
# notifications # notifications

View file

@ -1,344 +0,0 @@
@import "app/styles/bootstrap/variables"
#profile-view
$sideBackground: rgb(220, 220, 220)
#login-message
h1, h2, h3, h4
font-family: Arial, Helvetica, sans-serif
color: #333333
width: 100%
text-align: center
margin-top: 200px
.profile-control-bar
background-color: $sideBackground
width: 100%
text-align: center
.profile-completion-progress
width: 100%
height: 33px
margin-bottom: 0
border-radius: 0
background-color: darken($sideBackground, 15%)
.progress-bar
line-height: 33px
font-size: 16px
.progress-text
position: absolute
width: 100%
text-align: center
line-height: 33px
font-size: 16px
color: white
text-shadow: 0px 1px 0px black
button, a.btn
margin: 10px 2px 10px 2px
&:disabled
border-radius: 0
opacity: 1
i
margin-right: 5px
.sample-profile
position: absolute
right: 5px
.main-content-area
padding: 0
background-color: white
.flat-button
width: 100%
margin-bottom: 10px
background: rgb(78, 78, 78)
border: 0
border-radius: 0
padding: 10px
.public-profile-container
padding: 20px
img.profile-photo
width: 256px
border-radius: 6px
.job-profile-container
width: 100%
height: 100%
min-height: 600px
padding: 0
display: table
h1, h2, h3, h4, h5, h6
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif
color: #555
ul.links, ul.projects, ul.sessions
margin: 0
padding: 0
li
list-style: none
.job-profile-row
height: 100%
display: table-row
$side-width: 250px
$side-padding: 5px
$middle-width: 524px
$middle-padding: 20px
.full-height-column
height: 100%
padding: $side-padding
display: table-cell
vertical-align: top
h3:first-child
margin: 5px 0 5px 0
.left-column
width: $side-width - 2 * $side-padding
padding: $side-padding
background-color: $sideBackground
.sub-column
width: $side-width - 2 * $side-padding
overflow-wrap: break-word
#profile-photo-container
position: relative
margin-bottom: 10px
img.profile-photo
width: $side-width - 2 * $side-padding
border-radius: 6px
.profile-caption
background-color: rgba(0, 0, 0, 0.5)
color: white
border-bottom-right-radius: 6px
border-bottom-left-radius: 6px
position: absolute
width: 100%
bottom: 0px
text-align: center
ul.links
text-align: center
li.has-icon
display: inline-block
img
margin: 0 0 10px 0
li.has-icon:not(:nth-child(5))
img
margin: 0 5px 10px 5px
#contact-candidate
margin-top: 20px
background-color: rgb(177, 55, 25)
padding: 15px
font-size: 20px
.middle-column
width: $middle-width - 2 * $middle-padding
padding-left: $middle-padding
padding-right: $middle-padding
background-color: white
.sub-column
width: $middle-width - 2 * $middle-padding
overflow-wrap: break-word
&.double-column
width: $middle-width + $side-width + 2 * $side-padding - 2 * $middle-padding
$middle-padding-double: 30px
padding-left: $middle-padding-double
padding-right: $middle-padding-double
.sub-column
width: $middle-width + $side-width + 2 * $side-padding - 2 * $middle-padding - 2 * $middle-padding-double
overflow-wrap: break-word
code
background-color: $sideBackground
color: #555
margin: 2px 0
display: inline-block
text-transform: lowercase
.long-description
margin-top: 10px
img
max-width: 524px - 60px
max-height: 200px
.experience-header
margin-top: 25px
.header-icon
margin-right: 10px
width: 32px
height: 32px
.experience-entry
margin-bottom: 15px
.duration
margin-left: 10px
margin-bottom: 10px
#job-profile-notes
width: 100%
height: 100px
#remark-treema
background-color: white
border: 0
padding-top: 0
.right-column
width: $side-width
background-color: $sideBackground
.sub-column
width: $side-width - 2 * $side-padding
overflow-wrap: break-word
> h3:first-child
background-color: white
padding: 5px 5px
margin: 5px 2px 5px 2px
ul.projects
li
margin-bottom: 10px
padding: 5px 3px
border: 2px solid $sideBackground
transition: .5s ease-in-out
position: relative
background-color: white
&:hover
border-color: rgb(100, 130, 255)
a
position: relative
z-index: 2
> a
position: absolute
width: 100%
height: 100%
top: 0
left: 0
z-index: 1
.project-image
width: 230px
height: 115px
background-size: cover
background-repeat: no-repeat
background-position: center
-webkit-filter: grayscale(100%)
-webkit-transition: .5s ease-in-out
-moz-filter: grayscale(100%)
-moz-transition: .5s ease-in-out
-o-filter: grayscale(100%)
-o-transition: .5s ease-in-out
filter: grayscale(100%)
transition: .5s ease-in-out
ul.projects li:hover .project-image, .project-image:hover
-webkit-filter: grayscale(0%)
-moz-filter: grayscale(0%)
-o-filter: grayscale(0%)
filter: grayscale(0%)
.main-content-area
.job-profile-container
.editable-section
position: relative
transition: box-shadow 0.5s easeInOutQuad
min-height: 30px
&.just-saved
box-shadow: 0px 0px 20px 0px #080
z-index: 1
.editable-form
display: none
background-color: white
padding: 5px 5px 5px 5px
.skill-array-item
display: inline-block
input
width: 120px
margin: 5px
.project-image
width: 210px
height: 105px
cursor: pointer
.editable-icon
display: none
.job-profile-container.editable-profile
.full-height-column.deemphasized
background-color: $sideBackground
.saving
opacity: 0.75
.editable-thinner
padding-right: 30px
.editable-icon
display: block
position: absolute
right: 5px
top: 5px
font-size: 20px
color: $blue
opacity: 0.5
.edit-label
color: $blue
font-weight: 300
.edit-example-button
background-color: transparentize($blue, 0.25)
.edit-example-text
color: $blue
code.edit-example-tag
color: $blue
.emphasized
outline: 1px solid $green
.editable-section.deemphasized:not(.just-saved), .our-notes-section.deemphasized
opacity: 0.5
.editable-section:hover
cursor: pointer
outline: 1px solid $blue
.editable-icon
opacity: 1.0
cursor: pointer
.editable-form
cursor: default

View file

@ -1,74 +0,0 @@
#admin-candidates-view
h1, h2, h3
font: Arial
.see-candidates-header
margin: 30px
text-align: center
#see-candidates
cursor: pointer
.employer_icon
width: 125px
float: left
margin: 0px 15px 15px 0px
.information_row
height: 150px
padding-right: 15px
#leftside
width: 500px
float: left
#rightside
width: 500px
float: left
.tablesorter
//img
// display: none
.tablesorter-header
cursor: pointer
&:hover
color: black
&:first-child
// Make sure that "Developer #56" doesn't wrap onto second row
min-width: 110px
.tablesorter-headerAsc
background-color: #cfc
.tablesorter-headerDesc
background-color: #ccf
tr
cursor: pointer
tr.expired
opacity: 0.5
code
background-color: rgb(220, 220, 220)
color: #555
margin: 2px 0
display: inline-block
text-transform: lowercase
td:nth-child(3) select
min-width: 100px
td:nth-child(6) select
min-width: 50px
td:nth-child(7) select
min-width: 100px
#employers-view, #profile-view.viewed-by-employer
#outer-content-wrapper, #intermediate-content-wrapper, #inner-content-wrapper
background: #949494
.main-content-area
background-color: #EAEAEA

View file

@ -18,9 +18,20 @@
.name-row .name-row
@extend .body-row @extend .body-row
max-width: 300px max-width: 300px
.description-row
@extend .body-row
max-width: 520px
.small-name-row .small-name-row
@extend .body-row @extend .body-row
max-width: 200px max-width: 200px
.watch-row
@extend .body-row
max-width: 80px
text-align: center
&.watching
opacity: 1.0
&.not-watching
opacity: 0.5
tr.mine tr.mine
background-color: #f8ecaa background-color: #f8ecaa

View file

@ -1,7 +1,7 @@
@import "app/styles/mixins" @import "app/styles/mixins"
@import "app/styles/bootstrap/variables" @import "app/styles/bootstrap/variables"
#ladder-home-view #main-ladder-view
.level .level
width: 100% width: 100%
position: relative position: relative

View file

@ -1,52 +0,0 @@
@import "app/styles/mixins"
@import "app/styles/bootstrap/variables"
#employers-wrapper
background-color: #B4B4B4
height: 100%
#outer-content-wrapper, #intermediate-content-wrapper, #inner-content-wrapper
background: #B4B4B4
.navbar, #top-nav, .content.clearfix
background-color: #B4B4B4
.footer
border-top: none
background-color: #B4B4B4
padding-bottom: 50px
#employer-content-area
margin: auto
.employer-modal-background-wrapper
background-color: white
border: 2px #333333 solid
border-radius: 4px
h1, h2, h3, h4, h5
color: black
font-family: Arial, Helvetica, sans-serif
.input-large
font-size: 28px
height: 60px
border: 2px rgb(231,231,231) solid
box-shadow: none
width: 70%
margin-left: 15%
#create-account-button, #contract-agreement-button, #login-button
background: #fce232 /* Old browsers */
background: -moz-linear-gradient(top, #fce232 0%, #ea8e2b 100%)
background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#fce232), color-stop(100%,#ea8e2b))
background: -webkit-linear-gradient(top, #fce232 0%,#ea8e2b 100%)
background: -o-linear-gradient(top, #fce232 0%,#ea8e2b 100%)
background: -ms-linear-gradient(top, #fce232 0%,#ea8e2b 100%)
background: linear-gradient(to bottom, #fce232 0%,#ea8e2b 100%)
filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#fce232', endColorstr='#ea8e2b',GradientType=0 )
height: 60px
font-size: 24px
color: black
.login-link
text-decoration: underline
#login-button
margin-left: 40%
width: 20%

View file

@ -159,7 +159,7 @@ else
| (Translator) | (Translator)
input#email_diplomatNews(name="email_diplomatNews", type="checkbox", checked=subs.diplomatNews) 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. span(data-i18n="contribute.diplomat_subscribe_desc").help-block Get emails about i18n developments and, eventually, levels to translate.
.form-group.checkbox .form-group.checkbox
label.control-label(for="email_ambassadorNews") label.control-label(for="email_ambassadorNews")
span(data-i18n="classes.ambassador_title") span(data-i18n="classes.ambassador_title")
@ -169,7 +169,13 @@ else
| (Support) | (Support)
input#email_ambassadorNews(name="email_ambassadorNews", type="checkbox", checked=subs.ambassadorNews) 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. span(data-i18n="contribute.ambassador_subscribe_desc").help-block Get emails on support updates and multiplayer developments.
.form-group.checkbox
label.control-label(for="email_teacherNews")
span(data-i18n="classes.teacher_title")
input#email_teacherNews(name="email_teacherNews", type="checkbox", checked=subs.teacherNews)
span(data-i18n="contribute.teacher_subscribe_desc").help-block
button#toggle-all-btn.btn.btn-primary.form-control(data-i18n="account_settings.email_toggle") Toggle All button#toggle-all-btn.btn.btn-primary.form-control(data-i18n="account_settings.email_toggle") Toggle All
.panel.panel-default .panel.panel-default

View file

@ -1 +1,35 @@
extends /templates/common/table extends /templates/common/table
block tableResultsHeader
tr
th(colspan=4)
span(data-i18n="general.results")
| Results
span
|: #{documents.length}
block tableHeader
tr
th(data-i18n="general.name") Name
th(data-i18n="general.description") Description
th(data-i18n="general.version") Version
th(data-i18n="common.watch") Watch
block tableBody
for document in documents
- var data = document.attributes;
tr(class=document.get('creator') == me.id ? 'mine' : '')
td(title=data.name).name-row
a(href="/editor/#{page}/#{data.slug || data._id}")
| #{data.name}
td(title=data.description).description-row
| #{data.description}
td #{data.version.major}.#{data.version.minor}
if document.watching()
td.watch-row.watching
span(aria-hidden="true").glyphicon.glyphicon-eye-open
span(data-i18n="common.watch").sr-only Watch
else
td.watch-row.not-watching
span(aria-hidden="true").glyphicon.glyphicon-eye-close
span(data-i18n="common.unwatch").sr-only Unwatch

View file

@ -1,21 +0,0 @@
body
#fb-root
#employers-wrapper
block header
.nav
.content.clearfix
.navbar-header
a.navbar-brand(href='/')
img(src="/images/pages/base/recruitment_logo.png", title="CodeCombat - Learn how to code by playing a game", alt="CodeCombat")
block outer_content
#outer-content-wrapper
#intermediate-content-wrapper
#inner-content-wrapper
.main-content-area#employer-content-area
block content
p If this is showing, you dun goofed
block footer
.footer

View file

@ -7,7 +7,7 @@ class PendingPatchesCollection extends CocoCollection
url: '/db/patch?view=pending' url: '/db/patch?view=pending'
model: Patch model: Patch
module.exports = class PatchesView extends RootView module.exports = class PendingPatchesView extends RootView
id: 'pending-patches-view' id: 'pending-patches-view'
template: template template: template

View file

@ -2,7 +2,7 @@ Classroom = require 'models/Classroom'
ModalView = require 'views/core/ModalView' ModalView = require 'views/core/ModalView'
template = require 'templates/courses/classroom-settings-modal' template = require 'templates/courses/classroom-settings-modal'
module.exports = class AddLevelSystemModal extends ModalView module.exports = class ClassroomSettingsModal extends ModalView
id: 'classroom-settings-modal' id: 'classroom-settings-modal'
template: template template: template

View file

@ -4,7 +4,7 @@ auth = require 'core/auth'
forms = require 'core/forms' forms = require 'core/forms'
User = require 'models/User' User = require 'models/User'
module.exports = class StudentSignInModal extends ModalView module.exports = class StudentLogInModal extends ModalView
id: 'student-log-in-modal' id: 'student-log-in-modal'
template: template template: template

View file

@ -6,6 +6,7 @@ module.exports = class LevelSearchView extends SearchView
model: require 'models/Level' model: require 'models/Level'
modelURL: '/db/level' modelURL: '/db/level'
tableTemplate: require 'templates/editor/level/table' tableTemplate: require 'templates/editor/level/table'
projection: ['slug', 'name', 'description', 'version', 'watchers', 'creator']
page: 'level' page: 'level'
getRenderData: -> getRenderData: ->

View file

@ -1,8 +1,8 @@
I18NEditModelView = require './I18NEditModelView' I18NEditModelView = require './I18NEditModelView'
ThangType = require 'models/ThangType' ThangType = require 'models/ThangType'
module.exports = class ThangTypeI18NView extends I18NEditModelView module.exports = class I18NEditThangTypeView extends I18NEditModelView
id: 'thang-type-i18n-view' id: 'i18n-thang-type-view'
modelClass: ThangType modelClass: ThangType
buildTranslationList: -> buildTranslationList: ->

View file

@ -11,8 +11,8 @@ class LevelSessionsCollection extends CocoCollection
super() super()
@url = "/db/user/#{me.id}/level.sessions?project=state.complete,levelID" @url = "/db/user/#{me.id}/level.sessions?project=state.complete,levelID"
module.exports = class LadderHomeView extends RootView module.exports = class MainLadderView extends RootView
id: 'ladder-home-view' id: 'main-ladder-view'
template: template template: template
constructor: (options) -> constructor: (options) ->

View file

@ -1,4 +0,0 @@
AuthModal = require 'views/core/AuthModal'
module.exports = class SignupModalView extends AuthModal
mode: 'signup'

View file

@ -2,7 +2,7 @@ ModalView = require 'views/core/ModalView'
template = require 'templates/play/modal/share-progress-modal' template = require 'templates/play/modal/share-progress-modal'
storage = require 'core/storage' storage = require 'core/storage'
module.exports = class SubscribeModal extends ModalView module.exports = class ShareProgressModal extends ModalView
id: 'share-progress-modal' id: 'share-progress-modal'
template: template template: template
plain: true plain: true

View file

@ -4,6 +4,7 @@ config = require '../../server_config'
plugins = require '../plugins/plugins' plugins = require '../plugins/plugins'
User = require '../users/User' User = require '../users/User'
jsonSchema = require '../../app/schemas/models/classroom.schema' jsonSchema = require '../../app/schemas/models/classroom.schema'
utils = require '../lib/utils'
ClassroomSchema = new mongoose.Schema {}, {strict: false, minimize: false, read:config.mongo.readpref} ClassroomSchema = new mongoose.Schema {}, {strict: false, minimize: false, read:config.mongo.readpref}
@ -18,12 +19,10 @@ ClassroomSchema.statics.editableProperties = [
'aceConfig' 'aceConfig'
] ]
# 250 words; will want to use 4 code words once we get past 10M classrooms.
words = 'angry apple arm army art baby back bad bag ball bath bean bear bed bell best big bird bite blue boat book box boy bread burn bus cake car cat chair city class clock cloud coat coin cold cook cool corn crash cup dark day deep desk dish dog door down draw dream drink drop dry duck dust east eat egg enemy eye face false farm fast fear fight find fire flag floor fly food foot fork fox free fruit full fun funny game gate gift glass goat gold good green hair half hand happy heart heavy help hide hill home horse house ice idea iron jelly job jump key king lamp large last late lazy leaf left leg life light lion lock long luck map mean milk mix moon more most mouth music name neck net new next nice night north nose old only open page paint pan paper park party path pig pin pink place plane plant plate play point pool power pull push queen rain ready red rest rice ride right ring road rock room run sad safe salt same sand sell shake shape share sharp sheep shelf ship shirt shoe shop short show sick side silly sing sink sit size sky sleep slow small snow sock soft soup south space speed spell spoon star start step stone stop sweet swim sword table team thick thin thing think today tooth top town tree true turn type under want warm watch water west wide win word yes zoo'.split(' ')
ClassroomSchema.statics.generateNewCode = (done) -> ClassroomSchema.statics.generateNewCode = (done) ->
tryCode = -> tryCode = ->
codeCamel = _.map(_.sample(words, 3), (s) -> s[0].toUpperCase() + s.slice(1)).join('') # Use 4 code words once we get past 10M classrooms
codeCamel = utils.getCodeCamel(3)
code = codeCamel.toLowerCase() code = codeCamel.toLowerCase()
Classroom.findOne code: code, (err, classroom) -> Classroom.findOne code: code, (err, classroom) ->
return done() if err return done() if err
@ -31,8 +30,6 @@ ClassroomSchema.statics.generateNewCode = (done) ->
tryCode() tryCode()
tryCode() tryCode()
#ClassroomSchema.plugin plugins.NamedPlugin
ClassroomSchema.pre('save', (next) -> ClassroomSchema.pre('save', (next) ->
return next() if @get('code') return next() if @get('code')
Classroom.generateNewCode (code, codeCamel) => Classroom.generateNewCode (code, codeCamel) =>

View file

@ -3,6 +3,6 @@ config = require '../../server_config'
module.exports.MAILCHIMP_LIST_ID = 'e9851239eb' module.exports.MAILCHIMP_LIST_ID = 'e9851239eb'
module.exports.MAILCHIMP_GROUP_ID = '4529' module.exports.MAILCHIMP_GROUP_ID = '4529'
# these two need to be parallel # These two need to be parallel
module.exports.MAILCHIMP_GROUPS = ['Announcements', 'Adventurers', 'Artisans', 'Archmages', 'Scribes', 'Diplomats', 'Ambassadors'] module.exports.MAILCHIMP_GROUPS = ['Announcements', 'Adventurers', 'Artisans', 'Archmages', 'Scribes', 'Diplomats', 'Ambassadors', 'Teachers']
module.exports.NEWS_GROUPS = ['generalNews', 'adventurerNews', 'artisanNews', 'archmageNews', 'scribeNews', 'diplomatNews', 'ambassadorNews'] module.exports.NEWS_GROUPS = ['generalNews', 'adventurerNews', 'artisanNews', 'archmageNews', 'scribeNews', 'diplomatNews', 'ambassadorNews', 'teacherNews']

View file

@ -6,6 +6,11 @@ config = require '../../server_config'
module.exports = module.exports =
isID: (id) -> _.isString(id) and id.length is 24 and id.match(/[a-f0-9]/gi)?.length is 24 isID: (id) -> _.isString(id) and id.length is 24 and id.match(/[a-f0-9]/gi)?.length is 24
getCodeCamel: (numWords=3) ->
# 250 words
words = 'angry apple arm army art baby back bad bag ball bath bean bear bed bell best big bird bite blue boat book box boy bread burn bus cake car cat chair city class clock cloud coat coin cold cook cool corn crash cup dark day deep desk dish dog door down draw dream drink drop dry duck dust east eat egg enemy eye face false farm fast fear fight find fire flag floor fly food foot fork fox free fruit full fun funny game gate gift glass goat gold good green hair half hand happy heart heavy help hide hill home horse house ice idea iron jelly job jump key king lamp large last late lazy leaf left leg life light lion lock long luck map mean milk mix moon more most mouth music name neck net new next nice night north nose old only open page paint pan paper park party path pig pin pink place plane plant plate play point pool power pull push queen rain ready red rest rice ride right ring road rock room run sad safe salt same sand sell shake shape share sharp sheep shelf ship shirt shoe shop short show sick side silly sing sink sit size sky sleep slow small snow sock soft soup south space speed spell spoon star start step stone stop sweet swim sword table team thick thin thing think today tooth top town tree true turn type under want warm watch water west wide win word yes zoo'.split(' ')
_.map(_.sample(words, numWords), (s) -> s[0].toUpperCase() + s.slice(1)).join('')
objectIdFromTimestamp: (timestamp) -> objectIdFromTimestamp: (timestamp) ->
# mongoDB ObjectId contains creation date in first 4 bytes # mongoDB ObjectId contains creation date in first 4 bytes
# So, it can be used instead of a redundant created field # So, it can be used instead of a redundant created field

View file

@ -41,7 +41,7 @@ module.exports.formatSessionInformation = (session) ->
shouldUpdateLastOpponentSubmitDateForLeague: session.shouldUpdateLastOpponentSubmitDateForLeague shouldUpdateLastOpponentSubmitDateForLeague: session.shouldUpdateLastOpponentSubmitDateForLeague
module.exports.calculateSessionScores = (callback) -> module.exports.calculateSessionScores = (callback) ->
sessionIDs = _.pluck @clientResponseObject.sessions, 'sessionID' sessionIDs = _.map @clientResponseObject.sessions, 'sessionID'
async.map sessionIDs, retrieveOldSessionData.bind(@), (err, oldScores) => async.map sessionIDs, retrieveOldSessionData.bind(@), (err, oldScores) =>
if err? then return callback err, {error: 'There was an error retrieving the old scores'} if err? then return callback err, {error: 'There was an error retrieving the old scores'}
try try
@ -151,7 +151,7 @@ module.exports.addMatchToSessionsAndUpdate = (newScoreObject, callback) ->
#log.info "Match object computed, result: #{JSON.stringify(matchObject, null, 2)}" #log.info "Match object computed, result: #{JSON.stringify(matchObject, null, 2)}"
#log.info 'Writing match object to database...' #log.info 'Writing match object to database...'
#use bind with async to do the writes #use bind with async to do the writes
sessionIDs = _.pluck @clientResponseObject.sessions, 'sessionID' sessionIDs = _.map @clientResponseObject.sessions, 'sessionID'
async.each sessionIDs, updateMatchesInSession.bind(@, matchObject), (err) -> async.each sessionIDs, updateMatchesInSession.bind(@, matchObject), (err) ->
callback err callback err

View file

@ -8,6 +8,7 @@ errors = require '../commons/errors'
languages = require '../routes/languages' languages = require '../routes/languages'
sendwithus = require '../sendwithus' sendwithus = require '../sendwithus'
log = require 'winston' log = require 'winston'
utils = require '../lib/utils'
module.exports.setup = (app) -> module.exports.setup = (app) ->
authentication.serializeUser((user, done) -> done(null, user._id)) authentication.serializeUser((user, done) -> done(null, user._id))
@ -101,7 +102,10 @@ module.exports.setup = (app) ->
if not user if not user
return errors.notFound(res, [{message: 'not found', property: 'email'}]) return errors.notFound(res, [{message: 'not found', property: 'email'}])
user.set('passwordReset', Math.random().toString(36).slice(2, 7).toUpperCase()) user.set('passwordReset', utils.getCodeCamel())
emailContent = "<h3>Your temporary password: <b>#{user.get('passwordReset')}</b></h3>"
emailContent += "<p>Reset your password at <a href=\"http://codecombat.com/account/settings\">http://codecombat.com/account/settings</a></p>"
emailContent += "<p>Your old password cannot be retrieved.</p>"
user.save (err) => user.save (err) =>
return errors.serverError(res) if err return errors.serverError(res) if err
unless config.unittest unless config.unittest
@ -111,8 +115,8 @@ module.exports.setup = (app) ->
address: req.body.email address: req.body.email
email_data: email_data:
subject: 'CodeCombat Recovery Password' subject: 'CodeCombat Recovery Password'
title: 'Recovery Password' title: ''
content: "<p>Your CodeCombat recovery password for email #{req.body.email} is: #{user.get('passwordReset')}</p><p>Log in at <a href=\"http://codecombat.com/account/settings\">http://codecombat.com/account/settings</a> and change it.</p><p>Hope this helps!</p>" content: emailContent
sendwithus.api.send context, (err, result) -> sendwithus.api.send context, (err, result) ->
if err if err
console.error "Error sending password reset email: #{err.message or err}" console.error "Error sending password reset email: #{err.message or err}"

View file

@ -5,6 +5,7 @@ hipchat = require '../hipchat'
sendwithus = require '../sendwithus' sendwithus = require '../sendwithus'
Prepaid = require '../prepaids/Prepaid' Prepaid = require '../prepaids/Prepaid'
jsonSchema = require '../../app/schemas/models/trial_request.schema' jsonSchema = require '../../app/schemas/models/trial_request.schema'
User = require '../users/User'
TrialRequestSchema = new mongoose.Schema {}, {strict: false, minimize: false, read:config.mongo.readpref} TrialRequestSchema = new mongoose.Schema {}, {strict: false, minimize: false, read:config.mongo.readpref}
@ -32,6 +33,19 @@ TrialRequestSchema.post 'save', (doc) ->
sendwithus.api.send emailParams, (err, result) => sendwithus.api.send emailParams, (err, result) =>
log.error "sendwithus trial request approved error: #{err}, result: #{result}" if err log.error "sendwithus trial request approved error: #{err}, result: #{result}" if err
# Subscribe to teacher news group
User.findById doc.get('applicant'), (err, user) =>
if err
log.error "Trial request user find error: #{err}"
return
emails = _.cloneDeep(user.get('emails') ? {})
emails.teacherNews ?= {}
emails.teacherNews.enabled = true
user.update {$set: {emails: emails}}, {}, (err) =>
if err
log.error "Trial request user update error: #{err}"
return
TrialRequestSchema.statics.privateProperties = [] TrialRequestSchema.statics.privateProperties = []
TrialRequestSchema.statics.editableProperties = [ TrialRequestSchema.statics.editableProperties = [
'created' 'created'

View file

@ -76,6 +76,7 @@ emailNameMap =
diplomatNews: 'translator' diplomatNews: 'translator'
ambassadorNews: 'support' ambassadorNews: 'support'
anyNotes: 'notification' anyNotes: 'notification'
teacherNews: 'teacher'
UserSchema.methods.setEmailSubscription = (newName, enabled) -> UserSchema.methods.setEmailSubscription = (newName, enabled) ->
oldSubs = _.clone @get('emailSubscriptions') oldSubs = _.clone @get('emailSubscriptions')

View file

@ -134,7 +134,11 @@ describe 'Trial Requests', ->
expect(prepaid.get('type')).toEqual('course') expect(prepaid.get('type')).toEqual('course')
expect(prepaid.get('creator')).toEqual(user.get('_id')) expect(prepaid.get('creator')).toEqual(user.get('_id'))
expect(prepaid.get('maxRedeemers')).toEqual(2) expect(prepaid.get('maxRedeemers')).toEqual(2)
done() User.findById user._id, (err, user) =>
expect(err).toBeNull()
return done(err) if err
expect(user.get('emails')?.teacherNews?.enabled).toEqual(true)
done()
it 'Deny trial request', (done) -> it 'Deny trial request', (done) ->
loginNewUser (user) -> loginNewUser (user) ->