ready to test

Testing

Signed-off-by: Kenneth Obsequio <k80308392@gmail.com>
This commit is contained in:
2026-06-22 10:06:58 +08:00
parent bf48c95467
commit 439bb33f77
189 changed files with 17559 additions and 686 deletions
@@ -0,0 +1,58 @@
// models/advertisements/advertisements.attributes.js
// ─── Exclude sets ─────────────────────────────────────────────────────────────
// Fields hidden from all roles (internal/denormalized details)
const excludeAttributes = [
"image_asset_id", // internal FK, "image" association exposes the resolved asset instead
];
// Admins see everything except the base excludes
const adminExclude = [
...excludeAttributes,
];
// Regular users also cannot see audit trails, soft-delete info, or click metrics
const userExclude = [
...excludeAttributes,
"click_count",
"createdBy",
"updatedBy",
"deletedBy",
"deletedAt",
];
// ─── JSONB schemas ──────────────────────────────────────────────────────────
// Describes shape of the `ctas` JSONB column for paginate's coercion/validation step.
const jsonbSchemas = {
ctas: {
type: "array",
itemShape: {
label: "string",
link: "string",
variant: "string", // "default" | "outline"
},
},
};
// ─── Computed attributes ──────────────────────────────────────────────────────
// Add any SQL-computed fields here (e.g. a "is_currently_live" derived flag).
// Format: { key, label, type, order, literal }
const computedAttributes = [
// Example:
// {
// key: "is_live",
// label: "Currently Live",
// type: "boolean",
// order: 99,
// literal: `("Advertisement"."is_active" = true AND "Advertisement"."start_date" <= NOW() AND ("Advertisement"."end_date" IS NULL OR "Advertisement"."end_date" >= NOW()))`,
// },
];
module.exports = {
excludeAttributes,
adminExclude,
userExclude,
jsonbSchemas,
computedAttributes,
};
@@ -0,0 +1,71 @@
// models/advertisements/advertisements.mdl.js
const { DataTypes } = require("sequelize");
const sequelize = require("../../config/db.config");
const mdl_Users = require("../users/users.mdl");
const mdl_Assets = require("../assets/assets.mdl");
const Advertisement = sequelize.define("Advertisement", {
// ─── Identity ─────────────────────────────────────────────────────────────
advertisement_id: { type: DataTypes.BIGINT, primaryKey: true, autoIncrement: true, label: "Advertisement ID", order: 0, hidden: true },
uuid: { type: DataTypes.UUID, defaultValue: DataTypes.UUIDV4, allowNull: false, unique: true, label: "UUID", order: 0, hidden: true },
// ─── Identification / placement ──────────────────────────────────────────
type: {
type: DataTypes.ENUM("hero", "banner", "popup", "sidebar"),
allowNull: false,
label: "Type", order: 1
},
status: {
type: DataTypes.ENUM("draft", "active", "scheduled", "expired", "archived"),
allowNull: false,
defaultValue: "draft", label: "Status", order: 2
},
// ─── Content ──────────────────────────────────────────────────────────────
badge_label: { type: DataTypes.STRING(100), label: "Badge Label", order: 3 },
headline: { type: DataTypes.STRING(255), label: "Headline", order: 4 },
description: { type: DataTypes.TEXT, label: "Description", order: 5 },
// ─── Media ────────────────────────────────────────────────────────────────
image_url: { type: DataTypes.STRING(512), label: "Image URL", order: 0, hidden: true },
image_asset_id: { type: DataTypes.BIGINT, allowNull: true, label: "Image Asset", order: 0, hidden: true },
// ─── Calls-to-action ──────────────────────────────────────────────────────
// [{ label, link }, ...] — 0-2 entries depending on type
ctas: { type: DataTypes.JSONB, allowNull: false, defaultValue: [], label: "CTAs", order: 6 },
// ─── Scheduling ───────────────────────────────────────────────────────────
start_date: { type: DataTypes.DATE, label: "Start Date", order: 7 },
end_date: { type: DataTypes.DATE, label: "End Date", order: 8 },
// ─── Display behavior ─────────────────────────────────────────────────────
order: { type: DataTypes.INTEGER, allowNull: false, defaultValue: 0, field: "order", label: "Order", order: 9 },
is_active: { type: DataTypes.BOOLEAN, allowNull: false, defaultValue: true, label: "Active", order: 10 },
size: { type: DataTypes.ENUM("sm", "md", "lg"), allowNull: true, label: "Size", order: 11 }, // banner-only, ignored by other types
// ─── Metrics ──────────────────────────────────────────────────────────────
click_count: { type: DataTypes.INTEGER, allowNull: false, defaultValue: 0, label: "Clicks", order: 12, hidden: true },
// ─── Audit trails ─────────────────────────────────────────────────────────
createdBy: { type: DataTypes.BIGINT, allowNull: true, label: "Created By" },
updatedBy: { type: DataTypes.BIGINT, allowNull: true, label: "Modified By" },
deletedBy: { type: DataTypes.BIGINT, allowNull: true, label: "Deleted By" },
}, {
tableName: "advertisements",
timestamps: true, // createdAt, updatedAt
paranoid: true,
indexes: [
{ fields: ["uuid"] },
{ fields: ["type"] },
{ fields: ["status"] },
{ fields: ["is_active"] },
{ fields: ["deletedAt"] },
],
});
Advertisement.belongsTo(mdl_Assets, { as: "image", foreignKey: "image_asset_id" });
Advertisement.belongsTo(mdl_Users, { as: "creator", foreignKey: "createdBy" });
Advertisement.belongsTo(mdl_Users, { as: "updater", foreignKey: "updatedBy" });
module.exports = Advertisement;