diff --git a/app/models.py b/app/models.py index 3d2910a..237a4ea 100644 --- a/app/models.py +++ b/app/models.py @@ -1,51 +1,65 @@ -from datetime import datetime -from typing import Literal +from datetime import datetime, timezone +from enum import Enum +from typing import Annotated from uuid import UUID -from pydantic import AnyHttpUrl, BaseModel, ConfigDict, Field, model_validator +from pydantic import AnyHttpUrl, BaseModel, ConfigDict, Field, StringConstraints, field_validator -StatusState = Literal["unknown", "up", "down", "error"] +Name = Annotated[str, StringConstraints(strip_whitespace=True, min_length=1, max_length=100)] -class CheckStatus(BaseModel): - state: StatusState = "unknown" - checked_at: datetime | None = None - status_code: int | None = None - latency_ms: float | None = Field(default=None, ge=0) - error: str | None = None +def utcnow() -> datetime: + return datetime.now(timezone.utc) + + +class MonitorStatus(str, Enum): + unknown = "unknown" + up = "up" + down = "down" + error = "error" class MonitorCreate(BaseModel): - name: str = Field(min_length=1, max_length=200) + name: Name url: AnyHttpUrl + @field_validator("url") + @classmethod + def no_credentials(cls, value: AnyHttpUrl) -> AnyHttpUrl: + if value.username or value.password: + raise ValueError("URL credentials are not allowed") + return value -class MonitorUpdate(BaseModel): - name: str | None = Field(default=None, min_length=1, max_length=200) - url: AnyHttpUrl | None = None - @model_validator(mode="after") - def not_empty(self) -> "MonitorUpdate": - if self.name is None and self.url is None: - raise ValueError("at least one field is required") - return self +class MonitorUpdate(MonitorCreate): + pass class Monitor(BaseModel): - model_config = ConfigDict(from_attributes=True) - + model_config = ConfigDict(frozen=True) id: UUID name: str url: AnyHttpUrl + status: MonitorStatus = MonitorStatus.unknown created_at: datetime updated_at: datetime - status: CheckStatus + checked_at: datetime | None = None + latency_ms: float | None = Field(default=None, ge=0) + http_status: int | None = Field(default=None, ge=100, le=599) + error: str | None = None -class CheckResult(CheckStatus): - monitor_id: UUID - status_applied: bool = True +class CheckResult(BaseModel): + status: MonitorStatus + checked_at: datetime + latency_ms: float = Field(ge=0) + http_status: int | None = None + error: str | None = None -class HealthResponse(BaseModel): - status: Literal["ok", "ready"] +class StatusResponse(CheckResult): + id: UUID + + +class ProbeResponse(BaseModel): + status: str