diff --git a/client/coral-admin/src/reducers/stories.js b/client/coral-admin/src/reducers/stories.js index a942dd724..4a069c82e 100644 --- a/client/coral-admin/src/reducers/stories.js +++ b/client/coral-admin/src/reducers/stories.js @@ -11,6 +11,7 @@ const initialState = { criteria: { asc: 'false', filter: 'all', + limit: 20, }, }; diff --git a/client/coral-ui/components/Paginate.css b/client/coral-ui/components/Paginate.css index 813786d66..c188ccf57 100644 --- a/client/coral-ui/components/Paginate.css +++ b/client/coral-ui/components/Paginate.css @@ -16,7 +16,8 @@ text-align: center; vertical-align: middle; line-height: 30px; - width: 30px; + min-width: 30px; + padding: 5px; user-select: none; } @@ -24,7 +25,14 @@ font-size: 1.8em; } -.active { +.page:hover { + background-color: #ababab; + box-shadow: 0 2px 2px 0 rgba(0,0,0,.14), 0 3px 1px -2px rgba(0,0,0,.2), 0 1px 5px 0 rgba(0,0,0,.12); + a { + color: white; + } +} +.active, .active:hover { background-color: #696969; box-shadow: 0 2px 2px 0 rgba(0,0,0,.14), 0 3px 1px -2px rgba(0,0,0,.2), 0 1px 5px 0 rgba(0,0,0,.12); a { diff --git a/models/schema/asset.js b/models/schema/asset.js index 82de92120..6df93634b 100644 --- a/models/schema/asset.js +++ b/models/schema/asset.js @@ -81,6 +81,10 @@ Asset.index( } ); +// Indexes for listing the assets on the admin page. +Asset.index({ created_at: -1, publication_date: -1 }, { background: true }); +Asset.index({ created_at: 1, publication_date: 1 }, { background: true }); + /** * Returns true if the asset is closed, false else. */ diff --git a/routes/api/v1/assets.js b/routes/api/v1/assets.js index 6e3d4266d..a8b043946 100644 --- a/routes/api/v1/assets.js +++ b/routes/api/v1/assets.js @@ -1,4 +1,5 @@ const express = require('express'); +const Joi = require('joi'); const router = express.Router(); const authorization = require('../../../middleware/authorization'); const { ErrHTTPNotFound } = require('../../../errors'); @@ -31,36 +32,63 @@ const FilterOpenAssets = (query, filter) => { } }; +const ListAssetsSchema = Joi.object({ + value: Joi.string() + .empty('') + .default(''), + field: Joi.string() + .empty('') + .default('publication_date'), + page: Joi.number() + .empty('') + .default(1) + .min(1), + asc: Joi.bool() + .empty('') + .default(false), + filter: Joi.string() + .empty('') + .valid(['all', 'open', 'closed']) + .default('all'), + limit: Joi.number() + .empty('') + .default(20) + .max(500) + .min(0), +}); + // List assets. router.get( '/', authorization.needed('ADMIN', 'MODERATOR'), async (req, res, next) => { - const { - value = '', - field = 'publication_date', - page = 1, - asc = 'false', - filter = 'all', - limit = 20, - } = req.query; + const { value: query, error: err } = Joi.validate( + req.query, + ListAssetsSchema, + {} + ); + if (err) { + return next(err); + } + + let { value, field, page, asc, filter, limit } = query; try { - const order = asc === 'true' ? 1 : -1; + const order = asc ? 1 : -1; const queryOpts = { sort: { [field]: order, created_at: order }, skip: (page - 1) * limit, - limit, + limit: limit, }; // Find all the assets. let [result, count] = await Promise.all([ - // Find the actuall assets. + // Find the actual assets. FilterOpenAssets(AssetsService.search({ value }), filter) .sort(queryOpts.sort) - .skip(parseInt(queryOpts.skip)) - .limit(parseInt(queryOpts.limit)) + .skip(queryOpts.skip) + .limit(queryOpts.limit) .lean(), // Get the count of actual assets. @@ -70,9 +98,9 @@ router.get( // Send back the asset data. res.json({ result, - limit: Number(limit), + limit, count, - page: Number(page), + page, totalPages: Math.ceil(count / (limit === 0 ? 1 : limit)), }); } catch (e) {