import { Router } from 'express'; import { query } from '../config/db.js'; import { requireAuth } from '../middleware/auth.js'; import { getRole } from './guards.js'; const router = Router(); router.use(requireAuth); // GET /api/chat/:workspaceId — last 100 messages router.get('/:workspaceId', async (req, res) => { const role = await getRole(req.params.workspaceId, req.user.id); if (!role) return res.status(403).json({ error: 'Not a workspace member' }); const { rows } = await query( `SELECT m.*, u.name AS author_name, u.avatar AS author_avatar FROM chat_messages m LEFT JOIN users u ON u.id = m.user_id WHERE m.workspace_id = $1 ORDER BY m.created_at DESC LIMIT 100`, [req.params.workspaceId], ); return res.json({ messages: rows.reverse() }); }); // POST /api/chat/:workspaceId { body } — post + realtime broadcast + notify others router.post('/:workspaceId', async (req, res) => { const wsId = req.params.workspaceId; const role = await getRole(wsId, req.user.id); if (!role) return res.status(403).json({ error: 'Not a workspace member' }); const { body = '' } = req.body ?? {}; if (String(body).trim().length < 1 || String(body).length > 2000) { return res.status(400).json({ error: 'Body must be 1-2000 chars' }); } const { rows } = await query( 'INSERT INTO chat_messages (workspace_id, user_id, body) VALUES ($1,$2,$3) RETURNING *', [wsId, req.user.id, String(body).trim()], ); const { rows: author } = await query('SELECT name, avatar FROM users WHERE id = $1', [req.user.id]); const msg = { ...rows[0], author_name: author[0]?.name, author_avatar: author[0]?.avatar }; const io = req.app.get('io'); io?.to(`chat:${wsId}`).emit('chat:message', { message: msg }); // Notify all other members const { rows: members } = await query( 'SELECT user_id FROM workspace_members WHERE workspace_id = $1 AND user_id <> $2', [wsId, req.user.id], ); const { rows: wsRows } = await query('SELECT name FROM workspaces WHERE id = $1', [wsId]); for (const m of members) { await query( `INSERT INTO notifications (user_id, type, title, body, link) VALUES ($1,'chat',$2,$3,$4)`, [m.user_id, `💬 ${author[0]?.name || 'Someone'} in ${wsRows[0]?.name || ''}`, String(body).trim().slice(0, 140), '/chat'], ); io?.to(`user:${m.user_id}`).emit('notification:new', { type: 'chat' }); } return res.status(201).json({ message: msg }); }); // DELETE /api/chat/message/:id (own or admin+) router.delete('/message/:id', async (req, res) => { const { rows } = await query('SELECT * FROM chat_messages WHERE id = $1', [req.params.id]); const msg = rows[0]; if (!msg) return res.status(404).json({ error: 'Not found' }); const role = await getRole(msg.workspace_id, req.user.id); const { rows: me } = await query('SELECT is_admin FROM users WHERE id = $1', [req.user.id]); if (msg.user_id !== req.user.id && !me[0]?.is_admin && role !== 'owner' && role !== 'admin') { return res.status(403).json({ error: 'Forbidden' }); } await query('DELETE FROM chat_messages WHERE id = $1', [msg.id]); req.app.get('io')?.to(`chat:${msg.workspace_id}`).emit('chat:deleted', { id: msg.id }); return res.status(204).end(); }); export default router;