mirror of
https://github.com/codeninjasllc/codecombat.git
synced 2025-03-14 07:00:01 -04:00
Merge pull request #1138 from rubenvereecken/search-endpoint
Search endpoint is now without the /search prefix
This commit is contained in:
commit
8ab69cf833
9 changed files with 118 additions and 73 deletions
|
@ -4,7 +4,7 @@ ThangType = require 'models/ThangType'
|
|||
CocoCollection = require 'collections/CocoCollection'
|
||||
|
||||
class ThangTypeSearchCollection extends CocoCollection
|
||||
url: '/db/thang.type/search?project=true'
|
||||
url: '/db/thang.type?project=true'
|
||||
model: ThangType
|
||||
|
||||
addTerm: (term) ->
|
||||
|
@ -73,4 +73,4 @@ module.exports = class AddThangsView extends View
|
|||
|
||||
onEscapePressed: ->
|
||||
@$el.find('input#thang-search').val("")
|
||||
@runSearch
|
||||
@runSearch
|
||||
|
|
|
@ -5,7 +5,7 @@ LevelSystem = require 'models/LevelSystem'
|
|||
CocoCollection = require 'collections/CocoCollection'
|
||||
|
||||
class LevelSystemSearchCollection extends CocoCollection
|
||||
url: '/db/level_system/search'
|
||||
url: '/db/level_system'
|
||||
model: LevelSystem
|
||||
|
||||
module.exports = class LevelSystemAddView extends View
|
||||
|
|
|
@ -21,7 +21,7 @@ componentOriginals =
|
|||
"physics.Physical" : "524b75ad7fc0f6d519000001"
|
||||
|
||||
class ThangTypeSearchCollection extends CocoCollection
|
||||
url: '/db/thang.type/search?project=original,name,version,slug,kind,components'
|
||||
url: '/db/thang.type?project=original,name,version,slug,kind,components'
|
||||
model: ThangType
|
||||
|
||||
module.exports = class ThangsTabView extends View
|
||||
|
|
|
@ -5,7 +5,7 @@ app = require('application')
|
|||
|
||||
class SearchCollection extends Backbone.Collection
|
||||
initialize: (modelURL, @model, @term, @projection) ->
|
||||
@url = "#{modelURL}/search?project="
|
||||
@url = "#{modelURL}?project="
|
||||
if @projection? and not (@projection == [])
|
||||
@url += projection[0]
|
||||
@url += ',' + projected for projected in projection[1..]
|
||||
|
|
|
@ -11,11 +11,5 @@ class AchievementHandler extends Handler
|
|||
hasAccess: (req) ->
|
||||
req.method is 'GET' or req.user?.isAdmin()
|
||||
|
||||
get: (req, res) ->
|
||||
query = @modelClass.find({})
|
||||
query.exec (err, documents) =>
|
||||
return @sendDatabaseError(res, err) if err
|
||||
documents = (@formatEntity(req, doc) for doc in documents)
|
||||
@sendSuccess(res, documents)
|
||||
|
||||
module.exports = new AchievementHandler()
|
||||
|
|
|
@ -64,26 +64,72 @@ module.exports = class Handler
|
|||
|
||||
# generic handlers
|
||||
get: (req, res) ->
|
||||
# by default, ordinary users never get unfettered access to the database
|
||||
return @sendUnauthorizedError(res) unless req.user?.isAdmin()
|
||||
@sendUnauthorizedError(res) if not @hasAccess(req)
|
||||
|
||||
# admins can send any sort of query down the wire, though
|
||||
conditions = JSON.parse(req.query.conditions || '[]')
|
||||
query = @modelClass.find()
|
||||
specialParameters = ['term', 'project', 'conditions']
|
||||
|
||||
try
|
||||
for condition in conditions
|
||||
name = condition[0]
|
||||
f = query[name]
|
||||
args = condition[1..]
|
||||
query = query[name](args...)
|
||||
catch e
|
||||
return @sendError(res, 422, 'Badly formed conditions.')
|
||||
# If the model uses coco search it's probably a text search
|
||||
if @modelClass.schema.uses_coco_search
|
||||
term = req.query.term
|
||||
matchedObjects = []
|
||||
filters = if @modelClass.schema.uses_coco_versions or @modelClass.schema.uses_coco_permissions then [filter: {index: true}] else [filter: {}]
|
||||
if @modelClass.schema.uses_coco_permissions and req.user
|
||||
filters.push {filter: {index: req.user.get('id')}}
|
||||
projection = null
|
||||
if req.query.project is 'true'
|
||||
projection = PROJECT
|
||||
else if req.query.project
|
||||
if @modelClass.className is 'User'
|
||||
projection = PROJECT
|
||||
log.warn "Whoa, we haven't yet thought about public properties for User projection yet."
|
||||
else
|
||||
projection = {}
|
||||
projection[field] = 1 for field in req.query.project.split(',')
|
||||
for filter in filters
|
||||
callback = (err, results) =>
|
||||
return @sendDatabaseError(res, err) if err
|
||||
for r in results.results ? results
|
||||
obj = r.obj ? r
|
||||
continue if obj in matchedObjects # TODO: probably need a better equality check
|
||||
matchedObjects.push obj
|
||||
filters.pop() # doesn't matter which one
|
||||
unless filters.length
|
||||
res.send matchedObjects
|
||||
res.end()
|
||||
if term
|
||||
filter.project = projection
|
||||
@modelClass.textSearch term, filter, callback
|
||||
else
|
||||
args = [filter.filter]
|
||||
args.push projection if projection
|
||||
@modelClass.find(args...).limit(FETCH_LIMIT).exec callback
|
||||
# if it's not a text search but the user is an admin, let him try stuff anyway
|
||||
else if req.user?.isAdmin()
|
||||
# admins can send any sort of query down the wire
|
||||
filter = {}
|
||||
filter[key] = (val for own key, val of req.query.filter when key not in specialParameters) if 'filter' of req.query
|
||||
|
||||
query = @modelClass.find(filter)
|
||||
|
||||
# Conditions are chained query functions, for example: query.find().limit(20).sort('-dateCreated')
|
||||
conditions = JSON.parse(req.query.conditions || '[]')
|
||||
try
|
||||
for condition in conditions
|
||||
name = condition[0]
|
||||
f = query[name]
|
||||
args = condition[1..]
|
||||
query = query[name](args...)
|
||||
catch e
|
||||
return @sendError(res, 422, 'Badly formed conditions.')
|
||||
|
||||
query.exec (err, documents) =>
|
||||
return @sendDatabaseError(res, err) if err
|
||||
documents = (@formatEntity(req, doc) for doc in documents)
|
||||
@sendSuccess(res, documents)
|
||||
# regular users are only allowed text searches for now, without any additional filters or sorting
|
||||
else
|
||||
return @sendUnauthorizedError(res)
|
||||
|
||||
query.exec (err, documents) =>
|
||||
return @sendDatabaseError(res, err) if err
|
||||
documents = (@formatEntity(req, doc) for doc in documents)
|
||||
@sendSuccess(res, documents)
|
||||
|
||||
getById: (req, res, id) ->
|
||||
# return @sendNotFoundError(res) # for testing
|
||||
|
@ -154,44 +200,6 @@ module.exports = class Handler
|
|||
return @sendDatabaseError(res, err) if err
|
||||
@sendSuccess(res, @formatEntity(req, document))
|
||||
|
||||
# project=true or project=name,description,slug for example
|
||||
search: (req, res) ->
|
||||
unless @modelClass.schema.uses_coco_search
|
||||
return @sendNotFoundError(res)
|
||||
term = req.query.term
|
||||
matchedObjects = []
|
||||
filters = if @modelClass.schema.uses_coco_versions or @modelClass.schema.uses_coco_permissions then [filter: {index: true}] else [filter: {}]
|
||||
if @modelClass.schema.uses_coco_permissions and req.user
|
||||
filters.push {filter: {index: req.user.get('id')}}
|
||||
projection = null
|
||||
if req.query.project is 'true'
|
||||
projection = PROJECT
|
||||
else if req.query.project
|
||||
if @modelClass.className is 'User'
|
||||
projection = PROJECT
|
||||
log.warn "Whoa, we haven't yet thought about public properties for User projection yet."
|
||||
else
|
||||
projection = {}
|
||||
projection[field] = 1 for field in req.query.project.split(',')
|
||||
for filter in filters
|
||||
callback = (err, results) =>
|
||||
return @sendDatabaseError(res, err) if err
|
||||
for r in results.results ? results
|
||||
obj = r.obj ? r
|
||||
continue if obj in matchedObjects # TODO: probably need a better equality check
|
||||
matchedObjects.push obj
|
||||
filters.pop() # doesn't matter which one
|
||||
unless filters.length
|
||||
res.send matchedObjects
|
||||
res.end()
|
||||
if term
|
||||
filter.project = projection
|
||||
@modelClass.textSearch term, filter, callback
|
||||
else
|
||||
args = [filter.filter]
|
||||
args.push projection if projection
|
||||
@modelClass.find(args...).limit(FETCH_LIMIT).exec callback
|
||||
|
||||
versions: (req, res, id) ->
|
||||
# TODO: a flexible system for doing GAE-like cursors for these sort of paginating queries
|
||||
# Keeping it simple for now and just allowing access to the first FETCH_LIMIT results.
|
||||
|
|
|
@ -23,7 +23,7 @@ module.exports.PatchablePlugin = (schema) ->
|
|||
schema.is_patchable = true
|
||||
schema.index({'target.original':1, 'status':'1', 'created':-1})
|
||||
|
||||
RESERVED_NAMES = ['search', 'names']
|
||||
RESERVED_NAMES = ['names']
|
||||
|
||||
module.exports.NamedPlugin = (schema) ->
|
||||
schema.uses_coco_names = true
|
||||
|
|
|
@ -34,7 +34,6 @@ module.exports.setup = (app) ->
|
|||
return handler.getLatestVersion(req, res, parts[1], parts[3]) if parts[2] is 'version'
|
||||
return handler.versions(req, res, parts[1]) if parts[2] is 'versions'
|
||||
return handler.files(req, res, parts[1]) if parts[2] is 'files'
|
||||
return handler.search(req, res) if req.route.method is 'get' and parts[1] is 'search'
|
||||
return handler.getNamesByIDs(req, res) if req.route.method in ['get', 'post'] and parts[1] is 'names'
|
||||
return handler.getByRelationship(req, res, parts[1..]...) if parts.length > 2
|
||||
return handler.getById(req, res, parts[1]) if req.route.method is 'get' and parts[1]?
|
||||
|
|
|
@ -8,6 +8,8 @@ describe '/db/article', ->
|
|||
done()
|
||||
|
||||
article = {name: 'Yo', body:'yo ma'}
|
||||
article2 = {name: 'Original', body:'yo daddy'}
|
||||
|
||||
url = getURL('/db/article')
|
||||
articles = {}
|
||||
|
||||
|
@ -27,11 +29,22 @@ describe '/db/article', ->
|
|||
expect(body.original).toBeDefined()
|
||||
expect(body.creator).toBeDefined()
|
||||
articles[0] = body
|
||||
done()
|
||||
|
||||
# Having two articles allow for testing article search and such
|
||||
request.post {uri:url, json:article2}, (err, res, body) ->
|
||||
expect(res.statusCode).toBe(200)
|
||||
expect(body.slug).toBeDefined()
|
||||
expect(body.body).toBeDefined()
|
||||
expect(body.name).toBeDefined()
|
||||
expect(body.original).toBeDefined()
|
||||
expect(body.creator).toBeDefined()
|
||||
articles[0] = body
|
||||
|
||||
done()
|
||||
|
||||
it 'allows admins to make new minor versions', (done) ->
|
||||
new_article = _.clone(articles[0])
|
||||
new_article.body = '...'
|
||||
new_article.body = 'yo daddy'
|
||||
request.post {uri:url, json:new_article}, (err, res, body) ->
|
||||
expect(res.statusCode).toBe(200)
|
||||
expect(body.version.major).toBe(0)
|
||||
|
@ -61,7 +74,6 @@ describe '/db/article', ->
|
|||
expect(res.statusCode).toBe(200)
|
||||
expect(body.body).toBe(articles[0].body)
|
||||
done()
|
||||
|
||||
|
||||
it 'does not allow regular users to make new versions', (done) ->
|
||||
new_article = _.clone(articles[2])
|
||||
|
@ -87,9 +99,41 @@ describe '/db/article', ->
|
|||
|
||||
it 'does not allow naming an article a reserved word', (done) ->
|
||||
loginAdmin ->
|
||||
new_article = {name: 'Search', body:'is a reserved word'}
|
||||
new_article = {name: 'Names', body:'is a reserved word'}
|
||||
request.post {uri:url, json:new_article}, (err, res, body) ->
|
||||
expect(res.statusCode).toBe(422)
|
||||
done()
|
||||
|
||||
|
||||
it 'allows regular users to get all articles', (done) ->
|
||||
loginJoe ->
|
||||
request.get {uri:url, json:{}}, (err, res, body) ->
|
||||
expect(res.statusCode).toBe(200)
|
||||
expect(body.length).toBe(2)
|
||||
done()
|
||||
|
||||
it 'allows regular users to get articles and use projection', (done) ->
|
||||
loginJoe ->
|
||||
# default projection
|
||||
request.get {uri:url + '?project=true', json:{}}, (err, res, body) ->
|
||||
expect(res.statusCode).toBe(200)
|
||||
expect(body.length).toBe(2)
|
||||
expect(body[0].created).toBeUndefined()
|
||||
expect(body[0].version).toBeDefined()
|
||||
|
||||
# custom projection
|
||||
request.get {uri:url + '?project=original', json:{}}, (err, res, body) ->
|
||||
expect(res.statusCode).toBe(200)
|
||||
expect(body.length).toBe(2)
|
||||
expect(Object.keys(body[0]).length).toBe(2)
|
||||
expect(body[0].original).toBeDefined()
|
||||
done()
|
||||
|
||||
it 'allows regular users to perform a text search', (done) ->
|
||||
loginJoe ->
|
||||
request.get {uri:url + '?term="daddy"', json:{}}, (err, res, body) ->
|
||||
expect(res.statusCode).toBe(200)
|
||||
expect(body.length).toBe(1)
|
||||
expect(body[0].name).toBe(article2.name)
|
||||
expect(body[0].body).toBe(article2.body)
|
||||
done()
|
||||
|
||||
|
|
Loading…
Reference in a new issue