mirror of
https://github.com/codeninjasllc/codecombat.git
synced 2025-02-17 08:50:58 -05:00
Bunch of server changes, mainly adding all the JSON schema validation and fixing tests.
This commit is contained in:
parent
17c1bb7344
commit
c382889748
17 changed files with 45 additions and 22 deletions
|
@ -46,7 +46,7 @@
|
||||||
"mongoose": "3.8.x",
|
"mongoose": "3.8.x",
|
||||||
"mongoose-text-search": "~0.0.2",
|
"mongoose-text-search": "~0.0.2",
|
||||||
"request": "2.12.x",
|
"request": "2.12.x",
|
||||||
"tv4": "1.0.11",
|
"tv4": "1.0.x",
|
||||||
"lodash": "~2.0.0",
|
"lodash": "~2.0.0",
|
||||||
"underscore.string": "2.3.x",
|
"underscore.string": "2.3.x",
|
||||||
"async": "0.2.x",
|
"async": "0.2.x",
|
||||||
|
|
|
@ -4,6 +4,7 @@ Handler = require('../commons/Handler')
|
||||||
ArticleHandler = class ArticleHandler extends Handler
|
ArticleHandler = class ArticleHandler extends Handler
|
||||||
modelClass: Article
|
modelClass: Article
|
||||||
editableProperties: ['body', 'name', 'i18n']
|
editableProperties: ['body', 'name', 'i18n']
|
||||||
|
jsonSchema: require './article_schema'
|
||||||
|
|
||||||
hasAccess: (req) ->
|
hasAccess: (req) ->
|
||||||
req.method is 'GET' or req.user?.isAdmin()
|
req.method is 'GET' or req.user?.isAdmin()
|
||||||
|
|
|
@ -48,6 +48,7 @@ module.exports = class Handler
|
||||||
sendMethodNotAllowed: (res) -> errors.badMethod(res)
|
sendMethodNotAllowed: (res) -> errors.badMethod(res)
|
||||||
sendBadInputError: (res, message) -> errors.badInput(res, message)
|
sendBadInputError: (res, message) -> errors.badInput(res, message)
|
||||||
sendDatabaseError: (res, err) ->
|
sendDatabaseError: (res, err) ->
|
||||||
|
return @sendError(res, err.code, err.response) if err.response and err.code
|
||||||
log.error "Database error, #{err}"
|
log.error "Database error, #{err}"
|
||||||
errors.serverError(res, 'Database error, ' + err)
|
errors.serverError(res, 'Database error, ' + err)
|
||||||
|
|
||||||
|
@ -203,10 +204,9 @@ module.exports = class Handler
|
||||||
return @sendBadInputError(res, 'No input.') if _.isEmpty(req.body)
|
return @sendBadInputError(res, 'No input.') if _.isEmpty(req.body)
|
||||||
return @sendBadInputError(res, 'id should not be included.') if req.body._id
|
return @sendBadInputError(res, 'id should not be included.') if req.body._id
|
||||||
return @sendUnauthorizedError(res) unless @hasAccess(req)
|
return @sendUnauthorizedError(res) unless @hasAccess(req)
|
||||||
validation = @validateDocumentInput(req.body)
|
|
||||||
return @sendBadInputError(res, validation.errors) unless validation.valid
|
|
||||||
document = @makeNewInstance(req)
|
document = @makeNewInstance(req)
|
||||||
@saveChangesToDocument req, document, (err) =>
|
@saveChangesToDocument req, document, (err) =>
|
||||||
|
return @sendBadInputError(res, err.errors) if err?.valid is false
|
||||||
return @sendDatabaseError(res, err) if err
|
return @sendDatabaseError(res, err) if err
|
||||||
@sendSuccess(res, @formatEntity(req, document))
|
@sendSuccess(res, @formatEntity(req, document))
|
||||||
|
|
||||||
|
@ -220,13 +220,11 @@ module.exports = class Handler
|
||||||
return @sendBadInputError(res, 'No input.') if _.isEmpty(req.body)
|
return @sendBadInputError(res, 'No input.') if _.isEmpty(req.body)
|
||||||
return @sendBadInputError(res, 'id should not be included.') if req.body._id
|
return @sendBadInputError(res, 'id should not be included.') if req.body._id
|
||||||
return @sendUnauthorizedError(res) unless @hasAccess(req)
|
return @sendUnauthorizedError(res) unless @hasAccess(req)
|
||||||
validation = @validateDocumentInput(req.body)
|
|
||||||
return @sendBadInputError(res, validation.errors) unless validation.valid
|
|
||||||
document = @makeNewInstance(req)
|
document = @makeNewInstance(req)
|
||||||
document.set('original', document._id)
|
document.set('original', document._id)
|
||||||
document.set('creator', req.user._id)
|
document.set('creator', req.user._id)
|
||||||
@saveChangesToDocument req, document, (err) =>
|
@saveChangesToDocument req, document, (err) =>
|
||||||
return @sendBadInputError(res, err.response) if err?.response
|
return @sendBadInputError(res, err.errors) if err?.valid is false
|
||||||
return @sendDatabaseError(res, err) if err
|
return @sendDatabaseError(res, err) if err
|
||||||
@sendSuccess(res, @formatEntity(req, document))
|
@sendSuccess(res, @formatEntity(req, document))
|
||||||
|
|
||||||
|
@ -245,8 +243,6 @@ module.exports = class Handler
|
||||||
return @sendBadInputError(res, 'This entity is not versioned') unless @modelClass.schema.uses_coco_versions
|
return @sendBadInputError(res, 'This entity is not versioned') unless @modelClass.schema.uses_coco_versions
|
||||||
return @sendBadInputError(res, 'No input.') if _.isEmpty(req.body)
|
return @sendBadInputError(res, 'No input.') if _.isEmpty(req.body)
|
||||||
return @sendUnauthorizedError(res) unless @hasAccess(req)
|
return @sendUnauthorizedError(res) unless @hasAccess(req)
|
||||||
validation = @validateDocumentInput(req.body)
|
|
||||||
return @sendBadInputError(res, validation.errors) unless validation.valid
|
|
||||||
@getDocumentForIdOrSlug req.body._id, (err, parentDocument) =>
|
@getDocumentForIdOrSlug req.body._id, (err, parentDocument) =>
|
||||||
return @sendBadInputError(res, 'Bad id.') if err and err.name is 'CastError'
|
return @sendBadInputError(res, 'Bad id.') if err and err.name is 'CastError'
|
||||||
return @sendDatabaseError(res, err) if err
|
return @sendDatabaseError(res, err) if err
|
||||||
|
@ -261,6 +257,8 @@ module.exports = class Handler
|
||||||
delete updatedObject[prop]
|
delete updatedObject[prop]
|
||||||
delete updatedObject._id
|
delete updatedObject._id
|
||||||
major = req.body.version?.major
|
major = req.body.version?.major
|
||||||
|
validation = @validateDocumentInput(updatedObject)
|
||||||
|
return @sendBadInputError(res, validation.errors) unless validation.valid
|
||||||
|
|
||||||
done = (err, newDocument) =>
|
done = (err, newDocument) =>
|
||||||
return @sendDatabaseError(res, err) if err
|
return @sendDatabaseError(res, err) if err
|
||||||
|
|
|
@ -6,6 +6,7 @@ module.exports.handlers =
|
||||||
'level_feedback': 'levels/feedbacks/level_feedback_handler'
|
'level_feedback': 'levels/feedbacks/level_feedback_handler'
|
||||||
'level_session': 'levels/sessions/level_session_handler'
|
'level_session': 'levels/sessions/level_session_handler'
|
||||||
'level_system': 'levels/systems/level_system_handler'
|
'level_system': 'levels/systems/level_system_handler'
|
||||||
|
'patch': 'patches/patch_handler'
|
||||||
'thang_type': 'levels/thangs/thang_type_handler'
|
'thang_type': 'levels/thangs/thang_type_handler'
|
||||||
'user': 'users/user_handler'
|
'user': 'users/user_handler'
|
||||||
|
|
||||||
|
@ -19,6 +20,7 @@ module.exports.schemas =
|
||||||
'level_session': 'levels/sessions/level_session_schema'
|
'level_session': 'levels/sessions/level_session_schema'
|
||||||
'level_system': 'levels/systems/level_system_schema'
|
'level_system': 'levels/systems/level_system_schema'
|
||||||
'metaschema': 'commons/metaschema'
|
'metaschema': 'commons/metaschema'
|
||||||
|
'patch': 'patches/patch_schema'
|
||||||
'thang_component': 'levels/thangs/thang_component_schema'
|
'thang_component': 'levels/thangs/thang_component_schema'
|
||||||
'thang_type': 'levels/thangs/thang_type_schema'
|
'thang_type': 'levels/thangs/thang_type_schema'
|
||||||
'user': 'users/user_schema'
|
'user': 'users/user_schema'
|
||||||
|
|
|
@ -13,7 +13,7 @@ me.object = (ext, props) -> combine {type: 'object', additionalProperties: false
|
||||||
me.array = (ext, items) -> combine {type: 'array', items: items or {}}, ext
|
me.array = (ext, items) -> combine {type: 'array', items: items or {}}, ext
|
||||||
me.shortString = (ext) -> combine({type: 'string', maxLength: 100}, ext)
|
me.shortString = (ext) -> combine({type: 'string', maxLength: 100}, ext)
|
||||||
me.pct = (ext) -> combine({type: 'number', maximum: 1.0, minimum: 0.0}, ext)
|
me.pct = (ext) -> combine({type: 'number', maximum: 1.0, minimum: 0.0}, ext)
|
||||||
me.date = (ext) -> combine({type: 'string', format: 'date-time'}, ext)
|
me.date = (ext) -> combine({type: ['object', 'string'], format: 'date-time'}, ext)
|
||||||
# should just be string (Mongo ID), but sometimes mongoose turns them into objects representing those, so we are lenient
|
# should just be string (Mongo ID), but sometimes mongoose turns them into objects representing those, so we are lenient
|
||||||
me.objectId = (ext) -> schema = combine({type: ['object', 'string'] }, ext)
|
me.objectId = (ext) -> schema = combine({type: ['object', 'string'] }, ext)
|
||||||
|
|
||||||
|
@ -52,6 +52,17 @@ me.extendBasicProperties = (schema, linkFragment) ->
|
||||||
schema.properties = {} unless schema.properties?
|
schema.properties = {} unless schema.properties?
|
||||||
_.extend(schema.properties, basicProps(linkFragment))
|
_.extend(schema.properties, basicProps(linkFragment))
|
||||||
|
|
||||||
|
# PATCHABLE
|
||||||
|
|
||||||
|
patchableProps = ->
|
||||||
|
patches: me.array({title:'Patches'}, {
|
||||||
|
_id: me.objectId(links: [{rel: "db", href: "/db/patch/{($)}"}], title: "Patch ID", description: "A reference to the patch.")
|
||||||
|
status: { enum: ['pending', 'accepted', 'rejected', 'cancelled']}
|
||||||
|
})
|
||||||
|
|
||||||
|
me.extendPatchableProperties = (schema) ->
|
||||||
|
schema.properties = {} unless schema.properties?
|
||||||
|
_.extend(schema.properties, patchableProps())
|
||||||
|
|
||||||
# NAMED
|
# NAMED
|
||||||
|
|
||||||
|
|
|
@ -3,6 +3,7 @@ Handler = require('../../commons/Handler')
|
||||||
|
|
||||||
LevelComponentHandler = class LevelComponentHandler extends Handler
|
LevelComponentHandler = class LevelComponentHandler extends Handler
|
||||||
modelClass: LevelComponent
|
modelClass: LevelComponent
|
||||||
|
jsonSchema: require './level_component_schema'
|
||||||
editableProperties: [
|
editableProperties: [
|
||||||
'system'
|
'system'
|
||||||
'description'
|
'description'
|
||||||
|
|
|
@ -4,6 +4,7 @@ Handler = require('../../commons/Handler')
|
||||||
class LevelFeedbackHandler extends Handler
|
class LevelFeedbackHandler extends Handler
|
||||||
modelClass: LevelFeedback
|
modelClass: LevelFeedback
|
||||||
editableProperties: ['rating', 'review', 'level', 'levelID', 'levelName']
|
editableProperties: ['rating', 'review', 'level', 'levelID', 'levelName']
|
||||||
|
jsonSchema: require './level_feedback_schema'
|
||||||
|
|
||||||
makeNewInstance: (req) ->
|
makeNewInstance: (req) ->
|
||||||
feedback = super(req)
|
feedback = super(req)
|
||||||
|
|
|
@ -8,6 +8,7 @@ mongoose = require('mongoose')
|
||||||
|
|
||||||
LevelHandler = class LevelHandler extends Handler
|
LevelHandler = class LevelHandler extends Handler
|
||||||
modelClass: Level
|
modelClass: Level
|
||||||
|
jsonSchema: require './level_schema'
|
||||||
editableProperties: [
|
editableProperties: [
|
||||||
'description'
|
'description'
|
||||||
'documentation'
|
'documentation'
|
||||||
|
|
|
@ -9,6 +9,7 @@ class LevelSessionHandler extends Handler
|
||||||
editableProperties: ['multiplayer', 'players', 'code', 'completed', 'state',
|
editableProperties: ['multiplayer', 'players', 'code', 'completed', 'state',
|
||||||
'levelName', 'creatorName', 'levelID', 'screenshot',
|
'levelName', 'creatorName', 'levelID', 'screenshot',
|
||||||
'chat', 'teamSpells', 'submitted', 'unsubscribed']
|
'chat', 'teamSpells', 'submitted', 'unsubscribed']
|
||||||
|
jsonSchema: require './level_session_schema'
|
||||||
|
|
||||||
getByRelationship: (req, res, args...) ->
|
getByRelationship: (req, res, args...) ->
|
||||||
return @getActiveSessions req, res if args.length is 2 and args[1] is 'active'
|
return @getActiveSessions req, res if args.length is 2 and args[1] is 'active'
|
||||||
|
|
|
@ -13,6 +13,7 @@ LevelSystemHandler = class LevelSystemHandler extends Handler
|
||||||
'configSchema'
|
'configSchema'
|
||||||
]
|
]
|
||||||
postEditableProperties: ['name']
|
postEditableProperties: ['name']
|
||||||
|
jsonSchema: require './level_system_schema'
|
||||||
|
|
||||||
getEditableProperties: (req, document) ->
|
getEditableProperties: (req, document) ->
|
||||||
props = super(req, document)
|
props = super(req, document)
|
||||||
|
|
|
@ -3,6 +3,7 @@ Handler = require('../../commons/Handler')
|
||||||
|
|
||||||
ThangTypeHandler = class ThangTypeHandler extends Handler
|
ThangTypeHandler = class ThangTypeHandler extends Handler
|
||||||
modelClass: ThangType
|
modelClass: ThangType
|
||||||
|
jsonSchema: require './thang_type_schema'
|
||||||
editableProperties: [
|
editableProperties: [
|
||||||
'name',
|
'name',
|
||||||
'raw',
|
'raw',
|
||||||
|
|
|
@ -42,6 +42,7 @@ module.exports.setup = (app) ->
|
||||||
catch error
|
catch error
|
||||||
log.error("Error trying db method #{req.route.method} route #{parts} from #{name}: #{error}")
|
log.error("Error trying db method #{req.route.method} route #{parts} from #{name}: #{error}")
|
||||||
log.error(error)
|
log.error(error)
|
||||||
|
log.error(error.stack)
|
||||||
errors.notFound(res, "Route #{req.path} not found.")
|
errors.notFound(res, "Route #{req.path} not found.")
|
||||||
|
|
||||||
getSchema = (req, res, moduleName) ->
|
getSchema = (req, res, moduleName) ->
|
||||||
|
@ -49,7 +50,7 @@ getSchema = (req, res, moduleName) ->
|
||||||
name = schemas[moduleName.replace '.', '_']
|
name = schemas[moduleName.replace '.', '_']
|
||||||
schema = require('../' + name)
|
schema = require('../' + name)
|
||||||
|
|
||||||
res.send(schema)
|
res.send(JSON.stringify(schema, null, '\t'))
|
||||||
res.end()
|
res.end()
|
||||||
|
|
||||||
catch error
|
catch error
|
||||||
|
|
|
@ -78,12 +78,9 @@ unittest.getUser = (email, password, done, force) ->
|
||||||
req = request.post(getURL('/db/user'), (err, response, body) ->
|
req = request.post(getURL('/db/user'), (err, response, body) ->
|
||||||
throw err if err
|
throw err if err
|
||||||
User.findOne({email:email}).exec((err, user) ->
|
User.findOne({email:email}).exec((err, user) ->
|
||||||
if password is '80yqxpb38j'
|
user.set('permissions', if password is '80yqxpb38j' then [ 'admin' ] else [])
|
||||||
user.set('permissions', [ 'admin' ])
|
|
||||||
user.save (err) ->
|
user.save (err) ->
|
||||||
wrapUpGetUser(email, user, done)
|
wrapUpGetUser(email, user, done)
|
||||||
else
|
|
||||||
wrapUpGetUser(email, user, done)
|
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
form = req.form()
|
form = req.form()
|
||||||
|
|
|
@ -55,7 +55,7 @@ describe '/auth/login', ->
|
||||||
it 'rejects wrong passwords', (done) ->
|
it 'rejects wrong passwords', (done) ->
|
||||||
req = request.post(urlLogin, (error, response) ->
|
req = request.post(urlLogin, (error, response) ->
|
||||||
expect(response.statusCode).toBe(401)
|
expect(response.statusCode).toBe(401)
|
||||||
expect(response.body.indexOf("wrong, wrong")).toBeGreaterThan(-1)
|
expect(response.body.indexOf("wrong")).toBeGreaterThan(-1)
|
||||||
done()
|
done()
|
||||||
)
|
)
|
||||||
form = req.form()
|
form = req.form()
|
||||||
|
@ -96,7 +96,6 @@ describe '/auth/reset', ->
|
||||||
it 'resets user password', (done) ->
|
it 'resets user password', (done) ->
|
||||||
req = request.post(urlReset, (error, response) ->
|
req = request.post(urlReset, (error, response) ->
|
||||||
expect(response).toBeDefined()
|
expect(response).toBeDefined()
|
||||||
console.log 'status code is', response.statusCode
|
|
||||||
expect(response.statusCode).toBe(200)
|
expect(response.statusCode).toBe(200)
|
||||||
expect(response.body).toBeDefined()
|
expect(response.body).toBeDefined()
|
||||||
passwordReset = response.body
|
passwordReset = response.body
|
||||||
|
|
|
@ -6,6 +6,9 @@ describe 'Level', ->
|
||||||
name: "King's Peak 3"
|
name: "King's Peak 3"
|
||||||
description: 'Climb a mountain.'
|
description: 'Climb a mountain.'
|
||||||
permissions: simplePermissions
|
permissions: simplePermissions
|
||||||
|
scripts: []
|
||||||
|
thangs: []
|
||||||
|
documentation: {specificArticles:[], generalArticles:[]}
|
||||||
|
|
||||||
urlLevel = '/db/level'
|
urlLevel = '/db/level'
|
||||||
|
|
||||||
|
|
|
@ -3,11 +3,14 @@ require '../common'
|
||||||
describe 'LevelComponent', ->
|
describe 'LevelComponent', ->
|
||||||
|
|
||||||
component =
|
component =
|
||||||
name:'Bashes Everything'
|
name:'BashesEverything'
|
||||||
description:'Makes the unit uncontrollably bash anything bashable, using the bash system.'
|
description:'Makes the unit uncontrollably bash anything bashable, using the bash system.'
|
||||||
code: 'bash();'
|
code: 'bash();'
|
||||||
language: 'javascript'
|
language: 'coffeescript'
|
||||||
permissions:simplePermissions
|
permissions:simplePermissions
|
||||||
|
propertyDocumentation: []
|
||||||
|
system: 'ai'
|
||||||
|
dependencies: []
|
||||||
|
|
||||||
components = {}
|
components = {}
|
||||||
|
|
||||||
|
@ -45,7 +48,7 @@ describe 'LevelComponent', ->
|
||||||
it 'have a unique name.', (done) ->
|
it 'have a unique name.', (done) ->
|
||||||
loginAdmin ->
|
loginAdmin ->
|
||||||
request.post {uri:url, json:component}, (err, res, body) ->
|
request.post {uri:url, json:component}, (err, res, body) ->
|
||||||
expect(res.statusCode).toBe(422)
|
expect(res.statusCode).toBe(409)
|
||||||
done()
|
done()
|
||||||
|
|
||||||
it 'can be read by an admin.', (done) ->
|
it 'can be read by an admin.', (done) ->
|
||||||
|
|
|
@ -11,6 +11,8 @@ describe 'LevelSystem', ->
|
||||||
"""
|
"""
|
||||||
language: 'coffeescript'
|
language: 'coffeescript'
|
||||||
permissions:simplePermissions
|
permissions:simplePermissions
|
||||||
|
dependencies: []
|
||||||
|
propertyDocumentation: []
|
||||||
|
|
||||||
systems = {}
|
systems = {}
|
||||||
|
|
||||||
|
@ -48,7 +50,7 @@ describe 'LevelSystem', ->
|
||||||
it 'have a unique name.', (done) ->
|
it 'have a unique name.', (done) ->
|
||||||
loginAdmin ->
|
loginAdmin ->
|
||||||
request.post {uri:url, json:system}, (err, res, body) ->
|
request.post {uri:url, json:system}, (err, res, body) ->
|
||||||
expect(res.statusCode).toBe(422)
|
expect(res.statusCode).toBe(409)
|
||||||
done()
|
done()
|
||||||
|
|
||||||
it 'can be read by an admin.', (done) ->
|
it 'can be read by an admin.', (done) ->
|
||||||
|
|
Loading…
Reference in a new issue