"""Request bodies. Responses are plain dicts built by the services.""" from __future__ import annotations from datetime import date from typing import Any, Literal from pydantic import BaseModel, Field class StartTimerRequest(BaseModel): kind: Literal["work", "short_break", "long_break"] | None = None minutes: int | None = Field(default=None, ge=1, le=240) task_id: int | None = None note: str | None = None class AbandonRequest(BaseModel): reason: str | None = None class ExtendRequest(BaseModel): minutes: int = Field(default=5, ge=1, le=120) class AttachTaskRequest(BaseModel): task_id: int | None = None class CompleteTimerRequest(BaseModel): auto_next: bool = True session_id: int | None = None """Which interval the client believes it is finishing. Every client ticks its own local countdown and calls `complete` when it reaches zero, so with two of them open (the phone and the desktop, say) both fire within the same second. Unconditionally, the second call lands on whatever `auto_next` has just started and ends *that* — which is why a break could vanish the instant it began. Sent along, a stale call is a no-op instead. Omitted, the old unconditional behaviour stands. """ class TaskCreateRequest(BaseModel): title: str = Field(min_length=1, max_length=500) description: str | None = None due_date: date | None = None priority: str | None = None project: str | None = None estimated_pomodoros: int | None = Field(default=None, ge=1, le=50) class TaskUpdateRequest(BaseModel): title: str | None = None description: str | None = None due_date: date | None = None priority: str | None = None project: str | None = None estimated_pomodoros: int | None = None pinned: bool | None = None class BlockEventRequest(BaseModel): kind: Literal["domain", "app"] = "domain" target: str = Field(min_length=1, max_length=255) action: Literal["blocked", "killed", "warned"] = "blocked" details: str | None = None class SendReportRequest(BaseModel): report_date: date | None = None force: bool = True class TestNotificationRequest(BaseModel): text: str | None = None class SettingsPatch(BaseModel): """A partial settings document; anything omitted keeps its stored value.""" model_config = {"extra": "allow"} def as_dict(self) -> dict[str, Any]: return self.model_dump(exclude_unset=True)