← back to Professional Directory

agents/api-agent/routes/threads.js

119 lines

/**
 * /threads — community discussion threads.
 *
 *   GET  /threads                — list (any user)
 *   GET  /threads/:id            — single thread + posts
 *   POST /threads                — create (paid tier)
 *   POST /threads/:id/posts      — reply (paid tier)
 *   POST /admin/threads/:id/hide — admin moderation
 */
const express = require('express');
const { query } = require('../../shared/db');
const { requireRole, requireTier } = require('../auth');

const router = express.Router();

router.get('/', async (req, res, next) => {
  try {
    const limit = Math.min(Number(req.query.limit) || 50, 100);
    const cat   = req.query.category ? String(req.query.category) : null;
    const city  = req.query.city ? String(req.query.city) : null;
    const where = ['hidden_by_admin = false']; const params = [];
    if (cat)  { params.push(cat);  where.push(`category = $${params.length}`); }
    if (city) { params.push(city); where.push(`city ILIKE $${params.length}`); }
    const r = await query(`
      SELECT t.id, t.title, t.body, t.category, t.city, t.zip, t.organization_id,
             t.reply_count, t.last_post_at, t.created_at,
             u.display_name AS author, u.role AS author_role
        FROM threads t
   LEFT JOIN users u ON u.id = t.created_by_user_id
       WHERE ${where.join(' AND ')}
       ORDER BY t.pinned DESC, t.last_post_at DESC
       LIMIT ${limit}
    `, params);
    res.json({ count: r.rowCount, rows: r.rows });
  } catch (e) { next(e); }
});

router.get('/:id', async (req, res, next) => {
  try {
    const id = Number(req.params.id);
    const t = (await query(`
      SELECT t.*, u.display_name AS author, u.role AS author_role
        FROM threads t
   LEFT JOIN users u ON u.id = t.created_by_user_id
       WHERE t.id = $1 AND t.hidden_by_admin = false`, [id])).rows[0];
    if (!t) return res.status(404).json({ error: 'not found' });
    const posts = (await query(`
      SELECT p.id, p.body, p.created_at,
             u.display_name AS author, u.role AS author_role
        FROM thread_posts p
   LEFT JOIN users u ON u.id = p.author_user_id
       WHERE p.thread_id = $1 AND p.hidden_by_admin = false
       ORDER BY p.created_at`, [id])).rows;
    res.json({ thread: t, posts });
  } catch (e) { next(e); }
});

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

router.post('/', requireTier('paid'), async (req, res, next) => {
  try {
    const b = req.body || {};
    const title = String(b.title || '').trim().slice(0, 250);
    const body  = String(b.body  || '').trim().slice(0, 10_000);
    if (title.length < 5) return res.status(400).json({ error: 'title 5+ chars' });
    const cat = ['general','recommendations','specialty','insurance','pediatrics','eldercare','mental_health','dental','optometry','question','rant'].includes(b.category) ? b.category : 'general';
    const city = b.city ? String(b.city).slice(0, 80) : null;
    const orgId = b.organization_id ? Number(b.organization_id) : null;
    const r = await query(`
      INSERT INTO threads (title, body, city, organization_id, category, created_by_user_id)
      VALUES ($1, $2, $3, $4, $5, $6) RETURNING id, created_at`,
      [title, body || null, city, orgId, cat, req.user.id]);
    res.json({ ok: true, thread_id: r.rows[0].id });
  } catch (e) { next(e); }
});

router.post('/:id/posts', requireTier('paid'), async (req, res, next) => {
  try {
    const id = Number(req.params.id);
    const body = String(req.body?.body || '').trim();
    if (body.length < 2 || body.length > 10_000) return res.status(400).json({ error: 'body 2-10000 chars' });
    const t = (await query(`SELECT id FROM threads WHERE id=$1 AND hidden_by_admin=false`, [id])).rows[0];
    if (!t) return res.status(404).json({ error: 'thread not found' });
    const r = await query(`
      INSERT INTO thread_posts (thread_id, author_user_id, body)
      VALUES ($1, $2, $3) RETURNING id, created_at`,
      [id, req.user.id, body]);
    res.json({ ok: true, post_id: r.rows[0].id });
  } catch (e) { next(e); }
});

// ─── /admin/threads moderation ─────────────────────────────────────────────
const adminRouter = express.Router();
adminRouter.use(requireRole('admin'));

adminRouter.post('/:id/hide', async (req, res, next) => {
  try {
    const id = Number(req.params.id);
    const flag = req.body?.hidden !== false;
    await query(`UPDATE threads SET hidden_by_admin=$2, updated_at=now() WHERE id=$1`, [id, flag]);
    res.json({ ok: true, hidden: flag });
  } catch (e) { next(e); }
});

adminRouter.post('/posts/:id/hide', async (req, res, next) => {
  try {
    const id = Number(req.params.id);
    const flag = req.body?.hidden !== false;
    await query(`UPDATE thread_posts SET hidden_by_admin=$2, updated_at=now() WHERE id=$1`, [id, flag]);
    res.json({ ok: true, hidden: flag });
  } catch (e) { next(e); }
});

module.exports = { router, adminRouter };