mirror of
https://github.com/rgrgogu/new_starr.git
synced 2026-09-27 00:12:54 +08:00
174 lines
5.9 KiB
JavaScript
174 lines
5.9 KiB
JavaScript
// utils/modelToAttributes.js
|
|
const { DataTypes } = require("sequelize");
|
|
|
|
/**
|
|
* Maps Sequelize DataType to a simple UI type string.
|
|
*/
|
|
function resolveType(dataType) {
|
|
if (!dataType) return "text";
|
|
|
|
const type = dataType.constructor?.key || dataType.key || "";
|
|
|
|
if (["BIGINT", "INTEGER", "FLOAT", "DOUBLE", "DECIMAL"].includes(type)) return "number";
|
|
if (["DATE", "DATEONLY"].includes(type)) return "date";
|
|
if (["BOOLEAN"].includes(type)) return "enum";
|
|
if (["ENUM"].includes(type)) return "enum";
|
|
if (["JSONB", "JSON"].includes(type)) return "jsonb";
|
|
|
|
return "text";
|
|
}
|
|
|
|
/**
|
|
* Resolves options (e.g. enum choices) from a Sequelize field definition.
|
|
*/
|
|
function resolveOptions(dataType) {
|
|
if (!dataType) return "text";
|
|
|
|
const type = dataType.constructor?.key || dataType.key || "";
|
|
|
|
if (type === "ENUM") {
|
|
return { choices: dataType.values ?? [] };
|
|
}
|
|
|
|
if (type === "BOOLEAN") {
|
|
return { choices: ["true", "false"] };
|
|
}
|
|
|
|
return {};
|
|
}
|
|
|
|
/**
|
|
* Converts a camelCase or snake_case field name to a readable label.
|
|
*/
|
|
function toLabel(field) {
|
|
return field
|
|
.replace(/_/g, " ")
|
|
.replace(/([a-z])([A-Z])/g, "$1 $2")
|
|
.replace(/\b\w/g, (c) => c.toUpperCase());
|
|
}
|
|
|
|
/**
|
|
* Flattens JSONB field paths from a schema definition.
|
|
*
|
|
* @param {Object} jsonbSchema - e.g. { name: { given_name, full_name }, date_of_birth }
|
|
* @param {string} prefix - e.g. "personal_info"
|
|
* @returns {Array} flat attribute entries for each leaf path
|
|
*/
|
|
function flattenJsonb(jsonbSchema = {}, prefix = "", exclude = []) {
|
|
const result = [];
|
|
|
|
for (const [key, value] of Object.entries(jsonbSchema)) {
|
|
const path = prefix ? `${prefix}.${key}` : key;
|
|
|
|
if (exclude.includes(path)) continue;
|
|
|
|
// Nested object (no `type` key = it's a group, not a leaf)
|
|
if (value && typeof value === "object" && !value.type) {
|
|
result.push(...flattenJsonb(value, path, exclude));
|
|
} else {
|
|
result.push({
|
|
name: value?.label || toLabel(key), // <-- prefer label
|
|
type: value?.type ?? "text",
|
|
field: path,
|
|
order: value?.order ?? Infinity, // ← carry order from schema
|
|
hidden: value?.hidden ?? false, // ← carry hidden flag
|
|
filterable: value.filterable ?? true,
|
|
options: {},
|
|
});
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* Generates an attributes array from a Sequelize model + optional JSONB schema map.
|
|
*
|
|
* @param {Object} model - Sequelize model (e.g. mdl_Users)
|
|
* @param {Object} jsonbSchemas - map of JSONB field names to their schema definition
|
|
* e.g. { personal_info: { name: { full_name: "text", given_name: "text" }, date_of_birth: "date" } }
|
|
* @param {string[]} exclude - field names to exclude (e.g. ["password", "otp_code"])
|
|
* @returns {Array} attributes array
|
|
*/
|
|
function modelToAttributes(model, { jsonbSchemas = {}, exclude = [], timestampLabels = {}, context = "" } = {}) {
|
|
const rawAttrs = model.rawAttributes || model.tableAttributes;
|
|
const attributes = [];
|
|
|
|
const defaultTimestampLabels = {
|
|
createdAt: "Created",
|
|
modifiedAt: "Modified",
|
|
updatedAt: "Modified",
|
|
deletedAt: "Deleted",
|
|
createdBy: "Created By",
|
|
updatedBy: "Modified By",
|
|
deletedBy: "Deleted By",
|
|
...timestampLabels,
|
|
};
|
|
|
|
// Audit fields excluded from normal loop — handled separately at the end
|
|
const auditFields = [
|
|
'createdAt', 'updatedAt', 'modifiedAt',
|
|
'deletedAt',
|
|
'createdBy', 'updatedBy', 'deletedBy',
|
|
];
|
|
|
|
// Ordered audit sequence
|
|
const auditSequence = [
|
|
{ field: "updatedAt", type: "date", hiddenOnList: false, hiddenOnArchived: true },
|
|
{ field: "modifiedAt", type: "date", hiddenOnList: false, hiddenOnArchived: true },
|
|
{ field: "updatedBy", type: "text", hiddenOnList: false, hiddenOnArchived: true },
|
|
{ field: "createdAt", type: "date", hiddenOnList: false, hiddenOnArchived: true },
|
|
{ field: "createdBy", type: "text", hiddenOnList: false, hiddenOnArchived: true },
|
|
{ field: "deletedAt", type: "date", hiddenOnList: true, hiddenOnArchived: false },
|
|
{ field: "deletedBy", type: "text", hiddenOnList: true, hiddenOnArchived: false },
|
|
];
|
|
|
|
// ── Normal fields (excluding audit) ─────────────────────────────────────────
|
|
for (const [field, def] of Object.entries(rawAttrs)) {
|
|
if (exclude.includes(field)) continue;
|
|
if (auditFields.includes(field)) continue; // skip audit — added later in order
|
|
|
|
const dataType = def.type;
|
|
const type = resolveType(def.type); // ← pass only the type
|
|
|
|
if (type === "jsonb" && jsonbSchemas[field]) {
|
|
attributes.push(...flattenJsonb(jsonbSchemas[field], field, exclude));
|
|
continue;
|
|
}
|
|
|
|
if (type === "jsonb") continue;
|
|
|
|
attributes.push({
|
|
name: defaultTimestampLabels[field] ?? def.label ?? toLabel(field),
|
|
type,
|
|
field,
|
|
order: def.order ?? Infinity,
|
|
hidden: def.hidden ?? false,
|
|
filterable: def.filterable ?? true,
|
|
options: resolveOptions(def.type)
|
|
});
|
|
}
|
|
|
|
// ── Audit fields in correct sequence ────────────────────────────────────────
|
|
for (const { field, type, order, hiddenOnList, hiddenOnArchived, filterable } of auditSequence) {
|
|
if (exclude.includes(field)) continue;
|
|
if (!rawAttrs[field]) continue; // skip if field doesn't exist on model
|
|
|
|
const isArchived = context === "archived";
|
|
|
|
attributes.push({
|
|
name: defaultTimestampLabels[field],
|
|
type,
|
|
field,
|
|
order,
|
|
hidden: isArchived ? hiddenOnArchived : hiddenOnList,
|
|
options: resolveOptions(rawAttrs[field]?.type),
|
|
filterable: filterable ?? true,
|
|
});
|
|
}
|
|
|
|
// ── Sort by order — DO NOT strip order here, paginate.util does it ───────────
|
|
return attributes.sort((a, b) => (a.order ?? Infinity) - (b.order ?? Infinity));
|
|
}
|
|
|
|
module.exports = { modelToAttributes }; |