From 36e1be8f40c406a44fa3d996a917be8c1ef578c5 Mon Sep 17 00:00:00 2001 From: Ricky Stretch Date: Sat, 28 Mar 2026 21:23:19 -0400 Subject: [PATCH] event notification update --- backend/src/routes/schedule.js | 97 ++++++++++++++++++++++++---------- 1 file changed, 69 insertions(+), 28 deletions(-) diff --git a/backend/src/routes/schedule.js b/backend/src/routes/schedule.js index f6e62da..cc0516e 100644 --- a/backend/src/routes/schedule.js +++ b/backend/src/routes/schedule.js @@ -15,37 +15,32 @@ const router = express.Router(); // Posts a plain system message to each assigned user group's DM channel // when an event is created or updated. -async function postEventNotification(schema, eventId, actorId, isUpdate) { +async function sendEventMessage(schema, dmGroupId, actorId, content) { + const r = await queryResult(schema, + "INSERT INTO messages (group_id,user_id,content,type) VALUES ($1,$2,$3,'system') RETURNING id", + [dmGroupId, actorId, content] + ); + const msg = await queryOne(schema, ` + SELECT m.*, u.name AS user_name, u.display_name AS user_display_name, + u.avatar AS user_avatar, u.role AS user_role, u.status AS user_status, + u.hide_admin_tag AS user_hide_admin_tag, u.about_me AS user_about_me, u.allow_dm AS user_allow_dm + FROM messages m JOIN users u ON m.user_id = u.id WHERE m.id = $1 + `, [r.rows[0].id]); + if (msg) { msg.reactions = []; io.to(R(schema, 'group', dmGroupId)).emit('message:new', msg); } +} + +async function postEventNotification(schema, eventId, actorId) { try { const event = await queryOne(schema, 'SELECT * FROM events WHERE id=$1', [eventId]); if (!event) return; - - const dateStr = new Date(event.start_at).toLocaleDateString('en-US', { - weekday: 'short', month: 'short', day: 'numeric', - }); - const verb = isUpdate ? 'updated' : 'added'; - const content = `📅 Event ${verb}: "${event.title}" on ${dateStr}`; - + const dateStr = new Date(event.start_at).toLocaleDateString('en-US', { weekday:'short', month:'short', day:'numeric' }); const groups = await query(schema, ` - SELECT ug.dm_group_id - FROM event_user_groups eug + SELECT ug.dm_group_id FROM event_user_groups eug JOIN user_groups ug ON ug.id = eug.user_group_id WHERE eug.event_id = $1 AND ug.dm_group_id IS NOT NULL `, [eventId]); - - for (const { dm_group_id } of groups) { - const r = await queryResult(schema, - "INSERT INTO messages (group_id,user_id,content,type) VALUES ($1,$2,$3,'system') RETURNING id", - [dm_group_id, actorId, content] - ); - const msg = await queryOne(schema, ` - SELECT m.*, u.name AS user_name, u.display_name AS user_display_name, - u.avatar AS user_avatar, u.role AS user_role, u.status AS user_status, - u.hide_admin_tag AS user_hide_admin_tag, u.about_me AS user_about_me, u.allow_dm AS user_allow_dm - FROM messages m JOIN users u ON m.user_id = u.id WHERE m.id = $1 - `, [r.rows[0].id]); - if (msg) { msg.reactions = []; io.to(R(schema, 'group', dm_group_id)).emit('message:new', msg); } - } + for (const { dm_group_id } of groups) + await sendEventMessage(schema, dm_group_id, actorId, `📅 Event added: "${event.title}" on ${dateStr}`); } catch (e) { console.error('[Schedule] postEventNotification error:', e.message); } @@ -291,7 +286,7 @@ router.post('/', authMiddleware, async (req, res) => { for (const ugId of groupIds) await exec(req.schema, 'INSERT INTO event_user_groups (event_id,user_group_id) VALUES ($1,$2) ON CONFLICT DO NOTHING', [eventId, ugId]); if (groupIds.length > 0) - await postEventNotification(req.schema, eventId, req.user.id, false); + await postEventNotification(req.schema, eventId, req.user.id); const event = await queryOne(req.schema, 'SELECT * FROM events WHERE id=$1', [eventId]); res.json({ event: await enrichEvent(req.schema, event) }); } catch (e) { res.status(500).json({ error: e.message }); } @@ -317,6 +312,14 @@ router.patch('/:id', authMiddleware, async (req, res) => { } const fields = { title, eventTypeId, startAt, endAt, allDay, location, description, isPublic, trackAvailability, recurrenceRule, origEvent: event }; + // Capture group/DM mapping before applyEventUpdate modifies event_user_groups + const prevGroupRows = await query(req.schema, ` + SELECT eug.user_group_id, ug.dm_group_id FROM event_user_groups eug + JOIN user_groups ug ON ug.id=eug.user_group_id + WHERE eug.event_id=$1 AND ug.dm_group_id IS NOT NULL + `, [req.params.id]); + const prevGroupIdSet = new Set(prevGroupRows.map(r => r.user_group_id)); + await applyEventUpdate(req.schema, req.params.id, fields, userGroupIds); // Recurring future scope — update this and all future occurrences @@ -356,9 +359,47 @@ router.patch('/:id', authMiddleware, async (req, res) => { } const updated = await queryOne(req.schema, 'SELECT * FROM events WHERE id=$1', [req.params.id]); - const finalGroups = await query(req.schema, 'SELECT user_group_id FROM event_user_groups WHERE event_id=$1', [req.params.id]); - if (finalGroups.length > 0) - await postEventNotification(req.schema, req.params.id, req.user.id, true); + + // Targeted notifications — only for meaningful changes, only to relevant groups + try { + const finalGroupRows = await query(req.schema, ` + SELECT eug.user_group_id, ug.dm_group_id FROM event_user_groups eug + JOIN user_groups ug ON ug.id=eug.user_group_id + WHERE eug.event_id=$1 AND ug.dm_group_id IS NOT NULL + `, [req.params.id]); + const allDmIds = finalGroupRows.map(r => r.dm_group_id); + const dateStr = new Date(updated.start_at).toLocaleDateString('en-US', { weekday:'short', month:'short', day:'numeric' }); + + // Newly added groups → "Event added" only to those groups + if (Array.isArray(userGroupIds)) { + for (const { user_group_id, dm_group_id } of finalGroupRows) { + if (!prevGroupIdSet.has(user_group_id)) + await sendEventMessage(req.schema, dm_group_id, req.user.id, `📅 Event added: "${updated.title}" on ${dateStr}`); + } + } + + // Date/time changed → "Event updated" to all groups + const timeChanged = (startAt && new Date(startAt).getTime() !== new Date(event.start_at).getTime()) + || (endAt && new Date(endAt).getTime() !== new Date(event.end_at).getTime()) + || (allDay !== undefined && !!allDay !== !!event.all_day); + if (timeChanged) { + for (const dmId of allDmIds) + await sendEventMessage(req.schema, dmId, req.user.id, `📅 Event updated: "${updated.title}" on ${dateStr}`); + } + + // Location changed → "Location updated" to all groups + const locationChanged = location !== undefined && (location || null) !== (event.location || null); + if (locationChanged) { + const locContent = updated.location + ? `📍 Location updated to "${updated.location}": "${updated.title}" on ${dateStr}` + : `📍 Location removed: "${updated.title}" on ${dateStr}`; + for (const dmId of allDmIds) + await sendEventMessage(req.schema, dmId, req.user.id, locContent); + } + } catch (e) { + console.error('[Schedule] event update notification error:', e.message); + } + res.json({ event: await enrichEvent(req.schema, updated) }); } catch (e) { res.status(500).json({ error: e.message }); } });