import { NextRequest, NextResponse } from 'next/server' import { Pool } from 'pg' // Disable SSL rejection for self-signed certs process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0' const dbUrl = process.env.COMPLIANCE_DATABASE_URL || process.env.DATABASE_URL || 'postgresql://breakpilot:breakpilot123@bp-core-postgres:5432/breakpilot_db' const pool = new Pool({ connectionString: dbUrl }) // handleMeta returns global (filter-independent) counts incl. a ~2s member-join // facet. It is refetched on every filter change, so cache it briefly. let metaCache: { at: number; data: unknown } | null = null const META_TTL_MS = 120_000 type MCListRow = { id: string; control_id: string; title: string; objective: string severity: string; category: string; total_controls: number phases_covered: string[] | null; created_at: string verification_method: string | null; use_cases: string[] | null primary_regulation: string | null } /** * MC API that returns data in the same format as the canonical controls * endpoint. This allows the MC page to reuse ControlListView components. */ export async function GET(request: NextRequest) { try { const { searchParams } = new URL(request.url) const endpoint = searchParams.get('endpoint') || 'controls' switch (endpoint) { case 'frameworks': return NextResponse.json([]) case 'controls': return handleControls(searchParams) case 'controls-count': return handleCount(searchParams) case 'controls-meta': return handleMeta(searchParams) case 'control': return handleDetail(searchParams) default: return NextResponse.json({ error: 'unknown' }, { status: 400 }) } } catch (e) { return NextResponse.json({ error: String(e) }, { status: 500 }) } } // Shared WHERE builder so list + count stay in lock-step (incl. the // use_case / verification_method / source_regulation mapping filters). function buildControlsWhere(params: URLSearchParams): { where: string; args: unknown[]; idx: number } { let where = "WHERE 1=1" const args: unknown[] = [] let idx = 1 const search = params.get('search') || '' if (search) { where += ` AND mc.canonical_name ILIKE $${idx}` args.push(`%${search}%`) idx++ } const severity = params.get('severity') || '' if (severity === 'high') { where += ` AND mc.total_controls > 100` } else if (severity === 'medium') { where += ` AND mc.total_controls BETWEEN 20 AND 100` } else if (severity === 'low') { where += ` AND mc.total_controls < 20` } const domain = params.get('domain') || '' if (domain) { where += ` AND mc.canonical_name LIKE $${idx}` args.push(`${domain}%`) idx++ } const useCase = params.get('use_case') || '' const primaryOnly = params.get('primary') === '1' if (useCase) { where += ` AND EXISTS (SELECT 1 FROM compliance.mc_use_case_mappings m WHERE m.master_control_uuid = mc.id AND m.use_case = $${idx}${primaryOnly ? ' AND m.is_primary' : ''})` args.push(useCase) idx++ } const verification = params.get('verification_method') || '' if (verification === '__none__') { where += ` AND NOT EXISTS (SELECT 1 FROM compliance.mc_verification v WHERE v.master_control_uuid = mc.id)` } else if (verification) { where += ` AND EXISTS (SELECT 1 FROM compliance.mc_verification v WHERE v.master_control_uuid = mc.id AND v.verification_method = $${idx})` args.push(verification) idx++ } const regulation = params.get('source_regulation') || '' if (regulation) { where += ` AND EXISTS (SELECT 1 FROM compliance.mc_regulations r WHERE r.master_control_uuid = mc.id AND r.source_regulation = $${idx})` args.push(regulation) idx++ } const mapped = params.get('mapped') || '' if (mapped === 'mapped') { where += ` AND EXISTS (SELECT 1 FROM compliance.mc_use_case_mappings m WHERE m.master_control_uuid = mc.id)` } else if (mapped === 'unmapped') { where += ` AND NOT EXISTS (SELECT 1 FROM compliance.mc_use_case_mappings m WHERE m.master_control_uuid = mc.id)` } // Member-based filter: an MC matches if ANY of its atomic members has the // category. Only category/severity/release_state are populated on the // deduplicated members; evidence_type, target_audience and source_citation // are 100% NULL there, so those canonical filters cannot apply to MCs // without an upstream backfill (wiring them would just return 0). const category = params.get('category') || '' if (category) { where += ` AND EXISTS (SELECT 1 FROM compliance.master_control_members mcm JOIN compliance.canonical_controls cc ON cc.id = mcm.control_uuid WHERE mcm.master_control_uuid = mc.id AND cc.category = $${idx})` args.push(category); idx++ } return { where, args, idx } } async function handleControls(params: URLSearchParams) { const limit = Math.min(parseInt(params.get('limit') || '50'), 200) const offset = parseInt(params.get('offset') || '0') const sort = params.get('sort') || 'control_id' const order = params.get('order') === 'desc' ? 'DESC' : 'ASC' const { where, args, idx } = buildControlsWhere(params) const sortCol = sort === 'control_id' ? 'mc.master_control_id' : sort === 'created_at' ? 'mc.created_at' : sort === 'source' ? 'mc.canonical_name' : 'mc.master_control_id' args.push(limit, offset) const res = await pool.query(` SELECT mc.master_control_id as control_id, mc.canonical_name as title, 'Master Control mit ' || mc.total_controls || ' Atomic Controls' as objective, CASE WHEN mc.total_controls > 100 THEN 'high' WHEN mc.total_controls > 20 THEN 'medium' ELSE 'low' END as severity, 'master_control' as category, mc.total_controls, mc.phases_covered, mc.id, mc.created_at, (SELECT v.verification_method FROM compliance.mc_verification v WHERE v.master_control_uuid = mc.id) as verification_method, (SELECT array_agg(m.use_case ORDER BY m.is_primary DESC, m.use_case) FROM compliance.mc_use_case_mappings m WHERE m.master_control_uuid = mc.id) as use_cases, (SELECT r.source_regulation FROM compliance.mc_regulations r WHERE r.master_control_uuid = mc.id AND r.is_primary LIMIT 1) as primary_regulation FROM compliance.master_controls mc ${where} ORDER BY ${sortCol} ${order} LIMIT $${idx} OFFSET $${idx + 1} `, args) // Map to canonical control format const controls = res.rows.map((r: MCListRow) => ({ id: r.id, control_id: r.control_id, title: r.title, objective: r.objective, severity: r.severity, category: r.category, release_state: 'active', source_citation: r.primary_regulation ? { source: r.primary_regulation } : null, verification_method: r.verification_method, evidence_type: null, target_audience: [], use_cases: r.use_cases || [], requirements: [], test_procedure: [], evidence: [], open_anchors: [], total_controls: r.total_controls, phases_covered: r.phases_covered, created_at: r.created_at, scope: { platforms: [], components: [], data_classes: [] }, risk_score: null, implementation_effort: null, })) return NextResponse.json(controls) } async function handleCount(params: URLSearchParams) { const { where, args } = buildControlsWhere(params) const res = await pool.query( `SELECT count(*) FROM compliance.master_controls mc ${where}`, args ) return NextResponse.json({ total: parseInt(res.rows[0].count) }) } async function handleMeta(_params: URLSearchParams) { if (metaCache && Date.now() - metaCache.at < META_TTL_MS) { return NextResponse.json(metaCache.data) } const res = await pool.query(` SELECT count(*) as total, count(CASE WHEN total_controls > 100 THEN 1 END) as high_count, count(CASE WHEN total_controls BETWEEN 20 AND 100 THEN 1 END) as medium_count, count(CASE WHEN total_controls < 20 THEN 1 END) as low_count FROM compliance.master_controls `) const r = res.rows[0] // Get top L1 tokens as "domains" const domainRes = await pool.query(` SELECT split_part(canonical_name, '_', 1) as domain, count(*) as count FROM compliance.master_controls GROUP BY 1 ORDER BY 2 DESC LIMIT 30 `) // Mapping distribution + coverage + member-derived category facet. Only // category is populated on the deduplicated members (evidence_type / // target_audience are NULL there), so it is the one canonical facet we surface. const [ucRes, vRes, regRes, mappedRes, catRes] = await Promise.all([ pool.query(`SELECT use_case, count(DISTINCT master_control_uuid) c FROM compliance.mc_use_case_mappings GROUP BY 1 ORDER BY 2 DESC`), pool.query(`SELECT verification_method, count(*) c FROM compliance.mc_verification GROUP BY 1 ORDER BY 2 DESC`), pool.query(`SELECT source_regulation, count(DISTINCT master_control_uuid) c FROM compliance.mc_regulations GROUP BY 1 ORDER BY 2 DESC LIMIT 200`), pool.query(`SELECT count(DISTINCT master_control_uuid) c FROM compliance.mc_use_case_mappings`), pool.query(`SELECT cc.category v, count(DISTINCT mcm.master_control_uuid) c FROM compliance.master_control_members mcm JOIN compliance.canonical_controls cc ON cc.id = mcm.control_uuid WHERE cc.category IS NOT NULL GROUP BY 1 ORDER BY 2 DESC`), ]) const facet = (rows: Array<{ v: string; c: string }>) => Object.fromEntries(rows.filter(x => x.v).map(x => [x.v, parseInt(x.c)])) const total = parseInt(r.total) const mappedTotal = parseInt(mappedRes.rows[0].c) const payload = { total, severity_counts: { high: parseInt(r.high_count), medium: parseInt(r.medium_count), low: parseInt(r.low_count), }, domains: domainRes.rows.map((d: { domain: string; count: string }) => ({ domain: d.domain, count: parseInt(d.count) })), sources: [], no_source_count: 0, release_state_counts: { active: total }, verification_method_counts: Object.fromEntries( vRes.rows.map((x: { verification_method: string; c: string }) => [x.verification_method, parseInt(x.c)])), category_counts: facet(catRes.rows), evidence_type_counts: {}, use_case_counts: Object.fromEntries( ucRes.rows .filter((x: { use_case: string | null }) => x.use_case) .map((x: { use_case: string; c: string }) => [x.use_case, parseInt(x.c)])), regulations: regRes.rows .filter((x: { source_regulation: string | null }) => x.source_regulation) .map((x: { source_regulation: string; c: string }) => ({ source_regulation: x.source_regulation, count: parseInt(x.c) })), mapped_total: mappedTotal, unmapped_count: total - mappedTotal, } metaCache = { at: Date.now(), data: payload } return NextResponse.json(payload) } async function handleDetail(params: URLSearchParams) { const id = params.get('id') || '' const res = await pool.query(` SELECT mc.id, mc.master_control_id as control_id, mc.canonical_name as title, 'Master Control mit ' || mc.total_controls || ' Atomic Controls' as objective, mc.total_controls, mc.phases_covered, mc.phase_control_count, mc.created_at 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 }) } const mc = res.rows[0] // Load members const membersRes = await pool.query(` SELECT cc.control_id, cc.title, cc.severity, mcm.phase, mcm.action FROM compliance.master_control_members mcm JOIN compliance.canonical_controls cc ON cc.id = mcm.control_uuid WHERE mcm.master_control_uuid = $1 ORDER BY mcm.phase, cc.control_id LIMIT 100 `, [mc.id]) // Use-case / verification / regulation mapping (the "regulation→code" lineage) const mapRes = await pool.query(` SELECT (SELECT json_agg(json_build_object('use_case', m.use_case, 'is_primary', m.is_primary) ORDER BY m.is_primary DESC, m.use_case) FROM compliance.mc_use_case_mappings m WHERE m.master_control_uuid = $1) as use_cases, (SELECT v.verification_method FROM compliance.mc_verification v WHERE v.master_control_uuid = $1) as verification_method, (SELECT json_agg(json_build_object('source_regulation', r.source_regulation, 'is_primary', r.is_primary, 'member_count', r.member_count) ORDER BY r.is_primary DESC, r.member_count DESC) FROM compliance.mc_regulations r WHERE r.master_control_uuid = $1) as regulations `, [mc.id]) const mapping = mapRes.rows[0] || {} const regs = mapping.regulations || [] const primaryReg = regs.find((x: { is_primary: boolean }) => x.is_primary) || regs[0] return NextResponse.json({ id: mc.id, control_id: mc.control_id, title: mc.title, objective: mc.objective, severity: mc.total_controls > 100 ? 'high' : mc.total_controls > 20 ? 'medium' : 'low', category: 'master_control', release_state: 'active', total_controls: mc.total_controls, phases_covered: mc.phases_covered, phase_control_count: mc.phase_control_count, members: membersRes.rows, requirements: membersRes.rows.map((m: { control_id: string; title: string; phase: string }) => `[${m.phase}] ${m.control_id}: ${m.title}` ), test_procedure: [], evidence: [], open_anchors: [], target_audience: [], verification_method: mapping.verification_method || null, use_cases: mapping.use_cases || [], regulations: regs, source_citation: primaryReg ? { source: primaryReg.source_regulation } : null, scope: { platforms: [], components: [], data_classes: [] }, risk_score: null, implementation_effort: null, created_at: mc.created_at, }) }