154 lines
3.9 KiB
Python
154 lines
3.9 KiB
Python
from datetime import datetime
|
|
from uuid import UUID
|
|
|
|
from pydantic import BaseModel, ConfigDict, Field, model_validator
|
|
|
|
|
|
class InitializeRequest(BaseModel):
|
|
username: str = Field(min_length=3, max_length=64, pattern=r"^[A-Za-z0-9_.-]+$")
|
|
password: str = Field(min_length=12, max_length=256)
|
|
|
|
|
|
class LoginRequest(BaseModel):
|
|
username: str
|
|
password: str
|
|
|
|
|
|
class ChangePasswordRequest(BaseModel):
|
|
current_password: str = Field(min_length=1, max_length=256)
|
|
new_password: str = Field(min_length=12, max_length=256)
|
|
|
|
@model_validator(mode="after")
|
|
def reject_same_password(self):
|
|
if self.current_password == self.new_password:
|
|
raise ValueError("新密码不能与当前密码相同")
|
|
return self
|
|
|
|
|
|
class UserOut(BaseModel):
|
|
model_config = ConfigDict(from_attributes=True)
|
|
id: UUID
|
|
username: str
|
|
|
|
|
|
class SessionOut(BaseModel):
|
|
model_config = ConfigDict(from_attributes=True)
|
|
id: UUID
|
|
current: bool
|
|
created_at: datetime
|
|
last_seen_at: datetime
|
|
expires_at: datetime
|
|
ip_address: str | None
|
|
user_agent: str | None
|
|
|
|
|
|
class NameUpdate(BaseModel):
|
|
name: str = Field(min_length=1, max_length=120)
|
|
|
|
|
|
class FolderCreate(NameUpdate):
|
|
pass
|
|
|
|
|
|
class FolderOut(BaseModel):
|
|
model_config = ConfigDict(from_attributes=True)
|
|
id: UUID
|
|
name: str
|
|
|
|
|
|
class ListCreate(BaseModel):
|
|
name: str = Field(min_length=1, max_length=120)
|
|
folder_id: UUID | None = None
|
|
|
|
|
|
class ListOut(BaseModel):
|
|
model_config = ConfigDict(from_attributes=True)
|
|
id: UUID
|
|
folder_id: UUID | None
|
|
name: str
|
|
is_inbox: bool
|
|
|
|
|
|
|
|
class TaskCreate(BaseModel):
|
|
title: str = Field(min_length=1, max_length=500)
|
|
list_id: UUID
|
|
description: str = ""
|
|
priority: int = Field(default=0, ge=0, le=3)
|
|
due_at: datetime | None = None
|
|
parent_id: UUID | None = None
|
|
rrule: str | None = Field(default=None, min_length=5, max_length=1000)
|
|
|
|
|
|
class TaskUpdate(BaseModel):
|
|
title: str | None = Field(default=None, min_length=1, max_length=500)
|
|
description: str | None = None
|
|
priority: int | None = Field(default=None, ge=0, le=3)
|
|
due_at: datetime | None = None
|
|
completed: bool | None = None
|
|
list_id: UUID | None = None
|
|
version: int = Field(ge=1)
|
|
|
|
@model_validator(mode="after")
|
|
def reject_null_non_nullable_fields(self):
|
|
for field in ("title", "description", "priority", "completed", "list_id"):
|
|
if field in self.model_fields_set and getattr(self, field) is None:
|
|
raise ValueError(f"{field} cannot be null")
|
|
return self
|
|
|
|
|
|
class TaskReorder(BaseModel):
|
|
task_ids: list[UUID] = Field(min_length=1)
|
|
|
|
@model_validator(mode="after")
|
|
def unique_ids(self):
|
|
if len(self.task_ids) != len(set(self.task_ids)):
|
|
raise ValueError("task_ids must be unique")
|
|
return self
|
|
|
|
|
|
class TaskOut(BaseModel):
|
|
model_config = ConfigDict(from_attributes=True)
|
|
id: UUID
|
|
list_id: UUID
|
|
parent_id: UUID | None
|
|
title: str
|
|
description: str
|
|
priority: int
|
|
completed: bool
|
|
due_at: datetime | None
|
|
version: int
|
|
|
|
|
|
class TaskDetailOut(TaskOut):
|
|
subtasks: list[TaskOut] = Field(default_factory=list)
|
|
|
|
|
|
class TaskPage(BaseModel):
|
|
items: list[TaskDetailOut]
|
|
next_cursor: str | None = None
|
|
total: int = 0
|
|
page: int = 1
|
|
page_size: int = 50
|
|
|
|
|
|
class BatchTaskUpdate(BaseModel):
|
|
task_ids: list[UUID] = Field(min_length=1)
|
|
completed: bool | None = None
|
|
list_id: UUID | None = None
|
|
due_at: datetime | None = None
|
|
soft_delete: bool | None = None
|
|
|
|
@model_validator(mode="after")
|
|
def require_operation(self):
|
|
operations = {"completed", "list_id", "due_at", "soft_delete"}
|
|
if not self.model_fields_set.intersection(operations):
|
|
raise ValueError("at least one batch operation is required")
|
|
if self.soft_delete is False:
|
|
raise ValueError("soft_delete can only be true")
|
|
return self
|
|
|
|
|
|
class BatchResult(BaseModel):
|
|
updated: int
|