Install LOC guardrails (check-loc.sh, architecture.md, pre-commit hook) and split all 44 files exceeding 500 LOC into domain-focused modules: - consent-service (Go): models, handlers, services, database splits - backend-core (Python): security_api, rbac_api, pdf_service, auth splits - admin-core (TypeScript): 5 page.tsx + sidebar extractions - pitch-deck (TypeScript): 6 slides, 3 UI components, engine.ts splits - voice-service (Python): enhanced_task_orchestrator split Result: 0 violations, 36 exempted (pipeline, tests, pure-data files). Go build verified clean. No behavior changes — pure structural splits. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
404 lines
18 KiB
Go
404 lines
18 KiB
Go
package models
|
|
|
|
import (
|
|
"time"
|
|
|
|
"github.com/google/uuid"
|
|
)
|
|
|
|
// ========================================
|
|
// DSGVO Betroffenenanfragen (DSR)
|
|
// Data Subject Request Management
|
|
// Art. 15, 16, 17, 18, 20 DSGVO
|
|
// ========================================
|
|
|
|
// DSRRequestType defines the GDPR article for the request
|
|
type DSRRequestType string
|
|
|
|
const (
|
|
DSRTypeAccess DSRRequestType = "access" // Art. 15 - Auskunftsrecht
|
|
DSRTypeRectification DSRRequestType = "rectification" // Art. 16 - Berichtigungsrecht
|
|
DSRTypeErasure DSRRequestType = "erasure" // Art. 17 - Löschungsrecht
|
|
DSRTypeRestriction DSRRequestType = "restriction" // Art. 18 - Einschränkungsrecht
|
|
DSRTypePortability DSRRequestType = "portability" // Art. 20 - Datenübertragbarkeit
|
|
)
|
|
|
|
// DSRStatus defines the workflow state of a DSR
|
|
type DSRStatus string
|
|
|
|
const (
|
|
DSRStatusIntake DSRStatus = "intake" // Eingegangen
|
|
DSRStatusIdentityVerification DSRStatus = "identity_verification" // Identitätsprüfung
|
|
DSRStatusProcessing DSRStatus = "processing" // In Bearbeitung
|
|
DSRStatusCompleted DSRStatus = "completed" // Abgeschlossen
|
|
DSRStatusRejected DSRStatus = "rejected" // Abgelehnt
|
|
DSRStatusCancelled DSRStatus = "cancelled" // Storniert
|
|
)
|
|
|
|
// DSRPriority defines the priority level of a DSR
|
|
type DSRPriority string
|
|
|
|
const (
|
|
DSRPriorityNormal DSRPriority = "normal"
|
|
DSRPriorityExpedited DSRPriority = "expedited" // Art. 16, 17, 18 - beschleunigt
|
|
DSRPriorityUrgent DSRPriority = "urgent"
|
|
)
|
|
|
|
// DSRSource defines where the request came from
|
|
type DSRSource string
|
|
|
|
const (
|
|
DSRSourceAPI DSRSource = "api" // Über API/Self-Service
|
|
DSRSourceAdminPanel DSRSource = "admin_panel" // Manuell im Admin
|
|
DSRSourceEmail DSRSource = "email" // Per E-Mail
|
|
DSRSourcePostal DSRSource = "postal" // Per Post
|
|
)
|
|
|
|
// Art. 17(3) Exception Types
|
|
const (
|
|
DSRExceptionFreedomExpression = "freedom_expression" // Art. 17(3)(a)
|
|
DSRExceptionLegalObligation = "legal_obligation" // Art. 17(3)(b)
|
|
DSRExceptionPublicInterest = "public_interest" // Art. 17(3)(c)
|
|
DSRExceptionPublicHealth = "public_health" // Art. 17(3)(c)
|
|
DSRExceptionArchiving = "archiving" // Art. 17(3)(d)
|
|
DSRExceptionLegalClaims = "legal_claims" // Art. 17(3)(e)
|
|
)
|
|
|
|
// DataSubjectRequest represents a GDPR data subject request
|
|
type DataSubjectRequest struct {
|
|
ID uuid.UUID `json:"id" db:"id"`
|
|
UserID *uuid.UUID `json:"user_id,omitempty" db:"user_id"`
|
|
RequestNumber string `json:"request_number" db:"request_number"`
|
|
RequestType DSRRequestType `json:"request_type" db:"request_type"`
|
|
Status DSRStatus `json:"status" db:"status"`
|
|
Priority DSRPriority `json:"priority" db:"priority"`
|
|
Source DSRSource `json:"source" db:"source"`
|
|
RequesterEmail string `json:"requester_email" db:"requester_email"`
|
|
RequesterName *string `json:"requester_name,omitempty" db:"requester_name"`
|
|
RequesterPhone *string `json:"requester_phone,omitempty" db:"requester_phone"`
|
|
IdentityVerified bool `json:"identity_verified" db:"identity_verified"`
|
|
IdentityVerifiedAt *time.Time `json:"identity_verified_at,omitempty" db:"identity_verified_at"`
|
|
IdentityVerifiedBy *uuid.UUID `json:"identity_verified_by,omitempty" db:"identity_verified_by"`
|
|
IdentityVerificationMethod *string `json:"identity_verification_method,omitempty" db:"identity_verification_method"`
|
|
RequestDetails map[string]interface{} `json:"request_details" db:"request_details"`
|
|
DeadlineAt time.Time `json:"deadline_at" db:"deadline_at"`
|
|
LegalDeadlineDays int `json:"legal_deadline_days" db:"legal_deadline_days"`
|
|
ExtendedDeadlineAt *time.Time `json:"extended_deadline_at,omitempty" db:"extended_deadline_at"`
|
|
ExtensionReason *string `json:"extension_reason,omitempty" db:"extension_reason"`
|
|
AssignedTo *uuid.UUID `json:"assigned_to,omitempty" db:"assigned_to"`
|
|
ProcessingNotes *string `json:"processing_notes,omitempty" db:"processing_notes"`
|
|
CompletedAt *time.Time `json:"completed_at,omitempty" db:"completed_at"`
|
|
CompletedBy *uuid.UUID `json:"completed_by,omitempty" db:"completed_by"`
|
|
ResultSummary *string `json:"result_summary,omitempty" db:"result_summary"`
|
|
ResultData map[string]interface{} `json:"result_data,omitempty" db:"result_data"`
|
|
RejectedAt *time.Time `json:"rejected_at,omitempty" db:"rejected_at"`
|
|
RejectedBy *uuid.UUID `json:"rejected_by,omitempty" db:"rejected_by"`
|
|
RejectionReason *string `json:"rejection_reason,omitempty" db:"rejection_reason"`
|
|
RejectionLegalBasis *string `json:"rejection_legal_basis,omitempty" db:"rejection_legal_basis"`
|
|
CreatedAt time.Time `json:"created_at" db:"created_at"`
|
|
UpdatedAt time.Time `json:"updated_at" db:"updated_at"`
|
|
CreatedBy *uuid.UUID `json:"created_by,omitempty" db:"created_by"`
|
|
}
|
|
|
|
// DSRStatusHistory tracks status changes for audit trail
|
|
type DSRStatusHistory struct {
|
|
ID uuid.UUID `json:"id" db:"id"`
|
|
RequestID uuid.UUID `json:"request_id" db:"request_id"`
|
|
FromStatus *DSRStatus `json:"from_status,omitempty" db:"from_status"`
|
|
ToStatus DSRStatus `json:"to_status" db:"to_status"`
|
|
ChangedBy *uuid.UUID `json:"changed_by,omitempty" db:"changed_by"`
|
|
Comment *string `json:"comment,omitempty" db:"comment"`
|
|
Metadata map[string]interface{} `json:"metadata,omitempty" db:"metadata"`
|
|
CreatedAt time.Time `json:"created_at" db:"created_at"`
|
|
}
|
|
|
|
// DSRCommunication tracks all communications related to a DSR
|
|
type DSRCommunication struct {
|
|
ID uuid.UUID `json:"id" db:"id"`
|
|
RequestID uuid.UUID `json:"request_id" db:"request_id"`
|
|
Direction string `json:"direction" db:"direction"`
|
|
Channel string `json:"channel" db:"channel"`
|
|
CommunicationType string `json:"communication_type" db:"communication_type"`
|
|
TemplateVersionID *uuid.UUID `json:"template_version_id,omitempty" db:"template_version_id"`
|
|
Subject *string `json:"subject,omitempty" db:"subject"`
|
|
BodyHTML *string `json:"body_html,omitempty" db:"body_html"`
|
|
BodyText *string `json:"body_text,omitempty" db:"body_text"`
|
|
RecipientEmail *string `json:"recipient_email,omitempty" db:"recipient_email"`
|
|
SentAt *time.Time `json:"sent_at,omitempty" db:"sent_at"`
|
|
ErrorMessage *string `json:"error_message,omitempty" db:"error_message"`
|
|
Attachments []map[string]interface{} `json:"attachments,omitempty" db:"attachments"`
|
|
CreatedAt time.Time `json:"created_at" db:"created_at"`
|
|
CreatedBy *uuid.UUID `json:"created_by,omitempty" db:"created_by"`
|
|
}
|
|
|
|
// DSRTemplate represents a template type for DSR communications
|
|
type DSRTemplate struct {
|
|
ID uuid.UUID `json:"id" db:"id"`
|
|
TemplateType string `json:"template_type" db:"template_type"`
|
|
Name string `json:"name" db:"name"`
|
|
Description *string `json:"description,omitempty" db:"description"`
|
|
RequestTypes []string `json:"request_types" db:"request_types"`
|
|
IsActive bool `json:"is_active" db:"is_active"`
|
|
SortOrder int `json:"sort_order" db:"sort_order"`
|
|
CreatedAt time.Time `json:"created_at" db:"created_at"`
|
|
UpdatedAt time.Time `json:"updated_at" db:"updated_at"`
|
|
}
|
|
|
|
// DSRTemplateVersion represents a versioned template for DSR communications
|
|
type DSRTemplateVersion struct {
|
|
ID uuid.UUID `json:"id" db:"id"`
|
|
TemplateID uuid.UUID `json:"template_id" db:"template_id"`
|
|
Version string `json:"version" db:"version"`
|
|
Language string `json:"language" db:"language"`
|
|
Subject string `json:"subject" db:"subject"`
|
|
BodyHTML string `json:"body_html" db:"body_html"`
|
|
BodyText string `json:"body_text" db:"body_text"`
|
|
Status string `json:"status" db:"status"`
|
|
PublishedAt *time.Time `json:"published_at,omitempty" db:"published_at"`
|
|
CreatedBy *uuid.UUID `json:"created_by,omitempty" db:"created_by"`
|
|
ApprovedBy *uuid.UUID `json:"approved_by,omitempty" db:"approved_by"`
|
|
ApprovedAt *time.Time `json:"approved_at,omitempty" db:"approved_at"`
|
|
CreatedAt time.Time `json:"created_at" db:"created_at"`
|
|
UpdatedAt time.Time `json:"updated_at" db:"updated_at"`
|
|
}
|
|
|
|
// DSRExceptionCheck tracks Art. 17(3) exception evaluations for erasure requests
|
|
type DSRExceptionCheck struct {
|
|
ID uuid.UUID `json:"id" db:"id"`
|
|
RequestID uuid.UUID `json:"request_id" db:"request_id"`
|
|
ExceptionType string `json:"exception_type" db:"exception_type"`
|
|
Description string `json:"description" db:"description"`
|
|
Applies *bool `json:"applies,omitempty" db:"applies"`
|
|
CheckedBy *uuid.UUID `json:"checked_by,omitempty" db:"checked_by"`
|
|
CheckedAt *time.Time `json:"checked_at,omitempty" db:"checked_at"`
|
|
Notes *string `json:"notes,omitempty" db:"notes"`
|
|
CreatedAt time.Time `json:"created_at" db:"created_at"`
|
|
}
|
|
|
|
// ========================================
|
|
// DSR DTOs
|
|
// ========================================
|
|
|
|
// CreateDSRRequest for creating a new data subject request
|
|
type CreateDSRRequest struct {
|
|
RequestType string `json:"request_type" binding:"required"`
|
|
RequesterEmail string `json:"requester_email" binding:"required,email"`
|
|
RequesterName *string `json:"requester_name"`
|
|
RequesterPhone *string `json:"requester_phone"`
|
|
Source string `json:"source"`
|
|
RequestDetails map[string]interface{} `json:"request_details"`
|
|
Priority string `json:"priority"`
|
|
}
|
|
|
|
// UpdateDSRRequest for updating a DSR
|
|
type UpdateDSRRequest struct {
|
|
Status *string `json:"status"`
|
|
AssignedTo *string `json:"assigned_to"`
|
|
ProcessingNotes *string `json:"processing_notes"`
|
|
ExtendDeadline *bool `json:"extend_deadline"`
|
|
ExtensionReason *string `json:"extension_reason"`
|
|
RequestDetails map[string]interface{} `json:"request_details"`
|
|
Priority *string `json:"priority"`
|
|
}
|
|
|
|
// VerifyDSRIdentityRequest for verifying identity of requester
|
|
type VerifyDSRIdentityRequest struct {
|
|
Method string `json:"method" binding:"required"`
|
|
Comment *string `json:"comment"`
|
|
}
|
|
|
|
// CompleteDSRRequest for completing a DSR
|
|
type CompleteDSRRequest struct {
|
|
ResultSummary string `json:"result_summary" binding:"required"`
|
|
ResultData map[string]interface{} `json:"result_data"`
|
|
}
|
|
|
|
// RejectDSRRequest for rejecting a DSR
|
|
type RejectDSRRequest struct {
|
|
Reason string `json:"reason" binding:"required"`
|
|
LegalBasis string `json:"legal_basis" binding:"required"`
|
|
}
|
|
|
|
// ExtendDSRDeadlineRequest for extending a DSR deadline
|
|
type ExtendDSRDeadlineRequest struct {
|
|
Reason string `json:"reason" binding:"required"`
|
|
Days int `json:"days"`
|
|
}
|
|
|
|
// AssignDSRRequest for assigning a DSR to a handler
|
|
type AssignDSRRequest struct {
|
|
AssigneeID string `json:"assignee_id" binding:"required"`
|
|
Comment *string `json:"comment"`
|
|
}
|
|
|
|
// SendDSRCommunicationRequest for sending a communication
|
|
type SendDSRCommunicationRequest struct {
|
|
CommunicationType string `json:"communication_type" binding:"required"`
|
|
TemplateVersionID *string `json:"template_version_id"`
|
|
CustomSubject *string `json:"custom_subject"`
|
|
CustomBody *string `json:"custom_body"`
|
|
Variables map[string]string `json:"variables"`
|
|
}
|
|
|
|
// UpdateDSRExceptionCheckRequest for updating an exception check
|
|
type UpdateDSRExceptionCheckRequest struct {
|
|
Applies bool `json:"applies"`
|
|
Notes *string `json:"notes"`
|
|
}
|
|
|
|
// DSRListFilters for filtering DSR list
|
|
type DSRListFilters struct {
|
|
Status *string `form:"status"`
|
|
RequestType *string `form:"request_type"`
|
|
AssignedTo *string `form:"assigned_to"`
|
|
Priority *string `form:"priority"`
|
|
OverdueOnly bool `form:"overdue_only"`
|
|
FromDate *time.Time `form:"from_date"`
|
|
ToDate *time.Time `form:"to_date"`
|
|
Search *string `form:"search"`
|
|
}
|
|
|
|
// DSRDashboardStats for the admin dashboard
|
|
type DSRDashboardStats struct {
|
|
TotalRequests int `json:"total_requests"`
|
|
PendingRequests int `json:"pending_requests"`
|
|
OverdueRequests int `json:"overdue_requests"`
|
|
CompletedThisMonth int `json:"completed_this_month"`
|
|
AverageProcessingDays float64 `json:"average_processing_days"`
|
|
ByType map[string]int `json:"by_type"`
|
|
ByStatus map[string]int `json:"by_status"`
|
|
UpcomingDeadlines []DataSubjectRequest `json:"upcoming_deadlines"`
|
|
}
|
|
|
|
// DSRWithDetails combines DSR with related data
|
|
type DSRWithDetails struct {
|
|
Request DataSubjectRequest `json:"request"`
|
|
StatusHistory []DSRStatusHistory `json:"status_history"`
|
|
Communications []DSRCommunication `json:"communications"`
|
|
ExceptionChecks []DSRExceptionCheck `json:"exception_checks,omitempty"`
|
|
AssigneeName *string `json:"assignee_name,omitempty"`
|
|
CreatorName *string `json:"creator_name,omitempty"`
|
|
}
|
|
|
|
// DSRTemplateWithVersions combines template with versions
|
|
type DSRTemplateWithVersions struct {
|
|
Template DSRTemplate `json:"template"`
|
|
LatestVersion *DSRTemplateVersion `json:"latest_version,omitempty"`
|
|
Versions []DSRTemplateVersion `json:"versions,omitempty"`
|
|
}
|
|
|
|
// CreateDSRTemplateVersionRequest for creating a template version
|
|
type CreateDSRTemplateVersionRequest struct {
|
|
TemplateID string `json:"template_id" binding:"required"`
|
|
Version string `json:"version" binding:"required"`
|
|
Language string `json:"language" binding:"required"`
|
|
Subject string `json:"subject" binding:"required"`
|
|
BodyHTML string `json:"body_html" binding:"required"`
|
|
BodyText string `json:"body_text" binding:"required"`
|
|
}
|
|
|
|
// UpdateDSRTemplateVersionRequest for updating a template version
|
|
type UpdateDSRTemplateVersionRequest struct {
|
|
Subject *string `json:"subject"`
|
|
BodyHTML *string `json:"body_html"`
|
|
BodyText *string `json:"body_text"`
|
|
Status *string `json:"status"`
|
|
}
|
|
|
|
// PreviewDSRTemplateRequest for previewing a template with variables
|
|
type PreviewDSRTemplateRequest struct {
|
|
Variables map[string]string `json:"variables"`
|
|
}
|
|
|
|
// DSRTemplatePreviewResponse for template preview
|
|
type DSRTemplatePreviewResponse struct {
|
|
Subject string `json:"subject"`
|
|
BodyHTML string `json:"body_html"`
|
|
BodyText string `json:"body_text"`
|
|
}
|
|
|
|
// ========================================
|
|
// DSR Helper Methods
|
|
// ========================================
|
|
|
|
// Label returns German label for request type
|
|
func (rt DSRRequestType) Label() string {
|
|
switch rt {
|
|
case DSRTypeAccess:
|
|
return "Auskunftsanfrage (Art. 15)"
|
|
case DSRTypeRectification:
|
|
return "Berichtigungsanfrage (Art. 16)"
|
|
case DSRTypeErasure:
|
|
return "Löschanfrage (Art. 17)"
|
|
case DSRTypeRestriction:
|
|
return "Einschränkungsanfrage (Art. 18)"
|
|
case DSRTypePortability:
|
|
return "Datenübertragung (Art. 20)"
|
|
default:
|
|
return string(rt)
|
|
}
|
|
}
|
|
|
|
// DeadlineDays returns the legal deadline in days for request type
|
|
func (rt DSRRequestType) DeadlineDays() int {
|
|
switch rt {
|
|
case DSRTypeAccess, DSRTypePortability:
|
|
return 30 // 1 month
|
|
case DSRTypeRectification, DSRTypeErasure, DSRTypeRestriction:
|
|
return 14 // 2 weeks (expedited per BDSG)
|
|
default:
|
|
return 30
|
|
}
|
|
}
|
|
|
|
// IsExpedited returns whether this request type should be processed expeditiously
|
|
func (rt DSRRequestType) IsExpedited() bool {
|
|
switch rt {
|
|
case DSRTypeRectification, DSRTypeErasure, DSRTypeRestriction:
|
|
return true
|
|
default:
|
|
return false
|
|
}
|
|
}
|
|
|
|
// Label returns German label for status
|
|
func (s DSRStatus) Label() string {
|
|
switch s {
|
|
case DSRStatusIntake:
|
|
return "Eingang"
|
|
case DSRStatusIdentityVerification:
|
|
return "Identitätsprüfung"
|
|
case DSRStatusProcessing:
|
|
return "In Bearbeitung"
|
|
case DSRStatusCompleted:
|
|
return "Abgeschlossen"
|
|
case DSRStatusRejected:
|
|
return "Abgelehnt"
|
|
case DSRStatusCancelled:
|
|
return "Storniert"
|
|
default:
|
|
return string(s)
|
|
}
|
|
}
|
|
|
|
// IsValidDSRRequestType checks if a string is a valid DSR request type
|
|
func IsValidDSRRequestType(reqType string) bool {
|
|
switch DSRRequestType(reqType) {
|
|
case DSRTypeAccess, DSRTypeRectification, DSRTypeErasure, DSRTypeRestriction, DSRTypePortability:
|
|
return true
|
|
default:
|
|
return false
|
|
}
|
|
}
|
|
|
|
// IsValidDSRStatus checks if a string is a valid DSR status
|
|
func IsValidDSRStatus(status string) bool {
|
|
switch DSRStatus(status) {
|
|
case DSRStatusIntake, DSRStatusIdentityVerification, DSRStatusProcessing,
|
|
DSRStatusCompleted, DSRStatusRejected, DSRStatusCancelled:
|
|
return true
|
|
default:
|
|
return false
|
|
}
|
|
}
|