package whistleblower import ( "crypto/rand" "fmt" "time" "github.com/google/uuid" ) // ============================================================================ // Constants / Enums // ============================================================================ // ReportCategory represents the category of a whistleblower report type ReportCategory string const ( ReportCategoryCorruption ReportCategory = "corruption" ReportCategoryFraud ReportCategory = "fraud" ReportCategoryDataProtection ReportCategory = "data_protection" ReportCategoryDiscrimination ReportCategory = "discrimination" ReportCategoryEnvironment ReportCategory = "environment" ReportCategoryCompetition ReportCategory = "competition" ReportCategoryProductSafety ReportCategory = "product_safety" ReportCategoryTaxEvasion ReportCategory = "tax_evasion" ReportCategoryOther ReportCategory = "other" ) // ReportStatus represents the status of a whistleblower report type ReportStatus string const ( ReportStatusNew ReportStatus = "new" ReportStatusAcknowledged ReportStatus = "acknowledged" ReportStatusUnderReview ReportStatus = "under_review" ReportStatusInvestigation ReportStatus = "investigation" ReportStatusMeasuresTaken ReportStatus = "measures_taken" ReportStatusClosed ReportStatus = "closed" ReportStatusRejected ReportStatus = "rejected" ) // MessageDirection represents the direction of an anonymous message type MessageDirection string const ( MessageDirectionReporterToAdmin MessageDirection = "reporter_to_admin" MessageDirectionAdminToReporter MessageDirection = "admin_to_reporter" ) // MeasureStatus represents the status of a corrective measure type MeasureStatus string const ( MeasureStatusPlanned MeasureStatus = "planned" MeasureStatusInProgress MeasureStatus = "in_progress" MeasureStatusCompleted MeasureStatus = "completed" ) // ============================================================================ // Main Entities // ============================================================================ // Report represents a whistleblower report (Hinweis) per HinSchG type Report struct { ID uuid.UUID `json:"id"` TenantID uuid.UUID `json:"tenant_id"` ReferenceNumber string `json:"reference_number"` // e.g. "WB-2026-0001" AccessKey string `json:"access_key,omitempty"` // for anonymous access, only returned once // Report content Category ReportCategory `json:"category"` Status ReportStatus `json:"status"` Title string `json:"title"` Description string `json:"description"` // Reporter info (optional, for non-anonymous reports) IsAnonymous bool `json:"is_anonymous"` ReporterName *string `json:"reporter_name,omitempty"` ReporterEmail *string `json:"reporter_email,omitempty"` ReporterPhone *string `json:"reporter_phone,omitempty"` // HinSchG deadlines ReceivedAt time.Time `json:"received_at"` DeadlineAcknowledgment time.Time `json:"deadline_acknowledgment"` // 7 days from received_at per HinSchG DeadlineFeedback time.Time `json:"deadline_feedback"` // 3 months from received_at per HinSchG // Status timestamps AcknowledgedAt *time.Time `json:"acknowledged_at,omitempty"` ClosedAt *time.Time `json:"closed_at,omitempty"` // Assignment AssignedTo *uuid.UUID `json:"assigned_to,omitempty"` // Resolution Resolution string `json:"resolution,omitempty"` // Audit trail (stored as JSONB) AuditTrail []AuditEntry `json:"audit_trail"` // Timestamps CreatedAt time.Time `json:"created_at"` UpdatedAt time.Time `json:"updated_at"` } // AnonymousMessage represents a message exchanged between reporter and admin type AnonymousMessage struct { ID uuid.UUID `json:"id"` ReportID uuid.UUID `json:"report_id"` Direction MessageDirection `json:"direction"` Content string `json:"content"` SentAt time.Time `json:"sent_at"` ReadAt *time.Time `json:"read_at,omitempty"` } // Measure represents a corrective measure taken for a report type Measure struct { ID uuid.UUID `json:"id"` ReportID uuid.UUID `json:"report_id"` Title string `json:"title"` Description string `json:"description"` Status MeasureStatus `json:"status"` Responsible string `json:"responsible"` DueDate *time.Time `json:"due_date,omitempty"` CompletedAt *time.Time `json:"completed_at,omitempty"` CreatedAt time.Time `json:"created_at"` } // AuditEntry represents an entry in the audit trail type AuditEntry struct { Timestamp time.Time `json:"timestamp"` Action string `json:"action"` UserID string `json:"user_id"` Details string `json:"details"` } // WhistleblowerStatistics contains aggregated statistics for a tenant type WhistleblowerStatistics struct { TotalReports int `json:"total_reports"` ByStatus map[string]int `json:"by_status"` ByCategory map[string]int `json:"by_category"` OverdueAcknowledgments int `json:"overdue_acknowledgments"` OverdueFeedbacks int `json:"overdue_feedbacks"` AvgResolutionDays float64 `json:"avg_resolution_days"` } // ============================================================================ // API Request/Response Types // ============================================================================ // PublicReportSubmission is the request for submitting a report (NO auth required) type PublicReportSubmission struct { Category ReportCategory `json:"category" binding:"required"` Title string `json:"title" binding:"required"` Description string `json:"description" binding:"required"` IsAnonymous bool `json:"is_anonymous"` ReporterName *string `json:"reporter_name,omitempty"` ReporterEmail *string `json:"reporter_email,omitempty"` ReporterPhone *string `json:"reporter_phone,omitempty"` } // PublicReportResponse is returned after submitting a report (access_key only shown once!) type PublicReportResponse struct { ReferenceNumber string `json:"reference_number"` AccessKey string `json:"access_key"` } // ReportUpdateRequest is the request for updating a report (admin) type ReportUpdateRequest struct { Category ReportCategory `json:"category,omitempty"` Status ReportStatus `json:"status,omitempty"` Title string `json:"title,omitempty"` Description string `json:"description,omitempty"` AssignedTo *uuid.UUID `json:"assigned_to,omitempty"` } // AcknowledgeRequest is the request for acknowledging a report type AcknowledgeRequest struct { Message string `json:"message,omitempty"` // optional acknowledgment message to reporter } // CloseReportRequest is the request for closing a report type CloseReportRequest struct { Resolution string `json:"resolution" binding:"required"` } // AddMeasureRequest is the request for adding a corrective measure type AddMeasureRequest struct { Title string `json:"title" binding:"required"` Description string `json:"description"` Responsible string `json:"responsible" binding:"required"` DueDate *time.Time `json:"due_date,omitempty"` } // UpdateMeasureRequest is the request for updating a measure type UpdateMeasureRequest struct { Title string `json:"title,omitempty"` Description string `json:"description,omitempty"` Status MeasureStatus `json:"status,omitempty"` Responsible string `json:"responsible,omitempty"` DueDate *time.Time `json:"due_date,omitempty"` } // SendMessageRequest is the request for sending an anonymous message type SendMessageRequest struct { Content string `json:"content" binding:"required"` } // ReportListResponse is the response for listing reports type ReportListResponse struct { Reports []Report `json:"reports"` Total int `json:"total"` } // ReportFilters defines filters for listing reports type ReportFilters struct { Status ReportStatus Category ReportCategory Limit int Offset int } // ============================================================================ // Helper Functions // ============================================================================ // generateAccessKey generates a random 12-character alphanumeric key func generateAccessKey() string { const charset = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789" b := make([]byte, 12) randomBytes := make([]byte, 12) rand.Read(randomBytes) for i := range b { b[i] = charset[int(randomBytes[i])%len(charset)] } return string(b) } // generateReferenceNumber generates a reference number like "WB-2026-0042" func generateReferenceNumber(year int, sequence int) string { return fmt.Sprintf("WB-%d-%04d", year, sequence) }