diff --git a/api/config/api_urls.py b/api/config/api_urls.py index b0d7eaaffefbf542eb4ef9b51dfe4d86692b37a7..7339df445d43653e7580220e528dd89a9b24b125 100644 --- a/api/config/api_urls.py +++ b/api/config/api_urls.py @@ -23,6 +23,7 @@ router.register(r"libraries", views.LibraryViewSet, "libraries") router.register(r"listen", views.ListenViewSet, "listen") router.register(r"artists", views.ArtistViewSet, "artists") router.register(r"channels", audio_views.ChannelViewSet, "channels") +router.register(r"subscriptions", audio_views.SubscriptionsViewSet, "subscriptions") router.register(r"albums", views.AlbumViewSet, "albums") router.register(r"licenses", views.LicenseViewSet, "licenses") router.register(r"playlists", playlists_views.PlaylistViewSet, "playlists") diff --git a/api/funkwhale_api/audio/views.py b/api/funkwhale_api/audio/views.py index a473dbbe80bf8adff550e16e6376d3f3a55b5c1c..ba9983672a6ff826d97fcc4bedd42539191bdf57 100644 --- a/api/funkwhale_api/audio/views.py +++ b/api/funkwhale_api/audio/views.py @@ -10,6 +10,7 @@ from django.db.utils import IntegrityError from funkwhale_api.common import permissions from funkwhale_api.common import preferences +from funkwhale_api.federation import models as federation_models from funkwhale_api.users.oauth import permissions as oauth_permissions from . import filters, models, serializers @@ -66,10 +67,12 @@ class ChannelViewSet( ) def subscribe(self, request, *args, **kwargs): object = self.get_object() + subscription = federation_models.Follow( + target=object.actor, approved=True, actor=request.user.actor, + ) + subscription.fid = subscription.get_federation_id() try: - subscription = object.actor.received_follows.create( - approved=True, actor=request.user.actor, - ) + subscription.save() except IntegrityError: # there's already a subscription for this actor/channel subscription = object.actor.received_follows.filter( @@ -88,3 +91,48 @@ class ChannelViewSet( object = self.get_object() request.user.actor.emitted_follows.filter(target=object.actor).delete() return response.Response(status=204) + + +class SubscriptionsViewSet( + ChannelsMixin, + mixins.RetrieveModelMixin, + mixins.ListModelMixin, + viewsets.GenericViewSet, +): + lookup_field = "uuid" + serializer_class = serializers.SubscriptionSerializer + queryset = ( + federation_models.Follow.objects.exclude(target__channel__isnull=True) + .prefetch_related( + "target__channel__library", + "target__channel__attributed_to", + "target__channel__artist__description", + "actor", + ) + .order_by("-creation_date") + ) + permission_classes = [ + oauth_permissions.ScopePermission, + rest_permissions.IsAuthenticated, + ] + required_scope = "libraries" + anonymous_policy = False + + def get_queryset(self): + qs = super().get_queryset() + return qs.filter(actor=self.request.user.actor) + + @decorators.action(methods=["get"], detail=False) + def all(self, request, *args, **kwargs): + """ + Return all the subscriptions of the current user, with only limited data + to have a performant endpoint and avoid lots of queries just to display + subscription status in the UI + """ + subscriptions = list(self.get_queryset().values_list("uuid", flat=True)) + + payload = { + "results": [str(u) for u in subscriptions], + "count": len(subscriptions), + } + return response.Response(payload, status=200) diff --git a/api/tests/audio/test_views.py b/api/tests/audio/test_views.py index f8cf456a669f9cc383284e6206d969654c22ebef..0724bfa98b07e69008c0d4abaa5a04625fede72b 100644 --- a/api/tests/audio/test_views.py +++ b/api/tests/audio/test_views.py @@ -140,6 +140,7 @@ def test_channel_subscribe(factories, logged_in_api_client): subscription = actor.emitted_follows.select_related( "target__channel__artist__description" ).latest("id") + assert subscription.fid == subscription.get_federation_id() expected = serializers.SubscriptionSerializer(subscription).data assert response.data == expected assert subscription.target == channel.actor @@ -157,3 +158,34 @@ def test_channel_unsubscribe(factories, logged_in_api_client): with pytest.raises(subscription.DoesNotExist): subscription.refresh_from_db() + + +def test_subscriptions_list(factories, logged_in_api_client): + actor = logged_in_api_client.user.create_actor() + channel = factories["audio.Channel"](artist__description=None) + subscription = factories["audio.Subscription"](target=channel.actor, actor=actor) + factories["audio.Subscription"](target=channel.actor) + url = reverse("api:v1:subscriptions-list") + expected = serializers.SubscriptionSerializer(subscription).data + response = logged_in_api_client.get(url) + + assert response.status_code == 200 + assert response.data["results"][0] == expected + assert response.data == { + "results": [expected], + "count": 1, + "next": None, + "previous": None, + } + + +def test_subscriptions_all(factories, logged_in_api_client): + actor = logged_in_api_client.user.create_actor() + channel = factories["audio.Channel"](artist__description=None) + subscription = factories["audio.Subscription"](target=channel.actor, actor=actor) + factories["audio.Subscription"](target=channel.actor) + url = reverse("api:v1:subscriptions-all") + response = logged_in_api_client.get(url) + + assert response.status_code == 200 + assert response.data == {"results": [subscription.uuid], "count": 1}