from typing import Optional, List, Any, Dict from pydantic import BaseModel, ConfigDict, Field from app.base_schema import CSTDatetime # ==================== SmartTable ==================== class SmartTableCreate(BaseModel): name: str = Field(..., min_length=1, max_length=200, description="表名") icon: str = Field(default="Grid", max_length=50, description="图标") description: Optional[str] = Field(None, description="描述") type: str = Field(default="table", description="类型: table / document") content: Optional[Dict[str, Any]] = Field(None, description="文档内容(Tiptap JSON), 仅 type=document 时使用") parent_id: Optional[str] = Field(None, description="父页面ID") wiki_space_id: Optional[str] = Field(None, description="所属文档库ID") class SmartTableUpdate(BaseModel): name: Optional[str] = Field(None, min_length=1, max_length=200, description="表名") icon: Optional[str] = Field(None, max_length=50, description="图标") description: Optional[str] = Field(None, description="描述") active_view_id: Optional[str] = Field(None, description="当前激活视图ID") content: Optional[Dict[str, Any]] = Field(None, description="文档内容(Tiptap JSON)") parent_id: Optional[str] = Field(None, description="父页面ID") class SmartTableMove(BaseModel): parent_id: Optional[str] = Field(None, description="目标父页面ID,null 表示移到根级") after_id: Optional[str] = Field(None, description="排在此项之后,null 表示排在同级首位") class SmartTableResponse(BaseModel): id: str name: str icon: str = "Grid" description: Optional[str] = None active_view_id: Optional[str] = None type: str = "table" parent_id: Optional[str] = None wiki_space_id: Optional[str] = None sort: int = 0 sys_create_datetime: Optional[CSTDatetime] = None sys_update_datetime: Optional[CSTDatetime] = None model_config = ConfigDict(from_attributes=True) class SmartTableSimple(BaseModel): """列表简要信息(含 parent_id 以便前端构建树)""" id: str name: str icon: str = "Grid" type: str = "table" parent_id: Optional[str] = None wiki_space_id: Optional[str] = None sort: int = 0 model_config = ConfigDict(from_attributes=True) # ==================== SmartField ==================== class SmartFieldCreate(BaseModel): table_id: Optional[str] = Field(None, description="所属表ID(从URL路径注入)") name: str = Field(..., min_length=1, max_length=200, description="字段名") type: str = Field(..., description="字段类型") width: int = Field(default=150, description="列宽") visible: bool = Field(default=True, description="是否可见") required: bool = Field(default=False, description="是否必填") description: Optional[str] = Field(None, description="描述") config: Optional[Dict[str, Any]] = Field(default_factory=dict, description="扩展配置") sort: int = Field(default=0, description="排序") class SmartFieldUpdate(BaseModel): name: Optional[str] = Field(None, min_length=1, max_length=200, description="字段名") type: Optional[str] = Field(None, description="字段类型") width: Optional[int] = Field(None, description="列宽") visible: Optional[bool] = Field(None, description="是否可见") required: Optional[bool] = Field(None, description="是否必填") description: Optional[str] = Field(None, description="描述") config: Optional[Dict[str, Any]] = Field(None, description="扩展配置") sort: Optional[int] = Field(None, description="排序") class SmartFieldResponse(BaseModel): id: str table_id: str name: str type: str width: int = 150 visible: bool = True required: bool = False description: Optional[str] = None config: Optional[Dict[str, Any]] = None sort: int = 0 sys_create_datetime: Optional[CSTDatetime] = None sys_update_datetime: Optional[CSTDatetime] = None model_config = ConfigDict(from_attributes=True) class SmartFieldReorder(BaseModel): field_ids: List[str] = Field(..., description="按顺序排列的字段ID列表") # ==================== SmartRecord ==================== class SmartRecordCreate(BaseModel): table_id: Optional[str] = Field(None, description="所属表ID(从URL路径注入)") values: Dict[str, Any] = Field(default_factory=dict, description="字段值映射") class SmartRecordUpdate(BaseModel): values: Optional[Dict[str, Any]] = Field(None, description="字段值映射") class SmartRecordCellUpdate(BaseModel): """更新单个单元格""" field_id: str = Field(..., description="字段ID") value: Any = Field(None, description="单元格值") class SmartRecordResponse(BaseModel): id: str table_id: str values: Dict[str, Any] = {} sys_create_datetime: Optional[CSTDatetime] = None sys_update_datetime: Optional[CSTDatetime] = None sys_creator_id: Optional[str] = None sys_modifier_id: Optional[str] = None model_config = ConfigDict(from_attributes=True) class SmartRecordBatchDelete(BaseModel): ids: List[str] = Field(..., description="要删除的记录ID列表") class SmartRecordBatchRestore(BaseModel): ids: List[str] = Field(..., description="要恢复的记录ID列表") class TrashRecordResponse(BaseModel): id: str table_id: str values: Dict[str, Any] = {} sys_create_datetime: Optional[CSTDatetime] = None sys_update_datetime: Optional[CSTDatetime] = None sys_creator_id: Optional[str] = None model_config = ConfigDict(from_attributes=True) class TrashListResponse(BaseModel): items: List[TrashRecordResponse] total: int # ==================== SmartView ==================== class SmartViewCreate(BaseModel): table_id: Optional[str] = Field(None, description="所属表ID(从URL路径注入)") name: str = Field(..., min_length=1, max_length=200, description="视图名") type: str = Field(default="grid", description="视图类型") config: Optional[Dict[str, Any]] = Field(default_factory=dict, description="视图配置") sort: int = Field(default=0, description="排序") class SmartViewUpdate(BaseModel): name: Optional[str] = Field(None, min_length=1, max_length=200, description="视图名") type: Optional[str] = Field(None, description="视图类型") config: Optional[Dict[str, Any]] = Field(None, description="视图配置") sort: Optional[int] = Field(None, description="排序") class SmartViewResponse(BaseModel): id: str table_id: str name: str type: str = "grid" config: Optional[Dict[str, Any]] = None sort: int = 0 sys_create_datetime: Optional[CSTDatetime] = None sys_update_datetime: Optional[CSTDatetime] = None model_config = ConfigDict(from_attributes=True) # ==================== Record Query (Server-side filter/sort/search) ==================== class RecordFilterRule(BaseModel): field_id: str = Field(..., description="字段ID") operator: str = Field(..., description="操作符: equals, notEquals, contains, notContains, isEmpty, isNotEmpty, greaterThan, lessThan, greaterThanOrEqual, lessThanOrEqual") value: Any = Field(None, description="筛选值") class RecordSortRule(BaseModel): field_id: str = Field(..., description="字段ID") direction: str = Field(default="asc", description="排序方向: asc / desc") class RecordQuery(BaseModel): filters: List[RecordFilterRule] = Field(default_factory=list, description="筛选条件") filter_logic: str = Field(default="and", description="筛选逻辑: and / or") sorts: List[RecordSortRule] = Field(default_factory=list, description="排序规则") search: Optional[str] = Field(None, description="全文搜索关键词") search_field_ids: Optional[List[str]] = Field(None, description="搜索范围字段ID列表,为空搜索所有文本字段") group_field_id: Optional[str] = Field(None, description="分组字段ID") cursor: Optional[str] = Field(None, description="游标(上一页最后一条记录的ID)") limit: int = Field(default=200, ge=1, le=5000, description="每页数量") class RecordGroupItem(BaseModel): key: str label: str records: List[SmartRecordResponse] = [] class GroupedRecordsResponse(BaseModel): groups: List[RecordGroupItem] total: int class SmartRecordReorder(BaseModel): record_ids: List[str] = Field(..., description="按顺序排列的记录ID列表") # ==================== Batch Cell Update ==================== class SmartRecordBatchCellUpdate(BaseModel): """批量更新多个单元格""" cells: Dict[str, Any] = Field(..., description="fieldId->value 映射") class MultiRecordCellUpdate(BaseModel): """单条记录的更新项""" record_id: str = Field(..., description="记录ID") cells: Dict[str, Any] = Field(..., description="fieldId->value 映射") class MultiRecordBatchUpdate(BaseModel): """批量更新多条记录的单元格 - 合并为单次事务""" updates: List[MultiRecordCellUpdate] = Field(..., description="更新列表", max_length=200) # ==================== Cursor Pagination ==================== class CursorPaginatedRecords(BaseModel): items: List[SmartRecordResponse] total: int next_cursor: Optional[str] = None has_more: bool = False # ==================== Full Table Load ==================== # ==================== Link / Lookup / Rollup ==================== class LinkCellUpdate(BaseModel): """更新 Link 字段的关联记录""" record_ids: List[str] = Field(..., description="目标记录ID列表") class LinkedRecordItem(BaseModel): """关联记录摘要""" id: str title: str = "" class RecordSearchResult(BaseModel): """搜索目标表记录结果""" id: str title: str = "" class RecordSearchQuery(BaseModel): """搜索请求""" keyword: str = Field(default="", description="搜索关键词") limit: int = Field(default=20, ge=1, le=100, description="返回数量") # ==================== Full Table Load ==================== class SmartTableFull(BaseModel): """完整表数据(含字段、首页记录、视图)""" id: str name: str icon: str = "Grid" description: Optional[str] = None active_view_id: Optional[str] = None type: str = "table" parent_id: Optional[str] = None wiki_space_id: Optional[str] = None content: Optional[Dict[str, Any]] = None fields: List[SmartFieldResponse] = [] records: List[SmartRecordResponse] = [] views: List[SmartViewResponse] = [] record_total: int = 0 next_cursor: Optional[str] = None has_more: bool = False sys_create_datetime: Optional[CSTDatetime] = None sys_update_datetime: Optional[CSTDatetime] = None sys_creator_id: Optional[str] = None creator_name: Optional[str] = None creator_avatar: Optional[str] = None model_config = ConfigDict(from_attributes=True) # ==================== Comment ==================== class CommentCreate(BaseModel): content: str = Field(..., min_length=1, max_length=5000, description="评论内容") mentions: List[str] = Field(default_factory=list, description="被@提及的用户ID列表") parent_id: Optional[str] = Field(None, description="父评论ID(回复)") class CommentUpdate(BaseModel): content: str = Field(..., min_length=1, max_length=5000, description="评论内容") mentions: List[str] = Field(default_factory=list, description="被@提及的用户ID列表") class CommentResponse(BaseModel): id: str record_id: str user_id: str content: str mentions: List[str] = [] parent_id: Optional[str] = None sys_create_datetime: Optional[CSTDatetime] = None sys_update_datetime: Optional[CSTDatetime] = None user_name: Optional[str] = None user_avatar: Optional[str] = None replies: List["CommentResponse"] = [] model_config = ConfigDict(from_attributes=True) CommentResponse.model_rebuild() # ==================== Summary / Aggregation ==================== class SummaryRequest(BaseModel): """汇总请求:指定字段及聚合方式""" aggregations: Dict[str, str] = Field( ..., description="fieldId -> aggregation_type 映射,如 {'fld_abc': 'SUM'}" ) filters: List[RecordFilterRule] = Field(default_factory=list, description="筛选条件") filter_logic: str = Field(default="and", description="筛选逻辑: and / or") search: Optional[str] = Field(None, description="搜索关键词") class SummaryResponse(BaseModel): """汇总响应""" summaries: Dict[str, Any] = Field( default_factory=dict, description="fieldId -> 聚合结果,如 {'fld_abc': 12345.67}" ) total_count: int = 0 # ==================== Document ==================== class DocumentContentUpdate(BaseModel): """更新文档内容""" content: Dict[str, Any] = Field(..., description="Tiptap JSON 文档内容") # ==================== Document Version ==================== class DocumentVersionCreate(BaseModel): """手动创建版本""" change_summary: Optional[str] = Field(None, max_length=500, description="变更摘要") class DocumentVersionResponse(BaseModel): id: str document_id: str version: int title: Optional[str] = None change_summary: Optional[str] = None content_size: int = 0 sys_create_datetime: Optional[CSTDatetime] = None sys_creator_id: Optional[str] = None creator_name: Optional[str] = None creator_avatar: Optional[str] = None model_config = ConfigDict(from_attributes=True) class DocumentVersionDetail(DocumentVersionResponse): """版本详情(含完整内容)""" content: Dict[str, Any] = {} class DocumentVersionCompare(BaseModel): """版本对比结果""" version_from: DocumentVersionDetail version_to: DocumentVersionDetail # ==================== Document Template ==================== class DocumentTemplateCreate(BaseModel): name: str = Field(..., min_length=1, max_length=200, description="模板名称") description: Optional[str] = Field(None, description="模板描述") icon: str = Field(default="FileText", max_length=50, description="模板图标") category: str = Field(default="custom", max_length=50, description="分类") content: Dict[str, Any] = Field(..., description="模板内容(Tiptap JSON)") preview_image: Optional[str] = Field(None, description="预览图URL") class DocumentTemplateUpdate(BaseModel): name: Optional[str] = Field(None, min_length=1, max_length=200, description="模板名称") description: Optional[str] = Field(None, description="模板描述") icon: Optional[str] = Field(None, max_length=50, description="模板图标") category: Optional[str] = Field(None, max_length=50, description="分类") content: Optional[Dict[str, Any]] = Field(None, description="模板内容(Tiptap JSON)") preview_image: Optional[str] = Field(None, description="预览图URL") class DocumentTemplateResponse(BaseModel): id: str name: str description: Optional[str] = None icon: str = "FileText" category: str = "custom" preview_image: Optional[str] = None is_system: bool = False use_count: int = 0 sys_create_datetime: Optional[CSTDatetime] = None sys_update_datetime: Optional[CSTDatetime] = None sys_creator_id: Optional[str] = None creator_name: Optional[str] = None model_config = ConfigDict(from_attributes=True) class DocumentTemplateDetail(DocumentTemplateResponse): """模板详情(含完整内容)""" content: Dict[str, Any] = {} # ==================== Wiki Space ==================== class WikiSpaceCreate(BaseModel): name: str = Field(..., min_length=1, max_length=200, description="文档库名称") icon: str = Field(default="BookOpen", max_length=50, description="图标") avatar: Optional[str] = Field(None, description="头像文件ID") description: Optional[str] = Field(None, description="描述") cover: Optional[str] = Field(None, description="封面图URL") category: str = Field(default="default", max_length=50, description="分类标签") visibility: str = Field(default="private", description="可见性: private/team/public") class WikiSpaceUpdate(BaseModel): name: Optional[str] = Field(None, min_length=1, max_length=200, description="文档库名称") icon: Optional[str] = Field(None, max_length=50, description="图标") avatar: Optional[str] = Field(None, description="头像文件ID") description: Optional[str] = Field(None, description="描述") cover: Optional[str] = Field(None, description="封面图URL") category: Optional[str] = Field(None, max_length=50, description="分类标签") visibility: Optional[str] = Field(None, description="可见性: private/team/public") class WikiSpaceResponse(BaseModel): id: str name: str icon: str = "BookOpen" avatar: Optional[str] = None description: Optional[str] = None cover: Optional[str] = None category: str = "default" visibility: str = "private" sort: int = 0 sys_create_datetime: Optional[CSTDatetime] = None sys_update_datetime: Optional[CSTDatetime] = None sys_creator_id: Optional[str] = None creator_name: Optional[str] = None model_config = ConfigDict(from_attributes=True) class WikiSpaceListItem(WikiSpaceResponse): """列表项,含文档数量""" document_count: int = 0 class WikiSpaceDetail(WikiSpaceResponse): """详情,含文档列表""" documents: List[SmartTableSimple] = []