← back to Norma

app/api/drafts/[id]/versions/route.ts

109 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/drafts/[id]/versions
 * List all versions for a draft, ordered by version_number DESC.
 */
export async function GET(request: NextRequest, context: RouteContext) {
  const auth = requireRole(request, 'admin', 'staff');
  if (auth instanceof NextResponse) return auth;

  const orgId = auth.role === 'admin' ? getOrgId(request) : auth.orgId;
  if (!orgId) {
    return NextResponse.json({ error: 'Missing org context' }, { status: 400 });
  }

  const { id } = await context.params;

  try {
    // Verify draft exists and belongs to org
    const draftCheck = await query(`SELECT id FROM drafts WHERE id = $1 AND org_id = $2`, [id, orgId]);
    if (draftCheck.rowCount === 0) {
      return NextResponse.json({ error: 'Draft not found' }, { status: 404 });
    }

    const result = await query(
      `SELECT * FROM draft_versions
       WHERE draft_id = $1
       ORDER BY version_number DESC`,
      [id]
    );

    return NextResponse.json({ versions: result.rows });
  } catch (err) {
    console.error('[api/drafts/[id]/versions] GET error:', (err as Error).message);
    return NextResponse.json({ error: 'Failed to fetch versions' }, { status: 500 });
  }
}

/**
 * POST /api/drafts/[id]/versions
 * Manually save a version snapshot. Copies current draft content into a new version entry.
 * Body: { change_summary?: string }
 */
export async function POST(request: NextRequest, context: RouteContext) {
  const auth = requireRole(request, 'admin', 'staff');
  if (auth instanceof NextResponse) return auth;

  const orgId = auth.role === 'admin' ? getOrgId(request) : auth.orgId;
  if (!orgId) {
    return NextResponse.json({ error: 'Missing org context' }, { status: 400 });
  }

  const { id } = await context.params;

  try {
    const body = await request.json().catch(() => ({}));
    const changeSummary = body.change_summary || 'Manual snapshot';

    // Fetch the current draft
    const draftResult = await query(`SELECT * FROM drafts WHERE id = $1 AND org_id = $2`, [id, orgId]);
    if (draftResult.rowCount === 0) {
      return NextResponse.json({ error: 'Draft not found' }, { status: 404 });
    }

    const draft = draftResult.rows[0];

    // Create the version snapshot using current_version as the version_number
    const versionResult = await query(
      `INSERT INTO draft_versions (draft_id, version_number, subject, body_html, body_text, change_summary, created_by)
       VALUES ($1, $2, $3, $4, $5, $6, $7)
       RETURNING *`,
      [
        id,
        draft.current_version,
        draft.subject,
        draft.body_html,
        draft.body_text || '',
        changeSummary, auth.username,
      ]
    );

    // Increment the draft's current_version
    await query(
      `UPDATE drafts SET current_version = current_version + 1 WHERE id = $1 AND org_id = $2`,
      [id, orgId]
    );

    const ip = request.headers.get('x-forwarded-for') || request.headers.get('x-real-ip') || undefined;
    await auditLog(
      'draft.version_saved',
      'draft',
      id,
      { version_number: draft.current_version, change_summary: changeSummary },
      ip
    );

    return NextResponse.json({ version: versionResult.rows[0] }, { status: 201 });
  } catch (err) {
    console.error('[api/drafts/[id]/versions] POST error:', (err as Error).message);
    return NextResponse.json({ error: 'Failed to save version' }, { status: 500 });
  }
}