← back to Norma

app/api/statements/[id]/route.ts

134 lines

import { NextRequest, NextResponse } from 'next/server';
import { query } from '@/lib/db';
import { requireRole } from '@/lib/require-role';
import { auditLog } from '@/lib/audit';
import { getOrgId } from '@/lib/orgId';

type RouteContext = { params: Promise<{ id: string }> };

/**
 * GET /api/statements/[id]
 */
export async function GET(request: NextRequest, ctx: RouteContext) {
  const auth = requireRole(request, 'admin', 'staff');
  if (auth instanceof NextResponse) return auth;

  const { id } = await ctx.params;
  const orgId = auth.role === 'admin' ? getOrgId(request) : auth.orgId;
  const orgFilter = orgId ? ' AND org_id = $2' : '';
  const result = await query(`SELECT * FROM statements WHERE id = $1${orgFilter}`, orgId ? [id, orgId] : [id]);

  if (!result.rows[0]) {
    return NextResponse.json({ error: 'Statement not found' }, { status: 404 });
  }

  return NextResponse.json({ statement: result.rows[0] });
}

/**
 * PUT /api/statements/[id]
 * Update a statement. Saves previous version before overwriting.
 */
export async function PUT(request: NextRequest, ctx: RouteContext) {
  const auth = requireRole(request, 'admin', 'staff');
  if (auth instanceof NextResponse) return auth;

  const { id } = await ctx.params;

  try {
    const data = await request.json();

    const orgId = auth.role === 'admin' ? getOrgId(request) : auth.orgId;
    const orgFilter = orgId ? ' AND org_id = $2' : '';
    // Fetch current version to save history
    const current = await query(`SELECT * FROM statements WHERE id = $1${orgFilter}`, orgId ? [id, orgId] : [id]);
    if (!current.rows[0]) {
      return NextResponse.json({ error: 'Statement not found' }, { status: 404 });
    }
    const prev = current.rows[0];

    // Save version history
    await query(
      `INSERT INTO statement_versions (statement_id, version, title, body_html, body_text, quote, edited_by)
       VALUES ($1, $2, $3, $4, $5, $6, $7)`,
      [id, prev.version, prev.title, prev.body_html, prev.body_text, prev.quote, 'user'],
    );

    // Build dynamic update
    const fields: string[] = [];
    const values: unknown[] = [];
    let idx = 1;

    const allowedFields = [
      'title', 'body_html', 'body_text', 'quote', 'quote_attribution',
      'event_type', 'event_title', 'event_summary', 'event_url', 'event_date',
      'tone', 'category', 'urgency', 'tags', 'status',
      'reviewed_by', 'review_notes',
    ];

    for (const field of allowedFields) {
      if (data[field] !== undefined) {
        fields.push(`${field} = $${idx++}`);
        values.push(data[field]);
      }
    }

    if (fields.length === 0) {
      return NextResponse.json({ error: 'No fields to update' }, { status: 400 });
    }

    // Auto-set review timestamp when status changes to reviewed/approved
    if (data.status === 'reviewed' || data.status === 'approved') {
      fields.push(`reviewed_at = NOW()`);
    }
    if (data.status === 'published') {
      fields.push(`published_at = NOW()`);
    }

    fields.push(`version = version + 1`);
    fields.push(`updated_at = NOW()`);

    values.push(id);
    idx++;
    let stmtOrgFilter = '';
    if (orgId) {
      values.push(orgId);
      stmtOrgFilter = ` AND org_id = $${idx}`;
    }
    const result = await query(
      `UPDATE statements SET ${fields.join(', ')} WHERE id = $${idx - 1}${stmtOrgFilter} RETURNING *`,
      values,
    );

    const ip = request.headers.get('x-forwarded-for') || request.headers.get('x-real-ip') || undefined;
    await auditLog('statement.updated', 'statement', id, { fields: Object.keys(data) }, ip);

    return NextResponse.json({ statement: result.rows[0] });
  } catch (err) {
    console.error('[api/statements/[id]] PUT error:', (err as Error).message);
    return NextResponse.json({ error: 'Failed to update statement' }, { status: 500 });
  }
}

/**
 * DELETE /api/statements/[id]
 */
export async function DELETE(request: NextRequest, ctx: RouteContext) {
  const auth = requireRole(request, 'admin', 'staff');
  if (auth instanceof NextResponse) return auth;

  const { id } = await ctx.params;
  const orgId = auth.role === 'admin' ? getOrgId(request) : auth.orgId;
  const orgFilter = orgId ? ' AND org_id = $2' : '';
  const result = await query(`DELETE FROM statements WHERE id = $1${orgFilter} RETURNING id`, orgId ? [id, orgId] : [id]);

  if (!result.rows[0]) {
    return NextResponse.json({ error: 'Statement not found' }, { status: 404 });
  }

  const ip = request.headers.get('x-forwarded-for') || request.headers.get('x-real-ip') || undefined;
  await auditLog('statement.deleted', 'statement', id, {}, ip);

  return NextResponse.json({ deleted: true });
}