← back to Norma

app/api/leads/route.ts

149 lines

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

/**
 * GET /api/leads?status=&type=&limit=&offset=
 * Returns old leads with computed days_cold. Supports pagination.
 */
export async function GET(request: NextRequest) {
  const auth = requireRole(request, 'admin', 'staff');
  if (auth instanceof NextResponse) return auth;

  const { searchParams } = new URL(request.url);
  const status = searchParams.get('status');
  const lType = searchParams.get('type');
  const orgId = auth.role === 'admin' ? getOrgId(request) : auth.orgId;

  let limit = parseInt(searchParams.get('limit') || '50', 10);
  if (isNaN(limit) || limit < 1) limit = 50;
  if (limit > 200) limit = 200;

  let offset = parseInt(searchParams.get('offset') || '0', 10);
  if (isNaN(offset) || offset < 0) offset = 0;

  const conditions: string[] = [];
  const params: unknown[] = [];
  let idx = 1;

  if (orgId) {
    conditions.push(`org_id = $${idx++}`);
    params.push(orgId);
  }
  if (status) {
    conditions.push(`status = $${idx++}`);
    params.push(status);
  }
  if (lType) {
    conditions.push(`lead_type = $${idx++}`);
    params.push(lType);
  }

  const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(' AND ')}` : '';

  try {
    const countResult = await query(
      `SELECT COUNT(*)::int AS total FROM old_leads ${whereClause}`,
      params
    );
    const total = countResult.rows[0]?.total ?? 0;

    const { rows } = await query(
      `SELECT *,
        CASE WHEN last_contact_date IS NOT NULL
          THEN CURRENT_DATE - last_contact_date
          ELSE NULL
        END AS days_cold
        FROM old_leads ${whereClause}
        ORDER BY warmth_score DESC, last_contact_date ASC NULLS LAST
        LIMIT $${idx} OFFSET $${idx + 1}`,
      [...params, limit, offset]
    );
    return NextResponse.json({ leads: rows, total, limit, offset });
  } catch (err) {
    console.error('[leads] GET error:', (err as Error).message);
    return NextResponse.json({ error: 'Internal server error' }, { status: 500 });
  }
}

/**
 * POST /api/leads
 * Add a lead manually.
 */
export async function POST(request: NextRequest) {
  const auth = requireRole(request, 'admin', 'staff');
  if (auth instanceof NextResponse) return auth;

  try {
    const body = await request.json();
    const {
      lead_type, name, contact_name, contact_title, organization,
      email, phone, website_url, city, state, original_interest,
      last_contact_date, warmth_score, ai_rekindle_reason, ai_approach,
      status: lStatus, notes,
    } = body;

    if (!lead_type || !name) {
      return NextResponse.json({ error: 'lead_type and name required' }, { status: 400 });
    }

    const orgId = auth.role === 'admin' ? getOrgId(request) : auth.orgId;
    const { rows } = await query(
      `INSERT INTO old_leads (
        lead_type, name, contact_name, contact_title, organization,
        email, phone, website_url, city, state, original_interest,
        last_contact_date, warmth_score, ai_rekindle_reason, ai_approach,
        status, notes, org_id
      ) VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13,$14,$15,$16,$17,$18)
      RETURNING *, CASE WHEN last_contact_date IS NOT NULL THEN CURRENT_DATE - last_contact_date ELSE NULL END AS days_cold`,
      [
        lead_type, name, contact_name || null, contact_title || null,
        organization || null, email || null, phone || null, website_url || null,
        city || null, state || null, original_interest || null,
        last_contact_date || null, warmth_score || 0.5,
        ai_rekindle_reason || null, ai_approach || null,
        lStatus || 'cold', notes || null, orgId,
      ]
    );

    return NextResponse.json({ lead: rows[0] });
  } catch (err) {
    console.error('[leads] POST error:', (err as Error).message);
    return NextResponse.json({ error: 'Internal server error' }, { status: 500 });
  }
}

/**
 * PATCH /api/leads
 * Update lead status.
 */
export async function PATCH(request: NextRequest) {
  const auth = requireRole(request, 'admin', 'staff');
  if (auth instanceof NextResponse) return auth;

  try {
    const body = await request.json();
    const { id, ...updates } = body;
    if (!id) return NextResponse.json({ error: 'id required' }, { status: 400 });

    const allowedFields = ['status', 'priority', 'notes', 'name', 'email', 'phone', 'organization', 'title', 'source', 'tags', 'assigned_to', 'last_contacted_at', 'score'];
    const fields = Object.keys(updates).filter(f => f !== 'id' && allowedFields.includes(f));
    if (fields.length === 0) return NextResponse.json({ error: 'No fields to update' }, { status: 400 });

    const setClauses = fields.map((f, i) => `${f} = $${i + 2}`);
    const values = fields.map(f => updates[f]);

    const { rows } = await query(
      `UPDATE old_leads SET ${setClauses.join(', ')} WHERE id = $1
       RETURNING *, CASE WHEN last_contact_date IS NOT NULL THEN CURRENT_DATE - last_contact_date ELSE NULL END AS days_cold`,
      [id, ...values]
    );

    return NextResponse.json({ lead: rows[0] });
  } catch (err) {
    console.error('[leads] PATCH error:', (err as Error).message);
    return NextResponse.json({ error: 'Internal server error' }, { status: 500 });
  }
}