← back to Professional Directory

agents/api-agent/routes/messages.js

105 lines

/**
 * /messages — direct messages between any two patients/doctors.
 *
 *   GET  /messages                     — my DM pair index
 *   GET  /messages/:pairId             — message history
 *   POST /messages/:pairId/read        — mark all incoming as read
 *   POST /messages                     — send (paid tier required)
 *
 * Disclaimer banner enforced client-side; server-side adds the
 * "not medical advice" prefix to every outgoing message body.
 */
const express = require('express');
const { query } = require('../../shared/db');
const { requireTier } = require('../auth');

const router = express.Router();

router.use((req, res, next) => {
  if (!req.user) return res.status(401).json({ error: 'auth required' });
  next();
});

// GET /messages — list my DM pairs with last-message preview + unread count.
router.get('/', async (req, res, next) => {
  try {
    const me = req.user.id;
    const r = await query(`
      SELECT p.id, p.user_a_id, p.user_b_id, p.last_message_at,
             other.display_name AS other_display, other.role AS other_role,
             other.id AS other_id,
             (SELECT body FROM messages m WHERE m.dm_pair_id = p.id ORDER BY m.sent_at DESC LIMIT 1) AS last_body,
             (SELECT COUNT(*) FROM messages m WHERE m.dm_pair_id = p.id AND m.author_user_id <> $1 AND m.read_at IS NULL) AS unread
        FROM dm_pairs p
        JOIN users other ON other.id = (CASE WHEN p.user_a_id = $1 THEN p.user_b_id ELSE p.user_a_id END)
       WHERE p.user_a_id = $1 OR p.user_b_id = $1
       ORDER BY p.last_message_at DESC NULLS LAST
       LIMIT 100`, [me]);
    res.json({ count: r.rowCount, rows: r.rows });
  } catch (e) { next(e); }
});

// GET /messages/:pairId — message history.
router.get('/:pairId', async (req, res, next) => {
  try {
    const pid = Number(req.params.pairId);
    const me = req.user.id;
    const pair = (await query(`SELECT * FROM dm_pairs WHERE id=$1`, [pid])).rows[0];
    if (!pair || (pair.user_a_id != me && pair.user_b_id != me && req.user.role !== 'admin')) {
      return res.status(404).json({ error: 'not found' });
    }
    const msgs = (await query(`
      SELECT id, author_user_id, body, sent_at, read_at
        FROM messages WHERE dm_pair_id = $1 ORDER BY sent_at LIMIT 500`, [pid])).rows;
    res.json({ pair, messages: msgs });
  } catch (e) { next(e); }
});

// POST /messages — send a DM. body: { to_user_id, body }
router.post('/', requireTier('paid'), async (req, res, next) => {
  try {
    const toUser = Number(req.body?.to_user_id);
    const myId = Number(req.user.id);   // PG bigint comes back as string
    const body = String(req.body?.body || '').trim();
    if (!toUser || toUser === myId) return res.status(400).json({ error: 'invalid to_user_id (cannot DM yourself)' });
    if (body.length < 1 || body.length > 5000) return res.status(400).json({ error: 'body 1-5000 chars' });

    // Verify target user exists and isn't deleted.
    const u = (await query(`SELECT id, role, deleted_at FROM users WHERE id=$1`, [toUser])).rows[0];
    if (!u || u.deleted_at) return res.status(404).json({ error: 'recipient not found' });

    // Canonical pair: smaller id first.
    const aId = Math.min(myId, toUser);
    const bId = Math.max(myId, toUser);
    let pair = (await query(`SELECT id FROM dm_pairs WHERE user_a_id=$1 AND user_b_id=$2`, [aId, bId])).rows[0];
    if (!pair) {
      pair = (await query(`INSERT INTO dm_pairs (user_a_id, user_b_id) VALUES ($1, $2) RETURNING id`, [aId, bId])).rows[0];
    }

    // Server-side disclaimer prefix on every message — defensive.
    const safeBody = body.startsWith('[Not medical advice]')
      ? body
      : '[Not medical advice — for clinical questions, call the office.]\n\n' + body;

    const m = await query(`
      INSERT INTO messages (dm_pair_id, author_user_id, body) VALUES ($1, $2, $3)
      RETURNING id, sent_at`, [pair.id, req.user.id, safeBody]);

    res.json({ ok: true, message_id: m.rows[0].id, dm_pair_id: pair.id });
  } catch (e) { next(e); }
});

// POST /messages/:pairId/read — mark incoming as read.
router.post('/:pairId/read', async (req, res, next) => {
  try {
    const pid = Number(req.params.pairId);
    await query(`
      UPDATE messages SET read_at = now()
       WHERE dm_pair_id = $1 AND author_user_id <> $2 AND read_at IS NULL`,
      [pid, req.user.id]);
    res.json({ ok: true });
  } catch (e) { next(e); }
});

module.exports = router;