feat: 增强日历功能并集成 AgentScope 代理服务

This commit is contained in:
qzl
2026-03-11 15:28:29 +08:00
parent e55e445906
commit e20e7d2a02
85 changed files with 5175 additions and 885 deletions
+56 -3
View File
@@ -1,5 +1,6 @@
from __future__ import annotations
from datetime import datetime, timezone
from typing import TYPE_CHECKING, Protocol
from uuid import UUID
@@ -21,11 +22,20 @@ class FriendshipRepository(Protocol):
"""Protocol defining the friendship repository interface."""
async def create_request(
self, initiator_id: UUID, recipient_id: UUID
self, initiator_id: UUID, recipient_id: UUID, content: str | None = None
) -> tuple[Friendship, InboxMessage]:
"""Create a friendship request and inbox message."""
...
async def reactivate_request(
self,
friendship: Friendship,
initiator_id: UUID,
content: str | None = None,
) -> tuple[Friendship, InboxMessage]:
"""Reactivate a declined or canceled friendship request."""
...
async def get_friendship_between_users(
self, user_id_1: UUID, user_id_2: UUID
) -> Friendship | None:
@@ -70,18 +80,21 @@ class SQLAlchemyFriendshipRepository(BaseRepository[Friendship]):
super().__init__(session, Friendship)
async def create_request(
self, initiator_id: UUID, recipient_id: UUID
self, initiator_id: UUID, recipient_id: UUID, content: str | None = None
) -> tuple[Friendship, InboxMessage]:
try:
user_low_id = min(initiator_id, recipient_id)
user_high_id = max(initiator_id, recipient_id)
now = datetime.now(timezone.utc)
friendship = Friendship(
user_low_id=user_low_id,
user_high_id=user_high_id,
initiator_id=initiator_id,
status=FriendshipStatus.PENDING,
requested_at=UUID(int=0),
requested_at=now,
created_by=initiator_id,
updated_by=initiator_id,
)
self._session.add(friendship)
await self._session.flush()
@@ -91,7 +104,9 @@ class SQLAlchemyFriendshipRepository(BaseRepository[Friendship]):
sender_id=initiator_id,
message_type=InboxMessageType.FRIEND_REQUEST,
friendship_id=friendship.id,
content=content,
status=InboxMessageStatus.PENDING,
created_by=initiator_id,
)
self._session.add(inbox)
await self._session.flush()
@@ -105,6 +120,44 @@ class SQLAlchemyFriendshipRepository(BaseRepository[Friendship]):
)
raise
async def reactivate_request(
self,
friendship: Friendship,
initiator_id: UUID,
content: str | None = None,
) -> tuple[Friendship, InboxMessage]:
try:
now = datetime.now(timezone.utc)
friendship.status = FriendshipStatus.PENDING
friendship.requested_at = now
friendship.initiator_id = initiator_id
friendship.updated_by = initiator_id
inbox = InboxMessage(
recipient_id=(
friendship.user_low_id
if initiator_id == friendship.user_high_id
else friendship.user_high_id
),
sender_id=initiator_id,
message_type=InboxMessageType.FRIEND_REQUEST,
friendship_id=friendship.id,
content=content,
status=InboxMessageStatus.PENDING,
created_by=initiator_id,
)
self._session.add(inbox)
await self._session.flush()
return friendship, inbox
except SQLAlchemyError:
logger.exception(
"Failed to reactivate friendship request",
friendship_id=str(friendship.id),
initiator_id=str(initiator_id),
)
raise
async def get_friendship_between_users(
self, user_id_1: UUID, user_id_2: UUID
) -> Friendship | None:
+8 -3
View File
@@ -7,7 +7,6 @@ from fastapi import APIRouter, Depends, status
from v1.friendships.dependencies import get_friendship_service
from v1.friendships.schemas import (
FriendRequestAction,
FriendRequestCreate,
FriendRequestResponse,
FriendResponse,
@@ -44,13 +43,20 @@ async def get_outgoing_requests(
return await service.get_outgoing_requests()
@router.get("/requests/{friendship_id}", response_model=FriendRequestResponse)
async def get_friendship_request(
friendship_id: UUID,
service: Annotated[FriendshipService, Depends(get_friendship_service)],
) -> FriendRequestResponse:
return await service.get_request_by_id(friendship_id)
@router.post(
"/requests/{friendship_id}/accept",
response_model=FriendRequestResponse,
)
async def accept_friend_request(
friendship_id: UUID,
_: FriendRequestAction,
service: Annotated[FriendshipService, Depends(get_friendship_service)],
) -> FriendRequestResponse:
return await service.accept_request(friendship_id)
@@ -62,7 +68,6 @@ async def accept_friend_request(
)
async def decline_friend_request(
friendship_id: UUID,
_: FriendRequestAction,
service: Annotated[FriendshipService, Depends(get_friendship_service)],
) -> FriendRequestResponse:
return await service.decline_request(friendship_id)
+129 -33
View File
@@ -1,7 +1,7 @@
from __future__ import annotations
from datetime import datetime, timezone
from typing import TYPE_CHECKING, Any
from typing import TYPE_CHECKING, Any, Literal, cast
from uuid import UUID
from fastapi import HTTPException
@@ -10,8 +10,8 @@ from sqlalchemy.exc import SQLAlchemyError
from core.auth.models import CurrentUser
from core.db.base_service import BaseService
from core.logging import get_logger
from models.friendships import FriendshipStatus
from models.inbox_messages import InboxMessageStatus, InboxMessageType
from models.friendships import Friendship, FriendshipStatus
from models.inbox_messages import InboxMessage, InboxMessageStatus, InboxMessageType
from v1.friendships.repository import FriendshipRepository
from v1.friendships.schemas import (
FriendRequestCreate,
@@ -67,18 +67,47 @@ class FriendshipService(BaseService):
user_id, target_user_id
)
if existing:
if existing.status == FriendshipStatus.ACCEPTED:
raise HTTPException(
status_code=400, detail="Already friends with this user"
)
if existing.status == FriendshipStatus.BLOCKED:
raise HTTPException(
status_code=400, detail="Cannot send friend request to blocked user"
)
match existing.status:
case FriendshipStatus.ACCEPTED:
raise HTTPException(
status_code=400, detail="Already friends with this user"
)
case FriendshipStatus.BLOCKED:
raise HTTPException(
status_code=400,
detail="Cannot send friend request to blocked user",
)
case FriendshipStatus.PENDING:
raise HTTPException(
status_code=400, detail="Friend request already sent"
)
case _:
# DECLINED, CANCELED - 允许重新发送
try:
friendship, inbox = await self._repository.reactivate_request(
existing, user_id, request.content
)
await self._session.commit()
except SQLAlchemyError:
await self._session.rollback()
raise HTTPException(
status_code=503, detail="Friendship service unavailable"
)
logger.info(
"friend_request_resent",
extra={
"initiator_id": str(user_id),
"target_id": str(target_user_id),
},
)
return await self._build_friend_request_response(
friendship, inbox, user_id, target_user_id
)
try:
friendship, inbox = await self._repository.create_request(
user_id, target_user_id
user_id, target_user_id, request.content
)
await self._session.commit()
except SQLAlchemyError:
@@ -92,16 +121,8 @@ class FriendshipService(BaseService):
extra={"initiator_id": str(user_id), "target_id": str(target_user_id)},
)
sender = await self._user_repository.get_by_user_id(user_id)
recipient = await self._user_repository.get_by_user_id(target_user_id)
return FriendRequestResponse(
id=friendship.id,
sender=self._build_user_basic_info(sender),
recipient=self._build_user_basic_info(recipient),
content=inbox.content,
status="pending",
created_at=friendship.created_at,
return await self._build_friend_request_response(
friendship, inbox, user_id, target_user_id
)
async def accept_request(self, friendship_id: UUID) -> FriendRequestResponse:
@@ -374,6 +395,61 @@ class FriendshipService(BaseService):
return result
async def get_request_by_id(self, friendship_id: UUID) -> FriendRequestResponse:
user_id = self.require_user_id()
try:
friendship = await self._repository.get_friendship_by_id(friendship_id)
except SQLAlchemyError:
raise HTTPException(
status_code=503, detail="Friendship service unavailable"
)
if friendship is None:
raise HTTPException(status_code=404, detail="Friend request not found")
# Determine sender and recipient based on current user
# initiator_id is the sender
initiator_id = friendship.initiator_id
if initiator_id is None:
raise HTTPException(status_code=400, detail="Invalid friendship data")
if friendship.user_low_id != user_id and friendship.user_high_id != user_id:
raise HTTPException(
status_code=403, detail="Not authorized to view this request"
)
sender = await self._user_repository.get_by_user_id(initiator_id)
recipient_id = (
friendship.user_low_id
if friendship.user_low_id != initiator_id
else friendship.user_high_id
)
recipient = await self._user_repository.get_by_user_id(recipient_id)
# Map FriendshipStatus to response status
status_value: Literal["pending", "accepted", "rejected", "canceled"]
status_map = {
FriendshipStatus.PENDING: "pending",
FriendshipStatus.ACCEPTED: "accepted",
FriendshipStatus.DECLINED: "rejected",
FriendshipStatus.CANCELED: "canceled",
FriendshipStatus.BLOCKED: "canceled",
}
status_value = cast(
Literal["pending", "accepted", "rejected", "canceled"],
status_map.get(friendship.status, "pending"),
)
return FriendRequestResponse(
id=friendship.id,
sender=self._build_user_basic_info(sender),
recipient=self._build_user_basic_info(recipient),
content=None,
status=status_value,
created_at=friendship.created_at,
)
async def get_outgoing_requests(self) -> list[FriendRequestResponse]:
user_id = self.require_user_id()
@@ -386,13 +462,9 @@ class FriendshipService(BaseService):
result: list[FriendRequestResponse] = []
for friendship in outgoing:
recipient_id = (
friendship.user_low_id
if friendship.initiator_id == friendship.user_high_id
else friendship.user_high_id
)
other_user_id = self._get_other_user_id(friendship, user_id)
sender = await self._user_repository.get_by_user_id(user_id)
recipient = await self._user_repository.get_by_user_id(recipient_id)
recipient = await self._user_repository.get_by_user_id(other_user_id)
result.append(
FriendRequestResponse(
@@ -419,11 +491,7 @@ class FriendshipService(BaseService):
result: list[FriendResponse] = []
for friendship in friendships:
friend_id = (
friendship.user_high_id
if friendship.user_low_id == user_id
else friendship.user_low_id
)
friend_id = self._get_other_user_id(friendship, user_id)
friend = await self._user_repository.get_by_user_id(friend_id)
result.append(
@@ -499,3 +567,31 @@ class FriendshipService(BaseService):
username=p.username,
avatar_url=p.avatar_url if hasattr(p, "avatar_url") else None,
)
async def _build_friend_request_response(
self,
friendship: "Friendship",
inbox: "InboxMessage",
initiator_id: UUID,
recipient_id: UUID,
) -> "FriendRequestResponse":
from v1.friendships.schemas import FriendRequestResponse
sender = await self._user_repository.get_by_user_id(initiator_id)
recipient = await self._user_repository.get_by_user_id(recipient_id)
return FriendRequestResponse(
id=friendship.id,
sender=self._build_user_basic_info(sender),
recipient=self._build_user_basic_info(recipient),
content=inbox.content,
status="pending",
created_at=friendship.created_at,
)
def _get_other_user_id(self, friendship: Friendship, current_user_id: UUID) -> UUID:
return (
friendship.user_high_id
if friendship.user_low_id == current_user_id
else friendship.user_low_id
)