feat: Add Academy, Whistleblower, Incidents, Vendor, DSB, SSO, Reporting, Multi-Tenant and Industry backends
Go handlers, models, stores and migrations for all SDK modules. Updates developer portal navigation and BYOEH page. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
242
ai-compliance-sdk/internal/whistleblower/models.go
Normal file
242
ai-compliance-sdk/internal/whistleblower/models.go
Normal file
@@ -0,0 +1,242 @@
|
||||
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)
|
||||
}
|
||||
Reference in New Issue
Block a user