preliminary autocomplete. THIS IS NOT A WORKING STATUS. ONLY COMMIT TO PULL CHANGES

This commit is contained in:
Dominik Kundel 2014-06-13 16:14:31 +01:00
commit e756c83d9e
112 changed files with 3010 additions and 1162 deletions
.travis.yml
app
bin
config.coffee
scripts
server
server_setup.coffee
test

View file

@ -2,6 +2,12 @@ language: node_js
node_js: node_js:
- 0.10 - 0.10
before_install:
- "sudo apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv 7F0CEB10"
- "echo 'deb http://downloads-distro.mongodb.org/repo/ubuntu-upstart dist 10gen' | sudo tee /etc/apt/sources.list.d/mongodb.list"
- "sudo apt-get update"
- "sudo apt-get install mongodb-org-server"
before_script: before_script:
- "npm install" - "npm install"
- export DISPLAY=:99.0 - export DISPLAY=:99.0
@ -9,7 +15,11 @@ before_script:
- "./node_modules/.bin/bower install" - "./node_modules/.bin/bower install"
- "gem install sass" - "gem install sass"
- "./node_modules/.bin/brunch b" - "./node_modules/.bin/brunch b"
- "mkdir mongo"
- "mongod --dbpath=./mongo --fork --logpath ./mongodb.log"
- "node index.js --unittest &"
- "sleep 10" # to give node a chance to start
script: script:
- "./node_modules/jasmine-node/bin/jasmine-node test/server/ --coffee --captureExceptions"
- "./node_modules/karma/bin/karma start --browsers Firefox --single-run" - "./node_modules/karma/bin/karma start --browsers Firefox --single-run"

Binary file not shown.

After

(image error) Size: 3.1 KiB

Binary file not shown.

After

(image error) Size: 2.7 KiB

Binary file not shown.

After

(image error) Size: 2.5 KiB

Binary file not shown.

After

(image error) Size: 3.7 KiB

View file

@ -13,9 +13,6 @@ userPropsToSave =
module.exports = FacebookHandler = class FacebookHandler extends CocoClass module.exports = FacebookHandler = class FacebookHandler extends CocoClass
constructor: ->
super()
subscriptions: subscriptions:
'facebook-logged-in':'onFacebookLogin' 'facebook-logged-in':'onFacebookLogin'
'facebook-logged-out': 'onFacebookLogout' 'facebook-logged-out': 'onFacebookLogout'
@ -42,22 +39,18 @@ module.exports = FacebookHandler = class FacebookHandler extends CocoClass
return return
oldEmail = me.get('email') oldEmail = me.get('email')
patch = {} me.set('firstName', r.first_name) if r.first_name
patch.firstName = r.first_name if r.first_name me.set('lastName', r.last_name) if r.last_name
patch.lastName = r.last_name if r.last_name me.set('gender', r.gender) if r.gender
patch.gender = r.gender if r.gender me.set('email', r.email) if r.email
patch.email = r.email if r.email me.set('facebookID', r.id) if r.id
patch.facebookID = r.id if r.id
me.set(patch)
patch._id = me.id
Backbone.Mediator.publish('logging-in-with-facebook') Backbone.Mediator.publish('logging-in-with-facebook')
window.tracker?.trackEvent 'Facebook Login' window.tracker?.trackEvent 'Facebook Login'
window.tracker?.identify() window.tracker?.identify()
me.save(patch, { me.patch({
patch: true
error: backboneFailure, error: backboneFailure,
url: "/db/user?facebookID=#{r.id}&facebookAccessToken=#{@authResponse.accessToken}" url: "/db/user/#{me.id}?facebookID=#{r.id}&facebookAccessToken=#{@authResponse.accessToken}"
success: (model) -> success: (model) ->
window.location.reload() if model.get('email') isnt oldEmail window.location.reload() if model.get('email') isnt oldEmail
}) })

View file

@ -22,13 +22,13 @@ module.exports = class LevelBus extends Bus
'tome:spell-changed': 'onSpellChanged' 'tome:spell-changed': 'onSpellChanged'
'tome:spell-created': 'onSpellCreated' 'tome:spell-created': 'onSpellCreated'
'application:idle-changed': 'onIdleChanged' 'application:idle-changed': 'onIdleChanged'
constructor: -> constructor: ->
super(arguments...) super(arguments...)
@changedSessionProperties = {} @changedSessionProperties = {}
@saveSession = _.debounce(@saveSession, 1000, {maxWait: 5000}) @saveSession = _.debounce(@saveSession, 1000, {maxWait: 5000})
@playerIsIdle = false @playerIsIdle = false
init: -> init: ->
super() super()
@fireScriptsRef = @fireRef?.child('scripts') @fireScriptsRef = @fireRef?.child('scripts')
@ -36,7 +36,7 @@ module.exports = class LevelBus extends Bus
setSession: (@session) -> setSession: (@session) ->
@listenTo(@session, 'change:multiplayer', @onMultiplayerChanged) @listenTo(@session, 'change:multiplayer', @onMultiplayerChanged)
@timerIntervalID = setInterval(@incrementSessionPlaytime, 1000) @timerIntervalID = setInterval(@incrementSessionPlaytime, 1000)
onIdleChanged: (e) -> onIdleChanged: (e) ->
@playerIsIdle = e.idle @playerIsIdle = e.idle
@ -44,7 +44,7 @@ module.exports = class LevelBus extends Bus
if @playerIsIdle then return if @playerIsIdle then return
@changedSessionProperties.playtime = true @changedSessionProperties.playtime = true
@session.set("playtime",@session.get("playtime") + 1) @session.set("playtime",@session.get("playtime") + 1)
onPoint: -> onPoint: ->
return true unless @session?.get('multiplayer') return true unless @session?.get('multiplayer')
super() super()
@ -224,7 +224,7 @@ module.exports = class LevelBus extends Bus
saveSession: -> saveSession: ->
return if _.isEmpty @changedSessionProperties return if _.isEmpty @changedSessionProperties
# don't let peaking admins mess with the session accidentally # don't let peeking admins mess with the session accidentally
return unless @session.get('multiplayer') or @session.get('creator') is me.id return unless @session.get('multiplayer') or @session.get('creator') is me.id
Backbone.Mediator.publish 'level:session-will-save', session: @session Backbone.Mediator.publish 'level:session-will-save', session: @session
patch = {} patch = {}

View file

@ -18,6 +18,7 @@ doQuerySelector = (value, operatorObj) ->
when '$ne' then return false if mapred value, body, (l, r) -> l == r when '$ne' then return false if mapred value, body, (l, r) -> l == r
when '$in' then return false unless _.reduce value, ((result, val) -> result or val in body), false when '$in' then return false unless _.reduce value, ((result, val) -> result or val in body), false
when '$nin' then return false if _.reduce value, ((result, val) -> result or val in body), false when '$nin' then return false if _.reduce value, ((result, val) -> result or val in body), false
when '$exists' then return false if value[0]? isnt body[0]
else return false else return false
true true
@ -34,11 +35,13 @@ matchesQuery = (target, queryObj) ->
pieces = prop.split('.') pieces = prop.split('.')
obj = target obj = target
for piece in pieces for piece in pieces
return false unless piece of obj unless piece of obj
obj = null
break
obj = obj[piece] obj = obj[piece]
if typeof query != 'object' or _.isArray query if typeof query != 'object' or _.isArray query
return false unless obj == query or (query in obj if _.isArray obj) return false unless obj == query or (query in obj if _.isArray obj)
else return false unless doQuerySelector obj, query else return false unless doQuerySelector obj, query
true true
LocalMongo.matchesQuery = matchesQuery LocalMongo.matchesQuery = matchesQuery

View file

@ -10,7 +10,7 @@ init = ->
if me and not me.get('testGroupNumber')? if me and not me.get('testGroupNumber')?
# Assign testGroupNumber to returning visitors; new ones in server/routes/auth # Assign testGroupNumber to returning visitors; new ones in server/routes/auth
me.set 'testGroupNumber', Math.floor(Math.random() * 256) me.set 'testGroupNumber', Math.floor(Math.random() * 256)
me.save() me.patch()
Backbone.listenTo(me, 'sync', Backbone.Mediator.publish('me:synced', {me:me})) Backbone.listenTo(me, 'sync', Backbone.Mediator.publish('me:synced', {me:me}))

View file

@ -75,3 +75,25 @@ module.exports.getByPath = (target, path) ->
return undefined unless piece of obj return undefined unless piece of obj
obj = obj[piece] obj = obj[piece]
obj obj
module.exports.round = _.curry (digits, n) ->
n = +n.toFixed(digits)
positify = (func) -> (x) -> if x > 0 then func(x) else 0
# f(x) = ax + b
createLinearFunc = (params) ->
(x) -> (params.a or 1) * x + (params.b or 0)
# f(x) = ax² + bx + c
createQuadraticFunc = (params) ->
(x) -> (params.a or 1) * x * x + (params.b or 1) * x + (params.c or 0)
# f(x) = a log(b (x + c)) + d
createLogFunc = (params) ->
(x) -> if x > 0 then (params.a or 1) * Math.log((params.b or 1) * (x + (params.c or 0))) + (params.d or 0) else 0
module.exports.functionCreators =
linear: positify(createLinearFunc)
quadratic: positify(createQuadraticFunc)
logarithmic: positify(createLogFunc)

View file

@ -1,5 +1,6 @@
module.exports.thangNames = thangNames = module.exports.thangNames = thangNames =
"Soldier M": [ "Soldier M": [
"Duke"
"William" "William"
"Lucas" "Lucas"
"Marcus" "Marcus"
@ -66,6 +67,7 @@ module.exports.thangNames = thangNames =
"Coco" "Coco"
"Buffy" "Buffy"
"Allankrita" "Allankrita"
"Kay"
] ]
"Peasant M": [ "Peasant M": [
"Yorik" "Yorik"
@ -355,6 +357,8 @@ module.exports.thangNames = thangNames =
"Hank" "Hank"
"Jeph" "Jeph"
"Neville" "Neville"
"Alphonse"
"Edward"
] ]
"Captain": [ "Captain": [
"Anya" "Anya"
@ -367,4 +371,5 @@ module.exports.thangNames = thangNames =
"Jane" "Jane"
"Lia" "Lia"
"Hardcastle" "Hardcastle"
"Leona"
] ]

View file

@ -198,8 +198,8 @@
done_editing: "Done Editing" done_editing: "Done Editing"
profile_for_prefix: "Profile for " profile_for_prefix: "Profile for "
profile_for_suffix: "" profile_for_suffix: ""
approved: "Approved" featured: "Featured"
not_approved: "Not Approved" not_featured: "Not Featured"
looking_for: "Looking for:" looking_for: "Looking for:"
last_updated: "Last updated:" last_updated: "Last updated:"
contact: "Contact" contact: "Contact"
@ -294,14 +294,25 @@
project_picture_help: "Upload a 230x115px or larger image showing off the project." project_picture_help: "Upload a 230x115px or larger image showing off the project."
project_link: "Link" project_link: "Link"
project_link_help: "Link to the project." project_link_help: "Link to the project."
player_code: "Player Code"
employers: employers:
want_to_hire_our_players: "Want to hire expert CodeCombat players?" want_to_hire_our_players: "Hire CodeCombat Players"
what: "What is CodeCombat?"
what_blurb: "CodeCombat is a multiplayer browser programming game. Players write code to control their forces in battle against other developers. We support JavaScript, Python, Lua, Clojure, CoffeeScript, and Io."
who: "Who Are the Players?"
who_blurb: "CodeCombateers are software developers who enjoy using their programming skills to play games. They range from college seniors at top 20 engineering programs to 20-year industry veterans."
how: "How Do We Find Developers?"
how_blurb: "We host competitive tournaments to attract competitive software engieneers. We then use in-house algorithms to identify the best players among the top 5% of tournament winners."
why: "Why Hire Through Us?"
why_blurb_1: "We will save you time. Every CodeCombateer we feaure is "
why_blurb_2: "looking for work"
why_blurb_3: ", has "
why_blurb_4: "demonstrated top notch technical skills"
why_blurb_5: ", and has been "
why_blurb_6: "personally screened by us"
why_blurb_7: ". Stop screening and start hiring."
see_candidates: "Click here to see our candidates" see_candidates: "Click here to see our candidates"
candidates_count_prefix: "We currently have "
candidates_count_many: "many"
candidates_count_suffix: "highly skilled and vetted developers looking for work."
candidate_name: "Name" candidate_name: "Name"
candidate_location: "Location" candidate_location: "Location"
candidate_looking_for: "Looking For" candidate_looking_for: "Looking For"
@ -309,8 +320,9 @@
candidate_top_skills: "Top Skills" candidate_top_skills: "Top Skills"
candidate_years_experience: "Yrs Exp" candidate_years_experience: "Yrs Exp"
candidate_last_updated: "Last Updated" candidate_last_updated: "Last Updated"
candidate_approved: "Us?" featured_developers: "Featured Developers"
candidate_active: "Them?" other_developers: "Other Developers"
inactive_developers: "Inactive Developers"
play_level: play_level:
done: "Done" done: "Done"
@ -436,6 +448,7 @@
av_entities_sub_title: "Entities" av_entities_sub_title: "Entities"
av_entities_users_url: "Users" av_entities_users_url: "Users"
av_entities_active_instances_url: "Active Instances" av_entities_active_instances_url: "Active Instances"
av_entities_employer_list_url: "Employer List"
av_other_sub_title: "Other" av_other_sub_title: "Other"
av_other_debug_base_url: "Base (for debugging base.jade)" av_other_debug_base_url: "Base (for debugging base.jade)"
u_title: "User List" u_title: "User List"
@ -505,7 +518,7 @@
new_thang_title_login: "Log In to Create a New Thang Type" new_thang_title_login: "Log In to Create a New Thang Type"
new_level_title_login: "Log In to Create a New Level" new_level_title_login: "Log In to Create a New Level"
new_achievement_title: "Create a New Achievement" new_achievement_title: "Create a New Achievement"
new_achievement_title_login: "Sign Up to Create a New Achievement" new_achievement_title_login: "Log In to Create a New Achievement"
article_search_title: "Search Articles Here" article_search_title: "Search Articles Here"
thang_search_title: "Search Thang Types Here" thang_search_title: "Search Thang Types Here"
level_search_title: "Search Levels Here" level_search_title: "Search Levels Here"
@ -784,9 +797,12 @@
watch_victory: "Watch your victory" watch_victory: "Watch your victory"
defeat_the: "Defeat the" defeat_the: "Defeat the"
tournament_ends: "Tournament ends" tournament_ends: "Tournament ends"
tournament_ended: "Tournament ended"
tournament_rules: "Tournament Rules" tournament_rules: "Tournament Rules"
tournament_blurb: "Write code, collect gold, build armies, crush foes, win prizes, and upgrade your career in our $40,000 Greed tournament! Check out the details" tournament_blurb: "Write code, collect gold, build armies, crush foes, win prizes, and upgrade your career in our $40,000 Greed tournament! Check out the details"
tournament_blurb_blog: "on our blog" tournament_blurb_blog: "on our blog"
rules: "Rules"
winners: "Winners"
ladder_prizes: ladder_prizes:
title: "Tournament Prizes" title: "Tournament Prizes"
@ -808,7 +824,6 @@
license: "license" license: "license"
oreilly: "ebook of your choice" oreilly: "ebook of your choice"
multiplayer_launch: multiplayer_launch:
introducing_dungeon_arena: "Introducing Dungeon Arena" introducing_dungeon_arena: "Introducing Dungeon Arena"
new_way: "The new way to compete with code." new_way: "The new way to compete with code."
@ -866,6 +881,7 @@
source_document: "Source Document" source_document: "Source Document"
document: "Document" # note to diplomats: not a physical document, a document in MongoDB, ie a record in a database document: "Document" # note to diplomats: not a physical document, a document in MongoDB, ie a record in a database
sprite_sheet: "Sprite Sheet" sprite_sheet: "Sprite Sheet"
candidate_sessions: "Candidate Sessions"
delta: delta:
added: "Added" added: "Added"

View file

@ -16,7 +16,7 @@ module.exports = nativeDescription: "français", englishDescription: "French", t
play: "Jouer" play: "Jouer"
retry: "Reessayer" retry: "Reessayer"
watch: "Regarder" watch: "Regarder"
# unwatch: "Unwatch" unwatch: "Ne plus regarder"
submit_patch: "Soumettre un correctif" submit_patch: "Soumettre un correctif"
units: units:
@ -26,14 +26,14 @@ module.exports = nativeDescription: "français", englishDescription: "French", t
minutes: "minutes" minutes: "minutes"
hour: "heure" hour: "heure"
hours: "heures" hours: "heures"
# day: "day" day: "jour"
# days: "days" days: "jours"
# week: "week" week: "semaine"
# weeks: "weeks" weeks: "semaines"
# month: "month" month: "mois"
# months: "months" months: "mois"
# year: "year" year: "année"
# years: "years" years: "années"
modal: modal:
close: "Fermer" close: "Fermer"
@ -128,8 +128,8 @@ module.exports = nativeDescription: "français", englishDescription: "French", t
forum_page: "notre forum" forum_page: "notre forum"
forum_suffix: " À la place." forum_suffix: " À la place."
send: "Envoyer un commentaire" send: "Envoyer un commentaire"
# contact_candidate: "Contact Candidate" contact_candidate: "Contacter le candidat"
# recruitment_reminder: "Use this form to reach out to candidates you are interested in interviewing. Remember that CodeCombat charges 15% of first-year salary. The fee is due upon hiring the employee and is refundable for 90 days if the employee does not remain employed. Part time, remote, and contract employees are free, as are interns." recruitment_reminder: "Utilisez ce formulaire pour entrer en contact avec le candidat qui vous interesse. Souvenez-vous que CodeCombat facture 15% de la première année de salaire. Ces frais sont dues à l'embauche de l'employé, ils sont remboursable pendant 90 jours si l'employé ne reste pas employé. Les employés à temps partiel, à distance ou contractuel sont gratuits en tant que stagiaires."
diplomat_suggestion: diplomat_suggestion:
title: "Aidez à traduire CodeCombat!" title: "Aidez à traduire CodeCombat!"
@ -173,11 +173,11 @@ module.exports = nativeDescription: "français", englishDescription: "French", t
email_announcements: "Annonces" email_announcements: "Annonces"
email_announcements_description: "Recevoir des mails sur les dernières actualités et sur le développement de CodeCombat." email_announcements_description: "Recevoir des mails sur les dernières actualités et sur le développement de CodeCombat."
email_notifications: "Notifications" email_notifications: "Notifications"
# email_notifications_summary: "Controls for personalized, automatic email notifications related to your CodeCombat activity." email_notifications_summary: "Commandes pour personaliser les notifications automatiques d'email liées à votre activité sur CodeCombat."
# email_any_notes: "Any Notifications" email_any_notes: "Toutes Notifications"
email_any_notes_description: "Désactivez pour ne plus recevoir de notifications par e-mail." email_any_notes_description: "Désactivez pour ne plus recevoir de notifications par e-mail."
# email_recruit_notes: "Job Opportunities" email_recruit_notes: "Offres d'emploi"
# email_recruit_notes_description: "If you play really well, we may contact you about getting you a (better) job." email_recruit_notes_description: "Si vous jouez vraiment bien, nous pouvons vous contacter pour vous proposer un (meilleur) emploi."
contributor_emails: "Emails des contributeurs" contributor_emails: "Emails des contributeurs"
contribute_prefix: "Nous recherchons des personnes pour se joindre à notre groupe! Consultez la " contribute_prefix: "Nous recherchons des personnes pour se joindre à notre groupe! Consultez la "
contribute_page: "page de contributions" contribute_page: "page de contributions"
@ -186,15 +186,15 @@ module.exports = nativeDescription: "français", englishDescription: "French", t
error_saving: "Problème d'enregistrement" error_saving: "Problème d'enregistrement"
saved: "Changements sauvegardés" saved: "Changements sauvegardés"
password_mismatch: "Le mot de passe ne correspond pas." password_mismatch: "Le mot de passe ne correspond pas."
# job_profile: "Job Profile" job_profile: "Profil d'emploi"
# job_profile_approved: "Your job profile has been approved by CodeCombat. Employers will be able to see it until you either mark it inactive or it has not been changed for four weeks." job_profile_approved: "Votre profil d'emploi a été approuvé par CodeCombat. Les employeurs seront en mesure de voir votre profil jusqu'à ce que vous le marquez inactif ou qu'il n'a pas été changé pendant quatre semaines."
# job_profile_explanation: "Hi! Fill this out, and we will get in touch about finding you a software developer job." job_profile_explanation: "Salut! Remplissez-le et nous prendrons contact pour vous trouver un emploi de développeur de logiciels."
# sample_profile: "See a sample profile" sample_profile: "Voir un exemple de profil"
view_profile: "Voir votre profil" view_profile: "Voir votre profil"
account_profile: account_profile:
edit_settings: "Éditer les préférences" edit_settings: "Éditer les préférences"
# done_editing_settings: "Done Editing" done_editing_settings: "Edition terminée"
profile_for_prefix: "Profil pour " profile_for_prefix: "Profil pour "
profile_for_suffix: "" profile_for_suffix: ""
approved: "Approuvé" approved: "Approuvé"
@ -202,57 +202,57 @@ module.exports = nativeDescription: "français", englishDescription: "French", t
looking_for: "à la recherche de:" looking_for: "à la recherche de:"
last_updated: "Dernière Mise à jour:" last_updated: "Dernière Mise à jour:"
contact: "Contact" contact: "Contact"
# active: "Looking for interview offers now" active: "En recherche d'offres"
# inactive: "Not looking for offers right now" inactive: "Ne recherche pas d'offres"
# complete: "complete" complete: "terminé"
# next: "Next" next: "Suivant"
# next_city: "city?" next_city: "ville ?"
# next_country: "pick your country." next_country: "choisissez votre pays."
# next_name: "name?" next_name: "nom ?"
# next_short_description: "summarize yourself at a glance." next_short_description: "résumez votre profil en quelques mots."
# next_long_description: "describe the work you're looking for." next_long_description: "décrivez le travail que vous cherchez."
# next_skills: "list at least five skills." next_skills: "listez au moins 5 compétances."
# next_work: "list your work experience." next_work: "décrivez votre expérience professionnelle."
# next_education: "recount your educational ordeals." next_education: "raconter votre scolarité."
# next_projects: "show off up to three projects you've worked on." next_projects: "décrivez jusqu'à 3 projets sur lesquels vous avez travaillé."
# next_links: "add any personal or social links." next_links: "ajouter des liens internet vers des sites personnels ou des réseaux sociaux."
# next_photo: "add an optional professional photo." next_photo: "ajouter une photo professionelle (optionnel)."
# next_active: "mark yourself open to offers to show up in searches." next_active: "déclarez vous ouvert aux offres pour apparaitre dans les recherches."
# example_blog: "Your Blog" example_blog: "Votre blog"
# example_github: "Your GitHub" example_github: "Votre GitHub"
# links_header: "Personal Links" links_header: "Liens personnels"
# links_blurb: "Link any other sites or profiles you want to highlight, like your GitHub, your LinkedIn, or your blog." links_blurb: "Lien vers d'autres sites ou profils que vous souhaitez mettre en avant, comme votre GitHub, LinkedIn ou votre blog."
# links_name: "Link Name" links_name: "Nom du lien"
# links_name_help: "What are you linking to?" links_name_help: "A quoi êtes vous lié ?"
# links_link_blurb: "Link URL" links_link_blurb: "Lien URL"
# basics_header: "Update basic info" basics_header: "Mettre à jour les information basiques"
# basics_active: "Open to Offers" basics_active: "Ouvert aux propositions"
# basics_active_help: "Want interview offers right now?" basics_active_help: "Voulez-vous des offres maintenant ?" # "Want interview offers right now?"
# basics_job_title: "Desired Job Title" basics_job_title: "Titre du poste souhaité"
# basics_job_title_help: "What role are you looking for?" basics_job_title_help: "Quel est le rôle que vous cherchez ?"
# basics_city: "City" basics_city: "Ville"
# basics_city_help: "City you want to work in (or live in now)." basics_city_help: "Ville dans laquelle vous souhaitez travailler (ou dans laquelle vous vivez actuellement)."
# basics_country: "Country" basics_country: "Pays"
# basics_country_help: "Country you want to work in (or live in now)." basics_country_help: "Pays dans lequel vous souhaitez travailler (ou dans lequel vous vivez actuellement)."
# basics_visa: "US Work Status" basics_visa: "Status de travail aux Etats-Unis"
# basics_visa_help: "Are you authorized to work in the US, or do you need visa sponsorship?" basics_visa_help: "Etes vous autorisé à travailler aux Etats-Unis ou avez vous besoin d'un parrainage pour le visa ?"
# basics_looking_for: "Looking For" basics_looking_for: "Recherche"
# basics_looking_for_full_time: "Full-time" basics_looking_for_full_time: "Temps plein"
# basics_looking_for_part_time: "Part-time" basics_looking_for_part_time: "Temps partiel"
# basics_looking_for_remote: "Remote" basics_looking_for_remote: "A distance"
# basics_looking_for_contracting: "Contracting" basics_looking_for_contracting: "Contrat"
# basics_looking_for_internship: "Internship" basics_looking_for_internship: "Stage"
# basics_looking_for_help: "What kind of developer position do you want?" basics_looking_for_help: "Quel genre de poste de développeur voulez-vous ?"
# name_header: "Fill in your name" name_header: "Remplissez votre nom"
# name_anonymous: "Anonymous Developer" name_anonymous: "Developpeur Anonyme"
# name_help: "Name you want employers to see, like 'Nick Winter'." name_help: "Le nom que vous souhaitez que l'employeur voie, par exemple 'Chuck Norris'."
# short_description_header: "Write a short description of yourself" short_description_header: "Décrivez vous en quelques mots"
# short_description_blurb: "Add a blurb here that will show, at a glance, whether you might be just the developer that an employer is looking for." # short_description_blurb: "Add a blurb here that will show, at a glance, whether you might be just the developer that an employer is looking for."
# short_description: "Short Description" short_description: "Description courte"
# short_description_help: "Who are you, and what are you looking for? 140 characters max." short_description_help: "Qui êtes vous et que recherchez vous ? 140 caractères max."
# skills_header: "Skills" skills_header: "Compétences"
# skills_help: "Tag relevant developer skills in order of proficiency." skills_help: "Notez vos compétence de développement par ordre de maitrise."
# long_description_header: "Detail your desired position" long_description_header: "Détaillez votre poste souhaité"
# long_description_blurb_1: "Write a little longer section here to describe the role you would like to pursue next." # long_description_blurb_1: "Write a little longer section here to describe the role you would like to pursue next."
# long_description_blurb_2: "Talk about how awesome you are and why it would be a good idea to hire you." # long_description_blurb_2: "Talk about how awesome you are and why it would be a good idea to hire you."
# long_description: "Description" # long_description: "Description"
@ -322,7 +322,7 @@ module.exports = nativeDescription: "français", englishDescription: "French", t
goals: "Objectifs" goals: "Objectifs"
success: "Succès" success: "Succès"
incomplete: "Imcoplet" incomplete: "Imcoplet"
# timed_out: "Ran out of time" timed_out: "Plus de temps"
failing: "Echec" failing: "Echec"
action_timeline: "Action sur la ligne de temps" action_timeline: "Action sur la ligne de temps"
click_to_select: "Clique sur une unité pour la sélectionner." click_to_select: "Clique sur une unité pour la sélectionner."
@ -465,8 +465,8 @@ module.exports = nativeDescription: "français", englishDescription: "French", t
back: "Retour" back: "Retour"
revert: "Annuler" revert: "Annuler"
revert_models: "Annuler les modèles" revert_models: "Annuler les modèles"
# fork_title: "Fork New Version" fork_title: "Fork une nouvelle version"
# fork_creating: "Creating Fork..." fork_creating: "Créer un Fork..."
more: "Plus" more: "Plus"
wiki: "Wiki" wiki: "Wiki"
live_chat: "Chat en live" live_chat: "Chat en live"
@ -533,7 +533,7 @@ module.exports = nativeDescription: "français", englishDescription: "French", t
message: "Message" message: "Message"
code: "Code" code: "Code"
ladder: "Companion" ladder: "Companion"
when: "Lorsuqe" when: "Quand"
opponent: "Adversaire" opponent: "Adversaire"
rank: "Rang" rank: "Rang"
score: "Score" score: "Score"
@ -744,8 +744,8 @@ module.exports = nativeDescription: "français", englishDescription: "French", t
simulation_explanation: "En simulant une partie, tu peux classer ton rang plus rapidement!" simulation_explanation: "En simulant une partie, tu peux classer ton rang plus rapidement!"
simulate_games: "Simuler une Partie!" simulate_games: "Simuler une Partie!"
simulate_all: "REINITIALISER ET SIMULER DES PARTIES" simulate_all: "REINITIALISER ET SIMULER DES PARTIES"
# games_simulated_by: "Games simulated by you:" games_simulated_by: "Parties que vous avez simulé :"
# games_simulated_for: "Games simulated for you:" games_simulated_for: "parties simulées pour vous :"
games_simulated: "Partie simulée" games_simulated: "Partie simulée"
games_played: "Parties jouées" games_played: "Parties jouées"
ratio: "Moyenne" ratio: "Moyenne"
@ -776,11 +776,11 @@ module.exports = nativeDescription: "français", englishDescription: "French", t
warmup: "Préchauffe" warmup: "Préchauffe"
vs: "VS" vs: "VS"
# friends_playing: "Friends Playing" # friends_playing: "Friends Playing"
# log_in_for_friends: "Log in to play with your friends!" log_in_for_friends: "Connectez vous pour jouer avec vos amis!"
# social_connect_blurb: "Connect and play against your friends!" social_connect_blurb: "Connectez vous pour jouer contre vos amis!"
# invite_friends_to_battle: "Invite your friends to join you in battle!" # invite_friends_to_battle: "Invite your friends to join you in battle!"
# fight: "Fight!" fight: "Combattez !"
# watch_victory: "Watch your victory" watch_victory: "Regardez votre victoire"
# defeat_the: "Defeat the" # defeat_the: "Defeat the"
tournament_ends: "Fin du tournoi" tournament_ends: "Fin du tournoi"
tournament_rules: "Règles du tournoi" tournament_rules: "Règles du tournoi"
@ -835,7 +835,7 @@ module.exports = nativeDescription: "français", englishDescription: "French", t
unknown: "Erreur inconnue." unknown: "Erreur inconnue."
resources: resources:
# your_sessions: "Your Sessions" your_sessions: "vos Sessions"
level: "Niveau" level: "Niveau"
# social_network_apis: "Social Network APIs" # social_network_apis: "Social Network APIs"
facebook_status: "Statut Facebook" facebook_status: "Statut Facebook"
@ -857,11 +857,11 @@ module.exports = nativeDescription: "français", englishDescription: "French", t
# level_session: "Your Session" # level_session: "Your Session"
# opponent_session: "Opponent Session" # opponent_session: "Opponent Session"
article: "Article" article: "Article"
# user_names: "User Names" user_names: "Nom d'utilisateur"
# thang_names: "Thang Names" # thang_names: "Thang Names"
files: "Fichiers" files: "Fichiers"
top_simulators: "Top Simulateurs" top_simulators: "Top Simulateurs"
# source_document: "Source Document" source_document: "Document Source"
document: "Document" document: "Document"
# sprite_sheet: "Sprite Sheet" # sprite_sheet: "Sprite Sheet"
@ -869,7 +869,7 @@ module.exports = nativeDescription: "français", englishDescription: "French", t
added: "Ajouté" added: "Ajouté"
modified: "Modifié" modified: "Modifié"
deleted: "Supprimé" deleted: "Supprimé"
# moved_index: "Moved Index" moved_index: "Index changé"
# text_diff: "Text Diff" text_diff: "Différence de texte"
# merge_conflict_with: "MERGE CONFLICT WITH" merge_conflict_with: "Fusionner les conflits avec"
no_changes: "Aucuns Changements" no_changes: "Aucuns Changements"

View file

@ -1,4 +1,5 @@
CocoModel = require './CocoModel' CocoModel = require './CocoModel'
util = require '../lib/utils'
module.exports = class Achievement extends CocoModel module.exports = class Achievement extends CocoModel
@className: 'Achievement' @className: 'Achievement'
@ -6,4 +7,10 @@ module.exports = class Achievement extends CocoModel
urlRoot: '/db/achievement' urlRoot: '/db/achievement'
isRepeatable: -> isRepeatable: ->
@get('proportionalTo')? @get('proportionalTo')?
# TODO logic is duplicated in Mongoose Achievement schema
getExpFunction: ->
kind = @get('function')?.kind or @schema.function.default.kind
parameters = @get('function')?.parameters or @schema.function.default.parameters
return utils.functionCreators[kind](parameters) if kind of utils.functionCreators

