refactor(admin): split 8 oversized lib/ files into focused modules under 500 LOC
Split these files that exceeded the 500-line hard cap: - privacy-policy.ts (965 LOC) -> sections + renderers - academy/api.ts (787 LOC) -> courses + mock-data - whistleblower/api.ts (755 LOC) -> operations + mock-data - vvt-profiling.ts (659 LOC) -> data + logic - cookie-banner.ts (595 LOC) -> config + embed - dsr/types.ts (581 LOC) -> core + api types - tom-generator/rules-engine.ts (560 LOC) -> evaluator + gap-analysis - datapoint-helpers.ts (548 LOC) -> generators + validators Each original file becomes a barrel re-export for backward compatibility. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
306
admin-compliance/lib/sdk/whistleblower/api-operations.ts
Normal file
306
admin-compliance/lib/sdk/whistleblower/api-operations.ts
Normal file
@@ -0,0 +1,306 @@
|
||||
/**
|
||||
* Whistleblower API Client — CRUD, Workflow, Messaging, Attachments, Statistics
|
||||
*
|
||||
* API client for Hinweisgeberschutzgesetz (HinSchG) compliant
|
||||
* Whistleblower/Hinweisgebersystem management
|
||||
*/
|
||||
|
||||
import {
|
||||
WhistleblowerReport,
|
||||
WhistleblowerStatistics,
|
||||
ReportListResponse,
|
||||
ReportFilters,
|
||||
PublicReportSubmission,
|
||||
ReportUpdateRequest,
|
||||
AnonymousMessage,
|
||||
WhistleblowerMeasure,
|
||||
FileAttachment,
|
||||
} from './types'
|
||||
|
||||
// =============================================================================
|
||||
// CONFIGURATION
|
||||
// =============================================================================
|
||||
|
||||
const WB_API_BASE = process.env.NEXT_PUBLIC_SDK_API_URL || 'http://localhost:8093'
|
||||
const API_TIMEOUT = 30000
|
||||
|
||||
// =============================================================================
|
||||
// HELPER FUNCTIONS
|
||||
// =============================================================================
|
||||
|
||||
function getTenantId(): string {
|
||||
if (typeof window !== 'undefined') {
|
||||
return localStorage.getItem('bp_tenant_id') || 'default-tenant'
|
||||
}
|
||||
return 'default-tenant'
|
||||
}
|
||||
|
||||
function getAuthHeaders(): HeadersInit {
|
||||
const headers: HeadersInit = {
|
||||
'Content-Type': 'application/json',
|
||||
'X-Tenant-ID': getTenantId()
|
||||
}
|
||||
|
||||
if (typeof window !== 'undefined') {
|
||||
const token = localStorage.getItem('authToken')
|
||||
if (token) {
|
||||
headers['Authorization'] = `Bearer ${token}`
|
||||
}
|
||||
const userId = localStorage.getItem('bp_user_id')
|
||||
if (userId) {
|
||||
headers['X-User-ID'] = userId
|
||||
}
|
||||
}
|
||||
|
||||
return headers
|
||||
}
|
||||
|
||||
async function fetchWithTimeout<T>(
|
||||
url: string,
|
||||
options: RequestInit = {},
|
||||
timeout: number = API_TIMEOUT
|
||||
): Promise<T> {
|
||||
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)
|
||||
}
|
||||
|
||||
const contentType = response.headers.get('content-type')
|
||||
if (contentType && contentType.includes('application/json')) {
|
||||
return response.json()
|
||||
}
|
||||
|
||||
return {} as T
|
||||
} finally {
|
||||
clearTimeout(timeoutId)
|
||||
}
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// ADMIN CRUD - Reports
|
||||
// =============================================================================
|
||||
|
||||
export async function fetchReports(filters?: ReportFilters): Promise<ReportListResponse> {
|
||||
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.category) {
|
||||
const categories = Array.isArray(filters.category) ? filters.category : [filters.category]
|
||||
categories.forEach(c => params.append('category', c))
|
||||
}
|
||||
if (filters.priority) params.set('priority', filters.priority)
|
||||
if (filters.assignedTo) params.set('assignedTo', filters.assignedTo)
|
||||
if (filters.isAnonymous !== undefined) params.set('isAnonymous', String(filters.isAnonymous))
|
||||
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 = `${WB_API_BASE}/api/v1/admin/whistleblower/reports${queryString ? `?${queryString}` : ''}`
|
||||
|
||||
return fetchWithTimeout<ReportListResponse>(url)
|
||||
}
|
||||
|
||||
export async function fetchReport(id: string): Promise<WhistleblowerReport> {
|
||||
return fetchWithTimeout<WhistleblowerReport>(
|
||||
`${WB_API_BASE}/api/v1/admin/whistleblower/reports/${id}`
|
||||
)
|
||||
}
|
||||
|
||||
export async function updateReport(id: string, update: ReportUpdateRequest): Promise<WhistleblowerReport> {
|
||||
return fetchWithTimeout<WhistleblowerReport>(
|
||||
`${WB_API_BASE}/api/v1/admin/whistleblower/reports/${id}`,
|
||||
{ method: 'PUT', body: JSON.stringify(update) }
|
||||
)
|
||||
}
|
||||
|
||||
export async function deleteReport(id: string): Promise<void> {
|
||||
await fetchWithTimeout<void>(
|
||||
`${WB_API_BASE}/api/v1/admin/whistleblower/reports/${id}`,
|
||||
{ method: 'DELETE' }
|
||||
)
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// PUBLIC ENDPOINTS
|
||||
// =============================================================================
|
||||
|
||||
export async function submitPublicReport(
|
||||
data: PublicReportSubmission
|
||||
): Promise<{ report: WhistleblowerReport; accessKey: string }> {
|
||||
const response = await fetch(
|
||||
`${WB_API_BASE}/api/v1/public/whistleblower/submit`,
|
||||
{
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(data)
|
||||
}
|
||||
)
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP ${response.status}: ${response.statusText}`)
|
||||
}
|
||||
|
||||
return response.json()
|
||||
}
|
||||
|
||||
export async function fetchReportByAccessKey(
|
||||
accessKey: string
|
||||
): Promise<WhistleblowerReport> {
|
||||
const response = await fetch(
|
||||
`${WB_API_BASE}/api/v1/public/whistleblower/report/${accessKey}`,
|
||||
{ method: 'GET', headers: { 'Content-Type': 'application/json' } }
|
||||
)
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP ${response.status}: ${response.statusText}`)
|
||||
}
|
||||
|
||||
return response.json()
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// WORKFLOW ACTIONS
|
||||
// =============================================================================
|
||||
|
||||
export async function acknowledgeReport(id: string): Promise<WhistleblowerReport> {
|
||||
return fetchWithTimeout<WhistleblowerReport>(
|
||||
`${WB_API_BASE}/api/v1/admin/whistleblower/reports/${id}/acknowledge`,
|
||||
{ method: 'POST' }
|
||||
)
|
||||
}
|
||||
|
||||
export async function startInvestigation(id: string): Promise<WhistleblowerReport> {
|
||||
return fetchWithTimeout<WhistleblowerReport>(
|
||||
`${WB_API_BASE}/api/v1/admin/whistleblower/reports/${id}/investigate`,
|
||||
{ method: 'POST' }
|
||||
)
|
||||
}
|
||||
|
||||
export async function addMeasure(
|
||||
id: string,
|
||||
measure: Omit<WhistleblowerMeasure, 'id' | 'reportId' | 'completedAt'>
|
||||
): Promise<WhistleblowerMeasure> {
|
||||
return fetchWithTimeout<WhistleblowerMeasure>(
|
||||
`${WB_API_BASE}/api/v1/admin/whistleblower/reports/${id}/measures`,
|
||||
{ method: 'POST', body: JSON.stringify(measure) }
|
||||
)
|
||||
}
|
||||
|
||||
export async function closeReport(
|
||||
id: string,
|
||||
resolution: { reason: string; notes: string }
|
||||
): Promise<WhistleblowerReport> {
|
||||
return fetchWithTimeout<WhistleblowerReport>(
|
||||
`${WB_API_BASE}/api/v1/admin/whistleblower/reports/${id}/close`,
|
||||
{ method: 'POST', body: JSON.stringify(resolution) }
|
||||
)
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// ANONYMOUS MESSAGING
|
||||
// =============================================================================
|
||||
|
||||
export async function sendMessage(
|
||||
reportId: string,
|
||||
message: string,
|
||||
role: 'reporter' | 'ombudsperson'
|
||||
): Promise<AnonymousMessage> {
|
||||
return fetchWithTimeout<AnonymousMessage>(
|
||||
`${WB_API_BASE}/api/v1/admin/whistleblower/reports/${reportId}/messages`,
|
||||
{ method: 'POST', body: JSON.stringify({ senderRole: role, message }) }
|
||||
)
|
||||
}
|
||||
|
||||
export async function fetchMessages(reportId: string): Promise<AnonymousMessage[]> {
|
||||
return fetchWithTimeout<AnonymousMessage[]>(
|
||||
`${WB_API_BASE}/api/v1/admin/whistleblower/reports/${reportId}/messages`
|
||||
)
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// ATTACHMENTS
|
||||
// =============================================================================
|
||||
|
||||
export async function uploadAttachment(
|
||||
reportId: string,
|
||||
file: File
|
||||
): Promise<FileAttachment> {
|
||||
const formData = new FormData()
|
||||
formData.append('file', file)
|
||||
|
||||
const controller = new AbortController()
|
||||
const timeoutId = setTimeout(() => controller.abort(), 60000)
|
||||
|
||||
try {
|
||||
const headers: HeadersInit = {
|
||||
'X-Tenant-ID': getTenantId()
|
||||
}
|
||||
if (typeof window !== 'undefined') {
|
||||
const token = localStorage.getItem('authToken')
|
||||
if (token) {
|
||||
headers['Authorization'] = `Bearer ${token}`
|
||||
}
|
||||
}
|
||||
|
||||
const response = await fetch(
|
||||
`${WB_API_BASE}/api/v1/admin/whistleblower/reports/${reportId}/attachments`,
|
||||
{
|
||||
method: 'POST',
|
||||
headers,
|
||||
body: formData,
|
||||
signal: controller.signal
|
||||
}
|
||||
)
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`Upload fehlgeschlagen: ${response.statusText}`)
|
||||
}
|
||||
|
||||
return response.json()
|
||||
} finally {
|
||||
clearTimeout(timeoutId)
|
||||
}
|
||||
}
|
||||
|
||||
export async function deleteAttachment(id: string): Promise<void> {
|
||||
await fetchWithTimeout<void>(
|
||||
`${WB_API_BASE}/api/v1/admin/whistleblower/attachments/${id}`,
|
||||
{ method: 'DELETE' }
|
||||
)
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// STATISTICS
|
||||
// =============================================================================
|
||||
|
||||
export async function fetchWhistleblowerStatistics(): Promise<WhistleblowerStatistics> {
|
||||
return fetchWithTimeout<WhistleblowerStatistics>(
|
||||
`${WB_API_BASE}/api/v1/admin/whistleblower/statistics`
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user