mirror of
https://github.com/rgrgogu/new_starr.git
synced 2026-09-27 00:12:54 +08:00
added and fix some of things
Signed-off-by: Kenneth Obsequio <k80308392@gmail.com>
This commit is contained in:
@@ -4,7 +4,6 @@ const sequelize = require("../../config/db.config");
|
||||
const NotificationBroadcast = require("../../models/notifications/notification_broadcast.mdl");
|
||||
const AdminNotification = require("../../models/notifications/admin_notification.mdl");
|
||||
const UserNotification = require("../../models/notifications/user_notification.mdl");
|
||||
const StickyBannerSetting = require("../../models/notifications/sticky_banner_setting.mdl");
|
||||
const mdl_Users = require('../../models/users/users.mdl');
|
||||
const mdl_Assets = require('../../models/assets/assets.mdl');
|
||||
const { TaskList } = require('../../models/task/task.mdl');
|
||||
@@ -68,7 +67,33 @@ async function countActiveSticky(excludeId = null) {
|
||||
}
|
||||
|
||||
const MAX_ACTIVE_STICKY = 3;
|
||||
const ACTIVE_STICKY_CAP_MESSAGE = `Maximum of ${MAX_ACTIVE_STICKY} active sticky announcements right now — this stays in Draft until one ends or is archived.`;
|
||||
const ACTIVE_STICKY_CAP_MESSAGE = `Maximum of ${MAX_ACTIVE_STICKY} active sticky alerts right now — this stays in Draft until one ends or is archived.`;
|
||||
|
||||
async function validateImageAssetId(image_asset_id) {
|
||||
if (!image_asset_id) return null;
|
||||
const asset = await mdl_Assets.findOne({ where: { asset_id: image_asset_id, deletedAt: null } });
|
||||
if (!asset) {
|
||||
const err = new Error("Selected image asset was not found.");
|
||||
err.status = 400;
|
||||
throw err;
|
||||
}
|
||||
return asset.asset_id;
|
||||
}
|
||||
|
||||
// Pushes a visibility change into the already-fanned-out per-recipient rows
|
||||
// (admin_notifications/user_notifications) so archive/restore take effect
|
||||
// immediately for anyone currently seeing the alert — same rationale as the
|
||||
// content/display propagation in updateBroadcast below, just for the two
|
||||
// visibility flags. `where` is a raw SQL fragment + its replacements so this
|
||||
// can target either a single broadcast_id or an IN-list.
|
||||
async function propagateNotificationVisibility(where, { show_in_sticky, show_in_notifications }, transaction) {
|
||||
for (const table of ['admin_notifications', 'user_notifications']) {
|
||||
await sequelize.query(
|
||||
`UPDATE ${table} SET show_in_sticky = :show_in_sticky, show_in_notifications = :show_in_notifications WHERE ${where.sql}`,
|
||||
{ replacements: { show_in_sticky, show_in_notifications, ...where.replacements }, transaction }
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
async function applyBroadcastFields(broadcast, body) {
|
||||
if (body.title !== undefined) broadcast.title = body.title;
|
||||
@@ -76,6 +101,7 @@ async function applyBroadcastFields(broadcast, body) {
|
||||
if (body.link_url !== undefined) broadcast.link_url = body.link_url?.trim() || null;
|
||||
if (body.link_label !== undefined) broadcast.link_label = body.link_label?.trim() || null;
|
||||
if (body.color !== undefined) broadcast.color = body.color || 'indigo';
|
||||
if (body.image_asset_id !== undefined) broadcast.image_asset_id = await validateImageAssetId(body.image_asset_id);
|
||||
|
||||
if (body.start_date !== undefined) broadcast.start_date = body.start_date || null;
|
||||
if (body.end_date !== undefined) broadcast.end_date = body.end_date || null;
|
||||
@@ -157,10 +183,10 @@ exports.getBroadcasts = async (req, res) => {
|
||||
|
||||
if (Array.isArray(result?.data)) await attachTargetLabels(result.data);
|
||||
|
||||
return R.success(res, "Announcements retrieved.", result);
|
||||
return R.success(res, "Alerts retrieved.", result);
|
||||
} catch (err) {
|
||||
console.error("[NOTIFICATION BROADCAST][GET ALL]", err);
|
||||
return R.error(res, "Could not retrieve announcements.", 500);
|
||||
return R.error(res, "Could not retrieve alerts.", 500);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -176,12 +202,14 @@ exports.getBroadcast = async (req, res) => {
|
||||
include: [
|
||||
{ model: mdl_Users, as: "creator", attributes: ["user_id", "personal_info"], foreignKey: "createdBy" },
|
||||
{ model: mdl_Users, as: "updater", attributes: ["user_id", "personal_info"], foreignKey: "updatedBy" },
|
||||
IMAGE_INCLUDE,
|
||||
],
|
||||
});
|
||||
|
||||
if (!broadcast) return R.error(res, "Announcement not found.", 404);
|
||||
if (!broadcast) return R.error(res, "Alert not found.", 404);
|
||||
|
||||
const json = broadcast.toJSON();
|
||||
if (json.image) await attachImageStreamToken(json.image, req);
|
||||
|
||||
if (json.creator) {
|
||||
json.creator = {
|
||||
@@ -198,7 +226,7 @@ exports.getBroadcast = async (req, res) => {
|
||||
|
||||
await attachTargetLabels(json);
|
||||
|
||||
return R.success(res, "Announcement retrieved.", { data: json });
|
||||
return R.success(res, "Alert retrieved.", { data: json });
|
||||
} catch (err) {
|
||||
console.error("[NOTIFICATION BROADCAST][GET ONE]", err);
|
||||
return R.error(res, "Internal server error.", 500);
|
||||
@@ -215,6 +243,7 @@ exports.createBroadcast = async (req, res) => {
|
||||
link_url,
|
||||
link_label,
|
||||
color,
|
||||
image_asset_id,
|
||||
target_type,
|
||||
target_id,
|
||||
createdBy,
|
||||
@@ -243,6 +272,8 @@ exports.createBroadcast = async (req, res) => {
|
||||
|
||||
if (SCOPED_TARGET_TYPES.includes(target_type)) await validateTargetId(target_type, target_id);
|
||||
|
||||
const validatedImageAssetId = await validateImageAssetId(image_asset_id);
|
||||
|
||||
const t = await sequelize.transaction();
|
||||
try {
|
||||
const broadcast = await NotificationBroadcast.build({
|
||||
@@ -251,6 +282,7 @@ exports.createBroadcast = async (req, res) => {
|
||||
link_url: link_url?.trim() || null,
|
||||
link_label: link_label?.trim() || null,
|
||||
color: color || 'indigo',
|
||||
image_asset_id: validatedImageAssetId,
|
||||
start_date: start_date || null,
|
||||
end_date: end_date || null,
|
||||
createdBy,
|
||||
@@ -264,7 +296,7 @@ exports.createBroadcast = async (req, res) => {
|
||||
await t.commit();
|
||||
|
||||
logActivity(req.user?.user_id, 'create_notification_broadcast', { entityType: 'notification_broadcast', entityId: broadcast.broadcast_id, details: { target_type } });
|
||||
return R.success(res, "Announcement created.", { data: broadcast }, 201);
|
||||
return R.success(res, "Alert created.", { data: broadcast }, 201);
|
||||
} catch (dbErr) {
|
||||
try { await t.rollback(); } catch { /* connection gone */ }
|
||||
throw dbErr;
|
||||
@@ -322,6 +354,7 @@ exports.updateBroadcast = async (req, res) => {
|
||||
title: broadcast.title,
|
||||
message: broadcast.message,
|
||||
color: broadcast.color,
|
||||
image_asset_id: broadcast.image_asset_id,
|
||||
show_in_sticky: broadcast.show_in_sticky,
|
||||
show_in_notifications: broadcast.show_in_notifications,
|
||||
start_date: broadcast.start_date,
|
||||
@@ -334,7 +367,7 @@ exports.updateBroadcast = async (req, res) => {
|
||||
for (const table of ['admin_notifications', 'user_notifications']) {
|
||||
await sequelize.query(
|
||||
`UPDATE ${table}
|
||||
SET title = :title, message = :message, color = :color,
|
||||
SET title = :title, message = :message, color = :color, image_asset_id = :image_asset_id,
|
||||
show_in_sticky = :show_in_sticky, show_in_notifications = :show_in_notifications,
|
||||
start_date = :start_date, end_date = :end_date,
|
||||
data = data || jsonb_build_object('linkUrl', :linkUrl, 'linkLabel', :linkLabel)
|
||||
@@ -347,7 +380,7 @@ exports.updateBroadcast = async (req, res) => {
|
||||
await t.commit();
|
||||
|
||||
logActivity(req.user?.user_id, 'update_notification_broadcast', { entityType: 'notification_broadcast', entityId: Number(broadcastId) });
|
||||
return R.success(res, "Announcement updated.", { data: broadcast });
|
||||
return R.success(res, "Alert updated.", { data: broadcast });
|
||||
} catch (dbErr) {
|
||||
try { await t.rollback(); } catch { /* gone */ }
|
||||
throw dbErr;
|
||||
@@ -367,7 +400,7 @@ exports.sendBroadcast = async (req, res) => {
|
||||
if (!broadcastId || broadcastId === "undefined") return R.error(res, "Invalid broadcast ID.", 400);
|
||||
|
||||
const broadcast = await NotificationBroadcast.findOne({ where: { broadcast_id: broadcastId, ...notDeleted } });
|
||||
if (!broadcast) return R.error(res, "Announcement not found.", 404);
|
||||
if (!broadcast) return R.error(res, "Alert not found.", 404);
|
||||
if (broadcast.status !== 'draft') return R.error(res, "Only draft broadcasts can be sent.", 400);
|
||||
|
||||
if (broadcast.show_in_sticky && (await countActiveSticky(broadcast.broadcast_id)) >= MAX_ACTIVE_STICKY) {
|
||||
@@ -395,7 +428,7 @@ exports.sendBroadcast = async (req, res) => {
|
||||
|
||||
if (targetType === 'admin' || targetType === 'both') {
|
||||
await AdminNotification.create(
|
||||
{ ...baseNotify, seen: false, show_in_sticky: showInSticky, show_in_notifications: showInNotifications, color: broadcast.color, start_date: broadcast.start_date, end_date: broadcast.end_date, broadcast_id: broadcast.broadcast_id },
|
||||
{ ...baseNotify, seen: false, show_in_sticky: showInSticky, show_in_notifications: showInNotifications, color: broadcast.color, image_asset_id: broadcast.image_asset_id, start_date: broadcast.start_date, end_date: broadcast.end_date, broadcast_id: broadcast.broadcast_id },
|
||||
{ transaction: t }
|
||||
);
|
||||
recipientCount += 1;
|
||||
@@ -437,6 +470,7 @@ exports.sendBroadcast = async (req, res) => {
|
||||
show_in_sticky: showInSticky,
|
||||
show_in_notifications: showInNotifications,
|
||||
color: broadcast.color,
|
||||
image_asset_id: broadcast.image_asset_id,
|
||||
start_date: broadcast.start_date,
|
||||
end_date: broadcast.end_date,
|
||||
broadcast_id: broadcast.broadcast_id,
|
||||
@@ -453,7 +487,7 @@ exports.sendBroadcast = async (req, res) => {
|
||||
await t.commit();
|
||||
|
||||
logActivity(req.user?.user_id, 'send_notification_broadcast', { entityType: 'notification_broadcast', entityId: Number(broadcastId), details: { target_type: targetType, target_id: broadcast.target_id, recipient_count: recipientCount } });
|
||||
return R.success(res, "Announcement sent.", { data: broadcast });
|
||||
return R.success(res, "Alert sent.", { data: broadcast });
|
||||
} catch (dbErr) {
|
||||
try { await t.rollback(); } catch { /* gone */ }
|
||||
throw dbErr;
|
||||
@@ -473,12 +507,25 @@ exports.archiveBroadcast = async (req, res) => {
|
||||
if (!broadcastId || broadcastId === "undefined") return R.error(res, "Invalid broadcast ID.", 400);
|
||||
|
||||
const broadcast = await NotificationBroadcast.findOne({ where: { broadcast_id: broadcastId, ...notDeleted } });
|
||||
if (!broadcast) return R.error(res, "Announcement not found.", 404);
|
||||
if (!broadcast) return R.error(res, "Alert not found.", 404);
|
||||
|
||||
const t = await sequelize.transaction();
|
||||
try {
|
||||
await broadcast.update({ deletedBy: req.body.deletedBy ?? null }, { transaction: t });
|
||||
await broadcast.destroy({ transaction: t });
|
||||
await propagateNotificationVisibility(
|
||||
{ sql: 'broadcast_id = :broadcastId', replacements: { broadcastId: broadcast.broadcast_id } },
|
||||
{ show_in_sticky: false, show_in_notifications: false },
|
||||
t
|
||||
);
|
||||
await t.commit();
|
||||
} catch (dbErr) {
|
||||
try { await t.rollback(); } catch { /* gone */ }
|
||||
throw dbErr;
|
||||
}
|
||||
|
||||
await broadcast.update({ deletedBy: req.body.deletedBy ?? null });
|
||||
await broadcast.destroy();
|
||||
logActivity(req.user?.user_id, 'archive_notification_broadcast', { entityType: 'notification_broadcast', entityId: Number(broadcastId) });
|
||||
return R.success(res, "Announcement archived.");
|
||||
return R.success(res, "Alert archived.");
|
||||
} catch (err) {
|
||||
console.error("[NOTIFICATION BROADCAST][ARCHIVE]", err);
|
||||
return R.error(res, "Internal server error.", 500);
|
||||
@@ -497,8 +544,20 @@ exports.archiveBroadcasts = async (req, res) => {
|
||||
|
||||
const activeIds = broadcasts.map((b) => b.broadcast_id);
|
||||
|
||||
await NotificationBroadcast.update({ deletedBy: deletedBy ?? null }, { where: { broadcast_id: { [Op.in]: activeIds } } });
|
||||
await NotificationBroadcast.destroy({ where: { broadcast_id: { [Op.in]: activeIds } } });
|
||||
const t = await sequelize.transaction();
|
||||
try {
|
||||
await NotificationBroadcast.update({ deletedBy: deletedBy ?? null }, { where: { broadcast_id: { [Op.in]: activeIds } }, transaction: t });
|
||||
await NotificationBroadcast.destroy({ where: { broadcast_id: { [Op.in]: activeIds } }, transaction: t });
|
||||
await propagateNotificationVisibility(
|
||||
{ sql: 'broadcast_id IN (:activeIds)', replacements: { activeIds } },
|
||||
{ show_in_sticky: false, show_in_notifications: false },
|
||||
t
|
||||
);
|
||||
await t.commit();
|
||||
} catch (dbErr) {
|
||||
try { await t.rollback(); } catch { /* gone */ }
|
||||
throw dbErr;
|
||||
}
|
||||
|
||||
logActivity(req.user?.user_id, 'bulk_archive_notification_broadcasts', { entityType: 'notification_broadcast', details: { ids: activeIds, count: activeIds.length } });
|
||||
return R.success(res, `${activeIds.length} notification broadcast(s) archived.`, {
|
||||
@@ -518,13 +577,30 @@ exports.restoreBroadcast = async (req, res) => {
|
||||
const { broadcastId } = req.params;
|
||||
|
||||
const broadcast = await NotificationBroadcast.findOne({ where: { broadcast_id: broadcastId }, paranoid: false });
|
||||
if (!broadcast) return R.error(res, "Announcement not found.", 404);
|
||||
if (!broadcast.deletedAt) return R.error(res, "Announcement is not archived.", 400);
|
||||
if (!broadcast) return R.error(res, "Alert not found.", 404);
|
||||
if (!broadcast.deletedAt) return R.error(res, "Alert is not archived.", 400);
|
||||
|
||||
const t = await sequelize.transaction();
|
||||
try {
|
||||
await broadcast.restore({ transaction: t });
|
||||
await broadcast.update({ deletedBy: null }, { transaction: t });
|
||||
// Drafts never had per-recipient rows created — only propagate for
|
||||
// broadcasts that were actually sent.
|
||||
if (broadcast.status === 'sent') {
|
||||
await propagateNotificationVisibility(
|
||||
{ sql: 'broadcast_id = :broadcastId', replacements: { broadcastId: broadcast.broadcast_id } },
|
||||
{ show_in_sticky: broadcast.show_in_sticky, show_in_notifications: broadcast.show_in_notifications },
|
||||
t
|
||||
);
|
||||
}
|
||||
await t.commit();
|
||||
} catch (dbErr) {
|
||||
try { await t.rollback(); } catch { /* gone */ }
|
||||
throw dbErr;
|
||||
}
|
||||
|
||||
await broadcast.restore();
|
||||
await broadcast.update({ deletedBy: null });
|
||||
logActivity(req.user?.user_id, 'restore_notification_broadcast', { entityType: 'notification_broadcast', entityId: Number(broadcastId) });
|
||||
return R.success(res, "Announcement restored.", { data: broadcast });
|
||||
return R.success(res, "Alert restored.", { data: broadcast });
|
||||
} catch (err) {
|
||||
console.error("[NOTIFICATION BROADCAST][RESTORE]", err);
|
||||
return R.error(res, "Internal server error.", 500);
|
||||
@@ -546,8 +622,27 @@ exports.restoreBroadcasts = async (req, res) => {
|
||||
|
||||
const archivedIds = archived.map((b) => b.broadcast_id);
|
||||
|
||||
await NotificationBroadcast.restore({ where: { broadcast_id: { [Op.in]: archivedIds } } });
|
||||
await NotificationBroadcast.update({ deletedBy: null }, { where: { broadcast_id: { [Op.in]: archivedIds } }, paranoid: false });
|
||||
const t = await sequelize.transaction();
|
||||
try {
|
||||
await NotificationBroadcast.restore({ where: { broadcast_id: { [Op.in]: archivedIds } }, transaction: t });
|
||||
await NotificationBroadcast.update({ deletedBy: null }, { where: { broadcast_id: { [Op.in]: archivedIds } }, paranoid: false, transaction: t });
|
||||
|
||||
// Visibility can differ per broadcast, so this can't be a single flat
|
||||
// UPDATE like the archive side — loop and restore each one's own
|
||||
// show_in_sticky/show_in_notifications values.
|
||||
for (const b of archived) {
|
||||
if (b.status !== 'sent') continue;
|
||||
await propagateNotificationVisibility(
|
||||
{ sql: 'broadcast_id = :broadcastId', replacements: { broadcastId: b.broadcast_id } },
|
||||
{ show_in_sticky: b.show_in_sticky, show_in_notifications: b.show_in_notifications },
|
||||
t
|
||||
);
|
||||
}
|
||||
await t.commit();
|
||||
} catch (dbErr) {
|
||||
try { await t.rollback(); } catch { /* gone */ }
|
||||
throw dbErr;
|
||||
}
|
||||
|
||||
logActivity(req.user?.user_id, 'bulk_restore_notification_broadcasts', { entityType: 'notification_broadcast', details: { ids: archivedIds, count: archivedIds.length } });
|
||||
return R.success(res, `${archivedIds.length} notification broadcast(s) restored.`, {
|
||||
@@ -572,10 +667,10 @@ exports.getArchivedBroadcasts = async (req, res) => {
|
||||
auditOptions: { mdl_Users, parentAlias: 'NotificationBroadcast' },
|
||||
findOptions: { paranoid: false, where: { deletedAt: { [Op.ne]: null } } },
|
||||
});
|
||||
return R.success(res, "Archived announcements retrieved.", result);
|
||||
return R.success(res, "Archived alerts retrieved.", result);
|
||||
} catch (err) {
|
||||
console.error("[NOTIFICATION BROADCAST][GET ARCHIVED]", err);
|
||||
return R.error(res, "Could not retrieve archived announcements.", 500);
|
||||
return R.error(res, "Could not retrieve archived alerts.", 500);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -586,15 +681,15 @@ exports.permanentlyDeleteBroadcast = async (req, res) => {
|
||||
const { broadcastId } = req.params;
|
||||
|
||||
const broadcast = await NotificationBroadcast.findOne({ where: { broadcast_id: broadcastId }, paranoid: false });
|
||||
if (!broadcast) return R.error(res, "Announcement not found.", 404);
|
||||
if (!broadcast.deletedAt) return R.error(res, "Announcement must be archived before it can be permanently deleted.", 400);
|
||||
if (!broadcast) return R.error(res, "Alert not found.", 404);
|
||||
if (!broadcast.deletedAt) return R.error(res, "Alert must be archived before it can be permanently deleted.", 400);
|
||||
|
||||
await broadcast.destroy({ force: true });
|
||||
logActivity(req.user?.user_id, 'permanently_delete_notification_broadcast', { entityType: 'notification_broadcast', entityId: Number(broadcastId) });
|
||||
return R.success(res, "Announcement permanently deleted.");
|
||||
return R.success(res, "Alert permanently deleted.");
|
||||
} catch (err) {
|
||||
console.error("[NOTIFICATION BROADCAST][PERMANENT DELETE]", err);
|
||||
return R.error(res, "Could not permanently delete announcement.", 500);
|
||||
return R.error(res, "Could not permanently delete alert.", 500);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -606,81 +701,23 @@ exports.permanentlyDeleteBroadcasts = async (req, res) => {
|
||||
if (!Array.isArray(ids) || !ids.length) return R.error(res, "ids must be a non-empty array.", 400);
|
||||
|
||||
const broadcasts = await NotificationBroadcast.findAll({ where: { broadcast_id: { [Op.in]: ids } }, paranoid: false });
|
||||
if (!broadcasts.length) return R.error(res, "No announcements found.", 404);
|
||||
if (!broadcasts.length) return R.error(res, "No alerts found.", 404);
|
||||
|
||||
const archived = broadcasts.filter((b) => b.deletedAt);
|
||||
if (!archived.length) return R.error(res, "All selected announcements must be archived before they can be permanently deleted.", 400);
|
||||
if (!archived.length) return R.error(res, "All selected alerts must be archived before they can be permanently deleted.", 400);
|
||||
|
||||
const archivedIds = archived.map((b) => b.broadcast_id);
|
||||
|
||||
await NotificationBroadcast.destroy({ where: { broadcast_id: { [Op.in]: archivedIds } }, force: true });
|
||||
|
||||
logActivity(req.user?.user_id, 'bulk_permanently_delete_notification_broadcasts', { entityType: 'notification_broadcast', details: { ids: archivedIds, count: archivedIds.length } });
|
||||
return R.success(res, `${archivedIds.length} announcement(s) permanently deleted.`, {
|
||||
return R.success(res, `${archivedIds.length} alert(s) permanently deleted.`, {
|
||||
deleted_ids: archivedIds,
|
||||
skipped_ids: ids.filter((id) => !archivedIds.includes(id)),
|
||||
});
|
||||
} catch (err) {
|
||||
console.error("[NOTIFICATION BROADCAST][BULK PERMANENT DELETE]", err);
|
||||
return R.error(res, "Could not permanently delete announcements.", 500);
|
||||
return R.error(res, "Could not permanently delete alerts.", 500);
|
||||
}
|
||||
};
|
||||
|
||||
// ─── STICKY BANNER (shared, singleton) ────────────────────────────────────────
|
||||
// One image for the whole rotating sticky bar (up to 3 concurrent
|
||||
// announcements share it) — not one per announcement. Set from the
|
||||
// Announcements list page.
|
||||
|
||||
exports.getStickyBannerSetting = async (req, res) => {
|
||||
try {
|
||||
const setting = await StickyBannerSetting.findOne({ where: { id: 1 }, include: [IMAGE_INCLUDE] });
|
||||
if (!setting) return R.success(res, "Sticky banner setting retrieved.", { data: null });
|
||||
|
||||
const json = setting.toJSON();
|
||||
if (json.image) await attachImageStreamToken(json.image, req);
|
||||
|
||||
return R.success(res, "Sticky banner setting retrieved.", { data: json });
|
||||
} catch (err) {
|
||||
console.error("[NOTIFICATION BROADCAST][GET STICKY BANNER]", err);
|
||||
return R.error(res, "Could not retrieve sticky banner setting.", 500);
|
||||
}
|
||||
};
|
||||
|
||||
exports.updateStickyBannerSetting = async (req, res) => {
|
||||
try {
|
||||
const { image_asset_id, updatedBy } = req.body;
|
||||
|
||||
let validatedImageAssetId = null;
|
||||
if (image_asset_id) {
|
||||
const asset = await mdl_Assets.findOne({ where: { asset_id: image_asset_id, deletedAt: null } });
|
||||
if (!asset) return R.error(res, "Selected image asset was not found.", 400);
|
||||
validatedImageAssetId = asset.asset_id;
|
||||
}
|
||||
|
||||
// Plain find-then-create/update rather than findOrCreate() — Sequelize's
|
||||
// postgres findOrCreate() relies on a temp PL/pgSQL function for atomicity
|
||||
// that CockroachDB doesn't support ("cannot create user-defined functions
|
||||
// under a temporary schema") — same fix as trustedDevice.service.js.
|
||||
let setting = await StickyBannerSetting.findOne({ where: { id: 1 } });
|
||||
if (setting) {
|
||||
setting.image_asset_id = validatedImageAssetId;
|
||||
setting.updatedBy = updatedBy ?? null;
|
||||
await setting.save();
|
||||
} else {
|
||||
setting = await StickyBannerSetting.create({ id: 1, image_asset_id: validatedImageAssetId, updatedBy: updatedBy ?? null });
|
||||
}
|
||||
|
||||
// Reload with the image association so the response carries a fully
|
||||
// resolved preview (stream token for S3) — same shape as the GET, so the
|
||||
// frontend never needs to locally guess/merge in an optimistic image.
|
||||
await setting.reload({ include: [IMAGE_INCLUDE] });
|
||||
const json = setting.toJSON();
|
||||
if (json.image) await attachImageStreamToken(json.image, req);
|
||||
|
||||
logActivity(req.user?.user_id, 'update_sticky_banner_setting', { entityType: 'sticky_banner_setting', entityId: 1 });
|
||||
return R.success(res, "Sticky banner setting updated.", { data: json });
|
||||
} catch (err) {
|
||||
console.error("[NOTIFICATION BROADCAST][UPDATE STICKY BANNER]", err);
|
||||
return R.error(res, "Could not update sticky banner setting.", 500);
|
||||
}
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user