View file

@ -97,6 +97,22 @@ class CocoModel extends Backbone.Model
noty text: "#{errorMessage}: #{res.status} #{res.statusText}", layout: 'topCenter', type: 'error', killer: false, timeout: 10000 noty text: "#{errorMessage}: #{res.status} #{res.statusText}", layout: 'topCenter', type: 'error', killer: false, timeout: 10000
@trigger "save", @ @trigger "save", @
return super attrs, options return super attrs, options
patch: (options) ->
return false unless @_revertAttributes
options ?= {}
options.patch = true
attrs = {_id: @id}
keys = []
for key in _.keys @attributes
unless _.isEqual @attributes[key], @_revertAttributes[key]
attrs[key] = @attributes[key]
keys.push key
return unless keys.length
console.debug 'Patching', @get('name') or @, keys
@save(attrs, options)
fetch: -> fetch: ->
@jqxhr = super(arguments...) @jqxhr = super(arguments...)
@ -104,7 +120,6 @@ class CocoModel extends Backbone.Model
@jqxhr @jqxhr
markToRevert: -> markToRevert: ->
console.debug "Saving _revertAttributes for #{@constructor.className}: '#{@get('name')}'"
if @type() is 'ThangType' if @type() is 'ThangType'
@_revertAttributes = _.clone @attributes # No deep clones for these! @_revertAttributes = _.clone @attributes # No deep clones for these!
else else

View file

@ -19,8 +19,8 @@ module.exports = class LevelComponent extends CocoModel
@set 'js', @compile(@get 'code') unless @get 'js' @set 'js', @compile(@get 'code') unless @get 'js'
compile: (code) -> compile: (code) ->
if @get('language') and @get('language') isnt 'coffeescript' if @get('codeLanguage') and @get('codeLanguage') isnt 'coffeescript'
return console.error("Can't compile", @get('language'), "-- only CoffeeScript.", @) return console.error("Can't compile", @get('codeLanguage'), "-- only CoffeeScript.", @)
try try
js = CoffeeScript.compile(code, bare: true) js = CoffeeScript.compile(code, bare: true)
catch e catch e

View file

@ -21,8 +21,8 @@ module.exports = class LevelSystem extends CocoModel
SystemNameLoader.setName @ SystemNameLoader.setName @
compile: (code) -> compile: (code) ->
if @get('language') and @get('language') isnt 'coffeescript' if @get('codeLanguage') and @get('codeLanguage') isnt 'coffeescript'
return console.error("Can't compile", @get('language'), "-- only CoffeeScript.", @) return console.error("Can't compile", @get('codeLanguage'), "-- only CoffeeScript.", @)
try try
js = CoffeeScript.compile(code, bare: true) js = CoffeeScript.compile(code, bare: true)
catch e catch e

View file

@ -52,22 +52,17 @@ _.extend(AchievementSchema.properties,
description: 'For repeatables only. Denotes the field a repeatable achievement needs for its calculations' description: 'For repeatables only. Denotes the field a repeatable achievement needs for its calculations'
function: function:
type: 'object' type: 'object'
oneOf: [ properties:
linear: kind: {enum: ['linear', 'logarithmic'], default: 'linear'}
parameters:
type: 'object' type: 'object'
properties:
a: {type: 'number', default: 1},
required: ['a']
description: 'f(x) = a * x'
logarithmic:
type:'object'
properties: properties:
a: {type: 'number', default: 1} a: {type: 'number', default: 1}
b: {type: 'number', default: 1} b: {type: 'number', default: 1}
required: ['a', 'b'] c: {type: 'number', default: 1}
description: 'f(x) = a * ln(1/b * (x + b))' default: {kind: 'linear', parameters: a: 1}
] required: ['kind', 'parameters']
default: linear: a: 1 additionalProperties: false
) )
AchievementSchema.definitions = {} AchievementSchema.definitions = {}

View file

@ -20,15 +20,11 @@ module.exports =
href: '/db/achievement/{($)}' href: '/db/achievement/{($)}'
} }
] ]
collection: collection: type: 'string'
type: 'string' achievementName: type: 'string'
achievementName: created: type: 'date'
type: 'string' changed: type: 'date'
created: achievedAmount: type: 'number'
type: 'date' earnedPoints: type: 'number'
changed: previouslyAchievedAmount: {type: 'number', default: 0}
type: 'date' notified: type: 'boolean'
achievedAmount:
type: 'number'
notified:
type: 'boolean'

View file

