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

View file

@ -2,6 +2,12 @@ language: node_js
node_js:
- 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:
- "npm install"
- export DISPLAY=:99.0
@ -9,7 +15,11 @@ before_script:
- "./node_modules/.bin/bower install"
- "gem install sass"
- "./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:
- "./node_modules/jasmine-node/bin/jasmine-node test/server/ --coffee --captureExceptions"
- "./node_modules/karma/bin/karma start --browsers Firefox --single-run"

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

View file

@ -13,9 +13,6 @@ userPropsToSave =
module.exports = FacebookHandler = class FacebookHandler extends CocoClass
constructor: ->
super()
subscriptions:
'facebook-logged-in':'onFacebookLogin'
'facebook-logged-out': 'onFacebookLogout'
@ -42,22 +39,18 @@ module.exports = FacebookHandler = class FacebookHandler extends CocoClass
return
oldEmail = me.get('email')
patch = {}
patch.firstName = r.first_name if r.first_name
patch.lastName = r.last_name if r.last_name
patch.gender = r.gender if r.gender
patch.email = r.email if r.email
patch.facebookID = r.id if r.id
me.set(patch)
patch._id = me.id
me.set('firstName', r.first_name) if r.first_name
me.set('lastName', r.last_name) if r.last_name
me.set('gender', r.gender) if r.gender
me.set('email', r.email) if r.email
me.set('facebookID', r.id) if r.id
Backbone.Mediator.publish('logging-in-with-facebook')
window.tracker?.trackEvent 'Facebook Login'
window.tracker?.identify()
me.save(patch, {
patch: true
me.patch({
error: backboneFailure,
url: "/db/user?facebookID=#{r.id}&facebookAccessToken=#{@authResponse.accessToken}"
url: "/db/user/#{me.id}?facebookID=#{r.id}&facebookAccessToken=#{@authResponse.accessToken}"
success: (model) ->
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-created': 'onSpellCreated'
'application:idle-changed': 'onIdleChanged'
constructor: ->
super(arguments...)
@changedSessionProperties = {}
@saveSession = _.debounce(@saveSession, 1000, {maxWait: 5000})
@playerIsIdle = false
init: ->
super()
@fireScriptsRef = @fireRef?.child('scripts')
@ -36,7 +36,7 @@ module.exports = class LevelBus extends Bus
setSession: (@session) ->
@listenTo(@session, 'change:multiplayer', @onMultiplayerChanged)
@timerIntervalID = setInterval(@incrementSessionPlaytime, 1000)
onIdleChanged: (e) ->
@playerIsIdle = e.idle
@ -44,7 +44,7 @@ module.exports = class LevelBus extends Bus
if @playerIsIdle then return
@changedSessionProperties.playtime = true
@session.set("playtime",@session.get("playtime") + 1)
onPoint: ->
return true unless @session?.get('multiplayer')
super()
@ -224,7 +224,7 @@ module.exports = class LevelBus extends Bus
saveSession: ->
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
Backbone.Mediator.publish 'level:session-will-save', session: @session
patch = {}

View file

@ -18,6 +18,7 @@ doQuerySelector = (value, operatorObj) ->
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 '$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
true
@ -34,11 +35,13 @@ matchesQuery = (target, queryObj) ->
pieces = prop.split('.')
obj = target
for piece in pieces
return false unless piece of obj
unless piece of obj
obj = null
break
obj = obj[piece]
if typeof query != 'object' or _.isArray query
return false unless obj == query or (query in obj if _.isArray obj)
else return false unless doQuerySelector obj, query
true
LocalMongo.matchesQuery = matchesQuery
LocalMongo.matchesQuery = matchesQuery

View file

@ -10,7 +10,7 @@ init = ->
if me and not me.get('testGroupNumber')?
# Assign testGroupNumber to returning visitors; new ones in server/routes/auth
me.set 'testGroupNumber', Math.floor(Math.random() * 256)
me.save()
me.patch()
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
obj = obj[piece]
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 =
"Soldier M": [
"Duke"
"William"
"Lucas"
"Marcus"
@ -66,6 +67,7 @@ module.exports.thangNames = thangNames =
"Coco"
"Buffy"
"Allankrita"
"Kay"
]
"Peasant M": [
"Yorik"
@ -355,6 +357,8 @@ module.exports.thangNames = thangNames =
"Hank"
"Jeph"
"Neville"
"Alphonse"
"Edward"
]
"Captain": [
"Anya"
@ -367,4 +371,5 @@ module.exports.thangNames = thangNames =
"Jane"
"Lia"
"Hardcastle"
"Leona"
]

View file

@ -198,8 +198,8 @@
done_editing: "Done Editing"
profile_for_prefix: "Profile for "
profile_for_suffix: ""
approved: "Approved"
not_approved: "Not Approved"
featured: "Featured"
not_featured: "Not Featured"
looking_for: "Looking for:"
last_updated: "Last updated:"
contact: "Contact"
@ -294,14 +294,25 @@
project_picture_help: "Upload a 230x115px or larger image showing off the project."
project_link: "Link"
project_link_help: "Link to the project."
player_code: "Player Code"
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"
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_location: "Location"
candidate_looking_for: "Looking For"
@ -309,8 +320,9 @@
candidate_top_skills: "Top Skills"
candidate_years_experience: "Yrs Exp"
candidate_last_updated: "Last Updated"
candidate_approved: "Us?"
candidate_active: "Them?"
featured_developers: "Featured Developers"
other_developers: "Other Developers"
inactive_developers: "Inactive Developers"
play_level:
done: "Done"
@ -436,6 +448,7 @@
av_entities_sub_title: "Entities"
av_entities_users_url: "Users"
av_entities_active_instances_url: "Active Instances"
av_entities_employer_list_url: "Employer List"
av_other_sub_title: "Other"
av_other_debug_base_url: "Base (for debugging base.jade)"
u_title: "User List"
@ -505,7 +518,7 @@
new_thang_title_login: "Log In to Create a New Thang Type"
new_level_title_login: "Log In to Create a New Level"
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"
thang_search_title: "Search Thang Types Here"
level_search_title: "Search Levels Here"
@ -784,9 +797,12 @@
watch_victory: "Watch your victory"
defeat_the: "Defeat the"
tournament_ends: "Tournament ends"
tournament_ended: "Tournament ended"
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_blog: "on our blog"
rules: "Rules"
winners: "Winners"
ladder_prizes:
title: "Tournament Prizes"
@ -808,7 +824,6 @@
license: "license"
oreilly: "ebook of your choice"
multiplayer_launch:
introducing_dungeon_arena: "Introducing Dungeon Arena"
new_way: "The new way to compete with code."
@ -866,6 +881,7 @@
source_document: "Source Document"
document: "Document" # note to diplomats: not a physical document, a document in MongoDB, ie a record in a database
sprite_sheet: "Sprite Sheet"
candidate_sessions: "Candidate Sessions"
delta:
added: "Added"

View file

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

View file

@ -1,4 +1,5 @@
CocoModel = require './CocoModel'
util = require '../lib/utils'
module.exports = class Achievement extends CocoModel
@className: 'Achievement'
@ -6,4 +7,10 @@ module.exports = class Achievement extends CocoModel
urlRoot: '/db/achievement'
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
@trigger "save", @
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: ->
@jqxhr = super(arguments...)
@ -104,7 +120,6 @@ class CocoModel extends Backbone.Model
@jqxhr
markToRevert: ->
console.debug "Saving _revertAttributes for #{@constructor.className}: '#{@get('name')}'"
if @type() is 'ThangType'
@_revertAttributes = _.clone @attributes # No deep clones for these!
else

View file

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

View file

@ -21,8 +21,8 @@ module.exports = class LevelSystem extends CocoModel
SystemNameLoader.setName @
compile: (code) ->
if @get('language') and @get('language') isnt 'coffeescript'
return console.error("Can't compile", @get('language'), "-- only CoffeeScript.", @)
if @get('codeLanguage') and @get('codeLanguage') isnt 'coffeescript'
return console.error("Can't compile", @get('codeLanguage'), "-- only CoffeeScript.", @)
try
js = CoffeeScript.compile(code, bare: true)
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'
function:
type: 'object'
oneOf: [
linear:
properties:
kind: {enum: ['linear', 'logarithmic'], default: 'linear'}
parameters:
type: 'object'
properties:
a: {type: 'number', default: 1},
required: ['a']
description: 'f(x) = a * x'
logarithmic:
type:'object'
properties:
a: {type: 'number', default: 1}
b: {type: 'number', default: 1}
required: ['a', 'b']
description: 'f(x) = a * ln(1/b * (x + b))'
]
default: linear: a: 1
c: {type: 'number', default: 1}
default: {kind: 'linear', parameters: a: 1}
required: ['kind', 'parameters']
additionalProperties: false
)
AchievementSchema.definitions = {}

View file

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

View file

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

View file

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

View file

@ -113,8 +113,10 @@ UserSchema = c.object {},
signedEmployerAgreement: c.object {},
linkedinID: c.shortString {title:"LinkedInID", description: "The user's LinkedIn ID when they signed the contract."}
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'}
activity: {type: 'object', description: 'Summary statistics about user activity', additionalProperties: c.activity}
c.extendBasicProperties UserSchema, 'user'

View file

@ -8,7 +8,7 @@ combine = (base, ext) ->
return base unless 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
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.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
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)
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) ->
schema.properties = {} unless schema.properties?
_.extend(schema.properties, basicProps(linkFragment))
# PATCHABLE
patchableProps = ->
@ -65,7 +65,7 @@ patchableProps = ->
allowPatches: { type: 'boolean' }
watchers: me.array({title:'Watchers'},
me.objectId(links: [{rel: 'extra', href: "/db/user/{($)}"}]))
me.extendPatchableProperties = (schema) ->
schema.properties = {} unless schema.properties?
_.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', 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.'}
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
color: #555
ul.links, ul.projects
ul.links, ul.projects, ul.sessions
margin: 0
padding: 0
@ -140,7 +140,7 @@
background-color: rgb(177, 55, 25)
padding: 15px
font-size: 20px
.middle-column
width: $middle-width - 2 * $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
#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
//img
// display: none
@ -36,3 +62,9 @@
min-width: 50px
td:nth-child(7) select
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-size: 16px
float: right
.progress-bar-white
background-color: white

