209 lines
5.7 KiB
JavaScript
209 lines
5.7 KiB
JavaScript
const express = require('express');
|
|
const {
|
|
wrapAsync,
|
|
commonErrorHandler,
|
|
isUuidV4,
|
|
assertRouteIdMatchesBody,
|
|
} = require('../helpers');
|
|
const { checkCrudPermissions } = require('../middlewares/check-permissions');
|
|
const { parse } = require('json2csv');
|
|
const { logger } = require('../utils/logger');
|
|
const { validateRequest } = require('../middlewares/validate-request');
|
|
const { crud: crudSchemas } = require('../validators/request-schemas');
|
|
|
|
const DEFAULT_LIST_LIMIT = 50;
|
|
const MAX_LIST_LIMIT = 1000;
|
|
const MAX_AUTOCOMPLETE_LIMIT = 50;
|
|
const MAX_CSV_LIMIT = 1000;
|
|
|
|
function clampLimit(value, { defaultLimit, maxLimit }) {
|
|
const parsed = Number.parseInt(value, 10);
|
|
if (!Number.isFinite(parsed) || parsed <= 0) return defaultLimit;
|
|
return Math.min(parsed, maxLimit);
|
|
}
|
|
|
|
function getSortableFields(DBApi) {
|
|
if (Array.isArray(DBApi.SORTABLE_FIELDS)) return DBApi.SORTABLE_FIELDS;
|
|
if (DBApi.MODEL?.rawAttributes) return Object.keys(DBApi.MODEL.rawAttributes);
|
|
return [];
|
|
}
|
|
|
|
function normalizeQuery(query = {}, DBApi, { csv = false } = {}) {
|
|
const normalized = { ...query };
|
|
const maxLimit = csv ? MAX_CSV_LIMIT : MAX_LIST_LIMIT;
|
|
normalized.limit = clampLimit(normalized.limit, {
|
|
defaultLimit: DEFAULT_LIST_LIMIT,
|
|
maxLimit,
|
|
});
|
|
|
|
const page = Number.parseInt(normalized.page, 10);
|
|
normalized.page = Number.isFinite(page) && page > 0 ? page : 1;
|
|
|
|
if (normalized.sort) {
|
|
const sort = String(normalized.sort).toUpperCase();
|
|
normalized.sort = sort === 'ASC' ? 'ASC' : 'DESC';
|
|
}
|
|
|
|
const sortableFields = getSortableFields(DBApi);
|
|
if (normalized.field && !sortableFields.includes(normalized.field)) {
|
|
delete normalized.field;
|
|
}
|
|
|
|
return normalized;
|
|
}
|
|
|
|
function createEntityRouter(entityName, Service, DBApi, options = {}) {
|
|
const router = express.Router();
|
|
|
|
const permissionEntity = options.permissionEntity || entityName;
|
|
const validation = options.validation || {};
|
|
const schemaFor = (name) => validation[name] || crudSchemas[name];
|
|
router.use(checkCrudPermissions(permissionEntity));
|
|
|
|
router.post(
|
|
'/',
|
|
validateRequest(schemaFor('create')),
|
|
wrapAsync(async (req, res) => {
|
|
const referer =
|
|
req.headers.referer ||
|
|
`${req.protocol}://${req.hostname}${req.originalUrl}`;
|
|
const link = new URL(referer);
|
|
const payload = await Service.create(
|
|
req.body.data,
|
|
req.currentUser,
|
|
true,
|
|
link.origin,
|
|
);
|
|
res.status(200).send(payload);
|
|
}),
|
|
);
|
|
|
|
router.post(
|
|
'/bulk-import',
|
|
wrapAsync(async (req, res) => {
|
|
const referer =
|
|
req.headers.referer ||
|
|
`${req.protocol}://${req.hostname}${req.originalUrl}`;
|
|
const link = new URL(referer);
|
|
await Service.bulkImport(req, res, true, link.origin);
|
|
res.status(200).send(true);
|
|
}),
|
|
);
|
|
|
|
router.put(
|
|
'/:id',
|
|
validateRequest(schemaFor('update')),
|
|
wrapAsync(async (req, res) => {
|
|
assertRouteIdMatchesBody(req);
|
|
await Service.update(req.body.data, req.params.id, req.currentUser);
|
|
res.status(200).send(true);
|
|
}),
|
|
);
|
|
|
|
router.delete(
|
|
'/:id',
|
|
validateRequest(schemaFor('remove')),
|
|
wrapAsync(async (req, res) => {
|
|
await Service.remove(req.params.id, req.currentUser);
|
|
res.status(200).send(true);
|
|
}),
|
|
);
|
|
|
|
router.post(
|
|
'/deleteByIds',
|
|
validateRequest(schemaFor('deleteByIds')),
|
|
wrapAsync(async (req, res) => {
|
|
await Service.deleteByIds(req.body.data, req.currentUser);
|
|
res.status(200).send(true);
|
|
}),
|
|
);
|
|
|
|
router.get(
|
|
'/',
|
|
validateRequest(schemaFor('list')),
|
|
wrapAsync(async (req, res) => {
|
|
const filetype = req.query.filetype;
|
|
const currentUser = req.currentUser;
|
|
const runtimeContext = req.runtimeContext;
|
|
const normalizedQuery = normalizeQuery(req.query, DBApi, {
|
|
csv: filetype === 'csv',
|
|
});
|
|
|
|
const payload = await DBApi.findAll(normalizedQuery, {
|
|
currentUser,
|
|
runtimeContext,
|
|
});
|
|
|
|
if (filetype === 'csv') {
|
|
const fields = options.csvFields ||
|
|
DBApi.CSV_FIELDS || ['id', 'createdAt'];
|
|
const opts = { fields };
|
|
try {
|
|
const csv = parse(payload.rows, opts);
|
|
res.status(200).attachment('export.csv').send(csv);
|
|
} catch (err) {
|
|
logger.error({ err, entityName }, 'CSV export error');
|
|
res.status(500).send('CSV export error');
|
|
}
|
|
} else {
|
|
res.status(200).send(payload);
|
|
}
|
|
}),
|
|
);
|
|
|
|
router.get(
|
|
'/count',
|
|
validateRequest(schemaFor('count')),
|
|
wrapAsync(async (req, res) => {
|
|
const currentUser = req.currentUser;
|
|
const runtimeContext = req.runtimeContext;
|
|
const payload = await DBApi.findAll(normalizeQuery(req.query, DBApi), {
|
|
countOnly: true,
|
|
currentUser,
|
|
runtimeContext,
|
|
});
|
|
res.status(200).send(payload);
|
|
}),
|
|
);
|
|
|
|
router.get(
|
|
'/autocomplete',
|
|
validateRequest(schemaFor('autocomplete')),
|
|
wrapAsync(async (req, res) => {
|
|
const limit = clampLimit(req.query.limit, {
|
|
defaultLimit: 20,
|
|
maxLimit: MAX_AUTOCOMPLETE_LIMIT,
|
|
});
|
|
const payload = await DBApi.findAllAutocomplete(
|
|
req.query.query,
|
|
limit,
|
|
req.query.offset,
|
|
);
|
|
res.status(200).send(payload);
|
|
}),
|
|
);
|
|
|
|
router.get(
|
|
'/:id',
|
|
validateRequest(schemaFor('findOne')),
|
|
wrapAsync(async (req, res) => {
|
|
const runtimeContext = req.runtimeContext;
|
|
const payload = await DBApi.findBy(
|
|
{ id: req.params.id },
|
|
{ runtimeContext },
|
|
);
|
|
res.status(200).send(payload);
|
|
}),
|
|
);
|
|
|
|
if (options.customRoutes) {
|
|
options.customRoutes(router, Service, DBApi);
|
|
}
|
|
|
|
router.use('/', commonErrorHandler);
|
|
|
|
return router;
|
|
}
|
|
|
|
module.exports = { createEntityRouter, isUuidV4 };
|