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>
1798 lines
82 KiB
Go
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
|
|
}
|
|
}
|