2026-02-09 15:49:50 +05:30
|
|
|
"""Schemas for board create/update/read API operations."""
|
|
|
|
|
|
2026-02-04 02:28:51 +05:30
|
|
|
from __future__ import annotations
|
|
|
|
|
|
2026-02-09 15:49:50 +05:30
|
|
|
from datetime import datetime # noqa: TCH003
|
2026-02-06 16:12:04 +05:30
|
|
|
from typing import Self
|
2026-02-09 15:49:50 +05:30
|
|
|
from uuid import UUID # noqa: TCH003
|
2026-02-04 02:28:51 +05:30
|
|
|
|
2026-02-05 14:40:48 +05:30
|
|
|
from pydantic import model_validator
|
2026-02-04 02:28:51 +05:30
|
|
|
from sqlmodel import SQLModel
|
|
|
|
|
|
2026-02-09 15:49:50 +05:30
|
|
|
_ERR_GOAL_FIELDS_REQUIRED = (
|
|
|
|
|
"Confirmed goal boards require objective and success_metrics"
|
|
|
|
|
)
|
|
|
|
|
_ERR_GATEWAY_REQUIRED = "gateway_id is required"
|
|
|
|
|
|
2026-02-04 02:28:51 +05:30
|
|
|
|
|
|
|
|
class BoardBase(SQLModel):
|
2026-02-09 15:49:50 +05:30
|
|
|
"""Shared board fields used across create and read payloads."""
|
|
|
|
|
|
2026-02-04 02:28:51 +05:30
|
|
|
name: str
|
|
|
|
|
slug: str
|
2026-02-04 23:07:22 +05:30
|
|
|
gateway_id: UUID | None = None
|
2026-02-07 20:29:50 +05:30
|
|
|
board_group_id: UUID | None = None
|
2026-02-05 14:40:48 +05:30
|
|
|
board_type: str = "goal"
|
|
|
|
|
objective: str | None = None
|
|
|
|
|
success_metrics: dict[str, object] | None = None
|
|
|
|
|
target_date: datetime | None = None
|
|
|
|
|
goal_confirmed: bool = False
|
|
|
|
|
goal_source: str | None = None
|
2026-02-04 02:28:51 +05:30
|
|
|
|
|
|
|
|
|
|
|
|
|
class BoardCreate(BoardBase):
|
2026-02-09 15:49:50 +05:30
|
|
|
"""Payload for creating a board."""
|
|
|
|
|
|
2026-02-06 16:12:04 +05:30
|
|
|
gateway_id: UUID
|
|
|
|
|
|
2026-02-05 14:40:48 +05:30
|
|
|
@model_validator(mode="after")
|
2026-02-06 16:12:04 +05:30
|
|
|
def validate_goal_fields(self) -> Self:
|
2026-02-09 15:49:50 +05:30
|
|
|
"""Require goal details when creating a confirmed goal board."""
|
|
|
|
|
if (
|
|
|
|
|
self.board_type == "goal"
|
|
|
|
|
and self.goal_confirmed
|
|
|
|
|
and (not self.objective or not self.success_metrics)
|
|
|
|
|
):
|
|
|
|
|
raise ValueError(_ERR_GOAL_FIELDS_REQUIRED)
|
2026-02-05 14:40:48 +05:30
|
|
|
return self
|
2026-02-04 02:28:51 +05:30
|
|
|
|
|
|
|
|
|
|
|
|
|
class BoardUpdate(SQLModel):
|
2026-02-09 15:49:50 +05:30
|
|
|
"""Payload for partial board updates."""
|
|
|
|
|
|
2026-02-04 02:28:51 +05:30
|
|
|
name: str | None = None
|
|
|
|
|
slug: str | None = None
|
2026-02-04 23:07:22 +05:30
|
|
|
gateway_id: UUID | None = None
|
2026-02-07 20:29:50 +05:30
|
|
|
board_group_id: UUID | None = None
|
2026-02-05 14:40:48 +05:30
|
|
|
board_type: str | None = None
|
|
|
|
|
objective: str | None = None
|
|
|
|
|
success_metrics: dict[str, object] | None = None
|
|
|
|
|
target_date: datetime | None = None
|
|
|
|
|
goal_confirmed: bool | None = None
|
|
|
|
|
goal_source: str | None = None
|
2026-02-04 02:28:51 +05:30
|
|
|
|
2026-02-06 16:12:04 +05:30
|
|
|
@model_validator(mode="after")
|
|
|
|
|
def validate_gateway_id(self) -> Self:
|
2026-02-09 15:49:50 +05:30
|
|
|
"""Reject explicit null gateway IDs in patch payloads."""
|
2026-02-06 16:12:04 +05:30
|
|
|
# Treat explicit null like "unset" is invalid for patch updates.
|
|
|
|
|
if "gateway_id" in self.model_fields_set and self.gateway_id is None:
|
2026-02-09 15:49:50 +05:30
|
|
|
raise ValueError(_ERR_GATEWAY_REQUIRED)
|
2026-02-06 16:12:04 +05:30
|
|
|
return self
|
|
|
|
|
|
2026-02-04 02:28:51 +05:30
|
|
|
|
|
|
|
|
class BoardRead(BoardBase):
|
2026-02-09 15:49:50 +05:30
|
|
|
"""Board payload returned from read endpoints."""
|
|
|
|
|
|
2026-02-04 02:28:51 +05:30
|
|
|
id: UUID
|
2026-02-08 21:16:26 +05:30
|
|
|
organization_id: UUID
|
2026-02-04 02:28:51 +05:30
|
|
|
created_at: datetime
|
|
|
|
|
updated_at: datetime
|