Merge branch 'master' into production

This commit is contained in:
Nick Winter 2015-11-24 08:56:36 -08:00
commit 747856bd4c
15 changed files with 239 additions and 64 deletions

View file

@ -724,16 +724,16 @@ module.exports = nativeDescription: "日本語", englishDescription: "Japanese",
wrong_password: "間違ったパスワード"
upload_picture: "画像をアップロード"
delete_this_account: "アカウントを完全削除する"
# reset_progress_tab: "Reset All Progress"
# reset_your_progress: "Clear all your progress and start over"
reset_progress_tab: "すべての進捗をリセットする"
reset_your_progress: "すべての進捗をリセットしやり直す"
god_mode: "ゴッドモード"
password_tab: "パスワード"
emails_tab: "メール"
admin: "管理者"
new_password: "新パスワード"
new_password_verify: "新パスワードを再入力"
type_in_email: "アカウントの削除を確認するために、メールアドレスを入力して下さい"
# type_in_email_progress: "Type in your email to confirm deleting your progress."
type_in_email: "アカウントの削除を確認するために、メールアドレスを入力して下さい"
type_in_email_progress: "進捗を消すために、メールアドレスを入力してください。"
type_in_password: "そして、パスワードを入力してください。"
email_subscriptions: "ニュースレターの購読"
email_subscriptions_none: "No Email Subscriptions."
@ -1147,22 +1147,22 @@ module.exports = nativeDescription: "日本語", englishDescription: "Japanese",
summary_wins: " 勝利数, "
summary_losses: " 敗北数"
rank_no_code: "新しいコードがランクにありません"
# rank_my_game: "Rank My Game!"
rank_my_game: "試合をランキングに送信!"
rank_submitting: "送信中..."
rank_submitted: "ランキングに送信されました。"
# rank_failed: "Failed to Rank"
# rank_being_ranked: "Game Being Ranked"
rank_failed: "ランキングに送信できませんでした。"
rank_being_ranked: "ランキングにのっています"
# rank_last_submitted: "submitted "
# help_simulate: "Help simulate games?"
# code_being_simulated: "Your new code is being simulated by other players for ranking. This will refresh as new matches come in."
# no_ranked_matches_pre: "No ranked matches for the "
# no_ranked_matches_post: " team! Play against some competitors and then come back here to get your game ranked."
# choose_opponent: "Choose an Opponent"
choose_opponent: "相手を選んでください"
select_your_language: "使う言語を選んでください!"
tutorial_play: "チュートリアルで遊ぶ"
tutorial_recommended: "はじめて遊ぶ人におすすめ"
tutorial_skip: "チュートリアルをスキップする"
# tutorial_not_sure: "Not sure what's going on?"
tutorial_not_sure: "なにが起きているのかわかりませんか?"
tutorial_play_first: "はじめからチュートリアルを遊ぶ"
simple_ai: "単純なAI"
warmup: "ウォームアップ"

View file

