This repository has been archived on 2026-02-15. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
Benjamin Admin 21a844cb8a fix: Restore all files lost during destructive rebase
A previous `git pull --rebase origin main` dropped 177 local commits,
losing 3400+ files across admin-v2, backend, studio-v2, website,
klausur-service, and many other services. The partial restore attempt
(660295e2) only recovered some files.

This commit restores all missing files from pre-rebase ref 98933f5e
while preserving post-rebase additions (night-scheduler, night-mode UI,
NightModeWidget dashboard integration).

Restored features include:
- AI Module Sidebar (FAB), OCR Labeling, OCR Compare
- GPU Dashboard, RAG Pipeline, Magic Help
- Klausur-Korrektur (8 files), Abitur-Archiv (5+ files)
- Companion, Zeugnisse-Crawler, Screen Flow
- Full backend, studio-v2, website, klausur-service
- All compliance SDKs, agent-core, voice-service
- CI/CD configs, documentation, scripts

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-09 09:51:32 +01:00

1798 lines
82 KiB
Go

package models
import (
"time"
"github.com/google/uuid"
)
// User represents a user with full authentication support
type User struct {
ID uuid.UUID `json:"id" db:"id"`
ExternalID *string `json:"external_id,omitempty" db:"external_id"`
Email string `json:"email" db:"email"`
PasswordHash *string `json:"-" db:"password_hash"` // Never exposed in JSON
Name *string `json:"name,omitempty" db:"name"`
Role string `json:"role" db:"role"` // 'user', 'admin', 'super_admin', 'data_protection_officer'
EmailVerified bool `json:"email_verified" db:"email_verified"`
EmailVerifiedAt *time.Time `json:"email_verified_at,omitempty" db:"email_verified_at"`
AccountStatus string `json:"account_status" db:"account_status"` // 'active', 'suspended', 'locked'
LastLoginAt *time.Time `json:"last_login_at,omitempty" db:"last_login_at"`
FailedLoginAttempts int `json:"failed_login_attempts" db:"failed_login_attempts"`
LockedUntil *time.Time `json:"locked_until,omitempty" db:"locked_until"`
CreatedAt time.Time `json:"created_at" db:"created_at"`
UpdatedAt time.Time `json:"updated_at" db:"updated_at"`
}
// LegalDocument represents a type of legal document (e.g., Terms, Privacy Policy)
type LegalDocument struct {
ID uuid.UUID `json:"id" db:"id"`
Type string `json:"type" db:"type"` // 'terms', 'privacy', 'cookies', 'community'
Name string `json:"name" db:"name"`
Description *string `json:"description" db:"description"`
IsMandatory bool `json:"is_mandatory" db:"is_mandatory"`
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"`
}
// DocumentVersion represents a specific version of a legal document
type DocumentVersion struct {
ID uuid.UUID `json:"id" db:"id"`
DocumentID uuid.UUID `json:"document_id" db:"document_id"`
Version string `json:"version" db:"version"` // Semver: 1.0.0, 1.1.0
Language string `json:"language" db:"language"` // ISO 639-1: de, en
Title string `json:"title" db:"title"`
Content string `json:"content" db:"content"` // HTML or Markdown
Summary *string `json:"summary" db:"summary"` // Summary of changes
Status string `json:"status" db:"status"` // 'draft', 'review', 'approved', 'scheduled', 'published', 'archived'
PublishedAt *time.Time `json:"published_at" db:"published_at"`
ScheduledPublishAt *time.Time `json:"scheduled_publish_at" db:"scheduled_publish_at"`
CreatedBy *uuid.UUID `json:"created_by" db:"created_by"`
ApprovedBy *uuid.UUID `json:"approved_by" db:"approved_by"`
ApprovedAt *time.Time `json:"approved_at" db:"approved_at"`
CreatedAt time.Time `json:"created_at" db:"created_at"`
UpdatedAt time.Time `json:"updated_at" db:"updated_at"`
}
// UserConsent represents a user's consent to a document version
type UserConsent struct {
ID uuid.UUID `json:"id" db:"id"`
UserID uuid.UUID `json:"user_id" db:"user_id"`
DocumentVersionID uuid.UUID `json:"document_version_id" db:"document_version_id"`
Consented bool `json:"consented" db:"consented"`
IPAddress *string `json:"ip_address" db:"ip_address"`
UserAgent *string `json:"user_agent" db:"user_agent"`
ConsentedAt time.Time `json:"consented_at" db:"consented_at"`
WithdrawnAt *time.Time `json:"withdrawn_at" db:"withdrawn_at"`
}
// CookieCategory represents a category of cookies
type CookieCategory struct {
ID uuid.UUID `json:"id" db:"id"`
Name string `json:"name" db:"name"` // 'necessary', 'functional', 'analytics', 'marketing'
DisplayNameDE string `json:"display_name_de" db:"display_name_de"`
DisplayNameEN *string `json:"display_name_en" db:"display_name_en"`
DescriptionDE *string `json:"description_de" db:"description_de"`
DescriptionEN *string `json:"description_en" db:"description_en"`
IsMandatory bool `json:"is_mandatory" db:"is_mandatory"`
SortOrder int `json:"sort_order" db:"sort_order"`
IsActive bool `json:"is_active" db:"is_active"`
CreatedAt time.Time `json:"created_at" db:"created_at"`
UpdatedAt time.Time `json:"updated_at" db:"updated_at"`
}
// CookieConsent represents a user's cookie preferences
type CookieConsent struct {
ID uuid.UUID `json:"id" db:"id"`
UserID uuid.UUID `json:"user_id" db:"user_id"`
CategoryID uuid.UUID `json:"category_id" db:"category_id"`
Consented bool `json:"consented" db:"consented"`
ConsentedAt time.Time `json:"consented_at" db:"consented_at"`
UpdatedAt time.Time `json:"updated_at" db:"updated_at"`
}
// AuditLog represents an audit trail entry for GDPR compliance
type AuditLog struct {
ID uuid.UUID `json:"id" db:"id"`
UserID *uuid.UUID `json:"user_id" db:"user_id"`
Action string `json:"action" db:"action"` // 'consent_given', 'consent_withdrawn', 'data_export', 'data_delete'
EntityType *string `json:"entity_type" db:"entity_type"` // 'document', 'cookie_category'
EntityID *uuid.UUID `json:"entity_id" db:"entity_id"`
Details *string `json:"details" db:"details"` // JSON string
IPAddress *string `json:"ip_address" db:"ip_address"`
UserAgent *string `json:"user_agent" db:"user_agent"`
CreatedAt time.Time `json:"created_at" db:"created_at"`
}
// DataExportRequest represents a user's request to export their data
type DataExportRequest struct {
ID uuid.UUID `json:"id" db:"id"`
UserID uuid.UUID `json:"user_id" db:"user_id"`
Status string `json:"status" db:"status"` // 'pending', 'processing', 'completed', 'failed'
DownloadURL *string `json:"download_url" db:"download_url"`
ExpiresAt *time.Time `json:"expires_at" db:"expires_at"`
CreatedAt time.Time `json:"created_at" db:"created_at"`
CompletedAt *time.Time `json:"completed_at" db:"completed_at"`
}
// DataDeletionRequest represents a user's request to delete their data
type DataDeletionRequest struct {
ID uuid.UUID `json:"id" db:"id"`
UserID uuid.UUID `json:"user_id" db:"user_id"`
Status string `json:"status" db:"status"` // 'pending', 'processing', 'completed', 'failed'
Reason *string `json:"reason" db:"reason"`
CreatedAt time.Time `json:"created_at" db:"created_at"`
ProcessedAt *time.Time `json:"processed_at" db:"processed_at"`
ProcessedBy *uuid.UUID `json:"processed_by" db:"processed_by"`
}
// ========================================
// DTOs (Data Transfer Objects)
// ========================================
// CreateConsentRequest is the request body for creating a consent
type CreateConsentRequest struct {
DocumentType string `json:"document_type" binding:"required"`
VersionID string `json:"version_id" binding:"required"`
Consented bool `json:"consented"`
}
// CookieConsentRequest is the request body for setting cookie preferences
type CookieConsentRequest struct {
Categories []CookieCategoryConsent `json:"categories" binding:"required"`
}
// CookieCategoryConsent represents consent for a single cookie category
type CookieCategoryConsent struct {
CategoryID string `json:"category_id" binding:"required"`
Consented bool `json:"consented"`
}
// ConsentCheckResponse is the response for checking consent status
type ConsentCheckResponse struct {
HasConsent bool `json:"has_consent"`
CurrentVersionID *string `json:"current_version_id,omitempty"`
ConsentedVersion *string `json:"consented_version,omitempty"`
NeedsUpdate bool `json:"needs_update"`
ConsentedAt *time.Time `json:"consented_at,omitempty"`
}
// DocumentWithVersion combines document info with its latest published version
type DocumentWithVersion struct {
Document LegalDocument `json:"document"`
LatestVersion *DocumentVersion `json:"latest_version,omitempty"`
}
// ConsentHistory represents a user's consent history for a document
type ConsentHistory struct {
Document LegalDocument `json:"document"`
Version DocumentVersion `json:"version"`
Consent UserConsent `json:"consent"`
}
// ConsentStats represents statistics about consents
type ConsentStats struct {
TotalUsers int `json:"total_users"`
ConsentedUsers int `json:"consented_users"`
ConsentRate float64 `json:"consent_rate"`
RecentConsents int `json:"recent_consents"` // Last 7 days
RecentWithdrawals int `json:"recent_withdrawals"`
}
// CookieStats represents statistics about cookie consents
type CookieStats struct {
Category string `json:"category"`
TotalUsers int `json:"total_users"`
ConsentedUsers int `json:"consented_users"`
ConsentRate float64 `json:"consent_rate"`
}
// MyDataResponse represents all data we have about a user
type MyDataResponse struct {
User User `json:"user"`
Consents []ConsentHistory `json:"consents"`
CookieConsents []CookieConsent `json:"cookie_consents"`
AuditLog []AuditLog `json:"audit_log"`
ExportedAt time.Time `json:"exported_at"`
}
// CreateDocumentRequest is the request body for creating a document
type CreateDocumentRequest struct {
Type string `json:"type" binding:"required"`
Name string `json:"name" binding:"required"`
Description *string `json:"description"`
IsMandatory bool `json:"is_mandatory"`
}
// CreateVersionRequest is the request body for creating a document version
type CreateVersionRequest struct {
DocumentID string `json:"document_id" binding:"required"`
Version string `json:"version" binding:"required"`
Language string `json:"language" binding:"required"`
Title string `json:"title" binding:"required"`
Content string `json:"content" binding:"required"`
Summary *string `json:"summary"`
}
// UpdateVersionRequest is the request body for updating a version
type UpdateVersionRequest struct {
Title *string `json:"title"`
Content *string `json:"content"`
Summary *string `json:"summary"`
Status *string `json:"status"`
}
// CreateCookieCategoryRequest is the request body for creating a cookie category
type CreateCookieCategoryRequest struct {
Name string `json:"name" binding:"required"`
DisplayNameDE string `json:"display_name_de" binding:"required"`
DisplayNameEN *string `json:"display_name_en"`
DescriptionDE *string `json:"description_de"`
DescriptionEN *string `json:"description_en"`
IsMandatory bool `json:"is_mandatory"`
SortOrder int `json:"sort_order"`
}
// ========================================
// Phase 1: Authentication Models
// ========================================
// EmailVerificationToken for email verification
type EmailVerificationToken struct {
ID uuid.UUID `json:"id" db:"id"`
UserID uuid.UUID `json:"user_id" db:"user_id"`
Token string `json:"token" db:"token"`
ExpiresAt time.Time `json:"expires_at" db:"expires_at"`
UsedAt *time.Time `json:"used_at,omitempty" db:"used_at"`
CreatedAt time.Time `json:"created_at" db:"created_at"`
}
// PasswordResetToken for password reset
type PasswordResetToken struct {
ID uuid.UUID `json:"id" db:"id"`
UserID uuid.UUID `json:"user_id" db:"user_id"`
Token string `json:"token" db:"token"`
ExpiresAt time.Time `json:"expires_at" db:"expires_at"`
UsedAt *time.Time `json:"used_at,omitempty" db:"used_at"`
IPAddress *string `json:"ip_address,omitempty" db:"ip_address"`
CreatedAt time.Time `json:"created_at" db:"created_at"`
}
// UserSession for session management
type UserSession struct {
ID uuid.UUID `json:"id" db:"id"`
UserID uuid.UUID `json:"user_id" db:"user_id"`
TokenHash string `json:"-" db:"token_hash"`
DeviceInfo *string `json:"device_info,omitempty" db:"device_info"`
IPAddress *string `json:"ip_address,omitempty" db:"ip_address"`
UserAgent *string `json:"user_agent,omitempty" db:"user_agent"`
ExpiresAt time.Time `json:"expires_at" db:"expires_at"`
RevokedAt *time.Time `json:"revoked_at,omitempty" db:"revoked_at"`
CreatedAt time.Time `json:"created_at" db:"created_at"`
LastActivityAt time.Time `json:"last_activity_at" db:"last_activity_at"`
}
// RegisterRequest for user registration
type RegisterRequest struct {
Email string `json:"email" binding:"required,email"`
Password string `json:"password" binding:"required,min=8"`
Name *string `json:"name"`
}
// LoginRequest for user login
type LoginRequest struct {
Email string `json:"email" binding:"required,email"`
Password string `json:"password" binding:"required"`
}
// LoginResponse after successful login
type LoginResponse struct {
User User `json:"user"`
AccessToken string `json:"access_token"`
RefreshToken string `json:"refresh_token"`
ExpiresIn int `json:"expires_in"` // seconds
}
// RefreshTokenRequest for token refresh
type RefreshTokenRequest struct {
RefreshToken string `json:"refresh_token" binding:"required"`
}
// VerifyEmailRequest for email verification
type VerifyEmailRequest struct {
Token string `json:"token" binding:"required"`
}
// ForgotPasswordRequest for password reset request
type ForgotPasswordRequest struct {
Email string `json:"email" binding:"required,email"`
}
// ResetPasswordRequest for password reset
type ResetPasswordRequest struct {
Token string `json:"token" binding:"required"`
NewPassword string `json:"new_password" binding:"required,min=8"`
}
// ChangePasswordRequest for changing password
type ChangePasswordRequest struct {
CurrentPassword string `json:"current_password" binding:"required"`
NewPassword string `json:"new_password" binding:"required,min=8"`
}
// UpdateProfileRequest for profile updates
type UpdateProfileRequest struct {
Name *string `json:"name"`
}
// ========================================
// Phase 3: Version Approval Models
// ========================================
// VersionApproval tracks the approval workflow
type VersionApproval struct {
ID uuid.UUID `json:"id" db:"id"`
VersionID uuid.UUID `json:"version_id" db:"version_id"`
ApproverID uuid.UUID `json:"approver_id" db:"approver_id"`
Action string `json:"action" db:"action"` // 'submitted_for_review', 'approved', 'rejected', 'published'
Comment *string `json:"comment,omitempty" db:"comment"`
CreatedAt time.Time `json:"created_at" db:"created_at"`
}
// SubmitForReviewRequest for submitting a version for review
type SubmitForReviewRequest struct {
Comment *string `json:"comment"`
}
// ApproveVersionRequest for approving a version (DSB)
type ApproveVersionRequest struct {
Comment *string `json:"comment"`
ScheduledPublishAt *string `json:"scheduled_publish_at"` // ISO 8601 datetime for scheduled publishing
}
// RejectVersionRequest for rejecting a version
type RejectVersionRequest struct {
Comment string `json:"comment" binding:"required"`
}
// VersionCompareResponse for comparing versions
type VersionCompareResponse struct {
Published *DocumentVersion `json:"published,omitempty"`
Draft *DocumentVersion `json:"draft"`
Diff *string `json:"diff,omitempty"`
Approvals []VersionApproval `json:"approvals"`
}
// ========================================
// Phase 4: Notification Models
// ========================================
// Notification represents a user notification
type Notification struct {
ID uuid.UUID `json:"id" db:"id"`
UserID uuid.UUID `json:"user_id" db:"user_id"`
Type string `json:"type" db:"type"` // 'new_version', 'consent_reminder', 'account_warning'
Channel string `json:"channel" db:"channel"` // 'email', 'in_app', 'push'
Title string `json:"title" db:"title"`
Body string `json:"body" db:"body"`
Data *string `json:"data,omitempty" db:"data"` // JSON string
ReadAt *time.Time `json:"read_at,omitempty" db:"read_at"`
SentAt *time.Time `json:"sent_at,omitempty" db:"sent_at"`
CreatedAt time.Time `json:"created_at" db:"created_at"`
}
// PushSubscription for Web Push notifications
type PushSubscription struct {
ID uuid.UUID `json:"id" db:"id"`
UserID uuid.UUID `json:"user_id" db:"user_id"`
Endpoint string `json:"endpoint" db:"endpoint"`
P256dh string `json:"p256dh" db:"p256dh"`
Auth string `json:"auth" db:"auth"`
UserAgent *string `json:"user_agent,omitempty" db:"user_agent"`
CreatedAt time.Time `json:"created_at" db:"created_at"`
}
// NotificationPreferences for user notification settings
type NotificationPreferences struct {
ID uuid.UUID `json:"id" db:"id"`
UserID uuid.UUID `json:"user_id" db:"user_id"`
EmailEnabled bool `json:"email_enabled" db:"email_enabled"`
PushEnabled bool `json:"push_enabled" db:"push_enabled"`
InAppEnabled bool `json:"in_app_enabled" db:"in_app_enabled"`
ReminderFrequency string `json:"reminder_frequency" db:"reminder_frequency"` // 'daily', 'weekly', 'never'
CreatedAt time.Time `json:"created_at" db:"created_at"`
UpdatedAt time.Time `json:"updated_at" db:"updated_at"`
}
// SubscribePushRequest for subscribing to push notifications
type SubscribePushRequest struct {
Endpoint string `json:"endpoint" binding:"required"`
P256dh string `json:"p256dh" binding:"required"`
Auth string `json:"auth" binding:"required"`
}
// UpdateNotificationPreferencesRequest for updating preferences
type UpdateNotificationPreferencesRequest struct {
EmailEnabled *bool `json:"email_enabled"`
PushEnabled *bool `json:"push_enabled"`
InAppEnabled *bool `json:"in_app_enabled"`
ReminderFrequency *string `json:"reminder_frequency"`
}
// ========================================
// Phase 6: OAuth 2.0 Authorization Code Flow
// ========================================
// OAuthClient represents a registered OAuth 2.0 client application
type OAuthClient struct {
ID uuid.UUID `json:"id" db:"id"`
ClientID string `json:"client_id" db:"client_id"`
ClientSecret string `json:"-" db:"client_secret"` // Never expose in JSON
Name string `json:"name" db:"name"`
Description *string `json:"description,omitempty" db:"description"`
RedirectURIs []string `json:"redirect_uris" db:"redirect_uris"` // JSON array
Scopes []string `json:"scopes" db:"scopes"` // Allowed scopes
GrantTypes []string `json:"grant_types" db:"grant_types"` // authorization_code, refresh_token
IsPublic bool `json:"is_public" db:"is_public"` // Public clients (SPAs) don't have secret
IsActive bool `json:"is_active" db:"is_active"`
CreatedBy *uuid.UUID `json:"created_by,omitempty" db:"created_by"`
CreatedAt time.Time `json:"created_at" db:"created_at"`
UpdatedAt time.Time `json:"updated_at" db:"updated_at"`
}
// OAuthAuthorizationCode represents an authorization code for the OAuth flow
type OAuthAuthorizationCode struct {
ID uuid.UUID `json:"id" db:"id"`
Code string `json:"-" db:"code"` // Hashed
ClientID string `json:"client_id" db:"client_id"`
UserID uuid.UUID `json:"user_id" db:"user_id"`
RedirectURI string `json:"redirect_uri" db:"redirect_uri"`
Scopes []string `json:"scopes" db:"scopes"`
CodeChallenge *string `json:"-" db:"code_challenge"` // For PKCE
CodeChallengeMethod *string `json:"-" db:"code_challenge_method"` // S256 or plain
ExpiresAt time.Time `json:"expires_at" db:"expires_at"`
UsedAt *time.Time `json:"used_at,omitempty" db:"used_at"`
CreatedAt time.Time `json:"created_at" db:"created_at"`
}
// OAuthAccessToken represents an OAuth access token
type OAuthAccessToken struct {
ID uuid.UUID `json:"id" db:"id"`
TokenHash string `json:"-" db:"token_hash"`
ClientID string `json:"client_id" db:"client_id"`
UserID uuid.UUID `json:"user_id" db:"user_id"`
Scopes []string `json:"scopes" db:"scopes"`
ExpiresAt time.Time `json:"expires_at" db:"expires_at"`
RevokedAt *time.Time `json:"revoked_at,omitempty" db:"revoked_at"`
CreatedAt time.Time `json:"created_at" db:"created_at"`
}
// OAuthRefreshToken represents an OAuth refresh token
type OAuthRefreshToken struct {
ID uuid.UUID `json:"id" db:"id"`
TokenHash string `json:"-" db:"token_hash"`
AccessTokenID uuid.UUID `json:"access_token_id" db:"access_token_id"`
ClientID string `json:"client_id" db:"client_id"`
UserID uuid.UUID `json:"user_id" db:"user_id"`
Scopes []string `json:"scopes" db:"scopes"`
ExpiresAt time.Time `json:"expires_at" db:"expires_at"`
RevokedAt *time.Time `json:"revoked_at,omitempty" db:"revoked_at"`
CreatedAt time.Time `json:"created_at" db:"created_at"`
}
// OAuthAuthorizeRequest for the authorization endpoint
type OAuthAuthorizeRequest struct {
ResponseType string `form:"response_type" binding:"required"` // Must be "code"
ClientID string `form:"client_id" binding:"required"`
RedirectURI string `form:"redirect_uri" binding:"required"`
Scope string `form:"scope"` // Space-separated scopes
State string `form:"state" binding:"required"` // CSRF protection
CodeChallenge string `form:"code_challenge"` // PKCE
CodeChallengeMethod string `form:"code_challenge_method"` // S256 (recommended) or plain
}
// OAuthTokenRequest for the token endpoint
type OAuthTokenRequest struct {
GrantType string `form:"grant_type" binding:"required"` // authorization_code or refresh_token
Code string `form:"code"` // For authorization_code grant
RedirectURI string `form:"redirect_uri"` // For authorization_code grant
ClientID string `form:"client_id" binding:"required"`
ClientSecret string `form:"client_secret"` // For confidential clients
CodeVerifier string `form:"code_verifier"` // For PKCE
RefreshToken string `form:"refresh_token"` // For refresh_token grant
Scope string `form:"scope"` // For refresh_token grant (optional)
}
// OAuthTokenResponse for successful token requests
type OAuthTokenResponse struct {
AccessToken string `json:"access_token"`
TokenType string `json:"token_type"` // Always "Bearer"
ExpiresIn int `json:"expires_in"` // Seconds until expiration
RefreshToken string `json:"refresh_token,omitempty"`
Scope string `json:"scope,omitempty"`
}
// OAuthErrorResponse for OAuth errors (RFC 6749)
type OAuthErrorResponse struct {
Error string `json:"error"`
ErrorDescription string `json:"error_description,omitempty"`
ErrorURI string `json:"error_uri,omitempty"`
}
// ========================================
// Phase 7: Two-Factor Authentication (2FA/TOTP)
// ========================================
// UserTOTP stores 2FA TOTP configuration for a user
type UserTOTP struct {
ID uuid.UUID `json:"id" db:"id"`
UserID uuid.UUID `json:"user_id" db:"user_id"`
Secret string `json:"-" db:"secret"` // Encrypted TOTP secret
Verified bool `json:"verified" db:"verified"` // Has 2FA been verified/activated
RecoveryCodes []string `json:"-" db:"recovery_codes"` // Encrypted backup codes
EnabledAt *time.Time `json:"enabled_at,omitempty" db:"enabled_at"`
LastUsedAt *time.Time `json:"last_used_at,omitempty" db:"last_used_at"`
CreatedAt time.Time `json:"created_at" db:"created_at"`
UpdatedAt time.Time `json:"updated_at" db:"updated_at"`
}
// TwoFactorChallenge represents a pending 2FA challenge during login
type TwoFactorChallenge struct {
ID uuid.UUID `json:"id" db:"id"`
UserID uuid.UUID `json:"user_id" db:"user_id"`
ChallengeID string `json:"challenge_id" db:"challenge_id"` // Temporary token
IPAddress *string `json:"ip_address,omitempty" db:"ip_address"`
UserAgent *string `json:"user_agent,omitempty" db:"user_agent"`
ExpiresAt time.Time `json:"expires_at" db:"expires_at"`
UsedAt *time.Time `json:"used_at,omitempty" db:"used_at"`
CreatedAt time.Time `json:"created_at" db:"created_at"`
}
// Setup2FAResponse when initiating 2FA setup
type Setup2FAResponse struct {
Secret string `json:"secret"` // Base32 encoded secret for manual entry
QRCodeDataURL string `json:"qr_code"` // Data URL for QR code image
RecoveryCodes []string `json:"recovery_codes"` // One-time backup codes
}
// Verify2FARequest for verifying 2FA setup or login
type Verify2FARequest struct {
Code string `json:"code" binding:"required"` // 6-digit TOTP code
ChallengeID string `json:"challenge_id,omitempty"` // For login flow
}
// TwoFactorLoginResponse when 2FA is required during login
type TwoFactorLoginResponse struct {
RequiresTwoFactor bool `json:"requires_two_factor"`
ChallengeID string `json:"challenge_id"` // Use this to complete 2FA
Message string `json:"message"`
}
// Complete2FALoginRequest to complete login with 2FA
type Complete2FALoginRequest struct {
ChallengeID string `json:"challenge_id" binding:"required"`
Code string `json:"code" binding:"required"` // 6-digit TOTP or recovery code
}
// Disable2FARequest for disabling 2FA
type Disable2FARequest struct {
Password string `json:"password" binding:"required"` // Require password confirmation
Code string `json:"code" binding:"required"` // Current TOTP code
}
// RecoveryCodeUseRequest for using a recovery code
type RecoveryCodeUseRequest struct {
ChallengeID string `json:"challenge_id" binding:"required"`
RecoveryCode string `json:"recovery_code" binding:"required"`
}
// TwoFactorStatusResponse for checking 2FA status
type TwoFactorStatusResponse struct {
Enabled bool `json:"enabled"`
Verified bool `json:"verified"`
EnabledAt *time.Time `json:"enabled_at,omitempty"`
RecoveryCodesCount int `json:"recovery_codes_count"`
}
// Verify2FAChallengeRequest for verifying a 2FA challenge during login
type Verify2FAChallengeRequest struct {
ChallengeID string `json:"challenge_id" binding:"required"`
Code string `json:"code,omitempty"` // 6-digit TOTP code
RecoveryCode string `json:"recovery_code,omitempty"` // Alternative: recovery code
}
// ========================================
// Phase 5: Consent Deadline Models
// ========================================
// ConsentDeadline tracks consent deadlines per user
type ConsentDeadline struct {
ID uuid.UUID `json:"id" db:"id"`
UserID uuid.UUID `json:"user_id" db:"user_id"`
DocumentVersionID uuid.UUID `json:"document_version_id" db:"document_version_id"`
DeadlineAt time.Time `json:"deadline_at" db:"deadline_at"`
ReminderCount int `json:"reminder_count" db:"reminder_count"`
LastReminderAt *time.Time `json:"last_reminder_at,omitempty" db:"last_reminder_at"`
ConsentGivenAt *time.Time `json:"consent_given_at,omitempty" db:"consent_given_at"`
CreatedAt time.Time `json:"created_at" db:"created_at"`
}
// AccountSuspension tracks account suspensions
type AccountSuspension struct {
ID uuid.UUID `json:"id" db:"id"`
UserID uuid.UUID `json:"user_id" db:"user_id"`
Reason string `json:"reason" db:"reason"` // 'consent_deadline_exceeded'
Details *string `json:"details,omitempty" db:"details"` // JSON
SuspendedAt time.Time `json:"suspended_at" db:"suspended_at"`
LiftedAt *time.Time `json:"lifted_at,omitempty" db:"lifted_at"`
LiftedReason *string `json:"lifted_reason,omitempty" db:"lifted_reason"`
}
// PendingConsentResponse for pending consents with deadline info
type PendingConsentResponse struct {
Document LegalDocument `json:"document"`
Version DocumentVersion `json:"version"`
DeadlineAt time.Time `json:"deadline_at"`
DaysLeft int `json:"days_left"`
IsOverdue bool `json:"is_overdue"`
}
// AccountStatusResponse for account status check
type AccountStatusResponse struct {
Status string `json:"status"` // 'active', 'suspended'
PendingConsents []PendingConsentResponse `json:"pending_consents,omitempty"`
SuspensionReason *string `json:"suspension_reason,omitempty"`
CanAccess bool `json:"can_access"`
}
// ========================================
// Phase 8: E-Mail Templates (Transactional)
// ========================================
// EmailTemplateType defines the types of transactional emails
// These are like document types but for emails
const (
// Auth & Security
EmailTypeWelcome = "welcome"
EmailTypeEmailVerification = "email_verification"
EmailTypePasswordReset = "password_reset"
EmailTypePasswordChanged = "password_changed"
EmailType2FAEnabled = "2fa_enabled"
EmailType2FADisabled = "2fa_disabled"
EmailTypeNewDeviceLogin = "new_device_login"
EmailTypeSuspiciousActivity = "suspicious_activity"
EmailTypeAccountLocked = "account_locked"
EmailTypeAccountUnlocked = "account_unlocked"
// Account Lifecycle
EmailTypeDeletionRequested = "deletion_requested"
EmailTypeDeletionConfirmed = "deletion_confirmed"
EmailTypeDataExportReady = "data_export_ready"
EmailTypeEmailChanged = "email_changed"
EmailTypeEmailChangeVerify = "email_change_verify"
// Consent-related
EmailTypeNewVersionPublished = "new_version_published"
EmailTypeConsentReminder = "consent_reminder"
EmailTypeConsentDeadlineWarning = "consent_deadline_warning"
EmailTypeAccountSuspended = "account_suspended"
)
// EmailTemplate represents a template for transactional emails (like LegalDocument)
type EmailTemplate struct {
ID uuid.UUID `json:"id" db:"id"`
Type string `json:"type" db:"type"` // One of EmailType constants
Name string `json:"name" db:"name"` // Human-readable name
Description *string `json:"description" db:"description"`
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"`
}
// EmailTemplateVersion represents a specific version of an email template (like DocumentVersion)
type EmailTemplateVersion struct {
ID uuid.UUID `json:"id" db:"id"`
TemplateID uuid.UUID `json:"template_id" db:"template_id"`
Version string `json:"version" db:"version"` // Semver: 1.0.0
Language string `json:"language" db:"language"` // ISO 639-1: de, en
Subject string `json:"subject" db:"subject"` // Email subject line
BodyHTML string `json:"body_html" db:"body_html"` // HTML version
BodyText string `json:"body_text" db:"body_text"` // Plain text version
Summary *string `json:"summary" db:"summary"` // Change summary
Status string `json:"status" db:"status"` // draft, review, approved, published, archived
PublishedAt *time.Time `json:"published_at" db:"published_at"`
ScheduledPublishAt *time.Time `json:"scheduled_publish_at" db:"scheduled_publish_at"`
CreatedBy *uuid.UUID `json:"created_by" db:"created_by"`
ApprovedBy *uuid.UUID `json:"approved_by" db:"approved_by"`
ApprovedAt *time.Time `json:"approved_at" db:"approved_at"`
CreatedAt time.Time `json:"created_at" db:"created_at"`
UpdatedAt time.Time `json:"updated_at" db:"updated_at"`
}
// EmailTemplateApproval tracks approval workflow for email templates
type EmailTemplateApproval struct {
ID uuid.UUID `json:"id" db:"id"`
VersionID uuid.UUID `json:"version_id" db:"version_id"`
ApproverID uuid.UUID `json:"approver_id" db:"approver_id"`
Action string `json:"action" db:"action"` // submitted_for_review, approved, rejected, published
Comment *string `json:"comment,omitempty" db:"comment"`
CreatedAt time.Time `json:"created_at" db:"created_at"`
}
// EmailSendLog tracks sent emails for audit purposes
type EmailSendLog struct {
ID uuid.UUID `json:"id" db:"id"`
UserID *uuid.UUID `json:"user_id,omitempty" db:"user_id"`
VersionID uuid.UUID `json:"version_id" db:"version_id"`
Recipient string `json:"recipient" db:"recipient"` // Email address
Subject string `json:"subject" db:"subject"`
Status string `json:"status" db:"status"` // queued, sent, delivered, bounced, failed
ErrorMsg *string `json:"error_msg,omitempty" db:"error_msg"`
Variables *string `json:"variables,omitempty" db:"variables"` // JSON of template variables used
SentAt *time.Time `json:"sent_at,omitempty" db:"sent_at"`
DeliveredAt *time.Time `json:"delivered_at,omitempty" db:"delivered_at"`
CreatedAt time.Time `json:"created_at" db:"created_at"`
}
// EmailTemplateSettings stores global email settings (logo, signature, etc.)
type EmailTemplateSettings struct {
ID uuid.UUID `json:"id" db:"id"`
LogoURL *string `json:"logo_url" db:"logo_url"`
LogoBase64 *string `json:"logo_base64" db:"logo_base64"` // For embedding in emails
CompanyName string `json:"company_name" db:"company_name"`
SenderName string `json:"sender_name" db:"sender_name"`
SenderEmail string `json:"sender_email" db:"sender_email"`
ReplyToEmail *string `json:"reply_to_email" db:"reply_to_email"`
FooterHTML *string `json:"footer_html" db:"footer_html"`
FooterText *string `json:"footer_text" db:"footer_text"`
PrimaryColor string `json:"primary_color" db:"primary_color"` // Hex color
SecondaryColor string `json:"secondary_color" db:"secondary_color"` // Hex color
UpdatedAt time.Time `json:"updated_at" db:"updated_at"`
UpdatedBy *uuid.UUID `json:"updated_by" db:"updated_by"`
}
// ========================================
// E-Mail Template DTOs
// ========================================
// CreateEmailTemplateRequest for creating a new email template type
type CreateEmailTemplateRequest struct {
Type string `json:"type" binding:"required"`
Name string `json:"name" binding:"required"`
Description *string `json:"description"`
}
// CreateEmailTemplateVersionRequest for creating a new version of an email template
type CreateEmailTemplateVersionRequest 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"`
Summary *string `json:"summary"`
}
// UpdateEmailTemplateVersionRequest for updating a version
type UpdateEmailTemplateVersionRequest struct {
Subject *string `json:"subject"`
BodyHTML *string `json:"body_html"`
BodyText *string `json:"body_text"`
Summary *string `json:"summary"`
Status *string `json:"status"`
}
// UpdateEmailTemplateSettingsRequest for updating global settings
type UpdateEmailTemplateSettingsRequest struct {
LogoURL *string `json:"logo_url"`
LogoBase64 *string `json:"logo_base64"`
CompanyName *string `json:"company_name"`
SenderName *string `json:"sender_name"`
SenderEmail *string `json:"sender_email"`
ReplyToEmail *string `json:"reply_to_email"`
FooterHTML *string `json:"footer_html"`
FooterText *string `json:"footer_text"`
PrimaryColor *string `json:"primary_color"`
SecondaryColor *string `json:"secondary_color"`
}
// EmailTemplateWithVersion combines template info with its latest published version
type EmailTemplateWithVersion struct {
Template EmailTemplate `json:"template"`
LatestVersion *EmailTemplateVersion `json:"latest_version,omitempty"`
}
// SendTestEmailRequest for sending a test email
type SendTestEmailRequest struct {
VersionID string `json:"version_id" binding:"required"`
Recipient string `json:"recipient" binding:"required,email"`
Variables map[string]string `json:"variables"` // Template variable overrides
}
// EmailPreviewResponse for previewing an email
type EmailPreviewResponse struct {
Subject string `json:"subject"`
BodyHTML string `json:"body_html"`
BodyText string `json:"body_text"`
}
// EmailTemplateVariables defines available variables for each template type
type EmailTemplateVariables struct {
TemplateType string `json:"template_type"`
Variables []string `json:"variables"`
Descriptions map[string]string `json:"descriptions"`
}
// EmailStats represents statistics about email sends
type EmailStats struct {
TotalSent int `json:"total_sent"`
Delivered int `json:"delivered"`
Bounced int `json:"bounced"`
Failed int `json:"failed"`
DeliveryRate float64 `json:"delivery_rate"`
RecentSent int `json:"recent_sent"` // Last 7 days
}
// ========================================
// Phase 9: Schulverwaltung / School Management
// Matrix-basierte Kommunikation für Schulen
// ========================================
// SchoolRole defines roles within the school system
const (
SchoolRoleTeacher = "teacher"
SchoolRoleClassTeacher = "class_teacher"
SchoolRoleParent = "parent"
SchoolRoleParentRep = "parent_representative"
SchoolRoleStudent = "student"
SchoolRoleAdmin = "school_admin"
SchoolRolePrincipal = "principal"
SchoolRoleSecretary = "secretary"
)
// AttendanceStatus defines the status of student attendance
const (
AttendancePresent = "present"
AttendanceAbsent = "absent"
AttendanceAbsentExcused = "excused"
AttendanceAbsentUnexcused = "unexcused"
AttendanceLate = "late"
AttendanceLateExcused = "late_excused"
AttendancePending = "pending_confirmation"
)
// School represents a school/educational institution
type School struct {
ID uuid.UUID `json:"id" db:"id"`
Name string `json:"name" db:"name"`
ShortName *string `json:"short_name,omitempty" db:"short_name"`
Type string `json:"type" db:"type"` // 'grundschule', 'hauptschule', 'realschule', 'gymnasium', 'gesamtschule', 'berufsschule'
Address *string `json:"address,omitempty" db:"address"`
City *string `json:"city,omitempty" db:"city"`
PostalCode *string `json:"postal_code,omitempty" db:"postal_code"`
State *string `json:"state,omitempty" db:"state"` // Bundesland
Country string `json:"country" db:"country"` // Default: DE
Phone *string `json:"phone,omitempty" db:"phone"`
Email *string `json:"email,omitempty" db:"email"`
Website *string `json:"website,omitempty" db:"website"`
MatrixServerName *string `json:"matrix_server_name,omitempty" db:"matrix_server_name"` // Optional: eigener Matrix-Server
LogoURL *string `json:"logo_url,omitempty" db:"logo_url"`
IsActive bool `json:"is_active" db:"is_active"`
CreatedAt time.Time `json:"created_at" db:"created_at"`
UpdatedAt time.Time `json:"updated_at" db:"updated_at"`
}
// SchoolYear represents an academic year
type SchoolYear struct {
ID uuid.UUID `json:"id" db:"id"`
SchoolID uuid.UUID `json:"school_id" db:"school_id"`
Name string `json:"name" db:"name"` // e.g., "2024/2025"
StartDate time.Time `json:"start_date" db:"start_date"`
EndDate time.Time `json:"end_date" db:"end_date"`
IsCurrent bool `json:"is_current" db:"is_current"`
CreatedAt time.Time `json:"created_at" db:"created_at"`
}
// Class represents a school class
type Class struct {
ID uuid.UUID `json:"id" db:"id"`
SchoolID uuid.UUID `json:"school_id" db:"school_id"`
SchoolYearID uuid.UUID `json:"school_year_id" db:"school_year_id"`
Name string `json:"name" db:"name"` // e.g., "5a", "10b"
Grade int `json:"grade" db:"grade"` // Klassenstufe: 1-13
Section *string `json:"section,omitempty" db:"section"` // e.g., "a", "b", "c"
Room *string `json:"room,omitempty" db:"room"` // Klassenzimmer
MatrixInfoRoom *string `json:"matrix_info_room,omitempty" db:"matrix_info_room"` // Broadcast-Raum
MatrixRepRoom *string `json:"matrix_rep_room,omitempty" db:"matrix_rep_room"` // Elternvertreter-Raum
IsActive bool `json:"is_active" db:"is_active"`
CreatedAt time.Time `json:"created_at" db:"created_at"`
UpdatedAt time.Time `json:"updated_at" db:"updated_at"`
}
// Subject represents a school subject
type Subject struct {
ID uuid.UUID `json:"id" db:"id"`
SchoolID uuid.UUID `json:"school_id" db:"school_id"`
Name string `json:"name" db:"name"` // e.g., "Mathematik", "Deutsch"
ShortName string `json:"short_name" db:"short_name"` // e.g., "Ma", "De"
Color *string `json:"color,omitempty" db:"color"` // Hex color for display
IsActive bool `json:"is_active" db:"is_active"`
CreatedAt time.Time `json:"created_at" db:"created_at"`
}
// Student represents a student
type Student struct {
ID uuid.UUID `json:"id" db:"id"`
SchoolID uuid.UUID `json:"school_id" db:"school_id"`
ClassID uuid.UUID `json:"class_id" db:"class_id"`
UserID *uuid.UUID `json:"user_id,omitempty" db:"user_id"` // Optional: linked account
StudentNumber *string `json:"student_number,omitempty" db:"student_number"` // Internal ID
FirstName string `json:"first_name" db:"first_name"`
LastName string `json:"last_name" db:"last_name"`
DateOfBirth *time.Time `json:"date_of_birth,omitempty" db:"date_of_birth"`
Gender *string `json:"gender,omitempty" db:"gender"` // 'm', 'f', 'd'
MatrixUserID *string `json:"matrix_user_id,omitempty" db:"matrix_user_id"`
MatrixDMRoom *string `json:"matrix_dm_room,omitempty" db:"matrix_dm_room"` // Kind-Dialograum
IsActive bool `json:"is_active" db:"is_active"`
CreatedAt time.Time `json:"created_at" db:"created_at"`
UpdatedAt time.Time `json:"updated_at" db:"updated_at"`
}
// Teacher represents a teacher
type Teacher struct {
ID uuid.UUID `json:"id" db:"id"`
SchoolID uuid.UUID `json:"school_id" db:"school_id"`
UserID uuid.UUID `json:"user_id" db:"user_id"` // Linked user account
TeacherCode *string `json:"teacher_code,omitempty" db:"teacher_code"` // e.g., "MÜL" for Müller
Title *string `json:"title,omitempty" db:"title"` // e.g., "Dr.", "StR"
FirstName string `json:"first_name" db:"first_name"`
LastName string `json:"last_name" db:"last_name"`
MatrixUserID *string `json:"matrix_user_id,omitempty" db:"matrix_user_id"`
IsActive bool `json:"is_active" db:"is_active"`
CreatedAt time.Time `json:"created_at" db:"created_at"`
UpdatedAt time.Time `json:"updated_at" db:"updated_at"`
}
// ClassTeacher assigns teachers to classes (Klassenlehrer)
type ClassTeacher struct {
ID uuid.UUID `json:"id" db:"id"`
ClassID uuid.UUID `json:"class_id" db:"class_id"`
TeacherID uuid.UUID `json:"teacher_id" db:"teacher_id"`
IsPrimary bool `json:"is_primary" db:"is_primary"` // Hauptklassenlehrer vs. Stellvertreter
CreatedAt time.Time `json:"created_at" db:"created_at"`
}
// TeacherSubject assigns subjects to teachers
type TeacherSubject struct {
ID uuid.UUID `json:"id" db:"id"`
TeacherID uuid.UUID `json:"teacher_id" db:"teacher_id"`
SubjectID uuid.UUID `json:"subject_id" db:"subject_id"`
CreatedAt time.Time `json:"created_at" db:"created_at"`
}
// Parent represents a parent/guardian
type Parent struct {
ID uuid.UUID `json:"id" db:"id"`
UserID uuid.UUID `json:"user_id" db:"user_id"` // Linked user account
MatrixUserID *string `json:"matrix_user_id,omitempty" db:"matrix_user_id"`
FirstName string `json:"first_name" db:"first_name"`
LastName string `json:"last_name" db:"last_name"`
Phone *string `json:"phone,omitempty" db:"phone"`
EmergencyContact bool `json:"emergency_contact" db:"emergency_contact"`
CreatedAt time.Time `json:"created_at" db:"created_at"`
UpdatedAt time.Time `json:"updated_at" db:"updated_at"`
}
// StudentParent links students to their parents
type StudentParent struct {
ID uuid.UUID `json:"id" db:"id"`
StudentID uuid.UUID `json:"student_id" db:"student_id"`
ParentID uuid.UUID `json:"parent_id" db:"parent_id"`
Relationship string `json:"relationship" db:"relationship"` // 'mother', 'father', 'guardian', 'other'
IsPrimary bool `json:"is_primary" db:"is_primary"` // Hauptansprechpartner
HasCustody bool `json:"has_custody" db:"has_custody"` // Sorgeberechtigt
CreatedAt time.Time `json:"created_at" db:"created_at"`
}
// ParentRepresentative assigns parent representatives to classes
type ParentRepresentative struct {
ID uuid.UUID `json:"id" db:"id"`
ClassID uuid.UUID `json:"class_id" db:"class_id"`
ParentID uuid.UUID `json:"parent_id" db:"parent_id"`
Role string `json:"role" db:"role"` // 'first_rep', 'second_rep', 'substitute'
ElectedAt time.Time `json:"elected_at" db:"elected_at"`
ExpiresAt *time.Time `json:"expires_at,omitempty" db:"expires_at"`
IsActive bool `json:"is_active" db:"is_active"`
CreatedAt time.Time `json:"created_at" db:"created_at"`
}
// ========================================
// Stundenplan / Timetable
// ========================================
// TimetableSlot represents a time slot in the timetable
type TimetableSlot struct {
ID uuid.UUID `json:"id" db:"id"`
SchoolID uuid.UUID `json:"school_id" db:"school_id"`
SlotNumber int `json:"slot_number" db:"slot_number"` // 1, 2, 3... (Stunde)
StartTime string `json:"start_time" db:"start_time"` // "08:00"
EndTime string `json:"end_time" db:"end_time"` // "08:45"
IsBreak bool `json:"is_break" db:"is_break"` // Pause
Name *string `json:"name,omitempty" db:"name"` // e.g., "1. Stunde", "Große Pause"
}
// TimetableEntry represents a single lesson in the timetable
type TimetableEntry struct {
ID uuid.UUID `json:"id" db:"id"`
SchoolYearID uuid.UUID `json:"school_year_id" db:"school_year_id"`
ClassID uuid.UUID `json:"class_id" db:"class_id"`
SubjectID uuid.UUID `json:"subject_id" db:"subject_id"`
TeacherID uuid.UUID `json:"teacher_id" db:"teacher_id"`
SlotID uuid.UUID `json:"slot_id" db:"slot_id"`
DayOfWeek int `json:"day_of_week" db:"day_of_week"` // 1=Monday, 5=Friday
Room *string `json:"room,omitempty" db:"room"`
ValidFrom time.Time `json:"valid_from" db:"valid_from"`
ValidUntil *time.Time `json:"valid_until,omitempty" db:"valid_until"`
CreatedAt time.Time `json:"created_at" db:"created_at"`
UpdatedAt time.Time `json:"updated_at" db:"updated_at"`
}
// TimetableSubstitution represents a substitution/replacement lesson
type TimetableSubstitution struct {
ID uuid.UUID `json:"id" db:"id"`
OriginalEntryID uuid.UUID `json:"original_entry_id" db:"original_entry_id"`
Date time.Time `json:"date" db:"date"`
SubstituteTeacherID *uuid.UUID `json:"substitute_teacher_id,omitempty" db:"substitute_teacher_id"`
SubstituteSubjectID *uuid.UUID `json:"substitute_subject_id,omitempty" db:"substitute_subject_id"`
Room *string `json:"room,omitempty" db:"room"`
Type string `json:"type" db:"type"` // 'substitution', 'cancelled', 'room_change', 'supervision'
Note *string `json:"note,omitempty" db:"note"`
CreatedAt time.Time `json:"created_at" db:"created_at"`
CreatedBy uuid.UUID `json:"created_by" db:"created_by"`
}
// ========================================
// Abwesenheit / Attendance
// ========================================
// AttendanceRecord represents a student's attendance for a specific lesson
type AttendanceRecord struct {
ID uuid.UUID `json:"id" db:"id"`
StudentID uuid.UUID `json:"student_id" db:"student_id"`
TimetableEntryID *uuid.UUID `json:"timetable_entry_id,omitempty" db:"timetable_entry_id"`
Date time.Time `json:"date" db:"date"`
SlotID uuid.UUID `json:"slot_id" db:"slot_id"`
Status string `json:"status" db:"status"` // AttendanceStatus constants
RecordedBy uuid.UUID `json:"recorded_by" db:"recorded_by"` // Teacher who recorded
Note *string `json:"note,omitempty" db:"note"`
CreatedAt time.Time `json:"created_at" db:"created_at"`
UpdatedAt time.Time `json:"updated_at" db:"updated_at"`
}
// AbsenceReport represents a full absence report (one or more days)
type AbsenceReport struct {
ID uuid.UUID `json:"id" db:"id"`
StudentID uuid.UUID `json:"student_id" db:"student_id"`
StartDate time.Time `json:"start_date" db:"start_date"`
EndDate time.Time `json:"end_date" db:"end_date"`
Reason *string `json:"reason,omitempty" db:"reason"`
ReasonCategory string `json:"reason_category" db:"reason_category"` // 'illness', 'family', 'appointment', 'other'
Status string `json:"status" db:"status"` // 'reported', 'confirmed', 'excused', 'unexcused'
ReportedBy uuid.UUID `json:"reported_by" db:"reported_by"` // Parent or student
ReportedAt time.Time `json:"reported_at" db:"reported_at"`
ConfirmedBy *uuid.UUID `json:"confirmed_by,omitempty" db:"confirmed_by"` // Teacher
ConfirmedAt *time.Time `json:"confirmed_at,omitempty" db:"confirmed_at"`
MedicalCertificate bool `json:"medical_certificate" db:"medical_certificate"` // Attestpflicht
CertificateUploaded bool `json:"certificate_uploaded" db:"certificate_uploaded"`
MatrixNotificationSent bool `json:"matrix_notification_sent" db:"matrix_notification_sent"`
EmailNotificationSent bool `json:"email_notification_sent" db:"email_notification_sent"`
CreatedAt time.Time `json:"created_at" db:"created_at"`
UpdatedAt time.Time `json:"updated_at" db:"updated_at"`
}
// AbsenceNotification tracks notifications sent to parents about absences
type AbsenceNotification struct {
ID uuid.UUID `json:"id" db:"id"`
AttendanceRecordID uuid.UUID `json:"attendance_record_id" db:"attendance_record_id"`
ParentID uuid.UUID `json:"parent_id" db:"parent_id"`
Channel string `json:"channel" db:"channel"` // 'matrix', 'email', 'push'
MessageContent string `json:"message_content" db:"message_content"`
SentAt *time.Time `json:"sent_at,omitempty" db:"sent_at"`
ReadAt *time.Time `json:"read_at,omitempty" db:"read_at"`
ResponseReceived bool `json:"response_received" db:"response_received"`
ResponseContent *string `json:"response_content,omitempty" db:"response_content"`
ResponseAt *time.Time `json:"response_at,omitempty" db:"response_at"`
CreatedAt time.Time `json:"created_at" db:"created_at"`
}
// ========================================
// Notenspiegel / Grades
// ========================================
// GradeType defines the type of grade
const (
GradeTypeExam = "exam" // Klassenarbeit/Klausur
GradeTypeTest = "test" // Test/Kurzarbeit
GradeTypeOral = "oral" // Mündlich
GradeTypeHomework = "homework" // Hausaufgabe
GradeTypeProject = "project" // Projekt
GradeTypeParticipation = "participation" // Mitarbeit
GradeTypeSemester = "semester" // Halbjahres-/Semesternote
GradeTypeFinal = "final" // Endnote/Zeugnisnote
)
// GradeScale represents the grading scale used
type GradeScale struct {
ID uuid.UUID `json:"id" db:"id"`
SchoolID uuid.UUID `json:"school_id" db:"school_id"`
Name string `json:"name" db:"name"` // e.g., "1-6", "Punkte 0-15"
MinValue float64 `json:"min_value" db:"min_value"` // e.g., 1 or 0
MaxValue float64 `json:"max_value" db:"max_value"` // e.g., 6 or 15
PassingValue float64 `json:"passing_value" db:"passing_value"` // e.g., 4 or 5
IsAscending bool `json:"is_ascending" db:"is_ascending"` // true: higher=better (Punkte), false: lower=better (Noten)
IsDefault bool `json:"is_default" db:"is_default"`
CreatedAt time.Time `json:"created_at" db:"created_at"`
}
// Grade represents a single grade for a student
type Grade struct {
ID uuid.UUID `json:"id" db:"id"`
StudentID uuid.UUID `json:"student_id" db:"student_id"`
SubjectID uuid.UUID `json:"subject_id" db:"subject_id"`
TeacherID uuid.UUID `json:"teacher_id" db:"teacher_id"`
SchoolYearID uuid.UUID `json:"school_year_id" db:"school_year_id"`
GradeScaleID uuid.UUID `json:"grade_scale_id" db:"grade_scale_id"`
Type string `json:"type" db:"type"` // GradeType constants
Value float64 `json:"value" db:"value"`
Weight float64 `json:"weight" db:"weight"` // Gewichtung: 1.0, 2.0, 0.5
Date time.Time `json:"date" db:"date"`
Title *string `json:"title,omitempty" db:"title"` // e.g., "1. Klassenarbeit"
Description *string `json:"description,omitempty" db:"description"`
IsVisible bool `json:"is_visible" db:"is_visible"` // Für Eltern/Schüler sichtbar
Semester int `json:"semester" db:"semester"` // 1 or 2
CreatedAt time.Time `json:"created_at" db:"created_at"`
UpdatedAt time.Time `json:"updated_at" db:"updated_at"`
}
// GradeComment represents a teacher comment on a student's grade
type GradeComment struct {
ID uuid.UUID `json:"id" db:"id"`
GradeID uuid.UUID `json:"grade_id" db:"grade_id"`
TeacherID uuid.UUID `json:"teacher_id" db:"teacher_id"`
Comment string `json:"comment" db:"comment"`
IsPrivate bool `json:"is_private" db:"is_private"` // Only visible to teachers
CreatedAt time.Time `json:"created_at" db:"created_at"`
}
// ========================================
// Klassenbuch / Class Diary
// ========================================
// ClassDiaryEntry represents an entry in the digital class diary
type ClassDiaryEntry struct {
ID uuid.UUID `json:"id" db:"id"`
ClassID uuid.UUID `json:"class_id" db:"class_id"`
Date time.Time `json:"date" db:"date"`
SlotID uuid.UUID `json:"slot_id" db:"slot_id"`
SubjectID uuid.UUID `json:"subject_id" db:"subject_id"`
TeacherID uuid.UUID `json:"teacher_id" db:"teacher_id"`
Topic *string `json:"topic,omitempty" db:"topic"` // Unterrichtsthema
Homework *string `json:"homework,omitempty" db:"homework"` // Hausaufgabe
HomeworkDueDate *time.Time `json:"homework_due_date,omitempty" db:"homework_due_date"`
Materials *string `json:"materials,omitempty" db:"materials"` // Benötigte Materialien
Notes *string `json:"notes,omitempty" db:"notes"` // Besondere Vorkommnisse
IsCancelled bool `json:"is_cancelled" db:"is_cancelled"`
CancellationReason *string `json:"cancellation_reason,omitempty" db:"cancellation_reason"`
CreatedAt time.Time `json:"created_at" db:"created_at"`
UpdatedAt time.Time `json:"updated_at" db:"updated_at"`
}
// ========================================
// Elterngespräche / Parent Meetings
// ========================================
// ParentMeetingSlot represents available time slots for parent meetings
type ParentMeetingSlot struct {
ID uuid.UUID `json:"id" db:"id"`
TeacherID uuid.UUID `json:"teacher_id" db:"teacher_id"`
Date time.Time `json:"date" db:"date"`
StartTime string `json:"start_time" db:"start_time"` // "14:00"
EndTime string `json:"end_time" db:"end_time"` // "14:15"
Location *string `json:"location,omitempty" db:"location"` // Room or "Online"
IsOnline bool `json:"is_online" db:"is_online"`
MeetingLink *string `json:"meeting_link,omitempty" db:"meeting_link"`
IsBooked bool `json:"is_booked" db:"is_booked"`
CreatedAt time.Time `json:"created_at" db:"created_at"`
}
// ParentMeeting represents a booked parent-teacher meeting
type ParentMeeting struct {
ID uuid.UUID `json:"id" db:"id"`
SlotID uuid.UUID `json:"slot_id" db:"slot_id"`
ParentID uuid.UUID `json:"parent_id" db:"parent_id"`
StudentID uuid.UUID `json:"student_id" db:"student_id"`
Topic *string `json:"topic,omitempty" db:"topic"`
Notes *string `json:"notes,omitempty" db:"notes"` // Teacher notes (private)
Status string `json:"status" db:"status"` // 'scheduled', 'completed', 'cancelled', 'no_show'
CancelledAt *time.Time `json:"cancelled_at,omitempty" db:"cancelled_at"`
CancelledBy *uuid.UUID `json:"cancelled_by,omitempty" db:"cancelled_by"`
CancelReason *string `json:"cancel_reason,omitempty" db:"cancel_reason"`
CompletedAt *time.Time `json:"completed_at,omitempty" db:"completed_at"`
CreatedAt time.Time `json:"created_at" db:"created_at"`
UpdatedAt time.Time `json:"updated_at" db:"updated_at"`
}
// ========================================
// Matrix / Communication Integration
// ========================================
// MatrixRoom tracks Matrix rooms created for school communication
type MatrixRoom struct {
ID uuid.UUID `json:"id" db:"id"`
SchoolID uuid.UUID `json:"school_id" db:"school_id"`
MatrixRoomID string `json:"matrix_room_id" db:"matrix_room_id"` // e.g., "!abc123:breakpilot.local"
Type string `json:"type" db:"type"` // 'class_info', 'class_rep', 'student_dm', 'teacher_dm', 'announcement'
ClassID *uuid.UUID `json:"class_id,omitempty" db:"class_id"`
StudentID *uuid.UUID `json:"student_id,omitempty" db:"student_id"`
Name string `json:"name" db:"name"`
IsEncrypted bool `json:"is_encrypted" db:"is_encrypted"`
CreatedAt time.Time `json:"created_at" db:"created_at"`
}
// MatrixRoomMember tracks membership in Matrix rooms
type MatrixRoomMember struct {
ID uuid.UUID `json:"id" db:"id"`
MatrixRoomID uuid.UUID `json:"matrix_room_id" db:"matrix_room_id"` // FK to MatrixRoom
MatrixUserID string `json:"matrix_user_id" db:"matrix_user_id"` // e.g., "@user:breakpilot.local"
UserID *uuid.UUID `json:"user_id,omitempty" db:"user_id"` // FK to User (if known)
PowerLevel int `json:"power_level" db:"power_level"` // Matrix power level (0, 50, 100)
CanWrite bool `json:"can_write" db:"can_write"`
JoinedAt time.Time `json:"joined_at" db:"joined_at"`
LeftAt *time.Time `json:"left_at,omitempty" db:"left_at"`
}
// ParentOnboardingToken for QR-code based parent onboarding
type ParentOnboardingToken struct {
ID uuid.UUID `json:"id" db:"id"`
SchoolID uuid.UUID `json:"school_id" db:"school_id"`
ClassID uuid.UUID `json:"class_id" db:"class_id"`
StudentID uuid.UUID `json:"student_id" db:"student_id"`
Token string `json:"token" db:"token"` // Unique token for QR code
Role string `json:"role" db:"role"` // 'parent' or 'parent_representative'
ExpiresAt time.Time `json:"expires_at" db:"expires_at"`
UsedAt *time.Time `json:"used_at,omitempty" db:"used_at"`
UsedByUserID *uuid.UUID `json:"used_by_user_id,omitempty" db:"used_by_user_id"`
CreatedAt time.Time `json:"created_at" db:"created_at"`
CreatedBy uuid.UUID `json:"created_by" db:"created_by"` // Teacher who created
}
// ========================================
// Schulverwaltung DTOs
// ========================================
// CreateSchoolRequest for creating a new school
type CreateSchoolRequest struct {
Name string `json:"name" binding:"required"`
ShortName *string `json:"short_name"`
Type string `json:"type" binding:"required"`
Address *string `json:"address"`
City *string `json:"city"`
PostalCode *string `json:"postal_code"`
State *string `json:"state"`
Phone *string `json:"phone"`
Email *string `json:"email"`
Website *string `json:"website"`
}
// CreateClassRequest for creating a new class
type CreateClassRequest struct {
SchoolYearID string `json:"school_year_id" binding:"required"`
Name string `json:"name" binding:"required"`
Grade int `json:"grade" binding:"required"`
Section *string `json:"section"`
Room *string `json:"room"`
}
// CreateStudentRequest for creating a new student
type CreateStudentRequest struct {
ClassID string `json:"class_id" binding:"required"`
StudentNumber *string `json:"student_number"`
FirstName string `json:"first_name" binding:"required"`
LastName string `json:"last_name" binding:"required"`
DateOfBirth *string `json:"date_of_birth"` // ISO 8601
Gender *string `json:"gender"`
}
// RecordAttendanceRequest for recording attendance
type RecordAttendanceRequest struct {
StudentID string `json:"student_id" binding:"required"`
Date string `json:"date" binding:"required"` // ISO 8601
SlotID string `json:"slot_id" binding:"required"`
Status string `json:"status" binding:"required"` // AttendanceStatus
Note *string `json:"note"`
}
// ReportAbsenceRequest for parents reporting absence
type ReportAbsenceRequest struct {
StudentID string `json:"student_id" binding:"required"`
StartDate string `json:"start_date" binding:"required"` // ISO 8601
EndDate string `json:"end_date" binding:"required"` // ISO 8601
Reason *string `json:"reason"`
ReasonCategory string `json:"reason_category" binding:"required"`
}
// CreateGradeRequest for creating a grade
type CreateGradeRequest struct {
StudentID string `json:"student_id" binding:"required"`
SubjectID string `json:"subject_id" binding:"required"`
SchoolYearID string `json:"school_year_id" binding:"required"`
Type string `json:"type" binding:"required"` // GradeType
Value float64 `json:"value" binding:"required"`
Weight float64 `json:"weight"`
Date string `json:"date" binding:"required"` // ISO 8601
Title *string `json:"title"`
Description *string `json:"description"`
Semester int `json:"semester" binding:"required"`
}
// StudentGradeOverview provides a summary of all grades for a student in a subject
type StudentGradeOverview struct {
Student Student `json:"student"`
Subject Subject `json:"subject"`
Grades []Grade `json:"grades"`
Average float64 `json:"average"`
OralAverage float64 `json:"oral_average"`
ExamAverage float64 `json:"exam_average"`
Semester int `json:"semester"`
}
// ClassAttendanceOverview provides attendance summary for a class
type ClassAttendanceOverview struct {
Class Class `json:"class"`
Date time.Time `json:"date"`
TotalStudents int `json:"total_students"`
PresentCount int `json:"present_count"`
AbsentCount int `json:"absent_count"`
LateCount int `json:"late_count"`
Records []AttendanceRecord `json:"records"`
}
// ParentDashboard provides a parent's view of their children's data
type ParentDashboard struct {
Children []StudentOverview `json:"children"`
UnreadMessages int `json:"unread_messages"`
UpcomingMeetings []ParentMeeting `json:"upcoming_meetings"`
RecentGrades []Grade `json:"recent_grades"`
PendingActions []string `json:"pending_actions"` // e.g., "Entschuldigung ausstehend"
}
// StudentOverview provides summary info about a student
type StudentOverview struct {
Student Student `json:"student"`
Class Class `json:"class"`
ClassTeacher *Teacher `json:"class_teacher,omitempty"`
AttendanceRate float64 `json:"attendance_rate"` // Percentage
UnexcusedAbsences int `json:"unexcused_absences"`
GradeAverage float64 `json:"grade_average"`
}
// TimetableView provides a formatted timetable for display
type TimetableView struct {
Class Class `json:"class"`
Week string `json:"week"` // ISO week: "2025-W01"
Days []TimetableDayView `json:"days"`
}
// TimetableDayView represents a single day in the timetable
type TimetableDayView struct {
Date time.Time `json:"date"`
DayName string `json:"day_name"` // "Montag"
Lessons []TimetableLessonView `json:"lessons"`
}
// TimetableLessonView represents a single lesson in the timetable view
type TimetableLessonView struct {
Slot TimetableSlot `json:"slot"`
Subject *Subject `json:"subject,omitempty"`
Teacher *Teacher `json:"teacher,omitempty"`
Room *string `json:"room,omitempty"`
IsSubstitution bool `json:"is_substitution"`
IsCancelled bool `json:"is_cancelled"`
Note *string `json:"note,omitempty"`
}
// ========================================
// Phase 10: 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
)
// 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"` // 'outbound', 'inbound'
Channel string `json:"channel" db:"channel"` // 'email', 'in_app', 'postal'
CommunicationType string `json:"communication_type" db:"communication_type"` // Template type used
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"` // Which DSR types use this template
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"` // draft, review, approved, published, archived
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"` // Type of exception (Art. 17(3) a-e)
Description string `json:"description" db:"description"`
Applies *bool `json:"applies,omitempty" db:"applies"` // nil = not checked, true/false = result
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"`
}
// 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)
)
// ========================================
// DSR DTOs (Data Transfer Objects)
// ========================================
// 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"` // UUID string
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"` // email_confirmation, id_document, in_person
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"` // Art. 12(5), Art. 17(3)(a-e), etc.
}
// ExtendDSRDeadlineRequest for extending a DSR deadline
type ExtendDSRDeadlineRequest struct {
Reason string `json:"reason" binding:"required"`
Days int `json:"days"` // Optional: custom extension 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"` // Search in request number, email, name
}
// 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"`
}
// GetRequestTypeLabel 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)
}
}
// GetDeadlineDays 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
}
}
// GetStatusLabel 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
}
}