@ -70,13 +70,13 @@ DependencySchema = c.object {
LevelComponentSchema = c.object { LevelComponentSchema = c.object {
title: "Component" title: "Component"
description: "A Component which can affect Thang behavior." description: "A Component which can affect Thang behavior."
required: ["system", "name", "description", "code", "dependencies", "propertyDocumentation", "language"] required: ["system", "name", "description", "code", "dependencies", "propertyDocumentation", "codeLanguage"]
"default": "default":
system: "ai" system: "ai"
name: "AttacksSelf" name: "AttacksSelf"
description: "This Component makes the Thang attack itself." description: "This Component makes the Thang attack itself."
code: attackSelfCode code: attackSelfCode
language: "coffeescript" codeLanguage: "coffeescript"
dependencies: [] # TODO: should depend on something by default dependencies: [] # TODO: should depend on something by default
propertyDocumentation: [] propertyDocumentation: []
} }
@ -95,7 +95,7 @@ _.extend LevelComponentSchema.properties,
type: "string" type: "string"
maxLength: 2000 maxLength: 2000
"default": "This Component makes the Thang attack itself." "default": "This Component makes the Thang attack itself."
language: codeLanguage:
type: "string" type: "string"
title: "Language" title: "Language"
description: "Which programming language this Component is written in." description: "Which programming language this Component is written in."

View file

@ -54,12 +54,12 @@ DependencySchema = c.object {
LevelSystemSchema = c.object { LevelSystemSchema = c.object {
title: "System" title: "System"
description: "A System which can affect Level behavior." description: "A System which can affect Level behavior."
required: ["name", "description", "code", "dependencies", "propertyDocumentation", "language"] required: ["name", "description", "code", "dependencies", "propertyDocumentation", "codeLanguage"]
"default": "default":
name: "JitterSystem" name: "JitterSystem"
description: "This System makes all idle, movable Thangs jitter around." description: "This System makes all idle, movable Thangs jitter around."
code: jitterSystemCode code: jitterSystemCode
language: "coffeescript" codeLanguage: "coffeescript"
dependencies: [] # TODO: should depend on something by default dependencies: [] # TODO: should depend on something by default
propertyDocumentation: [] propertyDocumentation: []
} }
@ -72,7 +72,7 @@ _.extend LevelSystemSchema.properties,
type: "string" type: "string"
maxLength: 2000 maxLength: 2000
"default": "This System doesn't do anything yet." "default": "This System doesn't do anything yet."
language: codeLanguage:
type: "string" type: "string"
title: "Language" title: "Language"
description: "Which programming language this System is written in." description: "Which programming language this System is written in."

View file

@ -113,8 +113,10 @@ UserSchema = c.object {},
signedEmployerAgreement: c.object {}, signedEmployerAgreement: c.object {},
linkedinID: c.shortString {title:"LinkedInID", description: "The user's LinkedIn ID when they signed the contract."} linkedinID: c.shortString {title:"LinkedInID", description: "The user's LinkedIn ID when they signed the contract."}
date: c.date {title: "Date signed employer agreement"} date: c.date {title: "Date signed employer agreement"}
data: c.object {description: "Cached LinkedIn data slurped from profile."} data: c.object {description: "Cached LinkedIn data slurped from profile.", additionalProperties: true}
points: {type:'number'} points: {type:'number'}
activity: {type: 'object', description: 'Summary statistics about user activity', additionalProperties: c.activity}
c.extendBasicProperties UserSchema, 'user' c.extendBasicProperties UserSchema, 'user'

View file

@ -8,7 +8,7 @@ combine = (base, ext) ->
return base unless ext? return base unless ext?
return _.extend(base, ext) return _.extend(base, ext)
urlPattern = '^(ht|f)tp(s?)\:\/\/[0-9a-zA-Z]([-.\w]*[0-9a-zA-Z])*(:(0-9)*)*(\/?)([a-zA-Z0-9\-\.\?\,\'\/\\\+&%\$#_=]*)?$' urlPattern = '^(ht|f)tp(s?)\:\/\/[0-9a-zA-Z]([-.\w]*[0-9a-zA-Z])*(:(0-9)*)*(\/?)([a-zA-Z0-9\-\.\?\,\'\/\\\+&%\$#_=]*)?$'
# Common schema properties # Common schema properties
me.object = (ext, props) -> combine {type: 'object', additionalProperties: false, properties: props or {}}, ext me.object = (ext, props) -> combine {type: 'object', additionalProperties: false, properties: props or {}}, ext
@ -17,7 +17,7 @@ me.shortString = (ext) -> combine({type: 'string', maxLength: 100}, ext)
me.pct = (ext) -> combine({type: 'number', maximum: 1.0, minimum: 0.0}, ext) me.pct = (ext) -> combine({type: 'number', maximum: 1.0, minimum: 0.0}, ext)
me.date = (ext) -> combine({type: ['object', 'string'], format: 'date-time'}, ext) me.date = (ext) -> combine({type: ['object', 'string'], format: 'date-time'}, ext)
# should just be string (Mongo ID), but sometimes mongoose turns them into objects representing those, so we are lenient # should just be string (Mongo ID), but sometimes mongoose turns them into objects representing those, so we are lenient
me.objectId = (ext) -> schema = combine(['object', 'string'], ext) me.objectId = (ext) -> schema = combine({type: ['object', 'string'] }, ext)
me.url = (ext) -> combine({type: 'string', format: 'url', pattern: urlPattern}, ext) me.url = (ext) -> combine({type: 'string', format: 'url', pattern: urlPattern}, ext)
PointSchema = me.object {title: "Point", description: "An {x, y} coordinate point.", format: "point2d", required: ["x", "y"]}, PointSchema = me.object {title: "Point", description: "An {x, y} coordinate point.", format: "point2d", required: ["x", "y"]},
@ -54,7 +54,7 @@ basicProps = (linkFragment) ->
me.extendBasicProperties = (schema, linkFragment) -> me.extendBasicProperties = (schema, linkFragment) ->
schema.properties = {} unless schema.properties? schema.properties = {} unless schema.properties?
_.extend(schema.properties, basicProps(linkFragment)) _.extend(schema.properties, basicProps(linkFragment))
# PATCHABLE # PATCHABLE
patchableProps = -> patchableProps = ->
@ -65,7 +65,7 @@ patchableProps = ->
allowPatches: { type: 'boolean' } allowPatches: { type: 'boolean' }
watchers: me.array({title:'Watchers'}, watchers: me.array({title:'Watchers'},
me.objectId(links: [{rel: 'extra', href: "/db/user/{($)}"}])) me.objectId(links: [{rel: 'extra', href: "/db/user/{($)}"}]))
me.extendPatchableProperties = (schema) -> me.extendPatchableProperties = (schema) ->
schema.properties = {} unless schema.properties? schema.properties = {} unless schema.properties?
_.extend(schema.properties, patchableProps()) _.extend(schema.properties, patchableProps())
@ -176,3 +176,9 @@ me.codeSnippet = (mode) ->
code: {type: 'string', title: 'Snippet', default: '', description: 'Code snippet. Use ${1:defaultValue} syntax to add flexible arguments'} code: {type: 'string', title: 'Snippet', default: '', description: 'Code snippet. Use ${1:defaultValue} syntax to add flexible arguments'}
# code: {type: 'string', format: 'ace', aceMode: 'ace/mode/'+mode, title: 'Snippet', default: '', description: 'Code snippet. Use ${1:defaultValue} syntax to add flexible arguments'} # code: {type: 'string', format: 'ace', aceMode: 'ace/mode/'+mode, title: 'Snippet', default: '', description: 'Code snippet. Use ${1:defaultValue} syntax to add flexible arguments'}
tab: {type: 'string', description: 'Tab completion text. Will be expanded to the snippet if typed and hit tab.'} tab: {type: 'string', description: 'Tab completion text. Will be expanded to the snippet if typed and hit tab.'}
me.activity = me.object {description: "Stats on an activity"},
first: me.date()
last: me.date()
count: {type: 'integer', minimum: 0}

View file

@ -75,7 +75,7 @@
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif font-family: "Helvetica Neue", Helvetica, Arial, sans-serif
color: #555 color: #555
ul.links, ul.projects ul.links, ul.projects, ul.sessions
margin: 0 margin: 0
padding: 0 padding: 0
@ -140,7 +140,7 @@
background-color: rgb(177, 55, 25) background-color: rgb(177, 55, 25)
padding: 15px padding: 15px
font-size: 20px font-size: 20px
.middle-column .middle-column
width: $middle-width - 2 * $middle-padding width: $middle-width - 2 * $middle-padding
padding-left: $middle-padding padding-left: $middle-padding

View file

@ -0,0 +1,12 @@
#editor-achievement-edit-view
.treema-root
margin: 28px 0px 20px
button
float: right
margin-top: 15px
margin-left: 10px
textarea
width: 92%
height: 300px

View file

@ -1,6 +1,32 @@
#employers-view #employers-view
#see-candidates
cursor: pointer 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 .tablesorter
//img //img
// display: none // display: none
@ -36,3 +62,9 @@
min-width: 50px min-width: 50px
td:nth-child(7) select td:nth-child(7) select
min-width: 100px min-width: 100px
#outer-content-wrapper, #intermediate-content-wrapper, #inner-content-wrapper
background: #949494
.main-content-area
background-color: #EAEAEA

View file

@ -45,3 +45,6 @@
font-family: Bangers font-family: Bangers
font-size: 16px font-size: 16px
float: right float: right
.progress-bar-white
background-color: white

View file

@ -101,9 +101,6 @@
background-image: none background-image: none
color: white color: white
td
padding: 1px 2px
#must-log-in button #must-log-in button
margin-right: 10px margin-right: 10px
@ -135,6 +132,12 @@
img img
margin-right: 10px margin-right: 10px
#winners
.win
color: #172
.loss
color: #712
@media only screen and (max-width: 800px) @media only screen and (max-width: 800px)
#ladder-view #ladder-view
#level-column img #level-column img

View file

@ -47,3 +47,6 @@
position: absolute position: absolute
right: 15px right: 15px
bottom: -5px bottom: -5px
td
padding: 1px 2px

View file

@ -35,4 +35,5 @@
tr.loss .state-cell tr.loss .state-cell
color: #712 color: #712
td
padding: 1px 2px

View file

@ -37,18 +37,13 @@ block content
if profileApproved if profileApproved
button.btn.btn-success#toggle-job-profile-approved(disabled=!me.isAdmin()) button.btn.btn-success#toggle-job-profile-approved(disabled=!me.isAdmin())
i.icon-eye-open i.icon-eye-open
span(data-i18n='account_profile.approved') Approved span(data-i18n='account_profile.featured') Featured
else if me.isAdmin() else if me.isAdmin()
button.btn#toggle-job-profile-approved button.btn#toggle-job-profile-approved
i.icon-eye-close i.icon-eye-close
span(data-i18n='account_profile.not_approved') Not Approved span(data-i18n='account_profile.not_featured') Not Featured
if me.isAdmin() && !myProfile if me.isAdmin() && !myProfile
button.btn.edit-settings-button#enter-espionage-mode 007 button.btn.edit-settings-button#enter-espionage-mode 007
//if editing && myProfile
// a.sample-profile(href="http://codecombat.com/images/pages/account/profile/sample_profile.png", target="_blank")
// button.btn
// i.icon-user
// span(data-i18n="account_settings.sample_profile") See a sample profile
if profile && allowedToViewJobProfile if profile && allowedToViewJobProfile
div(class="job-profile-container" + (editing ? " editable-profile" : "")) div(class="job-profile-container" + (editing ? " editable-profile" : ""))
@ -175,6 +170,19 @@ block content
span(data-i18n="account_profile.contact") Contact span(data-i18n="account_profile.contact") Contact
| #{profile.name.split(' ')[0]} | #{profile.name.split(' ')[0]}
if !editing && sessions.length
h3(data-i18n="account_profile.player_code") Player Code
ul.sessions
each session in sessions
li
- var sessionLink = "/play/level/" + session.levelID + "?team=" + (session.team || 'humans') + (myProfile ? '' : "&session=" + session._id);
a(href=sessionLink)
span= session.levelName
if session.team
span #{session.team}
if session.codeLanguage != 'javascript'
span - #{{coffeescript: 'CoffeeScript', python: 'Python', lua: 'Lua', io: 'Io', clojure: 'Clojure'}[session.codeLanguage]}
.middle-column.full-height-column .middle-column.full-height-column
.sub-column .sub-column
#name-container.editable-section #name-container.editable-section
@ -222,11 +230,11 @@ block content
h3.edit-label Tag your programming skills h3.edit-label Tag your programming skills
each skill in ["python", "coffeescript", "node", "ios", "objective-c", "javascript", "app-engine", "mongodb", "web dev", "django", "backbone"] each skill in ["python", "coffeescript", "node", "ios", "objective-c", "javascript", "app-engine", "mongodb", "web dev", "django", "backbone"]
code.edit-example-tag= skill code.edit-example-tag= skill
span span
else else
each skill in profile.skills each skill in profile.skills
code= skill code= skill
span span
form.editable-form form.editable-form
.editable-icon.glyphicon.glyphicon-remove .editable-icon.glyphicon.glyphicon-remove
@ -267,7 +275,7 @@ block content
img.header-icon(src="/images/pages/account/profile/work.png", alt="") img.header-icon(src="/images/pages/account/profile/work.png", alt="")
span(data-i18n="account_profile.work_experience") Work Experience span(data-i18n="account_profile.work_experience") Work Experience
| - #{profile.experience} | - #{profile.experience}
| |
span(data-i18n=profile.experience == 1 ? "units.year" : "units.years") span(data-i18n=profile.experience == 1 ? "units.year" : "units.years")
each job in profile.work each job in profile.work
if job.role && job.employer if job.role && job.employer
@ -454,9 +462,9 @@ block content
else if user else if user
.public-profile-container .public-profile-container
h2 h2
span(data-i18n="account_profile.profile_for_prefix") Profile for span(data-i18n="account_profile.profile_for_prefix") Profile for
span= user.get('name') || "Anonymous Wizard" span= user.get('name') || "Anonymous Wizard"
span(data-i18n="account_profile.profile_for_suffix") span(data-i18n="account_profile.profile_for_suffix")
img.profile-photo(src=user.getPhotoURL(256)) img.profile-photo(src=user.getPhotoURL(256))
@ -465,8 +473,8 @@ block content
else else
.public-profile-container .public-profile-container
h2 h2
span(data-i18n="account_profile.profile_for_prefix") Profile for span(data-i18n="account_profile.profile_for_prefix") Profile for
span= userID span= userID
span(data-i18n="account_profile.profile_for_suffix") span(data-i18n="account_profile.profile_for_suffix")
| |
span(data-i18n="loading_error.not_found") span(data-i18n="loading_error.not_found")

View file

@ -23,6 +23,8 @@ block content
a(href="/admin/users", data-i18n="admin.av_entities_users_url") Users a(href="/admin/users", data-i18n="admin.av_entities_users_url") Users
li li
a(href="/admin/level_sessions", data-i18n="admin.av_entities_active_instances_url") Active Instances a(href="/admin/level_sessions", data-i18n="admin.av_entities_active_instances_url") Active Instances
li
a(href="/admin/employer_list", data-i18n="admin.av_entities_employer_list_url") Employer List
h4(data-i18n="admin.av_other_sub_title") Other h4(data-i18n="admin.av_other_sub_title") Other
@ -31,3 +33,11 @@ block content
a(href="/admin/base", data-i18n="admin.av_other_debug_base_url") Base (for debugging base.jade) a(href="/admin/base", data-i18n="admin.av_other_debug_base_url") Base (for debugging base.jade)
li li
a(href="/admin/clas", data-i18n="admin.clas") CLAs a(href="/admin/clas", data-i18n="admin.clas") CLAs
hr
h3 Achievements
p This is just some stuff for temporary achievement testing. Should be replaced by a demo system.
input#increment-field(type="text")
a.btn.btn-secondary#increment-button(href="#") Increment

View file

@ -0,0 +1,65 @@
extends /templates/base
block content
if !me.isAdmin()
h1 Admin Only
else
h1(data-i18n="admin.av_entities_employer_list_url") Employer List
p
| We currently have
if employers.length
| #{employers.length}
else
| ...
| employers in the system.
if employers.length
table.table.table-condensed.table-hover.table-responsive.tablesorter
thead
tr
th(data-i18n="general.name") Name
th Company
th(data-i18n="general.email") Email
th Logins
th Candidates Viewed
th Candidates Contacted
th Signed Up
tbody
for employer, index in employers
- var activity = employer.get('activity') || {};
- var linkedIn = employer.get('signedEmployerAgreement').data
tr(data-employer-id=employer.id)
td
img(src=employer.getPhotoURL(50), height=50)
p
if employer.get('firstName')
span= employer.get('firstName') + ' ' + employer.get('lastName')
if employer.get('name')
| -
else if linkedIn.firstName
span= linkedIn.firstName + ' ' + linkedIn.lastName
if employer.get('name')
| -
if employer.get('name')
span= employer.get('name')
if !employer.get('firstName') && !linkedIn.firstName && !employer.get('name')
| Anoner
td
a(href=employer.get('signedEmployerAgreement').data.publicProfileUrl)= employer.get('employerAt')
td= employer.get('email')
for a in ['login', 'view_candidate', 'contact_candidate']
- var act = activity[a];
if act
td
strong= act.count
|
br
span= moment(act.first).fromNow()
br
span= moment(act.last).fromNow()
else
td 0
td(data-employer-age=(new Date() - new Date(employer.get('signedEmployerAgreement').date)) / 86400 / 1000)= moment(employer.get('signedEmployerAgreement').date).fromNow()

View file

@ -11,19 +11,21 @@ block content
li.active li.active
| #{achievement.attributes.name} | #{achievement.attributes.name}
button(data-i18n="common.save", disabled=authorized === true ? undefined : "true").btn.btn-primary#save-button Save button(data-i18n="", disabled=me.isAdmin() === true ? undefined : "true").btn.btn-primary#recalculate-button Recalculate
button(data-i18n="common.save", disabled=me.isAdmin() === true ? undefined : "true").btn.btn-primary#save-button Save
h3(data-i18n="achievement.edit_achievement_title") Edit Achievement h3(data-i18n="achievement.edit_achievement_title") Edit Achievement
span span
|: "#{achievement.attributes.name}" |: "#{achievement.attributes.name}"
#achievement-treema #achievement-treema
#achievement-view #achievement-view
hr hr
div#error-view
div#error-view
else else
.alert.alert-danger .alert.alert-danger
span Admin only. Turn around. span Admin only. Turn around.

View file

@ -2,66 +2,117 @@ extends /templates/base
block content block content
h1(data-i18n="employers.want_to_hire_our_players") Want to hire expert CodeCombat players? h1(data-i18n="employers.want_to_hire_our_players") Hire CodeCombat Players
div#info_wrapper
div#leftside
div.information_row
img(class="employer_icon" src="/images/pages/employer/employer_icon1.png")
h2(data-i18n="employers.what") What is CodeCombat?
p(data-i18n="employers.what_blurb") CodeCombat is a multiplayer browser programming game. Players write code to control their forces in battle against other developers. We support JavaScript, Python, Lua, Clojure, CoffeeScript, and Io.
div.information_row
img(class="employer_icon" src="/images/pages/employer/employer_icon3.png")
h2(data-i18n="employers.who") Who Are the Players?
p(data-i18n="employers.who_blurb") CodeCombateers are software developers who enjoy using their programming skills to play games. They range from college seniors at top 20 engineering programs to 20-year industry veterans.
div#rightside
div.information_row
img(class="employer_icon" src="/images/pages/employer/employer_icon2.png")
h2(data-i18n="employers.how") How Do We Find Developers?
p(data-i18n="employers.how_blurb") We host competitive tournaments to attract competitive software engieneers. We then use in-house algorithms to identify the best players among the top 5% of tournament winners.
div.information_row
img(class="employer_icon" src="/images/pages/employer/employer_icon4.png")
h2(data-i18n="employers.why") Why Hire Through Us?
p
span(data-i18n="employers.why_blurb_1") We will save you time. Every CodeCombateer we feaure is
strong(data-i18n="employers.why_blurb_2") looking for work
span(data-i18n="employers.why_blurb_3") , has
strong(data-i18n="employers.why_blurb_4") demonstrated top notch technical skills
span(data-i18n="employers.why_blurb_5") , and has been
strong(data-i18n="employers.why_blurb_6") personally screened by us
span(data-i18n="employers.why_blurb_7") . Stop screening and start hiring.
if !isEmployer && !me.isAdmin()
h3.see-candidates-header
a#see-candidates(title='Contact', tabindex=-1, data-toggle="coco-modal", data-target="modal/employer_signup", data-i18n="employers.see_candidates") Click here to see our candidates
p
span(data-i18n="employers.candidates_count_prefix") We currently have
if candidates.length
| #{candidates.length}
else
span(data-i18n="employers.candidates_count_many") many
|
span(data-i18n="employers.candidates_count_suffix") highly skilled and vetted developers looking for work.
if !isEmployer
h3
a#see-candidates(title='Contact', tabindex=-1, data-toggle="coco-modal", data-target="modal/employer_signup", data-i18n="employers.see_candidates") Click here to see our candidates
if candidates.length if candidates.length
table.table.table-condensed.table-hover.table-responsive.tablesorter
thead ul.nav.nav-pills
tr li.active
th(data-i18n="general.name") Name a(href="#featured-candidates", data-toggle="tab")
th(data-i18n="employers.candidate_location") Location span(data-i18n="employers.featured_developers") Featured Developers
th(data-i18n="employers.candidate_looking_for") Looking For | (#{featuredCandidates.length})
th(data-i18n="employers.candidate_role") Role if otherCandidates.length
th(data-i18n="employers.candidate_top_skills") Top Skills li
th(data-i18n="employers.candidate_years_experience") Yrs Exp a(href="#other-candidates", data-toggle="tab")
th(data-i18n="employers.candidate_last_updated") Last Updated span(data-i18n="employers.other_developers") Other Developers
if me.isAdmin() | (#{otherCandidates.length})
th(data-i18n="employers.candidate_approved") Us? if me.isAdmin() && inactiveCandidates.length
th(data-i18n="employers.candidate_active") Them? li
a(href="#inactive-candidates", data-toggle="tab")
tbody span(data-i18n="employers.inactive_developers") Inactive Developers
for candidate, index in candidates | (#{inactiveCandidates.length})
- var profile = candidate.get('jobProfile');
- var authorized = candidate.id; // If we have the id, then we are authorized. div.tab-content
tr(data-candidate-id=candidate.id, id=candidate.id) for area, tabIndex in [{id: "featured-candidates", candidates: featuredCandidates}, {id: "other-candidates", candidates: otherCandidates}, {id: "inactive-candidates", candidates: inactiveCandidates}]
td div(class="tab-pane well" + (tabIndex ? "" : " active"), id=area.id)
if authorized table.table.table-condensed.table-hover.table-responsive.tablesorter
img(src=candidate.getPhotoURL(50), alt=profile.name, title=profile.name, height=50) thead
p= profile.name tr
else th(data-i18n="general.name") Name
img(src="/images/pages/contribute/archmage.png", alt="", title="Sign up as an employer to see our candidates", width=50) th(data-i18n="employers.candidate_location") Location
p Developer ##{index + 1} th(data-i18n="employers.candidate_looking_for") Looking For
if profile.country == 'USA' th(data-i18n="employers.candidate_role") Role
td= profile.city th(data-i18n="employers.candidate_top_skills") Top Skills
else th(data-i18n="employers.candidate_years_experience") Yrs Exp
td= profile.country th(data-i18n="employers.candidate_last_updated") Last Updated
td= profile.lookingFor if me.isAdmin() && area.id == 'inactive-candidates'
td= profile.jobTitle th ✓?
td
each skill in profile.skills.slice(0, 10) tbody
code= skill for candidate, index in area.candidates
span - var profile = candidate.get('jobProfile');
td= profile.experience - var authorized = candidate.id; // If we have the id, then we are authorized.
td(data-profile-age=(new Date() - new Date(profile.updated)) / 86400 / 1000)= moment(profile.updated).fromNow() tr(data-candidate-id=candidate.id, id=candidate.id)
if me.isAdmin() td
if candidate.get('jobProfileApproved') if authorized
td ✓ img(src=candidate.getPhotoURL(50), alt=profile.name, title=profile.name, height=50)
else p= profile.name
td ✗ else
if profile.active img(src="/images/pages/contribute/archmage.png", alt="", title="Sign up as an employer to see our candidates", width=50)
td ✓ p Developer ##{index + 1 + (area.id == 'featured-candidates' ? 0 : featuredCandidates.length)}
else if profile.country == 'USA'
td ✗ td= profile.city
else
td= profile.country
td= profile.lookingFor
td= profile.jobTitle
td
each skill in profile.skills.slice(0, 10)
code= skill
span
td= profile.experience
td(data-profile-age=(new Date() - new Date(profile.updated)) / 86400 / 1000)= moment(profile.updated).fromNow()
if me.isAdmin() && area.id == 'inactive-candidates'
if candidate.get('jobProfileApproved')
td ✓
else
td ✗

View file

@ -37,6 +37,7 @@ block content
h3(data-i18n="play_level.tip_reticulating") Reticulating Splines... h3(data-i18n="play_level.tip_reticulating") Reticulating Splines...
.progress.progress-striped.active .progress.progress-striped.active
.progress-bar .progress-bar
else else
.alert.alert-danger .alert.alert-danger
span Admin only. Turn around. span Admin only. Turn around.

View file

@ -0,0 +1,11 @@
extends /templates/modal/modal_base
block modal-header-content
h3 #{confirmTitle}
block modal-body-content
p #{confirmBody}
block modal-footer-content
button.btn.btn-secondary#decline-button(type="button", data-dismiss="modal") #{confirmDecline}
button.btn.btn-primary#confirm-button(type="button", data-dismiss=closeOnConfirm === true ? "modal" : undefined) #{confirmConfirm}

View file

@ -24,4 +24,4 @@
block modal-footer block modal-footer
.modal-footer .modal-footer
block modal-footer-content block modal-footer-content
button.btn.btn-primary(type="button", data-dismiss="modal", aria-hidden="true", data-i18n="modal.okay") Okay button.btn.btn-primary(type="button", data-dismiss="modal", aria-hidden="true", data-i18n="modal.okay") Okay

File diff suppressed because it is too large Load diff

View file

@ -3,7 +3,7 @@
.level-content .level-content
#control-bar-view #control-bar-view
#canvas-wrapper #canvas-wrapper
canvas(width=924, height=589)#surface canvas(width=1848, height=1178)#surface
#canvas-left-gradient.gradient #canvas-left-gradient.gradient
#canvas-top-gradient.gradient #canvas-top-gradient.gradient
#gold-view.secret.expanded #gold-view.secret.expanded

View file

@ -1,11 +1,19 @@
View = require 'views/kinds/RootView' View = require 'views/kinds/RootView'
template = require 'templates/account/profile' template = require 'templates/account/profile'
User = require 'models/User' User = require 'models/User'
LevelSession = require 'models/LevelSession'
CocoCollection = require 'collections/CocoCollection'
{me} = require 'lib/auth' {me} = require 'lib/auth'
JobProfileContactView = require 'views/modal/job_profile_contact_modal' JobProfileContactView = require 'views/modal/job_profile_contact_modal'
JobProfileView = require 'views/account/job_profile_view' JobProfileView = require 'views/account/job_profile_view'
forms = require 'lib/forms' forms = require 'lib/forms'
class LevelSessionsCollection extends CocoCollection
url: -> "/db/user/#{@userID}/level.sessions/employer"
model: LevelSession
constructor: (@userID) ->
super()
module.exports = class ProfileView extends View module.exports = class ProfileView extends View
id: "profile-view" id: "profile-view"
template: template template: template
@ -42,9 +50,7 @@ module.exports = class ProfileView extends View
if User.isObjectID @userID if User.isObjectID @userID
@finishInit() @finishInit()
else else
console.log "getting", @userID
$.ajax "/db/user/#{@userID}/nameToID", success: (@userID) => $.ajax "/db/user/#{@userID}/nameToID", success: (@userID) =>
console.log " got", @userID
@finishInit() unless @destroyed @finishInit() unless @destroyed
@render() @render()
@ -59,8 +65,11 @@ module.exports = class ProfileView extends View
@user.fetch() @user.fetch()
@listenTo @user, "sync", => @listenTo @user, "sync", =>
@render() @render()
$.post "/db/user/#{me.id}/track/view_candidate"
$.post "/db/user/#{@userID}/track/viewed_by_employer" unless me.isAdmin()
else else
@user = User.getByID(@userID) @user = User.getByID(@userID)
@sessions = @supermodel.loadCollection(new LevelSessionsCollection(@userID), 'candidate_sessions').model
onLinkedInLoaded: => onLinkedInLoaded: =>
@linkedinLoaded = true @linkedinLoaded = true
@ -78,11 +87,11 @@ module.exports = class ProfileView extends View
@renderLinkedInButton() @renderLinkedInButton()
else else
@waitingForLinkedIn = true @waitingForLinkedIn = true
importLinkedIn: => importLinkedIn: =>
overwriteConfirm = confirm("Importing LinkedIn data will overwrite your current work experience, skills, name, descriptions, and education. Continue?") overwriteConfirm = confirm("Importing LinkedIn data will overwrite your current work experience, skills, name, descriptions, and education. Continue?")
unless overwriteConfirm then return unless overwriteConfirm then return
application.linkedinHandler.getProfileData (err, profileData) => application.linkedinHandler.getProfileData (err, profileData) =>
console.log profileData
@processLinkedInProfileData profileData @processLinkedInProfileData profileData
jobProfileSchema: -> @user.schema().properties.jobProfile.properties jobProfileSchema: -> @user.schema().properties.jobProfile.properties
@ -113,7 +122,7 @@ module.exports = class ProfileView extends View
for position in p["positions"]["values"] for position in p["positions"]["values"]
workObj = {} workObj = {}
descriptionMaxLength = workSchema.description.maxLength descriptionMaxLength = workSchema.description.maxLength
workObj.description = position.summary?.slice(0,descriptionMaxLength) workObj.description = position.summary?.slice(0,descriptionMaxLength)
workObj.description ?= "" workObj.description ?= ""
if position.startDate?.year? if position.startDate?.year?
@ -215,6 +224,11 @@ module.exports = class ProfileView extends View
links = ($.extend(true, {}, link) for link in links) links = ($.extend(true, {}, link) for link in links)
link.icon = @iconForLink link for link in links link.icon = @iconForLink link for link in links
context.profileLinks = _.sortBy links, (link) -> not link.icon # icons first context.profileLinks = _.sortBy links, (link) -> not link.icon # icons first
if @sessions
context.sessions = (s.attributes for s in @sessions.models when (s.get('submitted') or s.get('level-id') is 'gridmancer'))
context.sessions.sort (a, b) -> (b.playtime ? 0) - (a.playtime ? 0)
else
context.sessions = []
context context
afterRender: -> afterRender: ->
@ -301,7 +315,7 @@ module.exports = class ProfileView extends View
errors = @user.validate() errors = @user.validate()
return @showErrors errors if errors return @showErrors errors if errors
jobProfile = @user.get('jobProfile') jobProfile = @user.get('jobProfile')
jobProfile.updated = (new Date()).toISOString() jobProfile.updated = (new Date()).toISOString() if @user is me
@user.set 'jobProfile', jobProfile @user.set 'jobProfile', jobProfile
return unless res = @user.save() return unless res = @user.save()
res.error => res.error =>

View file

@ -113,7 +113,7 @@ module.exports = class SettingsView extends View
return unless me.hasLocalChanges() return unless me.hasLocalChanges()
res = me.save() res = me.patch()
return unless res return unless res
save = $('#save-button', @$el).text($.i18n.t('common.saving', defaultValue: 'Saving...')) save = $('#save-button', @$el).text($.i18n.t('common.saving', defaultValue: 'Saving...'))
.removeClass('btn-danger').addClass('btn-success').show() .removeClass('btn-danger').addClass('btn-success').show()

View file

@ -0,0 +1,160 @@
View = require 'views/kinds/RootView'
template = require 'templates/admin/employer_list'
app = require 'application'
User = require 'models/User'
{me} = require 'lib/auth'
CocoCollection = require 'collections/CocoCollection'
ModelModal = require 'views/modal/model_modal'
class EmployersCollection extends CocoCollection
url: '/db/user/x/employers'
model: User
module.exports = class EmployersView extends View
id: "employers-view"
template: template
events:
'click tbody tr td:first-child': 'onEmployerClicked'
constructor: (options) ->
super options
@getEmployers()
afterRender: ->
super()
@sortTable() if @employers.models.length
getRenderData: ->
ctx = super()
ctx.employers = @employers.models
ctx.moment = moment
ctx
getEmployers: ->
@employers = new EmployersCollection()
@employers.fetch()
# Re-render when we have fetched them, but don't wait and show a progress bar while loading.
@listenToOnce @employers, 'all', => @render()
sortTable: ->
# http://mottie.github.io/tablesorter/docs/example-widget-bootstrap-theme.html
$.extend $.tablesorter.themes.bootstrap,
# these classes are added to the table. To see other table classes available,
# look here: http://twitter.github.com/bootstrap/base-css.html#tables
table: "table table-bordered"
caption: "caption"
header: "bootstrap-header" # give the header a gradient background
footerRow: ""
footerCells: ""
icons: "" # add "icon-white" to make them white; this icon class is added to the <i> in the header
sortNone: "bootstrap-icon-unsorted"
sortAsc: "icon-chevron-up" # glyphicon glyphicon-chevron-up" # we are still using v2 icons
sortDesc: "icon-chevron-down" # glyphicon-chevron-down" # we are still using v2 icons
active: "" # applied when column is sorted
hover: "" # use custom css here - bootstrap class may not override it
filterRow: "" # filter row class
even: "" # odd row zebra striping
odd: "" # even row zebra striping
# e = exact text from cell
# n = normalized value returned by the column parser
# f = search filter input value
# i = column index
# $r = ???
filterSelectExactMatch = (e, n, f, i, $r) -> e is f
# call the tablesorter plugin and apply the uitheme widget
@$el.find(".tablesorter").tablesorter
theme: "bootstrap"
widthFixed: true
headerTemplate: "{content} {icon}"
textSorter:
6: (a, b, direction, column, table) ->
days = []
for s in [a, b]
n = parseInt s
n = 0 unless _.isNumber n
n = 1 if /^a/.test s
for [duration, factor] in [
[/second/i, 1 / (86400 * 1000)]
[/minute/i, 1 / 1440]
[/hour/i, 1 / 24]
[/week/i, 7]
[/month/i, 30.42]
[/year/i, 365.2425]
]
if duration.test s
n *= factor
break
if /^in /i.test s
n *= -1
days.push n
days[0] - days[1]
sortList: [[6, 0]]
# widget code contained in the jquery.tablesorter.widgets.js file
# use the zebra stripe widget if you plan on hiding any rows (filter widget)
widgets: ["uitheme", "zebra", "filter"]
widgetOptions:
# using the default zebra striping class name, so it actually isn't included in the theme variable above
# this is ONLY needed for bootstrap theming if you are using the filter widget, because rows are hidden
zebra: ["even", "odd"]
# extra css class applied to the table row containing the filters & the inputs within that row
filter_cssFilter: ""
# If there are child rows in the table (rows with class name from "cssChildRow" option)
# and this option is true and a match is found anywhere in the child row, then it will make that row
# visible; default is false
filter_childRows: false
# if true, filters are collapsed initially, but can be revealed by hovering over the grey bar immediately
# below the header row. Additionally, tabbing through the document will open the filter row when an input gets focus
filter_hideFilters: false
# Set this option to false to make the searches case sensitive
filter_ignoreCase: true
# jQuery selector string of an element used to reset the filters
filter_reset: ".reset"
# Use the $.tablesorter.storage utility to save the most recent filters
filter_saveFilters: true
# Delay in milliseconds before the filter widget starts searching; This option prevents searching for
# every character while typing and should make searching large tables faster.
filter_searchDelay: 150
# Set this option to true to use the filter to find text from the start of the column
# So typing in "a" will find "albert" but not "frank", both have a's; default is false
filter_startsWith: false
filter_functions:
3:
"0-1": (e, n, f, i, $r) -> parseInt(e) <= 1
"2-5": (e, n, f, i, $r) -> 2 <= parseInt(e) <= 5
"6+": (e, n, f, i, $r) -> 6 <= parseInt(e)
4:
"0-1": (e, n, f, i, $r) -> parseInt(e) <= 1
"2-5": (e, n, f, i, $r) -> 2 <= parseInt(e) <= 5
"6+": (e, n, f, i, $r) -> 6 <= parseInt(e)
5:
"0-1": (e, n, f, i, $r) -> parseInt(e) <= 1
"2-5": (e, n, f, i, $r) -> 2 <= parseInt(e) <= 5
"6+": (e, n, f, i, $r) -> 6 <= parseInt(e)
6:
"Last day": (e, n, f, i, $r) ->
days = parseFloat $($r.find('td')[i]).data('employer-age')
days <= 1
"Last week": (e, n, f, i, $r) ->
days = parseFloat $($r.find('td')[i]).data('employer-age')
days <= 7
"Last 4 weeks": (e, n, f, i, $r) ->
days = parseFloat $($r.find('td')[i]).data('employer-age')
days <= 28
onEmployerClicked: (e) ->
return unless id = $(e.target).closest('tr').data('employer-id')
employer = new User _id: id
@openModalView new ModelModal models: [employer]

View file

@ -8,6 +8,7 @@ module.exports = class AdminView extends View
events: events:
'click #enter-espionage-mode': 'enterEspionageMode' 'click #enter-espionage-mode': 'enterEspionageMode'
'click #increment-button': 'incrementUserAttribute'
enterEspionageMode: -> enterEspionageMode: ->
userEmail = $("#user-email").val().toLowerCase() userEmail = $("#user-email").val().toLowerCase()
@ -29,3 +30,8 @@ module.exports = class AdminView extends View
espionageFailure: (jqxhr, status,error)-> espionageFailure: (jqxhr, status,error)->
console.log "There was an error entering espionage mode: #{error}" console.log "There was an error entering espionage mode: #{error}"
incrementUserAttribute: (e) ->
val = $('#increment-field').val()
me.set(val, me.get(val) + 1)
me.save()

View file

@ -36,7 +36,7 @@ module.exports = class ContributeClassView extends View
subscription = el.attr('name') subscription = el.attr('name')
me.setEmailSubscription subscription+'News', checked me.setEmailSubscription subscription+'News', checked
me.save() me.patch()
@openModalView new SignupModalView() if me.get 'anonymous' @openModalView new SignupModalView() if me.get 'anonymous'
el.parent().find('.saved-notification').finish().show('fast').delay(3000).fadeOut(2000) el.parent().find('.saved-notification').finish().show('fast').delay(3000).fadeOut(2000)

View file

@ -1,6 +1,7 @@
View = require 'views/kinds/RootView' View = require 'views/kinds/RootView'
template = require 'templates/editor/achievement/edit' template = require 'templates/editor/achievement/edit'
Achievement = require 'models/Achievement' Achievement = require 'models/Achievement'
ConfirmModal = require 'views/modal/confirm'
module.exports = class AchievementEditView extends View module.exports = class AchievementEditView extends View
id: "editor-achievement-edit-view" id: "editor-achievement-edit-view"
@ -9,6 +10,7 @@ module.exports = class AchievementEditView extends View
events: events:
'click #save-button': 'saveAchievement' 'click #save-button': 'saveAchievement'
'click #recalculate-button': 'confirmRecalculation'
subscriptions: subscriptions:
'save-new': 'saveAchievement' 'save-new': 'saveAchievement'
@ -72,3 +74,34 @@ module.exports = class AchievementEditView extends View
res.success => res.success =>
url = "/editor/achievement/#{@achievement.get('slug') or @achievement.id}" url = "/editor/achievement/#{@achievement.get('slug') or @achievement.id}"
document.location.href = url document.location.href = url
confirmRecalculation: (e) ->
renderData =
'confirmTitle': "Are you really sure?"
'confirmBody': "This will trigger recalculation of the achievement for all users. Are you really sure you want to go down this path?"
'confirmDecline': "Not really"
'confirmConfirm': "Definitely"
confirmModal = new ConfirmModal(renderData)
confirmModal.onConfirm @recalculateAchievement
@openModalView confirmModal
recalculateAchievement: =>
$.ajax
data: JSON.stringify(achievements: [@achievement.get('slug') or @achievement.get('_id')])
success: (data, status, jqXHR) ->
noty
timeout: 5000
text: 'Recalculation process started'
type: 'success'
layout: 'topCenter'
error: (jqXHR, status, error) ->
console.error jqXHR
noty
timeout: 5000
text: "Starting recalculation process failed with error code #{jqXHR.status}"
type: 'error'
layout: 'topCenter'
url: '/admin/earned.achievement/recalculate'
type: 'POST'
contentType: 'application/json'

View file

@ -4,7 +4,7 @@ ThangType = require 'models/ThangType'
CocoCollection = require 'collections/CocoCollection' CocoCollection = require 'collections/CocoCollection'
class ThangTypeSearchCollection extends CocoCollection class ThangTypeSearchCollection extends CocoCollection
url: '/db/thang.type/search?project=true' url: '/db/thang.type?project=true'
model: ThangType model: ThangType
addTerm: (term) -> addTerm: (term) ->
@ -73,4 +73,4 @@ module.exports = class AddThangsView extends View
onEscapePressed: -> onEscapePressed: ->
@$el.find('input#thang-search').val("") @$el.find('input#thang-search').val("")
@runSearch @runSearch

View file

@ -8,7 +8,7 @@ SaveVersionModal = require 'views/modal/save_version_modal'
module.exports = class LevelComponentEditView extends View module.exports = class LevelComponentEditView extends View
id: "editor-level-component-edit-view" id: "editor-level-component-edit-view"
template: template template: template
editableSettings: ['name', 'description', 'system', 'language', 'dependencies', 'propertyDocumentation', 'i18n'] editableSettings: ['name', 'description', 'system', 'codeLanguage', 'dependencies', 'propertyDocumentation', 'i18n']
events: events:
'click #done-editing-component-button': 'endEditing' 'click #done-editing-component-button': 'endEditing'

View file

@ -5,7 +5,7 @@ LevelSystem = require 'models/LevelSystem'
CocoCollection = require 'collections/CocoCollection' CocoCollection = require 'collections/CocoCollection'
class LevelSystemSearchCollection extends CocoCollection class LevelSystemSearchCollection extends CocoCollection
url: '/db/level_system/search' url: '/db/level_system'
model: LevelSystem model: LevelSystem
module.exports = class LevelSystemAddView extends View module.exports = class LevelSystemAddView extends View

View file

@ -8,7 +8,7 @@ SaveVersionModal = require 'views/modal/save_version_modal'
module.exports = class LevelSystemEditView extends View module.exports = class LevelSystemEditView extends View
id: "editor-level-system-edit-view" id: "editor-level-system-edit-view"
template: template template: template
editableSettings: ['name', 'description', 'language', 'dependencies', 'propertyDocumentation', 'i18n'] editableSettings: ['name', 'description', 'codeLanguage', 'dependencies', 'propertyDocumentation', 'i18n']
events: events:
'click #done-editing-system-button': 'endEditing' 'click #done-editing-system-button': 'endEditing'

View file

@ -21,7 +21,7 @@ componentOriginals =
"physics.Physical" : "524b75ad7fc0f6d519000001" "physics.Physical" : "524b75ad7fc0f6d519000001"
class ThangTypeSearchCollection extends CocoCollection class ThangTypeSearchCollection extends CocoCollection
url: '/db/thang.type/search?project=original,name,version,slug,kind,components' url: '/db/thang.type?project=original,name,version,slug,kind,components'
model: ThangType model: ThangType
module.exports = class ThangsTabView extends View module.exports = class ThangsTabView extends View

View file

@ -20,10 +20,7 @@ module.exports = class EmployersView extends View
constructor: (options) -> constructor: (options) ->
super options super options
@getCandidates() @getCandidates()
checkForEmployerSignupHash: =>
if window.location.hash is "#employerSignupLoggingIn" and not ("employer" in me.get("permissions"))
@openModalView application.router.getView("modal/employer_signup","_modal")
window.location.hash = ""
afterRender: -> afterRender: ->
super() super()
@sortTable() if @candidates.models.length @sortTable() if @candidates.models.length
@ -33,13 +30,20 @@ module.exports = class EmployersView extends View
_.delay @checkForEmployerSignupHash, 500 _.delay @checkForEmployerSignupHash, 500
getRenderData: -> getRenderData: ->
c = super() ctx = super()
c.candidates = @candidates.models ctx.isEmployer = @isEmployer()
userPermissions = me.get('permissions') ? [] ctx.candidates = _.sortBy @candidates.models, (c) -> c.get('jobProfile').updated
ctx.activeCandidates = _.filter ctx.candidates, (c) -> c.get('jobProfile').active
ctx.inactiveCandidates = _.reject ctx.candidates, (c) -> c.get('jobProfile').active
ctx.featuredCandidates = _.filter ctx.activeCandidates, (c) -> c.get('jobProfileApproved')
ctx.otherCandidates = _.reject ctx.activeCandidates, (c) -> c.get('jobProfileApproved')
ctx.moment = moment
ctx._ = _
ctx
c.isEmployer = _.contains userPermissions, "employer" isEmployer: ->
c.moment = moment userPermissions = me.get('permissions') ? []
c _.contains userPermissions, "employer"
getCandidates: -> getCandidates: ->
@candidates = new CandidatesCollection() @candidates = new CandidatesCollection()
@ -48,6 +52,7 @@ module.exports = class EmployersView extends View
@listenToOnce @candidates, 'all', @renderCandidatesAndSetupScrolling @listenToOnce @candidates, 'all', @renderCandidatesAndSetupScrolling
renderCandidatesAndSetupScrolling: => renderCandidatesAndSetupScrolling: =>
@render() @render()
$(".nano").nanoScroller() $(".nano").nanoScroller()
if window.history?.state?.lastViewedCandidateID if window.history?.state?.lastViewedCandidateID
@ -55,6 +60,11 @@ module.exports = class EmployersView extends View
else if window.location.hash.length is 25 else if window.location.hash.length is 25
$(".nano").nanoScroller({scrollTo:$(window.location.hash)}) $(".nano").nanoScroller({scrollTo:$(window.location.hash)})
checkForEmployerSignupHash: =>
if window.location.hash is "#employerSignupLoggingIn" and not ("employer" in me.get("permissions"))
@openModalView application.router.getView("modal/employer_signup","_modal")
window.location.hash = ""
sortTable: -> sortTable: ->
# http://mottie.github.io/tablesorter/docs/example-widget-bootstrap-theme.html # http://mottie.github.io/tablesorter/docs/example-widget-bootstrap-theme.html
$.extend $.tablesorter.themes.bootstrap, $.extend $.tablesorter.themes.bootstrap,
@ -110,7 +120,7 @@ module.exports = class EmployersView extends View
n *= -1 n *= -1
days.push n days.push n
days[0] - days[1] days[0] - days[1]
sortList: [[6, 0]] sortList: if @isEmployer() or me.isAdmin() then [[6, 0]] else [[0, 1]]
# widget code contained in the jquery.tablesorter.widgets.js file # widget code contained in the jquery.tablesorter.widgets.js file
# use the zebra stripe widget if you plan on hiding any rows (filter widget) # use the zebra stripe widget if you plan on hiding any rows (filter widget)
widgets: ["uitheme", "zebra", "filter"] widgets: ["uitheme", "zebra", "filter"]
@ -172,9 +182,6 @@ module.exports = class EmployersView extends View
7: 7:
"": filterSelectExactMatch "": filterSelectExactMatch
"": filterSelectExactMatch "": filterSelectExactMatch
8:
"": filterSelectExactMatch
"": filterSelectExactMatch
onCandidateClicked: (e) -> onCandidateClicked: (e) ->
id = $(e.target).closest('tr').data('candidate-id') id = $(e.target).closest('tr').data('candidate-id')

View file

@ -8,6 +8,7 @@ locale = require 'locale/locale'
Achievement = require '../../models/Achievement' Achievement = require '../../models/Achievement'
User = require '../../models/User' User = require '../../models/User'
# TODO remove
filterKeyboardEvents = (allowedEvents, func) -> filterKeyboardEvents = (allowedEvents, func) ->
return (splat...) -> return (splat...) ->
@ -25,26 +26,24 @@ module.exports = class RootView extends CocoView
subscriptions: subscriptions:
'achievements:new': 'handleNewAchievements' 'achievements:new': 'handleNewAchievements'
initialize: ->
$ =>
# TODO Ruben remove this. Allows for easy testing right now though
#test = new Achievement(_id:'537ce4855c91b8d1dda7fda8')
#test.fetch(success:@showNewAchievement)
showNewAchievement: (achievement) -> showNewAchievement: (achievement, earnedAchievement) ->
currentLevel = me.level() currentLevel = me.level()
nextLevel = currentLevel + 1 nextLevel = currentLevel + 1
currentLevelExp = User.expForLevel(currentLevel) currentLevelExp = User.expForLevel(currentLevel)
nextLevelExp = User.expForLevel(nextLevel) nextLevelExp = User.expForLevel(nextLevel)
totalExpNeeded = nextLevelExp - currentLevelExp totalExpNeeded = nextLevelExp - currentLevelExp
expFunction = achievement.getExpFunction()
currentExp = me.get('points') currentExp = me.get('points')
worth = achievement.get('worth') previousExp = currentExp - achievement.get('worth')
leveledUp = currentExp - worth < currentLevelExp previousExp = expFunction(earnedAchievement.get('previouslyAchievedAmount')) * achievement.get('worth') if achievement.isRepeatable()
alreadyAchievedPercentage = 100 * (currentExp - currentLevelExp - worth) / totalExpNeeded achievedExp = currentExp - previousExp
newlyAchievedPercentage = if currentLevelExp is currentExp then 0 else 100 * worth / totalExpNeeded leveledUp = currentExp - achievedExp < currentLevelExp
alreadyAchievedPercentage = 100 * (previousExp - currentLevelExp) / totalExpNeeded
newlyAchievedPercentage = if leveledUp then 100 * (currentExp - currentLevelExp) / totalExpNeeded else 100 * achievedExp / totalExpNeeded
console.debug "Current level is #{currentLevel} (#{currentLevelExp} xp), next level is #{nextLevel} (#{nextLevelExp} xp)." console.debug "Current level is #{currentLevel} (#{currentLevelExp} xp), next level is #{nextLevel} (#{nextLevelExp} xp)."
console.debug "Need a total of #{nextLevelExp - currentLevelExp}, already had #{currentExp - currentLevelExp - worth} and just now earned #{worth} totalling on #{currentExp}" console.debug "Need a total of #{nextLevelExp - currentLevelExp}, already had #{previousExp} and just now earned #{achievedExp} totalling on #{currentExp}"
alreadyAchievedBar = $("<div class='progress-bar progress-bar-warning' style='width:#{alreadyAchievedPercentage}%'></div>") alreadyAchievedBar = $("<div class='progress-bar progress-bar-warning' style='width:#{alreadyAchievedPercentage}%'></div>")
newlyAchievedBar = $("<div data-toggle='tooltip' class='progress-bar progress-bar-success' style='width:#{newlyAchievedPercentage}%'></div>") newlyAchievedBar = $("<div data-toggle='tooltip' class='progress-bar progress-bar-success' style='width:#{newlyAchievedPercentage}%'></div>")
@ -53,7 +52,7 @@ module.exports = class RootView extends CocoView
message = if (currentLevel isnt 1) and leveledUp then "Reached level #{currentLevel}!" else null message = if (currentLevel isnt 1) and leveledUp then "Reached level #{currentLevel}!" else null
alreadyAchievedBar.tooltip(title: "#{currentExp} XP in total") alreadyAchievedBar.tooltip(title: "#{currentExp} XP in total")
newlyAchievedBar.tooltip(title: "#{worth} XP earned") newlyAchievedBar.tooltip(title: "#{achievedExp} XP earned")
emptyBar.tooltip(title: "#{nextLevelExp - currentExp} XP until level #{nextLevel}") emptyBar.tooltip(title: "#{nextLevelExp - currentExp} XP until level #{nextLevel}")
# TODO a default should be linked here # TODO a default should be linked here
@ -63,7 +62,7 @@ module.exports = class RootView extends CocoView
image: $("<img src='#{imageURL}' />") image: $("<img src='#{imageURL}' />")
description: achievement.get('description') description: achievement.get('description')
progressBar: progressBar progressBar: progressBar
earnedExp: "+ #{worth} XP" earnedExp: "+ #{achievedExp} XP"
message: message message: message
options = options =
@ -77,13 +76,11 @@ module.exports = class RootView extends CocoView
$.notify( data, options ) $.notify( data, options )
handleNewAchievements: (earnedAchievements) -> handleNewAchievements: (earnedAchievements) ->
console.debug 'Got new earned achievements'
# TODO performance?
_.each(earnedAchievements.models, (earnedAchievement) => _.each(earnedAchievements.models, (earnedAchievement) =>
achievement = new Achievement(_id: earnedAchievement.get('achievement')) achievement = new Achievement(_id: earnedAchievement.get('achievement'))
console.log achievement console.log achievement
achievement.fetch( achievement.fetch(
success: @showNewAchievement success: (achievement) => @showNewAchievement(achievement, earnedAchievement)
) )
) )
@ -150,7 +147,7 @@ module.exports = class RootView extends CocoView
saveLanguage: (newLang) -> saveLanguage: (newLang) ->
me.set('preferredLanguage', newLang) me.set('preferredLanguage', newLang)
res = me.save() res = me.patch()
return unless res return unless res
res.error -> res.error ->
errors = JSON.parse(res.responseText) errors = JSON.parse(res.responseText)

View file

@ -5,7 +5,7 @@ app = require('application')
class SearchCollection extends Backbone.Collection class SearchCollection extends Backbone.Collection
initialize: (modelURL, @model, @term, @projection) -> initialize: (modelURL, @model, @term, @projection) ->
@url = "#{modelURL}/search?project=" @url = "#{modelURL}?project="
if @projection? and not (@projection == []) if @projection? and not (@projection == [])
@url += projection[0] @url += projection[0]
@url += ',' + projected for projected in projection[1..] @url += ',' + projected for projected in projection[1..]

View file

@ -0,0 +1,30 @@
ModalView = require '../kinds/ModalView'
template = require 'templates/modal/confirm'
module.exports = class ConfirmModal extends ModalView
id: "confirm-modal"
template: template
closeButton: true
closeOnConfirm: true
events:
'click #decline-button': 'doDecline'
'click #confirm-button': 'doConfirm'
constructor: (@renderData={}, options={}) ->
super(options)
getRenderData: ->
context = super()
context.closeOnConfirm = @closeOnConfirm
_.extend context, @renderData
setRenderData: (@renderData) ->
onDecline: (@decline) ->
onConfirm: (@confirm) ->
doConfirm: -> @confirm() if @confirm
doDecline: -> @decline() if @decline

View file

@ -33,3 +33,4 @@ module.exports = class ContactView extends View
return forms.applyErrorsToForm @$el, res.errors unless res.valid return forms.applyErrorsToForm @$el, res.errors unless res.valid
window.tracker?.trackEvent 'Sent Feedback', message: contactMessage window.tracker?.trackEvent 'Sent Feedback', message: contactMessage
sendContactMessage contactMessage, @$el sendContactMessage contactMessage, @$el
$.post "/db/user/#{me.id}/track/contact_codecombat"

View file

@ -12,7 +12,7 @@ module.exports = class DiplomatSuggestionView extends View
subscribeAsDiplomat: -> subscribeAsDiplomat: ->
me.setEmailSubscription 'diplomatNews', true me.setEmailSubscription 'diplomatNews', true
me.save() me.patch()
$("#email_translator").prop("checked", 1) $("#email_translator").prop("checked", 1)
@hide() @hide()
return return

View file

@ -39,3 +39,5 @@ module.exports = class JobProfileContactView extends ContactView
contactMessage.message += '\n\n\n\n[CodeCombat says: please let us know if you end up accepting this job. Thanks!]' contactMessage.message += '\n\n\n\n[CodeCombat says: please let us know if you end up accepting this job. Thanks!]'
window.tracker?.trackEvent 'Sent Job Profile Message', message: contactMessage window.tracker?.trackEvent 'Sent Job Profile Message', message: contactMessage
sendContactMessage contactMessage, @$el sendContactMessage contactMessage, @$el
$.post "/db/user/#{me.id}/track/contact_candidate"
$.post "/db/user/#{@options.recipientID}/track/contacted_by_employer" unless me.isAdmin()

View file

@ -40,7 +40,7 @@ module.exports = class WizardSettingsModal extends View
forms.applyErrorsToForm(@$el, res) forms.applyErrorsToForm(@$el, res)
return return
res = me.save() res = me.patch()
return unless res return unless res
save = $('#save-button', @$el).text($.i18n.t('common.saving', defaultValue: 'Saving...')) save = $('#save-button', @$el).text($.i18n.t('common.saving', defaultValue: 'Saving...'))
.addClass('btn-info').show().removeClass('btn-danger') .addClass('btn-info').show().removeClass('btn-danger')

View file

@ -10,14 +10,6 @@ ModelModal = require 'views/modal/model_modal'
HIGHEST_SCORE = 1000000 HIGHEST_SCORE = 1000000
class LevelSessionsCollection extends CocoCollection
url: ''
model: LevelSession
constructor: (levelID) ->
super()
@url = "/db/level/#{levelID}/all_sessions"
module.exports = class LadderTabView extends CocoView module.exports = class LadderTabView extends CocoView
id: 'ladder-tab-view' id: 'ladder-tab-view'
template: require 'templates/play/ladder/ladder_tab' template: require 'templates/play/ladder/ladder_tab'

View file

@ -43,7 +43,7 @@ module.exports = class LadderView extends RootView
onLoaded: -> onLoaded: ->
@teams = teamDataFromLevel @level @teams = teamDataFromLevel @level
@render() super()
getRenderData: -> getRenderData: ->
ctx = super() ctx = super()
@ -54,6 +54,7 @@ module.exports = class LadderView extends RootView
ctx.levelDescription = marked(@level.get('description')) if @level.get('description') ctx.levelDescription = marked(@level.get('description')) if @level.get('description')
ctx._ = _ ctx._ = _
ctx.tournamentTimeLeft = moment(new Date(1402444800000)).fromNow() ctx.tournamentTimeLeft = moment(new Date(1402444800000)).fromNow()
ctx.winners = require('views/play/ladder/tournament_results')[@levelID]
ctx ctx
afterRender: -> afterRender: ->

View file

@ -0,0 +1,552 @@
module.exports = results = greed: {}
results.greed.humans = [
{team: "humans", rank: 1, sessionID: "5381e3537585483905a829c1", name: "Wizard Dude", playtime: 63184, wins: 363, losses: 0, score: 363}
{team: "humans", rank: 2, sessionID: "537cf76184c54c6e05c05415", name: "Vettax", playtime: 96757, wins: 365, losses: 9, score: 356}
{team: "humans", rank: 3, sessionID: "53836824c85c223a05f4a1dd", name: "HighSea", playtime: 98996, wins: 354, losses: 7, score: 347}
{team: "humans", rank: 4, sessionID: "537c5cf614aaabe80c69fc8d", name: "BubbleDragon", playtime: 136876, wins: 352, losses: 12, score: 340}
{team: "humans", rank: 5, sessionID: "537bad0cb0d477e005347fb5", name: "Bakanio", playtime: 103933, wins: 348, losses: 11, score: 337}
{team: "humans", rank: 6, sessionID: "537cbf86264b3a7d12eb9d55", name: "zero_degrees", playtime: 4236, wins: 346, losses: 17, score: 329}
{team: "humans", rank: 7, sessionID: "538915d2d06c503805fe40d2", name: "Transistor", playtime: 742, wins: 347, losses: 19, score: 328}
{team: "humans", rank: 8, sessionID: "537bbc56831db4ca0526ced3", name: "Chrc", playtime: 2105, wins: 339, losses: 15, score: 324}
{team: "humans", rank: 9, sessionID: "5381d7f87585483905a825fc", name: "nemoyatpeace", playtime: 121202, wins: 344, losses: 20, score: 324}
{team: "humans", rank: 10, sessionID: "53928ff24ca25c6205e290c0", name: "Catalina", playtime: 239, wins: 342, losses: 27, score: 315}
{team: "humans", rank: 11, sessionID: "5383f8162757353805a97454", name: "Zzadded", playtime: 53485, wins: 334, losses: 28, score: 306}
{team: "humans", rank: 12, sessionID: "537d047084c54c6e05c05ab9", name: "Moojo", playtime: 83687, wins: 331, losses: 27, score: 304}
{team: "humans", rank: 13, sessionID: "537bab02f5b6a9d405ec5107", name: "Pentiado", playtime: 70424, wins: 336, losses: 33, score: 303}
{team: "humans", rank: 14, sessionID: "537d11947e1e10b705bbc4a6", name: "Banadux", playtime: 141292, wins: 333, losses: 32, score: 301}
{team: "humans", rank: 15, sessionID: "538181fd7585483905a801e1", name: "chadnickbok", playtime: 922, wins: 334, losses: 33, score: 301}
{team: "humans", rank: 16, sessionID: "537bc4990de0a02c07e8799a", name: "Mepath", playtime: 94421, wins: 315, losses: 28, score: 287}
{team: "humans", rank: 17, sessionID: "53826025c85c223a05f42c5c", name: "Frederick the Great", playtime: 24696, wins: 325, losses: 42, score: 283}
{team: "humans", rank: 18, sessionID: "5387d070b924da39051331a5", name: "NoMan", playtime: 279296, wins: 320, losses: 46, score: 274}
{team: "humans", rank: 19, sessionID: "535e50c1ba35914a07a308c9", name: "Patogeno", playtime: 69601, wins: 318, losses: 44, score: 274}
{team: "humans", rank: 20, sessionID: "537b9b023803a287057583dd", name: "avv", playtime: 85418, wins: 316, losses: 50, score: 266}
{team: "humans", rank: 21, sessionID: "537c89d814aaabe80c6a1139", name: "Foosvald", playtime: 3197, wins: 307, losses: 41, score: 266}
{team: "humans", rank: 22, sessionID: "538350c778171d3d057eb1e4", name: "Ticaj", playtime: 29249, wins: 316, losses: 52, score: 264}
{team: "humans", rank: 23, sessionID: "537b7d9a5bc02238050d1450", name: "DrMonky", playtime: 14815, wins: 315, losses: 55, score: 260}
{team: "humans", rank: 24, sessionID: "537c7d8526529de30cca4310", name: "imkat", playtime: 14870, wins: 292, losses: 38, score: 254}
{team: "humans", rank: 25, sessionID: "5392283b2446a44105387cbb", name: "AKA", playtime: 134724, wins: 312, losses: 58, score: 254}
{team: "humans", rank: 26, sessionID: "537b7ff9e91b4d3b05525db2", name: "UltraNagog", playtime: 113091, wins: 296, losses: 46, score: 250}
{team: "humans", rank: 27, sessionID: "537c1b0e0ff88b2c06288354", name: "Master J", playtime: 32859, wins: 305, losses: 57, score: 248}
{team: "humans", rank: 28, sessionID: "537c551814aaabe80c69f8e9", name: "texastoast", playtime: 75930, wins: 300, losses: 54, score: 246}
{team: "humans", rank: 29, sessionID: "537b9f2551e98aa705b60500", name: "Otsix", playtime: 2270, wins: 302, losses: 58, score: 244}
{team: "humans", rank: 30, sessionID: "537bb4b88698e13805226bb9", name: "asselinpaul", playtime: 22992, wins: 306, losses: 64, score: 242}
{team: "humans", rank: 31, sessionID: "537b87c6e91b4d3b0552600f", name: "tedshot", playtime: 22872, wins: 306, losses: 65, score: 241}
{team: "humans", rank: 32, sessionID: "537bae76ec57a3e805cbf5b3", name: "Aeter", playtime: 18224, wins: 307, losses: 66, score: 241}
{team: "humans", rank: 33, sessionID: "537b9e8a51e98aa705b604ab", name: "bxp", playtime: 17385, wins: 304, losses: 67, score: 237}
{team: "humans", rank: 34, sessionID: "537bdbe4a41b6b3a059befd0", name: "princeben", playtime: 52246, wins: 290, losses: 56, score: 234}
{team: "humans", rank: 35, sessionID: "537bdf1d375835400576a207", name: "Mal Keshar", playtime: 985, wins: 295, losses: 68, score: 227}
{team: "humans", rank: 36, sessionID: "5383f2ea2757353805a972d3", name: "Mergen", playtime: 37792, wins: 297, losses: 71, score: 226}
{team: "humans", rank: 37, sessionID: "537cec6a9cce053a05c04385", name: "Simulatorboy", playtime: 15925, wins: 295, losses: 75, score: 220}
{team: "humans", rank: 38, sessionID: "537b977d556db17605be76a2", name: "shoebane", playtime: 89060, wins: 287, losses: 68, score: 219}
{team: "humans", rank: 39, sessionID: "538757328ca8b1120b8c0bb5", name: "ProvençalLeGaulois", playtime: 183850, wins: 289, losses: 71, score: 218}
{team: "humans", rank: 40, sessionID: "5384b9803e0daa3905188225", name: "Rincewind the Wizard", playtime: 49014, wins: 280, losses: 64, score: 216}
{team: "humans", rank: 41, sessionID: "53805c227585483905a77c38", name: "guuuuuuuuuuu", playtime: 40835, wins: 290, losses: 76, score: 214}
{team: "humans", rank: 42, sessionID: "537bc0060b070d6b0691fd47", name: "toothpaste", playtime: 116438, wins: 289, losses: 78, score: 211}
{team: "humans", rank: 43, sessionID: "538bc7d05572d43b0520ede9", name: "MaxF", playtime: 16012, wins: 271, losses: 62, score: 209}
{team: "humans", rank: 44, sessionID: "537ca06126529de30cca58a1", name: "firemanphil", playtime: 58857, wins: 287, losses: 81, score: 206}
{team: "humans", rank: 45, sessionID: "537b895ee91b4d3b0552606a", name: "Supaku", playtime: 23623, wins: 281, losses: 79, score: 202}
{team: "humans", rank: 46, sessionID: "537ba1d7903fd2b805155298", name: "Greediest", playtime: 32390, wins: 286, losses: 87, score: 199}
{team: "humans", rank: 47, sessionID: "537ba398903fd2b805155376", name: "RapTorS", playtime: 30828, wins: 280, losses: 83, score: 197}
{team: "humans", rank: 48, sessionID: "537bbb371aaa69c405267714", name: "PIptastic", playtime: 14165, wins: 282, losses: 85, score: 197}
{team: "humans", rank: 49, sessionID: "537fd2f62a5a5acd08dbbe90", name: "aldezar", playtime: 55511, wins: 260, losses: 64, score: 196}
{team: "humans", rank: 50, sessionID: "537c0f440ff88b2c06287c31", name: "yes", playtime: 42677, wins: 251, losses: 55, score: 196}
{team: "humans", rank: 51, sessionID: "537fb985c1a6b99109171054", name: "IamTesting", playtime: 242, wins: 280, losses: 90, score: 190}
{team: "humans", rank: 52, sessionID: "537cdd63665bde1b13ffdede", name: "wiggles", playtime: 9574, wins: 278, losses: 94, score: 184}
{team: "humans", rank: 53, sessionID: "537cda44665bde1b13ffdcb2", name: "Nullable", playtime: 21602, wins: 275, losses: 93, score: 182}
{team: "humans", rank: 54, sessionID: "537bf012b72b2bbe053c9173", name: "hyn", playtime: 5688, wins: 278, losses: 97, score: 181}
{team: "humans", rank: 55, sessionID: "537bda813dd7d35105f5fec2", name: "Rinomon", playtime: 19811, wins: 272, losses: 96, score: 176}
{team: "humans", rank: 56, sessionID: "537b9e7251e98aa705b604a2", name: "stuntman_fx", playtime: 12496, wins: 258, losses: 86, score: 172}
{team: "humans", rank: 57, sessionID: "537d1def7e1e10b705bbcbc4", name: "WizBit", playtime: 35790, wins: 270, losses: 98, score: 172}
{team: "humans", rank: 58, sessionID: "537b8a93e91b4d3b055260f3", name: "Korla March", playtime: 13093, wins: 265, losses: 94, score: 171}
{team: "humans", rank: 59, sessionID: "53873bc17d99a7390561afd2", name: "VicksC", playtime: 65582, wins: 262, losses: 93, score: 169}
{team: "humans", rank: 60, sessionID: "537b907d5bc02238050d1b22", name: "Jonanin", playtime: 60888, wins: 272, losses: 103, score: 169}
{team: "humans", rank: 61, sessionID: "537b9345c74f237005ecc475", name: "AdrianoKF", playtime: 8880, wins: 267, losses: 100, score: 167}
{team: "humans", rank: 62, sessionID: "537ba08651e98aa705b605a8", name: "frickinjason", playtime: 311295, wins: 266, losses: 99, score: 167}
{team: "humans", rank: 63, sessionID: "536076104c5ed51d05284aeb", name: "会打电脑的狼", playtime: 180932, wins: 262, losses: 96, score: 166}
{team: "humans", rank: 64, sessionID: "537fb41bc1a6b99109170daf", name: "avatar652", playtime: 23943, wins: 260, losses: 95, score: 165}
{team: "humans", rank: 65, sessionID: "537bb34a8698e13805226ae1", name: "Superice", playtime: 8561, wins: 267, losses: 103, score: 164}
{team: "humans", rank: 66, sessionID: "537b8f285bc02238050d1a91", name: "renner96", playtime: 8962, wins: 265, losses: 103, score: 162}
{team: "humans", rank: 67, sessionID: "537b97ba556db17605be76bc", name: "howsiwei", playtime: 37865, wins: 249, losses: 90, score: 159}
{team: "humans", rank: 68, sessionID: "537d6e997e6f2dba0510649c", name: "Brainoutoforder", playtime: 79551, wins: 263, losses: 104, score: 159}
{team: "humans", rank: 69, sessionID: "537bbe24d9644630065c90f4", name: "cc", playtime: 27244, wins: 247, losses: 94, score: 153}
{team: "humans", rank: 70, sessionID: "538f055b7558763705258211", name: "Klee", playtime: 29269, wins: 264, losses: 111, score: 153}
{team: "humans", rank: 71, sessionID: "537c3a51e8ea6e790a7cceb2", name: "NovaHorizon", playtime: 72951, wins: 254, losses: 103, score: 151}
{team: "humans", rank: 72, sessionID: "537c3e0dd08a96e40a64f01c", name: "Arnfrid", playtime: 13133, wins: 261, losses: 111, score: 150}
{team: "humans", rank: 73, sessionID: "537b7cf65bc02238050d141f", name: "rawpower", playtime: 60808, wins: 256, losses: 110, score: 146}
{team: "humans", rank: 74, sessionID: "537bcc75c57303a5070c2e9a", name: "dhimdis", playtime: 43092, wins: 256, losses: 111, score: 145}
{team: "humans", rank: 75, sessionID: "538bc1a15572d43b0520e8ae", name: "rednek", playtime: 38220, wins: 250, losses: 106, score: 144}
{team: "humans", rank: 76, sessionID: "5380c8ed7585483905a7ad55", name: "Kungfury", playtime: 628, wins: 254, losses: 113, score: 141}
{team: "humans", rank: 77, sessionID: "537c37dff1d9cfe4096ba9c9", name: "Auralien", playtime: 24844, wins: 255, losses: 115, score: 140}
{team: "humans", rank: 78, sessionID: "539234c52446a441053881ad", name: "Szalami", playtime: 45304, wins: 243, losses: 108, score: 135}
{team: "humans", rank: 79, sessionID: "537c15910ff88b2c06287fd5", name: "Ahrimen", playtime: 24541, wins: 253, losses: 120, score: 133}
{team: "humans", rank: 80, sessionID: "537ff34f4cd8023705770b31", name: "ColtYolo", playtime: 18704, wins: 244, losses: 115, score: 129}
{team: "humans", rank: 81, sessionID: "538ceadc56c3613905300df1", name: "Teshynil", playtime: 59986, wins: 233, losses: 105, score: 128}
{team: "humans", rank: 82, sessionID: "538f174df3691038051651f1", name: "Leas", playtime: 11670, wins: 251, losses: 126, score: 125}
{team: "humans", rank: 83, sessionID: "537d8cae13add33a051be70c", name: "Jamar", playtime: 9358, wins: 245, losses: 123, score: 122}
{team: "humans", rank: 84, sessionID: "53877f428ca8b1120b8c2ba8", name: "madcoder", playtime: 58824, wins: 241, losses: 120, score: 121}
{team: "humans", rank: 85, sessionID: "5391f1f282f0bc4705242218", name: "RedRudeBoy", playtime: 13356, wins: 230, losses: 110, score: 120}
{team: "humans", rank: 86, sessionID: "537bdb1c6b018e5505b9d5ae", name: "Ralkarin", playtime: 16137, wins: 245, losses: 128, score: 117}
{team: "humans", rank: 87, sessionID: "537bc9efa968548907de6dc5", name: "scrumlock", playtime: 43716, wins: 238, losses: 122, score: 116}
{team: "humans", rank: 88, sessionID: "53973dc02546283905a3d603", name: "Olivier_A", playtime: 5903, wins: 246, losses: 131, score: 115}
{team: "humans", rank: 89, sessionID: "537d3fffe20e956205f0da0c", name: "Lecky", playtime: 28974, wins: 242, losses: 127, score: 115}
{team: "humans", rank: 90, sessionID: "537b9edc7c89ec9805f4de89", name: "Cemiv", playtime: 5632, wins: 241, losses: 134, score: 107}
{team: "humans", rank: 91, sessionID: "537b8bede91b4d3b05526151", name: "Witchy", playtime: 29809, wins: 214, losses: 110, score: 104}
{team: "humans", rank: 92, sessionID: "535d728c30f061020b8f9893", name: "ZeoNFrosT", playtime: 0, wins: 233, losses: 130, score: 103}
{team: "humans", rank: 93, sessionID: "537b9c4b16613c8405155abd", name: "JD557", playtime: 17456, wins: 236, losses: 134, score: 102}
{team: "humans", rank: 94, sessionID: "537bdb396b018e5505b9d5ba", name: "devast8a", playtime: 89013, wins: 233, losses: 133, score: 100}
{team: "humans", rank: 95, sessionID: "537b7fbc5bc02238050d14ee", name: "jojman272", playtime: 859, wins: 232, losses: 132, score: 100}
{team: "humans", rank: 96, sessionID: "537cc552e4523d0113ba4eb2", name: "Sir Coward", playtime: 26839, wins: 235, losses: 135, score: 100}
{team: "humans", rank: 97, sessionID: "537b8abde91b4d3b05526103", name: "Jef", playtime: 10311, wins: 236, losses: 139, score: 97}
{team: "humans", rank: 98, sessionID: "537ed4db14bb1d38053b5b72", name: "ModernBarbershop", playtime: 33894, wins: 221, losses: 129, score: 92}
{team: "humans", rank: 99, sessionID: "537b8ba6e91b4d3b0552613c", name: "Wiz", playtime: 1655, wins: 227, losses: 137, score: 90}
{team: "humans", rank: 100, sessionID: "537bad3396ee90f605f2b0f9", name: "Rnq", playtime: 14853, wins: 226, losses: 138, score: 88}
{team: "humans", rank: 101, sessionID: "537f9d5ba7d2578f083d69b4", name: "BLACKORP", playtime: 2869, wins: 225, losses: 138, score: 87}
{team: "humans", rank: 102, sessionID: "537ba00e7c89ec9805f4defc", name: "xoko814", playtime: 869, wins: 225, losses: 139, score: 86}
{team: "humans", rank: 103, sessionID: "5391f83082f0bc47052424c7", name: "tarasiu", playtime: 228, wins: 225, losses: 139, score: 86}
{team: "humans", rank: 104, sessionID: "537d26d77e1e10b705bbd0d5", name: "Traitor", playtime: 8900, wins: 222, losses: 137, score: 85}
{team: "humans", rank: 105, sessionID: "537bbb0a1aaa69c4052676f5", name: "TROGDOR BURNINATE", playtime: 6630, wins: 231, losses: 146, score: 85}
{team: "humans", rank: 106, sessionID: "5387a8a3d06c503805fda60d", name: "masanorinyo", playtime: 68753, wins: 226, losses: 142, score: 84}
{team: "humans", rank: 107, sessionID: "538e8593f369103805160d76", name: "Aggar", playtime: 12492, wins: 228, losses: 144, score: 84}
{team: "humans", rank: 108, sessionID: "537e7eac63734c630505be27", name: "TheRealThrall", playtime: 587, wins: 224, losses: 141, score: 83}
{team: "humans", rank: 109, sessionID: "537c8dfa26529de30cca4c43", name: "xeno", playtime: 15394, wins: 222, losses: 142, score: 80}
{team: "humans", rank: 110, sessionID: "537bef0b8c297aa0055d1184", name: "Floogle", playtime: 36828, wins: 224, losses: 145, score: 79}
{team: "humans", rank: 111, sessionID: "537c666826529de30cca38c2", name: "Jerson Otzoy", playtime: 46519, wins: 201, losses: 122, score: 79}
{team: "humans", rank: 112, sessionID: "537cb6b2a3cb63ea103c8000", name: "phisixersai", playtime: 8170, wins: 224, losses: 146, score: 78}
{team: "humans", rank: 113, sessionID: "537b9ba716613c8405155a73", name: "Beerdroid", playtime: 2547, wins: 226, losses: 150, score: 76}
{team: "humans", rank: 114, sessionID: "537c9bcd26529de30cca54e0", name: "Dood", playtime: 1121, wins: 219, losses: 145, score: 74}
{team: "humans", rank: 115, sessionID: "537c5b7c26529de30cca3439", name: "Markoth", playtime: 787, wins: 220, losses: 147, score: 73}
{team: "humans", rank: 116, sessionID: "537d1c297e1e10b705bbca7d", name: "chotic", playtime: 1292, wins: 218, losses: 146, score: 72}
{team: "humans", rank: 117, sessionID: "538484a53e0daa39051863e4", name: "Drago", playtime: 465, wins: 217, losses: 147, score: 70}
{team: "humans", rank: 118, sessionID: "537baaab03ff8dc005b8436c", name: "Petteri", playtime: 15235, wins: 219, losses: 149, score: 70}
{team: "humans", rank: 119, sessionID: "537ca0aa26529de30cca58c8", name: "3ng3l", playtime: 1703, wins: 217, losses: 147, score: 70}
{team: "humans", rank: 120, sessionID: "537b897f5bc02238050d181c", name: "Jremz", playtime: 8386, wins: 221, losses: 152, score: 69}
{team: "humans", rank: 121, sessionID: "537ba85554a7b1d5053bb366", name: "Taters", playtime: 9664, wins: 193, losses: 126, score: 67}
{team: "humans", rank: 122, sessionID: "537b9220e91b4d3b055263ea", name: "Listr", playtime: 34000, wins: 202, losses: 136, score: 66}
{team: "humans", rank: 123, sessionID: "537cadbd2f6e3aee0ed581f0", name: "satefa", playtime: 10106, wins: 220, losses: 156, score: 64}
{team: "humans", rank: 124, sessionID: "537d0a8c84c54c6e05c05db7", name: "redWizzard", playtime: 53180, wins: 183, losses: 120, score: 63}
{team: "humans", rank: 125, sessionID: "536c6d1c68b5258d0c4b7da8", name: "Tober", playtime: 452, wins: 216, losses: 153, score: 63}
{team: "humans", rank: 126, sessionID: "535ee1e023f09c2c0836a2a7", name: "(ノಠ益ಠ)ノ彡┻━┻", playtime: 3229, wins: 218, losses: 158, score: 60}
{team: "humans", rank: 127, sessionID: "5371626351ce9b3a05d95d5c", name: "Perrekus", playtime: 9047, wins: 215, losses: 158, score: 57}
{team: "humans", rank: 128, sessionID: "53968372e1b8fd0c08c693d5", name: "leoTest", playtime: 10739, wins: 215, losses: 158, score: 57}
{team: "humans", rank: 129, sessionID: "537cfcd69cce053a05c04ce1", name: "Soulnai", playtime: 22641, wins: 210, losses: 154, score: 56}
{team: "humans", rank: 130, sessionID: "537bf57795356a43065f7403", name: "Mordecai", playtime: 11262, wins: 215, losses: 159, score: 56}
{team: "humans", rank: 131, sessionID: "5377aabf05e1483905cb99d5", name: "Umaris", playtime: 23418, wins: 211, losses: 156, score: 55}
{team: "humans", rank: 132, sessionID: "537c93ce26529de30cca5029", name: "Willybe", playtime: 4772, wins: 212, losses: 158, score: 54}
{team: "humans", rank: 133, sessionID: "537babf403838cf0050ace06", name: "Brian Humphrey", playtime: 7300, wins: 212, losses: 158, score: 54}
{team: "humans", rank: 134, sessionID: "537e19e30efa6a37059e0b4f", name: "Aqrrc", playtime: 5003, wins: 210, losses: 163, score: 47}
{team: "humans", rank: 135, sessionID: "5395d19622ca0e39054ea5b1", name: "anykey", playtime: 29278, wins: 201, losses: 155, score: 46}
{team: "humans", rank: 136, sessionID: "537b7fd6e91b4d3b05525da3", name: "Gily", playtime: 17673, wins: 205, losses: 163, score: 42}
{team: "humans", rank: 137, sessionID: "537bbbcb3dc5f3c3055c3040", name: "roseaboveit", playtime: 12398, wins: 205, losses: 168, score: 37}
{team: "humans", rank: 138, sessionID: "538757897d99a7390561bb7f", name: "Jeremy", playtime: 6842, wins: 199, losses: 166, score: 33}
{team: "humans", rank: 139, sessionID: "537b8b845bc02238050d18e8", name: "Abel Soares Siqueira", playtime: 4945, wins: 204, losses: 171, score: 33}
{team: "humans", rank: 140, sessionID: "537b8a37e91b4d3b055260c9", name: "mattmatt", playtime: 77241, wins: 198, losses: 166, score: 32}
{team: "humans", rank: 141, sessionID: "537fc26d2a5a5acd08dbb0a5", name: "Melrakal", playtime: 2393, wins: 200, losses: 173, score: 27}
{team: "humans", rank: 142, sessionID: "537bd8a73dd7d35105f5fd6a", name: "drakiac", playtime: 14601, wins: 196, losses: 169, score: 27}
{team: "humans", rank: 143, sessionID: "537cea3d9cce053a05c04226", name: "CNKLC", playtime: 11663, wins: 200, losses: 175, score: 25}
{team: "humans", rank: 144, sessionID: "537bf9c20ff88b2c06286e7c", name: "Jotipalo", playtime: 4124, wins: 199, losses: 176, score: 23}
{team: "humans", rank: 145, sessionID: "53807d9405e52a3305de99f5", name: "Booya", playtime: 118149, wins: 173, losses: 152, score: 21}
{team: "humans", rank: 146, sessionID: "537bf291d3443b2b068c1056", name: "Penguin", playtime: 7688, wins: 196, losses: 176, score: 20}
{team: "humans", rank: 147, sessionID: "5380b82005e52a3305deb679", name: "TheWrightDev", playtime: 26967, wins: 194, losses: 176, score: 18}
{team: "humans", rank: 148, sessionID: "537cf2aa98a1be440545d878", name: "Kipernicus", playtime: 11906, wins: 189, losses: 181, score: 8}
{team: "humans", rank: 149, sessionID: "535e1f1187ab8481075b8d95", name: "freek", playtime: 0, wins: 190, losses: 183, score: 7}
{team: "humans", rank: 150, sessionID: "535f0911d42bda62085e40fc", name: "heated", playtime: 65, wins: 188, losses: 182, score: 6}
{team: "humans", rank: 151, sessionID: "539120d882f0bc470523cd6f", name: "Fitbos", playtime: 1622, wins: 0, losses: 0, score: 0}
{team: "humans", rank: 152, sessionID: "539875130599fa3a05ca7293", name: "jeimmy", playtime: 663, wins: 0, losses: 0, score: 0}
{team: "humans", rank: 153, sessionID: "537b7ccce91b4d3b05525cdc", name: "Bart2121", playtime: 4177, wins: 184, losses: 187, score: -3}
{team: "humans", rank: 154, sessionID: "53866ed58ca8b1120b8b816d", name: "Nikolai", playtime: 26320, wins: 184, losses: 187, score: -3}
{team: "humans", rank: 155, sessionID: "5385610f3e0daa390518e2f6", name: "Stravinsky", playtime: 8870, wins: 182, losses: 185, score: -3}
{team: "humans", rank: 156, sessionID: "537b94956c13497e05de816e", name: "Maix", playtime: 3163, wins: 183, losses: 186, score: -3}
{team: "humans", rank: 157, sessionID: "537187dcce8e453b05c9d21c", name: "NasytToast", playtime: 0, wins: 183, losses: 188, score: -5}
{team: "humans", rank: 158, sessionID: "537bb696dd932f6b05d370fd", name: "HandmadeMercury", playtime: 5539, wins: 169, losses: 175, score: -6}
{team: "humans", rank: 159, sessionID: "537b7f605bc02238050d14c9", name: "Jaden", playtime: 10547, wins: 182, losses: 192, score: -10}
{team: "humans", rank: 160, sessionID: "537c489314aaabe80c69f413", name: "HiddEnigma", playtime: 5850, wins: 181, losses: 191, score: -10}
{team: "humans", rank: 161, sessionID: "537bb7cd1e8dd17a054c1044", name: "Alexander the Grape", playtime: 30398, wins: 178, losses: 189, score: -11}
{team: "humans", rank: 162, sessionID: "538eada37558763705255a0c", name: "Evran", playtime: 22253, wins: 179, losses: 192, score: -13}
{team: "humans", rank: 163, sessionID: "5386aedf7d99a7390561802a", name: "Reclusiarch", playtime: 3281, wins: 178, losses: 192, score: -14}
{team: "humans", rank: 164, sessionID: "538b7202b924da3905157e07", name: "Mlatic", playtime: 723, wins: 176, losses: 190, score: -14}
{team: "humans", rank: 165, sessionID: "537c4411c209bbd60c16a3d8", name: "liorst1", playtime: 14571, wins: 178, losses: 193, score: -15}
{team: "humans", rank: 166, sessionID: "537fcaee2a5a5acd08dbb79c", name: "Booya2nd", playtime: 9598, wins: 153, losses: 168, score: -15}
{team: "humans", rank: 167, sessionID: "538ae84fb924da3905154172", name: "WAAW", playtime: 3668, wins: 177, losses: 194, score: -17}
{team: "humans", rank: 168, sessionID: "538751b78ca8b1120b8c0792", name: "DZC", playtime: 5512, wins: 170, losses: 188, score: -18}
{team: "humans", rank: 169, sessionID: "537c0ab50ff88b2c0628792e", name: "maxily", playtime: 18792, wins: 174, losses: 192, score: -18}
{team: "humans", rank: 170, sessionID: "537ba1347c89ec9805f4df76", name: "qkhhly", playtime: 9518, wins: 176, losses: 196, score: -20}
{team: "humans", rank: 171, sessionID: "538b4bbdd06c503805ff3b8a", name: "iamcodewar", playtime: 10939, wins: 178, losses: 198, score: -20}
{team: "humans", rank: 172, sessionID: "537f14e18fe881f1055df20a", name: "Alanor", playtime: 20437, wins: 150, losses: 174, score: -24}
{team: "humans", rank: 173, sessionID: "537f57ff3282d7a507695039", name: "Navi' Dendi", playtime: 4382, wins: 172, losses: 197, score: -25}
{team: "humans", rank: 174, sessionID: "537c14950ff88b2c06287f22", name: "lolka", playtime: 3820, wins: 172, losses: 200, score: -28}
{team: "humans", rank: 175, sessionID: "53570b7a1bfa9bba14b5e045", name: "Scott", playtime: 6228, wins: 173, losses: 202, score: -29}
{team: "humans", rank: 176, sessionID: "537bbd0f3cd816fa05a48401", name: "DonutBaron", playtime: 9716, wins: 165, losses: 195, score: -30}
{team: "humans", rank: 177, sessionID: "5381726d7585483905a7f978", name: "armlol", playtime: 5149, wins: 171, losses: 204, score: -33}
{team: "humans", rank: 178, sessionID: "538c0bfb56c36139052fa076", name: "Aura Temple Network", playtime: 7489, wins: 169, losses: 205, score: -36}
{team: "humans", rank: 179, sessionID: "538ec77af3691038051629c3", name: "BI", playtime: 3870, wins: 164, losses: 200, score: -36}
{team: "humans", rank: 180, sessionID: "537b889fe91b4d3b0552603e", name: "McDerp", playtime: 3415, wins: 161, losses: 203, score: -42}
{team: "humans", rank: 181, sessionID: "537d169a7e1e10b705bbc736", name: "Nitor", playtime: 7036, wins: 159, losses: 205, score: -46}
{team: "humans", rank: 182, sessionID: "53837eee6f3bea3a05faf5fb", name: "HeadCrusher", playtime: 22806, wins: 133, losses: 182, score: -49}
{team: "humans", rank: 183, sessionID: "537ba4887c89ec9805f4e10d", name: "Geralt", playtime: 2353, wins: 158, losses: 208, score: -50}
{team: "humans", rank: 184, sessionID: "53802aab05e52a3305de797e", name: "micblayo", playtime: 14726, wins: 159, losses: 209, score: -50}
{team: "humans", rank: 185, sessionID: "538b60bdb924da39051576e2", name: "mydoom", playtime: 9270, wins: 159, losses: 209, score: -50}
{team: "humans", rank: 186, sessionID: "5380f3917585483905a7c0b3", name: "Lalaland1125", playtime: 689, wins: 160, losses: 211, score: -51}
{team: "humans", rank: 187, sessionID: "537b8125e91b4d3b05525e1e", name: "Garrett", playtime: 1833, wins: 157, losses: 212, score: -55}
{team: "humans", rank: 188, sessionID: "537b8c60e91b4d3b05526181", name: "Usopp", playtime: 8340, wins: 156, losses: 217, score: -61}
{team: "humans", rank: 189, sessionID: "537b9b4116613c8405155a41", name: "Y0DA", playtime: 5351, wins: 154, losses: 216, score: -62}
{team: "humans", rank: 190, sessionID: "5387f127d06c503805fdcd97", name: "Hexadecimage", playtime: 89476, wins: 151, losses: 222, score: -71}
{team: "humans", rank: 191, sessionID: "539265012446a44105389a8c", name: "odeakihumi", playtime: 23819, wins: 143, losses: 219, score: -76}
{team: "humans", rank: 192, sessionID: "537ba73a980cfcba051f19c5", name: "Torg", playtime: 7045, wins: 145, losses: 221, score: -76}
{team: "humans", rank: 193, sessionID: "5392bbc0e04ba13805ed4c0f", name: "iownspace", playtime: 51659, wins: 146, losses: 223, score: -77}
{team: "humans", rank: 194, sessionID: "537bf14bf8b46fbd0544fc18", name: "danshou", playtime: 22590, wins: 148, losses: 226, score: -78}
{team: "humans", rank: 195, sessionID: "537b212dfe0ec03905fcd712", name: "ThunderClan", playtime: 766, wins: 144, losses: 228, score: -84}
{team: "humans", rank: 196, sessionID: "536540d51415c79e648b1de5", name: "Nick04 Bubonic", playtime: 12, wins: 138, losses: 222, score: -84}
{team: "humans", rank: 197, sessionID: "537fb0792a5a5acd08dba76f", name: "Slackus", playtime: 49736, wins: 141, losses: 226, score: -85}
{team: "humans", rank: 198, sessionID: "537d44911333605305550411", name: "tehowner", playtime: 2969, wins: 138, losses: 223, score: -85}
{team: "humans", rank: 199, sessionID: "537df429933d99860613ab2d", name: "Farafonoff", playtime: 4315, wins: 142, losses: 228, score: -86}
{team: "humans", rank: 200, sessionID: "538501137d99a7390560db54", name: "Joodoc", playtime: 6042, wins: 144, losses: 231, score: -87}
{team: "humans", rank: 201, sessionID: "537b8a58e91b4d3b055260d8", name: "Terebijoke", playtime: 85680, wins: 122, losses: 210, score: -88}
{team: "humans", rank: 202, sessionID: "537c110195356a43065f8450", name: "Chen Yu LIu", playtime: 843, wins: 140, losses: 232, score: -92}
{team: "humans", rank: 203, sessionID: "537dba4613add33a051bf680", name: "Bobbybaby", playtime: 1509, wins: 140, losses: 232, score: -92}
{team: "humans", rank: 204, sessionID: "537b9003e91b4d3b05526329", name: "Coreth", playtime: 11677, wins: 131, losses: 224, score: -93}
{team: "humans", rank: 205, sessionID: "538b618bd06c503805ff4210", name: "robat", playtime: 5776, wins: 138, losses: 234, score: -96}
{team: "humans", rank: 206, sessionID: "5383b7196f3bea3a05fb1472", name: "lilos", playtime: 2464, wins: 135, losses: 232, score: -97}
{team: "humans", rank: 207, sessionID: "537f8672d409ac270861c842", name: "ThatOtherPerson", playtime: 2018, wins: 137, losses: 236, score: -99}
{team: "humans", rank: 208, sessionID: "53809c2c7585483905a795f1", name: "都比", playtime: 28855, wins: 136, losses: 235, score: -99}
{team: "humans", rank: 209, sessionID: "537c866f14aaabe80c6a0f90", name: "Energy", playtime: 1276, wins: 130, losses: 229, score: -99}
{team: "humans", rank: 210, sessionID: "538e4af0755876370525291c", name: "commando Tech", playtime: 3725, wins: 136, losses: 236, score: -100}
{team: "humans", rank: 211, sessionID: "537b9da67c89ec9805f4de0b", name: "tembelu", playtime: 9298, wins: 129, losses: 230, score: -101}
{team: "humans", rank: 212, sessionID: "53972a5f2546283905a3bdc5", name: "holyKoT", playtime: 2171, wins: 131, losses: 241, score: -110}
{team: "humans", rank: 213, sessionID: "537ba6dd980cfcba051f199d", name: "Aaron1011", playtime: 3449, wins: 130, losses: 240, score: -110}
{team: "humans", rank: 214, sessionID: "537c68bf26529de30cca39cb", name: "ESWAT", playtime: 893, wins: 128, losses: 239, score: -111}
{team: "humans", rank: 215, sessionID: "537d5fdb3dcf67c40571fb13", name: "JustTurrable", playtime: 264, wins: 125, losses: 240, score: -115}
{team: "humans", rank: 216, sessionID: "537e4268e1489fe206667e26", name: "EvanK", playtime: 668, wins: 125, losses: 245, score: -120}
{team: "humans", rank: 217, sessionID: "537be834a3b60b390500e0a3", name: "gilxa1226", playtime: 394, wins: 125, losses: 245, score: -120}
{team: "humans", rank: 218, sessionID: "537bbcfd9e1bf5f905926ebf", name: "Qin Shi Huang", playtime: 4691, wins: 125, losses: 246, score: -121}
{team: "humans", rank: 219, sessionID: "537b9bd016613c8405155a8b", name: "WaffleFries", playtime: 2944, wins: 121, losses: 246, score: -125}
{team: "humans", rank: 220, sessionID: "537b8a65e91b4d3b055260e0", name: "Gurra", playtime: 6295, wins: 107, losses: 233, score: -126}
{team: "humans", rank: 221, sessionID: "537e2025933d99860613c614", name: "Encosia", playtime: 2025, wins: 118, losses: 250, score: -132}
{team: "humans", rank: 222, sessionID: "537bb694dd932f6b05d370fc", name: "Kaboomm", playtime: 4333, wins: 116, losses: 253, score: -137}
{team: "humans", rank: 223, sessionID: "538001aebf8ae33a0501b98e", name: "taichi_kunnn", playtime: 3524, wins: 112, losses: 254, score: -142}
{team: "humans", rank: 224, sessionID: "53630b6573bdb4f7045a772c", name: "Lonib", playtime: 0, wins: 113, losses: 257, score: -144}
{team: "humans", rank: 225, sessionID: "5360f83b67c29a0609ddceb4", name: "Readper", playtime: 0, wins: 109, losses: 264, score: -155}
{team: "humans", rank: 226, sessionID: "53717f4551ce9b3a05d96043", name: "cpkenn09y", playtime: 0, wins: 106, losses: 263, score: -157}
{team: "humans", rank: 227, sessionID: "537b9b9416613c8405155a6b", name: "Rubini", playtime: 6763, wins: 103, losses: 271, score: -168}
{team: "humans", rank: 228, sessionID: "53902ad775587637052615d9", name: "Loxk", playtime: 32690, wins: 89, losses: 265, score: -176}
{team: "humans", rank: 229, sessionID: "537b93886c13497e05de80a6", name: "Klomnar", playtime: 405, wins: 98, losses: 274, score: -176}
{team: "humans", rank: 230, sessionID: "537b9c7c16613c8405155ae0", name: "dpen2000", playtime: 4313, wins: 93, losses: 273, score: -180}
{team: "humans", rank: 231, sessionID: "53692c2d84e82a3a0553305e", name: "rnprdk", playtime: 0, wins: 94, losses: 278, score: -184}
{team: "humans", rank: 232, sessionID: "537be99f20a501380564e764", name: "rizend", playtime: 3017, wins: 92, losses: 279, score: -187}
{team: "humans", rank: 233, sessionID: "537ba94d54a7b1d5053bb426", name: "Rokner", playtime: 3911, wins: 90, losses: 282, score: -192}
{team: "humans", rank: 234, sessionID: "537b949e556db17605be74ff", name: "baldeagle", playtime: 7698, wins: 91, losses: 284, score: -193}
{team: "humans", rank: 235, sessionID: "535f0cdfc2e83ad9048faef0", name: "Mobius", playtime: 0, wins: 87, losses: 288, score: -201}
{team: "humans", rank: 236, sessionID: "537bbe446d5f9f2f06e0c674", name: "Lakk", playtime: 12072, wins: 76, losses: 296, score: -220}
{team: "humans", rank: 237, sessionID: "535ac395d83daa1a052494c1", name: "Michael S.", playtime: 304, wins: 68, losses: 304, score: -236}
{team: "humans", rank: 238, sessionID: "53718c6cce8e453b05c9d298", name: "Blitz", playtime: 0, wins: 68, losses: 306, score: -238}
{team: "humans", rank: 239, sessionID: "536310848e22980605aa58c3", name: "sjarvie", playtime: 0, wins: 66, losses: 308, score: -242}
{team: "humans", rank: 240, sessionID: "537ba10e51e98aa705b605df", name: "Deleu", playtime: 7241, wins: 61, losses: 307, score: -246}
{team: "humans", rank: 241, sessionID: "537b92765bc02238050d1bf0", name: "Deneim", playtime: 13791, wins: 62, losses: 312, score: -250}
{team: "humans", rank: 242, sessionID: "538e609af36910380515f8d6", name: "Volgax", playtime: 6774, wins: 59, losses: 310, score: -251}
{team: "humans", rank: 243, sessionID: "537ba62557494fc405a6faac", name: "Zandrasco", playtime: 4193, wins: 60, losses: 315, score: -255}
{team: "humans", rank: 244, sessionID: "537ecf5414bb1d38053b58bb", name: "Sir Mouse", playtime: 16021, wins: 52, losses: 321, score: -269}
{team: "humans", rank: 245, sessionID: "538c8faf5572d43b05214e3b", name: "AceWizard", playtime: 54, wins: 45, losses: 315, score: -270}
{team: "humans", rank: 246, sessionID: "537deb820efa6a37059df268", name: "MadMan30", playtime: 316, wins: 46, losses: 319, score: -273}
{team: "humans", rank: 247, sessionID: "537bf38495356a43065f7291", name: "NicRio", playtime: 130, wins: 43, losses: 316, score: -273}
{team: "humans", rank: 248, sessionID: "537cd876665bde1b13ffdad4", name: "Is_G", playtime: 776, wins: 44, losses: 319, score: -275}
{team: "humans", rank: 249, sessionID: "537bb14b4f81f53805b6366a", name: "jeromeASF", playtime: 335, wins: 41, losses: 318, score: -277}
{team: "humans", rank: 250, sessionID: "5381745005e52a3305df1c23", name: "El Psy Congr00", playtime: 1032, wins: 41, losses: 319, score: -278}
{team: "humans", rank: 251, sessionID: "538451183e0daa39051845a0", name: "loquele", playtime: 549, wins: 43, losses: 322, score: -279}
{team: "humans", rank: 252, sessionID: "537b606a2daeeb3905a9292f", name: "DeathStalker", playtime: 166, wins: 38, losses: 319, score: -281}
{team: "humans", rank: 253, sessionID: "537bd56ab0d1766d05243002", name: "thelion", playtime: 338, wins: 40, losses: 323, score: -283}
{team: "humans", rank: 254, sessionID: "537d15d37e1e10b705bbc6ef", name: "Diegobrp", playtime: 2132, wins: 37, losses: 321, score: -284}
{team: "humans", rank: 255, sessionID: "537caa102f6e3aee0ed57f84", name: "Sakares Saengkaew", playtime: 34, wins: 37, losses: 321, score: -284}
{team: "humans", rank: 256, sessionID: "53627e2fca5c6f3c11ebc9b1", name: "Animex", playtime: 0, wins: 38, losses: 324, score: -286}
{team: "humans", rank: 257, sessionID: "537bc0ad1c8dfa820699fcb9", name: "Black Mage Wizard Guy", playtime: 691, wins: 37, losses: 323, score: -286}
{team: "humans", rank: 258, sessionID: "535f07d39894af8f7fd84e85", name: "Maksym", playtime: 0, wins: 38, losses: 327, score: -289}
{team: "humans", rank: 259, sessionID: "537b98e33803a287057582a8", name: "Angeland", playtime: 1758, wins: 41, losses: 330, score: -289}
{team: "humans", rank: 260, sessionID: "537c63f426529de30cca37ba", name: "Dmitry", playtime: 181, wins: 35, losses: 325, score: -290}
{team: "humans", rank: 261, sessionID: "537ceb1798a1be440545d40e", name: "jaba", playtime: 572, wins: 35, losses: 325, score: -290}
{team: "humans", rank: 262, sessionID: "537c0f3f0ff88b2c06287c2b", name: "Quan Pham", playtime: 4353, wins: 40, losses: 332, score: -292}
{team: "humans", rank: 263, sessionID: "537d60467e6f2dba051060b6", name: "rhall", playtime: 67548, wins: 11, losses: 316, score: -305}
{team: "humans", rank: 264, sessionID: "5384c9de7d99a7390560bfd7", name: "Secathor", playtime: 16404, wins: 28, losses: 345, score: -317}
{team: "humans", rank: 265, sessionID: "538a08bed06c503805feb6b0", name: "Nemoy", playtime: 1643, wins: 0, losses: 319, score: -319}
{team: "humans", rank: 266, sessionID: "537c3806f1d9cfe4096ba9d8", name: "Hannofcart", playtime: 17397, wins: 0, losses: 323, score: -323}
{team: "humans", rank: 267, sessionID: "537c4d5f26529de30cca2e68", name: "Morphumax", playtime: 58395, wins: 0, losses: 323, score: -323}
{team: "humans", rank: 268, sessionID: "53864aa37d99a73905615dda", name: "shoetest", playtime: 14876, wins: 0, losses: 324, score: -324}
{team: "humans", rank: 269, sessionID: "537b97cd556db17605be76c3", name: "domrein", playtime: 12339, wins: 0, losses: 324, score: -324}
{team: "humans", rank: 270, sessionID: "53835f1fc85c223a05f49ddb", name: "hace", playtime: 9175, wins: 0, losses: 325, score: -325}
{team: "humans", rank: 271, sessionID: "53666cc75af8b2c71dc017e8", name: "Bonesdog", playtime: 0, wins: 0, losses: 326, score: -326}
{team: "humans", rank: 272, sessionID: "537be26d20a501380564e359", name: "Cinnamon Scrolls", playtime: 6619, wins: 0, losses: 330, score: -330}
{team: "humans", rank: 273, sessionID: "537b8f3ce91b4d3b055262d7", name: "asdasd", playtime: 242330, wins: 0, losses: 335, score: -335}
{team: "humans", rank: 274, sessionID: "5383551cc85c223a05f49992", name: "marthyi", playtime: 30008, wins: 325, losses: 43, score: 282}
{team: "humans", rank: 275, sessionID: "538f5334f369103805167613", name: "Leshka", playtime: 10409, wins: 93, losses: 282, score: -189}
]
results.greed.ogres = [
{team: "ogres", rank: 1, sessionID: "537b958e6c13497e05de81f2", name: "Bellardia", playtime: 189111, wins: 387, losses: 7, score: 380}
{team: "ogres", rank: 2, sessionID: "537c11df95356a43065f84c9", name: "blinkingTore", playtime: 38360, wins: 381, losses: 9, score: 372}
{team: "ogres", rank: 3, sessionID: "537c09ee95356a43065f801c", name: "Eye", playtime: 172960, wins: 370, losses: 18, score: 352}
{team: "ogres", rank: 4, sessionID: "537b7d475bc02238050d1439", name: "Cripi", playtime: 61966, wins: 373, losses: 23, score: 350}
{team: "ogres", rank: 5, sessionID: "537e317450d1673a054b7dd2", name: "Pop-up", playtime: 39939, wins: 369, losses: 26, score: 343}
{team: "ogres", rank: 6, sessionID: "537e364a50d1673a054b8322", name: "Ravenclaw", playtime: 4579, wins: 367, losses: 24, score: 343}
{team: "ogres", rank: 7, sessionID: "537cb51fa0d7a5cc10992b11", name: "JerryP", playtime: 52872, wins: 368, losses: 33, score: 335}
{team: "ogres", rank: 8, sessionID: "537bd37888a86e67053f9d61", name: "Mickydtron", playtime: 38533, wins: 368, losses: 35, score: 333}
{team: "ogres", rank: 9, sessionID: "5384a3f63e0daa390518765d", name: "ProfBoesch", playtime: 10366, wins: 365, losses: 34, score: 331}
{team: "ogres", rank: 10, sessionID: "538064d27585483905a77f9f", name: "KaosWalking", playtime: 88663, wins: 359, losses: 35, score: 324}
{team: "ogres", rank: 11, sessionID: "53892a98d06c503805fe522b", name: "Urdaris", playtime: 87750, wins: 353, losses: 32, score: 321}
{team: "ogres", rank: 12, sessionID: "53668d64c6ab10ef1ea9a10d", name: "no_login_found", playtime: 11313, wins: 355, losses: 46, score: 309}
{team: "ogres", rank: 13, sessionID: "537f68bc14f08dec07950945", name: "Take it easy", playtime: 2839, wins: 348, losses: 47, score: 301}
{team: "ogres", rank: 14, sessionID: "537bac56b0d477e005347f67", name: "gosunero", playtime: 41429, wins: 346, losses: 54, score: 292}
{team: "ogres", rank: 15, sessionID: "536d258f68b5258d0c4b96ed", name: "Jex", playtime: 4910, wins: 337, losses: 48, score: 289}
{team: "ogres", rank: 16, sessionID: "537bcba8a797656f07c93db5", name: "Durbination", playtime: 13465, wins: 337, losses: 49, score: 288}
{team: "ogres", rank: 17, sessionID: "5381e48a7585483905a82a2d", name: "Edoardo Morandi", playtime: 96844, wins: 330, losses: 49, score: 281}
{team: "ogres", rank: 18, sessionID: "538471737d99a73905608fb4", name: "Artraxus", playtime: 61114, wins: 336, losses: 60, score: 276}
{team: "ogres", rank: 19, sessionID: "537ba9a154a7b1d5053bb45a", name: "skeltoac", playtime: 39087, wins: 330, losses: 55, score: 275}
{team: "ogres", rank: 20, sessionID: "537be145a3b60b390500dcc3", name: "ReyO", playtime: 204112, wins: 328, losses: 55, score: 273}
{team: "ogres", rank: 21, sessionID: "537cc939665bde1b13ffd197", name: "Orson Peters", playtime: 24821, wins: 333, losses: 60, score: 273}
{team: "ogres", rank: 22, sessionID: "5395cf0b7362c439054f7c3a", name: "nino48", playtime: 33541, wins: 327, losses: 62, score: 265}
{team: "ogres", rank: 23, sessionID: "537d541e3dcf67c40571f792", name: "pbd", playtime: 4062, wins: 319, losses: 84, score: 235}
{team: "ogres", rank: 24, sessionID: "537e284650d1673a054b7602", name: "Tech", playtime: 13208, wins: 306, losses: 71, score: 235}
{team: "ogres", rank: 25, sessionID: "537d025184c54c6e05c059b7", name: "Storm0x2a", playtime: 1026, wins: 306, losses: 71, score: 235}
{team: "ogres", rank: 26, sessionID: "537bed83f575b482052f03a0", name: "Blindfold", playtime: 45865, wins: 303, losses: 69, score: 234}
{team: "ogres", rank: 27, sessionID: "537bc4750de0a02c07e8797f", name: "Walter Danilo Galante", playtime: 24476, wins: 313, losses: 84, score: 229}
{team: "ogres", rank: 28, sessionID: "538d0a4356c3613905301e50", name: "Fedux", playtime: 1862, wins: 300, losses: 79, score: 221}
{team: "ogres", rank: 29, sessionID: "537b8cbee91b4d3b0552619e", name: "Nazywam", playtime: 6395, wins: 303, losses: 83, score: 220}
{team: "ogres", rank: 30, sessionID: "5382502a7585483905a85e42", name: "vlizard", playtime: 803, wins: 308, losses: 89, score: 219}
{team: "ogres", rank: 31, sessionID: "5385d1cf3e0daa3905191e57", name: "olu", playtime: 65635, wins: 301, losses: 86, score: 215}
{team: "ogres", rank: 32, sessionID: "537babd003838cf0050acdf2", name: "Pentar", playtime: 36006, wins: 309, losses: 95, score: 214}
{team: "ogres", rank: 33, sessionID: "537c31112b08c344082e22de", name: "Agathanar", playtime: 41371, wins: 308, losses: 94, score: 214}
{team: "ogres", rank: 34, sessionID: "537cd98ae4523d0113ba5ac7", name: "Tehvudgaw", playtime: 81681, wins: 298, losses: 94, score: 204}
{team: "ogres", rank: 35, sessionID: "537b803ae91b4d3b05525dc7", name: "Forsaken", playtime: 50237, wins: 294, losses: 91, score: 203}
{team: "ogres", rank: 36, sessionID: "537b7e44e91b4d3b05525d38", name: "COGSMITH", playtime: 284674, wins: 300, losses: 98, score: 202}
{team: "ogres", rank: 37, sessionID: "537ba3e0903fd2b80515539d", name: "Statik", playtime: 64062, wins: 294, losses: 98, score: 196}
{team: "ogres", rank: 38, sessionID: "537b85e15bc02238050d16d4", name: "Cuef", playtime: 81666, wins: 286, losses: 90, score: 196}
{team: "ogres", rank: 39, sessionID: "5392f7b1304ab93805efa3d2", name: "Cracker", playtime: 2576, wins: 282, losses: 90, score: 192}
{team: "ogres", rank: 40, sessionID: "537b9c42ffeaa29e051b2657", name: "JamesJNadeau.com", playtime: 28164, wins: 295, losses: 110, score: 185}
{team: "ogres", rank: 41, sessionID: "537ce37be9c4e97c05e3ea66", name: "Quasar", playtime: 21773, wins: 287, losses: 104, score: 183}
{team: "ogres", rank: 42, sessionID: "537baa2403ff8dc005b8433e", name: "Almatia", playtime: 15387, wins: 291, losses: 109, score: 182}
{team: "ogres", rank: 43, sessionID: "537b87f55bc02238050d1786", name: "mordonne", playtime: 71830, wins: 277, losses: 103, score: 174}
{team: "ogres", rank: 44, sessionID: "537e56129008f87b0541cd03", name: "O'Connor", playtime: 55869, wins: 281, losses: 109, score: 172}
{team: "ogres", rank: 45, sessionID: "537ba9e57b9ffadb05476784", name: "timmox", playtime: 13280, wins: 256, losses: 86, score: 170}
{team: "ogres", rank: 46, sessionID: "537b86e75bc02238050d1721", name: "Tomas", playtime: 21845, wins: 275, losses: 106, score: 169}
{team: "ogres", rank: 47, sessionID: "537bebbea3b60b390500e280", name: "Buge", playtime: 3506, wins: 281, losses: 118, score: 163}
{team: "ogres", rank: 48, sessionID: "53854c0b7d99a7390560f9f4", name: "MatBot", playtime: 15049, wins: 281, losses: 119, score: 162}
{team: "ogres", rank: 49, sessionID: "5380a9bb7585483905a79dff", name: "salesman", playtime: 60290, wins: 277, losses: 118, score: 159}
{team: "ogres", rank: 50, sessionID: "53949f635009fe5b075bb584", name: "Dematerial", playtime: 4661, wins: 273, losses: 116, score: 157}
{team: "ogres", rank: 51, sessionID: "53832a3ec85c223a05f48736", name: "Popey", playtime: 37593, wins: 256, losses: 104, score: 152}
{team: "ogres", rank: 52, sessionID: "537b89a2e91b4d3b05526082", name: "Ryemane", playtime: 20671, wins: 276, losses: 125, score: 151}
{team: "ogres", rank: 53, sessionID: "537cd542e4523d0113ba5782", name: "schups", playtime: 20886, wins: 277, losses: 127, score: 150}
{team: "ogres", rank: 54, sessionID: "537be1cca3b60b390500dd24", name: "ohsnap", playtime: 3574, wins: 273, losses: 127, score: 146}
{team: "ogres", rank: 55, sessionID: "538bef7356c36139052f8c80", name: "Hephaestian", playtime: 9626, wins: 272, losses: 131, score: 141}
{team: "ogres", rank: 56, sessionID: "537c527026529de30cca302f", name: "Jinrai", playtime: 23726, wins: 269, losses: 129, score: 140}
{team: "ogres", rank: 57, sessionID: "538471087d99a73905608f84", name: "Basque", playtime: 37986, wins: 269, losses: 130, score: 139}
{team: "ogres", rank: 58, sessionID: "537cd3c3665bde1b13ffd74c", name: "Stormaggedon Dark Lord of All", playtime: 27471, wins: 261, losses: 123, score: 138}
{team: "ogres", rank: 59, sessionID: "537b984f3803a2870575824f", name: "Cody", playtime: 85426, wins: 259, losses: 138, score: 121}
{team: "ogres", rank: 60, sessionID: "5383ead92757353805a96ff4", name: "hotdogeater", playtime: 55657, wins: 258, losses: 137, score: 121}
{team: "ogres", rank: 61, sessionID: "537d2a397e1e10b705bbd2c8", name: "PatchworkKnight", playtime: 23663, wins: 257, losses: 138, score: 119}
{team: "ogres", rank: 62, sessionID: "537b8185e91b4d3b05525e3c", name: "FlameFrost", playtime: 29939, wins: 251, losses: 142, score: 109}
{team: "ogres", rank: 63, sessionID: "537bb4168623bc410575f208", name: "NitrousDave", playtime: 29420, wins: 252, losses: 143, score: 109}
{team: "ogres", rank: 64, sessionID: "537f4fa1c3067a7b07ba7786", name: "Zodd", playtime: 8820, wins: 248, losses: 140, score: 108}
{team: "ogres", rank: 65, sessionID: "538358b978171d3d057eb471", name: "Lololala", playtime: 39892, wins: 252, losses: 144, score: 108}
{team: "ogres", rank: 66, sessionID: "537f224c8fe881f1055dfb7e", name: "Artoemius", playtime: 1470, wins: 251, losses: 150, score: 101}
{team: "ogres", rank: 67, sessionID: "537b7b25e91b4d3b05525b61", name: "Makaze", playtime: 21714, wins: 248, losses: 149, score: 99}
{team: "ogres", rank: 68, sessionID: "537cf18c9cce053a05c0467f", name: "CodeBane", playtime: 39749, wins: 233, losses: 137, score: 96}
{team: "ogres", rank: 69, sessionID: "537baac910c8a6e405b9cf92", name: "cooler", playtime: 49558, wins: 229, losses: 134, score: 95}
{team: "ogres", rank: 70, sessionID: "5380d0a405e52a3305dec328", name: "DoctorTacoPhD", playtime: 9723, wins: 249, losses: 155, score: 94}
{team: "ogres", rank: 71, sessionID: "53948cfc5009fe5b075ba92d", name: "zeus1200", playtime: 236, wins: 246, losses: 152, score: 94}
{team: "ogres", rank: 72, sessionID: "537c66d114aaabe80c6a0072", name: "Vratislav", playtime: 21203, wins: 237, losses: 144, score: 93}
{team: "ogres", rank: 73, sessionID: "537b996016613c8405155922", name: "Dafe", playtime: 701, wins: 232, losses: 140, score: 92}
{team: "ogres", rank: 74, sessionID: "537b8271e91b4d3b05525e8e", name: "@billyvg", playtime: 20844, wins: 243, losses: 154, score: 89}
{team: "ogres", rank: 75, sessionID: "537caafc2f6e3aee0ed58007", name: "Sakares", playtime: 1257, wins: 237, losses: 151, score: 86}
{team: "ogres", rank: 76, sessionID: "538142de05e52a3305df037d", name: "Meowth", playtime: 12054, wins: 241, losses: 155, score: 86}
{team: "ogres", rank: 77, sessionID: "537155a1ee35af3905987a87", name: "Lanner", playtime: 3658, wins: 236, losses: 152, score: 84}
{team: "ogres", rank: 78, sessionID: "537b99573803a287057582e3", name: "DavyWong", playtime: 16467, wins: 242, losses: 161, score: 81}
{team: "ogres", rank: 79, sessionID: "537be8fda3b60b390500e105", name: "BeAsT MoDe", playtime: 2097, wins: 234, losses: 153, score: 81}
{team: "ogres", rank: 80, sessionID: "537b96696c13497e05de8271", name: "vkeg", playtime: 1883, wins: 236, losses: 157, score: 79}
{team: "ogres", rank: 81, sessionID: "537ca60cb128a1850e083077", name: "SPAMR", playtime: 19978, wins: 233, losses: 154, score: 79}
{team: "ogres", rank: 82, sessionID: "537b5662fe0ec03905fcdcd4", name: "pages", playtime: 22679, wins: 231, losses: 155, score: 76}
{team: "ogres", rank: 83, sessionID: "537bf90295356a43065f7640", name: "", playtime: 167, wins: 218, losses: 144, score: 74}
{team: "ogres", rank: 84, sessionID: "535de130c969efa7053345ab", name: "Racksickle", playtime: 319, wins: 231, losses: 157, score: 74}
{team: "ogres", rank: 85, sessionID: "537c2facf67b6b1c08af376f", name: "greyworm", playtime: 22755, wins: 234, losses: 160, score: 74}
{team: "ogres", rank: 86, sessionID: "537eef8c8fe881f1055dddb6", name: "huang123", playtime: 2590, wins: 234, losses: 161, score: 73}
{team: "ogres", rank: 87, sessionID: "53934430304ab93805efcf4f", name: "LilDooda", playtime: 16603, wins: 229, losses: 156, score: 73}
{team: "ogres", rank: 88, sessionID: "5389c83fb924da39051485d2", name: "nineties", playtime: 58607, wins: 183, losses: 113, score: 70}
{team: "ogres", rank: 89, sessionID: "537b760f1ed81a3b05c4fbfe", name: "Xavion", playtime: 3593, wins: 229, losses: 159, score: 70}
{team: "ogres", rank: 90, sessionID: "5383ec74087dc139054d8658", name: "cattycat", playtime: 384, wins: 228, losses: 160, score: 68}
{team: "ogres", rank: 91, sessionID: "538375bb78171d3d057ebdb6", name: "Johnwg7", playtime: 65297, wins: 233, losses: 165, score: 68}
{team: "ogres", rank: 92, sessionID: "537b99063803a287057582c1", name: "Simba", playtime: 16847, wins: 230, losses: 163, score: 67}
{team: "ogres", rank: 93, sessionID: "53929b482446a4410538b59d", name: "Buh", playtime: 28766, wins: 230, losses: 165, score: 65}
{team: "ogres", rank: 94, sessionID: "537bf9410ff88b2c06286e43", name: "Ulfberht", playtime: 26046, wins: 232, losses: 171, score: 61}
{team: "ogres", rank: 95, sessionID: "537bc6e24add491607deaaa1", name: "Victor Hugo", playtime: 31094, wins: 221, losses: 163, score: 58}
{team: "ogres", rank: 96, sessionID: "53809a807585483905a79529", name: "stderr", playtime: 20568, wins: 229, losses: 173, score: 56}
{team: "ogres", rank: 97, sessionID: "537b8643e91b4d3b05525f83", name: "Austinh100", playtime: 12405, wins: 225, losses: 176, score: 49}
{team: "ogres", rank: 98, sessionID: "537c7fad26529de30cca440d", name: "efraglebagga", playtime: 22417, wins: 219, losses: 172, score: 47}
{team: "ogres", rank: 99, sessionID: "537dde5d933d99860613a256", name: "Meojifo", playtime: 50047, wins: 222, losses: 177, score: 45}
{team: "ogres", rank: 100, sessionID: "537bc0eb467df184064bb865", name: "Diginaut", playtime: 8029, wins: 225, losses: 180, score: 45}
{team: "ogres", rank: 101, sessionID: "538390336f3bea3a05faff49", name: "Ghostly_Cookie", playtime: 5804, wins: 221, losses: 177, score: 44}
{team: "ogres", rank: 102, sessionID: "537f2cb8fdacc2fa050231bf", name: "crazydiv", playtime: 12170, wins: 212, losses: 170, score: 42}
{team: "ogres", rank: 103, sessionID: "537ef0e78fe881f1055dde46", name: "Æ", playtime: 97439, wins: 204, losses: 163, score: 41}
{team: "ogres", rank: 104, sessionID: "537b96f216613c84051557f1", name: "mda", playtime: 11544, wins: 222, losses: 181, score: 41}
{team: "ogres", rank: 105, sessionID: "537b8231e91b4d3b05525e7e", name: "BobFranz", playtime: 632, wins: 222, losses: 184, score: 38}
{team: "ogres", rank: 106, sessionID: "537b9a1d3803a2870575835f", name: "pvande", playtime: 39177, wins: 216, losses: 179, score: 37}
{team: "ogres", rank: 107, sessionID: "537c23fffa0d80a106b10eb4", name: "Nylan", playtime: 1236, wins: 204, losses: 174, score: 30}
{team: "ogres", rank: 108, sessionID: "537b91795bc02238050d1b80", name: "Orange!", playtime: 22940, wins: 210, losses: 181, score: 29}
{team: "ogres", rank: 109, sessionID: "537b8cfde91b4d3b055261b6", name: "Thumb", playtime: 22271, wins: 212, losses: 185, score: 27}
{team: "ogres", rank: 110, sessionID: "53978487888ab73905184003", name: "Nick2", playtime: 5539, wins: 197, losses: 173, score: 24}
{team: "ogres", rank: 111, sessionID: "5386313f8ca8b1120b8b58a3", name: "fudgy", playtime: 2744, wins: 213, losses: 193, score: 20}
{team: "ogres", rank: 112, sessionID: "537cbcc062dea6b811f109c7", name: "Kirstin", playtime: 23151, wins: 211, losses: 192, score: 19}
{team: "ogres", rank: 113, sessionID: "53814d4605e52a3305df086c", name: "jsut", playtime: 21961, wins: 208, losses: 189, score: 19}
{team: "ogres", rank: 114, sessionID: "5384c51f7d99a7390560bce1", name: "Xhuy", playtime: 4562, wins: 204, losses: 188, score: 16}
{team: "ogres", rank: 115, sessionID: "536259c5fc1acf300b2ebcb7", name: "DarthNato", playtime: 26611, wins: 206, losses: 193, score: 13}
{team: "ogres", rank: 116, sessionID: "538db7f65572d43b0521cf9c", name: "rehashed", playtime: 12008, wins: 201, losses: 188, score: 13}
{team: "ogres", rank: 117, sessionID: "5361946c801ef0010d3a3369", name: "DeathScythe", playtime: 0, wins: 209, losses: 197, score: 12}
{team: "ogres", rank: 118, sessionID: "537f5d073282d7a5076952e6", name: "kraxor", playtime: 14420, wins: 191, losses: 179, score: 12}
{team: "ogres", rank: 119, sessionID: "537bc98ba968548907de6dad", name: "Huragok", playtime: 325, wins: 207, losses: 197, score: 10}
{team: "ogres", rank: 120, sessionID: "53718545d0d9b1370555448b", name: "SuchNoob", playtime: 0, wins: 196, losses: 188, score: 8}
{team: "ogres", rank: 121, sessionID: "538a1888b924da390514c2d6", name: "Lomidrevo", playtime: 9470, wins: 207, losses: 199, score: 8}
{team: "ogres", rank: 122, sessionID: "537b92265bc02238050d1bc3", name: "Drew", playtime: 21829, wins: 199, losses: 192, score: 7}
{team: "ogres", rank: 123, sessionID: "537b976e556db17605be769e", name: "Drak", playtime: 9554, wins: 202, losses: 197, score: 5}
{team: "ogres", rank: 124, sessionID: "5380b07205e52a3305deb2ea", name: "LinaLin", playtime: 1852, wins: 204, losses: 199, score: 5}
{team: "ogres", rank: 125, sessionID: "537b84de5bc02238050d1699", name: "WoLfulus", playtime: 9762, wins: 204, losses: 200, score: 4}
{team: "ogres", rank: 126, sessionID: "537b7f945bc02238050d14d8", name: "Shack", playtime: 16108, wins: 199, losses: 197, score: 2}
{team: "ogres", rank: 127, sessionID: "537badf8ec57a3e805cbf577", name: "borreltijd", playtime: 29749, wins: 202, losses: 203, score: -1}
{team: "ogres", rank: 128, sessionID: "537f8306ead05fd3075d1d39", name: "icoderz", playtime: 5382, wins: 202, losses: 204, score: -2}
{team: "ogres", rank: 129, sessionID: "537b92b55bc02238050d1c18", name: "Bullcity", playtime: 7478, wins: 197, losses: 204, score: -7}
{team: "ogres", rank: 130, sessionID: "537ba0937c89ec9805f4df2f", name: "Pavlov", playtime: 15761, wins: 198, losses: 205, score: -7}
{team: "ogres", rank: 131, sessionID: "537b85055bc02238050d16a6", name: "Drunk McLovin", playtime: 10225, wins: 194, losses: 204, score: -10}
{team: "ogres", rank: 132, sessionID: "537cbb38a0d7a5cc10992ee9", name: "Aion Crane", playtime: 9616, wins: 195, losses: 206, score: -11}
{team: "ogres", rank: 133, sessionID: "537be31fa3b60b390500ddc5", name: "antkim003", playtime: 19059, wins: 195, losses: 206, score: -11}
{team: "ogres", rank: 134, sessionID: "537d20d97e1e10b705bbcd58", name: "Tain", playtime: 28374, wins: 173, losses: 189, score: -16}
{team: "ogres", rank: 135, sessionID: "537bb6d6386abb6e05a3facf", name: "RTN", playtime: 4108, wins: 194, losses: 211, score: -17}
{team: "ogres", rank: 136, sessionID: "537b86b95bc02238050d1706", name: "Luogbelnu", playtime: 24526, wins: 191, losses: 208, score: -17}
{team: "ogres", rank: 137, sessionID: "537338f73108bc3905f7528d", name: "chess", playtime: 321, wins: 192, losses: 213, score: -21}
{team: "ogres", rank: 138, sessionID: "537df0950efa6a37059df463", name: "Rury", playtime: 14371, wins: 187, losses: 215, score: -28}
{team: "ogres", rank: 139, sessionID: "537c11290ff88b2c06287d29", name: "Crustopher", playtime: 15401, wins: 167, losses: 195, score: -28}
{team: "ogres", rank: 140, sessionID: "537df96a0efa6a37059df853", name: "HuangSY", playtime: 34229, wins: 178, losses: 209, score: -31}
{team: "ogres", rank: 141, sessionID: "537b909f5bc02238050d1b32", name: "ArthurDent", playtime: 63903, wins: 173, losses: 204, score: -31}
{team: "ogres", rank: 142, sessionID: "537e901b9008f87b0541fc83", name: "Mawox", playtime: 15961, wins: 186, losses: 218, score: -32}
{team: "ogres", rank: 143, sessionID: "537fa33bf56c5874086896cc", name: "Urg", playtime: 1760, wins: 182, losses: 218, score: -36}
{team: "ogres", rank: 144, sessionID: "53854e7d7d99a7390560fadd", name: "Dodrithard", playtime: 4087, wins: 160, losses: 196, score: -36}
{team: "ogres", rank: 145, sessionID: "537bfa3395356a43065f76bd", name: "Cicir", playtime: 7369, wins: 183, losses: 221, score: -38}
{team: "ogres", rank: 146, sessionID: "537ba4f3d60bb0b405d852b2", name: "Dubastot", playtime: 17312, wins: 160, losses: 201, score: -41}
{team: "ogres", rank: 147, sessionID: "537b8dbbe91b4d3b05526226", name: "Raetsel", playtime: 11005, wins: 180, losses: 223, score: -43}
{team: "ogres", rank: 148, sessionID: "537bc0b31c8dfa820699fcc2", name: "Source Error", playtime: 1731, wins: 171, losses: 214, score: -43}
{team: "ogres", rank: 149, sessionID: "537b92dde91b4d3b05526442", name: "Script Pimpkin", playtime: 18711, wins: 180, losses: 225, score: -45}
{team: "ogres", rank: 150, sessionID: "538f33d0755876370525988f", name: "Rigamortis", playtime: 23232, wins: 176, losses: 221, score: -45}
{team: "ogres", rank: 151, sessionID: "537c8bb826529de30cca4b02", name: "Static", playtime: 673, wins: 167, losses: 216, score: -49}
{team: "ogres", rank: 152, sessionID: "537bdd5ed3f3bb3305bd997a", name: "Tuna", playtime: 4854, wins: 177, losses: 226, score: -49}
{team: "ogres", rank: 153, sessionID: "537b945d556db17605be74d0", name: "Dino", playtime: 5413, wins: 177, losses: 227, score: -50}
{team: "ogres", rank: 154, sessionID: "537bcda2c57303a5070c2f3a", name: "LaughingMan", playtime: 52476, wins: 170, losses: 222, score: -52}
{team: "ogres", rank: 155, sessionID: "53825ebf78171d3d057e4e48", name: "Lord Azrael", playtime: 25968, wins: 175, losses: 227, score: -52}
{team: "ogres", rank: 156, sessionID: "537d19307e1e10b705bbc877", name: "Naoki", playtime: 7851, wins: 172, losses: 226, score: -54}
{team: "ogres", rank: 157, sessionID: "537b95526c13497e05de81d2", name: "Gromliqk", playtime: 15566, wins: 174, losses: 230, score: -56}
{team: "ogres", rank: 158, sessionID: "537bacbc03838cf0050ace6f", name: "cameltoe", playtime: 53128, wins: 171, losses: 230, score: -59}
{team: "ogres", rank: 159, sessionID: "537b82d75bc02238050d160d", name: "Kithid", playtime: 14756, wins: 169, losses: 229, score: -60}
{team: "ogres", rank: 160, sessionID: "537d4e5d3dcf67c40571f5c3", name: "bonnnie", playtime: 25945, wins: 173, losses: 233, score: -60}
{team: "ogres", rank: 161, sessionID: "537e4d7a63734c6305059374", name: "kotowlos", playtime: 6498, wins: 172, losses: 233, score: -61}
{team: "ogres", rank: 162, sessionID: "537b80425bc02238050d152c", name: "mdz", playtime: 12720, wins: 167, losses: 229, score: -62}
{team: "ogres", rank: 163, sessionID: "537bb50218bd7f4705b9d459", name: "Claros", playtime: 1250, wins: 170, losses: 236, score: -66}
{team: "ogres", rank: 164, sessionID: "537c8a5626529de30cca4a2f", name: "nulo", playtime: 5264, wins: 168, losses: 236, score: -68}
{team: "ogres", rank: 165, sessionID: "537bb4598698e13805226b77", name: "JojoTheGreat", playtime: 1733, wins: 162, losses: 235, score: -73}
{team: "ogres", rank: 166, sessionID: "537bab9b89a8aeea05970d0b", name: "gameloop.io", playtime: 2823, wins: 163, losses: 237, score: -74}
{team: "ogres", rank: 167, sessionID: "5382411905e52a3305df893b", name: "Richard Adleta", playtime: 3272, wins: 162, losses: 240, score: -78}
{team: "ogres", rank: 168, sessionID: "537c4408a56c78cb0cb4f353", name: "lichens", playtime: 59782, wins: 163, losses: 241, score: -78}
{team: "ogres", rank: 169, sessionID: "537e1ab60efa6a37059e0bf0", name: "Scnoobi", playtime: 3430, wins: 159, losses: 239, score: -80}
{team: "ogres", rank: 170, sessionID: "537f6adc14f08dec07950ab2", name: "Ralphy282", playtime: 42208, wins: 155, losses: 235, score: -80}
{team: "ogres", rank: 171, sessionID: "537be8d820a501380564e6e7", name: "gandalfStormcrow", playtime: 435, wins: 162, losses: 242, score: -80}
{team: "ogres", rank: 172, sessionID: "537bc4b10de0a02c07e879a8", name: "", playtime: 872, wins: 161, losses: 242, score: -81}
{team: "ogres", rank: 173, sessionID: "537e581863734c6305059a53", name: "taz", playtime: 24420, wins: 160, losses: 242, score: -82}
{team: "ogres", rank: 174, sessionID: "537ce331e9c4e97c05e3ea34", name: "bb", playtime: 391, wins: 159, losses: 245, score: -86}
{team: "ogres", rank: 175, sessionID: "537c5f4f14aaabe80c69fd70", name: "@ESWAT", playtime: 13389, wins: 152, losses: 239, score: -87}
{team: "ogres", rank: 176, sessionID: "537b8a515bc02238050d1873", name: "joshuacarley", playtime: 23663, wins: 156, losses: 244, score: -88}
{team: "ogres", rank: 177, sessionID: "537d550d3dcf67c40571f7dc", name: "J'Son", playtime: 5290, wins: 156, losses: 246, score: -90}
{team: "ogres", rank: 178, sessionID: "537c05ef95356a43065f7e1c", name: "Cerbi", playtime: 9095, wins: 151, losses: 250, score: -99}
{team: "ogres", rank: 179, sessionID: "537c532126529de30cca3067", name: "warrned", playtime: 3870, wins: 149, losses: 254, score: -105}
{team: "ogres", rank: 180, sessionID: "537fc780c1a6b99109171791", name: "OWalerys", playtime: 18624, wins: 146, losses: 251, score: -105}
{team: "ogres", rank: 181, sessionID: "537efa7e8fe881f1055de25e", name: "Smaug", playtime: 14526, wins: 146, losses: 253, score: -107}
{team: "ogres", rank: 182, sessionID: "537bf37d95356a43065f7286", name: "cokaroach", playtime: 47117, wins: 124, losses: 237, score: -113}
{team: "ogres", rank: 183, sessionID: "537e304e64659f3a0577248a", name: "Plak87", playtime: 5459, wins: 144, losses: 259, score: -115}
{team: "ogres", rank: 184, sessionID: "537cfe4d9cce053a05c04dc2", name: "xzores", playtime: 9686, wins: 145, losses: 260, score: -115}
{team: "ogres", rank: 185, sessionID: "537bb0ad2323353a05bd57ef", name: "Wicked", playtime: 4520, wins: 140, losses: 258, score: -118}
{team: "ogres", rank: 186, sessionID: "537cd1c4665bde1b13ffd635", name: "Delforas", playtime: 3970, wins: 138, losses: 258, score: -120}
{team: "ogres", rank: 187, sessionID: "53806a8405e52a3305de92fa", name: "Pabelo", playtime: 4629, wins: 141, losses: 262, score: -121}
{team: "ogres", rank: 188, sessionID: "537fcc57c1a6b99109171ced", name: "jhoie", playtime: 1045, wins: 137, losses: 259, score: -122}
{team: "ogres", rank: 189, sessionID: "5395d1ba7362c439054f7e6e", name: "JenJen", playtime: 151, wins: 137, losses: 259, score: -122}
{team: "ogres", rank: 190, sessionID: "537b9dc4ffeaa29e051b2738", name: "Kevin", playtime: 9970, wins: 136, losses: 263, score: -127}
{team: "ogres", rank: 191, sessionID: "537b9c09ffeaa29e051b2637", name: "Tuefekci", playtime: 10790, wins: 131, losses: 259, score: -128}
{team: "ogres", rank: 192, sessionID: "537c5b3526529de30cca340c", name: "patchnotes", playtime: 31167, wins: 119, losses: 252, score: -133}
{team: "ogres", rank: 193, sessionID: "5380fdb47585483905a7c753", name: "null0pointer", playtime: 9524, wins: 131, losses: 266, score: -135}
{team: "ogres", rank: 194, sessionID: "537b9f4751e98aa705b60515", name: "Sawyer", playtime: 9450, wins: 136, losses: 271, score: -135}
{team: "ogres", rank: 195, sessionID: "5380bd747585483905a7a707", name: "Lobo", playtime: 5280, wins: 132, losses: 269, score: -137}
{team: "ogres", rank: 196, sessionID: "5382593bc85c223a05f429d9", name: "squidlarkin", playtime: 2036, wins: 130, losses: 267, score: -137}
{team: "ogres", rank: 197, sessionID: "537cd5fee4523d0113ba57ea", name: "Borlak", playtime: 14470, wins: 133, losses: 271, score: -138}
{team: "ogres", rank: 198, sessionID: "537bbb201aaa69c4052676fd", name: "jMerliN", playtime: 3292, wins: 129, losses: 270, score: -141}
{team: "ogres", rank: 199, sessionID: "537be3c7a3b60b390500de0e", name: "Dragon DM", playtime: 17848, wins: 131, losses: 274, score: -143}
{team: "ogres", rank: 200, sessionID: "537b90295bc02238050d1af5", name: "octopushugs", playtime: 706, wins: 125, losses: 270, score: -145}
{team: "ogres", rank: 201, sessionID: "5380d5be7585483905a7b1e1", name: "Quaritch", playtime: 12711, wins: 128, losses: 274, score: -146}
{team: "ogres", rank: 202, sessionID: "5397732542cf7d3905ed7233", name: "BloodJohn", playtime: 5178, wins: 127, losses: 276, score: -149}
{team: "ogres", rank: 203, sessionID: "537cab3d0e905d000fbfad57", name: "Xor", playtime: 19480, wins: 126, losses: 278, score: -152}
{team: "ogres", rank: 204, sessionID: "537b88f85bc02238050d17e2", name: "Jojas", playtime: 9328, wins: 126, losses: 278, score: -152}
{team: "ogres", rank: 205, sessionID: "539436745009fe5b075b70a7", name: "Vhr", playtime: 3065, wins: 122, losses: 274, score: -152}
{team: "ogres", rank: 206, sessionID: "535f0c4b11c0ebd2049d1664", name: "Jebso", playtime: 0, wins: 125, losses: 278, score: -153}
{team: "ogres", rank: 207, sessionID: "537b9d3cffeaa29e051b26eb", name: "", playtime: 784, wins: 125, losses: 278, score: -153}
{team: "ogres", rank: 208, sessionID: "537c3bdb54bc23770a21e166", name: "seaclair", playtime: 385, wins: 122, losses: 279, score: -157}
{team: "ogres", rank: 209, sessionID: "537bf147f8b46fbd0544fc15", name: "", playtime: 16376, wins: 109, losses: 269, score: -160}
{team: "ogres", rank: 210, sessionID: "537bd2e0be90aa5605fb2ebd", name: "educavalcanti", playtime: 50086, wins: 121, losses: 282, score: -161}
{team: "ogres", rank: 211, sessionID: "537cd0e0665bde1b13ffd5ca", name: "itsbth", playtime: 1299, wins: 104, losses: 265, score: -161}
{team: "ogres", rank: 212, sessionID: "537b8e1ee91b4d3b05526274", name: "Jorge", playtime: 4728, wins: 118, losses: 280, score: -162}
{team: "ogres", rank: 213, sessionID: "537b88f6e91b4d3b05526048", name: "CzonI", playtime: 3033, wins: 120, losses: 283, score: -163}
{team: "ogres", rank: 214, sessionID: "537b7c26e91b4d3b05525c6a", name: "TTSS", playtime: 1132, wins: 120, losses: 283, score: -163}
{team: "ogres", rank: 215, sessionID: "5389d73bb924da3905149015", name: "oli-f", playtime: 10824, wins: 101, losses: 266, score: -165}
{team: "ogres", rank: 216, sessionID: "537b8c21e91b4d3b0552616d", name: "Luke Young", playtime: 10713, wins: 117, losses: 285, score: -168}
{team: "ogres", rank: 217, sessionID: "537b84e15bc02238050d169c", name: "", playtime: 359, wins: 116, losses: 284, score: -168}
{team: "ogres", rank: 218, sessionID: "537b845fe91b4d3b05525f03", name: "walesmd", playtime: 1630, wins: 116, losses: 285, score: -169}
{team: "ogres", rank: 219, sessionID: "538515a73e0daa390518ba45", name: "Azerroth", playtime: 19862, wins: 114, losses: 285, score: -171}
{team: "ogres", rank: 220, sessionID: "537bd660142ee637052420e3", name: "tkl", playtime: 8276, wins: 102, losses: 278, score: -176}
{team: "ogres", rank: 221, sessionID: "538a9449b924da3905151926", name: "cdaaar", playtime: 14955, wins: 45, losses: 222, score: -177}
{team: "ogres", rank: 222, sessionID: "538345b078171d3d057eae61", name: "Ikrus", playtime: 13687, wins: 111, losses: 295, score: -184}
{team: "ogres", rank: 223, sessionID: "537b8e26e91b4d3b05526277", name: "Zairja", playtime: 10077, wins: 101, losses: 293, score: -192}
{team: "ogres", rank: 224, sessionID: "537b97f53803a28705758218", name: "kookieblues", playtime: 2400, wins: 104, losses: 303, score: -199}
{team: "ogres", rank: 225, sessionID: "537d06bd7e1e10b705bbbe83", name: "llcossette", playtime: 2648, wins: 95, losses: 302, score: -207}
{team: "ogres", rank: 226, sessionID: "537b800de91b4d3b05525db8", name: "Merlin Gough", playtime: 8942, wins: 90, losses: 303, score: -213}
{team: "ogres", rank: 227, sessionID: "538382db86de19c31c1ce893", name: "Nick05 Testinator", playtime: 1454, wins: 90, losses: 311, score: -221}
{team: "ogres", rank: 228, sessionID: "537bcd7ec57303a5070c2f1e", name: "hedint", playtime: 8881, wins: 92, losses: 314, score: -222}
{team: "ogres", rank: 229, sessionID: "537cc425665bde1b13ffce89", name: "dotnetjohn", playtime: 1304, wins: 87, losses: 315, score: -228}
{team: "ogres", rank: 230, sessionID: "537c3c1454bc23770a21e187", name: "Prelude", playtime: 2351, wins: 85, losses: 313, score: -228}
{team: "ogres", rank: 231, sessionID: "538925b4d06c503805fe4ff7", name: "Perenolder", playtime: 21235, wins: 79, losses: 322, score: -243}
{team: "ogres", rank: 232, sessionID: "538a26cdd06c503805fec706", name: "the-k-man", playtime: 10321, wins: 78, losses: 327, score: -249}
{team: "ogres", rank: 233, sessionID: "537c531d26529de30cca3065", name: "masashi", playtime: 17871, wins: 75, losses: 330, score: -255}
{team: "ogres", rank: 234, sessionID: "5371861fd4491339051c3b11", name: "asdlk;fj asd fas", playtime: 0, wins: 65, losses: 333, score: -268}
{team: "ogres", rank: 235, sessionID: "537b8429e91b4d3b05525ef5", name: "Missblit", playtime: 28639, wins: 59, losses: 346, score: -287}
{team: "ogres", rank: 236, sessionID: "538b1322d06c503805ff299f", name: "judar1o", playtime: 5891, wins: 59, losses: 347, score: -288}
{team: "ogres", rank: 237, sessionID: "53583ab57962157d05b398f8", name: "makertech81", playtime: 170, wins: 55, losses: 344, score: -289}
{team: "ogres", rank: 238, sessionID: "539723388376eb3805dc2d4e", name: "immersion", playtime: 6471, wins: 54, losses: 348, score: -294}
{team: "ogres", rank: 239, sessionID: "537c99aa14aaabe80c6a1a44", name: "werewolf", playtime: 167, wins: 46, losses: 343, score: -297}
{team: "ogres", rank: 240, sessionID: "539085aaf369103805172236", name: "Kabbi", playtime: 6873, wins: 43, losses: 344, score: -301}
{team: "ogres", rank: 241, sessionID: "537bd587b0d1766d05243016", name: "Casey", playtime: 1229, wins: 44, losses: 346, score: -302}
{team: "ogres", rank: 242, sessionID: "537e2bd864659f3a05772075", name: "", playtime: 322, wins: 42, losses: 349, score: -307}
{team: "ogres", rank: 243, sessionID: "5385c7503e0daa39051919db", name: "Zack", playtime: 66, wins: 40, losses: 348, score: -308}
{team: "ogres", rank: 244, sessionID: "537ba0a551e98aa705b605b2", name: "", playtime: 232, wins: 39, losses: 349, score: -310}
{team: "ogres", rank: 245, sessionID: "535fdebe5c7324a80702cb3b", name: "mich55", playtime: 0, wins: 38, losses: 349, score: -311}
{team: "ogres", rank: 246, sessionID: "5390823e7558763705263cc8", name: "Duchess", playtime: 460, wins: 37, losses: 349, score: -312}
{team: "ogres", rank: 247, sessionID: "5360a43d9859fefb0673aefa", name: "daruba", playtime: 0, wins: 34, losses: 351, score: -317}
{team: "ogres", rank: 248, sessionID: "537bab4f6e54cbda057ee565", name: "Pajamas", playtime: 27043, wins: 14, losses: 338, score: -324}
{team: "ogres", rank: 249, sessionID: "537bf053f8b46fbd0544fb8c", name: "Fusspawn", playtime: 9524, wins: 37, losses: 368, score: -331}
{team: "ogres", rank: 250, sessionID: "5382f069c85c223a05f47200", name: "ferrak", playtime: 7015, wins: 33, losses: 373, score: -340}
{team: "ogres", rank: 251, sessionID: "5380a3737585483905a799e1", name: "Dark Pikachu", playtime: 1373, wins: 29, losses: 377, score: -348}
{team: "ogres", rank: 252, sessionID: "538711898ca8b1120b8bdb3f", name: "A", playtime: 4272, wins: 24, losses: 380, score: -356}
{team: "ogres", rank: 253, sessionID: "5384af823e0daa3905187cb1", name: "Macguffin", playtime: 837, wins: 18, losses: 384, score: -366}
{team: "ogres", rank: 254, sessionID: "537db76b13add33a051bf575", name: "Luyalve", playtime: 1404, wins: 3, losses: 372, score: -369}
{team: "ogres", rank: 255, sessionID: "537b9bac16613c8405155a79", name: "solarian", playtime: 24889, wins: 0, losses: 371, score: -371}
{team: "ogres", rank: 256, sessionID: "537ba09951e98aa705b605ae", name: "Potato", playtime: 61777, wins: 0, losses: 371, score: -371}
{team: "ogres", rank: 257, sessionID: "537d24db84c54c6e05c06ac4", name: "terriblesarcasm", playtime: 39553, wins: 0, losses: 371, score: -371}
{team: "ogres", rank: 258, sessionID: "537c2abc224747bb0761634f", name: "chicones", playtime: 9831, wins: 0, losses: 371, score: -371}
{team: "ogres", rank: 259, sessionID: "538381a5c83e563705a06fdb", name: "diddly-dum", playtime: 8077, wins: 0, losses: 372, score: -372}
{team: "ogres", rank: 260, sessionID: "5388a785b924da3905139f46", name: "AWESOMEHACKER", playtime: 564, wins: 0, losses: 372, score: -372}
{team: "ogres", rank: 261, sessionID: "537bb6d2386abb6e05a3facc", name: "chrm", playtime: 213307, wins: 0, losses: 375, score: -375}
{team: "ogres", rank: 262, sessionID: "537bc26e8fe668b406d4c019", name: "Sapid", playtime: 11917, wins: 0, losses: 376, score: -376}
{team: "ogres", rank: 263, sessionID: "5357127a1bfa9bba14b5e048", name: "dcm</script><script>alert('XSS')</script>", playtime: 5380, wins: 0, losses: 376, score: -376}
{team: "ogres", rank: 264, sessionID: "537aee4cfb4c173805cf26f7", name: "basicer", playtime: 10407, wins: 0, losses: 376, score: -376}
{team: "ogres", rank: 265, sessionID: "5382cbb078171d3d057e804f", name: "vvv", playtime: 719, wins: 0, losses: 376, score: -376}
{team: "ogres", rank: 266, sessionID: "537ba40a903fd2b8051553b0", name: "ElderTale", playtime: 13984, wins: 0, losses: 376, score: -376}
{team: "ogres", rank: 267, sessionID: "53839e256f3bea3a05fb09b3", name: "Gondolfs", playtime: 29985, wins: 0, losses: 378, score: -378}
{team: "ogres", rank: 268, sessionID: "537a73a6e488a7380545affd", name: "Galvan", playtime: 514, wins: 0, losses: 378, score: -378}
{team: "ogres", rank: 269, sessionID: "537b96b16c13497e05de82a8", name: "Krris", playtime: 2618, wins: 0, losses: 379, score: -379}
{team: "ogres", rank: 270, sessionID: "537bc1b81db8c8ac063a3b7b", name: "jpiasetz", playtime: 14565, wins: 0, losses: 380, score: -380}
]

View file

@ -79,7 +79,7 @@ module.exports = class EditorConfigModal extends View
Backbone.Mediator.publish 'tome:change-config' Backbone.Mediator.publish 'tome:change-config'
Backbone.Mediator.publish 'tome:change-language', language: newLanguage unless newLanguage is oldLanguage Backbone.Mediator.publish 'tome:change-language', language: newLanguage unless newLanguage is oldLanguage
@session.save() unless newLanguage is oldLanguage @session.save() unless newLanguage is oldLanguage
me.save() me.patch()
destroy: -> destroy: ->
super() super()

View file

@ -81,7 +81,7 @@ module.exports = class VictoryModal extends View
if enough and not me.get('hourOfCodeComplete') if enough and not me.get('hourOfCodeComplete')
$('body').append($("<img src='http://code.org/api/hour/finish_codecombat.png' style='visibility: hidden;'>")) $('body').append($("<img src='http://code.org/api/hour/finish_codecombat.png' style='visibility: hidden;'>"))
me.set 'hourOfCodeComplete', true me.set 'hourOfCodeComplete', true
me.save() me.patch()
window.tracker?.trackEvent 'Hour of Code Finish', {} window.tracker?.trackEvent 'Hour of Code Finish', {}
# Show the "I'm done" button if they get to the end, unless it's been over two hours # Show the "I'm done" button if they get to the end, unless it's been over two hours
tooMuch = elapsed >= 120 * 60 * 1000 tooMuch = elapsed >= 120 * 60 * 1000

View file

@ -355,7 +355,7 @@ module.exports = class PlaybackView extends View
onToggleMusic: (e) -> onToggleMusic: (e) ->
e?.preventDefault() e?.preventDefault()
me.set('music', not me.get('music')) me.set('music', not me.get('music'))
me.save() me.patch()
$(document.activeElement).blur() $(document.activeElement).blur()
destroy: -> destroy: ->

View file

@ -97,7 +97,7 @@ module.exports = class CastButtonView extends View
return unless delay return unless delay
@autocastDelay = delay = parseInt delay @autocastDelay = delay = parseInt delay
me.set('autocastDelay', delay) me.set('autocastDelay', delay)
me.save() me.patch()
spell.view.setAutocastDelay delay for spellKey, spell of @spells spell.view.setAutocastDelay delay for spellKey, spell of @spells
@castOptions.find('a').each -> @castOptions.find('a').each ->
$(@).toggleClass('selected', parseInt($(@).attr('data-delay')) is delay) $(@).toggleClass('selected', parseInt($(@).attr('data-delay')) is delay)

View file

@ -24,7 +24,7 @@ module.exports = class Spell
@permissions = read: p.permissions?.read ? [], readwrite: p.permissions?.readwrite ? [] # teams @permissions = read: p.permissions?.read ? [], readwrite: p.permissions?.readwrite ? [] # teams
teamSpells = @session.get('teamSpells') teamSpells = @session.get('teamSpells')
team = @session.get('team') ? 'humans' team = @session.get('team') ? 'humans'
@useTranspiledCode = @permissions.readwrite.length and ((teamSpells and not _.contains(teamSpells[team], @spellKey)) or (@session.get('creator') isnt me.id) or @spectateView) @useTranspiledCode = @permissions.readwrite.length and ((teamSpells and not _.contains(teamSpells[team], @spellKey)) or (@session.get('creator') isnt me.id and not (me.isAdmin() or 'employer' in me.get('permissions'))) or @spectateView)
#console.log @spellKey, "using transpiled code?", @useTranspiledCode #console.log @spellKey, "using transpiled code?", @useTranspiledCode
@source = @originalSource = p.source @source = @originalSource = p.source
@parameters = p.parameters @parameters = p.parameters

View file

@ -93,7 +93,7 @@ module.exports = class PlayLevelView extends View
setUpHourOfCode: -> setUpHourOfCode: ->
me.set 'hourOfCode', true me.set 'hourOfCode', true
me.save() me.patch()
$('body').append($("<img src='http://code.org/api/hour/begin_codecombat.png' style='visibility: hidden;'>")) $('body').append($("<img src='http://code.org/api/hour/begin_codecombat.png' style='visibility: hidden;'>"))
application.tracker?.trackEvent 'Hour of Code Begin', {} application.tracker?.trackEvent 'Hour of Code Begin', {}

View file

@ -211,14 +211,21 @@ module.exports = class PlayView extends View
difficulty: 3 difficulty: 3
id: 'bubble-sort-bootcamp-battle' id: 'bubble-sort-bootcamp-battle'
image: '/file/db/level/525ef8ef06e1ab0962000003/commanding_followers_icon.png' image: '/file/db/level/525ef8ef06e1ab0962000003/commanding_followers_icon.png'
description: "Write a bubble sort to organize your soldiers. - by Alexandru" description: "Write a bubble sort to organize your soldiers. - by Alexandru Caciulescu"
} }
{ {
name: 'Ogres of Hanoi' name: 'Ogres of Hanoi'
difficulty: 3 difficulty: 3
id: 'ogres-of-hanoi' id: 'ogres-of-hanoi'
image: '/file/db/level/526fd3043c637ece50001bb2/the_herd_icon.png' image: '/file/db/level/526fd3043c637ece50001bb2/the_herd_icon.png'
description: "Transfer a stack of ogres while preserving their honor. - by Alexandru" description: "Transfer a stack of ogres while preserving their honor. - by Alexandru Caciulescu"
}
{
name: 'Danger! Minefield'
difficulty: 3
id: 'danger-minefield'
image: '/file/db/level/526bda3fe79aefde2a003e36/mobile_artillery_icon.png'
description: "Learn how to find prime numbers while defusing mines! - by Alexandru Caciulescu"
} }
{ {
name: 'Find the Spy' name: 'Find the Spy'
@ -234,15 +241,6 @@ module.exports = class PlayView extends View
image: '/file/db/level/529662dfe0df8f0000000007/grab_the_mushroom_icon.png' image: '/file/db/level/529662dfe0df8f0000000007/grab_the_mushroom_icon.png'
description: "Collect a hundred mushrooms in just five lines of code - by Nathan Gossett" description: "Collect a hundred mushrooms in just five lines of code - by Nathan Gossett"
} }
#{
# name: 'Enemy Artillery'
# difficulty: 1
# id: 'enemy-artillery'
# image: '/file/db/level/526dba94a188322044000a40/mobile_artillery_icon.png'
# description: "Take cover while shells fly, then strike! - by mcdavid1991"
# disabled: true
#}
] ]
context.campaigns = [ context.campaigns = [

View file

@ -107,6 +107,7 @@ module.exports = TestView = class TestView extends CocoView
# TODO Stubbify more things # TODO Stubbify more things
# * document.location # * document.location
# * firebase # * firebase
# * all the services that load in main.html
afterEach -> afterEach ->
# TODO Clean up more things # TODO Clean up more things

View file

@ -88,5 +88,8 @@ mongo_db_path = os.path.abspath(os.path.join(current_directory,os.pardir)) + os.
if not os.path.exists(mongo_db_path): if not os.path.exists(mongo_db_path):
os.mkdir(mongo_db_path) os.mkdir(mongo_db_path)
mongo_arguments = [mongo_executable + u" --setParameter textSearchEnabled=true --dbpath=" + mongo_db_path] mongo_arguments = [mongo_executable + u" --setParameter textSearchEnabled=true --dbpath=" + mongo_db_path]
if 'fork' in sys.argv: mongo_arguments[0] += " --fork --logpath ./mongodb.log"
call(mongo_arguments,shell=True) call(mongo_arguments,shell=True)

View file

@ -3,14 +3,10 @@ startsWith = (string, substring) ->
string.lastIndexOf(substring, 0) is 0 string.lastIndexOf(substring, 0) is 0
exports.config = exports.config =
server:
path: 'server.coffee'
paths: paths:
'public': 'public' 'public': 'public'
conventions: conventions:
ignored: (path) -> startsWith(sysPath.basename(path), '_') ignored: (path) -> startsWith(sysPath.basename(path), '_')
workers:
enabled: false # turned out to be much, much slower than without workers
sourceMaps: true sourceMaps: true
files: files:
javascripts: javascripts:
@ -55,7 +51,6 @@ exports.config =
'vendor/scripts/movieclip-NEXT.min.js' 'vendor/scripts/movieclip-NEXT.min.js'
# Validated Backbone Mediator dependencies # Validated Backbone Mediator dependencies
'bower_components/tv4/tv4.js' 'bower_components/tv4/tv4.js'
# Aether before box2d for some strange Object.defineProperty thing # Aether before box2d for some strange Object.defineProperty thing
'bower_components/aether/build/aether.js' 'bower_components/aether/build/aether.js'
'bower_components/d3/d3.min.js' 'bower_components/d3/d3.min.js'
@ -77,7 +72,7 @@ exports.config =
plugins: plugins:
autoReload: autoReload:
delay: 300 # for race conditions, particularly waiting for onCompile to do its thing delay: 300
coffeelint: coffeelint:
pattern: /^app\/.*\.coffee$/ pattern: /^app\/.*\.coffee$/
options: options:

View file

@ -7,9 +7,10 @@ async = require 'async'
serverSetup = require '../server_setup' serverSetup = require '../server_setup'
sendwithus = require '../server/sendwithus' sendwithus = require '../server/sendwithus'
User = require '../server/users/User.coffee' User = require '../server/users/User'
Level = require '../server/levels/Level.coffee' Level = require '../server/levels/Level'
LevelSession = require '../server/levels/sessions/LevelSession.coffee' LevelSession = require '../server/levels/sessions/LevelSession'
tournamentResults = require '../app/views/play/ladder/tournament_results'
alreadyEmailed = [] alreadyEmailed = []
@ -27,7 +28,7 @@ sendInitialRecruitingEmail = ->
async.waterfall [ async.waterfall [
(callback) -> async.map leaderboards, grabSessions, callback (callback) -> async.map leaderboards, grabSessions, callback
(sessionLists, callback) -> async.map collapseSessions(sessionLists), grabUser, callback (sessionLists, callback) -> async.map collapseSessions(sessionLists), grabUser, callback
(users, callback) -> async.map users, emailUser, callback (users, callback) -> async.map users, emailUserInitialRecruiting, callback
], (err, results) -> ], (err, results) ->
return console.log "Error:", err if err return console.log "Error:", err if err
console.log "Looked at sending to #{results.length} users; sent to #{_.filter(results).length}." console.log "Looked at sending to #{results.length} users; sent to #{_.filter(results).length}."
@ -77,7 +78,7 @@ grabUser = (session, callback) ->
callback null, user callback null, user
totalEmailsSent = 0 totalEmailsSent = 0
emailUser = (user, callback) -> emailUserInitialRecruiting = (user, callback) ->
#return callback null, false if user.emails?.anyNotes?.enabled is false # TODO: later, uncomment to obey also "anyNotes" when that's untangled #return callback null, false if user.emails?.anyNotes?.enabled is false # TODO: later, uncomment to obey also "anyNotes" when that's untangled
return callback null, false if user.emails?.recruitNotes?.enabled is false return callback null, false if user.emails?.recruitNotes?.enabled is false
return callback null, false if user.email in alreadyEmailed return callback null, false if user.email in alreadyEmailed
@ -102,5 +103,64 @@ emailUser = (user, callback) ->
return callback err if err return callback err if err
callback null, user callback null, user
sendTournamentResultsEmail = ->
winners = tournamentResults.greed.humans.concat tournamentResults.greed.ogres
async.waterfall [
(callback) -> async.map winners, grabSession, callback
(winners, callback) -> async.map winners, grabEmail, callback
(winners, callback) -> async.map winners, emailUserTournamentResults, callback
], (err, results) ->
return console.log "Error:", err if err
console.log "Looked at sending to #{results.length} users; sent to #{_.filter(results).length}."
console.log "Sent to: ['#{(user.email for user in results when user).join('\', \'')}']"
grabSession = (winner, callback) ->
LevelSession.findOne(_id: winner.sessionID).select('creator').lean().exec (err, session) ->
return callback err if err
winner.userID = session.creator
callback null, winner
grabEmail = (winner, callback) ->
User.findOne(_id: winner.userID).select('email').lean().exec (err, user) ->
return callback err if err
winner.email = user.email
callback null, winner
emailUserTournamentResults = (winner, callback) ->
return callback null, false if DEBUGGING and (winner.team is 'humans' or totalEmailsSent > 1)
++totalEmailsSent
name = winner.name
team = winner.team.substr(0, winner.team.length - 1)
context =
email_id: sendwithus.templates.greed_tournament_rank
recipient:
address: if DEBUGGING then 'nick@codecombat.com' else winner.email
name: name
email_data:
userID: winner.userID
name: name
level_name: "Greed"
wins: winner.wins
ties: {humans: 377, ogres: 407}[winner.team] - winner.wins - winner.losses
losses: winner.losses
rank: winner.rank
team_name: team
ladder_url: "http://codecombat.com/play/ladder/greed#winners"
top3: winner.rank <= 3
top5: winner.rank <= 5
top10: winner.rank <= 10
top40: winner.rank <= 40
top100: winner.rank <= 100
sendwithus.api.send context, (err, result) ->
return callback err if err
callback null, winner
serverSetup.connectToDatabase() serverSetup.connectToDatabase()
sendInitialRecruitingEmail()
fn = process.argv[2]
try
eval fn + '()'
catch err
console.log "Error running #{fn}", err

View file

@ -1,7 +1,8 @@
mongoose = require('mongoose') mongoose = require('mongoose')
plugins = require('../plugins/plugins')
jsonschema = require('../../app/schemas/models/achievement') jsonschema = require('../../app/schemas/models/achievement')
log = require 'winston' log = require 'winston'
util = require '../../app/lib/utils'
plugins = require('../plugins/plugins')
# `pre` and `post` are not called for update operations executed directly on the database, # `pre` and `post` are not called for update operations executed directly on the database,
# including `Model.update`,`.findByIdAndUpdate`,`.findOneAndUpdate`, `.findOneAndRemove`,and `.findByIdAndRemove`.order # including `Model.update`,`.findByIdAndUpdate`,`.findOneAndUpdate`, `.findOneAndRemove`,and `.findByIdAndRemove`.order
@ -12,16 +13,21 @@ AchievementSchema = new mongoose.Schema({
userField: String userField: String
}, {strict: false}) }, {strict: false})
AchievementSchema.methods.objectifyQuery = () -> AchievementSchema.methods.objectifyQuery = ->
try try
@set('query', JSON.parse(@get('query'))) if typeof @get('query') == "string" @set('query', JSON.parse(@get('query'))) if typeof @get('query') == "string"
catch error catch error
log.error "Couldn't convert query string to object because of #{error}" log.error "Couldn't convert query string to object because of #{error}"
@set('query', {}) @set('query', {})
AchievementSchema.methods.stringifyQuery = () -> AchievementSchema.methods.stringifyQuery = ->
@set('query', JSON.stringify(@get('query'))) if typeof @get('query') != "string" @set('query', JSON.stringify(@get('query'))) if typeof @get('query') != "string"
getExpFunction: ->
kind = @get('function')?.kind or jsonschema.function.default.kind
parameters = @get('function')?.parameters or jsonschema.function.default.parameters
return utils.functionCreators[kind](parameters) if kind of utils.functionCreators
AchievementSchema.post('init', (doc) -> doc.objectifyQuery()) AchievementSchema.post('init', (doc) -> doc.objectifyQuery())
AchievementSchema.pre('save', (next) -> AchievementSchema.pre('save', (next) ->
@ -33,3 +39,7 @@ AchievementSchema.plugin(plugins.NamedPlugin)
AchievementSchema.plugin(plugins.SearchablePlugin, {searchable: ['name']}) AchievementSchema.plugin(plugins.SearchablePlugin, {searchable: ['name']})
module.exports = Achievement = mongoose.model('Achievement', AchievementSchema) module.exports = Achievement = mongoose.model('Achievement', AchievementSchema)
# Reload achievements upon save
AchievablePlugin = require '../plugins/achievements'
AchievementSchema.post 'save', (doc) -> AchievablePlugin.loadAchievements()

View file

@ -13,7 +13,15 @@ EarnedAchievementSchema = new mongoose.Schema({
default: false default: false
}, {strict:false}) }, {strict:false})
EarnedAchievementSchema.pre 'save', (next) ->
@set('changed', Date.now())
next()
EarnedAchievementSchema.index({user: 1, achievement: 1}, {unique: true, name: 'earned achievement index'}) EarnedAchievementSchema.index({user: 1, achievement: 1}, {unique: true, name: 'earned achievement index'})
EarnedAchievementSchema.index({user: 1, changed: -1}, {name: 'latest '}) EarnedAchievementSchema.index({user: 1, changed: -1}, {name: 'latest '})
module.exports = EarnedAchievement = mongoose.model('EarnedAchievement', EarnedAchievementSchema)
module.exports = EarnedAchievement = mongoose.model('EarnedAchievement', EarnedAchievementSchema)

View file

@ -5,17 +5,11 @@ class AchievementHandler extends Handler
modelClass: Achievement modelClass: Achievement
# Used to determine which properties requests may edit # Used to determine which properties requests may edit
editableProperties: ['name', 'query', 'worth', 'collection', 'description', 'userField', 'proportionalTo', 'icon'] editableProperties: ['name', 'query', 'worth', 'collection', 'description', 'userField', 'proportionalTo', 'icon', 'function']
jsonSchema = require '../../app/schemas/models/achievement.coffee' jsonSchema = require '../../app/schemas/models/achievement.coffee'
hasAccess: (req) -> hasAccess: (req) ->
req.method is 'GET' or req.user?.isAdmin() req.method is 'GET' or req.user?.isAdmin()
get: (req, res) ->
query = @modelClass.find({})
query.exec (err, documents) =>
return @sendDatabaseError(res, err) if err
documents = (@formatEntity(req, doc) for doc in documents)
@sendSuccess(res, documents)
module.exports = new AchievementHandler() module.exports = new AchievementHandler()

View file

@ -1,12 +1,115 @@
mongoose = require('mongoose') log = require 'winston'
mongoose = require 'mongoose'
async = require 'async'
Achievement = require './Achievement'
EarnedAchievement = require './EarnedAchievement' EarnedAchievement = require './EarnedAchievement'
User = require '../users/User'
Handler = require '../commons/Handler' Handler = require '../commons/Handler'
LocalMongo = require '../../app/lib/LocalMongo'
class EarnedAchievementHandler extends Handler class EarnedAchievementHandler extends Handler
modelClass: EarnedAchievement modelClass: EarnedAchievement
# Don't allow POSTs or anything yet # Don't allow POSTs or anything yet
hasAccess: (req) -> hasAccess: (req) ->
req.method is 'GET' req.method is 'GET' # or req.user.isAdmin()
recalculate: (req, res) ->
onSuccess = (data) => log.debug "Finished recalculating achievements"
if 'achievements' of req.body # Support both slugs and IDs separated by commas
achievementSlugsOrIDs = req.body.achievements
EarnedAchievementHandler.recalculate achievementSlugsOrIDs, onSuccess
else
EarnedAchievementHandler.recalculate onSuccess
@sendSuccess res, {}
# Returns success: boolean
# TODO call onFinished
@recalculate: (callbackOrSlugsOrIDs, onFinished) ->
if _.isArray callbackOrSlugsOrIDs
achievementSlugs = (thing for thing in callbackOrSlugsOrIDs when not Handler.isID(thing))
achievementIDs = (thing for thing in callbackOrSlugsOrIDs when Handler.isID(thing))
else
onFinished = callbackOrSlugsOrIDs
filter = {}
filter.$or = [
{_id: $in: achievementIDs},
{slug: $in: achievementSlugs}
] if achievementSlugs? or achievementIDs?
# Fetch all relevant achievements
Achievement.find filter, (err, achievements) ->
return log.error err if err?
# Fetch every single user
User.find {}, (err, users) ->
_.each users, (user) ->
# Keep track of a user's already achieved in order to set the notified values correctly
userID = user.get('_id').toHexString()
# Fetch all of a user's earned achievements
EarnedAchievement.find {user: userID}, (err, alreadyEarned) ->
alreadyEarnedIDs = []
previousPoints = 0
_.each alreadyEarned, (earned) ->
if (_.find achievements, (single) -> earned.get('achievement') is single.get('_id').toHexString())
alreadyEarnedIDs.push earned.get('achievement')
previousPoints += earned.get 'earnedPoints'
# TODO maybe also delete earned? Make sure you don't delete too many
newTotalPoints = 0
earnedAchievementSaverGenerator = (achievement) -> (callback) ->
isRepeatable = achievement.get('proportionalTo')?
model = mongoose.model(achievement.get('collection'))
if not model?
log.error "Model #{achievement.get 'collection'} doesn't even exist."
return callback()
model.findOne achievement.query, (err, something) ->
return callback() unless something
log.debug "Matched an achievement: #{achievement.get 'name'}"
earned =
user: userID
achievement: achievement._id.toHexString()
achievementName: achievement.get 'name'
notified: achievement._id in alreadyEarnedIDs
if isRepeatable
earned.achievedAmount = something.get(achievement.get 'proportionalTo')
earned.previouslyAchievedAmount = 0
expFunction = achievement.getExpFunction()
newPoints = expFunction(earned.achievedAmount) * achievement.get('worth')
else
newPoints = achievement.get 'worth'
earned.earnedPoints = newPoints
newTotalPoints += newPoints
EarnedAchievement.update {achievement:earned.achievement, user:earned.user}, earned, {upsert: true}, (err) ->
log.error err if err?
callback()
saveUserPoints = (callback) ->
# In principle it is enough to deduct the old amount of points and add the new amount,
# but just to be entirely safe let's start from 0 in case we're updating all of a user's achievements
log.debug "Matched a total of #{newTotalPoints} new points"
if _.isEmpty filter # Completely clean
User.update {_id: userID}, {$set: points: newTotalPoints}, {}, (err) -> log.error err if err?
else
log.debug "Incrementing score for these achievements with #{newTotalPoints - previousPoints}"
User.update {_id: userID}, {$inc: points: newTotalPoints - previousPoints}, {}, (err) -> log.error err if err?
earnedAchievementSavers = (earnedAchievementSaverGenerator(achievement) for achievement in achievements)
earnedAchievementSavers.push saveUserPoints
# We need to have all these database updates chained so we know the final score
async.series earnedAchievementSavers
module.exports = new EarnedAchievementHandler() module.exports = new EarnedAchievementHandler()

View file

@ -17,6 +17,7 @@ module.exports = class Handler
postEditableProperties: [] postEditableProperties: []
jsonSchema: {} jsonSchema: {}
waterfallFunctions: [] waterfallFunctions: []
allowedMethods: ['GET', 'POST', 'PUT', 'PATCH']
# subclasses should override these methods # subclasses should override these methods
hasAccess: (req) -> true hasAccess: (req) -> true
@ -63,26 +64,72 @@ module.exports = class Handler
# generic handlers # generic handlers
get: (req, res) -> get: (req, res) ->
# by default, ordinary users never get unfettered access to the database @sendUnauthorizedError(res) if not @hasAccess(req)
return @sendUnauthorizedError(res) unless req.user?.isAdmin()
# admins can send any sort of query down the wire, though specialParameters = ['term', 'project', 'conditions']
conditions = JSON.parse(req.query.conditions || '[]')
query = @modelClass.find()
try # If the model uses coco search it's probably a text search
for condition in conditions if @modelClass.schema.uses_coco_search
name = condition[0] term = req.query.term
f = query[name] matchedObjects = []
args = condition[1..] filters = if @modelClass.schema.uses_coco_versions or @modelClass.schema.uses_coco_permissions then [filter: {index: true}] else [filter: {}]
query = query[name](args...) if @modelClass.schema.uses_coco_permissions and req.user
catch e filters.push {filter: {index: req.user.get('id')}}
return @sendError(res, 422, 'Badly formed conditions.') projection = null
if req.query.project is 'true'
projection = PROJECT
else if req.query.project
if @modelClass.className is 'User'
projection = PROJECT
log.warn "Whoa, we haven't yet thought about public properties for User projection yet."
else
projection = {}
projection[field] = 1 for field in req.query.project.split(',')
for filter in filters
callback = (err, results) =>
return @sendDatabaseError(res, err) if err
for r in results.results ? results
obj = r.obj ? r
continue if obj in matchedObjects # TODO: probably need a better equality check
matchedObjects.push obj
filters.pop() # doesn't matter which one
unless filters.length
res.send matchedObjects
res.end()
if term
filter.project = projection
@modelClass.textSearch term, filter, callback
else
args = [filter.filter]
args.push projection if projection
@modelClass.find(args...).limit(FETCH_LIMIT).exec callback
# if it's not a text search but the user is an admin, let him try stuff anyway
else if req.user?.isAdmin()
# admins can send any sort of query down the wire
filter = {}
filter[key] = (val for own key, val of req.query.filter when key not in specialParameters) if 'filter' of req.query
query = @modelClass.find(filter)
# Conditions are chained query functions, for example: query.find().limit(20).sort('-dateCreated')
conditions = JSON.parse(req.query.conditions || '[]')
try
for condition in conditions
name = condition[0]
f = query[name]
args = condition[1..]
query = query[name](args...)
catch e
return @sendError(res, 422, 'Badly formed conditions.')
query.exec (err, documents) =>
return @sendDatabaseError(res, err) if err
documents = (@formatEntity(req, doc) for doc in documents)
@sendSuccess(res, documents)
# regular users are only allowed text searches for now, without any additional filters or sorting
else
return @sendUnauthorizedError(res)
query.exec (err, documents) =>
return @sendDatabaseError(res, err) if err
documents = (@formatEntity(req, doc) for doc in documents)
@sendSuccess(res, documents)
getById: (req, res, id) -> getById: (req, res, id) ->
# return @sendNotFoundError(res) # for testing # return @sendNotFoundError(res) # for testing
@ -153,44 +200,6 @@ module.exports = class Handler
return @sendDatabaseError(res, err) if err return @sendDatabaseError(res, err) if err
@sendSuccess(res, @formatEntity(req, document)) @sendSuccess(res, @formatEntity(req, document))
# project=true or project=name,description,slug for example
search: (req, res) ->
unless @modelClass.schema.uses_coco_search
return @sendNotFoundError(res)
term = req.query.term
matchedObjects = []
filters = if @modelClass.schema.uses_coco_versions or @modelClass.schema.uses_coco_permissions then [filter: {index: true}] else [filter: {}]
if @modelClass.schema.uses_coco_permissions and req.user
filters.push {filter: {index: req.user.get('id')}}
projection = null
if req.query.project is 'true'
projection = PROJECT
else if req.query.project
if @modelClass.className is 'User'
projection = PROJECT
log.warn "Whoa, we haven't yet thought about public properties for User projection yet."
else
projection = {}
projection[field] = 1 for field in req.query.project.split(',')
for filter in filters
callback = (err, results) =>
return @sendDatabaseError(res, err) if err
for r in results.results ? results
obj = r.obj ? r
continue if obj in matchedObjects # TODO: probably need a better equality check
matchedObjects.push obj
filters.pop() # doesn't matter which one
unless filters.length
res.send matchedObjects
res.end()
if term
filter.project = projection
@modelClass.textSearch term, filter, callback
else
args = [filter.filter]
args.push projection if projection
@modelClass.find(args...).limit(FETCH_LIMIT).exec callback
versions: (req, res, id) -> versions: (req, res, id) ->
# TODO: a flexible system for doing GAE-like cursors for these sort of paginating queries # TODO: a flexible system for doing GAE-like cursors for these sort of paginating queries
# 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.
@ -420,3 +429,7 @@ module.exports = class Handler
dict[document.id] = document dict[document.id] = document
res.send dict res.send dict
res.end() res.end()
delete: (req, res) -> @sendMethodNotAllowed res, @allowedMethods, "DELETE not allowed."
head: (req, res) -> @sendMethodNotAllowed res, @allowedMethods, "HEAD not allowed."

View file

@ -17,8 +17,9 @@ module.exports.notFound = (res, message='Not found.') ->
res.send 404, message res.send 404, message
res.end() res.end()
module.exports.badMethod = (res, message='Method Not Allowed') -> module.exports.badMethod = (res, allowed=['GET', 'POST', 'PUT', 'PATCH'], message='Method Not Allowed') ->
# TODO: The response MUST include an Allow header containing a list of valid methods for the requested resource allowHeader = _.reduce allowed, ((str, current) -> str += ', ' + current)
res.set 'Allow', allowHeader # TODO not sure if these are always the case
res.send 405, message res.send 405, message
res.end() res.end()
@ -40,4 +41,4 @@ module.exports.gatewayTimeoutError = (res, message="Gateway timeout") ->
module.exports.clientTimeout = (res, message="The server did not recieve the client response in a timely manner") -> module.exports.clientTimeout = (res, message="The server did not recieve the client response in a timely manner") ->
res.send 408, message res.send 408, message
res.end() res.end()

View file

@ -14,6 +14,7 @@ module.exports.handlers =
module.exports.routes = module.exports.routes =
[ [
'routes/admin'
'routes/auth' 'routes/auth'
'routes/contact' 'routes/contact'
'routes/db' 'routes/db'

View file

@ -9,7 +9,7 @@ LevelComponentHandler = class LevelComponentHandler extends Handler
'description' 'description'
'code' 'code'
'js' 'js'
'language' 'codeLanguage'
'dependencies' 'dependencies'
'propertyDocumentation' 'propertyDocumentation'
'configSchema' 'configSchema'
@ -25,4 +25,4 @@ LevelComponentHandler = class LevelComponentHandler extends Handler
req.method is 'GET' or req.user?.isAdmin() req.method is 'GET' or req.user?.isAdmin()
module.exports = new LevelComponentHandler() module.exports = new LevelComponentHandler()

View file

@ -14,14 +14,14 @@ class LevelSessionHandler extends Handler
getByRelationship: (req, res, args...) -> getByRelationship: (req, res, args...) ->
return @getActiveSessions req, res if args.length is 2 and args[1] is 'active' return @getActiveSessions req, res if args.length is 2 and args[1] is 'active'
super(arguments...) super(arguments...)
formatEntity: (req, document) -> formatEntity: (req, document) ->
documentObject = super(req, document) documentObject = super(req, document)
if req.user.isAdmin() or req.user.id is document.creator if req.user.isAdmin() or req.user.id is document.creator or ('employer' in req.user.get('permissions'))
return documentObject return documentObject
else else
return _.omit documentObject, ['submittedCode','code'] return _.omit documentObject, ['submittedCode','code']
getActiveSessions: (req, res) -> getActiveSessions: (req, res) ->
return @sendUnauthorizedError(res) unless req.user.isAdmin() return @sendUnauthorizedError(res) unless req.user.isAdmin()
start = new Date() start = new Date()
@ -34,6 +34,7 @@ class LevelSessionHandler extends Handler
hasAccessToDocument: (req, document, method=null) -> hasAccessToDocument: (req, document, method=null) ->
return true if req.method is 'GET' and document.get('totalScore') return true if req.method is 'GET' and document.get('totalScore')
return true if ('employer' in req.user.get('permissions')) and (method ? req.method).toLowerCase() is 'get'
super(arguments...) super(arguments...)
module.exports = new LevelSessionHandler() module.exports = new LevelSessionHandler()

View file

@ -7,7 +7,7 @@ LevelSystemHandler = class LevelSystemHandler extends Handler
'description' 'description'
'code' 'code'
'js' 'js'
'language' 'codeLanguage'
'dependencies' 'dependencies'
'propertyDocumentation' 'propertyDocumentation'
'configSchema' 'configSchema'

View file

@ -1,24 +1,15 @@
mongoose = require('mongoose') mongoose = require('mongoose')
Achievement = require('../achievements/Achievement')
EarnedAchievement = require '../achievements/EarnedAchievement' EarnedAchievement = require '../achievements/EarnedAchievement'
User = require '../users/User'
LocalMongo = require '../../app/lib/LocalMongo' LocalMongo = require '../../app/lib/LocalMongo'
util = require '../../app/lib/utils' util = require '../../app/lib/utils'
log = require 'winston' log = require 'winston'
achievements = {} achievements = {}
loadAchievements = ->
achievements = {}
query = Achievement.find({})
query.exec (err, docs) ->
_.each docs, (achievement) ->
category = achievement.get 'collection'
achievements[category] = [] unless category of achievements
achievements[category].push achievement
loadAchievements()
module.exports = AchievablePlugin = (schema, options) -> module.exports = AchievablePlugin = (schema, options) ->
User = require '../users/User' # Avoid mutual inclusion cycles
Achievement = require('../achievements/Achievement')
checkForAchievement = (doc) -> checkForAchievement = (doc) ->
collectionName = doc.constructor.modelName collectionName = doc.constructor.modelName
@ -53,6 +44,8 @@ module.exports = AchievablePlugin = (schema, options) ->
achievement: achievement._id.toHexString() achievement: achievement._id.toHexString()
achievementName: achievement.get 'name' achievementName: achievement.get 'name'
} }
worth = achievement.get('worth')
earnedPoints = 0 earnedPoints = 0
wrapUp = -> wrapUp = ->
# Update user's experience points # Update user's experience points
@ -67,23 +60,37 @@ module.exports = AchievablePlugin = (schema, options) ->
newAmount = docObj[proportionalTo] newAmount = docObj[proportionalTo]
if originalAmount isnt newAmount if originalAmount isnt newAmount
expFunction = achievement.getExpFunction()
earned.notified = false earned.notified = false
earned.achievedAmount = newAmount earned.achievedAmount = newAmount
earned.changed = Date.now() earned.earnedPoints = (expFunction(newAmount) - expFunction(originalAmount)) * worth
EarnedAchievement.findOneAndUpdate({achievement:earned.achievement, user:earned.user}, earned, upsert:true, (err, docs) -> earned.previouslyAchievedAmount = originalAmount
return log.debug err if err? EarnedAchievement.update {achievement:earned.achievement, user:earned.user}, earned, {upsert: true}, (err) ->
) return log.debug err if err?
earnedPoints = achievement.get('worth') * (newAmount - originalAmount) earnedPoints = earned.earnedPoints
log.debug earnedPoints
wrapUp() wrapUp()
else # not alreadyAchieved else # not alreadyAchieved
log.debug 'Creating a new earned achievement called \'' + (achievement.get 'name') + '\' for ' + userID log.debug 'Creating a new earned achievement called \'' + (achievement.get 'name') + '\' for ' + userID
earned.earnedPoints = worth
(new EarnedAchievement(earned)).save (err, doc) -> (new EarnedAchievement(earned)).save (err, doc) ->
return log.debug err if err? return log.debug err if err?
earnedPoints = worth
earnedPoints = achievement.get('worth')
wrapUp() wrapUp()
delete before[doc.id] unless isNew # This assumes everything we patch has a _id delete before[doc.id] unless isNew # This assumes everything we patch has a _id
return return
module.exports.loadAchievements = ->
achievements = {}
Achievement = require('../achievements/Achievement')
query = Achievement.find({})
query.exec (err, docs) ->
_.each docs, (achievement) ->
category = achievement.get 'collection'
achievements[category] = [] unless category of achievements
achievements[category].push achievement
AchievablePlugin.loadAchievements()

View file

@ -1,12 +1,29 @@
mongoose = require('mongoose') mongoose = require('mongoose')
User = require('../users/User')
textSearch = require('mongoose-text-search') textSearch = require('mongoose-text-search')
module.exports.MigrationPlugin = (schema, migrations) ->
# Property name migrations made EZ
# This is for just when you want one property to be named differently
# 1. Change the schema and the client/server logic to use the new name
# 2. Add this plugin to the target models, passing in a dictionary of old/new names.
# 3. Check that tests still run, deploy to production.
# 4. Run db.<collection>.update({}, { $rename: {'<oldname>':'<newname>'} }, { multi: true }) on the server
# 5. Remove the names you added to the migrations dictionaries for the next deploy
schema.post 'init', ->
for oldKey in _.keys migrations
val = @get oldKey
@set oldKey, undefined
continue if val is undefined
newKey = migrations[oldKey]
@set newKey, val
module.exports.PatchablePlugin = (schema) -> module.exports.PatchablePlugin = (schema) ->
schema.is_patchable = true schema.is_patchable = true
schema.index({'target.original':1, 'status':'1', 'created':-1}) schema.index({'target.original':1, 'status':'1', 'created':-1})
RESERVED_NAMES = ['search', 'names'] RESERVED_NAMES = ['names']
module.exports.NamedPlugin = (schema) -> module.exports.NamedPlugin = (schema) ->
schema.uses_coco_names = true schema.uses_coco_names = true

View file

@ -0,0 +1,27 @@
log = require 'winston'
errors = require '../commons/errors'
handlers = require('../commons/mapping').handlers
mongoose = require('mongoose')
module.exports.setup = (app) ->
app.post '/admin/*', (req, res) ->
# TODO apparently I can leave this out as long as I use res.send
res.setHeader('Content-Type', 'application/json')
module = req.path[7..]
parts = module.split('/')
module = parts[0]
return errors.unauthorized(res, 'Must be admin to access this area.') unless req.user?.isAdmin()
try
moduleName = module.replace '.', '_'
name = handlers[moduleName]
handler = require('../' + name)
return handler[parts[1]](req, res, parts[2..]...) if parts[1] of handler
catch error
log.error("Error trying db method '#{req.route.method}' route '#{parts}' from #{name}: #{error}")
errors.notFound(res, "Route #{req.path} not found.")

View file

@ -61,8 +61,11 @@ module.exports.setup = (app) ->
req.logIn(user, (err) -> req.logIn(user, (err) ->
return next(err) if (err) return next(err) if (err)
res.send(UserHandler.formatEntity(req, req.user)) activity = req.user.trackActivity 'login', 1
return res.end() user.update {activity: activity}, (err) ->
return next(err) if (err)
res.send(UserHandler.formatEntity(req, req.user))
return res.end()
) )
)(req, res, next) )(req, res, next)
) )
@ -134,12 +137,12 @@ module.exports.setup = (app) ->
emails = _.clone(user.get('emails')) or {} emails = _.clone(user.get('emails')) or {}
msg = '' msg = ''
if req.query.recruitNotes if req.query.recruitNotes
emails.recruitNotes ?= {} emails.recruitNotes ?= {}
emails.recruitNotes.enabled = false emails.recruitNotes.enabled = false
msg = "Unsubscribed #{req.query.email} from recruiting emails." msg = "Unsubscribed #{req.query.email} from recruiting emails."
else else
msg = "Unsubscribed #{req.query.email} from all CodeCombat emails. Sorry to see you go!" msg = "Unsubscribed #{req.query.email} from all CodeCombat emails. Sorry to see you go!"
emailSettings.enabled = false for emailSettings in _.values(emails) emailSettings.enabled = false for emailSettings in _.values(emails)
@ -147,7 +150,7 @@ module.exports.setup = (app) ->
emails.generalNews.enabled = false emails.generalNews.enabled = false
emails.anyNotes ?= {} emails.anyNotes ?= {}
emails.anyNotes.enabled = false emails.anyNotes.enabled = false
user.update {$set: {emails: emails}}, {}, => user.update {$set: {emails: emails}}, {}, =>
return errors.serverError res, 'Database failure.' if err return errors.serverError res, 'Database failure.' if err
res.send msg + "<p><a href='/account/settings'>Account settings</a></p>" res.send msg + "<p><a href='/account/settings'>Account settings</a></p>"
@ -172,7 +175,7 @@ module.exports.makeNewUser = makeNewUser = (req) ->
user = new User({anonymous:true}) user = new User({anonymous:true})
user.set 'testGroupNumber', Math.floor(Math.random() * 256) # also in app/lib/auth user.set 'testGroupNumber', Math.floor(Math.random() * 256) # also in app/lib/auth
user.set 'preferredLanguage', languages.languageCodeFromAcceptedLanguages req.acceptedLanguages user.set 'preferredLanguage', languages.languageCodeFromAcceptedLanguages req.acceptedLanguages
createMailOptions = (receiver, password) -> createMailOptions = (receiver, password) ->
# TODO: use email templates here # TODO: use email templates here
options = options =
@ -181,4 +184,3 @@ 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}"

