/** * DSFA API Client * * API client functions for DSFA (Data Protection Impact Assessment) endpoints. */ import type { DSFA, DSFAListResponse, DSFAStatsResponse, CreateDSFARequest, CreateDSFAFromAssessmentRequest, CreateDSFAFromAssessmentResponse, UpdateDSFASectionRequest, SubmitForReviewResponse, ApproveDSFARequest, DSFATriggerInfo, } from './types' import type { AIUseCaseModule } from './ai-use-case-types' // ============================================================================= // CONFIGURATION // ============================================================================= const getBaseUrl = () => { if (typeof window !== 'undefined') { // Browser environment return process.env.NEXT_PUBLIC_SDK_API_URL || '/api/sdk/v1' } // Server environment return process.env.SDK_API_URL || 'http://localhost:8080/api/sdk/v1' } // ============================================================================= // HELPER FUNCTIONS // ============================================================================= async function handleResponse(response: Response): Promise { if (!response.ok) { const errorBody = await response.text() let errorMessage = `HTTP ${response.status}` try { const errorJson = JSON.parse(errorBody) errorMessage = errorJson.error || errorJson.message || errorMessage } catch { // Keep HTTP status message } throw new Error(errorMessage) } return response.json() } function getHeaders(): HeadersInit { return { 'Content-Type': 'application/json', } } // ============================================================================= // DSFA CRUD OPERATIONS // ============================================================================= /** * List all DSFAs for the current tenant */ export async function listDSFAs(status?: string): Promise { const url = new URL(`${getBaseUrl()}/dsfa`, window.location.origin) if (status) { url.searchParams.set('status', status) } const response = await fetch(url.toString(), { method: 'GET', headers: getHeaders(), credentials: 'include', }) const data = await handleResponse(response) return data.dsfas || [] } /** * Get a single DSFA by ID */ export async function getDSFA(id: string): Promise { const response = await fetch(`${getBaseUrl()}/dsfa/${id}`, { method: 'GET', headers: getHeaders(), credentials: 'include', }) return handleResponse(response) } /** * Create a new DSFA */ export async function createDSFA(data: CreateDSFARequest): Promise { const response = await fetch(`${getBaseUrl()}/dsfa`, { method: 'POST', headers: getHeaders(), credentials: 'include', body: JSON.stringify(data), }) return handleResponse(response) } /** * Update an existing DSFA */ export async function updateDSFA(id: string, data: Partial): Promise { const response = await fetch(`${getBaseUrl()}/dsfa/${id}`, { method: 'PUT', headers: getHeaders(), credentials: 'include', body: JSON.stringify(data), }) return handleResponse(response) } /** * Delete a DSFA */ export async function deleteDSFA(id: string): Promise { const response = await fetch(`${getBaseUrl()}/dsfa/${id}`, { method: 'DELETE', headers: getHeaders(), credentials: 'include', }) if (!response.ok) { throw new Error(`Failed to delete DSFA: ${response.statusText}`) } } // ============================================================================= // DSFA SECTION OPERATIONS // ============================================================================= /** * Update a specific section of a DSFA */ export async function updateDSFASection( id: string, sectionNumber: number, data: UpdateDSFASectionRequest ): Promise { const response = await fetch(`${getBaseUrl()}/dsfa/${id}/sections/${sectionNumber}`, { method: 'PUT', headers: getHeaders(), credentials: 'include', body: JSON.stringify(data), }) return handleResponse(response) } // ============================================================================= // DSFA WORKFLOW OPERATIONS // ============================================================================= /** * Submit a DSFA for DPO review */ export async function submitDSFAForReview(id: string): Promise { const response = await fetch(`${getBaseUrl()}/dsfa/${id}/submit-for-review`, { method: 'POST', headers: getHeaders(), credentials: 'include', }) return handleResponse(response) } /** * Approve or reject a DSFA (DPO/CISO/GF action) */ export async function approveDSFA(id: string, data: ApproveDSFARequest): Promise<{ message: string }> { const response = await fetch(`${getBaseUrl()}/dsfa/${id}/approve`, { method: 'POST', headers: getHeaders(), credentials: 'include', body: JSON.stringify(data), }) return handleResponse<{ message: string }>(response) } // ============================================================================= // DSFA STATISTICS // ============================================================================= /** * Get DSFA statistics for the dashboard */ export async function getDSFAStats(): Promise { const response = await fetch(`${getBaseUrl()}/dsfa/stats`, { method: 'GET', headers: getHeaders(), credentials: 'include', }) return handleResponse(response) } // ============================================================================= // UCCA INTEGRATION // ============================================================================= /** * Create a DSFA from a UCCA assessment (pre-filled) */ export async function createDSFAFromAssessment( assessmentId: string, data?: CreateDSFAFromAssessmentRequest ): Promise { const response = await fetch(`${getBaseUrl()}/dsfa/from-assessment/${assessmentId}`, { method: 'POST', headers: getHeaders(), credentials: 'include', body: JSON.stringify(data || {}), }) return handleResponse(response) } /** * Get a DSFA by its linked UCCA assessment ID */ export async function getDSFAByAssessment(assessmentId: string): Promise { try { const response = await fetch(`${getBaseUrl()}/dsfa/by-assessment/${assessmentId}`, { method: 'GET', headers: getHeaders(), credentials: 'include', }) if (response.status === 404) { return null } return handleResponse(response) } catch (error) { // Return null if DSFA not found return null } } /** * Check if a DSFA is required for a UCCA assessment */ export async function checkDSFARequired(assessmentId: string): Promise { const response = await fetch(`${getBaseUrl()}/ucca/assessments/${assessmentId}/dsfa-required`, { method: 'GET', headers: getHeaders(), credentials: 'include', }) return handleResponse(response) } // ============================================================================= // EXPORT // ============================================================================= /** * Export a DSFA as JSON */ export async function exportDSFAAsJSON(id: string): Promise { const response = await fetch(`${getBaseUrl()}/dsfa/${id}/export?format=json`, { method: 'GET', headers: { 'Accept': 'application/json', }, credentials: 'include', }) if (!response.ok) { throw new Error(`Export failed: ${response.statusText}`) } return response.blob() } /** * Export a DSFA as PDF */ export async function exportDSFAAsPDF(id: string): Promise { const response = await fetch(`${getBaseUrl()}/dsfa/${id}/export/pdf`, { method: 'GET', headers: { 'Accept': 'application/pdf', }, credentials: 'include', }) if (!response.ok) { throw new Error(`PDF export failed: ${response.statusText}`) } return response.blob() } // ============================================================================= // RISK & MITIGATION OPERATIONS // ============================================================================= /** * Add a risk to a DSFA */ export async function addDSFARisk(dsfaId: string, risk: { category: string description: string likelihood: 'low' | 'medium' | 'high' impact: 'low' | 'medium' | 'high' affected_data?: string[] }): Promise { const dsfa = await getDSFA(dsfaId) const newRisk = { id: crypto.randomUUID(), ...risk, risk_level: calculateRiskLevelString(risk.likelihood, risk.impact), affected_data: risk.affected_data || [], } const updatedRisks = [...(dsfa.risks || []), newRisk] return updateDSFA(dsfaId, { risks: updatedRisks } as Partial) } /** * Remove a risk from a DSFA */ export async function removeDSFARisk(dsfaId: string, riskId: string): Promise { const dsfa = await getDSFA(dsfaId) const updatedRisks = (dsfa.risks || []).filter(r => r.id !== riskId) return updateDSFA(dsfaId, { risks: updatedRisks } as Partial) } /** * Add a mitigation to a DSFA */ export async function addDSFAMitigation(dsfaId: string, mitigation: { risk_id: string description: string type: 'technical' | 'organizational' | 'legal' responsible_party: string }): Promise { const dsfa = await getDSFA(dsfaId) const newMitigation = { id: crypto.randomUUID(), ...mitigation, status: 'planned' as const, residual_risk: 'medium' as const, } const updatedMitigations = [...(dsfa.mitigations || []), newMitigation] return updateDSFA(dsfaId, { mitigations: updatedMitigations } as Partial) } /** * Update mitigation status */ export async function updateDSFAMitigationStatus( dsfaId: string, mitigationId: string, status: 'planned' | 'in_progress' | 'implemented' | 'verified' ): Promise { const dsfa = await getDSFA(dsfaId) const updatedMitigations = (dsfa.mitigations || []).map(m => { if (m.id === mitigationId) { return { ...m, status, ...(status === 'implemented' && { implemented_at: new Date().toISOString() }), ...(status === 'verified' && { verified_at: new Date().toISOString() }), } } return m }) return updateDSFA(dsfaId, { mitigations: updatedMitigations } as Partial) } // ============================================================================= // HELPER FUNCTIONS // ============================================================================= // ============================================================================= // AI USE CASE MODULE OPERATIONS // ============================================================================= /** * Add a new AI use case module to a DSFA (Section 8) */ export async function addAIUseCaseModule(dsfaId: string, module: AIUseCaseModule): Promise { const dsfa = await getDSFA(dsfaId) const existing = dsfa.ai_use_case_modules || [] return updateDSFA(dsfaId, { ai_use_case_modules: [...existing, module] } as Partial) } /** * Update an existing AI use case module in a DSFA */ export async function updateAIUseCaseModule( dsfaId: string, moduleId: string, updates: Partial ): Promise { const dsfa = await getDSFA(dsfaId) const existing = dsfa.ai_use_case_modules || [] const updated = existing.map(m => m.id === moduleId ? { ...m, ...updates, updated_at: new Date().toISOString() } : m ) return updateDSFA(dsfaId, { ai_use_case_modules: updated } as Partial) } /** * Remove an AI use case module from a DSFA */ export async function removeAIUseCaseModule(dsfaId: string, moduleId: string): Promise { const dsfa = await getDSFA(dsfaId) const updated = (dsfa.ai_use_case_modules || []).filter(m => m.id !== moduleId) return updateDSFA(dsfaId, { ai_use_case_modules: updated } as Partial) } function calculateRiskLevelString( likelihood: 'low' | 'medium' | 'high', impact: 'low' | 'medium' | 'high' ): string { const matrix: Record> = { low: { low: 'low', medium: 'low', high: 'medium' }, medium: { low: 'low', medium: 'medium', high: 'high' }, high: { low: 'medium', medium: 'high', high: 'very_high' }, } return matrix[likelihood]?.[impact] || 'medium' }