/** * DSR API Client * * API client for Data Subject Request management * Connects to the Go Consent Service backend */ import { DSRRequest, DSRListResponse, DSRFilters, DSRCreateRequest, DSRUpdateRequest, DSRVerifyIdentityRequest, DSRCompleteRequest, DSRRejectRequest, DSRExtendDeadlineRequest, DSRSendCommunicationRequest, DSRCommunication, DSRAuditEntry, DSRStatistics, DSRDataExport, DSRErasureChecklist } from './types' // ============================================================================= // CONFIGURATION // ============================================================================= const DSR_API_BASE = process.env.NEXT_PUBLIC_CONSENT_SERVICE_URL || 'http://localhost:8081' const API_TIMEOUT = 30000 // 30 seconds // ============================================================================= // HELPER FUNCTIONS // ============================================================================= function getTenantId(): string { // In a real app, this would come from auth context or localStorage if (typeof window !== 'undefined') { return localStorage.getItem('tenantId') || 'default-tenant' } return 'default-tenant' } function getAuthHeaders(): HeadersInit { const headers: HeadersInit = { 'Content-Type': 'application/json', 'X-Tenant-ID': getTenantId() } // Add auth token if available if (typeof window !== 'undefined') { const token = localStorage.getItem('authToken') if (token) { headers['Authorization'] = `Bearer ${token}` } } return headers } async function fetchWithTimeout( url: string, options: RequestInit = {}, timeout: number = API_TIMEOUT ): Promise { const controller = new AbortController() const timeoutId = setTimeout(() => controller.abort(), timeout) try { const response = await fetch(url, { ...options, signal: controller.signal, headers: { ...getAuthHeaders(), ...options.headers } }) if (!response.ok) { const errorBody = await response.text() let errorMessage = `HTTP ${response.status}: ${response.statusText}` try { const errorJson = JSON.parse(errorBody) errorMessage = errorJson.error || errorJson.message || errorMessage } catch { // Keep the HTTP status message } throw new Error(errorMessage) } // Handle empty responses const contentType = response.headers.get('content-type') if (contentType && contentType.includes('application/json')) { return response.json() } return {} as T } finally { clearTimeout(timeoutId) } } // ============================================================================= // DSR LIST & CRUD // ============================================================================= /** * Fetch all DSR requests with optional filters */ export async function fetchDSRList(filters?: DSRFilters): Promise { const params = new URLSearchParams() if (filters) { if (filters.status) { const statuses = Array.isArray(filters.status) ? filters.status : [filters.status] statuses.forEach(s => params.append('status', s)) } if (filters.type) { const types = Array.isArray(filters.type) ? filters.type : [filters.type] types.forEach(t => params.append('type', t)) } if (filters.priority) params.set('priority', filters.priority) if (filters.assignedTo) params.set('assignedTo', filters.assignedTo) if (filters.overdue !== undefined) params.set('overdue', String(filters.overdue)) if (filters.search) params.set('search', filters.search) if (filters.dateFrom) params.set('dateFrom', filters.dateFrom) if (filters.dateTo) params.set('dateTo', filters.dateTo) } const queryString = params.toString() const url = `${DSR_API_BASE}/api/v1/admin/dsr${queryString ? `?${queryString}` : ''}` return fetchWithTimeout(url) } /** * Fetch a single DSR request by ID */ export async function fetchDSR(id: string): Promise { return fetchWithTimeout(`${DSR_API_BASE}/api/v1/admin/dsr/${id}`) } /** * Create a new DSR request */ export async function createDSR(request: DSRCreateRequest): Promise { return fetchWithTimeout(`${DSR_API_BASE}/api/v1/admin/dsr`, { method: 'POST', body: JSON.stringify(request) }) } /** * Update a DSR request */ export async function updateDSR(id: string, update: DSRUpdateRequest): Promise { return fetchWithTimeout(`${DSR_API_BASE}/api/v1/admin/dsr/${id}`, { method: 'PUT', body: JSON.stringify(update) }) } /** * Delete a DSR request (soft delete - marks as cancelled) */ export async function deleteDSR(id: string): Promise { await fetchWithTimeout(`${DSR_API_BASE}/api/v1/admin/dsr/${id}`, { method: 'DELETE' }) } // ============================================================================= // DSR WORKFLOW ACTIONS // ============================================================================= /** * Verify the identity of the requester */ export async function verifyIdentity( dsrId: string, verification: DSRVerifyIdentityRequest ): Promise { return fetchWithTimeout( `${DSR_API_BASE}/api/v1/admin/dsr/${dsrId}/verify-identity`, { method: 'POST', body: JSON.stringify(verification) } ) } /** * Complete a DSR request */ export async function completeDSR( dsrId: string, completion?: DSRCompleteRequest ): Promise { return fetchWithTimeout( `${DSR_API_BASE}/api/v1/admin/dsr/${dsrId}/complete`, { method: 'POST', body: JSON.stringify(completion || {}) } ) } /** * Reject a DSR request */ export async function rejectDSR( dsrId: string, rejection: DSRRejectRequest ): Promise { return fetchWithTimeout( `${DSR_API_BASE}/api/v1/admin/dsr/${dsrId}/reject`, { method: 'POST', body: JSON.stringify(rejection) } ) } /** * Extend the deadline for a DSR request */ export async function extendDeadline( dsrId: string, extension: DSRExtendDeadlineRequest ): Promise { return fetchWithTimeout( `${DSR_API_BASE}/api/v1/admin/dsr/${dsrId}/extend`, { method: 'POST', body: JSON.stringify(extension) } ) } /** * Assign a DSR request to a user */ export async function assignDSR( dsrId: string, assignedTo: string ): Promise { return fetchWithTimeout( `${DSR_API_BASE}/api/v1/admin/dsr/${dsrId}/assign`, { method: 'POST', body: JSON.stringify({ assignedTo }) } ) } // ============================================================================= // COMMUNICATION // ============================================================================= /** * Get all communications for a DSR request */ export async function getCommunications(dsrId: string): Promise { return fetchWithTimeout( `${DSR_API_BASE}/api/v1/admin/dsr/${dsrId}/communications` ) } /** * Send a communication (email, letter, internal note) */ export async function sendCommunication( dsrId: string, communication: DSRSendCommunicationRequest ): Promise { return fetchWithTimeout( `${DSR_API_BASE}/api/v1/admin/dsr/${dsrId}/send-communication`, { method: 'POST', body: JSON.stringify(communication) } ) } // ============================================================================= // AUDIT LOG // ============================================================================= /** * Get audit log entries for a DSR request */ export async function getAuditLog(dsrId: string): Promise { return fetchWithTimeout( `${DSR_API_BASE}/api/v1/admin/dsr/${dsrId}/audit` ) } // ============================================================================= // STATISTICS // ============================================================================= /** * Get DSR statistics */ export async function getDSRStatistics(): Promise { return fetchWithTimeout( `${DSR_API_BASE}/api/v1/admin/dsr/statistics` ) } // ============================================================================= // DATA EXPORT (Art. 15, 20) // ============================================================================= /** * Generate data export for Art. 15 (access) or Art. 20 (portability) */ export async function generateDataExport( dsrId: string, format: 'json' | 'csv' | 'xml' | 'pdf' = 'json' ): Promise { return fetchWithTimeout( `${DSR_API_BASE}/api/v1/admin/dsr/${dsrId}/export`, { method: 'POST', body: JSON.stringify({ format }) } ) } /** * Download generated data export */ export async function downloadDataExport(dsrId: string): Promise { const response = await fetch( `${DSR_API_BASE}/api/v1/admin/dsr/${dsrId}/export/download`, { headers: getAuthHeaders() } ) if (!response.ok) { throw new Error(`Download failed: ${response.statusText}`) } return response.blob() } // ============================================================================= // ERASURE CHECKLIST (Art. 17) // ============================================================================= /** * Get the erasure checklist for an Art. 17 request */ export async function getErasureChecklist(dsrId: string): Promise { return fetchWithTimeout( `${DSR_API_BASE}/api/v1/admin/dsr/${dsrId}/erasure-checklist` ) } /** * Update the erasure checklist */ export async function updateErasureChecklist( dsrId: string, checklist: DSRErasureChecklist ): Promise { return fetchWithTimeout( `${DSR_API_BASE}/api/v1/admin/dsr/${dsrId}/erasure-checklist`, { method: 'PUT', body: JSON.stringify(checklist) } ) } // ============================================================================= // EMAIL TEMPLATES // ============================================================================= /** * Get available email templates */ export async function getEmailTemplates(): Promise<{ id: string; name: string; stage: string }[]> { return fetchWithTimeout<{ id: string; name: string; stage: string }[]>( `${DSR_API_BASE}/api/v1/admin/dsr/email-templates` ) } /** * Preview an email with variables filled in */ export async function previewEmail( templateId: string, dsrId: string ): Promise<{ subject: string; body: string }> { return fetchWithTimeout<{ subject: string; body: string }>( `${DSR_API_BASE}/api/v1/admin/dsr/email-templates/${templateId}/preview`, { method: 'POST', body: JSON.stringify({ dsrId }) } ) } // ============================================================================= // MOCK DATA FUNCTIONS (for development without backend) // ============================================================================= export function createMockDSRList(): DSRRequest[] { const now = new Date() return [ { id: 'dsr-001', referenceNumber: 'DSR-2025-000001', type: 'access', status: 'intake', priority: 'high', requester: { name: 'Max Mustermann', email: 'max.mustermann@example.de' }, source: 'web_form', sourceDetails: 'Kontaktformular auf breakpilot.de', receivedAt: new Date(now.getTime() - 2 * 24 * 60 * 60 * 1000).toISOString(), deadline: { originalDeadline: new Date(now.getTime() + 28 * 24 * 60 * 60 * 1000).toISOString(), currentDeadline: new Date(now.getTime() + 28 * 24 * 60 * 60 * 1000).toISOString(), extended: false }, identityVerification: { verified: false }, assignment: { assignedTo: null }, createdAt: new Date(now.getTime() - 2 * 24 * 60 * 60 * 1000).toISOString(), createdBy: 'system', updatedAt: new Date(now.getTime() - 2 * 24 * 60 * 60 * 1000).toISOString(), tenantId: 'default-tenant' }, { id: 'dsr-002', referenceNumber: 'DSR-2025-000002', type: 'erasure', status: 'identity_verification', priority: 'high', requester: { name: 'Anna Schmidt', email: 'anna.schmidt@example.de', phone: '+49 170 1234567' }, source: 'email', requestText: 'Ich moechte, dass alle meine Daten geloescht werden.', receivedAt: new Date(now.getTime() - 5 * 24 * 60 * 60 * 1000).toISOString(), deadline: { originalDeadline: new Date(now.getTime() + 9 * 24 * 60 * 60 * 1000).toISOString(), currentDeadline: new Date(now.getTime() + 9 * 24 * 60 * 60 * 1000).toISOString(), extended: false }, identityVerification: { verified: false }, assignment: { assignedTo: 'DSB Mueller', assignedAt: new Date(now.getTime() - 4 * 24 * 60 * 60 * 1000).toISOString() }, createdAt: new Date(now.getTime() - 5 * 24 * 60 * 60 * 1000).toISOString(), createdBy: 'system', updatedAt: new Date(now.getTime() - 4 * 24 * 60 * 60 * 1000).toISOString(), tenantId: 'default-tenant' }, { id: 'dsr-003', referenceNumber: 'DSR-2025-000003', type: 'rectification', status: 'processing', priority: 'normal', requester: { name: 'Peter Meier', email: 'peter.meier@example.de' }, source: 'email', requestText: 'Meine Adresse ist falsch gespeichert.', receivedAt: new Date(now.getTime() - 7 * 24 * 60 * 60 * 1000).toISOString(), deadline: { originalDeadline: new Date(now.getTime() + 7 * 24 * 60 * 60 * 1000).toISOString(), currentDeadline: new Date(now.getTime() + 7 * 24 * 60 * 60 * 1000).toISOString(), extended: false }, identityVerification: { verified: true, method: 'existing_account', verifiedAt: new Date(now.getTime() - 6 * 24 * 60 * 60 * 1000).toISOString(), verifiedBy: 'DSB Mueller' }, assignment: { assignedTo: 'DSB Mueller', assignedAt: new Date(now.getTime() - 6 * 24 * 60 * 60 * 1000).toISOString() }, rectificationDetails: { fieldsToCorrect: [ { field: 'Adresse', currentValue: 'Musterstr. 1, 12345 Berlin', requestedValue: 'Musterstr. 10, 12345 Berlin', corrected: false } ] }, createdAt: new Date(now.getTime() - 7 * 24 * 60 * 60 * 1000).toISOString(), createdBy: 'system', updatedAt: new Date(now.getTime() - 1 * 24 * 60 * 60 * 1000).toISOString(), tenantId: 'default-tenant' }, { id: 'dsr-004', referenceNumber: 'DSR-2025-000004', type: 'portability', status: 'processing', priority: 'normal', requester: { name: 'Lisa Weber', email: 'lisa.weber@example.de' }, source: 'web_form', receivedAt: new Date(now.getTime() - 10 * 24 * 60 * 60 * 1000).toISOString(), deadline: { originalDeadline: new Date(now.getTime() + 20 * 24 * 60 * 60 * 1000).toISOString(), currentDeadline: new Date(now.getTime() + 20 * 24 * 60 * 60 * 1000).toISOString(), extended: false }, identityVerification: { verified: true, method: 'id_document', verifiedAt: new Date(now.getTime() - 8 * 24 * 60 * 60 * 1000).toISOString(), verifiedBy: 'DSB Mueller' }, assignment: { assignedTo: 'IT Team', assignedAt: new Date(now.getTime() - 8 * 24 * 60 * 60 * 1000).toISOString() }, notes: 'JSON-Export wird vorbereitet', createdAt: new Date(now.getTime() - 10 * 24 * 60 * 60 * 1000).toISOString(), createdBy: 'system', updatedAt: new Date(now.getTime() - 2 * 24 * 60 * 60 * 1000).toISOString(), tenantId: 'default-tenant' }, { id: 'dsr-005', referenceNumber: 'DSR-2025-000005', type: 'objection', status: 'rejected', priority: 'low', requester: { name: 'Thomas Klein', email: 'thomas.klein@example.de' }, source: 'letter', requestText: 'Ich widerspreche der Verarbeitung meiner Daten fuer Marketingzwecke.', receivedAt: new Date(now.getTime() - 35 * 24 * 60 * 60 * 1000).toISOString(), deadline: { originalDeadline: new Date(now.getTime() - 5 * 24 * 60 * 60 * 1000).toISOString(), currentDeadline: new Date(now.getTime() - 5 * 24 * 60 * 60 * 1000).toISOString(), extended: false }, completedAt: new Date(now.getTime() - 7 * 24 * 60 * 60 * 1000).toISOString(), identityVerification: { verified: true, method: 'postal', verifiedAt: new Date(now.getTime() - 30 * 24 * 60 * 60 * 1000).toISOString(), verifiedBy: 'DSB Mueller' }, assignment: { assignedTo: 'Rechtsabteilung', assignedAt: new Date(now.getTime() - 28 * 24 * 60 * 60 * 1000).toISOString() }, objectionDetails: { processingPurpose: 'Marketing', legalBasis: 'Berechtigtes Interesse (Art. 6(1)(f))', objectionGrounds: 'Keine konkreten Gruende genannt', decision: 'rejected', decisionReason: 'Zwingende schutzwuerdige Gruende fuer die Verarbeitung ueberwiegen', decisionBy: 'Rechtsabteilung', decisionAt: new Date(now.getTime() - 7 * 24 * 60 * 60 * 1000).toISOString() }, notes: 'Widerspruch unberechtigt - zwingende schutzwuerdige Gruende', createdAt: new Date(now.getTime() - 35 * 24 * 60 * 60 * 1000).toISOString(), createdBy: 'system', updatedAt: new Date(now.getTime() - 7 * 24 * 60 * 60 * 1000).toISOString(), tenantId: 'default-tenant' }, { id: 'dsr-006', referenceNumber: 'DSR-2025-000006', type: 'access', status: 'completed', priority: 'normal', requester: { name: 'Sarah Braun', email: 'sarah.braun@example.de' }, source: 'email', receivedAt: new Date(now.getTime() - 45 * 24 * 60 * 60 * 1000).toISOString(), deadline: { originalDeadline: new Date(now.getTime() - 15 * 24 * 60 * 60 * 1000).toISOString(), currentDeadline: new Date(now.getTime() - 15 * 24 * 60 * 60 * 1000).toISOString(), extended: false }, completedAt: new Date(now.getTime() - 20 * 24 * 60 * 60 * 1000).toISOString(), identityVerification: { verified: true, method: 'id_document', verifiedAt: new Date(now.getTime() - 42 * 24 * 60 * 60 * 1000).toISOString(), verifiedBy: 'DSB Mueller' }, assignment: { assignedTo: 'DSB Mueller', assignedAt: new Date(now.getTime() - 42 * 24 * 60 * 60 * 1000).toISOString() }, dataExport: { format: 'pdf', generatedAt: new Date(now.getTime() - 20 * 24 * 60 * 60 * 1000).toISOString(), generatedBy: 'DSB Mueller', fileName: 'datenauskunft_sarah_braun.pdf', fileSize: 245000, includesThirdPartyData: false }, createdAt: new Date(now.getTime() - 45 * 24 * 60 * 60 * 1000).toISOString(), createdBy: 'system', updatedAt: new Date(now.getTime() - 20 * 24 * 60 * 60 * 1000).toISOString(), tenantId: 'default-tenant' } ] } export function createMockStatistics(): DSRStatistics { return { total: 6, byStatus: { intake: 1, identity_verification: 1, processing: 2, completed: 1, rejected: 1, cancelled: 0 }, byType: { access: 2, rectification: 1, erasure: 1, restriction: 0, portability: 1, objection: 1 }, overdue: 0, dueThisWeek: 2, averageProcessingDays: 18, completedThisMonth: 1 } }