v0.8.5 fix pinning
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "jama-backend",
|
||||
"version": "0.8.4",
|
||||
"version": "0.8.5",
|
||||
"description": "TeamChat backend server",
|
||||
"main": "src/index.js",
|
||||
"scripts": {
|
||||
|
||||
@@ -207,23 +207,20 @@ function initDb() {
|
||||
console.log('[DB] Migration: user_group_names table ready');
|
||||
} catch (e) { console.error('[DB] user_group_names migration error:', e.message); }
|
||||
|
||||
// Migration: pinned messages within DMs (per-user, up to 5 per DM group)
|
||||
// Migration: pinned conversations (per-user, pins a group to top of sidebar)
|
||||
try {
|
||||
db.exec(`
|
||||
CREATE TABLE IF NOT EXISTS pinned_messages (
|
||||
user_id INTEGER NOT NULL,
|
||||
message_id INTEGER NOT NULL,
|
||||
group_id INTEGER NOT NULL,
|
||||
pinned_at TEXT NOT NULL DEFAULT (datetime('now')),
|
||||
pin_order INTEGER NOT NULL DEFAULT 0,
|
||||
PRIMARY KEY (user_id, message_id),
|
||||
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE,
|
||||
FOREIGN KEY (message_id) REFERENCES messages(id) ON DELETE CASCADE,
|
||||
FOREIGN KEY (group_id) REFERENCES groups(id) ON DELETE CASCADE
|
||||
CREATE TABLE IF NOT EXISTS pinned_conversations (
|
||||
user_id INTEGER NOT NULL,
|
||||
group_id INTEGER NOT NULL,
|
||||
pinned_at TEXT NOT NULL DEFAULT (datetime('now')),
|
||||
PRIMARY KEY (user_id, group_id),
|
||||
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE,
|
||||
FOREIGN KEY (group_id) REFERENCES groups(id) ON DELETE CASCADE
|
||||
)
|
||||
`);
|
||||
console.log('[DB] Migration: pinned_messages table ready');
|
||||
} catch (e) { console.error('[DB] pinned_messages migration error:', e.message); }
|
||||
console.log('[DB] Migration: pinned_conversations table ready');
|
||||
} catch (e) { console.error('[DB] pinned_conversations migration error:', e.message); }
|
||||
|
||||
console.log('[DB] Schema initialized');
|
||||
return db;
|
||||
|
||||
@@ -53,11 +53,13 @@ router.get('/', authMiddleware, (req, res) => {
|
||||
(SELECT COUNT(*) FROM messages m WHERE m.group_id = g.id AND m.is_deleted = 0) as message_count,
|
||||
(SELECT m.content FROM messages m WHERE m.group_id = g.id AND m.is_deleted = 0 ORDER BY m.created_at DESC LIMIT 1) as last_message,
|
||||
(SELECT m.created_at FROM messages m WHERE m.group_id = g.id AND m.is_deleted = 0 ORDER BY m.created_at DESC LIMIT 1) as last_message_at,
|
||||
(SELECT m.user_id FROM messages m WHERE m.group_id = g.id AND m.is_deleted = 0 ORDER BY m.created_at DESC LIMIT 1) as last_message_user_id
|
||||
(SELECT m.user_id FROM messages m WHERE m.group_id = g.id AND m.is_deleted = 0 ORDER BY m.created_at DESC LIMIT 1) as last_message_user_id,
|
||||
CASE WHEN pc.group_id IS NOT NULL THEN 1 ELSE 0 END as is_pinned
|
||||
FROM groups g
|
||||
LEFT JOIN pinned_conversations pc ON pc.group_id = g.id AND pc.user_id = ?
|
||||
WHERE g.type = 'public'
|
||||
ORDER BY g.is_default DESC, g.name ASC
|
||||
`).all();
|
||||
ORDER BY is_pinned DESC, g.is_default DESC, g.name ASC
|
||||
`).all(userId);
|
||||
|
||||
// For direct messages, replace name with opposite user's display name
|
||||
const privateGroupsRaw = db.prepare(`
|
||||
@@ -66,13 +68,15 @@ router.get('/', authMiddleware, (req, res) => {
|
||||
(SELECT COUNT(*) FROM messages m WHERE m.group_id = g.id AND m.is_deleted = 0) as message_count,
|
||||
(SELECT m.content FROM messages m WHERE m.group_id = g.id AND m.is_deleted = 0 ORDER BY m.created_at DESC LIMIT 1) as last_message,
|
||||
(SELECT m.created_at FROM messages m WHERE m.group_id = g.id AND m.is_deleted = 0 ORDER BY m.created_at DESC LIMIT 1) as last_message_at,
|
||||
(SELECT m.user_id FROM messages m WHERE m.group_id = g.id AND m.is_deleted = 0 ORDER BY m.created_at DESC LIMIT 1) as last_message_user_id
|
||||
(SELECT m.user_id FROM messages m WHERE m.group_id = g.id AND m.is_deleted = 0 ORDER BY m.created_at DESC LIMIT 1) as last_message_user_id,
|
||||
CASE WHEN pc.group_id IS NOT NULL THEN 1 ELSE 0 END as is_pinned
|
||||
FROM groups g
|
||||
JOIN group_members gm ON g.id = gm.group_id AND gm.user_id = ?
|
||||
LEFT JOIN users u ON g.owner_id = u.id
|
||||
LEFT JOIN pinned_conversations pc ON pc.group_id = g.id AND pc.user_id = ?
|
||||
WHERE g.type = 'private'
|
||||
ORDER BY last_message_at DESC NULLS LAST
|
||||
`).all(userId);
|
||||
ORDER BY is_pinned DESC, last_message_at DESC NULLS LAST
|
||||
`).all(userId, userId);
|
||||
|
||||
// For direct groups, set the name to the other user's display name
|
||||
// Uses direct_peer1_id / direct_peer2_id so the name survives after a user leaves
|
||||
|
||||
@@ -173,71 +173,4 @@ router.post('/:id/reactions', authMiddleware, (req, res) => {
|
||||
});
|
||||
|
||||
|
||||
// Get pinned messages for a DM group
|
||||
router.get('/pinned', authMiddleware, (req, res) => {
|
||||
const db = getDb();
|
||||
const userId = req.user.id;
|
||||
const groupId = parseInt(req.query.groupId);
|
||||
if (!groupId) return res.status(400).json({ error: 'groupId required' });
|
||||
|
||||
// Verify membership
|
||||
const member = db.prepare('SELECT 1 FROM group_members WHERE group_id = ? AND user_id = ?').get(groupId, userId);
|
||||
if (!member) return res.status(403).json({ error: 'Not a member' });
|
||||
|
||||
const pinned = db.prepare(`
|
||||
SELECT m.id, m.content, m.image_url, m.created_at, m.user_id,
|
||||
u.name as user_name, u.display_name as user_display_name, u.avatar as user_avatar,
|
||||
pm.pin_order, pm.pinned_at
|
||||
FROM pinned_messages pm
|
||||
JOIN messages m ON pm.message_id = m.id
|
||||
JOIN users u ON m.user_id = u.id
|
||||
WHERE pm.user_id = ? AND pm.group_id = ? AND m.is_deleted = 0
|
||||
ORDER BY pm.pin_order ASC
|
||||
`).all(userId, groupId);
|
||||
|
||||
res.json({ pinned, count: pinned.length });
|
||||
});
|
||||
|
||||
// Pin a message in a DM
|
||||
router.post('/:id/pin', authMiddleware, (req, res) => {
|
||||
const db = getDb();
|
||||
const userId = req.user.id;
|
||||
const messageId = parseInt(req.params.id);
|
||||
|
||||
const msg = db.prepare('SELECT m.*, g.is_direct FROM messages m JOIN groups g ON m.group_id = g.id WHERE m.id = ? AND m.is_deleted = 0').get(messageId);
|
||||
if (!msg) return res.status(404).json({ error: 'Message not found' });
|
||||
if (!msg.is_direct) return res.status(400).json({ error: 'Can only pin messages in direct messages' });
|
||||
|
||||
// Verify membership
|
||||
const member = db.prepare('SELECT 1 FROM group_members WHERE group_id = ? AND user_id = ?').get(msg.group_id, userId);
|
||||
if (!member) return res.status(403).json({ error: 'Not a member' });
|
||||
|
||||
// Check limit (5 per group per user)
|
||||
const count = db.prepare('SELECT COUNT(*) as n FROM pinned_messages WHERE user_id = ? AND group_id = ?').get(userId, msg.group_id).n;
|
||||
if (count >= 5) return res.status(400).json({ error: 'Maximum 5 pinned messages per conversation' });
|
||||
|
||||
const maxOrder = db.prepare('SELECT MAX(pin_order) as m FROM pinned_messages WHERE user_id = ? AND group_id = ?').get(userId, msg.group_id).m || 0;
|
||||
|
||||
db.prepare('INSERT OR IGNORE INTO pinned_messages (user_id, message_id, group_id, pin_order) VALUES (?, ?, ?, ?)')
|
||||
.run(userId, messageId, msg.group_id, maxOrder + 1);
|
||||
|
||||
const newCount = db.prepare('SELECT COUNT(*) as n FROM pinned_messages WHERE user_id = ? AND group_id = ?').get(userId, msg.group_id).n;
|
||||
res.json({ ok: true, count: newCount });
|
||||
});
|
||||
|
||||
// Unpin a message
|
||||
router.delete('/:id/pin', authMiddleware, (req, res) => {
|
||||
const db = getDb();
|
||||
const userId = req.user.id;
|
||||
const messageId = parseInt(req.params.id);
|
||||
|
||||
const msg = db.prepare('SELECT group_id FROM messages WHERE id = ?').get(messageId);
|
||||
if (!msg) return res.status(404).json({ error: 'Message not found' });
|
||||
|
||||
db.prepare('DELETE FROM pinned_messages WHERE user_id = ? AND message_id = ?').run(userId, messageId);
|
||||
|
||||
const newCount = db.prepare('SELECT COUNT(*) as n FROM pinned_messages WHERE user_id = ? AND group_id = ?').get(userId, msg.group_id).n;
|
||||
res.json({ ok: true, count: newCount });
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
|
||||
Reference in New Issue
Block a user