@ -147,7 +147,7 @@ module.exports = nativeDescription: "Tiếng Việt", englishDescription: "Vietn
unwatch: "Ngừng Quan Sát"
submit_patch: "Gửi Bản Cập Nhật"
submit_changes: "Gửi Những Thay Đổi"
# save_changes: "Save Changes"
save_changes: "Lưu Những Thay Đổi"
general:
and: ""
@ -251,7 +251,7 @@ module.exports = nativeDescription: "Tiếng Việt", englishDescription: "Vietn
victory_saving_progress: "Đang lưu tiến trình"
victory_go_home: "Quay về màn hình chính"
victory_review: "Hãy cho chúng tôi biết thêm"
# victory_review_placeholder: "How was the level?"
victory_review_placeholder: "Bàn vừa rồi như thế nào ?"
victory_hour_of_code_done: "Bạn xong chưa?"
victory_hour_of_code_done_yes: "Đúng vậy, tôi đã hoàn tất thời gian lập trình!"
victory_experience_gained: "XP nhận được"
@ -336,7 +336,7 @@ module.exports = nativeDescription: "Tiếng Việt", englishDescription: "Vietn
tip_google: "Có vấn đề mà bạn không thể giải quyết ? Hãy sử dụng Google để tìm ra phương án!"
# tip_adding_evil: "Adding a pinch of evil."
# tip_hate_computers: "That's the thing about people who think they hate computers. What they really hate is lousy programmers. - Larry Niven"
# tip_open_source_contribute: "You can help CodeCombat improve!"
tip_open_source_contribute: "Bạn có thể giúp CodeCombat trở nên tốt hơn!"
# tip_recurse: "To iterate is human, to recurse divine. - L. Peter Deutsch"
# tip_free_your_mind: "You have to let it all go, Neo. Fear, doubt, and disbelief. Free your mind. - Morpheus"
# tip_strong_opponents: "Even the strongest of opponents always has a weakness. - Itachi Uchiha"
@ -434,7 +434,7 @@ module.exports = nativeDescription: "Tiếng Việt", englishDescription: "Vietn
# 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: "Chỉ với $9.99 USD một tháng, con của bạn sẽ nhận được những thử thách mới mỗi tháng và sẽ nhận được sự hỗ trợ từ các lập trình viên chuyên nghiệp qua email." # {change}
parents_blurb3: "Không hề có rủi ro: Nếu bạn không hài lòng bạn có thể nhận lại 100% số tiền mình bỏ ra chỉ với 1 cú nhấp chuốt."
# payment_methods: "Payment Methods"
payment_methods: "Những phương thức thanh toán"
# payment_methods_title: "Accepted Payment Methods"
# payment_methods_blurb1: "We currently accept credit cards and Alipay."
# payment_methods_blurb2: "If you require an alternate form of payment, please contact"
@ -497,7 +497,7 @@ module.exports = nativeDescription: "Tiếng Việt", englishDescription: "Vietn
coffeescript_blurb: "Thực ra là JavaScript nhưng với cú pháp tốt hơn."
clojure_blurb: "Lisp thời đại mới."
lua_blurb: "Ngôn ngữ hay dùng trong làm game."
# io_blurb: "Simple but obscure."
io_blurb: "Đơn giản nhưng ít người biết đến."
status: "Tình trạng"
hero_type: "Loại"
weapons: "Vũ khí"
@ -635,7 +635,7 @@ module.exports = nativeDescription: "Tiếng Việt", englishDescription: "Vietn
# private_clans_1: "Private Clans provide increased privacy and detailed progress information for each student."
# private_clans_2: "To create a private Clan, check the 'Make clan private' checkbox when creating a"
# private_clans_3: "."
# who_for_title: "Who is CodeCombat for?"
who_for_title: "CodeCombat dành cho ai ?"
# who_for_1: "We recommend CodeCombat for students aged 9 and up. No prior programming experience is needed."
# who_for_2: "We've designed CodeCombat to appeal to both boys and girls."
# material_title: "How much material is there?"
@ -719,11 +719,11 @@ module.exports = nativeDescription: "Tiếng Việt", englishDescription: "Vietn
autosave: "Tự động lưu thay đổi"
me_tab: "Tôi"
picture_tab: "Bức tranh"
# delete_account_tab: "Delete Your Account"
# wrong_email: "Wrong Email"
# wrong_password: "Wrong Password"
delete_account_tab: "Xóa tài khoản của bạn"
wrong_email: "Email không đúng"
wrong_password: "Mật khẩu không đúng"
upload_picture: "Tải ảnh lên"
# delete_this_account: "Delete this account permanently"
delete_this_account: "Xóa tài khoản này vĩnh viễn"
# reset_progress_tab: "Reset All Progress"
# reset_your_progress: "Clear all your progress and start over"
# god_mode: "God Mode"
@ -745,7 +745,7 @@ module.exports = nativeDescription: "Tiếng Việt", englishDescription: "Vietn
# email_any_notes_description: "Disable to stop all activity notification emails."
# email_news: "News"
email_recruit_notes: "Cơ hội việc làm"
# email_recruit_notes_description: "If you play really well, we may contact you about getting you a (better) job."
email_recruit_notes_description: "Nếu bạn chơi trò này rất giỏi, chúng tôi có thể sẽ liên lạc với bạn về cơ hội nghề nghiệp."
# contributor_emails: "Contributor Class Emails"
contribute_prefix: "Chúng tôi đang tìm thêm người vào nhóm của chúng tôi! Hãy kiểm "
contribute_page: "trang đóng góp"
@ -765,7 +765,7 @@ module.exports = nativeDescription: "Tiếng Việt", englishDescription: "Vietn
keyboard_shortcuts: "Các phím tắt"
space: "Phím Space"
enter: "Phím Enter"
# press_enter: "press enter"
press_enter: "Ấn Enter"
escape: "Phím Escape"
shift: "Phím Shift"
# run_code: "Run current code."
@ -801,19 +801,19 @@ module.exports = nativeDescription: "Tiếng Việt", englishDescription: "Vietn
# social_hipchat: "Chat with us in the public CodeCombat Slack channel"
# contribute_to_the_project: "Contribute to the project"
# clans:
# clan: "Clan"
clans:
clan: "Clan"
# clans: "Clans"
# new_name: "New clan name"
# new_description: "New clan description"
# make_private: "Make clan private"
# subs_only: "subscribers only"
# create_clan: "Create New Clan"
create_clan: "Tạo một clan mới"
# private_preview: "Preview"
# public_clans: "Public Clans"
# my_clans: "My Clans"
# clan_name: "Clan Name"
# name: "Name"
name: "Tên"
# chieftain: "Chieftain"
# type: "Type"
# edit_clan_name: "Edit Clan Name"
@ -829,7 +829,7 @@ module.exports = nativeDescription: "Tiếng Việt", englishDescription: "Vietn
# join_clan: "Join Clan"
# invite_1: "Invite:"
# invite_2: "*Invite players to this Clan by sending them this link."
# members: "Members"
members: "Những thành viên"
# progress: "Progress"
# not_started_1: "not started"
# started_1: "started"
@ -859,9 +859,9 @@ module.exports = nativeDescription: "Tiếng Việt", englishDescription: "Vietn
# track_concepts8: "to join"
# private_require_sub: "Private clans require a subscription to create or join."
# courses:
# course: "Course"
# courses: "courses"
courses:
course: "Khóa học"
courses: "Những khóa học"
# not_enrolled: "You are not enrolled in this course."
# visit_pref: "Please visit the"
# visit_suf: "page to enroll."
@ -872,11 +872,11 @@ module.exports = nativeDescription: "Tiếng Việt", englishDescription: "Vietn
# edit_settings: "edit class settings"
# edit_settings1: "Edit Class Settings"
# progress: "Class Progress"
# add_students: "Add Students"
# stats: "Statistics"
# total_students: "Total students:"
add_students: "Thêm học sinh"
stats: "Thống kê"
total_students: "Tổng số học sinh:"
# average_time: "Average level play time:"
# total_time: "Total play time:"
total_time: "Tổng thời gian chơi:"
# average_levels: "Average levels completed:"
# total_levels: "Total levels completed:"
# furthest_level: "Furthest level completed:"
@ -896,20 +896,20 @@ module.exports = nativeDescription: "Tiếng Việt", englishDescription: "Vietn
# capacity_used: "Course slots used:"
# enter_emails: "Enter student emails to invite, one per line"
# send_invites: "Send Invites"
# title: "Title"
title: "Tiêu đề"
# description: "Description"
# creating_class: "Creating class..."
# purchasing_course: "Purchasing course..."
# buy_course: "Buy Course"
# buy_course1: "Buy this course"
buy_course: "Mua khóa học"
buy_course1: "Mua khóa học này"
# create_class: "Create Class"
# select_all_courses: "Select 'All Courses' for a 50% discount!"
# all_courses: "All Courses"
# number_students: "Number of students"
# enter_number_students: "Enter the number of students you need for this class."
all_courses: "Tất cả những khóa học"
number_students: "Số lượng học sinh"
enter_number_students: "Hãy nhập vào số lượng học sinh bạn cần cho lớp học này."
# name_class: "Name your class"
# displayed_course_page: "This will be displayed on the course page for you and your students. It can be changed later."
# buy: "Buy"
buy: "Mua"
# purchasing_for: "You are purchasing a license for"
# creating_for: "You are creating a class for"
# for: "for" # Like in 'for 30 students'
@ -925,7 +925,7 @@ module.exports = nativeDescription: "Tiếng Việt", englishDescription: "Vietn
# answer2: "The single player game has items, gems, hero selection, leveling up, and in-app purchases. Courses have classroom management features and streamlined student-focused level pacing."
# teachers_click: "Teachers Click Here"
# students_click: "Students Click Here"
# courses_on_coco: "Courses on CodeCombat"
courses_on_coco: "Những khóa học trên CodeCombat"
# designed_to: "Courses are designed to introduce computer science concepts using CodeCombat's fun and engaging environment. CodeCombat levels are organized around key topics to encourage progressive learning, over the course of 5 hours."
# more_in_less: "Learn more in less time"
# no_experience: "No coding experience necesssary"
@ -938,7 +938,7 @@ module.exports = nativeDescription: "Tiếng Việt", englishDescription: "Vietn
# enter_code1: "Enter unlock code"
# enroll: "Enroll"
# pick_from_classes: "Pick from your current classes"
# enter: "Enter"
enter: "Enter"
# or: "Or"
# topics: "Topics"
# hours_content: "Hours of content:"
@ -979,7 +979,7 @@ module.exports = nativeDescription: "Tiếng Việt", englishDescription: "Vietn
indoor: "Trong nhà"
desert: "Sa mạc"
# grassy: "Grassy"
# mountain: "Mountain"
mountain: "Đồi núi"
# glacier: "Glacier"
small: ""
large: "Lớn"
@ -1110,7 +1110,7 @@ module.exports = nativeDescription: "Tiếng Việt", englishDescription: "Vietn
# diplomat_i18n_page: "translations page"
# diplomat_i18n_page_suffix: ", or our interface and website on GitHub."
# diplomat_join_pref_github: "Find your language locale file "
# diplomat_github_url: "on GitHub"
diplomat_github_url: "ở trên GitHub"
# diplomat_join_suf_github: ", edit it online, and submit a pull request. Also, check this box below to keep up-to-date on new internationalization developments!"
# diplomat_subscribe_desc: "Get emails about i18n developments and levels to translate."
# ambassador_introduction: "This is a community we're building, and you are the connections. We've got forums, emails, and social networks with lots of people to talk with and help get acquainted with the game and learn from. If you want to help people get involved and have fun, and get a good feel of the pulse of CodeCombat and where we're going, then this class might be for you."
@ -1206,7 +1206,7 @@ module.exports = nativeDescription: "Tiếng Việt", englishDescription: "Vietn
# no_achievements: "No Achievements earned yet."
favorite_prefix: "Ngôn ngữ lập trình ưu thích là "
favorite_postfix: "."
# not_member_of_clans: "Not a member of any clans yet."
not_member_of_clans: "Không phải là thành viên của bất kì clan nào."
# achievements:
# last_earned: "Last Earned"
@ -1476,7 +1476,7 @@ module.exports = nativeDescription: "Tiếng Việt", englishDescription: "Vietn
complete: "Hoàn tất"
next: "Tiếp"
next_city: "Thành phố?"
# next_country: "pick your country."
next_country: "Chọn đất nước của bạn."
# next_name: "name?"
# next_short_description: "write a short description."
# next_long_description: "describe your desired position."
@ -1487,7 +1487,7 @@ module.exports = nativeDescription: "Tiếng Việt", englishDescription: "Vietn
# 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: "Blog"
example_blog: "Blog"
example_personal_site: "Trang cá nhân"
links_header: "Đường truyền cá nhân"
# links_blurb: "Link any other sites or profiles you want to highlight, like your GitHub, your LinkedIn, or your blog."
@ -1510,12 +1510,12 @@ module.exports = nativeDescription: "Tiếng Việt", englishDescription: "Vietn
# 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_internship: "Thực tập"
# basics_looking_for_help: "What kind of developer position do you want?"
name_header: "Điền tên của bạn"
# name_anonymous: "Anonymous Developer"
# name_help: "Name you want employers to see, like 'Nick Winter'."
# short_description_header: "Write a short description of yourself"
short_description_header: "Hãy viết một bản giới thiệu ngắn gọn về bạn"
# short_description_blurb: "Add a tagline to help an employer quickly learn more about you."
# short_description: "Tagline"
# short_description_help: "Who are you, and what are you looking for? 140 characters max."
@ -1577,9 +1577,9 @@ module.exports = nativeDescription: "Tiếng Việt", englishDescription: "Vietn
# filter_visa_no: "Not Authorized"
# filter_education_top: "Top School"
filter_education_other: "Khác"
# filter_role_web_developer: "Web Developer"
# filter_role_software_developer: "Software Developer"
# filter_role_mobile_developer: "Mobile Developer"
filter_role_web_developer: "Nhà phát triển web"
filter_role_software_developer: "Nhà phát triển phần mềm"
filter_role_mobile_developer: "Nhà phát triển phần ứng dụng di động"
filter_experience: "Kinh Nghiệm"
# filter_experience_senior: "Senior"
# filter_experience_junior: "Junior"

