← back to Norma

app/api/social/competitors/route.ts

168 lines

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

/**
 * GET /api/social/competitors?category=&platform=&active=true
 * List competitor accounts with optional filters.
 */
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 category = searchParams.get('category');
  const platform = searchParams.get('platform');
  const active = searchParams.get('active');

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

  if (category) {
    conditions.push(`category = $${idx++}`);
    values.push(category);
  }
  if (platform) {
    conditions.push(`platform = $${idx++}`);
    values.push(platform);
  }
  if (active !== null && active !== '') {
    conditions.push(`is_active = $${idx++}`);
    values.push(active === 'true');
  }

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

  const result = await query(
    `SELECT *
     FROM social_competitors
     ${where}
     ORDER BY follower_count DESC, created_at DESC`,
    values
  );

  return NextResponse.json({ competitors: result.rows, count: result.rows.length });
}

/**
 * POST /api/social/competitors
 * Add a new competitor account to track.
 * Body: { platform, handle, display_name?, category?, expertise_area?, avatar_url?, bio? }
 */
export async function POST(request: NextRequest) {
  const auth = requireRole(request, 'admin', 'staff');
  if (auth instanceof NextResponse) return auth;

  const body = await request.json();
  const {
    platform,
    handle,
    display_name,
    category = 'competitor',
    expertise_area,
    avatar_url,
    bio,
    follower_count = 0,
    following_count = 0,
    org_id,
  } = body;

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

  const result = await query(
    `INSERT INTO social_competitors
       (platform, handle, display_name, category, expertise_area, avatar_url, bio,
        follower_count, following_count, org_id)
     VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)
     ON CONFLICT (platform, handle) DO UPDATE SET
       display_name    = EXCLUDED.display_name,
       category        = EXCLUDED.category,
       expertise_area  = EXCLUDED.expertise_area,
       avatar_url      = COALESCE(EXCLUDED.avatar_url, social_competitors.avatar_url),
       bio             = COALESCE(EXCLUDED.bio, social_competitors.bio),
       follower_count  = EXCLUDED.follower_count,
       following_count = EXCLUDED.following_count,
       org_id          = COALESCE(EXCLUDED.org_id, social_competitors.org_id),
       is_active       = true
     RETURNING *`,
    [platform, handle, display_name || null, category, expertise_area || null,
     avatar_url || null, bio || null, follower_count, following_count, org_id || null]
  );

  return NextResponse.json({ competitor: result.rows[0] }, { status: 201 });
}

/**
 * PATCH /api/social/competitors
 * Update a competitor account.
 * Body: { id, ...fields }
 */
export async function PATCH(request: NextRequest) {
  const auth = requireRole(request, 'admin', 'staff');
  if (auth instanceof NextResponse) return auth;

  const body = await request.json();
  const { id, ...fields } = body;

  if (!id) return NextResponse.json({ error: 'id is required' }, { status: 400 });

  const allowed = [
    'display_name', 'avatar_url', 'bio', 'follower_count', 'following_count',
    'category', 'expertise_area', 'org_id', 'last_crawled_at', 'is_active',
  ];

  const sets: string[] = [];
  const values: unknown[] = [];
  let idx = 1;

  for (const key of allowed) {
    if (key in fields) {
      sets.push(`${key} = $${idx++}`);
      values.push(fields[key]);
    }
  }

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

  values.push(id);
  const result = await query(
    `UPDATE social_competitors SET ${sets.join(', ')} WHERE id = $${idx} RETURNING *`,
    values
  );

  if (result.rows.length === 0) {
    return NextResponse.json({ error: 'Competitor not found' }, { status: 404 });
  }

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

/**
 * DELETE /api/social/competitors
 * Remove a competitor and all their crawled posts (CASCADE).
 * Body: { id }
 */
export async function DELETE(request: NextRequest) {
  const auth = requireRole(request, 'admin');
  if (auth instanceof NextResponse) return auth;

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

  const result = await query(
    `DELETE FROM social_competitors WHERE id = $1 RETURNING id`,
    [id]
  );

  if (result.rows.length === 0) {
    return NextResponse.json({ error: 'Competitor not found' }, { status: 404 });
  }

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