View file

@ -34,7 +34,6 @@ module.exports.setup = (app) ->
return handler.getLatestVersion(req, res, parts[1], parts[3]) if parts[2] is 'version' return handler.getLatestVersion(req, res, parts[1], parts[3]) if parts[2] is 'version'
return handler.versions(req, res, parts[1]) if parts[2] is 'versions' return handler.versions(req, res, parts[1]) if parts[2] is 'versions'
return handler.files(req, res, parts[1]) if parts[2] is 'files' return handler.files(req, res, parts[1]) if parts[2] is 'files'
return handler.search(req, res) if req.route.method is 'get' and parts[1] is 'search'
return handler.getNamesByIDs(req, res) if req.route.method in ['get', 'post'] and parts[1] is 'names' return handler.getNamesByIDs(req, res) if req.route.method in ['get', 'post'] and parts[1] is 'names'
return handler.getByRelationship(req, res, parts[1..]...) if parts.length > 2 return handler.getByRelationship(req, res, parts[1..]...) if parts.length > 2
return handler.getById(req, res, parts[1]) if req.route.method is 'get' and parts[1]? return handler.getById(req, res, parts[1]) if req.route.method is 'get' and parts[1]?

View file

@ -8,7 +8,7 @@ module.exports.setup = (app) ->
app.all '/file*', (req, res) -> app.all '/file*', (req, res) ->
return fileGet(req, res) if req.route.method is 'get' return fileGet(req, res) if req.route.method is 'get'
return filePost(req, res) if req.route.method is 'post' return filePost(req, res) if req.route.method is 'post'
return errors.badMethod(res) return errors.badMethod(res, ['GET', 'POST'])
fileGet = (req, res) -> fileGet = (req, res) ->

