Merge branch 'master' into courses-vhoc

This commit is contained in:
Scott Erickson 2015-11-20 13:28:43 -08:00
commit 9dbd4d296a
23 changed files with 239 additions and 80 deletions

View file

@ -10,6 +10,7 @@ CocoClass = require 'core/CocoClass'
AudioPlayer = require 'lib/AudioPlayer'
app = require 'core/application'
World = require 'lib/world/world'
utils = require 'core/utils'
# This is an initial stab at unifying loading and setup into a single place which can
# monitor everything and keep a LoadingScreen visible overall progress.
@ -105,6 +106,8 @@ module.exports = class LevelLoader extends CocoClass
loadDependenciesForSession: (session) ->
if me.id isnt session.get 'creator'
session.patch = session.save = -> console.error "Not saving session, since we didn't create it."
else if codeLanguage = utils.getQueryVariable 'codeLanguage'
session.set 'codeLanguage', codeLanguage
@loadCodeLanguagesForSession session
if session is @session
@addSessionBrowserInfo session

View file

@ -247,7 +247,7 @@
victory_title_suffix: " Complete"
victory_sign_up: "Sign Up to Save Progress"
victory_sign_up_poke: "Want to save your code? Create a free account!"
victory_rate_the_level: "Rate the level: " # Only in old-style levels.
victory_rate_the_level: "How fun was this level?" # {change}
victory_return_to_ladder: "Return to Ladder"
victory_play_continue: "Continue"
victory_saving_progress: "Saving Progress"

View file

