mirror of
https://github.com/rgrgogu/new_starr.git
synced 2026-09-27 00:12:54 +08:00
initial
This commit is contained in:
@@ -0,0 +1,120 @@
|
||||
// utils/paginate.util.js
|
||||
const { Sequelize } = require('sequelize');
|
||||
const { modelToAttributes } = require('./modelToAttributes');
|
||||
const { excludeJsonbPaths } = require('./excludeJsonbPaths');
|
||||
const { buildQuery } = require('./buildQuery.util');
|
||||
|
||||
const PAGE_START = 1;
|
||||
const PAGE_SIZE = 10;
|
||||
const MAX_LIMIT = 100;
|
||||
|
||||
function safeParseJSON(value, fallback = []) {
|
||||
try {
|
||||
const parsed = JSON.parse(value);
|
||||
return Array.isArray(parsed) ? parsed : fallback;
|
||||
} catch {
|
||||
return fallback;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates audit subquery attributes for createdBy, updatedBy, deletedBy
|
||||
*
|
||||
* @param {Object} mdl_Users - Users model
|
||||
* @param {string} parentAlias - Sequelize model alias e.g. 'User', 'UserGroup'
|
||||
* @returns {Array} - Sequelize attribute include array
|
||||
*/
|
||||
function auditInclude(mdl_Users, parentAlias = 'User') {
|
||||
const tableName = mdl_Users.getTableName();
|
||||
|
||||
const fullNameSubquery = (foreignKey) =>
|
||||
Sequelize.literal(`(
|
||||
SELECT (u."personal_info"->>'name')::jsonb->>'full_name'
|
||||
FROM "${tableName}" AS u
|
||||
WHERE u."user_id" = "${parentAlias}"."${foreignKey}"
|
||||
LIMIT 1
|
||||
)`);
|
||||
|
||||
return {
|
||||
attributes: [
|
||||
[fullNameSubquery('createdBy'), 'createdByName'],
|
||||
[fullNameSubquery('updatedBy'), 'updatedByName'],
|
||||
[fullNameSubquery('deletedBy'), 'deletedByName'],
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Reusable paginated findAndCountAll
|
||||
*
|
||||
* @param {Object} model - Sequelize model
|
||||
* @param {Object} req - Express request
|
||||
* @param {Object} options
|
||||
* @param {string[]} options.excludeAttributes - fields to exclude
|
||||
* @param {Object} options.jsonbSchemas - JSONB schema map
|
||||
* @param {string} options.jsonbColumn - JSONB column name e.g. "personal_info"
|
||||
* @param {Object} options.findOptions - extra Sequelize options (include, where, etc.)
|
||||
* @param {Object} options.auditOptions - { mdl_Users, parentAlias } to auto-include audit subqueries
|
||||
*/
|
||||
async function paginate(model, req, {
|
||||
excludeAttributes = [],
|
||||
jsonbSchemas = {},
|
||||
jsonbColumn = null,
|
||||
findOptions = {},
|
||||
auditOptions = null, // ← { mdl_Users, parentAlias }
|
||||
} = {}) {
|
||||
const page = Math.max(PAGE_START, parseInt(req.query.page, 10) || PAGE_START);
|
||||
const limit = Math.min(parseInt(req.query.limit, 10) || PAGE_SIZE, MAX_LIMIT);
|
||||
const offset = (page - PAGE_START) * limit;
|
||||
|
||||
const filters = safeParseJSON(req.query.filters);
|
||||
const sort = safeParseJSON(req.query.sort);
|
||||
|
||||
const topLevelExclude = excludeAttributes.filter((f) => !f.includes('.'));
|
||||
const jsonbExclude = excludeAttributes.filter((f) => f.includes('.'));
|
||||
const jsonbAttr = jsonbColumn ? excludeJsonbPaths(jsonbColumn, jsonbExclude) : null;
|
||||
|
||||
const attributes = modelToAttributes(model, { exclude: excludeAttributes, jsonbSchemas });
|
||||
const ALLOWED_FIELDS = attributes.map((a) => a.field);
|
||||
|
||||
const { where, order } = buildQuery(filters, sort, ALLOWED_FIELDS);
|
||||
|
||||
// Build attribute includes: jsonb + audit subqueries + any extra from findOptions
|
||||
const baseIncludes = jsonbAttr ? [jsonbAttr] : [];
|
||||
const auditAttrs = auditOptions
|
||||
? auditInclude(auditOptions.mdl_Users, auditOptions.parentAlias).attributes
|
||||
: [];
|
||||
const extraIncludes = findOptions.attributes?.include ?? [];
|
||||
const mergedAttributeIncludes = [...baseIncludes, ...auditAttrs, ...extraIncludes];
|
||||
|
||||
const { attributes: _attr, ...restFindOptions } = findOptions;
|
||||
|
||||
const { count, rows } = await model.findAndCountAll({
|
||||
...restFindOptions,
|
||||
where: { ...where, ...(restFindOptions.where ?? {}) },
|
||||
order,
|
||||
limit,
|
||||
offset,
|
||||
attributes: {
|
||||
exclude: topLevelExclude,
|
||||
include: mergedAttributeIncludes,
|
||||
},
|
||||
});
|
||||
|
||||
const totalPages = Math.ceil(count / limit);
|
||||
|
||||
return {
|
||||
data: rows,
|
||||
pagination: {
|
||||
page,
|
||||
limit,
|
||||
totalRecords: count,
|
||||
totalPages,
|
||||
hasPrevPage: page > PAGE_START,
|
||||
hasNextPage: page < totalPages,
|
||||
},
|
||||
attributes,
|
||||
};
|
||||
}
|
||||
|
||||
module.exports = { paginate, auditInclude, safeParseJSON };
|
||||
Reference in New Issue
Block a user