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):
additional_attributes = {}
manually_approves_followers = False
def serialize(self):
actor = self.get_actor_instance()
serializer = serializers.ActorSerializer()
return serializer.data
def get_actor_instance(self):
args = self.get_instance_argument(
self.id,
......@@ -172,6 +177,17 @@ class LibraryActor(SystemActor):
'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
def manually_approves_followers(self):
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
router = routers.SimpleRouter(trailing_slash=False)
music_router = routers.SimpleRouter(trailing_slash=False)
router.register(
r'federation/instance/actors',
views.InstanceActorViewSet,
......@@ -12,4 +14,11 @@ router.register(
views.WellKnownViewSet,
'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.conf import settings
from django.core import paginator
from django.http import HttpResponse
from django.urls import reverse
from rest_framework import viewsets
from rest_framework import views
from rest_framework import response
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 authentication
from . import permissions
from . import renderers
from . import serializers
from . import utils
from . import webfinger
......@@ -38,8 +45,8 @@ class InstanceActorViewSet(FederationMixin, viewsets.GenericViewSet):
def retrieve(self, request, *args, **kwargs):
system_actor = self.get_object()
actor = system_actor.get_actor_instance()
serializer = serializers.ActorSerializer(actor)
return response.Response(serializer.data, status=200)
data = actor.system_conf.serialize()
return response.Response(data, status=200)
@detail_route(methods=['get', 'post'])
def inbox(self, request, *args, **kwargs):
......@@ -101,3 +108,47 @@ class WellKnownViewSet(FederationMixin, viewsets.GenericViewSet):
username, hostname = clean_result
actor = actors.SYSTEM_ACTORS[username].get_actor_instance()
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.core.paginator import Paginator
import pytest
from funkwhale_api.federation import actors
from funkwhale_api.federation import serializers
from funkwhale_api.federation import utils
from funkwhale_api.federation import webfinger
from funkwhale_api.music.serializers import AudioSerializer
@pytest.mark.parametrize('system_actor', actors.SYSTEM_ACTORS.keys())
......@@ -62,3 +64,89 @@ def test_wellknown_webfinger_system(
assert response.status_code == 200
assert response['Content-Type'] == 'application/jrd+json'
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