"""Pydantic Models für Request/Response Schemas.""" from pydantic import BaseModel, Field from typing import Optional from datetime import datetime # Auth (Magic Link) class MagicLinkRequest(BaseModel): email: str = Field(min_length=1, max_length=254) class MagicLinkResponse(BaseModel): message: str class VerifyTokenRequest(BaseModel): token: str class VerifyCodeRequest(BaseModel): email: str = Field(min_length=1, max_length=254) code: str = Field(min_length=6, max_length=6) class TokenResponse(BaseModel): access_token: str token_type: str = "bearer" username: str class UserMeResponse(BaseModel): id: int username: str email: str = "" role: str = "member" org_name: str = "" org_slug: str = "" tenant_id: Optional[int] = None license_status: str = "unknown" license_type: str = "" read_only: bool = False credits_total: Optional[int] = None credits_remaining: Optional[int] = None credits_percent_used: Optional[float] = None # Incidents (Lagen) class IncidentCreate(BaseModel): title: str = Field(min_length=1, max_length=200) description: Optional[str] = None type: str = Field(default="adhoc", pattern="^(adhoc|research)$") refresh_mode: str = Field(default="manual", pattern="^(manual|auto)$") refresh_interval: int = Field(default=15, ge=10, le=10080) retention_days: int = Field(default=0, ge=0, le=999) international_sources: bool = True include_telegram: bool = False visibility: str = Field(default="public", pattern="^(public|private)$") class IncidentUpdate(BaseModel): title: Optional[str] = None description: Optional[str] = None type: Optional[str] = Field(default=None, pattern="^(adhoc|research)$") status: Optional[str] = Field(default=None, pattern="^(active|archived)$") refresh_mode: Optional[str] = Field(default=None, pattern="^(manual|auto)$") refresh_interval: Optional[int] = Field(default=None, ge=10, le=10080) retention_days: Optional[int] = Field(default=None, ge=0, le=999) international_sources: Optional[bool] = None include_telegram: Optional[bool] = None visibility: Optional[str] = Field(default=None, pattern="^(public|private)$") class IncidentResponse(BaseModel): id: int title: str description: Optional[str] type: str status: str refresh_mode: str refresh_interval: int retention_days: int visibility: str = "public" summary: Optional[str] sources_json: Optional[str] = None international_sources: bool = True include_telegram: bool = False created_by: int created_by_username: str = "" created_at: str updated_at: str article_count: int = 0 source_count: int = 0 # Sources (Quellenverwaltung) class SourceCreate(BaseModel): name: str = Field(min_length=1, max_length=200) url: Optional[str] = None domain: Optional[str] = None source_type: str = Field(default="rss_feed", pattern="^(rss_feed|web_source|excluded|telegram_channel)$") category: str = Field(default="sonstige", pattern="^(nachrichtenagentur|oeffentlich-rechtlich|qualitaetszeitung|behoerde|fachmedien|think-tank|international|regional|boulevard|sonstige)$") status: str = Field(default="active", pattern="^(active|inactive)$") notes: Optional[str] = None class SourceUpdate(BaseModel): name: Optional[str] = Field(default=None, max_length=200) url: Optional[str] = None domain: Optional[str] = None source_type: Optional[str] = Field(default=None, pattern="^(rss_feed|web_source|excluded|telegram_channel)$") category: Optional[str] = Field(default=None, pattern="^(nachrichtenagentur|oeffentlich-rechtlich|qualitaetszeitung|behoerde|fachmedien|think-tank|international|regional|boulevard|sonstige)$") status: Optional[str] = Field(default=None, pattern="^(active|inactive)$") notes: Optional[str] = None class SourceResponse(BaseModel): id: int name: str url: Optional[str] domain: Optional[str] source_type: str category: str status: str notes: Optional[str] added_by: Optional[str] article_count: int = 0 last_seen_at: Optional[str] = None created_at: str language: Optional[str] = None bias: Optional[str] = None is_global: bool = False # Source Discovery class DiscoverRequest(BaseModel): url: str = Field(min_length=1, max_length=500) class DiscoverResponse(BaseModel): name: str domain: str rss_url: Optional[str] = None category: str source_type: str # Multi-Discovery class DiscoverMultiResponse(BaseModel): domain: str category: str added_count: int skipped_count: int total_found: int sources: list[SourceResponse] fallback_single: bool = False # Domain-Aktionen (Ausschließen/Ausschluss aufheben) class DomainActionRequest(BaseModel): domain: str = Field(min_length=1, max_length=200) notes: Optional[str] = None # Notifications class NotificationResponse(BaseModel): id: int incident_id: Optional[int] type: str title: str text: str icon: str is_read: bool created_at: str class NotificationMarkReadRequest(BaseModel): notification_ids: Optional[list[int]] = None # None = alle class SubscriptionUpdate(BaseModel): notify_email_summary: bool = False notify_email_new_articles: bool = False notify_email_status_change: bool = False class SubscriptionResponse(BaseModel): notify_email_summary: bool = False notify_email_new_articles: bool = False notify_email_status_change: bool = False class FeedbackRequest(BaseModel): category: str = Field(pattern="^(bug|feature|question|other)$") message: str = Field(min_length=10, max_length=5000)