feat: 增强日历功能并集成 AgentScope 代理服务
This commit is contained in:
@@ -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:
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user