From 5e634f9915bfc980a59bcd4d824ef45f37ca0448 Mon Sep 17 00:00:00 2001 From: Darredevil Date: Mon, 10 Mar 2014 04:10:29 +0200 Subject: [PATCH 1/6] Update ro.coffee Almost done. The legal stuff is the most boring thing i ever had to translate... --- app/locale/ro.coffee | 140 +++++++++++++++++++++---------------------- 1 file changed, 70 insertions(+), 70 deletions(-) diff --git a/app/locale/ro.coffee b/app/locale/ro.coffee index e9d7e3db6..531e3825f 100644 --- a/app/locale/ro.coffee +++ b/app/locale/ro.coffee @@ -265,7 +265,7 @@ module.exports = nativeDescription: "limba română", englishDescription: "Roman message: "Mesaj" about: - who_is_codecombat: "Cine este CodeCombat?" # I assume you meant (what) + who_is_codecombat: "Cine este CodeCombat?" why_codecombat: "De ce CodeCombat?" 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." @@ -277,77 +277,77 @@ module.exports = nativeDescription: "limba română", englishDescription: "Roman why_paragraph_3_center: "ci" 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_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_ending: "And hey, it's free. " -# why_ending_url: "Start wizarding now!" -# george_description: "CEO, business guy, web designer, game designer, and champion of beginning programmers everywhere." -# scott_description: "Programmer extraordinaire, software architect, kitchen wizard, and master of finances. Scott is the reasonable one." -# nick_description: "Programming wizard, eccentric motivation mage, and upside-down experimenter. Nick can do anything and chooses to build CodeCombat." -# jeremy_description: "Customer support mage, usability tester, and community organizer; you've probably already spoken with Jeremy." -# michael_description: "Programmer, sys-admin, and undergrad technical wunderkind, Michael is the person keeping our servers online." + 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: "Nu uita, este totul gratis. " + why_ending_url: "Devino un vrăjitor acum!" + george_description: "CEO, business guy, web designer, game designer, și campion al programatorilor începători." + 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 poate să facă orice si a ales să dezvolte CodeCombat." + 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 este cel care ține serverele in picioare." -# legal: -# page_title: "Legal" -# opensource_intro: "CodeCombat is free to play and completely open source." -# opensource_description_prefix: "Check out " -# github_url: "our GitHub" -# opensource_description_center: "and help out if you like! CodeCombat is built on dozens of open source projects, and we love them. See " -# archmage_wiki_url: "our Archmage wiki" -# opensource_description_suffix: "for a list of the software that makes this game possible." -# practices_title: "Respectful Best Practices" -# practices_description: "These are our promises to you, the player, in slightly less legalese." -# privacy_title: "Privacy" -# 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." -# security_title: "Security" -# 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." -# email_title: "Email" -# email_description_prefix: "We will not inundate you with spam. Through" -# email_settings_url: "your email settings" -# email_description_suffix: "or through links in the emails we send, you can change your preferences and easily unsubscribe at any time." -# 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:" -# recruitment_title: "Recruitment" -# recruitment_description_prefix: "Here on CodeCombat, you're going to become a powerful wizard–not just in the game, but also in real life." -# url_hire_programmers: "No one can hire programmers fast enough" -# 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_italic: "a lot" -# recruitment_description_ending: "the site remains free and everybody's happy. That's the plan." -# copyrights_title: "Copyrights and Licenses" -# contributor_title: "Contributor License Agreement" -# contributor_description_prefix: "All contributions, both on the site and on our GitHub repository, are subject to our" -# cla_url: "CLA" -# contributor_description_suffix: "to which you should agree before contributing." -# 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" -# 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." -# art_title: "Art/Music - Creative Commons " -# art_description_prefix: "All common content is available under the" -# 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_music: "Music" -# art_sound: "Sound" -# art_artwork: "Artwork" -# art_sprites: "Sprites" -# art_other: "Any and all other non-code creative works that are made available when creating Levels." -# 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_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:" -# use_list_1: "If used in a movie or another game, include codecombat.com in the credits." -# 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." -# 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." -# rights_title: "Rights Reserved" -# rights_desc: "All rights are reserved for Levels themselves. This includes" -# rights_scripts: "Scripts" -# rights_unit: "Unit configuration" -# rights_description: "Description" -# rights_writings: "Writings" -# rights_media: "Media (sounds, music) and any other creative content made specifically for that Level and not made generally available when creating Levels." -# 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." -# nutshell_title: "In a Nutshell" -# 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." -# canonical: "The English version of this document is the definitive, canonical version. If there are any discrepencies between translations, the English document takes precedence." + legal: + page_title: "Aspecte Legale" + opensource_intro: "CodeCombat este free-to-play și complet open source." + opensource_description_prefix: "Vizitează " + github_url: "pagina noastră de GitHub" + 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: "Archmage wiki" + opensource_description_suffix: "pentru o listă cu software-ul care face acest joc posibil." +# practices_title: "Respectful Best Practices" #not sure what you mean here? other word for /practices/? + practices_description: "Acestea sunt promisiunile noastre către tine, jucătorul, fără așa mulți termeni legali." + privacy_title: "Confidenţialitate şi termeni" + 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: "Securitate" + 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_description_prefix: "Noi nu vă vom inunda cu spam. Prin" + email_settings_url: "setările tale de email" + 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_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: "Recrutare" + 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: "Nimeni nu poate angaja programatori destul de rapid" + 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: "mult" + recruitment_description_ending: "site-ul rămâne gratis și toată lumea este fericită. Acesta este planul." + copyrights_title: "Drepturi de autor și licențe" + contributor_title: "Acord de licență Contributor" + contributor_description_prefix: "Toți contribuitorii, atât pe site cât și pe GitHub-ul nostru, sunt supuși la" + cla_url: "ALC" + contributor_description_suffix: "la care trebuie să fi de accord înainte să poți contribui." + code_title: "Code - MIT" + 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" + 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ă/Muzică - Conținut Comun " + art_description_prefix: "Tot conținutul creativ/artistic este valabil sub" + cc_license_url: "Creative Commons Attribution 4.0 International License" + 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: "Muzică" + art_sound: "Sunet" + art_artwork: "Artwork" + art_sprites: "Sprites" #can t be translated, either suggest alternative name or must be left like this + art_other: "Orice si toate celelalte creații non-cod care sunt disponibile când se crează nivele." + 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: "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: "Dacă este folosit într-un film sau alt joc, includeți codecombat.com la credite." + 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: "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: "Drepturi rezervate" + rights_desc: "Toate drepturile sunt rezervate pentru Nivele în sine. Asta include" + rights_scripts: "Script-uri" + rights_unit: "Configurații de unități" + rights_description: "Descriere" + rights_writings: "Scrieri" + 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: "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: "Pe scurt" + 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." -# contribute: + contribute: # page_title: "Contributing" # character_classes_title: "Character Classes" # introduction_desc_intro: "We have high hopes for CodeCombat." From c88c973fa714b14c51764e5669ea2c95c2aad34d Mon Sep 17 00:00:00 2001 From: Michael Schmatz Date: Mon, 10 Mar 2014 14:18:34 -0700 Subject: [PATCH 2/6] Added 2.6.0-rc1 to allowed Mongo versions --- bin/coco-mongodb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/coco-mongodb b/bin/coco-mongodb index be4f285ed..4ff889493 100755 --- a/bin/coco-mongodb +++ b/bin/coco-mongodb @@ -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])) -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): mongo_executable = "mongod" else: From 6b48577e56aed5eb3a71f6f80b781d9522c6a827 Mon Sep 17 00:00:00 2001 From: Darredevil Date: Tue, 11 Mar 2014 01:03:20 +0200 Subject: [PATCH 3/6] Update ro.coffee --- app/locale/ro.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/locale/ro.coffee b/app/locale/ro.coffee index 531e3825f..5038a79d8 100644 --- a/app/locale/ro.coffee +++ b/app/locale/ro.coffee @@ -347,7 +347,7 @@ module.exports = nativeDescription: "limba română", englishDescription: "Roman 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." - contribute: +# contribute: # page_title: "Contributing" # character_classes_title: "Character Classes" # introduction_desc_intro: "We have high hopes for CodeCombat." From 6ce65488aa727899336cff7585a893b37cee97f5 Mon Sep 17 00:00:00 2001 From: Nick Winter Date: Mon, 10 Mar 2014 20:22:25 -0700 Subject: [PATCH 4/6] Added simple rank history graph. Improving ladder update mail. --- app/templates/play/ladder/my_matches_tab.jade | 4 ++++ app/views/play/ladder/my_matches_tab.coffee | 9 +++++++++ app/views/play/ladder_view.coffee | 2 +- server/commons/Handler.coffee | 4 +++- server/routes/mail.coffee | 10 +++++++--- 5 files changed, 24 insertions(+), 5 deletions(-) diff --git a/app/templates/play/ladder/my_matches_tab.jade b/app/templates/play/ladder/my_matches_tab.jade index c70192221..e9bc0dd21 100644 --- a/app/templates/play/ladder/my_matches_tab.jade +++ b/app/templates/play/ladder/my_matches_tab.jade @@ -21,6 +21,10 @@ div#columns.row span.ranked.hidden Submitted for Ranking span.failed.hidden Failed to Rank + tr + th(colspan=4, style="color: #{team.primaryColor}") + img(src="https://chart.googleapis.com/chart?chs=450x125&cht=lxy&chco=#{team.chartColor}&chtt=Score+History&chts=#{team.chartColor},16,c&chf=a,s,000000FF&chls=2&chm=o,#{team.chartColor},0,4&chd=t:#{team.chartData}") + tr th Result th Opponent diff --git a/app/views/play/ladder/my_matches_tab.coffee b/app/views/play/ladder/my_matches_tab.coffee index a094aef82..b7708c8df 100644 --- a/app/views/play/ladder/my_matches_tab.coffee +++ b/app/views/play/ladder/my_matches_tab.coffee @@ -76,6 +76,15 @@ module.exports = class MyMatchesTabView extends CocoView team.wins = _.filter(team.matches, {state: 'win'}).length team.ties = _.filter(team.matches, {state: 'tie'}).length team.losses = _.filter(team.matches, {state: 'loss'}).length + team.scoreHistory = team.session.get('scoreHistory') + team.chartColor = team.primaryColor.replace '#', '' + times = (s[0] for s in team.scoreHistory) + times = (100 * (t - times[0]) / (times[times.length - 1] - times[0]) for t in times) + scores = (s[1] for s in team.scoreHistory) + lowest = _.min scores + highest = _.max scores + scores = (100 * (s - lowest) / highest for s in scores) + team.chartData = times.join(',') + '|' + scores.join(',') ctx diff --git a/app/views/play/ladder_view.coffee b/app/views/play/ladder_view.coffee index 11283af13..b7cf49599 100644 --- a/app/views/play/ladder_view.coffee +++ b/app/views/play/ladder_view.coffee @@ -65,7 +65,7 @@ module.exports = class LadderView extends RootView return if @startsLoading @insertSubView(@ladderTab = new LadderTabView({}, @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 if hash and not (hash in ['my-matches', 'simulate', 'ladder']) @showPlayModal(hash) if @sessions.loaded diff --git a/server/commons/Handler.coffee b/server/commons/Handler.coffee index ada7acad3..4460b2d22 100644 --- a/server/commons/Handler.coffee +++ b/server/commons/Handler.coffee @@ -123,7 +123,9 @@ module.exports = class Handler # Keeping it simple for now and just allowing access to the first FETCH_LIMIT results. query = {'original': mongoose.Types.ObjectId(id)} 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 return @sendUnauthorizedError(res) unless @hasAccessToDocument(req, doc) res.send(results) diff --git a/server/routes/mail.coffee b/server/routes/mail.coffee index 4ef488b67..5ed1fad22 100644 --- a/server/routes/mail.coffee +++ b/server/routes/mail.coffee @@ -42,8 +42,8 @@ handleLadderUpdate = (req, res) -> for daysAgo in emailDays # Get every session that was submitted in a 5-minute window after the time. startTime = getTimeFromDaysAgo daysAgo - #endTime = startTime + 5 * 60 * 1000 - endTime = startTime + 1 * 60 * 60 * 1000 # Debugging: make sure there's something to send + endTime = startTime + 5 * 60 * 1000 + #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)}} # TODO: think about putting screenshots in the email selectString = "creator team levelName levelID totalScore matches submitted submitDate numberOfWinsAndTies numberOfLosses" @@ -63,9 +63,12 @@ sendLadderUpdateEmail = (session, daysAgo) -> if err log.error "Couldn't find user for #{session.creator} from session #{session._id}" return - if not user.email or not ('notification' in user.emailSubscriptions) + unless user.email and ('notification' in user.emailSubscriptions) log.info "Not sending email to #{user.email} #{user.name} because they only want emails about #{user.emailSubscriptions}" return + 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} #{user.lastName}" else user.name name = "Wizard" if not name or name is "Anoner" @@ -84,6 +87,7 @@ sendLadderUpdateEmail = (session, daysAgo) -> losses: session.numberOfLosses total_score: Math.round(session.totalScore * 100) team: session.team + team_name: session.team[0].toUpperCase() + session.team.substr(1) level_name: session.levelName ladder_url: "http://codecombat.com/play/ladder/#{session.levelID}#my-matches" defeat: defeatContext From 32baf2ae7988685baeb3585a5a63b5dc1b186fa6 Mon Sep 17 00:00:00 2001 From: Nick Winter Date: Mon, 10 Mar 2014 21:30:46 -0700 Subject: [PATCH 5/6] Improved my-matches rank history and name fetching, ladder update emails, ladder update unsubscribes. --- app/templates/play/ladder/my_matches_tab.jade | 7 ++-- app/views/play/ladder/my_matches_tab.coffee | 21 ++++++----- server/routes/auth.coffee | 37 ++++++++++++------- server/routes/mail.coffee | 29 ++++++++------- 4 files changed, 55 insertions(+), 39 deletions(-) diff --git a/app/templates/play/ladder/my_matches_tab.jade b/app/templates/play/ladder/my_matches_tab.jade index e9bc0dd21..f40b4a04d 100644 --- a/app/templates/play/ladder/my_matches_tab.jade +++ b/app/templates/play/ladder/my_matches_tab.jade @@ -21,9 +21,10 @@ div#columns.row span.ranked.hidden Submitted for Ranking span.failed.hidden Failed to Rank - tr - th(colspan=4, style="color: #{team.primaryColor}") - img(src="https://chart.googleapis.com/chart?chs=450x125&cht=lxy&chco=#{team.chartColor}&chtt=Score+History&chts=#{team.chartColor},16,c&chf=a,s,000000FF&chls=2&chm=o,#{team.chartColor},0,4&chd=t:#{team.chartData}") + 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+History&chts=#{team.chartColor},16,c&chf=a,s,000000FF&chls=2&chm=o,#{team.chartColor},0,4&chd=t:#{team.chartData}") tr th Result diff --git a/app/views/play/ladder/my_matches_tab.coffee b/app/views/play/ladder/my_matches_tab.coffee index b7708c8df..24df7fbf1 100644 --- a/app/views/play/ladder/my_matches_tab.coffee +++ b/app/views/play/ladder/my_matches_tab.coffee @@ -35,7 +35,7 @@ module.exports = class MyMatchesTabView extends CocoView for session in @sessions.models for match in session.get('matches') or [] opponent = match.opponents[0] - @nameMap[opponent.userID] = nameMap[opponent.userID] + @nameMap[opponent.userID] ?= nameMap[opponent.userID] @finishRendering() $.ajax('/db/user/-/names', { @@ -76,15 +76,16 @@ module.exports = class MyMatchesTabView extends CocoView team.wins = _.filter(team.matches, {state: 'win'}).length team.ties = _.filter(team.matches, {state: 'tie'}).length team.losses = _.filter(team.matches, {state: 'loss'}).length - team.scoreHistory = team.session.get('scoreHistory') - team.chartColor = team.primaryColor.replace '#', '' - times = (s[0] for s in team.scoreHistory) - times = (100 * (t - times[0]) / (times[times.length - 1] - times[0]) for t in times) - scores = (s[1] for s in team.scoreHistory) - lowest = _.min scores - highest = _.max scores - scores = (100 * (s - lowest) / highest for s in scores) - team.chartData = times.join(',') + '|' + scores.join(',') + team.scoreHistory = team.session?.get('scoreHistory') + if team.scoreHistory?.length > 1 + 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 diff --git a/server/routes/auth.coffee b/server/routes/auth.coffee index 2e6dbf72d..c845b28c2 100644 --- a/server/routes/auth.coffee +++ b/server/routes/auth.coffee @@ -2,6 +2,7 @@ authentication = require('passport') LocalStrategy = require('passport-local').Strategy User = require('../users/User') UserHandler = require('../users/user_handler') +LevelSession = require '../levels/sessions/LevelSession' config = require '../../server_config' errors = require '../commons/errors' mail = require '../commons/mail' @@ -21,16 +22,16 @@ module.exports.setup = (app) -> if passwordReset and password.toLowerCase() is passwordReset User.update {_id: user.get('_id')}, {passwordReset: ''}, {}, -> return done(null, user) - + hash = User.hashPassword(password) 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) ) )) app.post '/auth/spy', (req, res, next) -> if req?.user?.isAdmin() - + username = req.body.usernameLower emailLower = req.body.emailLower if emailLower @@ -39,19 +40,19 @@ module.exports.setup = (app) -> query = {"nameLower":username} else return errors.badInput res, "You need to supply one of emailLower or username" - + User.findOne query, (err, 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" - + req.logIn user, (err) -> if err? then return errors.serverError res, "There was an error logging in with the specified" res.send(UserHandler.formatEntity(req, user)) return res.end() else return errors.unauthorized res, "You must be an admin to enter espionage mode" - + app.post('/auth/login', (req, res, next) -> authentication.authenticate('local', (err, user, info) -> return next(err) if err @@ -87,11 +88,11 @@ module.exports.setup = (app) -> user.save((err) -> if err return @sendDatabaseError(res, err) - + req.logIn(user, (err) -> if err return @sendDatabaseError(res, err) - + if send return @sendSuccess(res, user) next() if next @@ -110,7 +111,7 @@ module.exports.setup = (app) -> User.findOne({emailLower:req.body.email.toLowerCase()}).exec((err, user) -> if not user return errors.notFound(res, [{message:'not found.', property:'email'}]) - + user.set('passwordReset', Math.random().toString(36).slice(2,7).toUpperCase()) user.save (err) => return errors.serverError(res) if err @@ -127,12 +128,22 @@ module.exports.setup = (app) -> return res.end() ) ) - + app.get '/auth/unsubscribe', (req, res) -> email = req.query.email unless req.query.email 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!

