Server: add user list sort/pagination
authorChocobozzz <florian.bigard@gmail.com>
Tue, 16 Aug 2016 20:31:45 +0000 (22:31 +0200)
committerChocobozzz <florian.bigard@gmail.com>
Tue, 16 Aug 2016 20:31:45 +0000 (22:31 +0200)
server/controllers/api/v1/users.js
server/initializers/checker.js
server/initializers/constants.js
server/middlewares/sort.js
server/middlewares/validators/sort.js
server/models/user.js
server/models/utils.js [new file with mode: 0644]
server/models/video.js
server/tests/api/check-params.js
server/tests/api/users.js
server/tests/utils/users.js

index 704df770c4fb8099325ab363e95000bfa02f3614..975e25e68f5ea1979a501a7050d723a4bcb80e95 100644 (file)
@@ -11,6 +11,10 @@ const logger = require('../../../helpers/logger')
 const middlewares = require('../../../middlewares')
 const admin = middlewares.admin
 const oAuth = middlewares.oauth
+const pagination = middlewares.pagination
+const sort = middlewares.sort
+const validatorsPagination = middlewares.validators.pagination
+const validatorsSort = middlewares.validators.sort
 const validatorsUsers = middlewares.validators.users
 
 const User = mongoose.model('User')
@@ -18,9 +22,16 @@ const Video = mongoose.model('Video')
 
 const router = express.Router()
 
-router.get('/', listUsers)
 router.get('/me', oAuth.authenticate, getUserInformation)
 
