mirror of
https://github.com/rgrgogu/new_starr.git
synced 2026-09-27 00:12:54 +08:00
chore: relocate backend into apps/api ahead of monorepo merge
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,108 @@
|
||||
/***********************************************************************************************************************************************************************
|
||||
* File Name : notification.mdl.js
|
||||
* Type : Sequelize Model
|
||||
* Description : Admin-facing system notifications. Each row is a single event
|
||||
* surfaced in the admin bell dropdown (e.g. "5 tasks overdue").
|
||||
* Not user-scoped — visible to all admins.
|
||||
*
|
||||
* Author: Kenneth Obsequio (@lash0000)
|
||||
* Date Created: Jun. 19, 2026
|
||||
***********************************************************************************************************************************************************************/
|
||||
const { DataTypes } = require('sequelize');
|
||||
const sequelize = require('../../config/db.config');
|
||||
const mdl_Assets = require('../assets/assets.mdl');
|
||||
|
||||
const AdminNotification = sequelize.define('AdminNotification', {
|
||||
notification_id: {
|
||||
type: DataTypes.BIGINT,
|
||||
autoIncrement: true,
|
||||
primaryKey: true,
|
||||
},
|
||||
type: {
|
||||
type: DataTypes.STRING(64),
|
||||
allowNull: false,
|
||||
},
|
||||
title: {
|
||||
type: DataTypes.STRING(255),
|
||||
allowNull: false,
|
||||
},
|
||||
message: {
|
||||
type: DataTypes.TEXT,
|
||||
allowNull: false,
|
||||
},
|
||||
|
||||
// Controls where this notification is rendered in the admin UI.
|
||||
show_in_notifications: {
|
||||
type: DataTypes.BOOLEAN,
|
||||
allowNull: false,
|
||||
defaultValue: true,
|
||||
},
|
||||
show_in_sticky: {
|
||||
type: DataTypes.BOOLEAN,
|
||||
allowNull: false,
|
||||
defaultValue: false,
|
||||
},
|
||||
|
||||
// Named color key (utils/tierColors.js on the frontend) for the sticky banner.
|
||||
color: {
|
||||
type: DataTypes.STRING(20),
|
||||
allowNull: false,
|
||||
defaultValue: 'indigo',
|
||||
},
|
||||
|
||||
// Denormalized copy of the source NotificationBroadcast's per-alert
|
||||
// layout image (see notification_broadcast.mdl.js) — null when the
|
||||
// alert has none.
|
||||
image_asset_id: {
|
||||
type: DataTypes.BIGINT,
|
||||
allowNull: true,
|
||||
},
|
||||
|
||||
// Denormalized copy of the source NotificationBroadcast's visibility
|
||||
// window (see notificationVisibility.util.js) — null means unbounded.
|
||||
start_date: {
|
||||
type: DataTypes.DATE,
|
||||
allowNull: true,
|
||||
},
|
||||
end_date: {
|
||||
type: DataTypes.DATE,
|
||||
allowNull: true,
|
||||
},
|
||||
|
||||
// Links back to the source broadcast so editing it after send can
|
||||
// propagate content changes into already-created rows (see
|
||||
// notificationBroadcasts.controller.js#updateBroadcast). Null for
|
||||
// notification types with no broadcast source.
|
||||
broadcast_id: {
|
||||
type: DataTypes.BIGINT,
|
||||
allowNull: true,
|
||||
},
|
||||
|
||||
data: {
|
||||
type: DataTypes.JSONB,
|
||||
allowNull: true,
|
||||
defaultValue: null,
|
||||
},
|
||||
seen: {
|
||||
type: DataTypes.BOOLEAN,
|
||||
allowNull: false,
|
||||
defaultValue: false,
|
||||
},
|
||||
seen_at: {
|
||||
type: DataTypes.DATE,
|
||||
allowNull: true,
|
||||
defaultValue: null,
|
||||
},
|
||||
}, {
|
||||
tableName: 'admin_notifications',
|
||||
timestamps: true,
|
||||
indexes: [
|
||||
{ fields: ['seen'], name: 'idx_an_seen' },
|
||||
{ fields: ['type'], name: 'idx_an_type' },
|
||||
{ fields: ['createdAt'], name: 'idx_an_created_at' },
|
||||
],
|
||||
});
|
||||
|
||||
AdminNotification.belongsTo(mdl_Assets, { as: "image", foreignKey: "image_asset_id" });
|
||||
|
||||
module.exports = AdminNotification;
|
||||
@@ -0,0 +1,16 @@
|
||||
// models/notifications/cron_notification_setting.mdl.js
|
||||
const { DataTypes } = require("sequelize");
|
||||
const sequelize = require("../../config/db.config");
|
||||
|
||||
const CronNotificationSetting = sequelize.define("CronNotificationSetting", {
|
||||
job_name: { type: DataTypes.STRING(64), primaryKey: true, label: "Job" },
|
||||
enabled: { type: DataTypes.BOOLEAN, allowNull: false, defaultValue: true, label: "Enabled" },
|
||||
schedule: { type: DataTypes.STRING(20), allowNull: false, label: "Schedule" },
|
||||
target_status: { type: DataTypes.STRING(20), allowNull: true, label: "Target Status" },
|
||||
updatedBy: { type: DataTypes.BIGINT, allowNull: true, label: "Modified By" },
|
||||
}, {
|
||||
tableName: "cron_notification_settings",
|
||||
timestamps: true,
|
||||
});
|
||||
|
||||
module.exports = CronNotificationSetting;
|
||||
@@ -0,0 +1,24 @@
|
||||
// models/notifications/notification_broadcast.attributes.js
|
||||
|
||||
// ─── Exclude sets ─────────────────────────────────────────────────────────────
|
||||
|
||||
const excludeAttributes = [];
|
||||
|
||||
// Admins see everything
|
||||
const adminExclude = [
|
||||
...excludeAttributes,
|
||||
];
|
||||
|
||||
// ─── JSONB schemas ──────────────────────────────────────────────────────────
|
||||
// No JSONB columns on this model.
|
||||
const jsonbSchemas = {};
|
||||
|
||||
// ─── Computed attributes ──────────────────────────────────────────────────────
|
||||
const computedAttributes = [];
|
||||
|
||||
module.exports = {
|
||||
excludeAttributes,
|
||||
adminExclude,
|
||||
jsonbSchemas,
|
||||
computedAttributes,
|
||||
};
|
||||
@@ -0,0 +1,87 @@
|
||||
// models/notifications/notification_broadcast.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 NotificationBroadcast = sequelize.define("NotificationBroadcast", {
|
||||
|
||||
// ─── Identity ─────────────────────────────────────────────────────────────
|
||||
broadcast_id: { type: DataTypes.BIGINT, primaryKey: true, autoIncrement: true, label: "Broadcast ID", order: 0, hidden: true },
|
||||
uuid: { type: DataTypes.UUID, defaultValue: DataTypes.UUIDV4, allowNull: false, unique: true, label: "UUID", order: 0, hidden: true },
|
||||
|
||||
// ─── Content ──────────────────────────────────────────────────────────────
|
||||
title: { type: DataTypes.STRING(255), allowNull: false, label: "Title", order: 1 },
|
||||
// Only used by Notifications-type alerts (show_in_notifications) — Sticky
|
||||
// alerts (show_in_sticky) are title-only and leave this null.
|
||||
message: { type: DataTypes.TEXT, allowNull: true, label: "Message", order: 1.5 },
|
||||
// When set, the client's "view full content" dialog shows an "Open Link"
|
||||
// action pointing here. When null, that dialog is plain text info only.
|
||||
link_url: { type: DataTypes.STRING(2048), allowNull: true, label: "Link URL", order: 2.2 },
|
||||
// Custom CTA button text shown right after the title in the sticky banner
|
||||
// (e.g. "Shop now"). Falls back to "Open Link" when unset.
|
||||
link_label: { type: DataTypes.STRING(60), allowNull: true, label: "Button Label", order: 2.3 },
|
||||
|
||||
// Named color key (see utils/tierColors.js on the frontend) driving the
|
||||
// sticky banner's panel background/border — same registry rewards/tiers use.
|
||||
color: { type: DataTypes.STRING(20), allowNull: false, defaultValue: 'indigo', label: "Color", order: 2.4 },
|
||||
|
||||
// Optional per-alert image shown in the click-through details dialog when
|
||||
// opened from the sticky banner (see AnnouncementCarouselDialog on the
|
||||
// frontend). Replaces the old shared sticky_banner_settings singleton.
|
||||
image_asset_id: { type: DataTypes.BIGINT, allowNull: true, label: "Layout Image", order: 2.45 },
|
||||
|
||||
// Optional visibility window. Null start_date = show immediately once sent;
|
||||
// null end_date = show indefinitely. Copied onto AdminNotification/
|
||||
// UserNotification rows at send time so client reads can filter without
|
||||
// joining back to this table (see sendBroadcast + notificationVisibility.util.js).
|
||||
start_date: { type: DataTypes.DATE, allowNull: true, label: "Start Date", order: 2.7 },
|
||||
end_date: { type: DataTypes.DATE, allowNull: true, label: "End Date", order: 2.8 },
|
||||
|
||||
// ─── Visibility ──────────────────────────────────────────────────────────
|
||||
// Determines where a delivered announcement shows up for recipients.
|
||||
// - show_in_sticky: client sticky banner (fixed top)
|
||||
// - show_in_notifications: client + admin notifications lists/bells
|
||||
show_in_sticky: { type: DataTypes.BOOLEAN, allowNull: false, defaultValue: false, label: "Show in Sticky Alerts", order: 2.5 },
|
||||
show_in_notifications: { type: DataTypes.BOOLEAN, allowNull: false, defaultValue: true, label: "Show in Notifications", order: 2.6 },
|
||||
|
||||
// ─── Targeting ────────────────────────────────────────────────────────────
|
||||
target_type: {
|
||||
type: DataTypes.ENUM("admin", "user", "both", "task_list", "course", "tier_plan"),
|
||||
allowNull: false,
|
||||
label: "Target", order: 3
|
||||
},
|
||||
// Holds a task list UUID, course UUID, or tier plan ID (stringified) — only
|
||||
// set when target_type is 'task_list' / 'course' / 'tier_plan'.
|
||||
target_id: { type: DataTypes.STRING(64), allowNull: true, label: "Target ID", order: 3.5 },
|
||||
|
||||
// ─── Lifecycle ────────────────────────────────────────────────────────────
|
||||
status: {
|
||||
type: DataTypes.ENUM("draft", "sent", "archived"),
|
||||
allowNull: false,
|
||||
defaultValue: "draft", label: "Status", order: 4
|
||||
},
|
||||
sent_at: { type: DataTypes.DATE, allowNull: true, label: "Sent At", order: 5 },
|
||||
recipient_count: { type: DataTypes.INTEGER, allowNull: false, defaultValue: 0, label: "Recipients", order: 6 },
|
||||
|
||||
// ─── 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: "notification_broadcasts",
|
||||
timestamps: true, // createdAt, updatedAt
|
||||
paranoid: true,
|
||||
indexes: [
|
||||
{ fields: ["uuid"] },
|
||||
{ fields: ["status"] },
|
||||
{ fields: ["target_type"] },
|
||||
{ fields: ["deletedAt"] },
|
||||
],
|
||||
});
|
||||
|
||||
NotificationBroadcast.belongsTo(mdl_Users, { as: "creator", foreignKey: "createdBy" });
|
||||
NotificationBroadcast.belongsTo(mdl_Users, { as: "updater", foreignKey: "updatedBy" });
|
||||
NotificationBroadcast.belongsTo(mdl_Assets, { as: "image", foreignKey: "image_asset_id" });
|
||||
|
||||
module.exports = NotificationBroadcast;
|
||||
@@ -0,0 +1,115 @@
|
||||
/***********************************************************************************************************************************************************************
|
||||
* File Name : user_notification.mdl.js
|
||||
* Type : Sequelize Model
|
||||
* Description : Client-facing per-user notifications (achievement unlocked,
|
||||
* task events, etc.). Scoped to a single user_id — each row
|
||||
* is private to the user it belongs to.
|
||||
*
|
||||
* Author: Kenneth Obsequio (@lash0000)
|
||||
* Date Created: Jun. 19, 2026
|
||||
***********************************************************************************************************************************************************************/
|
||||
const { DataTypes } = require('sequelize');
|
||||
const sequelize = require('../../config/db.config');
|
||||
const mdl_Assets = require('../assets/assets.mdl');
|
||||
|
||||
const UserNotification = sequelize.define('UserNotification', {
|
||||
notification_id: {
|
||||
type: DataTypes.BIGINT,
|
||||
autoIncrement: true,
|
||||
primaryKey: true,
|
||||
},
|
||||
user_id: {
|
||||
type: DataTypes.BIGINT,
|
||||
allowNull: false,
|
||||
},
|
||||
type: {
|
||||
type: DataTypes.STRING(64),
|
||||
allowNull: false,
|
||||
},
|
||||
title: {
|
||||
type: DataTypes.STRING(255),
|
||||
allowNull: false,
|
||||
},
|
||||
message: {
|
||||
type: DataTypes.TEXT,
|
||||
allowNull: false,
|
||||
},
|
||||
|
||||
// Controls where this notification is rendered.
|
||||
// - show_in_notifications: appears in the /notifications list + bell badge.
|
||||
// - show_in_sticky: appears in the fixed "sticky announcement" banner.
|
||||
show_in_notifications: {
|
||||
type: DataTypes.BOOLEAN,
|
||||
allowNull: false,
|
||||
defaultValue: true,
|
||||
},
|
||||
show_in_sticky: {
|
||||
type: DataTypes.BOOLEAN,
|
||||
allowNull: false,
|
||||
defaultValue: false,
|
||||
},
|
||||
|
||||
// Named color key (utils/tierColors.js on the frontend) for the sticky banner.
|
||||
color: {
|
||||
type: DataTypes.STRING(20),
|
||||
allowNull: false,
|
||||
defaultValue: 'indigo',
|
||||
},
|
||||
|
||||
// Denormalized copy of the source NotificationBroadcast's per-alert
|
||||
// layout image (see notification_broadcast.mdl.js) — null when the
|
||||
// alert has none.
|
||||
image_asset_id: {
|
||||
type: DataTypes.BIGINT,
|
||||
allowNull: true,
|
||||
},
|
||||
|
||||
// Denormalized copy of the source NotificationBroadcast's visibility
|
||||
// window (see notificationVisibility.util.js) — null means unbounded.
|
||||
start_date: {
|
||||
type: DataTypes.DATE,
|
||||
allowNull: true,
|
||||
},
|
||||
end_date: {
|
||||
type: DataTypes.DATE,
|
||||
allowNull: true,
|
||||
},
|
||||
|
||||
// Links back to the source broadcast so editing it after send can
|
||||
// propagate content changes into already-created rows (see
|
||||
// notificationBroadcasts.controller.js#updateBroadcast). Null for
|
||||
// notification types with no broadcast source.
|
||||
broadcast_id: {
|
||||
type: DataTypes.BIGINT,
|
||||
allowNull: true,
|
||||
},
|
||||
|
||||
data: {
|
||||
type: DataTypes.JSONB,
|
||||
allowNull: true,
|
||||
defaultValue: null,
|
||||
},
|
||||
seen: {
|
||||
type: DataTypes.BOOLEAN,
|
||||
allowNull: false,
|
||||
defaultValue: false,
|
||||
},
|
||||
seen_at: {
|
||||
type: DataTypes.DATE,
|
||||
allowNull: true,
|
||||
defaultValue: null,
|
||||
},
|
||||
}, {
|
||||
tableName: 'user_notifications',
|
||||
timestamps: true,
|
||||
indexes: [
|
||||
{ fields: ['user_id'], name: 'idx_un_user_id' },
|
||||
{ fields: ['user_id', 'seen'], name: 'idx_un_seen' },
|
||||
{ fields: ['type'], name: 'idx_un_type' },
|
||||
{ fields: ['createdAt'], name: 'idx_un_created_at' },
|
||||
],
|
||||
});
|
||||
|
||||
UserNotification.belongsTo(mdl_Assets, { as: "image", foreignKey: "image_asset_id" });
|
||||
|
||||
module.exports = UserNotification;
|
||||
Reference in New Issue
Block a user