diff --git a/api/funkwhale_api/federation/models.py b/api/funkwhale_api/federation/models.py
index 7f8121641..fa285f3cf 100644
--- a/api/funkwhale_api/federation/models.py
+++ b/api/funkwhale_api/federation/models.py
@@ -388,16 +388,19 @@ class Fetch(models.Model):
from . import serializers
return {
- contexts.FW.Artist: serializers.ArtistSerializer,
- contexts.FW.Album: serializers.AlbumSerializer,
- contexts.FW.Track: serializers.TrackSerializer,
- contexts.AS.Audio: serializers.UploadSerializer,
- contexts.FW.Library: serializers.LibrarySerializer,
- contexts.AS.Group: serializers.ActorSerializer,
- contexts.AS.Person: serializers.ActorSerializer,
- contexts.AS.Organization: serializers.ActorSerializer,
- contexts.AS.Service: serializers.ActorSerializer,
- contexts.AS.Application: serializers.ActorSerializer,
+ contexts.FW.Artist: [serializers.ArtistSerializer],
+ contexts.FW.Album: [serializers.AlbumSerializer],
+ contexts.FW.Track: [serializers.TrackSerializer],
+ contexts.AS.Audio: [
+ serializers.UploadSerializer,
+ serializers.ChannelUploadSerializer,
+ ],
+ contexts.FW.Library: [serializers.LibrarySerializer],
+ contexts.AS.Group: [serializers.ActorSerializer],
+ contexts.AS.Person: [serializers.ActorSerializer],
+ contexts.AS.Organization: [serializers.ActorSerializer],
+ contexts.AS.Service: [serializers.ActorSerializer],
+ contexts.AS.Application: [serializers.ActorSerializer],
}
diff --git a/api/funkwhale_api/federation/serializers.py b/api/funkwhale_api/federation/serializers.py
index 313f225f7..33ecec3ab 100644
--- a/api/funkwhale_api/federation/serializers.py
+++ b/api/funkwhale_api/federation/serializers.py
@@ -1766,6 +1766,7 @@ class ChannelUploadSerializer(jsonld.JsonLdSerializer):
disc = serializers.IntegerField(min_value=1, allow_null=True, required=False)
album = serializers.URLField(max_length=500, required=False)
license = serializers.URLField(allow_null=True, required=False)
+ attributedTo = serializers.URLField(max_length=500, required=False)
copyright = TruncatedCharField(
truncate_length=music_models.MAX_LENGTHS["COPYRIGHT"],
allow_null=True,
@@ -1808,9 +1809,10 @@ class ChannelUploadSerializer(jsonld.JsonLdSerializer):
"position": jsonld.first_val(contexts.FW.position),
"image": jsonld.first_obj(contexts.AS.image),
"tags": jsonld.raw(contexts.AS.tag),
+ "attributedTo": jsonld.first_id(contexts.AS.attributedTo),
}
- def validate_album(self, v):
+ def _validate_album(self, v):
return utils.retrieve_ap_object(
v,
actor=actors.get_service_actor(),
@@ -1821,6 +1823,17 @@ class ChannelUploadSerializer(jsonld.JsonLdSerializer):
)
def validate(self, data):
+ if not self.context.get("channel"):
+ if not data.get("attributedTo"):
+ raise serializers.ValidationError(
+ "Missing channel context and no attributedTo available"
+ )
+ actor = actors.get_actor(data["attributedTo"])
+ if not actor.get_channel():
+ raise serializers.ValidationError("Not a channel")
+ self.context["channel"] = actor.get_channel()
+ if data.get("album"):
+ data["album"] = self._validate_album(data["album"])
validated_data = super().validate(data)
if data.get("content"):
validated_data["description"] = {
diff --git a/api/funkwhale_api/federation/tasks.py b/api/funkwhale_api/federation/tasks.py
index b84e4c5f9..6ab04db0d 100644
--- a/api/funkwhale_api/federation/tasks.py
+++ b/api/funkwhale_api/federation/tasks.py
@@ -374,8 +374,8 @@ def fetch(fetch_obj):
except IndexError:
return error("missing_jsonld_type")
try:
- serializer_class = fetch_obj.serializers[type]
- model = serializer_class.Meta.model
+ serializer_classes = fetch_obj.serializers[type]
+ model = serializer_classes[0].Meta.model
except (KeyError, AttributeError):
fetch_obj.status = "skipped"
fetch_obj.fetch_date = timezone.now()
@@ -388,8 +388,14 @@ def fetch(fetch_obj):
else:
existing = model.objects.filter(fid=id).first()
- serializer = serializer_class(existing, data=payload)
- if not serializer.is_valid():
+ serializer = None
+ for serializer_class in serializer_classes:
+ serializer = serializer_class(existing, data=payload)
+ if not serializer.is_valid():
+ continue
+ else:
+ break
+ if serializer.errors:
return error("validation", validation_errors=serializer.errors)
try:
obj = serializer.save()
diff --git a/api/funkwhale_api/music/factories.py b/api/funkwhale_api/music/factories.py
index 8537c5c50..14bcecb86 100644
--- a/api/funkwhale_api/music/factories.py
+++ b/api/funkwhale_api/music/factories.py
@@ -185,6 +185,16 @@ class UploadFactory(NoUpdateOnCreate, factory.django.DjangoModelFactory):
import_status="finished", library__privacy_level="everyone"
)
+ @factory.post_generation
+ def channel(self, created, extracted, **kwargs):
+ if not extracted:
+ return
+ from funkwhale_api.audio import factories as audio_factories
+
+ audio_factories.ChannelFactory(
+ library=self.library, artist=self.track.artist, **kwargs
+ )
+
@registry.register
class UploadVersionFactory(NoUpdateOnCreate, factory.django.DjangoModelFactory):
diff --git a/api/tests/federation/test_tasks.py b/api/tests/federation/test_tasks.py
index ce4736503..79573ff45 100644
--- a/api/tests/federation/test_tasks.py
+++ b/api/tests/federation/test_tasks.py
@@ -461,17 +461,25 @@ def test_fetch_rel_alternate(factories, r_mock, mocker):
@pytest.mark.parametrize(
- "factory_name, serializer_class",
+ "factory_name, factory_kwargs, serializer_class",
[
- ("federation.Actor", serializers.ActorSerializer),
- ("music.Library", serializers.LibrarySerializer),
- ("music.Artist", serializers.ArtistSerializer),
- ("music.Album", serializers.AlbumSerializer),
- ("music.Track", serializers.TrackSerializer),
+ ("federation.Actor", {}, serializers.ActorSerializer),
+ ("music.Library", {}, serializers.LibrarySerializer),
+ ("music.Artist", {}, serializers.ArtistSerializer),
+ ("music.Album", {}, serializers.AlbumSerializer),
+ ("music.Track", {}, serializers.TrackSerializer),
+ (
+ "music.Upload",
+ {"bitrate": 200, "duration": 20},
+ serializers.UploadSerializer,
+ ),
+ ("music.Upload", {"channel": True}, serializers.ChannelUploadSerializer),
],
)
-def test_fetch_url(factory_name, serializer_class, factories, r_mock, mocker):
- obj = factories[factory_name]()
+def test_fetch_url(
+ factory_name, factory_kwargs, serializer_class, factories, r_mock, mocker
+):
+ obj = factories[factory_name](**factory_kwargs)
fetch = factories["federation.Fetch"](url=obj.fid)
payload = serializer_class(obj).data
init = mocker.spy(serializer_class, "__init__")
diff --git a/front/src/components/library/UploadDetail.vue b/front/src/components/library/UploadDetail.vue
new file mode 100644
index 000000000..ce3a1e3a8
--- /dev/null
+++ b/front/src/components/library/UploadDetail.vue
@@ -0,0 +1,28 @@
+
+
+
+
+
+
+
diff --git a/front/src/router/index.js b/front/src/router/index.js
index 4581dd0d7..5c6a42e24 100644
--- a/front/src/router/index.js
+++ b/front/src/router/index.js
@@ -838,6 +838,15 @@ export default new Router({
}
]
},
+ {
+ path: "uploads/:id",
+ name: "library.uploads.detail",
+ props: true,
+ component: () =>
+ import(
+ /* webpackChunkName: "uploads" */ "@/components/library/UploadDetail"
+ ),
+ },
{
// browse a single library via it's uuid
path: ":id([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12})",