Files
ai_web/backend/apps/users/friends.py
ddrwode fc0679b199 haha
2026-02-02 13:48:11 +08:00

200 lines
7.0 KiB
Python

"""
Friendship and friend request API routes.
"""
from typing import List, Optional
from django.db import transaction
from django.db.models import Q
from django.utils import timezone
from ninja import Router
from ninja.errors import HttpError
from ninja_jwt.authentication import JWTAuth
from .models import User, FriendRequest
from .schemas import FriendOut, FriendRequestIn, FriendRequestOut, MessageOut, UserBrief
router = Router()
def serialize_user_brief(user: User) -> UserBrief:
return UserBrief(
id=user.id,
open_id=user.open_id,
user_id=user.user_id,
name=user.name,
email=user.email,
avatar=user.avatar,
)
def serialize_request(request_obj: FriendRequest) -> FriendRequestOut:
return FriendRequestOut(
id=request_obj.id,
requester=serialize_user_brief(request_obj.requester),
receiver=serialize_user_brief(request_obj.receiver),
status=request_obj.status,
accepted_at=request_obj.accepted_at,
created_at=request_obj.created_at,
updated_at=request_obj.updated_at,
)
@router.get("/", response=List[FriendOut], auth=JWTAuth())
def list_friends(request):
"""List accepted friends for current user."""
relations = FriendRequest.objects.select_related("requester", "receiver").filter(
status=FriendRequest.Status.ACCEPTED,
).filter(Q(requester=request.auth) | Q(receiver=request.auth))
friends = []
for relation in relations:
friend_user = relation.receiver if relation.requester_id == request.auth.id else relation.requester
friends.append(
FriendOut(
request_id=relation.id,
user=serialize_user_brief(friend_user),
since=relation.accepted_at,
)
)
return friends
@router.get("/requests/incoming/count", auth=JWTAuth())
def incoming_count(request):
"""Lightweight: only return count of pending incoming requests (for badge)."""
count = FriendRequest.objects.filter(
receiver=request.auth, status=FriendRequest.Status.PENDING
).count()
return {"count": count}
@router.get("/requests/incoming", response=List[FriendRequestOut], auth=JWTAuth())
def list_incoming_requests(request):
"""List incoming friend requests."""
requests = (
FriendRequest.objects.select_related("requester", "receiver")
.filter(receiver=request.auth, status=FriendRequest.Status.PENDING)
.order_by("-created_at")
)
return [serialize_request(r) for r in requests]
@router.get("/requests/outgoing", response=List[FriendRequestOut], auth=JWTAuth())
def list_outgoing_requests(request):
"""List outgoing friend requests."""
requests = (
FriendRequest.objects.select_related("requester", "receiver")
.filter(requester=request.auth, status=FriendRequest.Status.PENDING)
.order_by("-created_at")
)
return [serialize_request(r) for r in requests]
@router.post("/requests", response=FriendRequestOut, auth=JWTAuth())
@transaction.atomic
def create_request(request, data: FriendRequestIn):
"""Send a friend request."""
if data.receiver_id == request.auth.id:
raise HttpError(400, "不能添加自己为好友")
try:
receiver = User.objects.get(id=data.receiver_id)
except User.DoesNotExist:
raise HttpError(404, "用户不存在")
existing = FriendRequest.objects.select_related("requester", "receiver").filter(
Q(requester=request.auth, receiver=receiver) | Q(requester=receiver, receiver=request.auth)
).first()
if existing:
if existing.status == FriendRequest.Status.ACCEPTED:
raise HttpError(400, "你们已经是好友")
if existing.status == FriendRequest.Status.PENDING:
if existing.receiver_id == request.auth.id:
raise HttpError(400, "对方已向你发送好友请求")
return serialize_request(existing)
if existing.requester_id != request.auth.id:
existing.requester = request.auth
existing.receiver = receiver
existing.status = FriendRequest.Status.PENDING
existing.accepted_at = None
existing.save(update_fields=["requester", "receiver", "status", "accepted_at", "updated_at"])
return serialize_request(existing)
new_request = FriendRequest.objects.create(
requester=request.auth,
receiver=receiver,
status=FriendRequest.Status.PENDING,
)
return serialize_request(new_request)
@router.post("/requests/{request_id}/accept", response=FriendRequestOut, auth=JWTAuth())
def accept_request(request, request_id: int):
"""Accept incoming friend request."""
friend_request = FriendRequest.objects.select_related("requester", "receiver").filter(
id=request_id,
receiver=request.auth,
status=FriendRequest.Status.PENDING,
).first()
if not friend_request:
raise HttpError(404, "未找到待处理好友请求")
friend_request.status = FriendRequest.Status.ACCEPTED
friend_request.accepted_at = timezone.now()
friend_request.save(update_fields=["status", "accepted_at", "updated_at"])
return serialize_request(friend_request)
@router.post("/requests/{request_id}/reject", response=FriendRequestOut, auth=JWTAuth())
def reject_request(request, request_id: int):
"""Reject incoming friend request."""
friend_request = FriendRequest.objects.select_related("requester", "receiver").filter(
id=request_id,
receiver=request.auth,
status=FriendRequest.Status.PENDING,
).first()
if not friend_request:
raise HttpError(404, "未找到待处理好友请求")
friend_request.status = FriendRequest.Status.REJECTED
friend_request.save(update_fields=["status", "updated_at"])
return serialize_request(friend_request)
@router.post("/requests/{request_id}/cancel", response=FriendRequestOut, auth=JWTAuth())
def cancel_request(request, request_id: int):
"""Cancel an outgoing friend request."""
friend_request = FriendRequest.objects.select_related("requester", "receiver").filter(
id=request_id,
requester=request.auth,
status=FriendRequest.Status.PENDING,
).first()
if not friend_request:
raise HttpError(404, "未找到待处理好友请求")
friend_request.status = FriendRequest.Status.CANCELED
friend_request.save(update_fields=["status", "updated_at"])
return serialize_request(friend_request)
@router.get("/search", response=List[UserBrief], auth=JWTAuth())
def search_users(request, q: Optional[str] = None, limit: int = 10):
"""Search users by user_id (exact), open_id or name."""
if not q:
return []
q_clean = (q or "").strip()
queryset = (
User.objects.filter(
Q(user_id__iexact=q_clean)
| Q(user_id__icontains=q_clean)
| Q(open_id__icontains=q_clean)
| Q(name__icontains=q_clean)
)
.exclude(id=request.auth.id)
.order_by("id")[: max(1, min(limit, 50))]
)
return [serialize_user_brief(user) for user in queryset]