View file

@ -116,6 +116,7 @@ _.extend CampaignSchema.properties, {
}}
campaign: c.shortString title: 'Campaign', description: 'Which campaign this level is part of (like "desert").', format: 'hidden' # Automatically set by campaign editor.
campaignIndex: c.int title: 'Campaign Index', description: 'The 0-based index of this level in its campaign.', format: 'hidden' # Automatically set by campaign editor.
tasks: c.array {title: 'Tasks', description: 'Tasks to be completed for this level.'}, c.task
concepts: c.array {title: 'Programming Concepts', description: 'Which programming concepts this level covers.'}, c.concept

View file

@ -344,6 +344,7 @@ _.extend LevelSchema.properties,
type: 'string', links: [{rel: 'db', href: '/db/thang.type/{($)}/version'}], format: 'latest-version-original-reference'
}}
campaign: c.shortString title: 'Campaign', description: 'Which campaign this level is part of (like "desert").', format: 'hidden' # Automatically set by campaign editor.
campaignIndex: c.int title: 'Campaign Index', description: 'The 0-based index of this level in its campaign.', format: 'hidden' # Automatically set by campaign editor.
scoreTypes: c.array {title: 'Score Types', description: 'What metric to show leaderboards for.', uniqueItems: true},
c.shortString(title: 'Score Type', 'enum': ['time', 'damage-taken', 'damage-dealt', 'gold-collected', 'difficulty']) # TODO: good version of LoC; total gear value.
concepts: c.array {title: 'Programming Concepts', description: 'Which programming concepts this level covers.', uniqueItems: true}, c.concept

