Files
Andros Fenollosa aa5f200fb5 Added boosts
2025-11-16 18:41:45 +01:00

196 lines
7.2 KiB
Python

from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import status
from django.core.cache import cache
from django.db.models import Q
import logging
from app.feeds.models import Profile, Post, Mention
logger = logging.getLogger(__name__)
class NotificationsView(APIView):
"""Get all notifications (mentions, reactions, and replies) for a specific feed URL"""
def get(self, request):
feed_url = request.query_params.get("feed")
notification_type = request.query_params.get("type", "").strip().lower()
if not feed_url or not feed_url.strip():
return Response(
{
"type": "Error",
"errors": ["Feed URL parameter is required"],
"data": None,
},
status=status.HTTP_400_BAD_REQUEST,
)
feed_url = feed_url.strip()
# Validate type parameter if provided
valid_types = ["mention", "reaction", "reply", "boost"]
if notification_type and notification_type not in valid_types:
return Response(
{
"type": "Error",
"errors": [
f"Invalid type parameter. Must be one of: {', '.join(valid_types)}"
],
"data": None,
},
status=status.HTTP_400_BAD_REQUEST,
)
# Try to get notifications from cache first
cache_key = f"notifications_{feed_url}_{notification_type}"
cached_response = cache.get(cache_key)
if cached_response is not None:
return Response(cached_response, status=status.HTTP_200_OK)
# Check if the profile exists
try:
profile = Profile.objects.get(feed=feed_url)
except Profile.DoesNotExist:
return Response(
{
"type": "Error",
"errors": ["Profile not found for the given feed URL"],
"data": None,
},
status=status.HTTP_404_NOT_FOUND,
)
notifications_data = []
counts = {"mentions": 0, "reactions": 0, "replies": 0, "boosts": 0}
# Get all post IDs from this profile (for reactions and replies)
profile_post_ids = list(profile.posts.values_list("post_id", flat=True))
reply_to_patterns = [f"{feed_url}#{post_id}" for post_id in profile_post_ids]
# 1. Get mentions (if not filtering or filtering for mentions)
if not notification_type or notification_type == "mention":
mentions = (
Mention.objects.filter(mentioned_profile=profile)
.select_related("post", "post__profile")
.order_by("-post__post_id")
)
for mention in mentions:
notifications_data.append(
{
"type": "mention",
"post": f"{mention.post.profile.feed}#{mention.post.post_id}",
"_sort_key": mention.post.post_id, # For sorting
}
)
counts["mentions"] = len(mentions)
# 2. Get reactions (if not filtering or filtering for reactions)
if not notification_type or notification_type == "reaction":
reactions = (
Post.objects.filter(reply_to__in=reply_to_patterns, mood__isnull=False)
.exclude(mood="")
.select_related("profile")
.order_by("-post_id")
)
for reaction in reactions:
notifications_data.append(
{
"type": "reaction",
"post": f"{reaction.profile.feed}#{reaction.post_id}",
"emoji": reaction.mood,
"parent": reaction.reply_to,
"_sort_key": reaction.post_id,
}
)
counts["reactions"] = len(reactions)
# 3. Get replies (if not filtering or filtering for replies)
if not notification_type or notification_type == "reply":
replies = (
Post.objects.filter(reply_to__in=reply_to_patterns)
.filter(Q(mood="") | Q(mood__isnull=True))
.exclude(poll_votes__isnull=False)
.select_related("profile")
.order_by("-post_id")
)
for reply in replies:
notifications_data.append(
{
"type": "reply",
"post": f"{reply.profile.feed}#{reply.post_id}",
"parent": reply.reply_to,
"_sort_key": reply.post_id,
}
)
counts["replies"] = len(replies)
# 4. Get boosts (if not filtering or filtering for boosts)
# Build include patterns (feed#post_id) for all posts from this profile
include_patterns = [f"{feed_url}#{post_id}" for post_id in profile_post_ids]
if not notification_type or notification_type == "boost":
boosts = (
Post.objects.filter(include__in=include_patterns)
.select_related("profile")
.order_by("-post_id")
)
for boost in boosts:
notifications_data.append(
{
"type": "boost",
"post": f"{boost.profile.feed}#{boost.post_id}",
"boosted": boost.include,
"_sort_key": boost.post_id,
}
)
counts["boosts"] = len(boosts)
# Sort all notifications by post_id (most recent first)
notifications_data.sort(key=lambda x: x["_sort_key"], reverse=True)
# Remove the _sort_key field from the final output
for notification in notifications_data:
del notification["_sort_key"]
# URL encode the feed_url for links
from urllib.parse import quote
encoded_feed_url = quote(feed_url, safe="")
response_data = {
"type": "Success",
"errors": [],
"data": notifications_data,
"meta": {
"feed": feed_url,
"total": len(notifications_data),
"by_type": counts,
},
"_links": {
"self": {
"href": f"/notifications/?feed={encoded_feed_url}",
"method": "GET",
},
"mentions": {
"href": f"/mentions/?feed={encoded_feed_url}",
"method": "GET",
},
"reactions": {
"href": f"/reactions/?feed={encoded_feed_url}",
"method": "GET",
},
"replies-to": {
"href": f"/replies-to/?feed={encoded_feed_url}",
"method": "GET",
},
},
}
# Cache permanently (will be cleared by scan_feeds task)
cache.set(cache_key, response_data, None)
return Response(response_data, status=status.HTTP_200_OK)