diff --git a/app/api/admin/phishing-automation/[companyId]/route.ts b/app/api/admin/phishing-automation/[companyId]/route.ts new file mode 100644 index 0000000..92dc3cf --- /dev/null +++ b/app/api/admin/phishing-automation/[companyId]/route.ts @@ -0,0 +1,76 @@ +/** + * PATCH /api/admin/phishing-automation/[companyId] + * Upsert a company's three automation gate flags. + * Body: { autoParse: boolean, autoClassify: boolean, autoReport: boolean } + * Client always sends all three current values (the admin page knows them + * from local state), avoiding partial-update SQL complexity. + * + * DELETE /api/admin/phishing-automation/[companyId] + * Remove the explicit override — company reverts to the all-OFF default. + */ + +import { NextRequest, NextResponse } from 'next/server'; +import { requireAdmin } from '@/lib/auth-utils'; +import postgresClient from '@/lib/services/postgres-client'; + +export async function PATCH( + request: NextRequest, + { params }: { params: Promise<{ companyId: string }> } +) { + const { session, error } = await requireAdmin(); + if (error) return error; + + const { companyId } = await params; + const id = parseInt(companyId, 10); + if (isNaN(id)) return NextResponse.json({ error: 'Invalid companyId' }, { status: 400 }); + + const body = await request.json().catch(() => null); + if ( + body == null || + typeof body.autoParse !== 'boolean' || + typeof body.autoClassify !== 'boolean' || + typeof body.autoReport !== 'boolean' + ) { + return NextResponse.json( + { error: 'body.autoParse, body.autoClassify, body.autoReport (all boolean) required' }, + { status: 400 } + ); + } + + const userEmail = (session?.user as any)?.email ?? null; + + await postgresClient.query( + `INSERT INTO phishing_automation_gate (company_id, auto_parse, auto_classify, auto_report, updated_by, updated_at) + VALUES ($1, $2, $3, $4, $5, NOW()) + ON CONFLICT (company_id) + DO UPDATE SET auto_parse = EXCLUDED.auto_parse, + auto_classify = EXCLUDED.auto_classify, + auto_report = EXCLUDED.auto_report, + updated_by = EXCLUDED.updated_by, + updated_at = NOW()`, + [id, body.autoParse, body.autoClassify, body.autoReport, userEmail] + ); + + return NextResponse.json({ + ok: true, + companyId: id, + autoParse: body.autoParse, + autoClassify: body.autoClassify, + autoReport: body.autoReport, + }); +} + +export async function DELETE( + _request: NextRequest, + { params }: { params: Promise<{ companyId: string }> } +) { + const { error } = await requireAdmin(); + if (error) return error; + + const { companyId } = await params; + const id = parseInt(companyId, 10); + if (isNaN(id)) return NextResponse.json({ error: 'Invalid companyId' }, { status: 400 }); + + await postgresClient.query(`DELETE FROM phishing_automation_gate WHERE company_id = $1`, [id]); + return NextResponse.json({ ok: true }); +} diff --git a/app/api/admin/phishing-automation/route.ts b/app/api/admin/phishing-automation/route.ts new file mode 100644 index 0000000..8e7f21b --- /dev/null +++ b/app/api/admin/phishing-automation/route.ts @@ -0,0 +1,82 @@ +/** + * GET /api/admin/phishing-automation + * Returns all active companies with their current phishing automation gate flags. + * Companies without a phishing_automation_gate row are implicitly all-OFF + * (opt-in model — opposite polarity from /api/admin/company-scope). + * + * Query params: + * search — filter by company name (ILIKE) + * type — filter by company_type integer + */ + +import { NextRequest, NextResponse } from 'next/server'; +import { requireAdmin } from '@/lib/auth-utils'; +import postgresClient from '@/lib/services/postgres-client'; + +interface CompanyRow { + id: string; + company_name: string; + company_type: number | null; + auto_parse: boolean; + auto_classify: boolean; + auto_report: boolean; +} + +const COMPANY_TYPE_LABELS: Record = { + 1: 'Customer', + 2: 'Canceled', + 3: 'Cold', + 4: 'Dead', + 5: 'Warm', + 6: 'Vendor', + 7: 'Partner', + 8: 'Prospect', +}; + +export async function GET(request: NextRequest) { + const { error } = await requireAdmin(); + if (error) return error; + + const url = request.nextUrl; + const search = url.searchParams.get('search')?.trim() || null; + const typeParam = url.searchParams.get('type'); + const typeFilter = typeParam ? parseInt(typeParam, 10) : null; + + const params: unknown[] = []; + const conditions = ['c.is_active = true', 'c.is_deleted = false']; + + if (search) { + params.push(`%${search}%`); + conditions.push(`c.company_name ILIKE $${params.length}`); + } + if (typeFilter !== null && !isNaN(typeFilter)) { + params.push(typeFilter); + conditions.push(`c.company_type = $${params.length}`); + } + + const result = await postgresClient.query( + `SELECT c.id::text, c.company_name, c.company_type, + COALESCE(pag.auto_parse, false) AS auto_parse, + COALESCE(pag.auto_classify, false) AS auto_classify, + COALESCE(pag.auto_report, false) AS auto_report + FROM companies c + LEFT JOIN phishing_automation_gate pag ON pag.company_id = c.id + WHERE ${conditions.join(' AND ')} + ORDER BY c.company_name`, + params + ); + + const companies = result.rows.map((r) => ({ + id: r.id, + companyName: r.company_name, + companyType: r.company_type, + companyTypeLabel: r.company_type != null ? (COMPANY_TYPE_LABELS[r.company_type] ?? `Type ${r.company_type}`) : null, + autoParse: r.auto_parse, + autoClassify: r.auto_classify, + autoReport: r.auto_report, + })); + + const enabled = companies.filter((c) => c.autoParse || c.autoClassify || c.autoReport).length; + + return NextResponse.json({ companies, total: companies.length, enabled }); +}