Initial commit: AegisSight-Monitor (OSINT-Monitoringsystem)
Dieser Commit ist enthalten in:
245
src/models.py
Normale Datei
245
src/models.py
Normale Datei
@@ -0,0 +1,245 @@
|
||||
"""Pydantic Models für Request/Response Schemas."""
|
||||
from pydantic import BaseModel, Field
|
||||
from typing import Optional
|
||||
from datetime import datetime
|
||||
|
||||
|
||||
# Auth (Legacy)
|
||||
class LoginRequest(BaseModel):
|
||||
username: str
|
||||
password: str
|
||||
|
||||
|
||||
# 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 UserResponse(BaseModel):
|
||||
id: int
|
||||
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
|
||||
|
||||
|
||||
# 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
|
||||
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
|
||||
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
|
||||
created_by: int
|
||||
created_by_username: str = ""
|
||||
created_at: str
|
||||
updated_at: str
|
||||
article_count: int = 0
|
||||
source_count: int = 0
|
||||
|
||||
|
||||
# Articles
|
||||
class ArticleResponse(BaseModel):
|
||||
id: int
|
||||
incident_id: int
|
||||
headline: str
|
||||
headline_de: Optional[str]
|
||||
source: str
|
||||
source_url: Optional[str]
|
||||
content_original: Optional[str]
|
||||
content_de: Optional[str]
|
||||
language: str
|
||||
published_at: Optional[str]
|
||||
collected_at: str
|
||||
verification_status: str
|
||||
|
||||
|
||||
# Fact Checks
|
||||
class FactCheckResponse(BaseModel):
|
||||
id: int
|
||||
incident_id: int
|
||||
claim: str
|
||||
status: str
|
||||
sources_count: int
|
||||
evidence: Optional[str]
|
||||
is_notification: bool
|
||||
checked_at: str
|
||||
|
||||
|
||||
# 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)$")
|
||||
category: str = Field(default="sonstige", pattern="^(nachrichtenagentur|oeffentlich-rechtlich|qualitaetszeitung|behoerde|fachmedien|think-tank|international|regional|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)$")
|
||||
category: Optional[str] = Field(default=None, pattern="^(nachrichtenagentur|oeffentlich-rechtlich|qualitaetszeitung|behoerde|fachmedien|think-tank|international|regional|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
|
||||
|
||||
|
||||
# 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 (Sperren/Entsperren)
|
||||
class DomainActionRequest(BaseModel):
|
||||
domain: str = Field(min_length=1, max_length=200)
|
||||
notes: Optional[str] = None
|
||||
|
||||
|
||||
# Refresh-Log
|
||||
class RefreshLogResponse(BaseModel):
|
||||
id: int
|
||||
started_at: str
|
||||
completed_at: Optional[str] = None
|
||||
articles_found: int = 0
|
||||
status: str
|
||||
trigger_type: str = "manual"
|
||||
retry_count: int = 0
|
||||
error_message: Optional[str] = None
|
||||
duration_seconds: Optional[float] = 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)
|
||||
|
||||
|
||||
class WSMessage(BaseModel):
|
||||
type: str # new_article, status_update, notification, refresh_complete
|
||||
incident_id: int
|
||||
data: dict
|
||||
In neuem Issue referenzieren
Einen Benutzer sperren