View file

@ -4,7 +4,7 @@ errors = require '../commons/errors'
module.exports.setup = (app) -> module.exports.setup = (app) ->
app.all '/folder*', (req, res) -> app.all '/folder*', (req, res) ->
return folderGet(req, res) if req.route.method is 'get' return folderGet(req, res) if req.route.method is 'get'
return errors.badMethod(res) return errors.badMethod(res, ['GET'])
folderGet = (req, res) -> folderGet = (req, res) ->
folder = req.path[7..] folder = req.path[7..]
@ -15,4 +15,4 @@ folderGet = (req, res) ->
mongoose.connection.db.collection 'media.files', (errors, collection) -> mongoose.connection.db.collection 'media.files', (errors, collection) ->
collection.find({'metadata.path': folder}).toArray (err, results) -> collection.find({'metadata.path': folder}).toArray (err, results) ->
res.send(results) res.send(results)
res.end() res.end()

View file

@ -11,7 +11,7 @@ module.exports.setup = (app) ->
app.all '/languages', (req, res) -> app.all '/languages', (req, res) ->
# Now that these are in the client, not sure when we would use this, but hey # Now that these are in the client, not sure when we would use this, but hey
return errors.badMethod(res) if req.route.method isnt 'get' return errors.badMethod(res, ['GET']) if req.route.method isnt 'get'
res.send(languages) res.send(languages)
return res.end() return res.end()

