refactor: 重整 schemas 作用域并统一用户上下文模型

This commit is contained in:
zl-q
2026-03-13 01:01:54 +08:00
parent f201babb48
commit fb3c649db7
42 changed files with 4205 additions and 2013 deletions
+5 -8
View File
@@ -1,15 +1,12 @@
from __future__ import annotations
from datetime import datetime
from typing import ClassVar, Literal
from uuid import UUID
from pydantic import BaseModel, ConfigDict, Field
class UserBasicInfo(BaseModel):
id: str
username: str
avatar_url: str | None = None
from schemas.user.context import UserContext
class FriendRequestCreate(BaseModel):
@@ -21,8 +18,8 @@ class FriendRequestCreate(BaseModel):
class FriendRequestResponse(BaseModel):
id: UUID
sender: UserBasicInfo
recipient: UserBasicInfo
sender: UserContext
recipient: UserContext
content: str | None
status: Literal["pending", "accepted", "rejected", "canceled"]
created_at: datetime
@@ -30,7 +27,7 @@ class FriendRequestResponse(BaseModel):
class FriendResponse(BaseModel):
id: UUID
friend: UserBasicInfo
friend: UserContext
status: Literal["active"]
created_at: datetime
accepted_at: datetime | None
+5 -5
View File
@@ -22,7 +22,7 @@ from v1.users.repository import UserRepository
if TYPE_CHECKING:
from sqlalchemy.ext.asyncio import AsyncSession
from v1.friendships.schemas import UserBasicInfo
from schemas.user.context import UserContext
logger = get_logger("v1.friendships.service")
@@ -555,14 +555,14 @@ class FriendshipService(BaseService):
accepted_at=friendship.updated_at,
)
def _build_user_basic_info(self, profile: Any) -> "UserBasicInfo":
from v1.friendships.schemas import UserBasicInfo
def _build_user_basic_info(self, profile: Any) -> "UserContext":
from schemas.user.context import UserContext
if profile is None:
return UserBasicInfo(id="", username="")
return UserContext(id="", username="")
p = profile # type: ignore[assignment]
return UserBasicInfo(
return UserContext(
id=str(p.id),
username=p.username,
avatar_url=p.avatar_url if hasattr(p, "avatar_url") else None,
+1 -14
View File
@@ -1,25 +1,12 @@
from __future__ import annotations
from datetime import datetime
from enum import Enum
from typing import ClassVar
from uuid import UUID
from pydantic import BaseModel, ConfigDict
class InboxMessageType(str, Enum):
FRIEND_REQUEST = "friend_request"
CALENDAR = "calendar"
SYSTEM = "system"
GROUP = "group"
class InboxMessageStatus(str, Enum):
PENDING = "pending"
ACCEPTED = "accepted"
REJECTED = "rejected"
DISMISSED = "dismissed"
from schemas.inbox.messages import InboxMessageStatus, InboxMessageType
class InboxMessageResponse(BaseModel):
+35 -92
View File
@@ -1,52 +1,43 @@
from __future__ import annotations
import json
from datetime import datetime
from enum import Enum
from typing import Literal, ClassVar, Union
from typing import ClassVar
from uuid import UUID
from pydantic import BaseModel, ConfigDict, EmailStr, Field
from schemas.schedule.items import (
AttachmentType,
CalendarContent,
CalendarDeleteContent,
CalendarInviteContent,
CalendarUpdateContent,
ScheduleItemMetadata,
ScheduleItemMetadataAttachment,
ScheduleItemSourceType,
ScheduleItemStatus,
parse_calendar_content,
)
class AttachmentType(str, Enum):
DOCUMENT = "document"
REMINDER = "reminder"
class ScheduleItemMetadataAttachment(BaseModel):
model_config: ClassVar[ConfigDict] = ConfigDict(extra="forbid")
name: str
type: AttachmentType
visible_to: list[UUID] = Field(default_factory=list)
url: str | None = None
note: str | None = None
content: str | None = None
class ScheduleItemMetadata(BaseModel):
model_config: ClassVar[ConfigDict] = ConfigDict(extra="forbid")
color: str | None = Field(default=None, pattern=r"^#[0-9A-Fa-f]{6}$")
location: str | None = None
notes: str | None = None
attachments: list[ScheduleItemMetadataAttachment] = Field(default_factory=list)
reminder_minutes: int | None = Field(default=None, ge=0, le=10080)
version: Literal[1] = 1
class ScheduleItemStatus(str, Enum):
ACTIVE = "active"
COMPLETED = "completed"
CANCELED = "canceled"
ARCHIVED = "archived"
class ScheduleItemSourceType(str, Enum):
MANUAL = "manual"
IMPORTED = "imported"
AGENT_GENERATED = "agent_generated"
__all__ = [
"AttachmentType",
"CalendarContent",
"CalendarDeleteContent",
"CalendarInviteContent",
"CalendarUpdateContent",
"ScheduleItemMetadata",
"ScheduleItemMetadataAttachment",
"ScheduleItemSourceType",
"ScheduleItemStatus",
"parse_calendar_content",
"ScheduleItemCreateRequest",
"ScheduleItemUpdateRequest",
"ScheduleItemResponse",
"ScheduleItemListItem",
"ScheduleItemListRequest",
"ScheduleItemShareRequest",
"ScheduleItemShareResponse",
]
class ScheduleItemCreateRequest(BaseModel):
@@ -107,10 +98,9 @@ class ScheduleItemListRequest(BaseModel):
end_at: datetime
# Permission bit constants (matching PermissionBits in inbox_messages/schemas.py)
_PERMISSION_VIEW = 1 # 001
_PERMISSION_INVITE = 2 # 010
_PERMISSION_EDIT = 4 # 100
_PERMISSION_VIEW = 1
_PERMISSION_INVITE = 2
_PERMISSION_EDIT = 4
class ScheduleItemShareRequest(BaseModel):
@@ -134,50 +124,3 @@ class ScheduleItemShareRequest(BaseModel):
class ScheduleItemShareResponse(BaseModel):
message: str
class CalendarInviteContent(BaseModel):
model_config: ClassVar[ConfigDict] = ConfigDict(extra="forbid")
type: Literal["invite"]
permission: int = Field(..., description="权限: 1=view, 4=edit, 8=invite")
action: Literal["pending"] = "pending"
class CalendarUpdateContent(BaseModel):
model_config: ClassVar[ConfigDict] = ConfigDict(extra="forbid")
type: Literal["update"]
title: str = Field(..., description="事件标题")
action: Literal["updated"] = "updated"
class CalendarDeleteContent(BaseModel):
model_config: ClassVar[ConfigDict] = ConfigDict(extra="forbid")
type: Literal["delete"]
title: str = Field(..., description="事件标题")
action: Literal["deleted"] = "deleted"
CalendarContent = Union[
CalendarInviteContent, CalendarUpdateContent, CalendarDeleteContent
]
def parse_calendar_content(content: str | None) -> CalendarContent | None:
if not content:
return None
try:
data = json.loads(content)
content_type = data.get("type")
if content_type == "invite":
return CalendarInviteContent(**data)
elif content_type == "update":
return CalendarUpdateContent(**data)
elif content_type == "delete":
return CalendarDeleteContent(**data)
else:
raise ValueError(f"Unknown calendar content type: {content_type}")
except Exception:
return None
+3 -3
View File
@@ -5,10 +5,10 @@ from uuid import UUID
from fastapi import APIRouter, Depends
from schemas.user.context import UserContext
from v1.users.dependencies import get_user_service
from v1.users.schemas import UserResponse, UserSearchRequest, UserUpdateRequest
from v1.users.service import UserService
from v1.friendships.schemas import UserBasicInfo
router = APIRouter(prefix="/users", tags=["users"])
@@ -37,9 +37,9 @@ async def search_users(
return await service.search_users(payload)
@router.get("/{user_id}", response_model=UserBasicInfo)
@router.get("/{user_id}", response_model=UserContext)
async def get_user(
user_id: UUID,
service: Annotated[UserService, Depends(get_user_service)],
) -> UserBasicInfo:
) -> UserContext:
return await service.get_user_by_id(user_id)
+4 -4
View File
@@ -19,8 +19,8 @@ from v1.users.schemas import UserResponse, UserSearchRequest, UserUpdateRequest
if TYPE_CHECKING:
from sqlalchemy.ext.asyncio import AsyncSession
from schemas.user.context import UserContext
from v1.auth.schemas import UserByEmailResponse
from v1.friendships.schemas import UserBasicInfo
logger = get_logger("v1.users.service")
@@ -100,8 +100,8 @@ class UserService(BaseService):
bio=user.bio,
)
async def get_user_by_id(self, user_id: UUID) -> "UserBasicInfo":
from v1.friendships.schemas import UserBasicInfo
async def get_user_by_id(self, user_id: UUID) -> "UserContext":
from schemas.user.context import UserContext
try:
profile = await self._repository.get_by_user_id(user_id)
@@ -110,7 +110,7 @@ class UserService(BaseService):
if profile is None:
raise HTTPException(status_code=404, detail="User not found")
return UserBasicInfo(
return UserContext(
id=str(profile.id),
username=profile.username,
avatar_url=profile.avatar_url,