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>
307 lines
9.2 KiB
TypeScript
307 lines
9.2 KiB
TypeScript
/**
|
|
* 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`
|
|
)
|
|
}
|