View file

@ -28,7 +28,7 @@ module.exports.setup = (app) ->
app.all '/queue/*', (req, res) -> app.all '/queue/*', (req, res) ->
setResponseHeaderToJSONContentType res setResponseHeaderToJSONContentType res
queueName = getQueueNameFromPath req.path queueName = getQueueNameFromPath req.path
try try
handler = loadQueueHandler queueName handler = loadQueueHandler queueName
@ -64,7 +64,7 @@ isHTTPMethodPost = (req) -> return req.route.method is 'post'
isHTTPMethodPut = (req) -> return req.route.method is 'put' isHTTPMethodPut = (req) -> return req.route.method is 'put'
sendMethodNotSupportedError = (req, res) -> errors.badMethod(res,"Queues do not support the HTTP method used." ) sendMethodNotSupportedError = (req, res) -> errors.badMethod(res, ['GET', 'POST', 'PUT'], "Queues do not support the HTTP method used." )
sendQueueError = (req,res, error) -> errors.serverError(res, "Route #{req.path} had a problem: #{error}") sendQueueError = (req,res, error) -> errors.serverError(res, "Route #{req.path} had a problem: #{error}")

View file

@ -15,3 +15,4 @@ module.exports.templates =
patch_created: 'tem_xhxuNosLALsizTNojBjNcL' patch_created: 'tem_xhxuNosLALsizTNojBjNcL'
change_made_notify_watcher: 'tem_7KVkfmv9SZETb25dtHbUtG' change_made_notify_watcher: 'tem_7KVkfmv9SZETb25dtHbUtG'
one_time_recruiting_email: 'tem_mdFMgtcczHKYu94Jmq68j8' one_time_recruiting_email: 'tem_mdFMgtcczHKYu94Jmq68j8'
greed_tournament_rank: 'tem_c4KYnk2TriEkkZx5NqqGLG'

