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>
This commit is contained in:
74
klausur-service/backend/models/__init__.py
Normal file
74
klausur-service/backend/models/__init__.py
Normal file
@@ -0,0 +1,74 @@
|
||||
"""
|
||||
Klausur-Service Models
|
||||
|
||||
Data models for exams, students, grading, and Erwartungshorizont.
|
||||
"""
|
||||
|
||||
from .enums import KlausurModus, StudentKlausurStatus, EHStatus
|
||||
from .exam import Klausur, StudentKlausur
|
||||
from .grading import AuditLogEntry, GRADE_THRESHOLDS, GRADE_LABELS, DEFAULT_CRITERIA
|
||||
from .eh import (
|
||||
Erwartungshorizont,
|
||||
EHRightsConfirmation,
|
||||
EHAuditLogEntry,
|
||||
EHKeyShare,
|
||||
EHKlausurLink,
|
||||
EHShareInvitation,
|
||||
)
|
||||
from .requests import (
|
||||
KlausurCreate,
|
||||
KlausurUpdate,
|
||||
StudentUpload,
|
||||
CriterionScoreUpdate,
|
||||
GutachtenUpdate,
|
||||
ExaminerAssignment,
|
||||
ExaminerResult,
|
||||
GutachtenGenerateRequest,
|
||||
EHMetadata,
|
||||
EHUploadMetadata,
|
||||
EHRAGQuery,
|
||||
EHIndexRequest,
|
||||
EHShareRequest,
|
||||
EHLinkKlausurRequest,
|
||||
EHInviteRequest,
|
||||
EHAcceptInviteRequest,
|
||||
)
|
||||
|
||||
__all__ = [
|
||||
# Enums
|
||||
"KlausurModus",
|
||||
"StudentKlausurStatus",
|
||||
"EHStatus",
|
||||
# Exam Models
|
||||
"Klausur",
|
||||
"StudentKlausur",
|
||||
# Grading
|
||||
"AuditLogEntry",
|
||||
"GRADE_THRESHOLDS",
|
||||
"GRADE_LABELS",
|
||||
"DEFAULT_CRITERIA",
|
||||
# EH Models
|
||||
"Erwartungshorizont",
|
||||
"EHRightsConfirmation",
|
||||
"EHAuditLogEntry",
|
||||
"EHKeyShare",
|
||||
"EHKlausurLink",
|
||||
"EHShareInvitation",
|
||||
# Request Models
|
||||
"KlausurCreate",
|
||||
"KlausurUpdate",
|
||||
"StudentUpload",
|
||||
"CriterionScoreUpdate",
|
||||
"GutachtenUpdate",
|
||||
"ExaminerAssignment",
|
||||
"ExaminerResult",
|
||||
"GutachtenGenerateRequest",
|
||||
"EHMetadata",
|
||||
"EHUploadMetadata",
|
||||
"EHRAGQuery",
|
||||
"EHIndexRequest",
|
||||
"EHShareRequest",
|
||||
"EHLinkKlausurRequest",
|
||||
"EHInviteRequest",
|
||||
"EHAcceptInviteRequest",
|
||||
]
|
||||
197
klausur-service/backend/models/eh.py
Normal file
197
klausur-service/backend/models/eh.py
Normal file
@@ -0,0 +1,197 @@
|
||||
"""
|
||||
Klausur-Service Erwartungshorizont Models
|
||||
|
||||
Data classes for BYOEH (Bring-Your-Own-Expectation-Horizon).
|
||||
"""
|
||||
|
||||
from dataclasses import dataclass
|
||||
from datetime import datetime
|
||||
from typing import Optional, Dict, Any
|
||||
|
||||
from .enums import EHStatus
|
||||
|
||||
|
||||
@dataclass
|
||||
class Erwartungshorizont:
|
||||
"""An encrypted Erwartungshorizont (expectation horizon)."""
|
||||
id: str
|
||||
tenant_id: str
|
||||
teacher_id: str
|
||||
title: str
|
||||
subject: str
|
||||
niveau: str # 'eA' or 'gA'
|
||||
year: int
|
||||
aufgaben_nummer: Optional[str]
|
||||
encryption_key_hash: str
|
||||
salt: str
|
||||
encrypted_file_path: str
|
||||
file_size_bytes: int
|
||||
original_filename: str
|
||||
rights_confirmed: bool
|
||||
rights_confirmed_at: Optional[datetime]
|
||||
status: EHStatus
|
||||
chunk_count: int
|
||||
indexed_at: Optional[datetime]
|
||||
error_message: Optional[str]
|
||||
training_allowed: bool # ALWAYS FALSE
|
||||
created_at: datetime
|
||||
deleted_at: Optional[datetime]
|
||||
|
||||
def to_dict(self) -> Dict[str, Any]:
|
||||
"""Convert to dictionary for JSON serialization."""
|
||||
status_value = self.status.value if hasattr(self.status, 'value') else self.status
|
||||
return {
|
||||
'id': self.id,
|
||||
'tenant_id': self.tenant_id,
|
||||
'teacher_id': self.teacher_id,
|
||||
'title': self.title,
|
||||
'subject': self.subject,
|
||||
'niveau': self.niveau,
|
||||
'year': self.year,
|
||||
'aufgaben_nummer': self.aufgaben_nummer,
|
||||
'status': status_value,
|
||||
'chunk_count': self.chunk_count,
|
||||
'rights_confirmed': self.rights_confirmed,
|
||||
'rights_confirmed_at': self.rights_confirmed_at.isoformat() if self.rights_confirmed_at else None,
|
||||
'indexed_at': self.indexed_at.isoformat() if self.indexed_at else None,
|
||||
'file_size_bytes': self.file_size_bytes,
|
||||
'original_filename': self.original_filename,
|
||||
'training_allowed': self.training_allowed,
|
||||
'created_at': self.created_at.isoformat(),
|
||||
'deleted_at': self.deleted_at.isoformat() if self.deleted_at else None
|
||||
}
|
||||
|
||||
|
||||
@dataclass
|
||||
class EHRightsConfirmation:
|
||||
"""Rights confirmation for an Erwartungshorizont upload."""
|
||||
id: str
|
||||
eh_id: str
|
||||
teacher_id: str
|
||||
confirmation_type: str # 'upload' | 'annual'
|
||||
confirmation_text: str
|
||||
ip_address: Optional[str]
|
||||
user_agent: Optional[str]
|
||||
confirmed_at: datetime
|
||||
|
||||
def to_dict(self) -> Dict[str, Any]:
|
||||
"""Convert to dictionary for JSON serialization."""
|
||||
return {
|
||||
'id': self.id,
|
||||
'eh_id': self.eh_id,
|
||||
'teacher_id': self.teacher_id,
|
||||
'confirmation_type': self.confirmation_type,
|
||||
'confirmation_text': self.confirmation_text,
|
||||
'confirmed_at': self.confirmed_at.isoformat()
|
||||
}
|
||||
|
||||
|
||||
@dataclass
|
||||
class EHAuditLogEntry:
|
||||
"""Audit log entry for EH operations."""
|
||||
id: str
|
||||
eh_id: Optional[str]
|
||||
tenant_id: str
|
||||
user_id: str
|
||||
action: str # upload, index, rag_query, download, delete
|
||||
details: Optional[Dict]
|
||||
ip_address: Optional[str]
|
||||
user_agent: Optional[str]
|
||||
created_at: datetime
|
||||
|
||||
def to_dict(self) -> Dict[str, Any]:
|
||||
"""Convert to dictionary for JSON serialization."""
|
||||
return {
|
||||
'id': self.id,
|
||||
'eh_id': self.eh_id,
|
||||
'tenant_id': self.tenant_id,
|
||||
'user_id': self.user_id,
|
||||
'action': self.action,
|
||||
'details': self.details,
|
||||
'created_at': self.created_at.isoformat()
|
||||
}
|
||||
|
||||
|
||||
@dataclass
|
||||
class EHKeyShare:
|
||||
"""Encrypted passphrase share for authorized users."""
|
||||
id: str
|
||||
eh_id: str
|
||||
user_id: str
|
||||
encrypted_passphrase: str # Passphrase encrypted with recipient's public key
|
||||
passphrase_hint: str # Optional hint for the passphrase
|
||||
granted_by: str
|
||||
granted_at: datetime
|
||||
role: str # 'second_examiner', 'third_examiner', 'supervisor'
|
||||
klausur_id: Optional[str] # Link to specific Klausur if applicable
|
||||
active: bool
|
||||
|
||||
def to_dict(self) -> Dict[str, Any]:
|
||||
"""Convert to dictionary for JSON serialization."""
|
||||
return {
|
||||
'id': self.id,
|
||||
'eh_id': self.eh_id,
|
||||
'user_id': self.user_id,
|
||||
'passphrase_hint': self.passphrase_hint,
|
||||
'granted_by': self.granted_by,
|
||||
'granted_at': self.granted_at.isoformat(),
|
||||
'role': self.role,
|
||||
'klausur_id': self.klausur_id,
|
||||
'active': self.active
|
||||
}
|
||||
|
||||
|
||||
@dataclass
|
||||
class EHKlausurLink:
|
||||
"""Link between an EH and a Klausur."""
|
||||
id: str
|
||||
eh_id: str
|
||||
klausur_id: str
|
||||
linked_by: str
|
||||
linked_at: datetime
|
||||
|
||||
def to_dict(self) -> Dict[str, Any]:
|
||||
"""Convert to dictionary for JSON serialization."""
|
||||
return {
|
||||
'id': self.id,
|
||||
'eh_id': self.eh_id,
|
||||
'klausur_id': self.klausur_id,
|
||||
'linked_by': self.linked_by,
|
||||
'linked_at': self.linked_at.isoformat()
|
||||
}
|
||||
|
||||
|
||||
@dataclass
|
||||
class EHShareInvitation:
|
||||
"""Invitation to share an EH with another user."""
|
||||
id: str
|
||||
eh_id: str
|
||||
inviter_id: str # User who sent the invitation
|
||||
invitee_id: str # User receiving the invitation
|
||||
invitee_email: str # Email for notification
|
||||
role: str # Target role for the invitee
|
||||
klausur_id: Optional[str] # Optional link to specific Klausur
|
||||
message: Optional[str] # Optional message from inviter
|
||||
status: str # 'pending', 'accepted', 'declined', 'expired', 'revoked'
|
||||
expires_at: datetime # Invitation expiration
|
||||
created_at: datetime
|
||||
accepted_at: Optional[datetime]
|
||||
declined_at: Optional[datetime]
|
||||
|
||||
def to_dict(self) -> Dict[str, Any]:
|
||||
"""Convert to dictionary for JSON serialization."""
|
||||
return {
|
||||
'id': self.id,
|
||||
'eh_id': self.eh_id,
|
||||
'inviter_id': self.inviter_id,
|
||||
'invitee_id': self.invitee_id,
|
||||
'invitee_email': self.invitee_email,
|
||||
'role': self.role,
|
||||
'klausur_id': self.klausur_id,
|
||||
'message': self.message,
|
||||
'status': self.status,
|
||||
'expires_at': self.expires_at.isoformat(),
|
||||
'created_at': self.created_at.isoformat(),
|
||||
'accepted_at': self.accepted_at.isoformat() if self.accepted_at else None,
|
||||
'declined_at': self.declined_at.isoformat() if self.declined_at else None
|
||||
}
|
||||
33
klausur-service/backend/models/enums.py
Normal file
33
klausur-service/backend/models/enums.py
Normal file
@@ -0,0 +1,33 @@
|
||||
"""
|
||||
Klausur-Service Enums
|
||||
|
||||
Status and type enumerations.
|
||||
"""
|
||||
|
||||
from enum import Enum
|
||||
|
||||
|
||||
class KlausurModus(str, Enum):
|
||||
"""Klausur mode: Landes-Abitur or Vorabitur."""
|
||||
LANDES_ABITUR = "landes_abitur"
|
||||
VORABITUR = "vorabitur"
|
||||
|
||||
|
||||
class StudentKlausurStatus(str, Enum):
|
||||
"""Processing status of a student's work."""
|
||||
UPLOADED = "uploaded"
|
||||
OCR_PROCESSING = "ocr_processing"
|
||||
OCR_COMPLETE = "ocr_complete"
|
||||
ANALYZING = "analyzing"
|
||||
FIRST_EXAMINER = "first_examiner"
|
||||
SECOND_EXAMINER = "second_examiner"
|
||||
COMPLETED = "completed"
|
||||
ERROR = "error"
|
||||
|
||||
|
||||
class EHStatus(str, Enum):
|
||||
"""Status of an Erwartungshorizont."""
|
||||
PENDING_RIGHTS = "pending_rights"
|
||||
PROCESSING = "processing"
|
||||
INDEXED = "indexed"
|
||||
ERROR = "error"
|
||||
68
klausur-service/backend/models/exam.py
Normal file
68
klausur-service/backend/models/exam.py
Normal file
@@ -0,0 +1,68 @@
|
||||
"""
|
||||
Klausur-Service Exam Models
|
||||
|
||||
Data classes for Klausur and StudentKlausur.
|
||||
"""
|
||||
|
||||
from dataclasses import dataclass, asdict
|
||||
from datetime import datetime
|
||||
from typing import Optional, List, Dict, Any
|
||||
|
||||
from .enums import KlausurModus, StudentKlausurStatus
|
||||
|
||||
|
||||
@dataclass
|
||||
class StudentKlausur:
|
||||
"""A student's exam work."""
|
||||
id: str
|
||||
klausur_id: str
|
||||
student_name: str
|
||||
student_id: Optional[str]
|
||||
file_path: Optional[str]
|
||||
ocr_text: Optional[str]
|
||||
status: StudentKlausurStatus
|
||||
criteria_scores: Dict[str, Dict]
|
||||
gutachten: Optional[Dict]
|
||||
raw_points: int
|
||||
grade_points: int
|
||||
created_at: datetime
|
||||
|
||||
def to_dict(self) -> Dict[str, Any]:
|
||||
"""Convert to dictionary for JSON serialization."""
|
||||
d = asdict(self)
|
||||
d['status'] = self.status.value if hasattr(self.status, 'value') else self.status
|
||||
d['created_at'] = self.created_at.isoformat()
|
||||
return d
|
||||
|
||||
|
||||
@dataclass
|
||||
class Klausur:
|
||||
"""An exam/Klausur with associated student works."""
|
||||
id: str
|
||||
title: str
|
||||
subject: str
|
||||
modus: KlausurModus
|
||||
class_id: Optional[str]
|
||||
year: int
|
||||
semester: str
|
||||
erwartungshorizont: Optional[Dict]
|
||||
students: List[StudentKlausur]
|
||||
created_at: datetime
|
||||
teacher_id: str
|
||||
|
||||
def to_dict(self) -> Dict[str, Any]:
|
||||
"""Convert to dictionary for JSON serialization."""
|
||||
return {
|
||||
'id': self.id,
|
||||
'title': self.title,
|
||||
'subject': self.subject,
|
||||
'modus': self.modus.value,
|
||||
'class_id': self.class_id,
|
||||
'year': self.year,
|
||||
'semester': self.semester,
|
||||
'erwartungshorizont': self.erwartungshorizont,
|
||||
'student_count': len(self.students),
|
||||
'students': [s.to_dict() for s in self.students],
|
||||
'created_at': self.created_at.isoformat(),
|
||||
'teacher_id': self.teacher_id
|
||||
}
|
||||
71
klausur-service/backend/models/grading.py
Normal file
71
klausur-service/backend/models/grading.py
Normal file
@@ -0,0 +1,71 @@
|
||||
"""
|
||||
Klausur-Service Grading Models
|
||||
|
||||
Grade thresholds, labels, criteria, and audit logging.
|
||||
"""
|
||||
|
||||
from dataclasses import dataclass
|
||||
from datetime import datetime
|
||||
from typing import Optional, Dict, Any
|
||||
|
||||
|
||||
# =============================================
|
||||
# GRADE CONSTANTS
|
||||
# =============================================
|
||||
|
||||
GRADE_THRESHOLDS = {
|
||||
15: 95, 14: 90, 13: 85, 12: 80, 11: 75, 10: 70,
|
||||
9: 65, 8: 60, 7: 55, 6: 50, 5: 45, 4: 40,
|
||||
3: 33, 2: 27, 1: 20, 0: 0
|
||||
}
|
||||
|
||||
GRADE_LABELS = {
|
||||
15: "1+ (sehr gut plus)", 14: "1 (sehr gut)", 13: "1- (sehr gut minus)",
|
||||
12: "2+ (gut plus)", 11: "2 (gut)", 10: "2- (gut minus)",
|
||||
9: "3+ (befriedigend plus)", 8: "3 (befriedigend)", 7: "3- (befriedigend minus)",
|
||||
6: "4+ (ausreichend plus)", 5: "4 (ausreichend)", 4: "4- (ausreichend minus)",
|
||||
3: "5+ (mangelhaft plus)", 2: "5 (mangelhaft)", 1: "5- (mangelhaft minus)",
|
||||
0: "6 (ungenuegend)"
|
||||
}
|
||||
|
||||
DEFAULT_CRITERIA = {
|
||||
"rechtschreibung": {"weight": 0.15, "label": "Rechtschreibung"},
|
||||
"grammatik": {"weight": 0.15, "label": "Grammatik"},
|
||||
"inhalt": {"weight": 0.40, "label": "Inhalt"},
|
||||
"struktur": {"weight": 0.15, "label": "Struktur"},
|
||||
"stil": {"weight": 0.15, "label": "Stil"},
|
||||
}
|
||||
|
||||
|
||||
# =============================================
|
||||
# AUDIT LOG
|
||||
# =============================================
|
||||
|
||||
@dataclass
|
||||
class AuditLogEntry:
|
||||
"""Audit log entry for tracking changes."""
|
||||
id: str
|
||||
timestamp: datetime
|
||||
user_id: str
|
||||
action: str # score_update, gutachten_update, status_change, examiner_assign
|
||||
entity_type: str # klausur, student
|
||||
entity_id: str
|
||||
field: Optional[str]
|
||||
old_value: Optional[str]
|
||||
new_value: Optional[str]
|
||||
details: Optional[Dict]
|
||||
|
||||
def to_dict(self) -> Dict[str, Any]:
|
||||
"""Convert to dictionary for JSON serialization."""
|
||||
return {
|
||||
'id': self.id,
|
||||
'timestamp': self.timestamp.isoformat(),
|
||||
'user_id': self.user_id,
|
||||
'action': self.action,
|
||||
'entity_type': self.entity_type,
|
||||
'entity_id': self.entity_id,
|
||||
'field': self.field,
|
||||
'old_value': self.old_value,
|
||||
'new_value': self.new_value,
|
||||
'details': self.details
|
||||
}
|
||||
152
klausur-service/backend/models/requests.py
Normal file
152
klausur-service/backend/models/requests.py
Normal file
@@ -0,0 +1,152 @@
|
||||
"""
|
||||
Klausur-Service Request Models
|
||||
|
||||
Pydantic models for API request/response validation.
|
||||
"""
|
||||
|
||||
from typing import Optional, List, Dict, Any
|
||||
from pydantic import BaseModel
|
||||
|
||||
from .enums import KlausurModus
|
||||
|
||||
|
||||
# =============================================
|
||||
# KLAUSUR REQUESTS
|
||||
# =============================================
|
||||
|
||||
class KlausurCreate(BaseModel):
|
||||
"""Request to create a new Klausur."""
|
||||
title: str
|
||||
subject: str
|
||||
modus: KlausurModus = KlausurModus.VORABITUR
|
||||
class_id: Optional[str] = None
|
||||
year: int = 2025
|
||||
semester: str = "Q1"
|
||||
|
||||
|
||||
class KlausurUpdate(BaseModel):
|
||||
"""Request to update a Klausur."""
|
||||
title: Optional[str] = None
|
||||
subject: Optional[str] = None
|
||||
erwartungshorizont: Optional[Dict[str, Any]] = None
|
||||
|
||||
|
||||
# =============================================
|
||||
# STUDENT REQUESTS
|
||||
# =============================================
|
||||
|
||||
class StudentUpload(BaseModel):
|
||||
"""Request for student work upload metadata."""
|
||||
student_name: str
|
||||
student_id: Optional[str] = None
|
||||
|
||||
|
||||
# =============================================
|
||||
# GRADING REQUESTS
|
||||
# =============================================
|
||||
|
||||
class CriterionScoreUpdate(BaseModel):
|
||||
"""Request to update a criterion score."""
|
||||
criterion: str
|
||||
score: int
|
||||
annotations: Optional[List[str]] = None
|
||||
|
||||
|
||||
class GutachtenUpdate(BaseModel):
|
||||
"""Request to update the Gutachten."""
|
||||
einleitung: str
|
||||
hauptteil: str
|
||||
fazit: str
|
||||
staerken: Optional[List[str]] = None
|
||||
schwaechen: Optional[List[str]] = None
|
||||
|
||||
|
||||
class GutachtenGenerateRequest(BaseModel):
|
||||
"""Request to generate a KI-based Gutachten."""
|
||||
include_strengths: bool = True
|
||||
include_weaknesses: bool = True
|
||||
tone: str = "formal" # formal, friendly, constructive
|
||||
# BYOEH RAG Integration
|
||||
use_eh: bool = False # Whether to use Erwartungshorizont for context
|
||||
eh_passphrase: Optional[str] = None # Passphrase for EH decryption
|
||||
|
||||
|
||||
# =============================================
|
||||
# EXAMINER REQUESTS
|
||||
# =============================================
|
||||
|
||||
class ExaminerAssignment(BaseModel):
|
||||
"""Request to assign an examiner."""
|
||||
examiner_id: str
|
||||
examiner_role: str # first_examiner, second_examiner
|
||||
notes: Optional[str] = None
|
||||
|
||||
|
||||
class ExaminerResult(BaseModel):
|
||||
"""Request to submit an examiner's result."""
|
||||
grade_points: int
|
||||
notes: Optional[str] = None
|
||||
|
||||
|
||||
# =============================================
|
||||
# BYOEH REQUESTS
|
||||
# =============================================
|
||||
|
||||
class EHMetadata(BaseModel):
|
||||
"""Metadata for an Erwartungshorizont."""
|
||||
title: str
|
||||
subject: str
|
||||
niveau: str # 'eA' | 'gA'
|
||||
year: int
|
||||
aufgaben_nummer: Optional[str] = None
|
||||
|
||||
|
||||
class EHUploadMetadata(BaseModel):
|
||||
"""Metadata for EH upload including encryption info."""
|
||||
metadata: EHMetadata
|
||||
encryption_key_hash: str
|
||||
salt: str
|
||||
rights_confirmed: bool
|
||||
original_filename: str
|
||||
|
||||
|
||||
class EHRAGQuery(BaseModel):
|
||||
"""Request for RAG query against Erwartungshorizonte."""
|
||||
query_text: str
|
||||
passphrase: str
|
||||
subject: Optional[str] = None
|
||||
limit: int = 5
|
||||
|
||||
|
||||
class EHIndexRequest(BaseModel):
|
||||
"""Request to index an Erwartungshorizont."""
|
||||
passphrase: str
|
||||
|
||||
|
||||
class EHShareRequest(BaseModel):
|
||||
"""Request to share EH with another examiner."""
|
||||
user_id: str # User to share with
|
||||
role: str # second_examiner, third_examiner, supervisor
|
||||
encrypted_passphrase: str # Passphrase encrypted for recipient
|
||||
passphrase_hint: Optional[str] = None
|
||||
klausur_id: Optional[str] = None # Optional: link to specific Klausur
|
||||
|
||||
|
||||
class EHLinkKlausurRequest(BaseModel):
|
||||
"""Request to link EH to a Klausur."""
|
||||
klausur_id: str
|
||||
|
||||
|
||||
class EHInviteRequest(BaseModel):
|
||||
"""Request to invite another user to access an EH."""
|
||||
invitee_email: str # Email of the recipient
|
||||
invitee_id: Optional[str] = None # User ID if known
|
||||
role: str # second_examiner, third_examiner, supervisor, department_head
|
||||
klausur_id: Optional[str] = None # Optional: link to specific Klausur
|
||||
message: Optional[str] = None # Optional message for the invitee
|
||||
expires_in_days: int = 14 # Default 14 days expiration
|
||||
|
||||
|
||||
class EHAcceptInviteRequest(BaseModel):
|
||||
"""Request to accept an invitation and receive the encrypted passphrase."""
|
||||
encrypted_passphrase: str # The passphrase encrypted for the invitee
|
||||
Reference in New Issue
Block a user