View file

@ -269,6 +269,7 @@ mixin levels-tab
tbody
if campaign
- var lastLevelCompleted = true;
- var levelCount = 0;
each level, levelID in campaign.get('levels')
tr
td
@ -281,7 +282,7 @@ mixin levels-tab
- lastLevelCompleted = userLevelStateMap[me.id][levelID] === 'complete'
else
- lastLevelCompleted = false
td= level.name.replace('Course: ', '')
td= ++levelCount + '. ' + level.name.replace('Course: ', '')
td
if levelConceptMap[levelID]
each concept in course.get('concepts')

View file

@ -22,7 +22,7 @@ else
.level-name-area
.level-label(data-i18n="play_level.level")
.level-name(title=difficultyTitle || "")
span= worldName.replace('Course: ', '')
span= (campaignIndex ? (campaignIndex + 1) + '. ' : '') + worldName.replace('Course: ', '')
if levelDifficulty
sup.level-difficulty= levelDifficulty

View file

@ -311,7 +311,7 @@ module.exports = class AnalyticsSubscriptionsView extends RootView
getAnalyticsInvoices = (done) =>
@updateFetchDataState "Fetching invoices #{Object.keys(invoices).length}..."
options =
url: '/db/analytics_stripe_invoice/-/all'
url: '/db/analytics.stripe.invoice/-/all'
method: 'GET'
options.error = (model, response, options) =>
return if @destroyed

View file

@ -92,7 +92,7 @@ module.exports = class CampaignEditorView extends RootView
onLoaded: ->
@toSave.add @campaign if @campaign.hasLocalChanges()
campaignLevels = $.extend({}, @campaign.get('levels'))
for level in @levels.models
for level, levelIndex in @levels.models
levelOriginal = level.get('original')
campaignLevel = campaignLevels[levelOriginal]
continue if not campaignLevel
@ -129,6 +129,8 @@ module.exports = class CampaignEditorView extends RootView
delete campaignLevel.unlocks
# Save campaign to level, unless it's a course campaign, since we reuse hero levels for course levels.
campaignLevel.campaign = @campaign.get 'slug' if @campaign.get('type', true) isnt 'course'
# Save campaign index to level if it's a course campaign, since we show linear level order numbers for course levels.
campaignLevel.campaignIndex = (@levels.models.length - levelIndex - 1) if @campaign.get('type', true) is 'course'
campaignLevels[levelOriginal] = campaignLevel
@campaign.set('levels', campaignLevels)

View file

@ -268,6 +268,7 @@ module.exports = class SpectateLevelView extends RootView
onNextGamePressed: (e) ->
@fetchRandomSessionPair (err, data) =>
return if @destroyed
if err? then return console.log "There was an error fetching the random session pair: #{data}"
@sessionOne = data[0]._id
@sessionTwo = data[1]._id

View file

@ -61,6 +61,7 @@ module.exports = class ControlBarView extends CocoView
getRenderData: (c={}) ->
super c
c.worldName = @worldName
c.campaignIndex = @level.get('campaignIndex') if @level.get('type') is 'course'
c.multiplayerEnabled = @session.get('multiplayer')
c.ladderGame = @level.get('type') in ['ladder', 'hero-ladder', 'course-ladder']
if c.isMultiplayerLevel = @isMultiplayerLevel

View file

@ -12,12 +12,12 @@ module.exports = class LevelGoalsView extends CocoView
template: template
className: 'secret expanded'
playbackEnded: false
mouseEntered: false
subscriptions:
'goal-manager:new-goal-states': 'onNewGoalStates'
'tome:cast-spells': 'onTomeCast'
'level:set-letterbox': 'onSetLetterbox'
'level:set-playing': 'onSetPlaying'
'surface:playback-restarted': 'onSurfacePlaybackRestarted'
'surface:playback-ended': 'onSurfacePlaybackEnded'
@ -84,6 +84,13 @@ module.exports = class LevelGoalsView extends CocoView
@$el.find('.goal-status').addClass('secret')
@$el.find('.goal-status.running').removeClass('secret')
onSetPlaying: (e) ->
return unless e.playing
# Automatically hide it while we replay
@mouseEntered = false
@expanded = true
@updatePlacement()
onSurfacePlaybackRestarted: ->
@playbackEnded = false
@$el.removeClass 'brighter'
@ -105,11 +112,17 @@ module.exports = class LevelGoalsView extends CocoView
@normalHeight = @$el.outerHeight()
updatePlacement: ->
expand = @playbackEnded or @mouseEntered
# Expand it if it's at the end. Mousing over reverses this.
expand = @playbackEnded isnt @mouseEntered
return if expand is @expanded
@updateHeight()
sound = if expand then 'goals-expand' else 'goals-collapse'
top = if expand then -5 else 41 - (@normalHeight ? @$el.outerHeight())
if expand
top = -5
else
height = @normalHeight
height = @$el.outerHeight() if not height or @playbackEnded
top = 41 - height
@$el.css 'top', top
if @soundTimeout
# Don't play the sound we were going to play after all; the transition has reversed.

View file

@ -444,10 +444,9 @@ module.exports = class HeroVictoryModal extends ModalView
viewArgs.push @courseInstanceID if @courseInstanceID
else if @level.get('type', true) is 'course-ladder'
leagueID = @getQueryVariable 'league'
link = "/play/ladder/"+@level.get('slug')+"/course/"+leagueID
Backbone.Mediator.publish 'router:navigate', {
route: link
}
link = "/play/ladder/#{@level.get('slug')}"
link += "/course/#{leagueID}" if leagueID
Backbone.Mediator.publish 'router:navigate', route: link
return
else
viewClass = require 'views/play/CampaignView'

View file

@ -0,0 +1,155 @@
// Add 2 course headcount to older approved teacher surveys
// Usage:
// mongo <address>:<port>/<database> <script file> -u <username> -p <password>
// Who needs 2 course headcount added?
// Approved trial.requests but no prepaid with properties.trialRequestID set
// Priority userID selection
// 1. UserID that redeemed approved prepaid
// 2. UserID that applied for trial request
// 3. User email that applied for trial request
// NOTE: May give course headcount to multiple accounts if they applied and redeemed with different users
addHeadcount();
function addHeadcount() {
print("Finding approved trial requests..");
var approvedUserIDMap = {};
var approvedUserEmails = [];
var codeRequestMap = {};
var userIDRequestMap = {};
var userEmailRequestMap = {};
var cursor = db['trial.requests'].find({status: 'approved', type: 'subscription'}, {});
while (cursor.hasNext()) {
var doc = cursor.next();
if (doc.applicant) {
approvedUserIDMap[doc.applicant.valueOf()] = false;
userIDRequestMap[doc.applicant.valueOf()] = doc._id;
}
if (doc.prepaidCode) {
codeRequestMap[doc.prepaidCode] = doc._id;
}
approvedUserEmails.push(doc.properties.email.toLowerCase());
userEmailRequestMap[doc.properties.email.toLowerCase()] = doc._id;
}
print("Finding users via redeemed prepaids..");
cursor = db.prepaids.find({code: {$in: Object.keys(codeRequestMap)}});
while (cursor.hasNext()) {
var doc = cursor.next();
if (doc.redeemers && doc.redeemers.length > 0) {
for (var i = 0; i < doc.redeemers.length; i++) {
approvedUserIDMap[doc.redeemers[i].userID.valueOf()] = false;
userIDRequestMap[doc.redeemers[i].userID.valueOf()] = codeRequestMap[doc.code];
}
}
}
print("Finding users via approved emails..");
cursor = db.users.find({emailLower: {$in: approvedUserEmails}});
while (cursor.hasNext()) {
var doc = cursor.next();
approvedUserIDMap[doc._id.valueOf()] = false;
if (userEmailRequestMap[doc.emailLower]) {
// Trial request had a known email, but not an applicant field set
userIDRequestMap[doc._id.valueOf()] = userEmailRequestMap[doc.emailLower];
}
}
var approvedUserIDs = [];
for (var userID in approvedUserIDMap) {
approvedUserIDs.push(new ObjectId(userID));
}
print("Approved user IDs:", approvedUserIDs.length);
print("Finding approved users with trial request headcount..");
cursor = db.prepaids.find({$and: [{creator: {$in: approvedUserIDs}}, {'properties.trialRequestID': {$exists: true}}]});
while (cursor.hasNext()) {
var doc = cursor.next();
approvedUserIDMap[doc.creator.valueOf()] = true;
}
var needsHeadcount = [];
for (var userID in approvedUserIDMap) {
if (approvedUserIDMap[userID] === false) {
needsHeadcount.push(ObjectId(userID));
}
}
print("Needs headcount:", needsHeadcount.length);
var updateCount = 0;
function insertHeadCount(userID) {
if (!userIDRequestMap[userID.valueOf()]) {
print('ERROR: No trial request ID', userID);
print('Trial course headcount prepaids inserted:', updateCount);
return;
}
generateNewCode(function(code) {
if (!code) {
print("ERROR: no code");
return;
}
criteria = {
creator: userID,
type: 'course',
maxRedeemers: NumberInt(2),
properties: {
trialRequestID: userIDRequestMap[userID.valueOf()]
},
exhausted: false,
__v: NumberInt(0)
};
if (!db.prepaids.findOne(criteria)) {
// print('Adding trial request prepaid for', userID, code);
criteria.code = code;
var writeResult = db.prepaids.insert(criteria);
updateCount += writeResult.nInserted;
}
else {
print('ERROR: Already has trial request headcount', userID, criteria.properties.trialRequestID);
print('Trial course headcount prepaids inserted:', updateCount);
return;
}
if (updateCount < 500 && needsHeadcount.length > 0) {
insertHeadCount(needsHeadcount.pop());
}
else {
print('Trial course headcount prepaids inserted:', updateCount);
}
});
}
if (needsHeadcount.length > 0) {
insertHeadCount(needsHeadcount.pop());
}
}
function generateNewCode(done)
{
function tryCode() {
code = createCode(8);
criteria = {code: code};
if (db.prepaids.findOne(criteria)) {
return tryCode();
}
return done(code);
}
tryCode();
}
function createCode(length)
{
var text = "";
var possible = "abcdefghijklmnopqrstuvwxyz0123456789";
for( var i=0; i < length; i++ ) {
text += possible.charAt(Math.floor(Math.random() * possible.length));
}
return text;
}

View file

@ -57,7 +57,7 @@ CampaignHandler = class CampaignHandler extends Handler
projection = {}
if req.query.project
projection[field] = 1 for field in req.query.project.split(',')
q = @modelClass.find {}, projection
q = @modelClass.find {type: 'hero'}, projection
q.exec (err, documents) =>
return @sendDatabaseError(res, err) if err
formatCampaign = (doc) =>

View file

@ -61,6 +61,7 @@ LevelHandler = class LevelHandler extends Handler
'tasks'
'helpVideos'
'campaign'
'campaignIndex'
'replayable'
'buildTime'
'scoreTypes'