from __future__ import annotations from datetime import date, datetime from enum import Enum from typing import Annotated, Literal from pydantic import BaseModel, ConfigDict, Field, field_validator, model_validator class MetricKind(str, Enum): checkin = "checkin" quantity = "quantity" duration = "duration" class MetricPeriod(str, Enum): day = "day" week = "week" month = "month" lifetime = "lifetime" class MetricInput(BaseModel): model_config = ConfigDict(extra="forbid", str_strip_whitespace=True) metricId: str | None = None name: Annotated[str, Field(min_length=1, max_length=60)] kind: MetricKind period: MetricPeriod = MetricPeriod.day target: Annotated[int | None, Field(ge=1)] = None unit: Annotated[str, Field(max_length=20)] = "" @model_validator(mode="after") def normalize_metric(self) -> "MetricInput": if self.kind == MetricKind.checkin: self.target = 1 if self.target is None else self.target self.unit = self.unit or "次" elif self.kind == MetricKind.duration: self.unit = "秒" return self class TaskInput(BaseModel): model_config = ConfigDict(extra="forbid", str_strip_whitespace=True) name: Annotated[str, Field(min_length=1, max_length=120)] description: Annotated[str, Field(max_length=2000)] = "" startDate: date endDate: date | None = None priority: Annotated[int, Field(ge=1, le=5)] = 3 state: Literal["active", "paused"] = "active" metrics: Annotated[list[MetricInput], Field(min_length=1, max_length=20)] @model_validator(mode="after") def validate_dates_and_metrics(self) -> "TaskInput": if self.endDate and self.endDate < self.startDate: raise ValueError("结束日期不能早于开始日期") ids = [metric.metricId for metric in self.metrics if metric.metricId] if len(ids) != len(set(ids)): raise ValueError("metricId 不能重复") return self class TaskUpdate(TaskInput): expectedVersion: Annotated[int, Field(ge=1)] class ReorderInput(BaseModel): model_config = ConfigDict(extra="forbid") taskIds: list[str] expectedVersion: Annotated[int, Field(ge=1)] @field_validator("taskIds") @classmethod def unique_task_ids(cls, value: list[str]) -> list[str]: if len(value) != len(set(value)): raise ValueError("taskIds 不能重复") return value class MetricValue(BaseModel): model_config = ConfigDict(extra="forbid") metricId: str value: Annotated[int, Field(ge=0)] class DailyRecordInput(BaseModel): model_config = ConfigDict(extra="forbid", str_strip_whitespace=True) expectedVersion: Annotated[int | None, Field(ge=0)] = None note: Annotated[str, Field(max_length=4000)] = "" completedAt: datetime | None = None values: list[MetricValue] = Field(default_factory=list) @field_validator("values") @classmethod def unique_metric_values(cls, value: list[MetricValue]) -> list[MetricValue]: ids = [item.metricId for item in value] if len(ids) != len(set(ids)): raise ValueError("同一指标只能提交一次") return value class LoginInput(BaseModel): model_config = ConfigDict(extra="forbid") password: Annotated[str, Field(min_length=1, max_length=500)] class PurgeInput(BaseModel): model_config = ConfigDict(extra="forbid") confirmation: str