mirror of
https://github.com/codeninjasllc/codecombat.git
synced 2024-11-25 00:28:31 -05:00
Merge branch 'master' into production
This commit is contained in:
commit
3576805145
8 changed files with 153 additions and 102 deletions
|
@ -265,7 +265,7 @@ module.exports = nativeDescription: "limba română", englishDescription: "Roman
|
||||||
message: "Mesaj"
|
message: "Mesaj"
|
||||||
|
|
||||||
about:
|
about:
|
||||||
who_is_codecombat: "Cine este CodeCombat?" # I assume you meant (what)
|
who_is_codecombat: "Cine este CodeCombat?"
|
||||||
why_codecombat: "De ce CodeCombat?"
|
why_codecombat: "De ce CodeCombat?"
|
||||||
who_description_prefix: "au pornit împreuna CodeCombat în 2013. Tot noi am creat "
|
who_description_prefix: "au pornit împreuna CodeCombat în 2013. Tot noi am creat "
|
||||||
who_description_suffix: "în 2008, dezvoltând aplicația web si iOS #1 de învățat cum să scri caractere Japoneze si Chinezești."
|
who_description_suffix: "în 2008, dezvoltând aplicația web si iOS #1 de învățat cum să scri caractere Japoneze si Chinezești."
|
||||||
|
@ -277,75 +277,75 @@ module.exports = nativeDescription: "limba română", englishDescription: "Roman
|
||||||
why_paragraph_3_center: "ci"
|
why_paragraph_3_center: "ci"
|
||||||
why_paragraph_3_italic_caps: "TREBUIE SĂ TERMIN ACEST NIVEL!"
|
why_paragraph_3_italic_caps: "TREBUIE SĂ TERMIN ACEST NIVEL!"
|
||||||
why_paragraph_3_suffix: "De aceea CodeCombat este un joc multiplayer, nu un curs transfigurat în joc. Nu ne vom opri până când tu nu te poți opri--și de data asta, e de bine."
|
why_paragraph_3_suffix: "De aceea CodeCombat este un joc multiplayer, nu un curs transfigurat în joc. Nu ne vom opri până când tu nu te poți opri--și de data asta, e de bine."
|
||||||
# why_paragraph_4: "If you're going to get addicted to some game, get addicted to this one and become one of the wizards of the tech age."
|
why_paragraph_4: "Dacă e să devi dependent de vreun joc, devino dependent de acesta și fi un vrăjitor al noii ere tehnologice."
|
||||||
# why_ending: "And hey, it's free. "
|
why_ending: "Nu uita, este totul gratis. "
|
||||||
# why_ending_url: "Start wizarding now!"
|
why_ending_url: "Devino un vrăjitor acum!"
|
||||||
# george_description: "CEO, business guy, web designer, game designer, and champion of beginning programmers everywhere."
|
george_description: "CEO, business guy, web designer, game designer, și campion al programatorilor începători."
|
||||||
# scott_description: "Programmer extraordinaire, software architect, kitchen wizard, and master of finances. Scott is the reasonable one."
|
scott_description: "Programmer extraordinaire, software architect, kitchen wizard, și maestru al finanțelor. Scott este cel rezonabil."
|
||||||
# nick_description: "Programming wizard, eccentric motivation mage, and upside-down experimenter. Nick can do anything and chooses to build CodeCombat."
|
nick_description: "Programming wizard, eccentric motivation mage, and upside-down experimenter. Nick poate să facă orice si a ales să dezvolte CodeCombat."
|
||||||
# jeremy_description: "Customer support mage, usability tester, and community organizer; you've probably already spoken with Jeremy."
|
jeremy_description: "Customer support mage, usability tester, and community organizer; probabil ca ați vorbit deja cu Jeremy."
|
||||||
# michael_description: "Programmer, sys-admin, and undergrad technical wunderkind, Michael is the person keeping our servers online."
|
michael_description: "Programmer, sys-admin, and undergrad technical wunderkind, Michael este cel care ține serverele in picioare."
|
||||||
|
|
||||||
# legal:
|
legal:
|
||||||
# page_title: "Legal"
|
page_title: "Aspecte Legale"
|
||||||
# opensource_intro: "CodeCombat is free to play and completely open source."
|
opensource_intro: "CodeCombat este free-to-play și complet open source."
|
||||||
# opensource_description_prefix: "Check out "
|
opensource_description_prefix: "Vizitează "
|
||||||
# github_url: "our GitHub"
|
github_url: "pagina noastră de GitHub"
|
||||||
# opensource_description_center: "and help out if you like! CodeCombat is built on dozens of open source projects, and we love them. See "
|
opensource_description_center: "și ajută-ne dacă îți place! CodeCombat este construit peste o mulțime de proiecte open source, care noi le iubim. Vizitați"
|
||||||
# archmage_wiki_url: "our Archmage wiki"
|
archmage_wiki_url: "Archmage wiki"
|
||||||
# opensource_description_suffix: "for a list of the software that makes this game possible."
|
opensource_description_suffix: "pentru o listă cu software-ul care face acest joc posibil."
|
||||||
# practices_title: "Respectful Best Practices"
|
# practices_title: "Respectful Best Practices" #not sure what you mean here? other word for /practices/?
|
||||||
# practices_description: "These are our promises to you, the player, in slightly less legalese."
|
practices_description: "Acestea sunt promisiunile noastre către tine, jucătorul, fără așa mulți termeni legali."
|
||||||
# privacy_title: "Privacy"
|
privacy_title: "Confidenţialitate şi termeni"
|
||||||
# privacy_description: "We will not sell any of your personal information. We intend to make money through recruitment eventually, but rest assured we will not distribute your personal information to interested companies without your explicit consent."
|
privacy_description: "Noi nu vom vinde nici o informație personală. Intenționăm să obținem profit prin recrutare eventual, dar stați liniștiți , nu vă vom vinde informațiile personale companiilor interesate fără consimțământul vostru explicit."
|
||||||
# security_title: "Security"
|
security_title: "Securitate"
|
||||||
# security_description: "We strive to keep your personal information safe. As an open source project, our site is freely open to anyone to review and improve our security systems."
|
security_description: "Ne străduim să vă protejăm informațiile personale. Fiind un proiect open-source, site-ul nostru oferă oricui posibilitatea de a ne revizui și îmbunătăți sistemul de securitate."
|
||||||
# email_title: "Email"
|
email_title: "Email"
|
||||||
# email_description_prefix: "We will not inundate you with spam. Through"
|
email_description_prefix: "Noi nu vă vom inunda cu spam. Prin"
|
||||||
# email_settings_url: "your email settings"
|
email_settings_url: "setările tale de email"
|
||||||
# email_description_suffix: "or through links in the emails we send, you can change your preferences and easily unsubscribe at any time."
|
email_description_suffix: " sau prin link-urile din email-urile care vi le trimitem, puteți să schimbați preferințele și să vâ dezabonați oricând."
|
||||||
# cost_title: "Cost"
|
cost_title: "Cost"
|
||||||
# cost_description: "Currently, CodeCombat is 100% free! One of our main goals is to keep it that way, so that as many people can play as possible, regardless of place in life. If the sky darkens, we might have to charge subscriptions or for some content, but we'd rather not. With any luck, we'll be able to sustain the company with:"
|
cost_description: "Momentan, CodeCombat este 100% gratis! Unul dintre obiectele noastre principale este să îl menținem așa, astfel încât să poată juca cât mai mulți oameni. Dacă va fi nevoie , s-ar putea să percepem o plată pentru o pentru anumite servici,dar am prefera să nu o facem. Cu puțin noroc, vom putea susține compania cu:"
|
||||||
# recruitment_title: "Recruitment"
|
recruitment_title: "Recrutare"
|
||||||
# recruitment_description_prefix: "Here on CodeCombat, you're going to become a powerful wizard–not just in the game, but also in real life."
|
recruitment_description_prefix: "Aici la CodeCombat, vei deveni un vrăjitor puternic nu doar în joc , ci și în viața reală."
|
||||||
# url_hire_programmers: "No one can hire programmers fast enough"
|
url_hire_programmers: "Nimeni nu poate angaja programatori destul de rapid"
|
||||||
# recruitment_description_suffix: "so once you've sharpened your skills and if you agree, we will demo your best coding accomplishments to the thousands of employers who are drooling for the chance to hire you. They pay us a little, they pay you"
|
recruitment_description_suffix: "așa că odată ce ți-ai dezvoltat abilitățile și esti de acord, noi vom trimite un demo cu cele mai bune realizări ale tale către miile de angajatori care se omoară să pună mâna pe tine. Pe noi ne plătesc puțin, pe tine te vor plăti"
|
||||||
# recruitment_description_italic: "a lot"
|
recruitment_description_italic: "mult"
|
||||||
# recruitment_description_ending: "the site remains free and everybody's happy. That's the plan."
|
recruitment_description_ending: "site-ul rămâne gratis și toată lumea este fericită. Acesta este planul."
|
||||||
# copyrights_title: "Copyrights and Licenses"
|
copyrights_title: "Drepturi de autor și licențe"
|
||||||
# contributor_title: "Contributor License Agreement"
|
contributor_title: "Acord de licență Contributor"
|
||||||
# contributor_description_prefix: "All contributions, both on the site and on our GitHub repository, are subject to our"
|
contributor_description_prefix: "Toți contribuitorii, atât pe site cât și pe GitHub-ul nostru, sunt supuși la"
|
||||||
# cla_url: "CLA"
|
cla_url: "ALC"
|
||||||
# contributor_description_suffix: "to which you should agree before contributing."
|
contributor_description_suffix: "la care trebuie să fi de accord înainte să poți contribui."
|
||||||
# code_title: "Code - MIT"
|
code_title: "Code - MIT"
|
||||||
# code_description_prefix: "All code owned by CodeCombat or hosted on codecombat.com, both in the GitHub repository or in the codecombat.com database, is licensed under the"
|
code_description_prefix: "Tot codul deținut de CodeCombat sau hostat pe codecombat.com, atât pe GitHub cât și în baza de date codecombat.com, este licențiată sub"
|
||||||
# mit_license_url: "MIT license"
|
mit_license_url: "MIT license"
|
||||||
# code_description_suffix: "This includes all code in Systems and Components that are made available by CodeCombat for the purpose of creating levels."
|
code_description_suffix: "Asta include tot codul din Systems și Components care este oferit de către CodeCombat cu scopul de a crea nivele."
|
||||||
# art_title: "Art/Music - Creative Commons "
|
art_title: "Artă/Muzică - Conținut Comun "
|
||||||
# art_description_prefix: "All common content is available under the"
|
art_description_prefix: "Tot conținutul creativ/artistic este valabil sub"
|
||||||
# cc_license_url: "Creative Commons Attribution 4.0 International License"
|
cc_license_url: "Creative Commons Attribution 4.0 International License"
|
||||||
# art_description_suffix: "Common content is anything made generally available by CodeCombat for the purpose of creating Levels. This includes:"
|
art_description_suffix: "Conținut comun este orice făcut general valabil de către CodeCombat cu scopul de a crea nivele. Asta include:"
|
||||||
# art_music: "Music"
|
art_music: "Muzică"
|
||||||
# art_sound: "Sound"
|
art_sound: "Sunet"
|
||||||
# art_artwork: "Artwork"
|
art_artwork: "Artwork"
|
||||||
# art_sprites: "Sprites"
|
art_sprites: "Sprites" #can t be translated, either suggest alternative name or must be left like this
|
||||||
# art_other: "Any and all other non-code creative works that are made available when creating Levels."
|
art_other: "Orice si toate celelalte creații non-cod care sunt disponibile când se crează nivele."
|
||||||
# art_access: "Currently there is no universal, easy system for fetching these assets. In general, fetch them from the URLs as used by the site, contact us for assistance, or help us in extending the site to make these assets more easily accessible."
|
art_access: "Momentan nu există nici un sistem universal,ușor pentru preluarea acestor bunuri. În general, preluați-le precum site-ul din URL-urile folosite, contactați-ne pentru asistență, sau ajutați-ne sa extindem site-ul pentru a face aceste bunuri mai ușor accesibile."
|
||||||
# art_paragraph_1: "For attribution, please name and link to codecombat.com near where the source is used or where appropriate for the medium. For example:"
|
art_paragraph_1: "Pentru atribuire, vă rugăm numiți și lăsați referire link la codecombat.com unde este folosită sursa sau unde este adecvat pentru mediu. De exemplu:"
|
||||||
# use_list_1: "If used in a movie or another game, include codecombat.com in the credits."
|
use_list_1: "Dacă este folosit într-un film sau alt joc, includeți codecombat.com la credite."
|
||||||
# use_list_2: "If used on a website, include a link near the usage, for example underneath an image, or in a general attributions page where you might also mention other Creative Commons works and open source software being used on the site. Something that's already clearly referencing CodeCombat, such as a blog post mentioning CodeCombat, does not need some separate attribution."
|
use_list_2: "Dacă este folosit pe un site, includeți un link in apropiere, de exemplu sub o imagine, sau in pagina generală de atribuiri unde menționați și alte Bunuri Creative și software open source folosit pe site. Ceva care face referință explicit la CodeCombat, precum o postare pe un blog care menționează CodeCombat, nu trebuie să facă o atribuire separată."
|
||||||
# art_paragraph_2: "If the content being used is created not by CodeCombat but instead by a user of codecombat.com, attribute them instead, and follow attribution directions provided in that resource's description if there are any."
|
art_paragraph_2: "Dacă conținutul folosit nu este creat de către CodeCombat ci de către un utilizator al codecombat.com,atunci faceți referință către ei, și urmăriți indicațiile de atribuire prevăzute în descrierea resursei dacă există."
|
||||||
# rights_title: "Rights Reserved"
|
rights_title: "Drepturi rezervate"
|
||||||
# rights_desc: "All rights are reserved for Levels themselves. This includes"
|
rights_desc: "Toate drepturile sunt rezervate pentru Nivele în sine. Asta include"
|
||||||
# rights_scripts: "Scripts"
|
rights_scripts: "Script-uri"
|
||||||
# rights_unit: "Unit configuration"
|
rights_unit: "Configurații de unități"
|
||||||
# rights_description: "Description"
|
rights_description: "Descriere"
|
||||||
# rights_writings: "Writings"
|
rights_writings: "Scrieri"
|
||||||
# rights_media: "Media (sounds, music) and any other creative content made specifically for that Level and not made generally available when creating Levels."
|
rights_media: "Media (sunete, muzică) și orice alt conținut creativ dezvoltat special pentru acel nivel care nu este valabil în mod normal pentru creat nivele."
|
||||||
# rights_clarification: "To clarify, anything that is made available in the Level Editor for the purpose of making levels is under CC, whereas the content created with the Level Editor or uploaded in the course of creation of Levels is not."
|
rights_clarification: "Pentru a clarifica, orice este valabil in Editorul de Nivele pentru scopul de a crea nivele se află sub CC,pe când conținutul creat cu Editorul de Nivele sau încărcat pentru a face nivelul nu se află." #CC stands for...?
|
||||||
# nutshell_title: "In a Nutshell"
|
nutshell_title: "Pe scurt"
|
||||||
# nutshell_description: "Any resources we provide in the Level Editor are free to use as you like for creating Levels. But we reserve the right to restrict distribution of the Levels themselves (that are created on codecombat.com) so that they may be charged for in the future, if that's what ends up happening."
|
nutshell_description: "Orice resurse vă punem la dispoziție în Editorul de Nivele puteți folosi liber cum vreți pentru a crea nivele. Dar ne rezervăm dreptul de a rezerva distribuția de nivele în sine (care sunt create pe codecombat.com) astfel încât să se poată percepe o taxă pentru ele pe vitor, dacă se va ajunge la așa ceva."
|
||||||
# canonical: "The English version of this document is the definitive, canonical version. If there are any discrepencies between translations, the English document takes precedence."
|
canonical: "The English version of this document is the definitive, canonical version. If there are any discrepencies between translations, the English document takes precedence."
|
||||||
|
|
||||||
# contribute:
|
# contribute:
|
||||||
# page_title: "Contributing"
|
# page_title: "Contributing"
|
||||||
|
|
|
@ -21,6 +21,11 @@ div#columns.row
|
||||||
span.ranked.hidden Submitted for Ranking
|
span.ranked.hidden Submitted for Ranking
|
||||||
span.failed.hidden Failed to Rank
|
span.failed.hidden Failed to Rank
|
||||||
|
|
||||||
|
if team.chartData
|
||||||
|
tr
|
||||||
|
th(colspan=4, style="color: #{team.primaryColor}")
|
||||||
|
img(src="https://chart.googleapis.com/chart?chs=450x125&cht=lxy&chco=#{team.chartColor}&chtt=Score%3A+#{team.currentScore}&chts=#{team.chartColor},16,c&chf=a,s,000000FF&chls=2&chm=o,#{team.chartColor},0,4&chd=t:#{team.chartData}")
|
||||||
|
|
||||||
tr
|
tr
|
||||||
th Result
|
th Result
|
||||||
th Opponent
|
th Opponent
|
||||||
|
|
|
@ -35,7 +35,7 @@ module.exports = class MyMatchesTabView extends CocoView
|
||||||
for session in @sessions.models
|
for session in @sessions.models
|
||||||
for match in session.get('matches') or []
|
for match in session.get('matches') or []
|
||||||
opponent = match.opponents[0]
|
opponent = match.opponents[0]
|
||||||
@nameMap[opponent.userID] = nameMap[opponent.userID]
|
@nameMap[opponent.userID] ?= nameMap[opponent.userID]
|
||||||
@finishRendering()
|
@finishRendering()
|
||||||
|
|
||||||
$.ajax('/db/user/-/names', {
|
$.ajax('/db/user/-/names', {
|
||||||
|
@ -76,6 +76,17 @@ module.exports = class MyMatchesTabView extends CocoView
|
||||||
team.wins = _.filter(team.matches, {state: 'win'}).length
|
team.wins = _.filter(team.matches, {state: 'win'}).length
|
||||||
team.ties = _.filter(team.matches, {state: 'tie'}).length
|
team.ties = _.filter(team.matches, {state: 'tie'}).length
|
||||||
team.losses = _.filter(team.matches, {state: 'loss'}).length
|
team.losses = _.filter(team.matches, {state: 'loss'}).length
|
||||||
|
team.scoreHistory = team.session?.get('scoreHistory')
|
||||||
|
if team.scoreHistory?.length > 1
|
||||||
|
team.currentScore = Math.round team.scoreHistory[team.scoreHistory.length - 1][1] * 100
|
||||||
|
team.chartColor = team.primaryColor.replace '#', ''
|
||||||
|
times = (s[0] for s in team.scoreHistory)
|
||||||
|
times = ((100 * (t - times[0]) / (times[times.length - 1] - times[0])).toFixed(1) for t in times)
|
||||||
|
scores = (s[1] for s in team.scoreHistory)
|
||||||
|
lowest = _.min scores
|
||||||
|
highest = _.max scores
|
||||||
|
scores = (Math.round(100 * (s - lowest) / (highest - lowest)) for s in scores)
|
||||||
|
team.chartData = times.join(',') + '|' + scores.join(',')
|
||||||
|
|
||||||
ctx
|
ctx
|
||||||
|
|
||||||
|
|
|
@ -65,7 +65,7 @@ module.exports = class LadderView extends RootView
|
||||||
return if @startsLoading
|
return if @startsLoading
|
||||||
@insertSubView(@ladderTab = new LadderTabView({}, @level, @sessions))
|
@insertSubView(@ladderTab = new LadderTabView({}, @level, @sessions))
|
||||||
@insertSubView(@myMatchesTab = new MyMatchesTabView({}, @level, @sessions))
|
@insertSubView(@myMatchesTab = new MyMatchesTabView({}, @level, @sessions))
|
||||||
@refreshInterval = setInterval(@fetchSessionsAndRefreshViews.bind(@), 10000)
|
@refreshInterval = setInterval(@fetchSessionsAndRefreshViews.bind(@), 10 * 1000)
|
||||||
hash = document.location.hash[1..] if document.location.hash
|
hash = document.location.hash[1..] if document.location.hash
|
||||||
if hash and not (hash in ['my-matches', 'simulate', 'ladder'])
|
if hash and not (hash in ['my-matches', 'simulate', 'ladder'])
|
||||||
@showPlayModal(hash) if @sessions.loaded
|
@showPlayModal(hash) if @sessions.loaded
|
||||||
|
|
|
@ -71,7 +71,7 @@ def which(cmd, mode=os.F_OK | os.X_OK, path=None):
|
||||||
|
|
||||||
|
|
||||||
current_directory = os.path.dirname(os.path.realpath(sys.argv[0]))
|
current_directory = os.path.dirname(os.path.realpath(sys.argv[0]))
|
||||||
allowedMongoVersions = ["v2.5.4","v2.5.5"]
|
allowedMongoVersions = ["v2.5.4","v2.5.5","v2.6.0-rc1"]
|
||||||
if which("mongod") and any(i in subprocess.check_output("mongod --version",shell=True) for i in allowedMongoVersions):
|
if which("mongod") and any(i in subprocess.check_output("mongod --version",shell=True) for i in allowedMongoVersions):
|
||||||
mongo_executable = "mongod"
|
mongo_executable = "mongod"
|
||||||
else:
|
else:
|
||||||
|
|
|
@ -123,7 +123,9 @@ module.exports = class Handler
|
||||||
# Keeping it simple for now and just allowing access to the first FETCH_LIMIT results.
|
# Keeping it simple for now and just allowing access to the first FETCH_LIMIT results.
|
||||||
query = {'original': mongoose.Types.ObjectId(id)}
|
query = {'original': mongoose.Types.ObjectId(id)}
|
||||||
sort = {'created': -1}
|
sort = {'created': -1}
|
||||||
@modelClass.find(query).limit(FETCH_LIMIT).sort(sort).exec (err, results) =>
|
selectString = 'slug name version commitMessage created' # Is this even working?
|
||||||
|
@modelClass.find(query).select(selectString).lean().limit(FETCH_LIMIT).sort(sort).exec (err, results) =>
|
||||||
|
return @sendDatabaseError(res, err) if err
|
||||||
for doc in results
|
for doc in results
|
||||||
return @sendUnauthorizedError(res) unless @hasAccessToDocument(req, doc)
|
return @sendUnauthorizedError(res) unless @hasAccessToDocument(req, doc)
|
||||||
res.send(results)
|
res.send(results)
|
||||||
|
|
|
@ -2,6 +2,7 @@ authentication = require('passport')
|
||||||
LocalStrategy = require('passport-local').Strategy
|
LocalStrategy = require('passport-local').Strategy
|
||||||
User = require('../users/User')
|
User = require('../users/User')
|
||||||
UserHandler = require('../users/user_handler')
|
UserHandler = require('../users/user_handler')
|
||||||
|
LevelSession = require '../levels/sessions/LevelSession'
|
||||||
config = require '../../server_config'
|
config = require '../../server_config'
|
||||||
errors = require '../commons/errors'
|
errors = require '../commons/errors'
|
||||||
mail = require '../commons/mail'
|
mail = require '../commons/mail'
|
||||||
|
@ -21,16 +22,16 @@ module.exports.setup = (app) ->
|
||||||
if passwordReset and password.toLowerCase() is passwordReset
|
if passwordReset and password.toLowerCase() is passwordReset
|
||||||
User.update {_id: user.get('_id')}, {passwordReset: ''}, {}, ->
|
User.update {_id: user.get('_id')}, {passwordReset: ''}, {}, ->
|
||||||
return done(null, user)
|
return done(null, user)
|
||||||
|
|
||||||
hash = User.hashPassword(password)
|
hash = User.hashPassword(password)
|
||||||
unless user.get('passwordHash') is hash
|
unless user.get('passwordHash') is hash
|
||||||
return done(null, false, {message:'is wrong, wrong, wrong', property:'password'})
|
return done(null, false, {message:'is wrong, wrong, wrong', property:'password'})
|
||||||
return done(null, user)
|
return done(null, user)
|
||||||
)
|
)
|
||||||
))
|
))
|
||||||
app.post '/auth/spy', (req, res, next) ->
|
app.post '/auth/spy', (req, res, next) ->
|
||||||
if req?.user?.isAdmin()
|
if req?.user?.isAdmin()
|
||||||
|
|
||||||
username = req.body.usernameLower
|
username = req.body.usernameLower
|
||||||
emailLower = req.body.emailLower
|
emailLower = req.body.emailLower
|
||||||
if emailLower
|
if emailLower
|
||||||
|
@ -39,19 +40,19 @@ module.exports.setup = (app) ->
|
||||||
query = {"nameLower":username}
|
query = {"nameLower":username}
|
||||||
else
|
else
|
||||||
return errors.badInput res, "You need to supply one of emailLower or username"
|
return errors.badInput res, "You need to supply one of emailLower or username"
|
||||||
|
|
||||||
User.findOne query, (err, user) ->
|
User.findOne query, (err, user) ->
|
||||||
if err? then return errors.serverError res, "There was an error finding the specified user"
|
if err? then return errors.serverError res, "There was an error finding the specified user"
|
||||||
|
|
||||||
unless user then return errors.badInput res, "The specified user couldn't be found"
|
unless user then return errors.badInput res, "The specified user couldn't be found"
|
||||||
|
|
||||||
req.logIn user, (err) ->
|
req.logIn user, (err) ->
|
||||||
if err? then return errors.serverError res, "There was an error logging in with the specified"
|
if err? then return errors.serverError res, "There was an error logging in with the specified"
|
||||||
res.send(UserHandler.formatEntity(req, user))
|
res.send(UserHandler.formatEntity(req, user))
|
||||||
return res.end()
|
return res.end()
|
||||||
else
|
else
|
||||||
return errors.unauthorized res, "You must be an admin to enter espionage mode"
|
return errors.unauthorized res, "You must be an admin to enter espionage mode"
|
||||||
|
|
||||||
app.post('/auth/login', (req, res, next) ->
|
app.post('/auth/login', (req, res, next) ->
|
||||||
authentication.authenticate('local', (err, user, info) ->
|
authentication.authenticate('local', (err, user, info) ->
|
||||||
return next(err) if err
|
return next(err) if err
|
||||||
|
@ -87,11 +88,11 @@ module.exports.setup = (app) ->
|
||||||
user.save((err) ->
|
user.save((err) ->
|
||||||
if err
|
if err
|
||||||
return @sendDatabaseError(res, err)
|
return @sendDatabaseError(res, err)
|
||||||
|
|
||||||
req.logIn(user, (err) ->
|
req.logIn(user, (err) ->
|
||||||
if err
|
if err
|
||||||
return @sendDatabaseError(res, err)
|
return @sendDatabaseError(res, err)
|
||||||
|
|
||||||
if send
|
if send
|
||||||
return @sendSuccess(res, user)
|
return @sendSuccess(res, user)
|
||||||
next() if next
|
next() if next
|
||||||
|
@ -110,7 +111,7 @@ module.exports.setup = (app) ->
|
||||||
User.findOne({emailLower:req.body.email.toLowerCase()}).exec((err, user) ->
|
User.findOne({emailLower:req.body.email.toLowerCase()}).exec((err, user) ->
|
||||||
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', Math.random().toString(36).slice(2,7).toUpperCase())
|
||||||
user.save (err) =>
|
user.save (err) =>
|
||||||
return errors.serverError(res) if err
|
return errors.serverError(res) if err
|
||||||
|
@ -127,12 +128,22 @@ module.exports.setup = (app) ->
|
||||||
return res.end()
|
return res.end()
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
app.get '/auth/unsubscribe', (req, res) ->
|
app.get '/auth/unsubscribe', (req, res) ->
|
||||||
email = req.query.email
|
email = req.query.email
|
||||||
unless req.query.email
|
unless req.query.email
|
||||||
return errors.badInput res, 'No email provided to unsubscribe.'
|
return errors.badInput res, 'No email provided to unsubscribe.'
|
||||||
|
|
||||||
|
if req.query.session
|
||||||
|
# Unsubscribe from just one session's notifications instead.
|
||||||
|
return LevelSession.findOne({_id: req.query.session}).exec (err, session) ->
|
||||||
|
return errors.serverError res, 'Could not unsubscribe: #{req.query.session}, #{req.query.email}: #{err}' if err
|
||||||
|
session.set 'unsubscribed', true
|
||||||
|
session.save (err) ->
|
||||||
|
return errors.serverError res, 'Database failure.' if err
|
||||||
|
res.send "Unsubscribed #{req.query.email} from CodeCombat emails for #{session.levelName} #{session.team} ladder updates. Sorry to see you go! <p><a href='/play/ladder/#{session.levelID}#my-matches'>Ladder preferences</a></p>"
|
||||||
|
res.end()
|
||||||
|
|
||||||
User.findOne({emailLower:req.query.email.toLowerCase()}).exec (err, user) ->
|
User.findOne({emailLower:req.query.email.toLowerCase()}).exec (err, user) ->
|
||||||
if not user
|
if not user
|
||||||
return errors.notFound res, "No user found with email '#{req.query.email}'"
|
return errors.notFound res, "No user found with email '#{req.query.email}'"
|
||||||
|
@ -152,4 +163,4 @@ createMailOptions = (receiver, password) ->
|
||||||
replyTo: config.mail.username
|
replyTo: config.mail.username
|
||||||
subject: "[CodeCombat] Password Reset"
|
subject: "[CodeCombat] Password Reset"
|
||||||
text: "You can log into your account with: #{password}"
|
text: "You can log into your account with: #{password}"
|
||||||
#
|
#
|
||||||
|
|
|
@ -42,11 +42,11 @@ handleLadderUpdate = (req, res) ->
|
||||||
for daysAgo in emailDays
|
for daysAgo in emailDays
|
||||||
# Get every session that was submitted in a 5-minute window after the time.
|
# Get every session that was submitted in a 5-minute window after the time.
|
||||||
startTime = getTimeFromDaysAgo daysAgo
|
startTime = getTimeFromDaysAgo daysAgo
|
||||||
#endTime = startTime + 5 * 60 * 1000
|
endTime = startTime + 5 * 60 * 1000
|
||||||
endTime = startTime + 1 * 60 * 60 * 1000 # Debugging: make sure there's something to send
|
#endTime = startTime + 1.5 * 60 * 60 * 1000 # Debugging: make sure there's something to send
|
||||||
findParameters = {submitted: true, submitDate: {$gt: new Date(startTime), $lte: new Date(endTime)}}
|
findParameters = {submitted: true, submitDate: {$gt: new Date(startTime), $lte: new Date(endTime)}}
|
||||||
# TODO: think about putting screenshots in the email
|
# TODO: think about putting screenshots in the email
|
||||||
selectString = "creator team levelName levelID totalScore matches submitted submitDate numberOfWinsAndTies numberOfLosses"
|
selectString = "creator team levelName levelID totalScore matches submitted submitDate scoreHistory"
|
||||||
query = LevelSession.find(findParameters)
|
query = LevelSession.find(findParameters)
|
||||||
.select(selectString)
|
.select(selectString)
|
||||||
.lean()
|
.lean()
|
||||||
|
@ -63,41 +63,49 @@ sendLadderUpdateEmail = (session, daysAgo) ->
|
||||||
if err
|
if err
|
||||||
log.error "Couldn't find user for #{session.creator} from session #{session._id}"
|
log.error "Couldn't find user for #{session.creator} from session #{session._id}"
|
||||||
return
|
return
|
||||||
if not user.email or not ('notification' in user.emailSubscriptions)
|
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}"
|
log.info "Not sending email to #{user.email} #{user.name} because they only want emails about #{user.emailSubscriptions} - session unsubscribed: #{session.unsubscribed}"
|
||||||
return
|
return
|
||||||
name = if user.firstName and user.lastName then "#{user.firstName} #{user.lastName}" else user.name
|
unless session.levelName
|
||||||
|
log.info "Not sending email to #{user.email} #{user.name} because the session had no levelName in it."
|
||||||
|
return
|
||||||
|
name = if user.firstName and user.lastName then "#{user.firstName}" else user.name
|
||||||
name = "Wizard" if not name or name is "Anoner"
|
name = "Wizard" if not name or name is "Anoner"
|
||||||
|
|
||||||
|
# Fetch the most recent defeat and victory, if there are any.
|
||||||
|
# (We could look at strongest/weakest, but we'd have to fetch everyone, or denormalize more.)
|
||||||
|
matches = _.filter session.matches, (match) -> match.date >= (new Date() - 86400 * 1000 * daysAgo)
|
||||||
|
defeats = _.filter matches, (match) -> match.metrics.rank is 1 and match.opponents[0].metrics.rank is 0
|
||||||
|
victories = _.filter matches, (match) -> match.metrics.rank is 0
|
||||||
|
defeat = _.last defeats
|
||||||
|
victory = _.last victories
|
||||||
|
|
||||||
sendEmail = (defeatContext, victoryContext) ->
|
sendEmail = (defeatContext, victoryContext) ->
|
||||||
# TODO: do something with the preferredLanguage?
|
# TODO: do something with the preferredLanguage?
|
||||||
context =
|
context =
|
||||||
email_id: sendwithus.templates.ladder_update_email
|
email_id: sendwithus.templates.ladder_update_email
|
||||||
recipient:
|
recipient:
|
||||||
#address: user.email
|
address: user.email
|
||||||
address: 'nick@codecombat.com' # Debugging
|
#address: 'nick@codecombat.com' # Debugging
|
||||||
name: name
|
name: name
|
||||||
email_data:
|
email_data:
|
||||||
name: name
|
name: name
|
||||||
days_ago: daysAgo
|
days_ago: daysAgo
|
||||||
wins: session.numberOfWinsAndTies
|
wins: victories.length
|
||||||
losses: session.numberOfLosses
|
losses: defeats.length
|
||||||
total_score: Math.round(session.totalScore * 100)
|
total_score: Math.round(session.totalScore * 100)
|
||||||
team: session.team
|
team: session.team
|
||||||
|
team_name: session.team[0].toUpperCase() + session.team.substr(1)
|
||||||
level_name: session.levelName
|
level_name: session.levelName
|
||||||
|
session_id: session._id
|
||||||
ladder_url: "http://codecombat.com/play/ladder/#{session.levelID}#my-matches"
|
ladder_url: "http://codecombat.com/play/ladder/#{session.levelID}#my-matches"
|
||||||
|
score_history_graph_url: getScoreHistoryGraphURL session, daysAgo
|
||||||
defeat: defeatContext
|
defeat: defeatContext
|
||||||
victory: victoryContext
|
victory: victoryContext
|
||||||
log.info "Sending ladder update email to #{context.recipient.address} with #{context.email_data.wins} wins and #{context.email_data.losses} since #{daysAgo} day(s) ago."
|
log.info "Sending ladder update email to #{context.recipient.address} with #{context.email_data.wins} wins and #{context.email_data.losses} since #{daysAgo} day(s) ago."
|
||||||
sendwithus.api.send context, (err, result) ->
|
sendwithus.api.send context, (err, result) ->
|
||||||
log.error "Error sending ladder update email: #{err} with result #{result}" if err
|
log.error "Error sending ladder update email: #{err} with result #{result}" if err
|
||||||
|
|
||||||
# Fetch the most recent defeat and victory, if there are any.
|
|
||||||
# (We could look at strongest/weakest, but we'd have to fetch everyone, or denormalize more.)
|
|
||||||
defeats = _.filter session.matches, (match) -> match.metrics.rank is 1 and match.opponents[0].metrics.rank is 0
|
|
||||||
victories = _.filter session.matches, (match) -> match.metrics.rank is 0
|
|
||||||
defeat = _.last defeats
|
|
||||||
victory = _.last victories
|
|
||||||
urlForMatch = (match) ->
|
urlForMatch = (match) ->
|
||||||
"http://codecombat.com/play/level/#{session.levelID}?team=#{session.team}&session=#{session._id}&opponent=#{match.opponents[0].sessionID}"
|
"http://codecombat.com/play/level/#{session.levelID}?team=#{session.team}&session=#{session._id}&opponent=#{match.opponents[0].sessionID}"
|
||||||
|
|
||||||
|
@ -124,6 +132,20 @@ sendLadderUpdateEmail = (session, daysAgo) ->
|
||||||
else
|
else
|
||||||
onFetchedDefeatedOpponent null, null
|
onFetchedDefeatedOpponent null, null
|
||||||
|
|
||||||
|
getScoreHistoryGraphURL = (session, daysAgo) ->
|
||||||
|
# Totally duplicated in My Matches tab for now until we figure out what we're doing.
|
||||||
|
since = new Date() - 86400 * 1000 * daysAgo
|
||||||
|
scoreHistory = (s for s in session.scoreHistory ? [] when s[0] >= since)
|
||||||
|
return '' unless scoreHistory.length > 1
|
||||||
|
times = (s[0] for s in scoreHistory)
|
||||||
|
times = ((100 * (t - times[0]) / (times[times.length - 1] - times[0])).toFixed(1) for t in times)
|
||||||
|
scores = (s[1] for s in scoreHistory)
|
||||||
|
lowest = _.min scores
|
||||||
|
highest = _.max scores
|
||||||
|
scores = (Math.round(100 * (s - lowest) / (highest - lowest)) for s in scores)
|
||||||
|
currentScore = Math.round scoreHistory[scoreHistory.length - 1][1] * 100
|
||||||
|
chartData = times.join(',') + '|' + scores.join(',')
|
||||||
|
"https://chart.googleapis.com/chart?chs=600x75&cht=lxy&chtt=Score%3A+#{currentScore}&chts=222222,12,r&chf=a,s,000000FF&chls=2&chd=t:#{chartData}"
|
||||||
|
|
||||||
handleMailchimpWebHook = (req, res) ->
|
handleMailchimpWebHook = (req, res) ->
|
||||||
post = req.body
|
post = req.body
|
||||||
|
|
Loading…
Reference in a new issue