View file

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

View file

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

View file

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

View file

@ -37,18 +37,13 @@ block content
if profileApproved
button.btn.btn-success#toggle-job-profile-approved(disabled=!me.isAdmin())
i.icon-eye-open
span(data-i18n='account_profile.approved') Approved
span(data-i18n='account_profile.featured') Featured
else if me.isAdmin()
button.btn#toggle-job-profile-approved
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
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
div(class="job-profile-container" + (editing ? " editable-profile" : ""))
@ -175,6 +170,19 @@ block content
span(data-i18n="account_profile.contact") Contact
| #{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
.sub-column
#name-container.editable-section
@ -222,11 +230,11 @@ block content
h3.edit-label Tag your programming skills
each skill in ["python", "coffeescript", "node", "ios", "objective-c", "javascript", "app-engine", "mongodb", "web dev", "django", "backbone"]
code.edit-example-tag= skill
span
span
else
each skill in profile.skills
code= skill
span
span
form.editable-form
.editable-icon.glyphicon.glyphicon-remove
@ -267,7 +275,7 @@ block content
img.header-icon(src="/images/pages/account/profile/work.png", alt="")
span(data-i18n="account_profile.work_experience") Work Experience
| - #{profile.experience}
|
|
span(data-i18n=profile.experience == 1 ? "units.year" : "units.years")
each job in profile.work
if job.role && job.employer
@ -454,9 +462,9 @@ block content
else if user
.public-profile-container
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(data-i18n="account_profile.profile_for_suffix")
span(data-i18n="account_profile.profile_for_suffix")
img.profile-photo(src=user.getPhotoURL(256))
@ -465,8 +473,8 @@ block content
else
.public-profile-container
h2
span(data-i18n="account_profile.profile_for_prefix") Profile for
span(data-i18n="account_profile.profile_for_prefix") Profile for
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")

