← back to Norma

app/api/email-analyzer/skills/route.ts

123 lines

/**
 * GET    /api/email-analyzer/skills — list saved skills
 * POST   /api/email-analyzer/skills — save a new skill
 * DELETE /api/email-analyzer/skills?id=X — delete a skill
 *
 * A "skill" is a reusable rewrite recipe: tone + length + audience + angles +
 * optional custom instruction. Stored in analyzer_skills table.
 */

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

function getOrgId(request: NextRequest): string | null {
  return request.headers.get('x-org-id') || null;
}

export async function GET(request: NextRequest) {
  const auth = requireRole(request, 'admin', 'staff');
  if (auth instanceof NextResponse) return auth;

  try {
    const orgId = auth.role === 'admin' ? getOrgId(request) : auth.orgId;
    const conditions: string[] = [];
    const values: unknown[] = [];
    if (orgId) {
      conditions.push('org_id = $1');
      values.push(orgId);
    }
    const where = conditions.length ? `WHERE ${conditions.join(' AND ')}` : '';
    const result = await query(
      `SELECT id, name, description, tone, length, audience, angles,
              custom_instruction, use_count, created_at
       FROM analyzer_skills
       ${where}
       ORDER BY use_count DESC, created_at DESC
       LIMIT 100`,
      values,
    );
    return NextResponse.json({ skills: result.rows });
  } catch (err) {
    console.error('[email-analyzer/skills] GET error:', (err as Error).message);
    return NextResponse.json({ error: 'Failed to list skills' }, { status: 500 });
  }
}

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 {
      name,
      description = '',
      tone = null,
      length = null,
      audience = null,
      angles = [],
      custom_instruction = null,
    } = body;

    if (!name || typeof name !== 'string' || name.trim().length === 0) {
      return NextResponse.json({ error: 'name is required' }, { status: 400 });
    }

    const orgId = auth.role === 'admin' ? getOrgId(request) : auth.orgId;
    const createdBy = auth.username || 'unknown';

    const result = await query(
      `INSERT INTO analyzer_skills
         (name, description, tone, length, audience, angles, custom_instruction, created_by, org_id)
       VALUES ($1,$2,$3,$4,$5,$6::jsonb,$7,$8,$9)
       RETURNING id, name, description, tone, length, audience, angles,
                 custom_instruction, use_count, created_at`,
      [
        name.trim().slice(0, 120),
        (description || '').slice(0, 500),
        tone,
        length,
        audience,
        JSON.stringify(Array.isArray(angles) ? angles : []),
        custom_instruction,
        createdBy,
        orgId,
      ],
    );

    return NextResponse.json({ skill: result.rows[0] });
  } catch (err) {
    console.error('[email-analyzer/skills] POST error:', (err as Error).message);
    return NextResponse.json({ error: 'Failed to save skill' }, { status: 500 });
  }
}

export async function DELETE(request: NextRequest) {
  const auth = requireRole(request, 'admin', 'staff');
  if (auth instanceof NextResponse) return auth;

  try {
    const { searchParams } = new URL(request.url);
    const id = searchParams.get('id');
    if (!id) {
      return NextResponse.json({ error: 'id is required' }, { status: 400 });
    }
    const orgId = auth.role === 'admin' ? getOrgId(request) : auth.orgId;
    const conditions: string[] = ['id = $1'];
    const values: unknown[] = [id];
    if (orgId) {
      conditions.push('org_id = $2');
      values.push(orgId);
    }
    const result = await query(
      `DELETE FROM analyzer_skills WHERE ${conditions.join(' AND ')} RETURNING id`,
      values,
    );
    return NextResponse.json({ deleted: result.rows.length });
  } catch (err) {
    console.error('[email-analyzer/skills] DELETE error:', (err as Error).message);
    return NextResponse.json({ error: 'Failed to delete skill' }, { status: 500 });
  }
}