const { Op } = require("sequelize"); const sequelize = require("../config/db.config"); const Sequelize = require("sequelize"); const R = require("../utils/response.util"); // adjust path as needed const auditByFields = ["createdBy", "updatedBy", "deletedBy"]; const getFieldValues = (Model, logTag, options = {}) => async (req, res) => { const { blockedFields = [], extraDateFields = [], allowJsonb = false, paranoid = true, selfJoin = false, // for users auditing users } = options; try { const { field } = req.query; if (!field) return R.error(res, "Field is required.", 400); const dateFields = ["createdAt", "updatedAt", "deletedAt", ...extraDateFields]; // ─── Non-JSONB path ─────────────────────────────────────────────────── if (!field.includes(".")) { const allowedFields = blockedFields.length ? Object.keys(Model.rawAttributes).filter((f) => !blockedFields.includes(f)) : Object.keys(Model.rawAttributes).filter((f) => Model.rawAttributes[f].filterable === true); if (!allowedFields.includes(field) && !dateFields.includes(field)) return R.error(res, "Invalid or restricted field.", 400); if (auditByFields.includes(field)) { const tableName = Model.getTableName(); const [rows] = selfJoin ? await sequelize.query(` SELECT DISTINCT u2."personal_info"->'name'->>'full_name' AS value FROM "${tableName}" u1 JOIN "${tableName}" u2 ON u2.user_id = u1."${field}" WHERE u1."${field}" IS NOT NULL AND u2."personal_info"->'name'->>'full_name' IS NOT NULL ORDER BY value ASC `) : await sequelize.query(` SELECT DISTINCT u."personal_info"->'name'->>'full_name' AS value FROM "${tableName}" t JOIN users u ON u.user_id = t."${field}" WHERE t."${field}" IS NOT NULL AND u."personal_info"->'name'->>'full_name' IS NOT NULL ORDER BY value ASC `); return R.success(res, "Field values retrieved.", rows.map((r) => r.value).filter(Boolean)); } if (dateFields.includes(field)) { const results = await Model.findAll({ attributes: [[Sequelize.fn("DISTINCT", Sequelize.fn("DATE", Sequelize.col(field))), "value"]], where: { [field]: { [Op.ne]: null } }, order: [[Sequelize.fn("DATE", Sequelize.col(field)), "DESC"]], paranoid, raw: true, }); return R.success(res, "Field values retrieved.", results.map((r) => r.value).filter(Boolean)); } const results = await Model.findAll({ attributes: [[Sequelize.fn("DISTINCT", Sequelize.col(field)), "value"]], where: { [field]: { [Op.ne]: null } }, paranoid, raw: true, }); return R.success(res, "Field values retrieved.", results.map((r) => r.value).filter(Boolean).sort()); } // ─── JSONB dot-notation path ────────────────────────────────────────── if (!allowJsonb) return R.error(res, "JSONB fields are not supported.", 400); const [column, ...pathParts] = field.split("."); const keys = [...pathParts]; const lastKey = keys.pop(); const jsonbPath = keys.length ? `"${column}"->${keys.map((k) => `'${k}'`).join("->")}->>'${lastKey}'` : `"${column}"->>'${lastKey}'`; const results = await Model.findAll({ attributes: [[Sequelize.literal(`DISTINCT ${jsonbPath}`), "value"]], where: Sequelize.literal(`${jsonbPath} IS NOT NULL`), raw: true, }); return R.success(res, "Field values retrieved.", results.map((r) => r.value).filter(Boolean).sort()); } catch (err) { console.error(`[${logTag}][GET FIELD VALUES]`, err); return R.error(res, "Could not retrieve field values.", 500); } }; module.exports = { getFieldValues };