148 lines
5.4 KiB
Python
148 lines
5.4 KiB
Python
"""Pydantic schemas for request/response validation.
|
|
|
|
All models are plain Pydantic BaseModel (not SQLModel) so they can be
|
|
used purely for API I/O without DB-session side-effects.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from datetime import datetime
|
|
from typing import Any, Generic, List, Optional, TypeVar
|
|
from uuid import UUID
|
|
|
|
from pydantic import BaseModel, Field
|
|
|
|
|
|
# ──────────────────────────────────────────────────────────────────────
|
|
# User
|
|
# ──────────────────────────────────────────────────────────────────────
|
|
|
|
class UserCreate(BaseModel):
|
|
email: str
|
|
password: str = Field(min_length=8)
|
|
username: str = Field(min_length=3, max_length=50)
|
|
first_name: str
|
|
last_name: str
|
|
phone: Optional[str] = None
|
|
|
|
|
|
class UserLogin(BaseModel):
|
|
email: str
|
|
password: str
|
|
|
|
|
|
class UserResponse(BaseModel):
|
|
id: UUID
|
|
email: str
|
|
username: str
|
|
first_name: str
|
|
last_name: str
|
|
phone: Optional[str] = None
|
|
role: str
|
|
avatar_url: Optional[str] = None
|
|
is_active: bool
|
|
created_at: datetime
|
|
|
|
model_config = {"from_attributes": True}
|
|
|
|
|
|
# ──────────────────────────────────────────────────────────────────────
|
|
# Auth / Token
|
|
# ──────────────────────────────────────────────────────────────────────
|
|
|
|
class TokenResponse(BaseModel):
|
|
access_token: str
|
|
refresh_token: str
|
|
token_type: str = "bearer"
|
|
|
|
|
|
# ──────────────────────────────────────────────────────────────────────
|
|
# Sensor
|
|
# ──────────────────────────────────────────────────────────────────────
|
|
|
|
class SensorResponse(BaseModel):
|
|
id: UUID
|
|
name: str
|
|
type: str
|
|
status: str
|
|
latitude: float
|
|
longitude: float
|
|
zone_id: Optional[UUID] = None
|
|
last_value: Optional[float] = None
|
|
last_reading_at: Optional[datetime] = None
|
|
battery_level: Optional[int] = None
|
|
|
|
model_config = {"from_attributes": True}
|
|
|
|
|
|
class SensorReadingResponse(BaseModel):
|
|
id: UUID
|
|
sensor_id: UUID
|
|
value: float
|
|
unit: str
|
|
quality: float
|
|
recorded_at: datetime
|
|
|
|
model_config = {"from_attributes": True}
|
|
|
|
|
|
# ──────────────────────────────────────────────────────────────────────
|
|
# Zone
|
|
# ──────────────────────────────────────────────────────────────────────
|
|
|
|
class ZoneResponse(BaseModel):
|
|
id: UUID
|
|
name: str
|
|
description: Optional[str] = None
|
|
color: str
|
|
geojson: Optional[str] = None
|
|
|
|
model_config = {"from_attributes": True}
|
|
|
|
|
|
# ──────────────────────────────────────────────────────────────────────
|
|
# Alert
|
|
# ──────────────────────────────────────────────────────────────────────
|
|
|
|
class AlertResponse(BaseModel):
|
|
id: UUID
|
|
sensor_id: UUID
|
|
type: str
|
|
severity: str
|
|
message: str
|
|
value: Optional[float] = None
|
|
threshold: Optional[float] = None
|
|
status: str
|
|
created_at: datetime
|
|
|
|
model_config = {"from_attributes": True}
|
|
|
|
|
|
# ──────────────────────────────────────────────────────────────────────
|
|
# Dashboard
|
|
# ──────────────────────────────────────────────────────────────────────
|
|
|
|
class DashboardStats(BaseModel):
|
|
total_sensors: int
|
|
active_sensors: int
|
|
total_readings_24h: int
|
|
active_alerts: int
|
|
avg_air_quality: Optional[float] = None
|
|
avg_temperature: Optional[float] = None
|
|
avg_humidity: Optional[float] = None
|
|
|
|
|
|
# ──────────────────────────────────────────────────────────────────────
|
|
# Pagination (generic)
|
|
# ──────────────────────────────────────────────────────────────────────
|
|
|
|
T = TypeVar("T")
|
|
|
|
|
|
class PaginatedResponse(BaseModel, Generic[T]):
|
|
items: List[T]
|
|
total: int
|
|
page: int
|
|
page_size: int
|
|
pages: int
|