+router.get('/',
+  validatorsPagination.pagination,
+  validatorsSort.usersSort,
+  sort.setUsersSort,
+  pagination.setPagination,
+  listUsers
+)
+
 router.post('/',
   oAuth.authenticate,
   admin.ensureIsAdmin,
@@ -73,10 +84,10 @@ function getUserInformation (req, res, next) {
 }
 
 function listUsers (req, res, next) {
-  User.list(function (err, usersList) {
+  User.listForApi(req.query.start, req.query.count, req.query.sort, function (err, usersList, usersTotal) {
     if (err) return next(err)
 
-    res.json(getFormatedUsers(usersList))
+    res.json(getFormatedUsers(usersList, usersTotal))
   })
 }
 
@@ -145,7 +156,7 @@ function success (req, res, next) {
 
 // ---------------------------------------------------------------------------
 
-function getFormatedUsers (users) {
+function getFormatedUsers (users, usersTotal) {
   const formatedUsers = []
 
   users.forEach(function (user) {
@@ -153,6 +164,7 @@ function getFormatedUsers (users) {
   })
 
   return {
+    total: usersTotal,
     data: formatedUsers
   }
 }
index 871d3cac2079c49ab2c8ca22c78be39681e84c43..4b6997547b31b62d957954757e4df8c1d18e9a53 100644 (file)
@@ -39,7 +39,7 @@ function clientsExist (callback) {
 }
 
 function usersExist (callback) {
-  User.count(function (err, totalUsers) {
+  User.countTotal(function (err, totalUsers) {
     if (err) return callback(err)
 
     return callback(null, totalUsers !== 0)
index 4163564009a40e10144eaddbdb1db9e36e38b93b..cd2e0cfb9f32ac832711ab6c0effdf130cd561a4 100644 (file)
@@ -63,6 +63,7 @@ const SEEDS_IN_PARALLEL = 3
 
 // Sortable columns per schema
 const SORTABLE_COLUMNS = {
+  USERS: [ 'username', '-username', 'createdDate', '-createdDate' ],
   VIDEOS: [ 'name', '-name', 'duration', '-duration', 'createdDate', '-createdDate' ]
 }
 
index 9f52290a6446a1f85dae27da13374082e9963c0f..8ed15780587355c949ae1a3b569e57fb3cc6a84b 100644 (file)
@@ -1,9 +1,16 @@
 'use strict'
 
 const sortMiddleware = {
+  setUsersSort: setUsersSort,
   setVideosSort: setVideosSort
 }
 
+function setUsersSort (req, res, next) {
+  if (!req.query.sort) req.query.sort = '-createdDate'
+
+  return next()
+}
+
 function setVideosSort (req, res, next) {
   if (!req.query.sort) req.query.sort = '-createdDate'
 
index 56b63cc8b9ddaffd5c5e1304ec4c0c38683382da..37b34ef52cbca232c0153459640a10d88817f1d3 100644 (file)
@@ -5,9 +5,20 @@ const constants = require('../../initializers/constants')
 const logger = require('../../helpers/logger')
 
 const validatorsSort = {
+  usersSort: usersSort,
   videosSort: videosSort
 }
 
+function usersSort (req, res, next) {
+  const sortableColumns = constants.SORTABLE_COLUMNS.USERS
+
+  req.checkQuery('sort', 'Should have correct sortable column').optional().isIn(sortableColumns)
+
+  logger.debug('Checking sort parameters', { parameters: req.query })
+
+  checkErrors(req, res, next)
+}
+
 function videosSort (req, res, next) {
   const sortableColumns = constants.SORTABLE_COLUMNS.VIDEOS
 
index d289da19af804615ee1f075409a5fa760c2189ea..c9c35b3e2dffc0c06cbe6d35f379135998aae583 100644 (file)
@@ -1,10 +1,15 @@
 const mongoose = require('mongoose')
 
 const customUsersValidators = require('../helpers/custom-validators').users
+const modelUtils = require('./utils')
 
 // ---------------------------------------------------------------------------
 
 const UserSchema = mongoose.Schema({
+  createdDate: {
+    type: Date,
+    default: Date.now
+  },
   password: String,
   username: String,
   role: String
@@ -19,9 +24,9 @@ UserSchema.methods = {
 }
 
 UserSchema.statics = {
-  count: count,
+  countTotal: countTotal,
   getByUsernameAndPassword: getByUsernameAndPassword,
-  list: list,
+  listForApi: listForApi,
   loadById: loadById,
   loadByUsername: loadByUsername
 }
@@ -30,7 +35,7 @@ mongoose.model('User', UserSchema)
 
 // ---------------------------------------------------------------------------
 
-function count (callback) {
+function countTotal (callback) {
   return this.count(callback)
 }
 
@@ -38,8 +43,9 @@ function getByUsernameAndPassword (username, password) {
   return this.findOne({ username: username, password: password })
 }
 
-function list (callback) {
-  return this.find(callback)
+function listForApi (start, count, sort, callback) {
+  const query = {}
+  return modelUtils.listForApiWithCount.call(this, query, start, count, sort, callback)
 }
 
 function loadById (id, callback) {
diff --git a/server/models/utils.js b/server/models/utils.js
new file mode 100644 (file)
index 0000000..a961e8c
--- /dev/null
@@ -0,0 +1,30 @@
+'use strict'
+
+const parallel = require('async/parallel')
+
+const utils = {
+  listForApiWithCount: listForApiWithCount
+}
+
+function listForApiWithCount (query, start, count, sort, callback) {
+  const self = this
+
+  parallel([
+    function (asyncCallback) {
+      self.find(query).skip(start).limit(count).sort(sort).exec(asyncCallback)
+    },
+    function (asyncCallback) {
+      self.count(query, asyncCallback)
+    }
+  ], function (err, results) {
+    if (err) return callback(err)
+
+    const data = results[0]
+    const total = results[1]
+    return callback(null, data, total)
+  })
+}
+
+// ---------------------------------------------------------------------------
+
+module.exports = utils
index a5540d127a157630b92f834d92dd4e78bcd7aac1..63afc2efef4f6db5eef3c5818b229047fb5e50c2 100644 (file)
@@ -197,7 +197,7 @@ function getDurationFromFile (videoPath, callback) {
 
 function listForApi (start, count, sort, callback) {
   const query = {}
-  return modelUtils.findWithCount.call(this, query, start, count, sort, callback)
+  return modelUtils.listForApiWithCount.call(this, query, start, count, sort, callback)
 }
 
 function listByUrlAndMagnet (fromUrl, magnetUri, callback) {
@@ -234,7 +234,7 @@ function search (value, field, start, count, sort, callback) {
     query[field] = new RegExp(value)
   }
 
-  modelUtils.findWithCount.call(this, query, start, count, sort, callback)
+  modelUtils.listForApiWithCount.call(this, query, start, count, sort, callback)
 }
 
 function seedAllExisting (callback) {
index 882948facf9a398d6140186252ea78a39b85b300..fc8b0a42a93e30130f774221c3e551cfe213fd92 100644 (file)
@@ -459,6 +459,32 @@ describe('Test parameters validator', function () {
     let userId = null
     let userAccessToken = null
 
+    describe('When listing users', function () {
+      it('Should fail with a bad start pagination', function (done) {
+        request(server.url)
+          .get(path)
+          .query({ start: 'hello' })
+          .set('Accept', 'application/json')
+          .expect(400, done)
+      })
+
+      it('Should fail with a bad count pagination', function (done) {
+        request(server.url)
+          .get(path)
+          .query({ count: 'hello' })
+          .set('Accept', 'application/json')
+          .expect(400, done)
+      })
+
+      it('Should fail with an incorrect sort', function (done) {
+        request(server.url)
+          .get(path)
+          .query({ sort: 'hello' })
+          .set('Accept', 'application/json')
+          .expect(400, done)
+      })
+    })
+
     describe('When adding a new user', function () {
       it('Should fail with a too small username', function (done) {
         const data = {
index a2557d2aba6cb67ef2e9e1616a289acadc1c4333..c6c892bf279c1a264197bfb1392d0c07db55e908 100644 (file)
@@ -209,17 +209,92 @@ describe('Test users', function () {
     usersUtils.getUsersList(server.url, function (err, res) {
       if (err) throw err
 
-      const users = res.body.data
+      const result = res.body
+      const total = result.total
+      const users = result.data
 
+      expect(total).to.equal(2)
       expect(users).to.be.an('array')
       expect(users.length).to.equal(2)
 
-      const rootUser = users[0]
+      const user = users[0]
+      expect(user.username).to.equal('user_1')
+
+      const rootUser = users[1]
       expect(rootUser.username).to.equal('root')
+      userId = user.id
+
+      done()
+    })
+  })
+
+  it('Should list only the first user by username asc', function (done) {
+    usersUtils.getUsersListPaginationAndSort(server.url, 0, 1, 'username', function (err, res) {
+      if (err) throw err
+
+      const result = res.body
+      const total = result.total
+      const users = result.data
+
+      expect(total).to.equal(2)
+      expect(users.length).to.equal(1)
 
-      const user = users[1]
+      const user = users[0]
+      expect(user.username).to.equal('root')
+
+      done()
+    })
+  })
+
+  it('Should list only the first user by username desc', function (done) {
+    usersUtils.getUsersListPaginationAndSort(server.url, 0, 1, '-username', function (err, res) {
+      if (err) throw err
+
+      const result = res.body
+      const total = result.total
+      const users = result.data
+
+      expect(total).to.equal(2)
+      expect(users.length).to.equal(1)
+
+      const user = users[0]
       expect(user.username).to.equal('user_1')
-      userId = user.id
+
+      done()
+    })
+  })
+
+  it('Should list only the second user by createdDate desc', function (done) {
+    usersUtils.getUsersListPaginationAndSort(server.url, 0, 1, '-createdDate', function (err, res) {
+      if (err) throw err
+
+      const result = res.body
+      const total = result.total
+      const users = result.data
+
+      expect(total).to.equal(2)
+      expect(users.length).to.equal(1)
+
+      const user = users[0]
+      expect(user.username).to.equal('user_1')
+
+      done()
+    })
+  })
+
+  it('Should list all the users by createdDate asc', function (done) {
+    usersUtils.getUsersListPaginationAndSort(server.url, 0, 2, 'createdDate', function (err, res) {
+      if (err) throw err
+
+      const result = res.body
+      const total = result.total
+      const users = result.data
+
+      expect(total).to.equal(2)
+      expect(users.length).to.equal(2)
+
+      expect(users[0].username).to.equal('root')
+      expect(users[1].username).to.equal('user_1')
 
       done()
     })
index 3b560e409e4615edf027340b9964bc23e065e3cb..0cf4e4adb7ef56bcae91058e49858fdac734659e 100644 (file)
@@ -6,6 +6,7 @@ const usersUtils = {
   createUser: createUser,
   getUserInformation: getUserInformation,
   getUsersList: getUsersList,
+  getUsersListPaginationAndSort: getUsersListPaginationAndSort,
   removeUser: removeUser,
   updateUser: updateUser
 }
@@ -52,6 +53,20 @@ function getUsersList (url, end) {
     .end(end)
 }
 
+function getUsersListPaginationAndSort (url, start, count, sort, end) {
+  const path = '/api/v1/users'
+
+  request(url)
+    .get(path)
+    .query({ start: start })
+    .query({ count: count })
+    .query({ sort: sort })
+    .set('Accept', 'application/json')
+    .expect(200)
+    .expect('Content-Type', /json/)
+    .end(end)
+}
+
 function removeUser (url, userId, accessToken, expectedStatus, end) {
   if (!end) {
     end = expectedStatus