Skip to content

Commit

Permalink
Merge pull request #4 from wajez/add-total-count
Browse files Browse the repository at this point in the history
adding `Content-Total` to response of `list` and `showManyRelated` routes
  • Loading branch information
webNeat committed May 30, 2018
2 parents 47c73b6 + 8c17f22 commit eef5fa9
Show file tree
Hide file tree
Showing 13 changed files with 192 additions and 55 deletions.
16 changes: 14 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,7 @@ app.use((err, req, res, next) => {

That's all, we now have a functional REST API with the following routes and features:

- `GET /users`: returns an array of users with format `{id, name, type, email, password}`. The query parameters `offset`, `limit` and `sort` can be used to sort by a field, specify the range of users to return. By default `offset = 0` and `limit = 100`. The query parameter `where` can be used to filter results.
- `GET /users`: returns an array of users with format `{id, name, type, email, password}`. The response header `Content-Total` will contain the total count of results. The query parameters `offset`, `limit` and `sort` can be used to sort by a field, specify the range of users to return. By default `offset = 0` and `limit = 100`. The query parameter `where` can be used to filter results.
- `GET /users/:id`: returns the single user having the `id` or `null` if not found.
- `GET /users/:id/posts`: returns the list of posts of a specific user. The query parameters `offset`, `limit` and `sort` are supported.
- `POST /users`: adds and returns a new user with the data in `req.body`. Giving the `posts` attribute as array of ids will update the corresponding posts to use the added user as their `writer`. if some of the posts are missing or have already a `writer`, an error is returned.
Expand Down Expand Up @@ -215,7 +215,7 @@ Let's start by listing the defined data types in this library.

### Query Types

- `Query`: one of `CreateQuery`, `FindQuery`, `UpdateQuery`, and `RemoveQuery`.
- `Query`: one of `CreateQuery`, `FindQuery`, `CountQuery`, `UpdateQuery`, and `RemoveQuery`.

- `CreateQuery`: an object of format

Expand Down Expand Up @@ -244,6 +244,15 @@ Let's start by listing the defined data types in this library.
}
```

- `CountQuery`: an object of format

```js
{
type: 'count',
conditions: Object
}
```

- `UpdateQuery`: an object of format

```js
Expand Down Expand Up @@ -489,6 +498,7 @@ Constructs a route that returns a list of the given model, then merges the `conv
- The offset parameter is set from query parameter `offset`, same for `limit`, `sort`, and `where` parameters.
- The `where` parameter is parsed as JSON and used as query conditions if given.
- Default values for offset and limit are `0` and `100` respectively. No sort is defined by default.
- The response header `Content-Total` will contain the total count of items matching the `where` conditions.

### show

Expand Down Expand Up @@ -706,6 +716,8 @@ Returns an express router containing all [`resource`](#resource) routes of all g

# Development Notes

- **1.4.0:** The response of `list` and `showRelated` contains now a header `Content-Total` equal to the total count of items; useful for pagination.

- **1.3.0:** The query parameter `where` is now used to filter results on `list` and `show-many-related` routes.

- **1.2.0:** `req.body` is now used to filter results on `list` and `show-many-related` routes.
Expand Down
2 changes: 1 addition & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "wajez-api",
"version": "1.3.0",
"version": "1.4.1",
"description": "REST API Development made easy.",
"main": "src/index.js",
"scripts": {
Expand Down
61 changes: 37 additions & 24 deletions src/middlewares/basic.js
Original file line number Diff line number Diff line change
Expand Up @@ -58,53 +58,65 @@ const setRelated = def('setRelated', {}, [$.String, T.Middleware],
const getRelated = getParam('related')

const setQuery = def('setQuery', {}, [$.Any, T.Middleware],
queryGetter => (req, res, next) => {
queryGetter(req)
.then(q => {
req.wz.query = q
queryGetter => async (req, res, next) => {
try {
req.wz.query = await queryGetter(req)
next()
})
.catch(err => next(err))
} catch (err) {
next(err)
}
}
)

const getQuery = getParam('query')

const runQuery = def('run', {}, [T.MongooseModel, T.Middleware],
model => (req, res, next) => {
applyQuery(getQuery(req), model)
.then(data => {
req.wz.data = data
model => async (req, res, next) => {
try {
req.wz.data = await applyQuery(getQuery(req), model)
next()
})
.catch(err => next(err))
} catch (err) {
next(err)
}
}
)

const setData = def('setData', {}, [$.Any, T.Middleware],
dataGetter => (req, res, next) => {
dataGetter(req)
.then(data => {
req.wz.data = data
dataGetter => async (req, res, next) => {
try {
req.wz.data = await dataGetter(req)
next()
})
.catch(err => next(err))
} catch (err) {
next(err)
}
}
)

const getData = getParam('data')

const convertData = def('convert', {}, [$.AnyFunction, T.Middleware],
converterGetter => (req, res, next) => {
converterGetter(req)
.then(fn => {
const convertData = def('convertData', {}, [$.AnyFunction, T.Middleware],
converterGetter => async (req, res, next) => {
try {
const data = getData(req)
if (data == null)
return next()
const fn = await converterGetter(req)
req.wz.data = fn(data)
next()
})
.catch(err => next(err))
} catch (err) {
next(err)
}
}
)

const setHeader = def('setHeader', {}, [$.String, $.AnyFunction, T.Middleware],
(headerName, valueGetter) => async (req, res, next) => {
try {
res.set(headerName, await valueGetter(req))
next()
} catch (err) {
next(err)
}
}
)

Expand Down Expand Up @@ -137,6 +149,7 @@ module.exports = {
setData,
getData,
convertData,
setHeader,
sendData,
finish
}
8 changes: 8 additions & 0 deletions src/query.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ const applyQuery = def('applyQuery', {}, [T.Query, T.MongooseModel, $.Any],
return applyCreateQuery(query, model)
if (query.type === 'find')
return applyFindQuery(query, model)
if (query.type === 'count')
return applyCountQuery(query, model)
if (query.type === 'update')
return applyUpdateQuery(query, model)
if (query.type === 'remove')
Expand All @@ -28,6 +30,12 @@ const applyCreateQuery = def('applyCreateQuery', {}, [T.Query, T.MongooseModel,
}
)

const applyCountQuery = def('applyCountQuery', {}, [T.Query, T.MongooseModel, $.Any],
async (query, model) => {
return model.count(query.conditions)
}
)

const applyUpdateQuery = def('applyUpdateQuery', {}, [T.Query, T.MongooseModel, $.Any],
async (query, model) => {
const old = await model.findOne(query.conditions)
Expand Down
14 changes: 12 additions & 2 deletions src/routes/resource/list.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,15 @@ const {$, def, S} = require('wajez-utils')
const T = require('../../types')
const helpers = require('../../helpers')
const {get, extend} = require('../basic')
const {onQuery, onRun, onConvert, onReadParams, beforeQuery} = require('../../actions')
const {
onQuery, onRun, onConvert, onReadParams, beforeQuery, beforeSend
} = require('../../actions')
const {merge, applyConverter} = require('wajez-utils')
const {
setQuery, runQuery, convertData, setRoute,
setQuery, runQuery, convertData, setRoute, setHeader,
getOffset, getLimit, getSort, setModel, getWhere
} = require('../../middlewares')
const {applyQuery} = require('../../query')

const list = (model, {converter, uri, actions} = {}) =>
extend(get(helpers.uri(model), [
Expand All @@ -25,6 +28,13 @@ const list = (model, {converter, uri, actions} = {}) =>
populate: []
}))),
onRun(runQuery(model)),
beforeSend(setHeader('Content-Total', async req =>
applyQuery({
type: 'count',
conditions: getWhere(req) || {}
}, model)
)),
beforeSend(setHeader('access-control-expose-headers', async () => 'Content-Total')),
onConvert(convertData(helpers.routeConverter(model, converter || {})))
]), {uri, actions})

Expand Down
51 changes: 30 additions & 21 deletions src/routes/resource/show-related.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
const mongoose = require('mongoose')
const {$, def, S, model} = require('wajez-utils')
const {$, def, S, model, merge} = require('wajez-utils')
const T = require('../../types')
const helpers = require('../../helpers')
const {get, extend} = require('../basic')
const {applyQuery} = require('../../query')
const {onQuery, onRun, beforeConvert, onConvert, onReadParams} = require('../../actions')
const {
setQuery, runQuery, convertData, setData, setModel, getWhere,
setRoute, setRelated, getData, getOffset, getLimit, getSort
setQuery, runQuery, convertData, setData, setModel, getWhere, getQuery,
setRoute, setRelated, getData, getOffset, getLimit, getSort, setHeader
} = require('../../middlewares')

const showRelated = ({type, source, target}, config = {}) => {
Expand Down Expand Up @@ -54,29 +55,37 @@ const showManyRelated = def('showManyRelated', {}, [T.MongooseModel, T.MongooseM
onReadParams(setModel(parent.modelName)),
onReadParams(setRelated(child.modelName)),
onReadParams(setRoute('show-many-related')),
onQuery(setQuery(async req => ({
type: 'find',
conditions: {_id: req.params.id},
projection: '_id',
options: {
limit: 1
},
populate: [{
path: field,
match: getWhere(req) || {},
select: null,
onQuery(setQuery(async req => {
const [parentItem] = await applyQuery({
type: 'find',
conditions: {_id: req.params.id},
projection: '_id ' + field,
options: { limit: 1 },
populate: []
}, parent)
if (!parentItem)
return {type: 'mock', data: []}
return {
type: 'find',
conditions: merge(getWhere(req) || {}, {_id: {$in: parentItem[field]}}),
projection: null,
options: {
skip: getOffset(req),
limit: getLimit(req),
sort: getSort(req)
}
}]
}))),
onRun(runQuery(parent)),
beforeConvert(setData(async req => {
const instance = getData(req)[0]
return !instance ? null : (instance[field] || null)
},
populate: []
}
})),
onRun(runQuery(child)),
onRun(setHeader('Content-Total', async req => {
const query = getQuery(req)
return applyQuery({
type: 'count',
conditions: query.conditions
}, child)
})),
onRun(setHeader('access-control-expose-headers', async () => 'Content-Total')),
onConvert(convertData(helpers.routeConverter(child, converter || {})))
]), {uri, actions})
)
Expand Down
6 changes: 6 additions & 0 deletions src/types/query.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,11 @@ const FindQuery = _({
}))
})

const CountQuery = _({
type: Enum('FindQueryType', ['count']),
conditions: QueryConditions
})

const RemoveQuery = _({
type: Enum('RemoveQueryType', ['remove']),
conditions: QueryConditions,
Expand All @@ -46,6 +51,7 @@ const UpdateQuery = _({
const Query = Union('Query', [
CreateQuery,
FindQuery,
CountQuery,
UpdateQuery,
RemoveQuery,
MockQuery
Expand Down
2 changes: 1 addition & 1 deletion test/acceptance/one-to-many-relation.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,7 @@ describe('Acceptance > One to Many Relation', () => {
// check users1 posts
it.get('/users/:id/posts', {
params: () => users[1],
body: () => [posts[9], posts[0], posts[1]]
body: () => [posts[1], posts[9], posts[0]]
})
// check posts 9, 0 and 1 has user1 as writer
it.get('/posts/:id/writer', {
Expand Down
7 changes: 6 additions & 1 deletion test/acceptance/pagination.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,18 @@ describe('Acceptance > Pagination and Sort', () => {
it.get('/users?limit=200', {
verify: (res, done) => {
assert.lengthOf(res.body, 150)
assert.equal(res.get('Content-Total'), 150)
users = res.body
done()
}
})

it.get('/users', {
body: () => users.slice(0, 100)
body: () => users.slice(0, 100),
verify: (res, done) => {
assert.equal(res.get('content-total'), 150)
done()
}
})

it.get('/users?offset=17&limit=24', {
Expand Down
Loading

0 comments on commit eef5fa9

Please sign in to comment.