@ -80,7 +80,7 @@ module.exports = nativeDescription: "Italiano", englishDescription: "Italian", t
adjust_volume: "Regola il volume"
campaign_multiplayer: "Arene multigiocatore"
campaign_multiplayer_description: "... nelle quali programmi faccia a faccia contro altri giocatori."
# campaign_old_multiplayer: "(Deprecated) Old Multiplayer Arenas"
campaign_old_multiplayer: "(Deprecato) Vecchia Arena multiplayer"
campaign_old_multiplayer_description: "Reliquie di un'epoca più civilizzata. Nessuna simulazione viene eseguita per queste arene multi-giocatore più vecchie e senza eroi" #"Relics of a more civilized age. No simulations are run for these older, hero-less multiplayer arenas."
share_progress_modal:
@ -218,7 +218,7 @@ module.exports = nativeDescription: "Italiano", englishDescription: "Italian", t
play_level:
done: "Fatto"
next_game: "Prossimo gioco" #"Next game"
# show_menu: "Show game menu"
show_menu: "Visualizza menu gioco"
home: "Pagina iniziale" # Not used any more, will be removed soon.
level: "Livello" # Like "Level: Dungeons of Kithgard"
skip: "Salta"
@ -306,8 +306,8 @@ module.exports = nativeDescription: "Italiano", englishDescription: "Italian", t
tip_baby_coders: "Nel futuro, persino i neonati saranno Arcimaghi."
tip_morale_improves: "Il caricamento continuerà fino a che il morale migliora." #"Loading will continue until morale improves."
tip_all_species: "Crediamo che chiunque debba avere le stesse opportunità di imparare a programmare."
# tip_reticulating: "Reticulating spines."
# tip_harry: "Yer a Wizard, "
tip_reticulating: "Reticolazione spine"
tip_harry: "Yer il mago, "
tip_great_responsibility: "Da grandi abilità di programmazione derivano grandi responsabilità."
tip_munchkin: "Se non mangi la tua verdura, un munchkin verrà a cercarti mentre dormi."
tip_binary: "Ci sono solo 10 tipi di persone al mondo: quelli che capiscono il binario, e quelli che non lo capiscono." #"There are only 10 types of people in the world: those who understand binary, and those who don't."
@ -335,7 +335,7 @@ module.exports = nativeDescription: "Italiano", englishDescription: "Italian", t
tip_move_forward: "Qualsiasi cosa tu faccia, vai sempre avanti. - Martin Luther King Jr."
tip_google: "Hai un problema che non riesci a risolvere? Cerca su Google!"
tip_adding_evil: "Aggiungendo un pizzico di malvagità."
# tip_hate_computers: "That's the thing about people who think they hate computers. What they really hate is lousy programmers. - Larry Niven"
tip_hate_computers: "A proposito delle persone che pensano di odiare i computer. Cio' che odiano realmente sono i programmatori scarsi - Larry Niven"
tip_open_source_contribute: "Puoi aiutare CodeCombat a migliorare!"
tip_recurse: "Iterare e umano, usare la ricorsione è divino. - L. Peter Deutsch"
tip_free_your_mind: "Devi liberarti di tutto questo, Neo. Paura, dubbio, sfiducia. Libera la tua mente. - Morpheus"
@ -431,60 +431,60 @@ module.exports = nativeDescription: "Italiano", englishDescription: "Italian", t
parents: "Per i genitori"
parents_title: "Caro Genitore: Tuo figlio/a sta imparando a programmare. Vuoi continuare ad aiutarlo/a ? "
parents_blurb1: "Tuo figlio/a ha giocato a _nLevels__ livelli ed ha imparato le basi della programmazione. Aiutalo/a a coltivare i suoi interessi ed acquistagli unabbonamento così potrà continuare a giocare."
# parents_blurb1a: "Computer programming is an essential skill that your child will undoubtedly use as an adult. By 2020, basic software skills will be needed by 77% of jobs, and software engineers are in high demand across the world. Did you know that Computer Science is the highest-paid university degree?"
# parents_blurb2: "For $9.99 USD/mo, your child will get new challenges every week and personal email support from professional programmers."
# parents_blurb3: "No Risk: 100% money back guarantee, easy 1-click unsubscribe."
parents_blurb1a: "La programmazione e' una capacita' che indubbiamente sara' utile a tuo figlio da adulto. Nel 2020 una capacita' basilare di programmazione sara' richiesta dal 77% dei lavori e la richiesta a livello mondiale di ingegneri del software e' in continua crescita. Lo sai che la laurea in Informatica e' la piu' pagata sul mercato?"
parents_blurb2: "Per $9.99 dollari/mese, tuo figlio ricevera' settimanalmente nuove avventure e un supporto personale via email fornito d aprogrammatori professionisti."
parents_blurb3: "Nessun rischio: 100% restituzione dei soldi con un semplice processo di deregistrazione"
payment_methods: "Metodi di Pagamento"
payment_methods_title: "Metodi di Pagamento Accetati"
payment_methods_blurb1: "Attualmente accettiamo come metodi di pagamento la carta di credito e Alipay."
payment_methods_blurb2: "Se necessiti di un forma di pagamento diverso.Per favore contattaci"
sale_already_subscribed: "Sei già abbonato!" #"You're already subscribed!"
sale_blurb1: "Risparmia il 35%"
# sale_blurb2: "off regular subscription price of $120 for a whole year!"
sale_blurb2: "rispetto all'abbinamento ordinario di 120$ per l'intero anno!"
sale_button: "Saldi!" #"Sale!"
sale_button_title: "Risparmi il 35% quando compri l'abbonamento per 1 anno"
sale_click_here: "Clicca qui"
# sale_ends: "Ends"
# sale_extended: "*Existing subscriptions will be extended by 1 year."
# sale_feature_here: "Here's what you'll get:"
# sale_feature2: "Access to 9 powerful <strong>new heroes</strong> with unique skills!"
# sale_feature4: "<strong>42,000 bonus gems</strong> awarded immediately!"
# sale_continue: "Ready to continue adventuring?"
# sale_limited_time: "Limited time offer!"
# sale_new_heroes: "New heroes!"
# sale_title: "Back to School Sale"
# sale_view_button: "Buy 1 year subscription for"
sale_ends: "Fine"
sale_extended: "*Gli abbonamenti in corso verranno estesi per 1 anno."
sale_feature_here: "Qui cio' che otterrai:"
sale_feature2: "Accesso a 9 nuovi potenti <strong>eroi</strong> con abilita' uniche!"
sale_feature4: "<strong>42,000 gemme premio</strong> riconosciute immediatamente!"
sale_continue: "Pronto per continuare l'avventura?"
sale_limited_time: "Offerta limitata!"
sale_new_heroes: "Nuovi eroi!"
sale_title: "Saldi di inizio scuola"
sale_view_button: "Compra un abbonamento annuale per "
stripe_description: "Sottoscrizione mensile"
# stripe_description_year_sale: "1 Year Subscription (35% discount)"
# subscription_required_to_play: "You'll need a subscription to play this level."
# unlock_help_videos: "Subscribe to unlock all video tutorials."
stripe_description_year_sale: "Abbonamneto annuale (sconto 35%)"
subscription_required_to_play: "Devi essere abbonato per giocare su questo livello."
unlock_help_videos: "Abbonati per accedere a tutti i tutorial video."
personal_sub: "Sottoscrizione Personale" # Accounts Subscription View below
# loading_info: "Loading subscription information..."
# managed_by: "Managed by"
# will_be_cancelled: "Will be cancelled on"
# currently_free: "You currently have a free subscription"
# currently_free_until: "You currently have a subscription until"
# was_free_until: "You had a free subscription until"
# managed_subs: "Managed Subscriptions"
# managed_subs_desc: "Add subscriptions for other players (students, children, etc.)"
# managed_subs_desc_2: "Recipients must have a CodeCombat account associated with the email address you provide."
# group_discounts: "Group discounts"
# group_discounts_1: "We also offer group discounts for bulk subscriptions."
loading_info: "Caricamento informazioni abbonamento..."
managed_by: "Gestito da"
will_be_cancelled: "Sara' rimosso il"
currently_free: "Al momento hai un abbonamento gratuito"
currently_free_until: "Hai un abbonamento valido fino al"
was_free_until: "Hai avuto un abbonamento gratuito fino al "
managed_subs: "Gestione Abbonamenti"
managed_subs_desc: "Aggiungi un abbonamento per altri giocatori (studenti, bambini, etc.)"
managed_subs_desc_2: "I beneficiari devono avere un abbonamento CodeCombat associato con l'email fornito."
group_discounts: "Sconto comitiva"
group_discounts_1: "Offriamo sconto comitiva anche per abbonamenti in grandi volumi."
group_discounts_1st: "Prima sottoscrizione"
group_discounts_full: "Prezzo completo"
group_discounts_2nd: "Sottoscrizione 2-11" #"Subscriptions 2-11"
group_discounts_20: "20% disconto"
group_discounts_12th: "Sottoscrizione 12+" #"Subscriptions 12+"
group_discounts_40: "40% di sconto"
# subscribing: "Subscribing..."
# recipient_emails_placeholder: "Enter email address to subscribe, one per line."
subscribing: "Abbonamento..."
recipient_emails_placeholder: "Inserisci l'indirizzo email per abbonarti, uno per linea."
subscribe_users: "Iscrivere Utenti"
users_subscribed: "Utenti iscritti:"
# no_users_subscribed: "No users subscribed, please double check your email addresses."
# current_recipients: "Current Recipients"
# unsubscribing: "Unsubscribing"
# subscribe_prepaid: "Click Subscribe to use prepaid code"
# using_prepaid: "Using prepaid code for monthly subscription"
no_users_subscribed: "Utente non abbonato, per favore verifica il tuo indirizzo di email."
current_recipients: "Destinatari attuali"
unsubscribing: "Deregistrazione"
subscribe_prepaid: "Clicca su Registrazione per usare un codice pre pagato"
using_prepaid: "Usa un codice pre pagato per un abbonamento mensile"
choose_hero:
choose_hero: "Scegli il tuo eroe"
@ -1612,7 +1612,7 @@ module.exports = nativeDescription: "Italiano", englishDescription: "Italian", t
# inactive_developers: "Inactive Developers"
admin:
# av_espionage: "Espionage" # Really not important to translate /admin controls.
av_espionage: "Spionaggio" # Really not important to translate /admin controls.
av_espionage_placeholder: "Email o nome utente"
av_usersearch: "Cerca utenti"
av_usersearch_placeholder: "Email, username, nome, qualsiasi cosa"
@ -1621,8 +1621,8 @@ module.exports = nativeDescription: "Italiano", englishDescription: "Italian", t
av_entities_sub_title: "Entità"
av_entities_users_url: "Utenti"
av_entities_active_instances_url: "Istanze attive"
# av_entities_employer_list_url: "Employer List"
# av_entities_candidates_list_url: "Candidate List"
av_entities_employer_list_url: "Lista datore di lavoro"
av_entities_candidates_list_url: "Lista Candidati"
av_entities_user_code_problems_list_url: "Lista problemi codice utenti"
av_other_sub_title: "Altro"
av_other_debug_base_url: "Base (per il debug di base.jade)"

