relay/app/feeds/views.py
Andros Fenollosa c446ae514d Exclude bridge virtual feeds from feed listing, discovery and stats
Bridges help users connect to external content (RSS, ActivityPub) but
are not real Org Social accounts. They no longer appear in /feeds/,
cannot be registered via POST, are skipped by both feed discovery
tasks, and do not count in /stats/. A data migration removes bridge
URLs already registered as feeds.
2026-07-20 09:33:28 +02:00

126 lines
4.1 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
import logging
from app.bridge.models import bridge_urls_q, is_bridge_feed_url
from .models import Feed
from .parser import validate_org_social_feed
logger = logging.getLogger(__name__)
class FeedsView(APIView):
"""List feeds or add new feed"""
def get(self, request):
# Try to get feeds from cache first
cache_key = "feeds_list"
cached_feeds = cache.get(cache_key)
if cached_feeds is not None:
return Response(
{
"type": "Success",
"errors": [],
"data": cached_feeds,
"_links": {
"self": {"href": "/feeds/", "method": "GET"},
"add": {"href": "/feeds/", "method": "POST"},
},
},
status=status.HTTP_200_OK,
)
# If not in cache, query database. Bridge virtual feeds are a
# connection helper, not real accounts, so they are never listed.
feeds = list(
Feed.objects.exclude(bridge_urls_q("url")).values_list("url", flat=True)
)
# Cache permanently (will be cleared by scan_feeds task)
cache.set(cache_key, feeds, None)
return Response(
{
"type": "Success",
"errors": [],
"data": feeds,
"_links": {
"self": {"href": "/feeds/", "method": "GET"},
"add": {"href": "/feeds/", "method": "POST"},
},
},
status=status.HTTP_200_OK,
)
def post(self, request):
feed_url = request.data.get("feed")
if not feed_url or not feed_url.strip():
return Response(
{"type": "Error", "errors": ["Feed URL is required"], "data": None},
status=status.HTTP_400_BAD_REQUEST,
)
feed_url = feed_url.strip()
# Bridge virtual feeds serve valid Org Social content but are not
# real accounts; they cannot be registered
if is_bridge_feed_url(feed_url):
return Response(
{
"type": "Error",
"errors": ["Bridge virtual feeds cannot be registered"],
"data": None,
},
status=status.HTTP_400_BAD_REQUEST,
)
# Check if feed already exists
existing_feed = Feed.objects.filter(url=feed_url).first()
if existing_feed:
return Response(
{"type": "Success", "errors": [], "data": {"feed": feed_url}},
status=status.HTTP_200_OK,
)
# Validate the feed before adding it
logger.info(f"Validating new feed: {feed_url}")
is_valid, error_message = validate_org_social_feed(feed_url)
if not is_valid:
logger.warning(f"Feed validation failed for {feed_url}: {error_message}")
return Response(
{
"type": "Error",
"errors": [f"Invalid Org Social feed: {error_message}"],
"data": None,
},
status=status.HTTP_400_BAD_REQUEST,
)
# Create the feed
try:
Feed.objects.create(url=feed_url)
logger.info(f"Successfully added new feed: {feed_url}")
# Invalidate the cache
cache.delete("feeds_list")
return Response(
{"type": "Success", "errors": [], "data": {"feed": feed_url}},
status=status.HTTP_201_CREATED,
)
except Exception as e:
logger.error(f"Failed to create feed {feed_url}: {str(e)}")
return Response(
{
"type": "Error",
"errors": ["Failed to add feed to database"],
"data": None,
},
status=status.HTTP_500_INTERNAL_SERVER_ERROR,
)