[split-required] [guardrail-change] Enforce 500 LOC budget across all services
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>
This commit is contained in:
403
consent-service/internal/models/dsr.go
Normal file
403
consent-service/internal/models/dsr.go
Normal file
@@ -0,0 +1,403 @@
|
||||
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
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user