Skip to content
Snippets Groups Projects
Verified Commit a03f0ffe authored by Eliot Berriot's avatar Eliot Berriot
Browse files

We now have a library browsable via activitypub

parent 393110a7
No related branches found
No related tags found
No related merge requests found
...@@ -50,6 +50,11 @@ class SystemActor(object): ...@@ -50,6 +50,11 @@ class SystemActor(object):
additional_attributes = {} additional_attributes = {}
manually_approves_followers = False manually_approves_followers = False
def serialize(self):
actor = self.get_actor_instance()
serializer = serializers.ActorSerializer()
return serializer.data
def get_actor_instance(self): def get_actor_instance(self):
args = self.get_instance_argument( args = self.get_instance_argument(
self.id, self.id,
...@@ -172,6 +177,17 @@ class LibraryActor(SystemActor): ...@@ -172,6 +177,17 @@ class LibraryActor(SystemActor):
'manually_approves_followers': True 'manually_approves_followers': True
} }
def serialize(self):
data = super().serialize()
urls = data.setdefault('url', [])
urls.append({
'type': 'Link',
'mediaType': 'application/activity+json',
'name': 'library',
'href': utils.full_url(reverse('federation:music:files-list'))
})
return data
@property @property
def manually_approves_followers(self): def manually_approves_followers(self):
return settings.FEDERATION_MUSIC_NEEDS_APPROVAL return settings.FEDERATION_MUSIC_NEEDS_APPROVAL
......
from rest_framework import routers from django.conf.urls import include, url
from rest_framework import routers
from . import views from . import views
router = routers.SimpleRouter(trailing_slash=False) router = routers.SimpleRouter(trailing_slash=False)
music_router = routers.SimpleRouter(trailing_slash=False)
router.register( router.register(
r'federation/instance/actors', r'federation/instance/actors',
views.InstanceActorViewSet, views.InstanceActorViewSet,
...@@ -12,4 +14,11 @@ router.register( ...@@ -12,4 +14,11 @@ router.register(
views.WellKnownViewSet, views.WellKnownViewSet,
'well-known') 'well-known')
urlpatterns = router.urls music_router.register(
r'federation/files',
views.MusicFilesViewSet,
'files',
)
urlpatterns = router.urls + [
url('music/', include((music_router.urls, 'music'), namespace='music'))
]
from django import forms from django import forms
from django.conf import settings from django.conf import settings
from django.core import paginator
from django.http import HttpResponse from django.http import HttpResponse
from django.urls import reverse
from rest_framework import viewsets from rest_framework import viewsets
from rest_framework import views from rest_framework import views
from rest_framework import response from rest_framework import response
from rest_framework.decorators import list_route, detail_route from rest_framework.decorators import list_route, detail_route
from funkwhale_api.music.models import TrackFile
from funkwhale_api.music.serializers import AudioSerializer
from . import actors from . import actors
from . import authentication from . import authentication
from . import permissions
from . import renderers from . import renderers
from . import serializers from . import serializers
from . import utils
from . import webfinger from . import webfinger
...@@ -38,8 +45,8 @@ class InstanceActorViewSet(FederationMixin, viewsets.GenericViewSet): ...@@ -38,8 +45,8 @@ class InstanceActorViewSet(FederationMixin, viewsets.GenericViewSet):
def retrieve(self, request, *args, **kwargs): def retrieve(self, request, *args, **kwargs):
system_actor = self.get_object() system_actor = self.get_object()
actor = system_actor.get_actor_instance() actor = system_actor.get_actor_instance()
serializer = serializers.ActorSerializer(actor) data = actor.system_conf.serialize()
return response.Response(serializer.data, status=200) return response.Response(data, status=200)
@detail_route(methods=['get', 'post']) @detail_route(methods=['get', 'post'])
def inbox(self, request, *args, **kwargs): def inbox(self, request, *args, **kwargs):
...@@ -101,3 +108,47 @@ class WellKnownViewSet(FederationMixin, viewsets.GenericViewSet): ...@@ -101,3 +108,47 @@ class WellKnownViewSet(FederationMixin, viewsets.GenericViewSet):
username, hostname = clean_result username, hostname = clean_result
actor = actors.SYSTEM_ACTORS[username].get_actor_instance() actor = actors.SYSTEM_ACTORS[username].get_actor_instance()
return serializers.ActorWebfingerSerializer(actor).data return serializers.ActorWebfingerSerializer(actor).data
class MusicFilesViewSet(FederationMixin, viewsets.GenericViewSet):
authentication_classes = [
authentication.SignatureAuthentication]
permission_classes = [permissions.LibraryFollower]
renderer_classes = [renderers.ActivityPubRenderer]
def list(self, request, *args, **kwargs):
page = request.GET.get('page')
library = actors.SYSTEM_ACTORS['library'].get_actor_instance()
qs = TrackFile.objects.order_by('-creation_date')
if page is None:
conf = {
'id': utils.full_url(reverse('federation:music:files-list')),
'page_size': settings.FEDERATION_COLLECTION_PAGE_SIZE,
'items': qs,
'item_serializer': AudioSerializer,
'actor': library,
}
serializer = serializers.PaginatedCollectionSerializer(conf)
data = serializer.data
else:
try:
page_number = int(page)
except:
return response.Response(
{'page': ['Invalid page number']}, status=400)
p = paginator.Paginator(
qs, settings.FEDERATION_COLLECTION_PAGE_SIZE)
try:
page = p.page(page_number)
except paginator.EmptyPage:
return response.Response(status=404)
conf = {
'id': utils.full_url(reverse('federation:music:files-list')),
'page': page,
'item_serializer': AudioSerializer,
'actor': library,
}
serializer = serializers.CollectionPageSerializer(conf)
data = serializer.data
return response.Response(data)
from django.urls import reverse from django.urls import reverse
from django.core.paginator import Paginator
import pytest import pytest
from funkwhale_api.federation import actors from funkwhale_api.federation import actors
from funkwhale_api.federation import serializers from funkwhale_api.federation import serializers
from funkwhale_api.federation import utils
from funkwhale_api.federation import webfinger from funkwhale_api.federation import webfinger
from funkwhale_api.music.serializers import AudioSerializer
@pytest.mark.parametrize('system_actor', actors.SYSTEM_ACTORS.keys()) @pytest.mark.parametrize('system_actor', actors.SYSTEM_ACTORS.keys())
...@@ -62,3 +64,89 @@ def test_wellknown_webfinger_system( ...@@ -62,3 +64,89 @@ def test_wellknown_webfinger_system(
assert response.status_code == 200 assert response.status_code == 200
assert response['Content-Type'] == 'application/jrd+json' assert response['Content-Type'] == 'application/jrd+json'
assert response.data == serializer.data assert response.data == serializer.data
def test_audio_file_list_requires_authenticated_actor(
db, settings, api_client):
settings.FEDERATION_MUSIC_NEEDS_APPROVAL = True
url = reverse('federation:music:files-list')
response = api_client.get(url)
assert response.status_code == 403
def test_audio_file_list_actor_no_page(
db, settings, api_client, factories):
settings.FEDERATION_MUSIC_NEEDS_APPROVAL = False
settings.FEDERATION_COLLECTION_PAGE_SIZE = 2
library = actors.SYSTEM_ACTORS['library'].get_actor_instance()
tfs = factories['music.TrackFile'].create_batch(size=5)
conf = {
'id': utils.full_url(reverse('federation:music:files-list')),
'page_size': 2,
'items': list(reversed(tfs)), # we order by -creation_date
'item_serializer': AudioSerializer,
'actor': library
}
expected = serializers.PaginatedCollectionSerializer(conf).data
url = reverse('federation:music:files-list')
response = api_client.get(url)
assert response.status_code == 200
assert response.data == expected
def test_audio_file_list_actor_page(
db, settings, api_client, factories):
settings.FEDERATION_MUSIC_NEEDS_APPROVAL = False
settings.FEDERATION_COLLECTION_PAGE_SIZE = 2
library = actors.SYSTEM_ACTORS['library'].get_actor_instance()
tfs = factories['music.TrackFile'].create_batch(size=5)
conf = {
'id': utils.full_url(reverse('federation:music:files-list')),
'page': Paginator(list(reversed(tfs)), 2).page(2),
'item_serializer': AudioSerializer,
'actor': library
}
expected = serializers.CollectionPageSerializer(conf).data
url = reverse('federation:music:files-list')
response = api_client.get(url, data={'page': 2})
assert response.status_code == 200
assert response.data == expected
def test_audio_file_list_actor_page_error(
db, settings, api_client, factories):
settings.FEDERATION_MUSIC_NEEDS_APPROVAL = False
url = reverse('federation:music:files-list')
response = api_client.get(url, data={'page': 'nope'})
assert response.status_code == 400
def test_audio_file_list_actor_page_error_too_far(
db, settings, api_client, factories):
settings.FEDERATION_MUSIC_NEEDS_APPROVAL = False
url = reverse('federation:music:files-list')
response = api_client.get(url, data={'page': 5000})
assert response.status_code == 404
def test_library_actor_includes_library_link(db, settings, api_client):
actor = actors.SYSTEM_ACTORS['library'].get_actor_instance()
url = reverse(
'federation:instance-actors-detail',
kwargs={'actor': 'library'})
response = api_client.get(url)
expected_links = [
{
'type': 'Link',
'name': 'library',
'mediaType': 'application/activity+json',
'href': utils.full_url(reverse('federation:music:files-list'))
}
]
assert response.status_code == 200
assert response.data['url'] == expected_links
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment