mirror of
https://github.com/codeninjasllc/codecombat.git
synced 2024-11-29 18:45:48 -05:00
Merge branch 'master' into production
This commit is contained in:
commit
c1948ab083
41 changed files with 299 additions and 80 deletions
|
@ -15,8 +15,10 @@ before_script:
|
|||
- "./node_modules/.bin/bower install"
|
||||
- "gem install sass"
|
||||
- "./node_modules/.bin/brunch b"
|
||||
- "./bin/coco-mongodb fork"
|
||||
- "mkdir mongo"
|
||||
- "mongod --dbpath=./mongo --fork --logpath ./mongodb.log"
|
||||
- "node index.js --unittest &"
|
||||
- "sleep 5" # to give node a chance to start
|
||||
|
||||
script:
|
||||
- "./node_modules/jasmine-node/bin/jasmine-node test/server/ --coffee --captureExceptions"
|
||||
|
|
|
@ -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
|
||||
})
|
||||
|
|
|
@ -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 = {}
|
||||
|
|
|
@ -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}))
|
||||
|
||||
|
|
|
@ -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"
|
||||
]
|
||||
|
|
|
@ -294,6 +294,7 @@
|
|||
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?"
|
||||
|
@ -867,6 +868,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"
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -170,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') + "&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
|
||||
|
|
|
@ -57,9 +57,9 @@ block content
|
|||
strong= act.count
|
||||
|
|
||||
br
|
||||
span= moment(activity.login.first).fromNow()
|
||||
span= moment(act.first).fromNow()
|
||||
br
|
||||
span= moment(activity.login.last).fromNow()
|
||||
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()
|
||||
|
|
|
@ -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()
|
||||
|
||||
|
@ -63,6 +69,7 @@ module.exports = class ProfileView extends View
|
|||
$.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
|
||||
|
@ -80,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
|
||||
|
||||
|
@ -217,6 +224,8 @@ 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
|
||||
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)
|
||||
context
|
||||
|
||||
afterRender: ->
|
||||
|
@ -303,7 +312,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 =>
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -52,6 +52,7 @@ module.exports = class EmployersView extends View
|
|||
@listenToOnce @candidates, 'all', @renderCandidatesAndSetupScrolling
|
||||
|
||||
renderCandidatesAndSetupScrolling: =>
|
||||
|
||||
@render()
|
||||
$(".nano").nanoScroller()
|
||||
if window.history?.state?.lastViewedCandidateID
|
||||
|
|
|
@ -150,7 +150,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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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')
|
||||
|
|
|
@ -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'
|
||||
|
|
|
@ -43,7 +43,7 @@ module.exports = class LadderView extends RootView
|
|||
|
||||
onLoaded: ->
|
||||
@teams = teamDataFromLevel @level
|
||||
@render()
|
||||
super()
|
||||
|
||||
getRenderData: ->
|
||||
ctx = super()
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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: ->
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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', {}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
mongoose = require('mongoose')
|
||||
plugins = require('../plugins/plugins')
|
||||
jsonschema = require('../../app/schemas/models/achievement')
|
||||
log = require 'winston'
|
||||
|
||||
|
@ -29,7 +28,9 @@ AchievementSchema.pre('save', (next) ->
|
|||
next()
|
||||
)
|
||||
|
||||
module.exports = Achievement = mongoose.model('Achievement', AchievementSchema)
|
||||
|
||||
plugins = require('../plugins/plugins')
|
||||
|
||||
AchievementSchema.plugin(plugins.NamedPlugin)
|
||||
AchievementSchema.plugin(plugins.SearchablePlugin, {searchable: ['name']})
|
||||
|
||||
module.exports = Achievement = mongoose.model('Achievement', AchievementSchema)
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -7,7 +7,7 @@ LevelSystemHandler = class LevelSystemHandler extends Handler
|
|||
'description'
|
||||
'code'
|
||||
'js'
|
||||
'language'
|
||||
'codeLanguage'
|
||||
'dependencies'
|
||||
'propertyDocumentation'
|
||||
'configSchema'
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
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'
|
||||
|
@ -19,6 +18,8 @@ loadAchievements = ->
|
|||
loadAchievements()
|
||||
|
||||
module.exports = AchievablePlugin = (schema, options) ->
|
||||
User = require '../users/User'
|
||||
|
||||
checkForAchievement = (doc) ->
|
||||
collectionName = doc.constructor.modelName
|
||||
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
mongoose = require('mongoose')
|
||||
User = require('../users/User')
|
||||
textSearch = require('mongoose-text-search')
|
||||
|
||||
module.exports.MigrationPlugin = (schema, migrations) ->
|
||||
|
|
|
@ -62,7 +62,6 @@ module.exports.setup = (app) ->
|
|||
req.logIn(user, (err) ->
|
||||
return next(err) if (err)
|
||||
activity = req.user.trackActivity 'login', 1
|
||||
console.log "updating", activity
|
||||
user.update {activity: activity}, (err) ->
|
||||
return next(err) if (err)
|
||||
res.send(UserHandler.formatEntity(req, req.user))
|
||||
|
|
|
@ -189,6 +189,7 @@ 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'
|
||||
|
@ -227,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 = {}
|
||||
|
|
|
@ -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) ->
|
||||
|
|
80
test/app/lib/FacebookHandler.spec.coffee
Normal file
80
test/app/lib/FacebookHandler.spec.coffee
Normal 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()
|
84
test/app/models/CocoModel.spec.coffee
Normal file
84
test/app/models/CocoModel.spec.coffee
Normal 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()
|
|
@ -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())
|
||||
|
@ -149,3 +153,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
|
|
@ -6,9 +6,8 @@ urlLogin = getURL('/auth/login')
|
|||
urlReset = getURL('/auth/reset')
|
||||
|
||||
describe '/auth/whoami', ->
|
||||
http = require 'http'
|
||||
it 'returns 200', (done) ->
|
||||
http.get(getURL('/auth/whoami'), (response) ->
|
||||
request.get(getURL('/auth/whoami'), (err, response) ->
|
||||
expect(response).toBeDefined()
|
||||
expect(response.statusCode).toBe(200)
|
||||
done()
|
||||
|
|
|
@ -1,12 +1,16 @@
|
|||
require '../common'
|
||||
|
||||
describe '/file', ->
|
||||
# Doesn't work on Travis. Need to figure out why, probably by having the
|
||||
# url not depend on some external resource.
|
||||
|
||||
xdescribe '/file', ->
|
||||
url = getURL('/file')
|
||||
files = []
|
||||
options = {
|
||||
uri:url
|
||||
json: {
|
||||
url: 'http://scotterickson.info/images/where-are-you.jpg'
|
||||
# url: 'http://scotterickson.info/images/where-are-you.jpg'
|
||||
url: 'http://fc07.deviantart.net/fs37/f/2008/283/5/1/Chu_Chu_Pikachu_by_angelishi.gif'
|
||||
filename: 'where-are-you.jpg'
|
||||
mimetype: 'image/jpeg'
|
||||
description: 'None!'
|
||||
|
@ -20,7 +24,8 @@ describe '/file', ->
|
|||
filename: 'ittybitty.data'
|
||||
mimetype: 'application/octet-stream'
|
||||
description: 'rando-info'
|
||||
my_buffer_url: 'http://scotterickson.info/images/where-are-you.jpg'
|
||||
# my_buffer_url: 'http://scotterickson.info/images/where-are-you.jpg'
|
||||
my_buffer_url: 'http://fc07.deviantart.net/fs37/f/2008/283/5/1/Chu_Chu_Pikachu_by_angelishi.gif'
|
||||
}
|
||||
|
||||
it 'preparing test : deletes all the files first', (done) ->
|
||||
|
|
|
@ -18,13 +18,3 @@ describe 'Level', ->
|
|||
level.save (err) ->
|
||||
throw err if err
|
||||
done()
|
||||
|
||||
it 'loads again after being saved', (done) ->
|
||||
url = getURL('/db/level/'+level._id)
|
||||
request.get url, (err, res, body) ->
|
||||
expect(res.statusCode).toBe(200)
|
||||
sameLevel = JSON.parse(body)
|
||||
expect(sameLevel.name).toEqual(level.get 'name')
|
||||
expect(sameLevel.description).toEqual(level.get 'description')
|
||||
expect(sameLevel.permissions).toEqual(simplePermissions)
|
||||
done()
|
||||
|
|
Loading…
Reference in a new issue