mirror of
https://github.com/codeninjasllc/codecombat.git
synced 2024-11-27 09:35:39 -05:00
Search endpoint is now without the /search prefix
Intermediate commit for search refactoring
This commit is contained in:
parent
77920b5416
commit
0751581113
8 changed files with 98 additions and 68 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()
|
||||
|
|
|
@ -63,26 +63,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
|
||||
|
@ -153,44 +199,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.
|
||||
|
|
|
@ -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]?
|
||||
|
|
|
@ -92,4 +92,33 @@ describe '/db/article', ->
|
|||
expect(res.statusCode).toBe(422)
|
||||
done()
|
||||
|
||||
|
||||
it 'allows regular users to get all articles', (done) ->
|
||||
loginJoe ->
|
||||
request.get {uri:url}, (err, res, body) ->
|
||||
expect(res.statusCode).toBe(200)
|
||||
expect(body.length).toBe(2)
|
||||
|
||||
it 'allows regular users to get articles and use projection', (done) ->
|
||||
loginJoe ->
|
||||
# default projection
|
||||
request.get {uri:url + '?project=true'}, (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'}, (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="friend"'}, (err, res, body) ->
|
||||
expect(res.statusCode).toBe(200)
|
||||
expect(body.length).toBe(1)
|
||||
# expect name blabla
|
||||
done()
|
||||
|
|
Loading…
Reference in a new issue