View file

@ -23,6 +23,8 @@ block content
a(href="/admin/users", data-i18n="admin.av_entities_users_url") Users
li
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
@ -31,3 +33,11 @@ block content
a(href="/admin/base", data-i18n="admin.av_other_debug_base_url") Base (for debugging base.jade)
li
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
| #{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
span
|: "#{achievement.attributes.name}"
h3(data-i18n="achievement.edit_achievement_title") Edit Achievement
span
|: "#{achievement.attributes.name}"
#achievement-treema
#achievement-treema
#achievement-view
#achievement-view
hr
hr
div#error-view
div#error-view
else
.alert.alert-danger
span Admin only. Turn around.

View file

@ -2,66 +2,117 @@ extends /templates/base
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
table.table.table-condensed.table-hover.table-responsive.tablesorter
thead
tr
th(data-i18n="general.name") Name
th(data-i18n="employers.candidate_location") Location
th(data-i18n="employers.candidate_looking_for") Looking For
th(data-i18n="employers.candidate_role") Role
th(data-i18n="employers.candidate_top_skills") Top Skills
th(data-i18n="employers.candidate_years_experience") Yrs Exp
th(data-i18n="employers.candidate_last_updated") Last Updated
if me.isAdmin()
th(data-i18n="employers.candidate_approved") Us?
th(data-i18n="employers.candidate_active") Them?
tbody
for candidate, index in candidates
- var profile = candidate.get('jobProfile');
- var authorized = candidate.id; // If we have the id, then we are authorized.
tr(data-candidate-id=candidate.id, id=candidate.id)
td
if authorized
img(src=candidate.getPhotoURL(50), alt=profile.name, title=profile.name, height=50)
p= profile.name
else
img(src="/images/pages/contribute/archmage.png", alt="", title="Sign up as an employer to see our candidates", width=50)
p Developer ##{index + 1}
if profile.country == 'USA'
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()
if candidate.get('jobProfileApproved')
td ✓
else
td ✗
if profile.active
td ✓
else
td ✗
ul.nav.nav-pills
li.active
a(href="#featured-candidates", data-toggle="tab")
span(data-i18n="employers.featured_developers") Featured Developers
| (#{featuredCandidates.length})
if otherCandidates.length
li
a(href="#other-candidates", data-toggle="tab")
span(data-i18n="employers.other_developers") Other Developers
| (#{otherCandidates.length})
if me.isAdmin() && inactiveCandidates.length
li
a(href="#inactive-candidates", data-toggle="tab")
span(data-i18n="employers.inactive_developers") Inactive Developers
| (#{inactiveCandidates.length})
div.tab-content
for area, tabIndex in [{id: "featured-candidates", candidates: featuredCandidates}, {id: "other-candidates", candidates: otherCandidates}, {id: "inactive-candidates", candidates: inactiveCandidates}]
div(class="tab-pane well" + (tabIndex ? "" : " active"), id=area.id)
table.table.table-condensed.table-hover.table-responsive.tablesorter
thead
tr
th(data-i18n="general.name") Name
th(data-i18n="employers.candidate_location") Location
th(data-i18n="employers.candidate_looking_for") Looking For
th(data-i18n="employers.candidate_role") Role
th(data-i18n="employers.candidate_top_skills") Top Skills
th(data-i18n="employers.candidate_years_experience") Yrs Exp
th(data-i18n="employers.candidate_last_updated") Last Updated
if me.isAdmin() && area.id == 'inactive-candidates'
th ✓?
tbody
for candidate, index in area.candidates
- var profile = candidate.get('jobProfile');
- var authorized = candidate.id; // If we have the id, then we are authorized.
tr(data-candidate-id=candidate.id, id=candidate.id)
td
if authorized
img(src=candidate.getPhotoURL(50), alt=profile.name, title=profile.name, height=50)
p= profile.name
else
img(src="/images/pages/contribute/archmage.png", alt="", title="Sign up as an employer to see our candidates", width=50)
p Developer ##{index + 1 + (area.id == 'featured-candidates' ? 0 : featuredCandidates.length)}
if profile.country == 'USA'
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...
.progress.progress-striped.active
.progress-bar
else
.alert.alert-danger
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
.modal-footer
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
#control-bar-view
#canvas-wrapper
canvas(width=924, height=589)#surface
canvas(width=1848, height=1178)#surface
#canvas-left-gradient.gradient
#canvas-top-gradient.gradient
#gold-view.secret.expanded

View file

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

View file

@ -113,7 +113,7 @@ module.exports = class SettingsView extends View
return unless me.hasLocalChanges()
res = me.save()
res = me.patch()
return unless res
save = $('#save-button', @$el).text($.i18n.t('common.saving', defaultValue: 'Saving...'))
.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:
'click #enter-espionage-mode': 'enterEspionageMode'
'click #increment-button': 'incrementUserAttribute'
enterEspionageMode: ->
userEmail = $("#user-email").val().toLowerCase()
@ -29,3 +30,8 @@ module.exports = class AdminView extends View
espionageFailure: (jqxhr, status,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')
me.setEmailSubscription subscription+'News', checked
me.save()
me.patch()
@openModalView new SignupModalView() if me.get 'anonymous'
el.parent().find('.saved-notification').finish().show('fast').delay(3000).fadeOut(2000)

View file

@ -1,6 +1,7 @@
View = require 'views/kinds/RootView'
template = require 'templates/editor/achievement/edit'
Achievement = require 'models/Achievement'
ConfirmModal = require 'views/modal/confirm'
module.exports = class AchievementEditView extends View
id: "editor-achievement-edit-view"
@ -9,6 +10,7 @@ module.exports = class AchievementEditView extends View
events:
'click #save-button': 'saveAchievement'
'click #recalculate-button': 'confirmRecalculation'
subscriptions:
'save-new': 'saveAchievement'
@ -72,3 +74,34 @@ module.exports = class AchievementEditView extends View
res.success =>
url = "/editor/achievement/#{@achievement.get('slug') or @achievement.id}"
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'
class ThangTypeSearchCollection extends CocoCollection
url: '/db/thang.type/search?project=true'
url: '/db/thang.type?project=true'
model: ThangType
addTerm: (term) ->
@ -73,4 +73,4 @@ module.exports = class AddThangsView extends View
onEscapePressed: ->
@$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
id: "editor-level-component-edit-view"
template: template
editableSettings: ['name', 'description', 'system', 'language', 'dependencies', 'propertyDocumentation', 'i18n']
editableSettings: ['name', 'description', 'system', 'codeLanguage', 'dependencies', 'propertyDocumentation', 'i18n']
events:
'click #done-editing-component-button': 'endEditing'

View file

@ -5,7 +5,7 @@ LevelSystem = require 'models/LevelSystem'
CocoCollection = require 'collections/CocoCollection'
class LevelSystemSearchCollection extends CocoCollection
url: '/db/level_system/search'
url: '/db/level_system'
model: LevelSystem
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
id: "editor-level-system-edit-view"
template: template
editableSettings: ['name', 'description', 'language', 'dependencies', 'propertyDocumentation', 'i18n']
editableSettings: ['name', 'description', 'codeLanguage', 'dependencies', 'propertyDocumentation', 'i18n']
events:
'click #done-editing-system-button': 'endEditing'

View file

@ -21,7 +21,7 @@ componentOriginals =
"physics.Physical" : "524b75ad7fc0f6d519000001"
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
module.exports = class ThangsTabView extends View

View file

@ -20,10 +20,7 @@ module.exports = class EmployersView extends View
constructor: (options) ->
super options
@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: ->
super()
@sortTable() if @candidates.models.length
@ -33,13 +30,20 @@ module.exports = class EmployersView extends View
_.delay @checkForEmployerSignupHash, 500
getRenderData: ->
c = super()
c.candidates = @candidates.models
userPermissions = me.get('permissions') ? []
ctx = super()
ctx.isEmployer = @isEmployer()
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"
c.moment = moment
c
isEmployer: ->
userPermissions = me.get('permissions') ? []
_.contains userPermissions, "employer"
getCandidates: ->
@candidates = new CandidatesCollection()
@ -48,6 +52,7 @@ module.exports = class EmployersView extends View
@listenToOnce @candidates, 'all', @renderCandidatesAndSetupScrolling
renderCandidatesAndSetupScrolling: =>
@render()
$(".nano").nanoScroller()
if window.history?.state?.lastViewedCandidateID
@ -55,6 +60,11 @@ module.exports = class EmployersView extends View
else if window.location.hash.length is 25
$(".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: ->
# http://mottie.github.io/tablesorter/docs/example-widget-bootstrap-theme.html
$.extend $.tablesorter.themes.bootstrap,
@ -110,7 +120,7 @@ module.exports = class EmployersView extends View
n *= -1
days.push n
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
# use the zebra stripe widget if you plan on hiding any rows (filter widget)
widgets: ["uitheme", "zebra", "filter"]
@ -172,9 +182,6 @@ module.exports = class EmployersView extends View
7:
"": filterSelectExactMatch
"": filterSelectExactMatch
8:
"": filterSelectExactMatch
"": filterSelectExactMatch
onCandidateClicked: (e) ->
id = $(e.target).closest('tr').data('candidate-id')

View file

@ -8,6 +8,7 @@ locale = require 'locale/locale'
Achievement = require '../../models/Achievement'
User = require '../../models/User'
# TODO remove
filterKeyboardEvents = (allowedEvents, func) ->
return (splat...) ->
@ -25,26 +26,24 @@ module.exports = class RootView extends CocoView
subscriptions:
'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()
nextLevel = currentLevel + 1
currentLevelExp = User.expForLevel(currentLevel)
nextLevelExp = User.expForLevel(nextLevel)
totalExpNeeded = nextLevelExp - currentLevelExp
expFunction = achievement.getExpFunction()
currentExp = me.get('points')
worth = achievement.get('worth')
leveledUp = currentExp - worth < currentLevelExp
alreadyAchievedPercentage = 100 * (currentExp - currentLevelExp - worth) / totalExpNeeded
newlyAchievedPercentage = if currentLevelExp is currentExp then 0 else 100 * worth / totalExpNeeded
previousExp = currentExp - achievement.get('worth')
previousExp = expFunction(earnedAchievement.get('previouslyAchievedAmount')) * achievement.get('worth') if achievement.isRepeatable()
achievedExp = currentExp - previousExp
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 "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>")
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
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}")
# TODO a default should be linked here
@ -63,7 +62,7 @@ module.exports = class RootView extends CocoView
image: $("<img src='#{imageURL}' />")
description: achievement.get('description')
progressBar: progressBar
earnedExp: "+ #{worth} XP"
earnedExp: "+ #{achievedExp} XP"
message: message
options =
@ -77,13 +76,11 @@ module.exports = class RootView extends CocoView
$.notify( data, options )
handleNewAchievements: (earnedAchievements) ->
console.debug 'Got new earned achievements'
# TODO performance?
_.each(earnedAchievements.models, (earnedAchievement) =>
achievement = new Achievement(_id: earnedAchievement.get('achievement'))
console.log achievement
achievement.fetch(
success: @showNewAchievement
success: (achievement) => @showNewAchievement(achievement, earnedAchievement)
)
)
@ -150,7 +147,7 @@ module.exports = class RootView extends CocoView
saveLanguage: (newLang) ->
me.set('preferredLanguage', newLang)
res = me.save()
res = me.patch()
return unless res
res.error ->
errors = JSON.parse(res.responseText)

View file

@ -5,7 +5,7 @@ app = require('application')
class SearchCollection extends Backbone.Collection
initialize: (modelURL, @model, @term, @projection) ->
@url = "#{modelURL}/search?project="
@url = "#{modelURL}?project="
if @projection? and not (@projection == [])
@url += projection[0]
@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
window.tracker?.trackEvent 'Sent Feedback', message: contactMessage
sendContactMessage contactMessage, @$el
$.post "/db/user/#{me.id}/track/contact_codecombat"

View file

@ -12,7 +12,7 @@ module.exports = class DiplomatSuggestionView extends View
subscribeAsDiplomat: ->
me.setEmailSubscription 'diplomatNews', true
me.save()
me.patch()
$("#email_translator").prop("checked", 1)
@hide()
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!]'
window.tracker?.trackEvent 'Sent Job Profile Message', message: contactMessage
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)
return
res = me.save()
res = me.patch()
return unless res
save = $('#save-button', @$el).text($.i18n.t('common.saving', defaultValue: 'Saving...'))
.addClass('btn-info').show().removeClass('btn-danger')

View file

@ -10,14 +10,6 @@ ModelModal = require 'views/modal/model_modal'
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
id: 'ladder-tab-view'
template: require 'templates/play/ladder/ladder_tab'

View file

@ -43,7 +43,7 @@ module.exports = class LadderView extends RootView
onLoaded: ->
@teams = teamDataFromLevel @level
@render()
super()
getRenderData: ->
ctx = super()
@ -54,6 +54,7 @@ module.exports = class LadderView extends RootView
ctx.levelDescription = marked(@level.get('description')) if @level.get('description')
ctx._ = _
ctx.tournamentTimeLeft = moment(new Date(1402444800000)).fromNow()
ctx.winners = require('views/play/ladder/tournament_results')[@levelID]
ctx
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-language', language: newLanguage unless newLanguage is oldLanguage
@session.save() unless newLanguage is oldLanguage
me.save()
me.patch()
destroy: ->
super()

View file

@ -81,7 +81,7 @@ module.exports = class VictoryModal extends View
if enough and not me.get('hourOfCodeComplete')
$('body').append($("<img src='http://code.org/api/hour/finish_codecombat.png' style='visibility: hidden;'>"))
me.set 'hourOfCodeComplete', true
me.save()
me.patch()
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
tooMuch = elapsed >= 120 * 60 * 1000

View file

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

View file

@ -97,7 +97,7 @@ module.exports = class CastButtonView extends View
return unless delay
@autocastDelay = delay = parseInt delay
me.set('autocastDelay', delay)
me.save()
me.patch()
spell.view.setAutocastDelay delay for spellKey, spell of @spells
@castOptions.find('a').each ->
$(@).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
teamSpells = @session.get('teamSpells')
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
@source = @originalSource = p.source
@parameters = p.parameters

View file

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

View file

@ -211,14 +211,21 @@ module.exports = class PlayView extends View
difficulty: 3
id: 'bubble-sort-bootcamp-battle'
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'
difficulty: 3
id: 'ogres-of-hanoi'
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'
@ -234,15 +241,6 @@ module.exports = class PlayView extends View
image: '/file/db/level/529662dfe0df8f0000000007/grab_the_mushroom_icon.png'
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 = [

View file

@ -107,6 +107,7 @@ module.exports = TestView = class TestView extends CocoView
# TODO Stubbify more things
# * document.location
# * firebase
# * all the services that load in main.html
afterEach ->
# 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):
os.mkdir(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)

View file

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

View file

@ -7,9 +7,10 @@ async = require 'async'
serverSetup = require '../server_setup'
sendwithus = require '../server/sendwithus'
User = require '../server/users/User.coffee'
Level = require '../server/levels/Level.coffee'
LevelSession = require '../server/levels/sessions/LevelSession.coffee'
User = require '../server/users/User'
Level = require '../server/levels/Level'
LevelSession = require '../server/levels/sessions/LevelSession'
tournamentResults = require '../app/views/play/ladder/tournament_results'
alreadyEmailed = []
@ -27,7 +28,7 @@ sendInitialRecruitingEmail = ->
async.waterfall [
(callback) -> async.map leaderboards, grabSessions, 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) ->
return console.log "Error:", err if err
console.log "Looked at sending to #{results.length} users; sent to #{_.filter(results).length}."
@ -77,7 +78,7 @@ grabUser = (session, callback) ->
callback null, user
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?.recruitNotes?.enabled is false
return callback null, false if user.email in alreadyEmailed
@ -102,5 +103,64 @@ emailUser = (user, callback) ->
return callback err if err
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()
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')
plugins = require('../plugins/plugins')
jsonschema = require('../../app/schemas/models/achievement')
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,
# including `Model.update`,`.findByIdAndUpdate`,`.findOneAndUpdate`, `.findOneAndRemove`,and `.findByIdAndRemove`.order
@ -12,16 +13,21 @@ AchievementSchema = new mongoose.Schema({
userField: String
}, {strict: false})
AchievementSchema.methods.objectifyQuery = () ->
AchievementSchema.methods.objectifyQuery = ->
try
@set('query', JSON.parse(@get('query'))) if typeof @get('query') == "string"
catch error
log.error "Couldn't convert query string to object because of #{error}"
@set('query', {})
AchievementSchema.methods.stringifyQuery = () ->
AchievementSchema.methods.stringifyQuery = ->
@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.pre('save', (next) ->
@ -33,3 +39,7 @@ AchievementSchema.plugin(plugins.NamedPlugin)
AchievementSchema.plugin(plugins.SearchablePlugin, {searchable: ['name']})
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
}, {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, 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
# 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'
hasAccess: (req) ->
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()

View file

@ -1,12 +1,115 @@
mongoose = require('mongoose')
log = require 'winston'
mongoose = require 'mongoose'
async = require 'async'
Achievement = require './Achievement'
EarnedAchievement = require './EarnedAchievement'
User = require '../users/User'
Handler = require '../commons/Handler'
LocalMongo = require '../../app/lib/LocalMongo'
class EarnedAchievementHandler extends Handler
modelClass: EarnedAchievement
# Don't allow POSTs or anything yet
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()

View file

@ -17,6 +17,7 @@ module.exports = class Handler
postEditableProperties: []
jsonSchema: {}
waterfallFunctions: []
allowedMethods: ['GET', 'POST', 'PUT', 'PATCH']
# subclasses should override these methods
hasAccess: (req) -> true
@ -63,26 +64,72 @@ module.exports = class Handler
# generic handlers
get: (req, res) ->
# by default, ordinary users never get unfettered access to the database
return @sendUnauthorizedError(res) unless req.user?.isAdmin()
@sendUnauthorizedError(res) if not @hasAccess(req)
# admins can send any sort of query down the wire, though
conditions = JSON.parse(req.query.conditions || '[]')
query = @modelClass.find()
specialParameters = ['term', 'project', '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.')
# If the model uses coco search it's probably a text search
if @modelClass.schema.uses_coco_search
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
# 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) ->
# return @sendNotFoundError(res) # for testing
@ -153,44 +200,6 @@ module.exports = class Handler
return @sendDatabaseError(res, err) if err
@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) ->
# 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.
@ -420,3 +429,7 @@ module.exports = class Handler
dict[document.id] = document
res.send dict
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.end()
module.exports.badMethod = (res, message='Method Not Allowed') ->
# TODO: The response MUST include an Allow header containing a list of valid methods for the requested resource
module.exports.badMethod = (res, allowed=['GET', 'POST', 'PUT', 'PATCH'], message='Method Not Allowed') ->
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.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") ->
res.send 408, message
res.end()
res.end()

View file

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

View file

@ -9,7 +9,7 @@ LevelComponentHandler = class LevelComponentHandler extends Handler
'description'
'code'
'js'
'language'
'codeLanguage'
'dependencies'
'propertyDocumentation'
'configSchema'
@ -25,4 +25,4 @@ LevelComponentHandler = class LevelComponentHandler extends Handler
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...) ->
return @getActiveSessions req, res if args.length is 2 and args[1] is 'active'
super(arguments...)
formatEntity: (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
else
return _.omit documentObject, ['submittedCode','code']
getActiveSessions: (req, res) ->
return @sendUnauthorizedError(res) unless req.user.isAdmin()
start = new Date()
@ -34,6 +34,7 @@ class LevelSessionHandler extends Handler
hasAccessToDocument: (req, document, method=null) ->
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...)
module.exports = new LevelSessionHandler()

View file

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

View file

@ -1,24 +1,15 @@
mongoose = require('mongoose')
Achievement = require('../achievements/Achievement')
EarnedAchievement = require '../achievements/EarnedAchievement'
User = require '../users/User'
LocalMongo = require '../../app/lib/LocalMongo'
util = require '../../app/lib/utils'
log = require 'winston'
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) ->
User = require '../users/User' # Avoid mutual inclusion cycles
Achievement = require('../achievements/Achievement')
checkForAchievement = (doc) ->
collectionName = doc.constructor.modelName
@ -53,6 +44,8 @@ module.exports = AchievablePlugin = (schema, options) ->
achievement: achievement._id.toHexString()
achievementName: achievement.get 'name'
}
worth = achievement.get('worth')
earnedPoints = 0
wrapUp = ->
# Update user's experience points
@ -67,23 +60,37 @@ module.exports = AchievablePlugin = (schema, options) ->
newAmount = docObj[proportionalTo]
if originalAmount isnt newAmount
expFunction = achievement.getExpFunction()
earned.notified = false
earned.achievedAmount = newAmount
earned.changed = Date.now()
EarnedAchievement.findOneAndUpdate({achievement:earned.achievement, user:earned.user}, earned, upsert:true, (err, docs) ->
return log.debug err if err?
)
earned.earnedPoints = (expFunction(newAmount) - expFunction(originalAmount)) * worth
earned.previouslyAchievedAmount = originalAmount
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()
else # not alreadyAchieved
log.debug 'Creating a new earned achievement called \'' + (achievement.get 'name') + '\' for ' + userID
earned.earnedPoints = worth
(new EarnedAchievement(earned)).save (err, doc) ->
return log.debug err if err?
earnedPoints = achievement.get('worth')
earnedPoints = worth
wrapUp()
delete before[doc.id] unless isNew # This assumes everything we patch has a _id
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')
User = require('../users/User')
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) ->
schema.is_patchable = true
schema.index({'target.original':1, 'status':'1', 'created':-1})
RESERVED_NAMES = ['search', 'names']
RESERVED_NAMES = ['names']
module.exports.NamedPlugin = (schema) ->
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) ->
return next(err) if (err)
res.send(UserHandler.formatEntity(req, req.user))
return res.end()
activity = req.user.trackActivity 'login', 1
user.update {activity: activity}, (err) ->
return next(err) if (err)
res.send(UserHandler.formatEntity(req, req.user))
return res.end()
)
)(req, res, next)
)
@ -134,12 +137,12 @@ module.exports.setup = (app) ->
emails = _.clone(user.get('emails')) or {}
msg = ''
if req.query.recruitNotes
emails.recruitNotes ?= {}
emails.recruitNotes.enabled = false
msg = "Unsubscribed #{req.query.email} from recruiting emails."
else
msg = "Unsubscribed #{req.query.email} from all CodeCombat emails. Sorry to see you go!"
emailSettings.enabled = false for emailSettings in _.values(emails)
@ -147,7 +150,7 @@ module.exports.setup = (app) ->
emails.generalNews.enabled = false
emails.anyNotes ?= {}
emails.anyNotes.enabled = false
user.update {$set: {emails: emails}}, {}, =>
return errors.serverError res, 'Database failure.' if err
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.set 'testGroupNumber', Math.floor(Math.random() * 256) # also in app/lib/auth
user.set 'preferredLanguage', languages.languageCodeFromAcceptedLanguages req.acceptedLanguages
createMailOptions = (receiver, password) ->
# TODO: use email templates here
options =
@ -181,4 +184,3 @@ createMailOptions = (receiver, password) ->
replyTo: config.mail.username
subject: "[CodeCombat] Password Reset"
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.versions(req, res, parts[1]) if parts[2] is 'versions'
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.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]?