View file

@ -29,6 +29,17 @@ UserSchema.methods.isAdmin = ->
p = @get('permissions') p = @get('permissions')
return p and 'admin' in p return p and 'admin' in p
UserSchema.methods.trackActivity = (activityName, increment) ->
now = new Date()
increment ?= parseInt increment or 1
increment = Math.max increment, 0
activity = @get('activity') ? {}
activity[activityName] ?= {first: now, count: 0}
activity[activityName].count += increment
activity[activityName].last = now
@set 'activity', activity
activity
emailNameMap = emailNameMap =
generalNews: 'announcement' generalNews: 'announcement'
adventurerNews: 'tester' adventurerNews: 'tester'

View file

@ -48,7 +48,7 @@ UserHandler = class UserHandler extends Handler
delete obj[prop] for prop in serverProperties delete obj[prop] for prop in serverProperties
includePrivates = req.user and (req.user.isAdmin() or req.user._id.equals(document._id)) includePrivates = req.user and (req.user.isAdmin() or req.user._id.equals(document._id))
delete obj[prop] for prop in privateProperties unless includePrivates delete obj[prop] for prop in privateProperties unless includePrivates
includeCandidate = includePrivates or (obj.jobProfileApproved and req.user and ('employer' in (req.user.get('permissions') ? [])) and @employerCanViewCandidate req.user, obj) includeCandidate = includePrivates or (obj.jobProfile?.active and req.user and ('employer' in (req.user.get('permissions') ? [])) and @employerCanViewCandidate req.user, obj)
delete obj[prop] for prop in candidateProperties unless includeCandidate delete obj[prop] for prop in candidateProperties unless includeCandidate
return obj return obj
@ -189,11 +189,14 @@ UserHandler = class UserHandler extends Handler
return @avatar(req, res, args[0]) if args[1] is 'avatar' return @avatar(req, res, args[0]) if args[1] is 'avatar'
return @getNamesByIDs(req, res) if args[1] is 'names' return @getNamesByIDs(req, res) if args[1] is 'names'
return @nameToID(req, res, args[0]) if args[1] is 'nameToID' return @nameToID(req, res, args[0]) if args[1] is 'nameToID'
return @getLevelSessionsForEmployer(req, res, args[0]) if args[1] is 'level.sessions' and args[2] is 'employer'
return @getLevelSessions(req, res, args[0]) if args[1] is 'level.sessions' return @getLevelSessions(req, res, args[0]) if args[1] is 'level.sessions'
return @getCandidates(req, res) if args[1] is 'candidates' return @getCandidates(req, res) if args[1] is 'candidates'
return @getEmployers(req, res) if args[1] is 'employers'
return @getSimulatorLeaderboard(req, res, args[0]) if args[1] is 'simulatorLeaderboard' return @getSimulatorLeaderboard(req, res, args[0]) if args[1] is 'simulatorLeaderboard'
return @getMySimulatorLeaderboardRank(req, res, args[0]) if args[1] is 'simulator_leaderboard_rank' return @getMySimulatorLeaderboardRank(req, res, args[0]) if args[1] is 'simulator_leaderboard_rank'
return @getEarnedAchievements(req, res, args[0]) if args[1] is 'achievements' return @getEarnedAchievements(req, res, args[0]) if args[1] is 'achievements'
return @trackActivity(req, res, args[0], args[2], args[3]) if args[1] is 'track' and args[2]
return @sendNotFoundError(res) return @sendNotFoundError(res)
super(arguments...) super(arguments...)
@ -225,9 +228,18 @@ UserHandler = class UserHandler extends Handler
res.redirect photoURL res.redirect photoURL
res.end() res.end()
getLevelSessionsForEmployer: (req, res, userID) ->
return @sendUnauthorizedError(res) unless req.user._id+'' is userID or req.user.isAdmin() or ('employer' in req.user.get('permissions'))
query = creator: userID, levelID: {$in: ['gridmancer', 'greed', 'dungeon-arena', 'brawlwood', 'gold-rush']}
projection = 'levelName levelID team playtime codeLanguage submitted' # code totalScore
LevelSession.find(query).select(projection).exec (err, documents) =>
return @sendDatabaseError(res, err) if err
documents = (LevelSessionHandler.formatEntity(req, doc) for doc in documents)
@sendSuccess(res, documents)
getLevelSessions: (req, res, userID) -> getLevelSessions: (req, res, userID) ->
return @sendUnauthorizedError(res) unless req.user._id+'' is userID or req.user.isAdmin() return @sendUnauthorizedError(res) unless req.user._id+'' is userID or req.user.isAdmin()
query = {'creator': userID} query = creator: userID
projection = null projection = null
if req.query.project if req.query.project
projection = {} projection = {}
@ -249,6 +261,25 @@ UserHandler = class UserHandler extends Handler
doc.save() doc.save()
@sendSuccess(res, cleandocs) @sendSuccess(res, cleandocs)
trackActivity: (req, res, userID, activityName, increment=1) ->
return @sendMethodNotAllowed res unless req.method is 'POST'
isMe = userID is req.user._id + ''
isAuthorized = isMe or req.user.isAdmin()
isAuthorized ||= ('employer' in req.user.get('permissions')) and (activityName in ['viewed_by_employer', 'messaged_by_employer'])
return @sendUnauthorizedError res unless isAuthorized
updateUser = (user) =>
activity = user.trackActivity activityName, increment
user.update {activity: activity}, (err) =>
return @sendDatabaseError res, err if err
@sendSuccess res, result: 'success'
if isMe
updateUser(req.user)
else
@getDocumentForIdOrSlug userID, (err, user) =>
return @sendDatabaseError res, err if err
return @sendNotFoundError res unless user
updateUser user
agreeToEmployerAgreement: (req, res) -> agreeToEmployerAgreement: (req, res) ->
userIsAnonymous = req.user?.get('anonymous') userIsAnonymous = req.user?.get('anonymous')
if userIsAnonymous then return errors.unauthorized(res, "You need to be logged in to agree to the employer agreeement.") if userIsAnonymous then return errors.unauthorized(res, "You need to be logged in to agree to the employer agreeement.")
@ -278,13 +309,11 @@ UserHandler = class UserHandler extends Handler
getCandidates: (req, res) -> getCandidates: (req, res) ->
authorized = req.user.isAdmin() or ('employer' in req.user.get('permissions')) authorized = req.user.isAdmin() or ('employer' in req.user.get('permissions'))
since = (new Date((new Date()) - 2 * 30.4 * 86400 * 1000)).toISOString() since = (new Date((new Date()) - 2 * 30.4 * 86400 * 1000)).toISOString()
#query = {'jobProfile.active': true, 'jobProfile.updated': {$gt: since}}
query = {'jobProfile.updated': {$gt: since}} query = {'jobProfile.updated': {$gt: since}}
query.jobProfileApproved = true unless req.user.isAdmin() #query.jobProfileApproved = true unless req.user.isAdmin() # We split into featured and other now.
query['jobProfile.active'] = true unless req.user.isAdmin() query['jobProfile.active'] = true unless req.user.isAdmin()
selection = 'jobProfile' selection = 'jobProfile jobProfileApproved photoURL'
selection += ' email' if authorized selection += ' email' if authorized
selection += ' jobProfileApproved' if req.user.isAdmin()
User.find(query).select(selection).exec (err, documents) => User.find(query).select(selection).exec (err, documents) =>
return @sendDatabaseError(res, err) if err return @sendDatabaseError(res, err) if err
candidates = (candidate for candidate in documents when @employerCanViewCandidate req.user, candidate.toObject()) candidates = (candidate for candidate in documents when @employerCanViewCandidate req.user, candidate.toObject())
@ -292,7 +321,7 @@ UserHandler = class UserHandler extends Handler
@sendSuccess(res, candidates) @sendSuccess(res, candidates)
formatCandidate: (authorized, document) -> formatCandidate: (authorized, document) ->
fields = if authorized then ['jobProfile', 'jobProfileApproved', 'photoURL', '_id'] else ['jobProfile'] fields = if authorized then ['jobProfile', 'jobProfileApproved', 'photoURL', '_id'] else ['jobProfile', 'jobProfileApproved']
obj = _.pick document.toObject(), fields obj = _.pick document.toObject(), fields
obj.photoURL ||= obj.jobProfile.photoURL if authorized obj.photoURL ||= obj.jobProfile.photoURL if authorized
subfields = ['country', 'city', 'lookingFor', 'jobTitle', 'skills', 'experience', 'updated', 'active'] subfields = ['country', 'city', 'lookingFor', 'jobTitle', 'skills', 'experience', 'updated', 'active']
@ -311,6 +340,14 @@ UserHandler = class UserHandler extends Handler
return false if job.employer?.toLowerCase() is employer.get('employerAt')?.toLowerCase() return false if job.employer?.toLowerCase() is employer.get('employerAt')?.toLowerCase()
true true
getEmployers: (req, res) ->
return @sendUnauthorizedError(res) unless req.user.isAdmin()
query = {employerAt: {$exists: true}}
selection = 'name firstName lastName email activity signedEmployerAgreement photoURL employerAt'
User.find(query).select(selection).lean().exec (err, documents) =>
return @sendDatabaseError res, err if err
@sendSuccess res, documents
buildGravatarURL: (user, size, fallback) -> buildGravatarURL: (user, size, fallback) ->
emailHash = @buildEmailHash user emailHash = @buildEmailHash user
fallback ?= "http://codecombat.com/file/db/thang.type/52a00d55cf1818f2be00000b/portrait.png" fallback ?= "http://codecombat.com/file/db/thang.type/52a00d55cf1818f2be00000b/portrait.png"

