From 7f6d066acc7bac8374e411dbbcd3cb74677658c9 Mon Sep 17 00:00:00 2001 From: Petitminion Date: Wed, 4 Jun 2025 02:10:15 +0200 Subject: [PATCH] fix(front):search fetch federation object was expecting a music object but got a activitypub obj --- api/funkwhale_api/common/schema.yml | 12 +++++++++++ .../federation/api_serializers.py | 21 ++++++++++++++++++- api/funkwhale_api/federation/api_views.py | 17 +++++++++++++++ api/funkwhale_api/federation/serializers.py | 3 ++- api/funkwhale_api/federation/tasks.py | 1 - api/tests/federation/test_api_serializers.py | 7 ++++++- front/src/composables/audio/usePlayOptions.ts | 12 ++++++++--- front/src/generated/types.ts | 4 ++++ 8 files changed, 70 insertions(+), 7 deletions(-) diff --git a/api/funkwhale_api/common/schema.yml b/api/funkwhale_api/common/schema.yml index 8289537e5..b27a21c0b 100644 --- a/api/funkwhale_api/common/schema.yml +++ b/api/funkwhale_api/common/schema.yml @@ -2462,6 +2462,9 @@ paths: responses: '201': content: + application/activity+json: + schema: + $ref: '#/components/schemas/Fetch' application/json: schema: $ref: '#/components/schemas/Fetch' @@ -2484,6 +2487,9 @@ paths: responses: '200': content: + application/activity+json: + schema: + $ref: '#/components/schemas/Fetch' application/json: schema: $ref: '#/components/schemas/Fetch' @@ -11972,6 +11978,9 @@ paths: responses: '201': content: + application/activity+json: + schema: + $ref: '#/components/schemas/Fetch' application/json: schema: $ref: '#/components/schemas/Fetch' @@ -11994,6 +12003,9 @@ paths: responses: '200': content: + application/activity+json: + schema: + $ref: '#/components/schemas/Fetch' application/json: schema: $ref: '#/components/schemas/Fetch' diff --git a/api/funkwhale_api/federation/api_serializers.py b/api/funkwhale_api/federation/api_serializers.py index c3e103af7..1fd9afdc1 100644 --- a/api/funkwhale_api/federation/api_serializers.py +++ b/api/funkwhale_api/federation/api_serializers.py @@ -13,7 +13,9 @@ from funkwhale_api.audio import models as audio_models from funkwhale_api.audio import serializers as audio_serializers from funkwhale_api.common import serializers as common_serializers from funkwhale_api.music import models as music_models +from funkwhale_api.music import serializers as music_serializers from funkwhale_api.playlists import models as playlists_models +from funkwhale_api.playlists import serializers as playlist_serializers from funkwhale_api.users import serializers as users_serializers from . import filters, models @@ -197,10 +199,19 @@ OBJECT_SERIALIZER_MAPPING = { music_models.Artist: federation_serializers.ArtistSerializer, music_models.Album: federation_serializers.AlbumSerializer, music_models.Track: federation_serializers.TrackSerializer, + music_models.Library: federation_serializers.LibrarySerializer, models.Actor: federation_serializers.APIActorSerializer, audio_models.Channel: audio_serializers.ChannelSerializer, playlists_models.Playlist: federation_serializers.PlaylistSerializer, } +OBJECT_MUSIC_SERIALIZER_MAPPING = { + music_models.Artist: music_serializers.ArtistSerializer, + music_models.Album: music_serializers.AlbumSerializer, + music_models.Track: music_serializers.TrackSerializer, + models.Actor: federation_serializers.APIActorSerializer, + audio_models.Channel: audio_serializers.ChannelSerializer, + playlists_models.Playlist: playlist_serializers.PlaylistSerializer, +} def convert_url_to_webfinger(url): @@ -283,6 +294,9 @@ class FetchSerializer(serializers.ModelSerializer): return value return f"webfinger://{value}" + # to do : this is incomplete, schema conflict because + # federation serializers have the same name than musi serializer -> upgrade fed serializers to new names + # and add the new object here @extend_schema_field( { "oneOf": [ @@ -300,7 +314,12 @@ class FetchSerializer(serializers.ModelSerializer): if obj is None: return None - serializer_class = OBJECT_SERIALIZER_MAPPING.get(type(obj)) + media_type = self.context.get("media_type") + + if media_type == "application/activity+json": + serializer_class = OBJECT_SERIALIZER_MAPPING.get(type(obj)) + else: + serializer_class = OBJECT_MUSIC_SERIALIZER_MAPPING.get(type(obj)) if serializer_class: return serializer_class(obj).data return None diff --git a/api/funkwhale_api/federation/api_views.py b/api/funkwhale_api/federation/api_views.py index 5498c67d5..3386dd173 100644 --- a/api/funkwhale_api/federation/api_views.py +++ b/api/funkwhale_api/federation/api_views.py @@ -7,10 +7,13 @@ from django.db.models import Count, Q from drf_spectacular.utils import extend_schema, extend_schema_view from rest_framework import decorators, mixins, permissions, response, viewsets from rest_framework.exceptions import NotFound as RestNotFound +from rest_framework.negotiation import DefaultContentNegotiation +from rest_framework.renderers import JSONRenderer from funkwhale_api.common import preferences from funkwhale_api.common import utils as common_utils from funkwhale_api.common.permissions import ConditionalAuthentication +from funkwhale_api.common.renderers import ActivityStreamRenderer from funkwhale_api.music import models as music_models from funkwhale_api.music import serializers as music_serializers from funkwhale_api.music import views as music_views @@ -245,10 +248,24 @@ class FetchViewSet( serializer_class = api_serializers.FetchSerializer permission_classes = [permissions.IsAuthenticated] throttling_scopes = {"create": {"authenticated": "fetch"}} + renderer_classes = [ActivityStreamRenderer, JSONRenderer] def get_queryset(self): return super().get_queryset().filter(actor=self.request.user.actor) + def get_serializer_context(self): + context = super().get_serializer_context() + + negotiator = DefaultContentNegotiation() + try: + renderer, media_type = negotiator.select_renderer( + self.request, self.get_renderers() + ) + context["media_type"] = media_type + except Exception: + context["media_type"] = None + return context + def perform_create(self, serializer): fetch = serializer.save(actor=self.request.user.actor) if fetch.status == "finished": diff --git a/api/funkwhale_api/federation/serializers.py b/api/funkwhale_api/federation/serializers.py index dcdaca87d..490a0b3b6 100644 --- a/api/funkwhale_api/federation/serializers.py +++ b/api/funkwhale_api/federation/serializers.py @@ -1066,6 +1066,7 @@ class LibrarySerializer(PaginatedCollectionSerializer): privacy = {"": "me", "./": "me", None: "me", contexts.AS.Public: "everyone"} library, created = music_models.Library.objects.update_or_create( fid=validated_data["id"], + uuid=validated_data["id"].rstrip("/").split("/")[-1], actor=actor, defaults={ "uploads_count": validated_data["totalItems"], @@ -1449,7 +1450,7 @@ class AlbumSerializer(MusicEntitySerializer): acs.append( utils.retrieve_ap_object( ac["id"], - actor=self.context.get("fetch_actor"), + actor=self.context.get("_actor"), queryset=music_models.ArtistCredit, serializer_class=ArtistCreditSerializer, ) diff --git a/api/funkwhale_api/federation/tasks.py b/api/funkwhale_api/federation/tasks.py index d8694fe5c..dbf3168f9 100644 --- a/api/funkwhale_api/federation/tasks.py +++ b/api/funkwhale_api/federation/tasks.py @@ -454,7 +454,6 @@ def fetch(fetch_obj): max_pages=settings.FEDERATION_COLLECTION_MAX_PAGES - 1, is_page=True, ) - fetch_obj.object = obj fetch_obj.status = "finished" fetch_obj.fetch_date = timezone.now() diff --git a/api/tests/federation/test_api_serializers.py b/api/tests/federation/test_api_serializers.py index 25339b345..730cdf6e2 100644 --- a/api/tests/federation/test_api_serializers.py +++ b/api/tests/federation/test_api_serializers.py @@ -173,7 +173,12 @@ def test_fetch_serializer_with_object( "actor": serializers.APIActorSerializer(fetch.actor).data, } - assert api_serializers.FetchSerializer(fetch).data == expected + assert ( + api_serializers.FetchSerializer( + fetch, context={"media_type": "application/activity+json"} + ).data + == expected + ) def test_fetch_serializer_unhandled_obj(factories, to_api_date): diff --git a/front/src/composables/audio/usePlayOptions.ts b/front/src/composables/audio/usePlayOptions.ts index 108571f33..41632913b 100644 --- a/front/src/composables/audio/usePlayOptions.ts +++ b/front/src/composables/audio/usePlayOptions.ts @@ -210,13 +210,19 @@ export default (props: PlayOptionsProps) => { if (!id) { throw new Error("Library id not found in response."); } - const fetchResponse = await axios.post('federation/fetches', - { object: id } + const fetchResponse = await axios.post( + 'federation/fetches', + { object_uri: id }, + { + headers: { + Accept: 'application/activity+json' + } + } ); const response = await axios.post( 'federation/follows/library', - { target: fetchResponse.data.object.uuid } + { target: fetchResponse.data.object.id.split('/').pop() } ); return response; diff --git a/front/src/generated/types.ts b/front/src/generated/types.ts index 03e6c53af..65b4a4d4b 100644 --- a/front/src/generated/types.ts +++ b/front/src/generated/types.ts @@ -10590,6 +10590,7 @@ export interface operations { [name: string]: unknown; }; content: { + "application/activity+json": components["schemas"]["Fetch"]; "application/json": components["schemas"]["Fetch"]; }; }; @@ -10612,6 +10613,7 @@ export interface operations { [name: string]: unknown; }; content: { + "application/activity+json": components["schemas"]["Fetch"]; "application/json": components["schemas"]["Fetch"]; }; }; @@ -17537,6 +17539,7 @@ export interface operations { [name: string]: unknown; }; content: { + "application/activity+json": components["schemas"]["Fetch"]; "application/json": components["schemas"]["Fetch"]; }; }; @@ -17559,6 +17562,7 @@ export interface operations { [name: string]: unknown; }; content: { + "application/activity+json": components["schemas"]["Fetch"]; "application/json": components["schemas"]["Fetch"]; }; };