View file

@ -7,6 +7,7 @@ TrialRequestSchema = c.object {
_.extend TrialRequestSchema.properties,
applicant: c.objectId(links: [{rel: 'extra', href: '/db/user/{($)}'}])
created: c.date()
prepaidCode: c.objectId()
reviewDate: c.date({readOnly: true})
reviewer: c.objectId(links: [{rel: 'extra', href: '/db/user/{($)}'}])

View file

@ -3,6 +3,9 @@
.name-col-cell
max-width: 170px
&.ai
color: #3f44bf
.histogram-display
height: 130px

View file

@ -31,7 +31,7 @@
td.hero-portrait-cell(style="background-image: url(/file/db/thang.type/#{(session.get('heroConfig') || {}).thangType || '529ffbf1cf1818f2be000001'}/portrait.png)")
td.rank-cell= rank + 1
td.score-cell= Math.round(sessionStats.totalScore * 100)
td.name-col-cell= session.get('creatorName') || "Anonymous"
td(class='name-col-cell' + ((new RegExp('(Simple|Shaman|Brawler|Chieftain|Thoktar) AI')).test(session.get('creatorName')) ? ' ai' : ''))= session.get('creatorName') || "Anonymous"
td.age-cell= moment(session.get('submitDate')).fromNow().replace('a few ', '')
td.fight-cell
a(href="/play/level/#{level.get('slug') || level.id}?team=#{team.otherTeam}&opponent=#{session.id}" + (league ? "&league=" + league.id : ""))
@ -51,7 +51,7 @@
td.hero-portrait-cell(style="background-image: url(/file/db/thang.type/#{(session.get('heroConfig') || {}).thangType || '529ffbf1cf1818f2be000001'}/portrait.png)")
td.rank-cell= session.rank
td.score-cell= Math.round(sessionStats.totalScore * 100)
td.name-col-cell= session.get('creatorName') || "Anonymous"
td(class='name-col-cell' + ((new RegExp('(Simple|Shaman|Brawler|Chieftain|Thoktar) AI')).test(session.get('creatorName')) ? ' ai' : ''))= session.get('creatorName') || "Anonymous"
td.age-cell= moment(session.get('submitDate')).fromNow().replace('a few ', '')
td.fight-cell
a(href="/play/level/#{level.get('slug') || level.id}?team=#{team.otherTeam}&opponent=#{session.id}" + (league ? "&league=" + league.id : ""))

View file

@ -7,6 +7,6 @@ module.exports = class TeachersView extends RootView
constructor: ->
super()
# Redirect to HoC version of /courses/teachers until we update the /teachers landing page
application.router.navigate "/courses/teachers?hoc=true", trigger: true
_.defer ->
# Redirect to HoC version of /courses/teachers until we update the /teachers landing page
application.router.navigate "/courses/teachers?hoc=true", trigger: true

View file

@ -37,11 +37,9 @@ module.exports = class AccountSettingsView extends CocoView
#- Form input callbacks
onChangePanelInput: (e) ->
return if $(e.target).closest('.form').attr('id') in ['reset-progress-form', 'delete-account-form']
$(e.target).addClass 'changed'
if (JSON.stringify(document.getElementById('email1').className)).indexOf("changed") > -1 or (JSON.stringify(document.getElementById('password1').className)).indexOf("changed") > -1
$(e.target).removeClass 'changed'
else
@trigger 'input-changed'
@trigger 'input-changed'
onClickToggleAllButton: ->
subs = @getSubscriptions()

View file

@ -102,7 +102,7 @@ module.exports = class PatchesView extends RootView
@render()
modelNamesRequest = @supermodel.addRequestResource 'patches', {
url: "/db/#{collection}/names"
url: "/db/#{collection.replace('_', '.')}/names"
data: {ids: ids}
method: 'POST'
success: success

View file

@ -196,7 +196,7 @@ module.exports = class ClanDetailsView extends RootView
if level.concepts?
for concept in level.concepts
@conceptsProgression.push concept unless concept in @conceptsProgression
if level.type is 'hero-ladder'
if level.type is 'hero-ladder' and level.slug not in ['capture-their-flag']
@arenas.push level
@campaignLevelProgressions.push campaignLevelProgression
@render?()

View file

@ -221,9 +221,9 @@ module.exports = class CourseDetailsView extends RootView
levelID = $(e.target).data('level-id')
level = @campaign.get('levels')[levelID]
if level.type is 'course-ladder'
Backbone.Mediator.publish 'router:navigate', {
route: '/play/ladder/' + levelSlug + '/course/' + @courseInstance.id
}
route = '/play/ladder/' + levelSlug
route += '/course/' + @courseInstance.id if @courseInstance.get('members').length > 1 # No league for solo courses
Backbone.Mediator.publish 'router:navigate', route: route
else
Backbone.Mediator.publish 'router:navigate', {
route: @getLevelURL levelSlug

View file

@ -10,13 +10,13 @@ CourseInstance = require 'models/CourseInstance'
RootView = require 'views/core/RootView'
template = require 'templates/courses/teacher-courses-view'
utils = require 'core/utils'
InviteToClassroomModal = require 'views/courses/InviteToClassroomModal'
InviteToClassroomModal = require 'views/courses/InviteToClassroomModal'
ClassroomSettingsModal = require 'views/courses/ClassroomSettingsModal'
module.exports = class TeacherCoursesView extends RootView
id: 'teacher-courses-view'
template: template
events:
'click #create-new-class-btn': 'onClickCreateNewclassButton'
'click .add-students-btn': 'onClickAddStudentsButton'
@ -67,7 +67,7 @@ module.exports = class TeacherCoursesView extends RootView
@listenTo classroom, 'sync', ->
classroom.saving = false
@fillMissingCourseInstances()
renderManageTab: ->
isActive = @$('#manage-tab-pane').hasClass('active')
@renderSelectors('#manage-tab-pane')
@ -85,7 +85,7 @@ module.exports = class TeacherCoursesView extends RootView
classroom = @classrooms.get(classroomID)
modal = new InviteToClassroomModal({classroom: classroom})
@openModalView(modal)
onLoaded: ->
super()
@linkCourseIntancesToCourses()
@ -94,7 +94,7 @@ module.exports = class TeacherCoursesView extends RootView
linkCourseIntancesToCourses: ->
for courseInstance in @courseInstances.models
courseInstance.course = @courses.get(courseInstance.get('courseID'))
fillMissingCourseInstances: ->
# TODO: Give teachers control over which courses are enabled for a given class.
# Add/remove course instances and columns in the view to match.
@ -135,7 +135,7 @@ module.exports = class TeacherCoursesView extends RootView
@usersToRedeem = new CocoCollection(_.values(usersToRedeem), {model: User})
@numCourseInstancesToAddTo = checkedBoxes.length
@renderSelectors '#fixed-area'
onClickSaveChangesButton: ->
@$('.course-instance-membership-checkbox').attr('disabled', true)
checkedBoxes = @$('.course-instance-membership-checkbox:checked')
@ -154,12 +154,12 @@ module.exports = class TeacherCoursesView extends RootView
@state = 'saving-changes'
@renderSelectors '#fixed-area'
@redeemUsers()
redeemUsers: ->
if not @usersToRedeem.size()
@addMemberships()
return
user = @usersToRedeem.first()
prepaid = @prepaids.find (prepaid) -> prepaid.openSpots()
$.ajax({
@ -186,7 +186,7 @@ module.exports = class TeacherCoursesView extends RootView
@renderSelectors '#fixed-area'
document.location.reload()
return
membershipAddition = @membershipAdditions.first()
courseInstance = membershipAddition.get('courseInstance')
userID = membershipAddition.get('userID')
@ -210,4 +210,4 @@ module.exports = class TeacherCoursesView extends RootView
})
onClickManageTabLink: ->
@$('.nav-tabs a[href="#manage-tab-pane"]').tab('show')
@$('.nav-tabs a[href="#manage-tab-pane"]').tab('show')

View file

@ -150,7 +150,7 @@ module.exports = class LadderTabView extends CocoView
refreshLadder: ->
@supermodel.resetProgress()
@ladderLimit ?= parseInt @getQueryVariable('top_players', 20)
@ladderLimit ?= parseInt @getQueryVariable('top_players', if @options.league then 100 else 20)
for team in @teams
if oldLeaderboard = @leaderboards[team.id]
@supermodel.removeModelResource oldLeaderboard

View file

@ -11,10 +11,14 @@ module.exports.teamDataFromLevel = (level) ->
color = teamConfigs[team].color
bgColor = hslToHex([color.hue, color.saturation, color.lightness + (1 - color.lightness) * 0.5])
primaryColor = hslToHex([color.hue, 0.5, 0.5])
if level.get('slug') in ['wakka-maul']
displayName = _.string.titleize(team)
else
displayName = $.i18n.t("ladder.#{team}") # Use Red/Blue instead of Humans/Ogres
teams.push({
id: team
name: _.string.titleize(team)
displayName: $.i18n.t("ladder.#{team}") # Use Red/Blue instead of Humans/Ogres
displayName: displayName
otherTeam: otherTeam
otherTeamDisplayName: $.i18n.t("ladder.#{otherTeam}")
bgColor: bgColor

View file

@ -33,9 +33,9 @@ class LevelSessionsCollection extends CocoCollection
@url = "/db/user/#{me.id}/level.sessions?project=state.complete,levelID,state.difficulty,playtime"
class CampaignsCollection extends CocoCollection
url: '/db/campaign/-/overworld'
# We don't send all of levels, just the parts needed in countLevels
url: '/db/campaign/-/overworld?project=slug,adjacentCampaigns,name,fullName,description,i18n,color,levels'
model: Campaign
project: ['name', 'fullName', 'description', 'i18n']
module.exports = class CampaignView extends RootView
id: 'campaign-view'

View file

@ -381,12 +381,13 @@ module.exports = class HeroVictoryModal extends ModalView
returnToLadder: ->
# Preserve the supermodel as we navigate back to the ladder.
viewArgs = [{supermodel: if @options.hasReceivedMemoryWarning then null else @supermodel}, @level.get('slug')]
ladderURL = "/play/ladder/#{@level.get('slug') || @level.id}#my-matches"
ladderURL = "/play/ladder/#{@level.get('slug') || @level.id}"
if leagueID = @getQueryVariable 'league'
leagueType = if @level.get('type') is 'course-ladder' then 'course' else 'clan'
viewArgs.push leagueType
viewArgs.push leagueID
ladderURL += "/#{leagueType}/#{leagueID}"
ladderURL += '#my-matches'
Backbone.Mediator.publish 'router:navigate', route: ladderURL, viewClass: 'views/ladder/LadderView', viewArgs: viewArgs
playSelectionSound: (hero, preload=false) ->

View file

@ -74,12 +74,13 @@ module.exports = class MultiplayerView extends CocoView
onGameSubmitted: (e) ->
# Preserve the supermodel as we navigate back to the ladder.
viewArgs = [{supermodel: if @options.hasReceivedMemoryWarning then null else @supermodel}, @levelID]
ladderURL = "/play/ladder/#{@levelID}#my-matches"
ladderURL = "/play/ladder/#{@levelID}"
if leagueID = @getQueryVariable 'league'
leagueType = if @level?.get('type') is 'course-ladder' then 'course' else 'clan'
viewArgs.push leagueType
viewArgs.push leagueID
ladderURL += "/#{leagueType}/#{leagueID}"
ladderURL += '#my-matches'
Backbone.Mediator.publish 'router:navigate', route: ladderURL, viewClass: 'views/ladder/LadderView', viewArgs: viewArgs
updateLinkSection: ->

View file

@ -0,0 +1,78 @@
// Add user.courseInstances properties and then add those to session leagues
// Usage:
// mongo <address>:<port>/<database> <script file> -u <username> -p <password>
addCourseInstancesToUsers();
function uniq(array) {
var u = {}, a = [];
for(var i = 0, l = array.length; i < l; ++i){
if(u.hasOwnProperty(array[i])) {
continue;
}
a.push(array[i]);
u[array[i]] = 1;
}
return a;
}
function addCourseInstancesToUsers() {
print("Adding courseInstances to users...");
var cursor = db.course.instances.find({$where: "this.members && this.members.length > 1"}, {members: 1});
print("CourseInstances with users found: " + cursor.count());
var courseInstances = cursor.toArray();
var userIDList = [];
courseInstances.forEach(function (courseInstance, courseInstanceIndex) {
userIDList = userIDList.concat(courseInstance.members);
var conditions = {_id: {$in: courseInstance.members}};
var operations = {$addToSet: {courseInstances: courseInstance._id}};
//print('Fetching', JSON.stringify(conditions), 'with operations', JSON.stringify(operations));
//print("... Have this many:", db.users.count(conditions));
var result = db.users.update(conditions, operations, {multi: true});
if (courseInstanceIndex % 100 === 0)
print("Done", courseInstanceIndex, "\tof", courseInstances.length, "course instances.");
});
print("Done adding course instances to users; now going to add them to sessions for leagues.");
addCourseInstancesToSessions(userIDList);
}
function addCourseInstancesToSessions(userIDList) {
userIDList = uniq(userIDList);
print("Adding courseInstance leagues to sessions for", userIDList.length, "users...");
var cursor = db.users.find({_id: {$in: userIDList}, courseInstances: {$exists: true}}, {courseInstances: 1, name: 1, leagues: 1});
print("Users with courseInstances found: " + cursor.count(), '-- supposed to have:', userIDList.length);
var users = cursor.toArray();
var arenas = [
"5442ba0e1e835500007eb1c7",
"550363b4ec31df9c691ab629",
"5469643c37600b40e0e09c5b",
"54b83c2629843994803c838e",
"544437e0645c0c0000c3291d",
"5630eab0c0fcbd86057cc2f8",
"55de80407a57948705777e89"
];
users.forEach(function (user, userIndex) {
var sessions = db.level.sessions.find({creator: user._id + '', 'level.original': {$in: arenas}, submitted: true}).toArray();
//print("Found sessions", sessions, "for user", user._id, user.name, 'who has courseInstances', user.courseInstances.join(', '));
sessions.forEach(function(session, sessionIndex) {
var leagues = session.leagues || [];
for (var i = 0; i < user.courseInstances.length; ++i) {
var alreadyHave = false;
for (var j = 0; j < leagues.length; ++j)
if (leagues[j].leagueID == user.courseInstances[i])
alreadyHave = true;
if (!alreadyHave)
leagues.push({leagueID: user.courseInstances[i] + '', stats: {standardDeviation: 25 / 3, numberOfWinsAndTies: 0, numberOfLosses: 0, totalScore: 10, meanStrength: 25}});
}
//print(" Setting leagues to...");
//printjson(leagues);
session.leagues = leagues;
db.level.sessions.save(session);
});
if (userIndex % 100 === 0)
print("Done", userIndex, "\tof", users.length, "users.");
});
print("Done.");
}

View file

@ -54,12 +54,17 @@ CampaignHandler = class CampaignHandler extends Handler
getOverworld: (req, res) ->
return @sendForbiddenError(res) if not @hasAccess(req)
q = @modelClass.find {}, slug: 1, adjacentCampaigns: 1, fullName: 1, description: 1, color: 1
projection = {}
if req.query.project
projection[field] = 1 for field in req.query.project.split(',')
q = @modelClass.find {}, projection
q.exec (err, documents) =>
return @sendDatabaseError(res, err) if err
formatCampaign = (doc) =>
obj = @formatEntity(req, doc)
obj.adjacentCampaigns = _.mapValues(obj.adjacentCampaigns, (a) -> _.pick(a, ['showIfUnlocked', 'color', 'name', 'description' ]))
for original, level of obj.levels
obj.levels[original] = _.pick level, ['locked', 'disabled', 'original', 'rewards']
obj
documents = (formatCampaign(doc) for doc in documents)
@sendSuccess(res, documents)

View file

@ -80,13 +80,11 @@ PrepaidHandler = class PrepaidHandler extends Handler
return @sendDatabaseError(res, err) if err
return @sendNotFoundError(res, 'User for given ID not found') if not user
userID = user.get('_id')
# Prepaid.count {'redeemers.userID': userID}, (err, count) =>
# return @sendDatabaseError(res, err) if err
# return @sendSuccess(res, @formatEntity(req, prepaid)) if count
return @sendSuccess(res, @formatEntity(req, prepaid)) if user.get('coursePrepaidID')
query =
_id: prepaid.get('_id')
'redeemers.userID': { $ne: req.user.get('_id') }
'redeemers.userID': { $ne: user.get('_id') }
$where: "this.redeemers.length < #{prepaid.get('maxRedeemers')}"
update = { $push: { redeemers : { date: new Date(), userID: userID } }}
Prepaid.update query, update, (err, nMatched) =>

View file

@ -61,6 +61,7 @@ TrialRequestSchema.post 'save', (doc) ->
TrialRequestSchema.statics.privateProperties = []
TrialRequestSchema.statics.editableProperties = [
'created'
'prepaidCode'
'properties'
'reviewDate'

View file

@ -19,6 +19,7 @@ TrialRequestHandler = class TrialRequestHandler extends Handler
makeNewInstance: (req) ->
instance = super(req)
instance.set 'applicant', req.user._id
instance.set 'created', new Date()
instance.set 'status', 'submitted'
instance

View file

@ -43,6 +43,7 @@ describe '/db/prepaid', ->
redeemers: [],
creator: user1.get('_id')
code: 0
type: 'course'
})
prepaid.save (err, prepaid) ->
otherUser = new User()
@ -67,6 +68,7 @@ describe '/db/prepaid', ->
redeemers: [],
creator: user1.get('_id')
code: 1
type: 'course'
})
prepaid.save (err, prepaid) ->
otherUser = new User()
@ -84,6 +86,7 @@ describe '/db/prepaid', ->
redeemers: [],
creator: user1.get('_id')
code: 2
type: 'course'
})
prepaid.save (err, prepaid) ->
loginNewUser (user2) ->
@ -97,11 +100,14 @@ describe '/db/prepaid', ->
it 'is idempotent across prepaids collection', (done) ->
loginNewUser (user1) ->
otherUser = new User()
otherUser = new User({
'coursePrepaidID': new ObjectId()
})
otherUser.save (err, otherUser) ->
prepaid1 = new Prepaid({
redeemers: [{userID: otherUser.get('_id')}],
code: 3
type: 'course'
})
prepaid1.save (err, prepaid1) ->
prepaid2 = new Prepaid({
@ -109,6 +115,7 @@ describe '/db/prepaid', ->
redeemers: [],
creator: user1.get('_id')
code: 4
type: 'course'
})
prepaid2.save (err, prepaid2) ->
url = getURL("/db/prepaid/#{prepaid2.id}/redeemers")
@ -118,6 +125,64 @@ describe '/db/prepaid', ->
return done() unless res.statusCode is 200
expect(body.redeemers.length).toBe(0)
done()
it 'is idempotent to itself for a user other than the creator', (done) ->
loginNewUser (user1) ->
prepaid = new Prepaid({
maxRedeemers: 2,
redeemers: [],
creator: user1.get('_id')
code: 0
type: 'course'
})
prepaid.save (err, prepaid) ->
otherUser = new User()
otherUser.save (err, otherUser) ->
url = getURL("/db/prepaid/#{prepaid.id}/redeemers")
redeemer = { userID: otherUser.id }
request.post {uri: url, json: redeemer }, (err, res, body) ->
expect(body.redeemers?.length).toBe(1)
expect(res.statusCode).toBe(200)
request.post {uri: url, json: redeemer }, (err, res, body) ->
expect(body.redeemers?.length).toBe(1)
expect(res.statusCode).toBe(200)
prepaid = Prepaid.findById body._id, (err, prepaid) ->
expect(err).toBeNull()
expect(prepaid.get('redeemers').length).toBe(1)
User.findById otherUser.id, (err, user) ->
expect(user.get('coursePrepaidID').equals(prepaid.get('_id'))).toBe(true)
done()
it 'is idempotent to itself for the creator', (done) ->
loginNewUser (user1) ->
prepaid = new Prepaid({
maxRedeemers: 2,
redeemers: [],
creator: user1.get('_id')
code: 0
type: 'course'
})
prepaid.save (err, prepaid) ->
otherUser = new User()
otherUser.save (err, otherUser) ->
url = getURL("/db/prepaid/#{prepaid.id}/redeemers")
redeemer = { userID: user1.id }
request.post {uri: url, json: redeemer }, (err, res, body) ->
expect(body.redeemers?.length).toBe(1)
expect(res.statusCode).toBe(200)
request.post {uri: url, json: redeemer }, (err, res, body) ->
expect(body.redeemers?.length).toBe(1)
expect(res.statusCode).toBe(200)
prepaid = Prepaid.findById body._id, (err, prepaid) ->
expect(err).toBeNull()
expect(prepaid.get('redeemers').length).toBe(1)
User.findById user1.id, (err, user) ->
expect(user.get('coursePrepaidID').equals(prepaid.get('_id'))).toBe(true)
redeemer = { userID: otherUser.id }
request.post {uri: url, json: redeemer }, (err, res, body) ->
expect(body.redeemers?.length).toBe(2)
expect(res.statusCode).toBe(200)
done()
it 'Clear database', (done) ->
clearModels [Course, CourseInstance, Payment, Prepaid, User], (err) ->