Ladder preferences

" + res.end() + User.findOne({emailLower:req.query.email.toLowerCase()}).exec (err, user) -> if not user return errors.notFound res, "No user found with email '#{req.query.email}'" @@ -152,4 +163,4 @@ createMailOptions = (receiver, password) -> replyTo: config.mail.username subject: "[CodeCombat] Password Reset" text: "You can log into your account with: #{password}" -# \ No newline at end of file +# diff --git a/server/routes/mail.coffee b/server/routes/mail.coffee index 5ed1fad22..a9430115b 100644 --- a/server/routes/mail.coffee +++ b/server/routes/mail.coffee @@ -46,7 +46,7 @@ handleLadderUpdate = (req, res) -> #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)}} # 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" query = LevelSession.find(findParameters) .select(selectString) .lean() @@ -63,8 +63,8 @@ sendLadderUpdateEmail = (session, daysAgo) -> if err log.error "Couldn't find user for #{session.creator} from session #{session._id}" return - unless user.email and ('notification' in user.emailSubscriptions) - log.info "Not sending email to #{user.email} #{user.name} because they only want emails about #{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} - 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." @@ -72,23 +72,32 @@ sendLadderUpdateEmail = (session, daysAgo) -> name = if user.firstName and user.lastName then "#{user.firstName} #{user.lastName}" else user.name 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) -> # TODO: do something with the preferredLanguage? context = email_id: sendwithus.templates.ladder_update_email recipient: - #address: user.email - address: 'nick@codecombat.com' # Debugging + address: user.email + #address: 'nick@codecombat.com' # Debugging name: name email_data: name: name days_ago: daysAgo - wins: session.numberOfWinsAndTies - losses: session.numberOfLosses + wins: victories.length + losses: defeats.length total_score: Math.round(session.totalScore * 100) team: session.team team_name: session.team[0].toUpperCase() + session.team.substr(1) level_name: session.levelName + session_id: session._id ladder_url: "http://codecombat.com/play/ladder/#{session.levelID}#my-matches" defeat: defeatContext victory: victoryContext @@ -96,12 +105,6 @@ sendLadderUpdateEmail = (session, daysAgo) -> sendwithus.api.send context, (err, result) -> 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) -> "http://codecombat.com/play/level/#{session.levelID}?team=#{session.team}&session=#{session._id}&opponent=#{match.opponents[0].sessionID}" From eac219a2be8b7277d2985629d2abad769bfbdb1b Mon Sep 17 00:00:00 2001 From: Nick Winter Date: Mon, 10 Mar 2014 22:03:33 -0700 Subject: [PATCH 6/6] Inserted score history graph into ladder update emails. --- app/templates/play/ladder/my_matches_tab.jade | 2 +- app/views/play/ladder/my_matches_tab.coffee | 1 + server/routes/mail.coffee | 19 +++++++++++++++++-- 3 files changed, 19 insertions(+), 3 deletions(-) diff --git a/app/templates/play/ladder/my_matches_tab.jade b/app/templates/play/ladder/my_matches_tab.jade index f40b4a04d..0eb83dddb 100644 --- a/app/templates/play/ladder/my_matches_tab.jade +++ b/app/templates/play/ladder/my_matches_tab.jade @@ -24,7 +24,7 @@ div#columns.row 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+History&chts=#{team.chartColor},16,c&chf=a,s,000000FF&chls=2&chm=o,#{team.chartColor},0,4&chd=t:#{team.chartData}") + 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 th Result diff --git a/app/views/play/ladder/my_matches_tab.coffee b/app/views/play/ladder/my_matches_tab.coffee index 24df7fbf1..660114111 100644 --- a/app/views/play/ladder/my_matches_tab.coffee +++ b/app/views/play/ladder/my_matches_tab.coffee @@ -78,6 +78,7 @@ module.exports = class MyMatchesTabView extends CocoView 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) diff --git a/server/routes/mail.coffee b/server/routes/mail.coffee index a9430115b..5ad2dfcd4 100644 --- a/server/routes/mail.coffee +++ b/server/routes/mail.coffee @@ -46,7 +46,7 @@ handleLadderUpdate = (req, res) -> #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)}} # TODO: think about putting screenshots in the email - selectString = "creator team levelName levelID totalScore matches submitted submitDate" + selectString = "creator team levelName levelID totalScore matches submitted submitDate scoreHistory" query = LevelSession.find(findParameters) .select(selectString) .lean() @@ -69,7 +69,7 @@ sendLadderUpdateEmail = (session, daysAgo) -> 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} #{user.lastName}" else user.name + name = if user.firstName and user.lastName then "#{user.firstName}" else user.name name = "Wizard" if not name or name is "Anoner" # Fetch the most recent defeat and victory, if there are any. @@ -99,6 +99,7 @@ sendLadderUpdateEmail = (session, daysAgo) -> level_name: session.levelName session_id: session._id ladder_url: "http://codecombat.com/play/ladder/#{session.levelID}#my-matches" + score_history_graph_url: getScoreHistoryGraphURL session, daysAgo defeat: defeatContext 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." @@ -131,6 +132,20 @@ sendLadderUpdateEmail = (session, daysAgo) -> else 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) -> post = req.body