View file

@ -93,7 +93,10 @@ sendMain = (req, res) ->
log.error "Error modifying main.html: #{err}" if err log.error "Error modifying main.html: #{err}" if err
# insert the user object directly into the html so the application can have it immediately. Sanitize </script> # insert the user object directly into the html so the application can have it immediately. Sanitize </script>
data = data.replace('"userObjectTag"', JSON.stringify(UserHandler.formatEntity(req, req.user)).replace(/\//g, '\\/')) data = data.replace('"userObjectTag"', JSON.stringify(UserHandler.formatEntity(req, req.user)).replace(/\//g, '\\/'))
res.send data res.header "Cache-Control", "no-cache, no-store, must-revalidate"
res.header "Pragma", "no-cache"
res.header "Expires", 0
res.send 200, data
setupFacebookCrossDomainCommunicationRoute = (app) -> setupFacebookCrossDomainCommunicationRoute = (app) ->
app.get '/channel.html', (req, res) -> app.get '/channel.html', (req, res) ->

View file

@ -0,0 +1,80 @@
FacebookHandler = require 'lib/FacebookHandler'
mockAuthEvent =
response:
authResponse:
accessToken: "aksdhjflkqjrj245234b52k345q344le4j4k5l45j45s4dkljvdaskl"
userID: "4301938"
expiresIn: 5138
signedRequest: "akjsdhfjkhea.3423nkfkdsejnfkd"
status: "connected"
# Whatev, it's all public info anyway
mockMe =
id: "4301938"
email: "scott@codecombat.com"
first_name: "Scott"
gender: "male"
last_name: "Erickson"
link: "https://www.facebook.com/scott.erickson.779"
locale: "en_US"
name: "Scott Erickson"
timezone: -7
updated_time: "2014-05-21T04:58:06+0000"
username: "scott.erickson.779"
verified: true
work: [
{
employer:
id: "167559910060759"
name: "CodeCombat"
location:
id: "114952118516947"
name: "San Francisco, California"
start_date: "2013-02-28"
}
{
end_date: "2013-01-31"
employer:
id: "39198748555"
name: "Skritter"
location:
id: "106109576086811"
name: "Oberlin, Ohio"
start_date: "2008-06-01"
}
]
window.FB ?= {
api: ->
}
describe 'lib/FacebookHandler.coffee', ->
it 'on facebook-logged-in, gets data from FB and sends a patch to the server', ->
me.clear({silent:true})
me.markToRevert()
me.set({_id: '12345'})
spyOn FB, 'api'
new FacebookHandler()
Backbone.Mediator.publish 'facebook-logged-in', mockAuthEvent
expect(FB.api).toHaveBeenCalled()
apiArgs = FB.api.calls.argsFor(0)
expect(apiArgs[0]).toBe('/me')
apiArgs[1](mockMe) # sending the 'response'
request = jasmine.Ajax.requests.mostRecent()
expect(request).toBeDefined()
params = JSON.parse request.params
expect(params.firstName).toBe(mockMe.first_name)
expect(params.lastName).toBe(mockMe.last_name)
expect(params.gender).toBe(mockMe.gender)
expect(params.email).toBe(mockMe.email)
expect(params.facebookID).toBe(mockMe.id)
expect(request.method).toBe('PATCH')
expect(_.string.startsWith(request.url, '/db/user/12345')).toBeTruthy()

View file

@ -2,7 +2,7 @@ describe 'Local Mongo queries', ->
LocalMongo = require 'lib/LocalMongo' LocalMongo = require 'lib/LocalMongo'
beforeEach -> beforeEach ->
this.fixture1 = @fixture1 =
'id': 'somestring' 'id': 'somestring'
'value': 9000 'value': 9000
'levels': [3, 8, 21] 'levels': [3, 8, 21]
@ -10,68 +10,72 @@ describe 'Local Mongo queries', ->
'type': 'unicorn' 'type': 'unicorn'
'likes': ['poptarts', 'popsicles', 'popcorn'] 'likes': ['poptarts', 'popsicles', 'popcorn']
this.fixture2 = this: is: so: 'deep' @fixture2 = this: is: so: 'deep'
it 'regular match of a property', -> it 'regular match of a property', ->
expect(LocalMongo.matchesQuery(this.fixture1, 'gender': 'unicorn')).toBeFalsy() expect(LocalMongo.matchesQuery(@fixture1, 'gender': 'unicorn')).toBeFalsy()
expect(LocalMongo.matchesQuery(this.fixture1, 'type':'unicorn')).toBeTruthy() expect(LocalMongo.matchesQuery(@fixture1, 'type':'unicorn')).toBeTruthy()
expect(LocalMongo.matchesQuery(this.fixture1, 'type':'zebra')).toBeFalsy() expect(LocalMongo.matchesQuery(@fixture1, 'type':'zebra')).toBeFalsy()
expect(LocalMongo.matchesQuery(this.fixture1, 'type':'unicorn', 'id':'somestring')).toBeTruthy() expect(LocalMongo.matchesQuery(@fixture1, 'type':'unicorn', 'id':'somestring')).toBeTruthy()
it 'array match of a property', -> it 'array match of a property', ->
expect(LocalMongo.matchesQuery(this.fixture1, 'likes':'poptarts')).toBeTruthy() expect(LocalMongo.matchesQuery(@fixture1, 'likes':'poptarts')).toBeTruthy()
expect(LocalMongo.matchesQuery(this.fixture1, 'likes':'walks on the beach')).toBeFalsy() expect(LocalMongo.matchesQuery(@fixture1, 'likes':'walks on the beach')).toBeFalsy()
it 'nested match', -> it 'nested match', ->
expect(LocalMongo.matchesQuery(this.fixture2, 'this.is.so':'deep')).toBeTruthy() expect(LocalMongo.matchesQuery(@fixture2, 'this.is.so':'deep')).toBeTruthy()
it '$gt selector', -> it '$gt selector', ->
expect(LocalMongo.matchesQuery(this.fixture1, 'value': '$gt': 8000)).toBeTruthy() expect(LocalMongo.matchesQuery(@fixture1, 'value': '$gt': 8000)).toBeTruthy()
expect(LocalMongo.matchesQuery(this.fixture1, 'value': '$gt': [8000, 10000])).toBeTruthy() expect(LocalMongo.matchesQuery(@fixture1, 'value': '$gt': [8000, 10000])).toBeTruthy()
expect(LocalMongo.matchesQuery(this.fixture1, 'levels': '$gt': [10, 20, 30])).toBeTruthy() expect(LocalMongo.matchesQuery(@fixture1, 'levels': '$gt': [10, 20, 30])).toBeTruthy()
expect(LocalMongo.matchesQuery(this.fixture1, 'value': '$gt': 9000)).toBeFalsy() expect(LocalMongo.matchesQuery(@fixture1, 'value': '$gt': 9000)).toBeFalsy()
expect(LocalMongo.matchesQuery(this.fixture1, 'value': {'$gt': 8000}, 'worth': {'$gt': 5})).toBeTruthy() expect(LocalMongo.matchesQuery(@fixture1, 'value': {'$gt': 8000}, 'worth': {'$gt': 5})).toBeTruthy()
it '$gte selector', -> it '$gte selector', ->
expect(LocalMongo.matchesQuery(this.fixture1, 'value': '$gte': 9001)).toBeFalsy() expect(LocalMongo.matchesQuery(@fixture1, 'value': '$gte': 9001)).toBeFalsy()
expect(LocalMongo.matchesQuery(this.fixture1, 'value': '$gte': 9000)).toBeTruthy() expect(LocalMongo.matchesQuery(@fixture1, 'value': '$gte': 9000)).toBeTruthy()
expect(LocalMongo.matchesQuery(this.fixture1, 'value': '$gte': [9000, 10000])).toBeTruthy() expect(LocalMongo.matchesQuery(@fixture1, 'value': '$gte': [9000, 10000])).toBeTruthy()
expect(LocalMongo.matchesQuery(this.fixture1, 'levels': '$gte': [21, 30])).toBeTruthy() expect(LocalMongo.matchesQuery(@fixture1, 'levels': '$gte': [21, 30])).toBeTruthy()
it '$lt selector', -> it '$lt selector', ->
expect(LocalMongo.matchesQuery(this.fixture1, 'value': '$lt': 9001)).toBeTruthy() expect(LocalMongo.matchesQuery(@fixture1, 'value': '$lt': 9001)).toBeTruthy()
expect(LocalMongo.matchesQuery(this.fixture1, 'value': '$lt': 9000)).toBeFalsy() expect(LocalMongo.matchesQuery(@fixture1, 'value': '$lt': 9000)).toBeFalsy()
expect(LocalMongo.matchesQuery(this.fixture1, 'value': '$lt': [9001, 9000])).toBeTruthy() expect(LocalMongo.matchesQuery(@fixture1, 'value': '$lt': [9001, 9000])).toBeTruthy()
expect(LocalMongo.matchesQuery(this.fixture1, 'levels': '$lt': [10, 20, 30])).toBeTruthy() expect(LocalMongo.matchesQuery(@fixture1, 'levels': '$lt': [10, 20, 30])).toBeTruthy()
expect(LocalMongo.matchesQuery(this.fixture1, 'value': {'$lt': 9001}, 'worth': {'$lt': 7})).toBeTruthy() expect(LocalMongo.matchesQuery(@fixture1, 'value': {'$lt': 9001}, 'worth': {'$lt': 7})).toBeTruthy()
it '$lte selector', -> it '$lte selector', ->
expect(LocalMongo.matchesQuery(this.fixture1, 'value': '$lte': 9000)).toBeTruthy() expect(LocalMongo.matchesQuery(@fixture1, 'value': '$lte': 9000)).toBeTruthy()
expect(LocalMongo.matchesQuery(this.fixture1, 'value': '$lte': 8000)).toBeFalsy() expect(LocalMongo.matchesQuery(@fixture1, 'value': '$lte': 8000)).toBeFalsy()
expect(LocalMongo.matchesQuery(this.fixture1, 'value': {'$lte': 9000}, 'worth': {'$lte': [6, 5]})).toBeTruthy() expect(LocalMongo.matchesQuery(@fixture1, 'value': {'$lte': 9000}, 'worth': {'$lte': [6, 5]})).toBeTruthy()
it '$ne selector', -> it '$ne selector', ->
expect(LocalMongo.matchesQuery(this.fixture1, 'value': '$ne': 9000)).toBeFalsy() expect(LocalMongo.matchesQuery(@fixture1, 'value': '$ne': 9000)).toBeFalsy()
expect(LocalMongo.matchesQuery(this.fixture1, 'id': '$ne': 'otherstring')).toBeTruthy() expect(LocalMongo.matchesQuery(@fixture1, 'id': '$ne': 'otherstring')).toBeTruthy()
expect(LocalMongo.matchesQuery(this.fixture1, 'id': '$ne': ['otherstring', 'somestring'])).toBeFalsy() expect(LocalMongo.matchesQuery(@fixture1, 'id': '$ne': ['otherstring', 'somestring'])).toBeFalsy()
expect(LocalMongo.matchesQuery(this.fixture1, 'likes': '$ne': ['popcorn', 'chicken'])).toBeFalsy() expect(LocalMongo.matchesQuery(@fixture1, 'likes': '$ne': ['popcorn', 'chicken'])).toBeFalsy()
it '$in selector', -> it '$in selector', ->
expect(LocalMongo.matchesQuery(this.fixture1, 'type': '$in': ['unicorn', 'zebra'])).toBeTruthy() expect(LocalMongo.matchesQuery(@fixture1, 'type': '$in': ['unicorn', 'zebra'])).toBeTruthy()
expect(LocalMongo.matchesQuery(this.fixture1, 'type': '$in': ['cats', 'dogs'])).toBeFalsy() expect(LocalMongo.matchesQuery(@fixture1, 'type': '$in': ['cats', 'dogs'])).toBeFalsy()
expect(LocalMongo.matchesQuery(this.fixture1, 'likes': '$in': ['popcorn', 'chicken'])).toBeTruthy() expect(LocalMongo.matchesQuery(@fixture1, 'likes': '$in': ['popcorn', 'chicken'])).toBeTruthy()
it '$nin selector', -> it '$nin selector', ->
expect(LocalMongo.matchesQuery(this.fixture1, 'type': '$nin': ['unicorn', 'zebra'])).toBeFalsy() expect(LocalMongo.matchesQuery(@fixture1, 'type': '$nin': ['unicorn', 'zebra'])).toBeFalsy()
expect(LocalMongo.matchesQuery(this.fixture1, 'type': '$nin': ['cats', 'dogs'])).toBeTruthy() expect(LocalMongo.matchesQuery(@fixture1, 'type': '$nin': ['cats', 'dogs'])).toBeTruthy()
expect(LocalMongo.matchesQuery(this.fixture1, 'likes': '$nin': ['popcorn', 'chicken'])).toBeFalsy() expect(LocalMongo.matchesQuery(@fixture1, 'likes': '$nin': ['popcorn', 'chicken'])).toBeFalsy()
it '$or operator', -> it '$or operator', ->
expect(LocalMongo.matchesQuery(this.fixture1, $or: [{value:9000}, {type:'zebra'}])).toBeTruthy() expect(LocalMongo.matchesQuery(@fixture1, $or: [{value:9000}, {type:'zebra'}])).toBeTruthy()
expect(LocalMongo.matchesQuery(this.fixture1, $or: [{value:9001}, {worth:$lt:10}])).toBeTruthy() expect(LocalMongo.matchesQuery(@fixture1, $or: [{value:9001}, {worth:$lt:10}])).toBeTruthy()
it '$and operator', -> it '$and operator', ->
expect(LocalMongo.matchesQuery(this.fixture1, $and: [{value:9000}, {type:'zebra'}])).toBeFalsy() expect(LocalMongo.matchesQuery(@fixture1, $and: [{value:9000}, {type:'zebra'}])).toBeFalsy()
expect(LocalMongo.matchesQuery(this.fixture1, $and: [{value:9000}, {type:'unicorn'}])).toBeTruthy() expect(LocalMongo.matchesQuery(@fixture1, $and: [{value:9000}, {type:'unicorn'}])).toBeTruthy()
expect(LocalMongo.matchesQuery(this.fixture1, $and: [{value:$gte:9000}, {worth:$lt:10}])).toBeTruthy() expect(LocalMongo.matchesQuery(@fixture1, $and: [{value:$gte:9000}, {worth:$lt:10}])).toBeTruthy()
it '$exists operator', ->
expect(LocalMongo.matchesQuery(@fixture1, type: $exists: true)).toBeTruthy()
expect(LocalMongo.matchesQuery(@fixture1, interesting: $exists: false)).toBeTruthy()

View file

@ -0,0 +1,84 @@
CocoModel = require 'models/CocoModel'
class BlandClass extends CocoModel
@className: 'Bland'
@schema: {
type: 'object'
additionalProperties: false
properties:
number: {type: 'number'}
object: {type: 'object'}
string: {type: 'string'}
_id: {type: 'string'}
}
urlRoot: '/db/bland'
describe 'CocoModel', ->
describe 'save', ->
it 'saves to db/<urlRoot>', ->
b = new BlandClass({})
res = b.save()
request = jasmine.Ajax.requests.mostRecent()
expect(res).toBeDefined()
expect(request.url).toBe(b.urlRoot)
expect(request.method).toBe('POST')
it 'does not save if the data is invalid based on the schema', ->
b = new BlandClass({number: 'NaN'})
res = b.save()
expect(res).toBe(false)
request = jasmine.Ajax.requests.mostRecent()
expect(request).toBeUndefined()
it 'uses PUT when _id is included', ->
b = new BlandClass({_id: 'test'})
b.save()
request = jasmine.Ajax.requests.mostRecent()
expect(request.method).toBe('PUT')
describe 'patch', ->
it 'PATCHes only properties that have changed', ->
b = new BlandClass({_id: 'test', number:1})
b.loaded = true
b.set('string', 'string')
b.patch()
request = jasmine.Ajax.requests.mostRecent()
params = JSON.parse request.params
expect(params.string).toBeDefined()
expect(params.number).toBeUndefined()
it 'collates all changes made over several sets', ->
b = new BlandClass({_id: 'test', number:1})
b.loaded = true
b.set('string', 'string')
b.set('object', {4:5})
b.patch()
request = jasmine.Ajax.requests.mostRecent()
params = JSON.parse request.params
expect(params.string).toBeDefined()
expect(params.object).toBeDefined()
expect(params.number).toBeUndefined()
it 'does not include data from previous patches', ->
b = new BlandClass({_id: 'test', number:1})
b.loaded = true
b.set('object', {1:2})
b.patch()
request = jasmine.Ajax.requests.mostRecent()
attrs = JSON.stringify(b.attributes) # server responds with all
request.response({status: 200, responseText: attrs})
b.set('number', 3)
b.patch()
request = jasmine.Ajax.requests.mostRecent()
params = JSON.parse request.params
expect(params.object).toBeUndefined()
it 'does nothing when there\'s nothing to patch', ->
b = new BlandClass({_id: 'test', number:1})
b.loaded = true
b.set('number', 1)
b.patch()
request = jasmine.Ajax.requests.mostRecent()
expect(request).toBeUndefined()

View file

@ -9,6 +9,10 @@ jasmine.getEnv().addReporter(new jasmine.SpecReporter({
displaySuccessfulSpec: true, displaySuccessfulSpec: true,
displayFailedSpec: true displayFailedSpec: true
})) }))
rep = new jasmine.JsApiReporter()
jasmine.getEnv().addReporter(rep)
GLOBAL._ = require('lodash') GLOBAL._ = require('lodash')
_.str = require('underscore.string') _.str = require('underscore.string')
_.mixin(_.str.exports()) _.mixin(_.str.exports())
@ -26,6 +30,8 @@ models_path = [
'../../server/levels/thangs/LevelThangType' '../../server/levels/thangs/LevelThangType'
'../../server/users/User' '../../server/users/User'
'../../server/patches/Patch' '../../server/patches/Patch'
'../../server/achievements/Achievement'
'../../server/achievements/EarnedAchievement'
] ]
for m in models_path for m in models_path
@ -149,3 +155,13 @@ _drop = (done) ->
chunks = mongoose.connection.db.collection('media.chunks') chunks = mongoose.connection.db.collection('media.chunks')
chunks.remove {}, -> chunks.remove {}, ->
done() done()
tickInterval = null
tick = ->
# When you want jasmine-node to exit after running the tests,
# you have to close the connection first.
if rep.finished
mongoose.disconnect()
clearTimeout tickInterval
tickInterval = setInterval tick, 1000

Some files were not shown because too many files have changed in this diff Show more