feat(23-03): add admin phishing-automation GET/PATCH/DELETE routes
- GET /api/admin/phishing-automation: admin-gated list with COALESCE(...,false) gate defaults - PATCH /api/admin/phishing-automation/[companyId]: upserts all three flags, actor+timestamp stamped - DELETE /api/admin/phishing-automation/[companyId]: reverts company to all-OFF default - Mirrors app/api/admin/company-scope/* route pattern
This commit is contained in:
parent
db7d67cf25
commit
ea5047c80a
2 changed files with 158 additions and 0 deletions
76
app/api/admin/phishing-automation/[companyId]/route.ts
Normal file
76
app/api/admin/phishing-automation/[companyId]/route.ts
Normal file
|
|
@ -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 });
|
||||
}
|
||||
82
app/api/admin/phishing-automation/route.ts
Normal file
82
app/api/admin/phishing-automation/route.ts
Normal file
|
|
@ -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<number, string> = {
|
||||
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<CompanyRow>(
|
||||
`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 });
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue