fix(front):search fetch federation object was expecting a music object but got a activitypub obj

This commit is contained in:
Petitminion 2025-06-04 02:10:15 +02:00
parent 1be4b5204a
commit 7f6d066acc
8 changed files with 70 additions and 7 deletions

View File

@ -2462,6 +2462,9 @@ paths:
responses: responses:
'201': '201':
content: content:
application/activity+json:
schema:
$ref: '#/components/schemas/Fetch'
application/json: application/json:
schema: schema:
$ref: '#/components/schemas/Fetch' $ref: '#/components/schemas/Fetch'
@ -2484,6 +2487,9 @@ paths:
responses: responses:
'200': '200':
content: content:
application/activity+json:
schema:
$ref: '#/components/schemas/Fetch'
application/json: application/json:
schema: schema:
$ref: '#/components/schemas/Fetch' $ref: '#/components/schemas/Fetch'
@ -11972,6 +11978,9 @@ paths:
responses: responses:
'201': '201':
content: content:
application/activity+json:
schema:
$ref: '#/components/schemas/Fetch'
application/json: application/json:
schema: schema:
$ref: '#/components/schemas/Fetch' $ref: '#/components/schemas/Fetch'
@ -11994,6 +12003,9 @@ paths:
responses: responses:
'200': '200':
content: content:
application/activity+json:
schema:
$ref: '#/components/schemas/Fetch'
application/json: application/json:
schema: schema:
$ref: '#/components/schemas/Fetch' $ref: '#/components/schemas/Fetch'

View File

@ -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.audio import serializers as audio_serializers
from funkwhale_api.common import serializers as common_serializers from funkwhale_api.common import serializers as common_serializers
from funkwhale_api.music import models as music_models 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 models as playlists_models
from funkwhale_api.playlists import serializers as playlist_serializers
from funkwhale_api.users import serializers as users_serializers from funkwhale_api.users import serializers as users_serializers
from . import filters, models from . import filters, models
@ -197,10 +199,19 @@ OBJECT_SERIALIZER_MAPPING = {
music_models.Artist: federation_serializers.ArtistSerializer, music_models.Artist: federation_serializers.ArtistSerializer,
music_models.Album: federation_serializers.AlbumSerializer, music_models.Album: federation_serializers.AlbumSerializer,
music_models.Track: federation_serializers.TrackSerializer, music_models.Track: federation_serializers.TrackSerializer,
music_models.Library: federation_serializers.LibrarySerializer,
models.Actor: federation_serializers.APIActorSerializer, models.Actor: federation_serializers.APIActorSerializer,
audio_models.Channel: audio_serializers.ChannelSerializer, audio_models.Channel: audio_serializers.ChannelSerializer,
playlists_models.Playlist: federation_serializers.PlaylistSerializer, 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): def convert_url_to_webfinger(url):
@ -283,6 +294,9 @@ class FetchSerializer(serializers.ModelSerializer):
return value return value
return f"webfinger://{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( @extend_schema_field(
{ {
"oneOf": [ "oneOf": [
@ -300,7 +314,12 @@ class FetchSerializer(serializers.ModelSerializer):
if obj is None: if obj is None:
return None return None
media_type = self.context.get("media_type")
if media_type == "application/activity+json":
serializer_class = OBJECT_SERIALIZER_MAPPING.get(type(obj)) serializer_class = OBJECT_SERIALIZER_MAPPING.get(type(obj))
else:
serializer_class = OBJECT_MUSIC_SERIALIZER_MAPPING.get(type(obj))
if serializer_class: if serializer_class:
return serializer_class(obj).data return serializer_class(obj).data
return None return None

View File

@ -7,10 +7,13 @@ from django.db.models import Count, Q
from drf_spectacular.utils import extend_schema, extend_schema_view from drf_spectacular.utils import extend_schema, extend_schema_view
from rest_framework import decorators, mixins, permissions, response, viewsets from rest_framework import decorators, mixins, permissions, response, viewsets
from rest_framework.exceptions import NotFound as RestNotFound 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 preferences
from funkwhale_api.common import utils as common_utils from funkwhale_api.common import utils as common_utils
from funkwhale_api.common.permissions import ConditionalAuthentication 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 models as music_models
from funkwhale_api.music import serializers as music_serializers from funkwhale_api.music import serializers as music_serializers
from funkwhale_api.music import views as music_views from funkwhale_api.music import views as music_views
@ -245,10 +248,24 @@ class FetchViewSet(
serializer_class = api_serializers.FetchSerializer serializer_class = api_serializers.FetchSerializer
permission_classes = [permissions.IsAuthenticated] permission_classes = [permissions.IsAuthenticated]
throttling_scopes = {"create": {"authenticated": "fetch"}} throttling_scopes = {"create": {"authenticated": "fetch"}}
renderer_classes = [ActivityStreamRenderer, JSONRenderer]
def get_queryset(self): def get_queryset(self):
return super().get_queryset().filter(actor=self.request.user.actor) 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): def perform_create(self, serializer):
fetch = serializer.save(actor=self.request.user.actor) fetch = serializer.save(actor=self.request.user.actor)
if fetch.status == "finished": if fetch.status == "finished":

View File

@ -1066,6 +1066,7 @@ class LibrarySerializer(PaginatedCollectionSerializer):
privacy = {"": "me", "./": "me", None: "me", contexts.AS.Public: "everyone"} privacy = {"": "me", "./": "me", None: "me", contexts.AS.Public: "everyone"}
library, created = music_models.Library.objects.update_or_create( library, created = music_models.Library.objects.update_or_create(
fid=validated_data["id"], fid=validated_data["id"],
uuid=validated_data["id"].rstrip("/").split("/")[-1],
actor=actor, actor=actor,
defaults={ defaults={
"uploads_count": validated_data["totalItems"], "uploads_count": validated_data["totalItems"],
@ -1449,7 +1450,7 @@ class AlbumSerializer(MusicEntitySerializer):
acs.append( acs.append(
utils.retrieve_ap_object( utils.retrieve_ap_object(
ac["id"], ac["id"],
actor=self.context.get("fetch_actor"), actor=self.context.get("_actor"),
queryset=music_models.ArtistCredit, queryset=music_models.ArtistCredit,
serializer_class=ArtistCreditSerializer, serializer_class=ArtistCreditSerializer,
) )

View File

@ -454,7 +454,6 @@ def fetch(fetch_obj):
max_pages=settings.FEDERATION_COLLECTION_MAX_PAGES - 1, max_pages=settings.FEDERATION_COLLECTION_MAX_PAGES - 1,
is_page=True, is_page=True,
) )
fetch_obj.object = obj fetch_obj.object = obj
fetch_obj.status = "finished" fetch_obj.status = "finished"
fetch_obj.fetch_date = timezone.now() fetch_obj.fetch_date = timezone.now()

View File

@ -173,7 +173,12 @@ def test_fetch_serializer_with_object(
"actor": serializers.APIActorSerializer(fetch.actor).data, "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): def test_fetch_serializer_unhandled_obj(factories, to_api_date):

View File

@ -210,13 +210,19 @@ export default (props: PlayOptionsProps) => {
if (!id) { if (!id) {
throw new Error("Library id not found in response."); throw new Error("Library id not found in response.");
} }
const fetchResponse = await axios.post('federation/fetches', const fetchResponse = await axios.post(
{ object: id } 'federation/fetches',
{ object_uri: id },
{
headers: {
Accept: 'application/activity+json'
}
}
); );
const response = await axios.post( const response = await axios.post(
'federation/follows/library', 'federation/follows/library',
{ target: fetchResponse.data.object.uuid } { target: fetchResponse.data.object.id.split('/').pop() }
); );
return response; return response;

View File

@ -10590,6 +10590,7 @@ export interface operations {
[name: string]: unknown; [name: string]: unknown;
}; };
content: { content: {
"application/activity+json": components["schemas"]["Fetch"];
"application/json": components["schemas"]["Fetch"]; "application/json": components["schemas"]["Fetch"];
}; };
}; };
@ -10612,6 +10613,7 @@ export interface operations {
[name: string]: unknown; [name: string]: unknown;
}; };
content: { content: {
"application/activity+json": components["schemas"]["Fetch"];
"application/json": components["schemas"]["Fetch"]; "application/json": components["schemas"]["Fetch"];
}; };
}; };
@ -17537,6 +17539,7 @@ export interface operations {
[name: string]: unknown; [name: string]: unknown;
}; };
content: { content: {
"application/activity+json": components["schemas"]["Fetch"];
"application/json": components["schemas"]["Fetch"]; "application/json": components["schemas"]["Fetch"];
}; };
}; };
@ -17559,6 +17562,7 @@ export interface operations {
[name: string]: unknown; [name: string]: unknown;
}; };
content: { content: {
"application/activity+json": components["schemas"]["Fetch"];
"application/json": components["schemas"]["Fetch"]; "application/json": components["schemas"]["Fetch"];
}; };
}; };