from datetime import datetime, timedelta, timezone from typing import Literal from pydantic import BaseModel, Field class LoginRequest(BaseModel): username: str password: str class LoginResponse(BaseModel): token: str role: Literal["admin", "sensitive_admin"] = "admin" expires_in_seconds: int = 3600 class RegisterDeviceRequest(BaseModel): device_id: str hostname: str os_version: str agent_version: str class RegisterDeviceResponse(BaseModel): registered: bool = True session_token: str class HeartbeatRequest(BaseModel): device_id: str active_restriction_window: bool uptime_seconds: int = 0 class HeartbeatResponse(BaseModel): accepted: bool = True server_time: datetime = Field(default_factory=lambda: datetime.now(timezone.utc)) class DeviceStatusRow(BaseModel): device_id: str hostname: str agent_version: str os_version: str last_heartbeat_at: datetime | None = None active_restriction_window: bool = False class DeviceStatusResponse(DeviceStatusRow): is_online: bool = False class TimeWindow(BaseModel): day_of_week: Literal["mon", "tue", "wed", "thu", "fri", "sat", "sun"] start: str = Field(description="HH:MM") end: str = Field(description="HH:MM") class DevicePolicy(BaseModel): blocked_executables: list[str] = [] blocked_domains: list[str] = [] restriction_windows: list[TimeWindow] = [] class UpdatePolicyRequest(BaseModel): device_id: str policy: DevicePolicy class ScreenshotIngestResponse(BaseModel): screenshot_id: str queued: bool = True class ViolationScreenshot(BaseModel): screenshot_id: str device_id: str timestamp: str filename: str created_at: str image_url: str confidence: float reason: str visibility_scope: Literal["admin", "sensitive_admin"] = "admin" ocr_urls: list[str] = [] class GameReviewItem(BaseModel): review_id: str screenshot_id: str device_id: str timestamp: str status: Literal["pending", "approved", "rejected"] reason: str ocr_urls: list[str] = [] created_at: datetime class GameReviewDecisionRequest(BaseModel): review_id: str approve_lock: bool class GameReviewDecisionResponse(BaseModel): updated: bool class KeyGenerateRequest(BaseModel): device_id: str class KeyGenerateResponse(BaseModel): device_id: str key: str expires_at: datetime class KeyValidateRequest(BaseModel): device_id: str key: str class KeyValidateResponse(BaseModel): valid: bool lock_until: datetime | None = None class AlertEvent(BaseModel): device_id: str type: Literal[ "wrong_key", "policy_violation", "process_killed", "info", "device_status", "sensitive_detected", "game_lock_review_required", "game_lock_review_decision", ] message: str created_at: datetime = Field(default_factory=lambda: datetime.now(timezone.utc)) def lock_until_5_minutes() -> datetime: return datetime.now(timezone.utc) + timedelta(minutes=5)