View file

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

View file

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

View file

@ -11,7 +11,7 @@ module.exports.setup = (app) ->
app.all '/languages', (req, res) ->
# 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)
return res.end()

View file

@ -28,7 +28,7 @@ module.exports.setup = (app) ->
app.all '/queue/*', (req, res) ->
setResponseHeaderToJSONContentType res
queueName = getQueueNameFromPath req.path
try
handler = loadQueueHandler queueName
@ -64,7 +64,7 @@ isHTTPMethodPost = (req) -> return req.route.method is 'post'
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}")

View file

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

View file

@ -29,6 +29,17 @@ UserSchema.methods.isAdmin = ->
p = @get('permissions')
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 =
generalNews: 'announcement'
adventurerNews: 'tester'

View file

@ -48,7 +48,7 @@ UserHandler = class UserHandler extends Handler
delete obj[prop] for prop in serverProperties
includePrivates = req.user and (req.user.isAdmin() or req.user._id.equals(document._id))
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
return obj
@ -189,11 +189,14 @@ UserHandler = class UserHandler extends Handler
return @avatar(req, res, args[0]) if args[1] is 'avatar'
return @getNamesByIDs(req, res) if args[1] is 'names'
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 @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 @getMySimulatorLeaderboardRank(req, res, args[0]) if args[1] is 'simulator_leaderboard_rank'
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)
super(arguments...)
@ -225,9 +228,18 @@ UserHandler = class UserHandler extends Handler
res.redirect photoURL
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) ->
return @sendUnauthorizedError(res) unless req.user._id+'' is userID or req.user.isAdmin()
query = {'creator': userID}
query = creator: userID
projection = null
if req.query.project
projection = {}
@ -249,6 +261,25 @@ UserHandler = class UserHandler extends Handler
doc.save()
@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) ->
userIsAnonymous = req.user?.get('anonymous')
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) ->
authorized = req.user.isAdmin() or ('employer' in req.user.get('permissions'))
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.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()
selection = 'jobProfile'
selection = 'jobProfile jobProfileApproved photoURL'
selection += ' email' if authorized
selection += ' jobProfileApproved' if req.user.isAdmin()
User.find(query).select(selection).exec (err, documents) =>
return @sendDatabaseError(res, err) if err
candidates = (candidate for candidate in documents when @employerCanViewCandidate req.user, candidate.toObject())
@ -292,7 +321,7 @@ UserHandler = class UserHandler extends Handler
@sendSuccess(res, candidates)
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.photoURL ||= obj.jobProfile.photoURL if authorized
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()
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) ->
emailHash = @buildEmailHash user
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
# 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, '\\/'))
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) ->
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'
beforeEach ->
this.fixture1 =
@fixture1 =
'id': 'somestring'
'value': 9000
'levels': [3, 8, 21]
@ -10,68 +10,72 @@ describe 'Local Mongo queries', ->
'type': 'unicorn'
'likes': ['poptarts', 'popsicles', 'popcorn']
this.fixture2 = this: is: so: 'deep'
@fixture2 = this: is: so: 'deep'
it 'regular match of a property', ->
expect(LocalMongo.matchesQuery(this.fixture1, 'gender': 'unicorn')).toBeFalsy()
expect(LocalMongo.matchesQuery(this.fixture1, 'type':'unicorn')).toBeTruthy()
expect(LocalMongo.matchesQuery(this.fixture1, 'type':'zebra')).toBeFalsy()
expect(LocalMongo.matchesQuery(this.fixture1, 'type':'unicorn', 'id':'somestring')).toBeTruthy()
expect(LocalMongo.matchesQuery(@fixture1, 'gender': 'unicorn')).toBeFalsy()
expect(LocalMongo.matchesQuery(@fixture1, 'type':'unicorn')).toBeTruthy()
expect(LocalMongo.matchesQuery(@fixture1, 'type':'zebra')).toBeFalsy()
expect(LocalMongo.matchesQuery(@fixture1, 'type':'unicorn', 'id':'somestring')).toBeTruthy()
it 'array match of a property', ->
expect(LocalMongo.matchesQuery(this.fixture1, 'likes':'poptarts')).toBeTruthy()
expect(LocalMongo.matchesQuery(this.fixture1, 'likes':'walks on the beach')).toBeFalsy()
expect(LocalMongo.matchesQuery(@fixture1, 'likes':'poptarts')).toBeTruthy()
expect(LocalMongo.matchesQuery(@fixture1, 'likes':'walks on the beach')).toBeFalsy()
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', ->
expect(LocalMongo.matchesQuery(this.fixture1, 'value': '$gt': 8000)).toBeTruthy()
expect(LocalMongo.matchesQuery(this.fixture1, 'value': '$gt': [8000, 10000])).toBeTruthy()
expect(LocalMongo.matchesQuery(this.fixture1, 'levels': '$gt': [10, 20, 30])).toBeTruthy()
expect(LocalMongo.matchesQuery(this.fixture1, 'value': '$gt': 9000)).toBeFalsy()
expect(LocalMongo.matchesQuery(this.fixture1, 'value': {'$gt': 8000}, 'worth': {'$gt': 5})).toBeTruthy()
expect(LocalMongo.matchesQuery(@fixture1, 'value': '$gt': 8000)).toBeTruthy()
expect(LocalMongo.matchesQuery(@fixture1, 'value': '$gt': [8000, 10000])).toBeTruthy()
expect(LocalMongo.matchesQuery(@fixture1, 'levels': '$gt': [10, 20, 30])).toBeTruthy()
expect(LocalMongo.matchesQuery(@fixture1, 'value': '$gt': 9000)).toBeFalsy()
expect(LocalMongo.matchesQuery(@fixture1, 'value': {'$gt': 8000}, 'worth': {'$gt': 5})).toBeTruthy()
it '$gte selector', ->
expect(LocalMongo.matchesQuery(this.fixture1, 'value': '$gte': 9001)).toBeFalsy()
expect(LocalMongo.matchesQuery(this.fixture1, 'value': '$gte': 9000)).toBeTruthy()
expect(LocalMongo.matchesQuery(this.fixture1, 'value': '$gte': [9000, 10000])).toBeTruthy()
expect(LocalMongo.matchesQuery(this.fixture1, 'levels': '$gte': [21, 30])).toBeTruthy()
expect(LocalMongo.matchesQuery(@fixture1, 'value': '$gte': 9001)).toBeFalsy()
expect(LocalMongo.matchesQuery(@fixture1, 'value': '$gte': 9000)).toBeTruthy()
expect(LocalMongo.matchesQuery(@fixture1, 'value': '$gte': [9000, 10000])).toBeTruthy()
expect(LocalMongo.matchesQuery(@fixture1, 'levels': '$gte': [21, 30])).toBeTruthy()
it '$lt selector', ->
expect(LocalMongo.matchesQuery(this.fixture1, 'value': '$lt': 9001)).toBeTruthy()
expect(LocalMongo.matchesQuery(this.fixture1, 'value': '$lt': 9000)).toBeFalsy()
expect(LocalMongo.matchesQuery(this.fixture1, 'value': '$lt': [9001, 9000])).toBeTruthy()
expect(LocalMongo.matchesQuery(this.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)).toBeTruthy()
expect(LocalMongo.matchesQuery(@fixture1, 'value': '$lt': 9000)).toBeFalsy()
expect(LocalMongo.matchesQuery(@fixture1, 'value': '$lt': [9001, 9000])).toBeTruthy()
expect(LocalMongo.matchesQuery(@fixture1, 'levels': '$lt': [10, 20, 30])).toBeTruthy()
expect(LocalMongo.matchesQuery(@fixture1, 'value': {'$lt': 9001}, 'worth': {'$lt': 7})).toBeTruthy()
it '$lte selector', ->
expect(LocalMongo.matchesQuery(this.fixture1, 'value': '$lte': 9000)).toBeTruthy()
expect(LocalMongo.matchesQuery(this.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)).toBeTruthy()
expect(LocalMongo.matchesQuery(@fixture1, 'value': '$lte': 8000)).toBeFalsy()
expect(LocalMongo.matchesQuery(@fixture1, 'value': {'$lte': 9000}, 'worth': {'$lte': [6, 5]})).toBeTruthy()
it '$ne selector', ->
expect(LocalMongo.matchesQuery(this.fixture1, 'value': '$ne': 9000)).toBeFalsy()
expect(LocalMongo.matchesQuery(this.fixture1, 'id': '$ne': 'otherstring')).toBeTruthy()
expect(LocalMongo.matchesQuery(this.fixture1, 'id': '$ne': ['otherstring', 'somestring'])).toBeFalsy()
expect(LocalMongo.matchesQuery(this.fixture1, 'likes': '$ne': ['popcorn', 'chicken'])).toBeFalsy()
expect(LocalMongo.matchesQuery(@fixture1, 'value': '$ne': 9000)).toBeFalsy()
expect(LocalMongo.matchesQuery(@fixture1, 'id': '$ne': 'otherstring')).toBeTruthy()
expect(LocalMongo.matchesQuery(@fixture1, 'id': '$ne': ['otherstring', 'somestring'])).toBeFalsy()
expect(LocalMongo.matchesQuery(@fixture1, 'likes': '$ne': ['popcorn', 'chicken'])).toBeFalsy()
it '$in selector', ->
expect(LocalMongo.matchesQuery(this.fixture1, 'type': '$in': ['unicorn', 'zebra'])).toBeTruthy()
expect(LocalMongo.matchesQuery(this.fixture1, 'type': '$in': ['cats', 'dogs'])).toBeFalsy()
expect(LocalMongo.matchesQuery(this.fixture1, 'likes': '$in': ['popcorn', 'chicken'])).toBeTruthy()
expect(LocalMongo.matchesQuery(@fixture1, 'type': '$in': ['unicorn', 'zebra'])).toBeTruthy()
expect(LocalMongo.matchesQuery(@fixture1, 'type': '$in': ['cats', 'dogs'])).toBeFalsy()
expect(LocalMongo.matchesQuery(@fixture1, 'likes': '$in': ['popcorn', 'chicken'])).toBeTruthy()
it '$nin selector', ->
expect(LocalMongo.matchesQuery(this.fixture1, 'type': '$nin': ['unicorn', 'zebra'])).toBeFalsy()
expect(LocalMongo.matchesQuery(this.fixture1, 'type': '$nin': ['cats', 'dogs'])).toBeTruthy()
expect(LocalMongo.matchesQuery(this.fixture1, 'likes': '$nin': ['popcorn', 'chicken'])).toBeFalsy()
expect(LocalMongo.matchesQuery(@fixture1, 'type': '$nin': ['unicorn', 'zebra'])).toBeFalsy()
expect(LocalMongo.matchesQuery(@fixture1, 'type': '$nin': ['cats', 'dogs'])).toBeTruthy()
expect(LocalMongo.matchesQuery(@fixture1, 'likes': '$nin': ['popcorn', 'chicken'])).toBeFalsy()
it '$or operator', ->
expect(LocalMongo.matchesQuery(this.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:9000}, {type:'zebra'}])).toBeTruthy()
expect(LocalMongo.matchesQuery(@fixture1, $or: [{value:9001}, {worth:$lt:10}])).toBeTruthy()
it '$and operator', ->
expect(LocalMongo.matchesQuery(this.fixture1, $and: [{value:9000}, {type:'zebra'}])).toBeFalsy()
expect(LocalMongo.matchesQuery(this.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:9000}, {type:'zebra'}])).toBeFalsy()
expect(LocalMongo.matchesQuery(@fixture1, $and: [{value:9000}, {type:'unicorn'}])).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,
displayFailedSpec: true
}))
rep = new jasmine.JsApiReporter()
jasmine.getEnv().addReporter(rep)
GLOBAL._ = require('lodash')
_.str = require('underscore.string')
_.mixin(_.str.exports())
@ -26,6 +30,8 @@ models_path = [
'../../server/levels/thangs/LevelThangType'
'../../server/users/User'
'../../server/patches/Patch'
'../../server/achievements/Achievement'
'../../server/achievements/EarnedAchievement'
]
for m in models_path
@ -149,3 +155,13 @@ _drop = (done) ->
chunks = mongoose.connection.db.collection('media.chunks')
chunks.remove {}, ->
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