"""Pydantic Models für das Verwaltungsportal.""" from pydantic import BaseModel, Field from typing import Optional class MagicLinkRequest(BaseModel): email: str = Field(min_length=3, max_length=200) class MagicLinkResponse(BaseModel): message: str class VerifyTokenRequest(BaseModel): token: str = Field(min_length=10, max_length=200) class TokenResponse(BaseModel): access_token: str token_type: str = "bearer" username: str email: str = "" class LicenseParams(BaseModel): """Stellwerte einer Lizenz ohne Org-Bezug. Wird zweifach genutzt, direkt beim Lizenz-Anlegen (LicenseCreate) und verschachtelt beim Org-Anlegen (OrgCreate.license). Die Laufzeitfelder (credits_used, credits_carried, credits_period_start, budget_warning_sent) verwaltet der Monitor, das Portal setzt nur Stellwerte. """ license_type: str = Field(pattern="^(trial|annual|permanent)$") max_users: int = Field(default=5, ge=1, le=1000) duration_days: Optional[int] = Field(default=None, ge=1, le=3650) token_budget_usd: Optional[float] = None credits_total: Optional[int] = None cost_per_credit: Optional[float] = None budget_warning_percent: Optional[int] = Field(default=80, ge=0, le=100) # 0 = Warnung aus unlimited_budget: bool = False # 'monthly' = Kontingent wird zum Monatswechsel neu gefuellt (der Monitor # setzt den Verbrauch traege zurueck), 'total' = gilt fuer die Laufzeit. credits_period: str = Field(default="monthly", pattern="^(monthly|total)$") # Uebertrag ungenutzter Credits in den Folgemonat, der Monitor deckelt # auf ein Monatskontingent. Standard AUS (Entscheidung Nutzer 25.07.2026), # pro Kunde bewusst zuschaltbar. credits_rollover: bool = False class OrgCreate(BaseModel): name: str = Field(min_length=1, max_length=200) slug: str = Field(min_length=1, max_length=100, pattern="^[a-z0-9-]+$") output_language: str = Field(default="de", pattern="^(de|en)$") # Optional direkt eine Lizenz mit Credits-Kontingent anlegen license: Optional[LicenseParams] = None class OrgUpdate(BaseModel): name: Optional[str] = Field(default=None, max_length=200) is_active: Optional[bool] = None output_language: Optional[str] = Field(default=None, pattern="^(de|en)$") class OrgResponse(BaseModel): id: int name: str slug: str is_active: bool user_count: int = 0 license_status: str = "" license_type: str = "" created_at: str globe_access: bool = False network_access: bool = False output_language: str = "de" class LicenseCreate(LicenseParams): organization_id: int class LicenseResponse(BaseModel): id: int organization_id: int license_type: str max_users: int valid_from: str valid_until: Optional[str] status: str notes: Optional[str] token_budget_usd: Optional[float] = None credits_total: Optional[int] = None credits_used: Optional[float] = None cost_per_credit: Optional[float] = None budget_warning_percent: Optional[int] = None unlimited_budget: bool = False # Neue Kontingent-Felder, alle optional mit Default, damit Responses auch # von einer noch nicht migrierten Datenbank validieren. credits_period: Optional[str] = "monthly" credits_rollover: Optional[bool] = False credits_carried: Optional[float] = 0 credits_period_start: Optional[str] = None created_at: str globe_access: bool = False network_access: bool = False class UserCreate(BaseModel): email: str = Field(min_length=3, max_length=200) username: Optional[str] = Field(default=None, max_length=100) role: str = Field(default="member", pattern="^(org_admin|member)$") class UserResponse(BaseModel): id: int email: str username: str organization_id: int role: str is_active: bool last_login_at: Optional[str] created_at: str globe_access: bool = False network_access: bool = False