v0.11.14 event message

This commit is contained in:
2026-03-21 21:57:05 -04:00
parent 69515c9e95
commit 8a2ca50032
13 changed files with 65 additions and 8 deletions

View File

@@ -1,6 +1,6 @@
{
"name": "jama-backend",
"version": "0.11.13",
"version": "0.11.14",
"description": "TeamChat backend server",
"main": "src/index.js",
"scripts": {

View File

@@ -35,7 +35,7 @@ app.use('/api/users', require('./routes/users'));
app.use('/api/groups', require('./routes/groups')(io));
app.use('/api/messages', require('./routes/messages')(io));
app.use('/api/usergroups', require('./routes/usergroups')(io));
app.use('/api/schedule', require('./routes/schedule'));
app.use('/api/schedule', require('./routes/schedule')(io));
app.use('/api/settings', require('./routes/settings'));
app.use('/api/about', require('./routes/about'));
app.use('/api/help', require('./routes/help'));

View File

@@ -1,11 +1,56 @@
const express = require('express');
const router = express.Router();
const { query, queryOne, queryResult, exec } = require('../models/db');
const { authMiddleware, teamManagerMiddleware } = require('../middleware/auth');
const multer = require('multer');
const { parse: csvParse } = require('csv-parse/sync');
const upload = multer({ storage: multer.memoryStorage(), limits: { fileSize: 2 * 1024 * 1024 } });
const R = (schema, type, id) => `${schema}:${type}:${id}`;
module.exports = function(io) {
const router = express.Router();
// ── Event notification helper ─────────────────────────────────────────────────
// 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) {
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 groups = await query(schema, `
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); }
}
} catch (e) {
console.error('[Schedule] postEventNotification error:', e.message);
}
}
// ── Helpers ───────────────────────────────────────────────────────────────────
async function isToolManagerFn(schema, user) {
@@ -212,6 +257,8 @@ router.post('/', authMiddleware, teamManagerMiddleware, async (req, res) => {
const eventId = r.rows[0].id;
for (const ugId of (Array.isArray(userGroupIds) ? userGroupIds : []))
await exec(req.schema, 'INSERT INTO event_user_groups (event_id,user_group_id) VALUES ($1,$2) ON CONFLICT DO NOTHING', [eventId, ugId]);
if (Array.isArray(userGroupIds) && userGroupIds.length > 0)
await postEventNotification(req.schema, eventId, req.user.id, false);
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 }); }
@@ -255,6 +302,9 @@ router.patch('/:id', authMiddleware, teamManagerMiddleware, 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);
res.json({ event: await enrichEvent(req.schema, updated) });
} catch (e) { res.status(500).json({ error: e.message }); }
});
@@ -375,4 +425,5 @@ router.post('/import/confirm', authMiddleware, teamManagerMiddleware, async (req
} catch (e) { res.status(500).json({ error: e.message }); }
});
module.exports = router;
return router;
}; // end module.exports

View File

@@ -13,7 +13,7 @@
# ─────────────────────────────────────────────────────────────
set -euo pipefail
VERSION="${1:-0.11.13}"
VERSION="${1:-0.11.14}"
ACTION="${2:-}"
REGISTRY="${REGISTRY:-}"
IMAGE_NAME="jama"

View File

@@ -1,6 +1,6 @@
{
"name": "jama-frontend",
"version": "0.11.13",
"version": "0.11.14",
"private": true,
"scripts": {
"dev": "vite",

Binary file not shown.

Before

Width:  |  Height:  |  Size: 786 B

After

Width:  |  Height:  |  Size: 682 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 60 KiB

After

Width:  |  Height:  |  Size: 64 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 43 KiB

After

Width:  |  Height:  |  Size: 115 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 43 KiB

After

Width:  |  Height:  |  Size: 115 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.1 KiB

After

Width:  |  Height:  |  Size: 4.1 KiB

View File

@@ -22,10 +22,16 @@
"purpose": "maskable"
},
{
"purpose": "any maskable",
"src": "/icons/icon-512.png",
"sizes": "512x512",
"type": "image/png",
"purpose": "any"
},
{
"src": "/icons/icon-512-maskable.png",
"sizes": "512x512",
"type": "image/png"
"type": "image/png",
"purpose": "maskable"
}
],
"min_width": "320px"