feat(reaction): 用户端表情接口
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
7
reaction/urls/oj.py
Normal file
7
reaction/urls/oj.py
Normal file
@@ -0,0 +1,7 @@
|
||||
from django.urls import path
|
||||
|
||||
from ..views.oj import ReactionAPI
|
||||
|
||||
urlpatterns = [
|
||||
path("reaction", ReactionAPI.as_view()),
|
||||
]
|
||||
69
reaction/views/oj.py
Normal file
69
reaction/views/oj.py
Normal file
@@ -0,0 +1,69 @@
|
||||
from asgiref.sync import sync_to_async
|
||||
from django.db import transaction
|
||||
from django.db.models import Count, Q
|
||||
|
||||
from account.decorators import login_required
|
||||
from problem.models import Problem
|
||||
from reaction.models import Reaction, ReactionType
|
||||
from reaction.serializers import SetReactionSerializer
|
||||
from submission.models import JudgeStatus, Submission
|
||||
from utils.api import AsyncAPIView
|
||||
from utils.api.api import validate_serializer
|
||||
from utils.async_helpers import async_cache_delete, async_cache_get, async_cache_set
|
||||
from utils.constants import CacheKey
|
||||
|
||||
ACCEPTED_RESULTS = [JudgeStatus.ACCEPTED, JudgeStatus.AST_CHECK_FAILED]
|
||||
|
||||
|
||||
class ReactionAPI(AsyncAPIView):
|
||||
async def get_counts(self, problem_id):
|
||||
"""返回该题七个表情的计数,带 Redis 缓存。"""
|
||||
cache_key = f"{CacheKey.reaction_stats}:{problem_id}"
|
||||
cached = await async_cache_get(cache_key)
|
||||
if cached is not None:
|
||||
return cached
|
||||
counts = await Reaction.objects.filter(problem_id=problem_id).aaggregate(**{t.value: Count("id", filter=Q(type=t.value)) for t in ReactionType})
|
||||
await async_cache_set(cache_key, counts, 3600)
|
||||
return counts
|
||||
|
||||
@login_required
|
||||
async def get(self, request):
|
||||
problem_id = request.GET.get("problem_id")
|
||||
if not problem_id:
|
||||
return self.error("problem_id is required")
|
||||
mine = [r.type async for r in Reaction.objects.filter(user=request.user, problem_id=problem_id)]
|
||||
if not mine:
|
||||
return self.success({"mine": [], "counts": None})
|
||||
return self.success({"mine": mine, "counts": await self.get_counts(problem_id)})
|
||||
|
||||
@login_required
|
||||
@validate_serializer(SetReactionSerializer)
|
||||
async def post(self, request):
|
||||
data = request.data
|
||||
try:
|
||||
problem = await Problem.objects.aget(id=data["problem_id"], visible=True)
|
||||
except Problem.DoesNotExist:
|
||||
return self.error("problem is not exists")
|
||||
|
||||
solved = await Submission.objects.filter(
|
||||
user_id=request.user.id,
|
||||
problem_id=problem.id,
|
||||
result__in=ACCEPTED_RESULTS,
|
||||
).aexists()
|
||||
if not solved:
|
||||
return self.error("submission is not exists or not accepted")
|
||||
|
||||
types = data["types"]
|
||||
user = request.user
|
||||
|
||||
def overwrite():
|
||||
with transaction.atomic():
|
||||
Reaction.objects.filter(user=user, problem=problem).delete()
|
||||
Reaction.objects.bulk_create([Reaction(user=user, problem=problem, type=t) for t in types])
|
||||
|
||||
await sync_to_async(overwrite)()
|
||||
await async_cache_delete(f"{CacheKey.reaction_stats}:{problem.id}")
|
||||
|
||||
if not types:
|
||||
return self.success({"mine": [], "counts": None})
|
||||
return self.success({"mine": types, "counts": await self.get_counts(problem.id)})
|
||||
Reference in New Issue
Block a user