import { NextRequest, NextResponse } from 'next/server' import { Pool } from 'pg' const pool = new Pool({ connectionString: process.env.DATABASE_URL || 'postgresql://breakpilot:breakpilot123@bp-core-postgres:5432/breakpilot_db', }) /** * GET /api/sdk/v1/master-controls?action=list|detail|members */ export async function GET(request: NextRequest) { try { const { searchParams } = new URL(request.url) const action = searchParams.get('action') || 'list' if (action === 'list') { return handleList(searchParams) } if (action === 'detail') { return handleDetail(searchParams) } if (action === 'members') { return handleMembers(searchParams) } return NextResponse.json({ error: 'Unknown action' }, { status: 400 }) } catch (e) { return NextResponse.json({ error: String(e) }, { status: 500 }) } } async function handleList(params: URLSearchParams) { const search = params.get('search') || '' const minControls = parseInt(params.get('min_controls') || '0') const sortBy = params.get('sort') || 'total_controls' const order = params.get('order') || 'DESC' const limit = Math.min(parseInt(params.get('limit') || '100'), 500) const offset = parseInt(params.get('offset') || '0') const validSort = ['total_controls', 'canonical_name', 'created_at'].includes(sortBy) ? sortBy : 'total_controls' const validOrder = order === 'ASC' ? 'ASC' : 'DESC' let where = 'WHERE 1=1' const args: unknown[] = [] let idx = 1 if (search) { where += ` AND mc.canonical_name ILIKE $${idx}` args.push(`%${search}%`) idx++ } if (minControls > 0) { where += ` AND mc.total_controls >= $${idx}` args.push(minControls) idx++ } const countRes = await pool.query( `SELECT count(*) FROM compliance.master_controls mc ${where}`, args ) const total = parseInt(countRes.rows[0].count) args.push(limit, offset) const res = await pool.query( `SELECT mc.id, mc.master_control_id, mc.canonical_name, mc.total_controls, mc.phases_covered, mc.phase_control_count FROM compliance.master_controls mc ${where} ORDER BY mc.${validSort} ${validOrder} LIMIT $${idx} OFFSET $${idx + 1}`, args ) return NextResponse.json({ total, limit, offset, master_controls: res.rows, }) } async function handleDetail(params: URLSearchParams) { const id = params.get('id') if (!id) return NextResponse.json({ error: 'id required' }, { status: 400 }) const res = await pool.query( `SELECT mc.id, mc.master_control_id, mc.canonical_name, mc.total_controls, mc.phases_covered, mc.phase_control_count FROM compliance.master_controls mc WHERE mc.master_control_id = $1 OR mc.id::text = $1`, [id] ) if (res.rows.length === 0) { return NextResponse.json({ error: 'not found' }, { status: 404 }) } return NextResponse.json({ master_control: res.rows[0] }) } async function handleMembers(params: URLSearchParams) { const mcId = params.get('mc_id') if (!mcId) return NextResponse.json({ error: 'mc_id required' }, { status: 400 }) const limit = Math.min(parseInt(params.get('limit') || '50'), 200) const offset = parseInt(params.get('offset') || '0') const res = await pool.query( `SELECT cc.control_id, cc.title, cc.objective, cc.severity, mcm.phase, mcm.action, COALESCE(pc.source_citation::jsonb->>'source', '') as regulation_source, COALESCE(pc.source_citation::jsonb->>'article', '') as regulation_article FROM compliance.master_control_members mcm JOIN compliance.canonical_controls cc ON cc.id = mcm.control_uuid LEFT JOIN compliance.canonical_controls pc ON pc.id = cc.parent_control_uuid WHERE mcm.master_control_uuid = ( SELECT id FROM compliance.master_controls WHERE master_control_id = $1 OR id::text = $1 LIMIT 1 ) ORDER BY mcm.phase, cc.control_id LIMIT $2 OFFSET $3`, [mcId, limit, offset] ) return NextResponse.json({ mc_id: mcId, members: res.rows, count: res.rows.length, }) }