# plugins/http/models.py from __future__ import annotations import uuid from datetime import datetime, timezone from sqlalchemy import Boolean, DateTime, Float, Integer, String, Text from sqlalchemy.orm import Mapped, mapped_column from fabledscryer.models.base import Base class HttpMonitor(Base): """A configured HTTP endpoint to check periodically.""" __tablename__ = "http_monitors" id: Mapped[str] = mapped_column( String(36), primary_key=True, default=lambda: str(uuid.uuid4()) ) name: Mapped[str] = mapped_column(String(128), nullable=False) url: Mapped[str] = mapped_column(String(2048), nullable=False) method: Mapped[str] = mapped_column(String(8), nullable=False, default="GET") expected_status: Mapped[int] = mapped_column(Integer, nullable=False, default=200) # Optional substring to find in the response body (empty = skip check) content_match: Mapped[str] = mapped_column(String(512), nullable=False, default="") # JSON dict of extra request headers, e.g. {"Authorization": "Bearer ..."} headers_json: Mapped[str] = mapped_column(Text, nullable=False, default="{}") timeout_seconds: Mapped[int] = mapped_column(Integer, nullable=False, default=10) # 0 = use global plugin check_interval_seconds check_interval_seconds: Mapped[int] = mapped_column(Integer, nullable=False, default=0) follow_redirects: Mapped[bool] = mapped_column(Boolean, nullable=False, default=True) verify_ssl: Mapped[bool] = mapped_column(Boolean, nullable=False, default=True) enabled: Mapped[bool] = mapped_column(Boolean, nullable=False, default=True) # Updated after each check so the scheduler can compute next-run time efficiently last_checked_at: Mapped[datetime | None] = mapped_column( DateTime(timezone=True), nullable=True ) created_at: Mapped[datetime] = mapped_column( DateTime(timezone=True), nullable=False, default=lambda: datetime.now(timezone.utc), ) class HttpResult(Base): """One check result for an HttpMonitor.""" __tablename__ = "http_results" id: Mapped[str] = mapped_column( String(36), primary_key=True, default=lambda: str(uuid.uuid4()) ) monitor_id: Mapped[str] = mapped_column(String(36), nullable=False, index=True) checked_at: Mapped[datetime] = mapped_column( DateTime(timezone=True), nullable=False, default=lambda: datetime.now(timezone.utc), index=True, ) status_code: Mapped[int | None] = mapped_column(Integer, nullable=True) response_ms: Mapped[float | None] = mapped_column(Float, nullable=True) is_up: Mapped[bool] = mapped_column(Boolean, nullable=False, default=False) # None when no content_match is configured; True/False when it is content_matched: Mapped[bool | None] = mapped_column(Boolean, nullable=True) error_msg: Mapped[str | None] = mapped_column(String(512), nullable=True) # TLS expiry if the endpoint is HTTPS; None for HTTP or on error tls_expires_at: Mapped[datetime | None] = mapped_column( DateTime(timezone=True), nullable=True )