200 lines
7.0 KiB
Python
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]
|