diff --git a/api/funkwhale_api/federation/actors.py b/api/funkwhale_api/federation/actors.py index 5a4e917bd214083c6c6d35206883a966f14f5fe8..27a418c7dad9ec7f233905b659554df26eb51b6b 100644 --- a/api/funkwhale_api/federation/actors.py +++ b/api/funkwhale_api/federation/actors.py @@ -170,7 +170,7 @@ class SystemActor(object): if not serializer.is_valid(raise_exception=True): return logger.info('Received invalid payload') - serializer.save() + return serializer.save() def handle_undo_follow(self, ac, sender): system_actor = self.get_actor_instance() diff --git a/api/funkwhale_api/federation/migrations/0004_auto_20180410_1624.py b/api/funkwhale_api/federation/migrations/0004_auto_20180410_2025.py similarity index 65% rename from api/funkwhale_api/federation/migrations/0004_auto_20180410_1624.py rename to api/funkwhale_api/federation/migrations/0004_auto_20180410_2025.py index b199706aaf380b91a8977a100aca33053264c303..bea4d14ae6502272486b48bfa6f6f2074e06cb2c 100644 --- a/api/funkwhale_api/federation/migrations/0004_auto_20180410_1624.py +++ b/api/funkwhale_api/federation/migrations/0004_auto_20180410_2025.py @@ -1,6 +1,7 @@ -# Generated by Django 2.0.3 on 2018-04-10 16:24 +# Generated by Django 2.0.3 on 2018-04-10 20:25 from django.db import migrations, models +import django.db.models.deletion class Migration(migrations.Migration): @@ -23,6 +24,11 @@ class Migration(migrations.Migration): name='approved', field=models.NullBooleanField(default=None), ), + migrations.AddField( + model_name='library', + name='follow', + field=models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='library', to='federation.Follow'), + ), migrations.DeleteModel( name='FollowRequest', ), diff --git a/api/funkwhale_api/federation/models.py b/api/funkwhale_api/federation/models.py index 201463066802a526e4978bb73d00344a16b56f1a..7dc9c46e4fa98970357e18ebdf77a61761db186d 100644 --- a/api/funkwhale_api/federation/models.py +++ b/api/funkwhale_api/federation/models.py @@ -137,6 +137,13 @@ class Library(models.Model): # should we automatically import new files from this library? autoimport = models.BooleanField() tracks_count = models.PositiveIntegerField(null=True, blank=True) + follow = models.OneToOneField( + Follow, + related_name='library', + null=True, + blank=True, + on_delete=models.SET_NULL, + ) class LibraryTrack(models.Model): diff --git a/api/funkwhale_api/federation/serializers.py b/api/funkwhale_api/federation/serializers.py index f0d1e35fd1aa2d810b26087d87db8ae92e0587a8..68eef135505b87fad8c60f8227ad544efb2f2812 100644 --- a/api/funkwhale_api/federation/serializers.py +++ b/api/funkwhale_api/federation/serializers.py @@ -8,7 +8,7 @@ from django.db import transaction from rest_framework import serializers from dynamic_preferences.registries import global_preferences_registry -from funkwhale_api.common.utils import set_query_parameter +from funkwhale_api.common import utils as funkwhale_utils from . import activity from . import models @@ -121,6 +121,66 @@ class LibraryActorSerializer(ActorSerializer): return validated_data +class APILibraryCreateSerializer(serializers.ModelSerializer): + actor = serializers.URLField() + + class Meta: + model = models.Library + fields = [ + 'actor', + 'autoimport', + 'federation_enabled', + 'download_files', + ] + + def validate(self, validated_data): + from . import actors + from . import library + + actor_url = validated_data['actor'] + actor_data = actors.get_actor_data(actor_url) + acs = LibraryActorSerializer(data=actor_data) + acs.is_valid(raise_exception=True) + try: + actor = models.Actor.objects.get(url=actor_url) + except models.Actor.DoesNotExist: + actor = acs.save() + library_actor = actors.SYSTEM_ACTORS['library'].get_actor_instance() + validated_data['follow'] = models.Follow.objects.get_or_create( + actor=library_actor, + target=actor, + )[0] + if validated_data['follow'].approved is None: + funkwhale_utils.on_commit( + activity.deliver, + FollowSerializer(validated_data['follow']).data, + on_behalf_of=validated_data['follow'].actor, + to=[validated_data['follow'].target.url], + ) + + library_data = library.get_library_data( + acs.validated_data['library_url']) + if 'errors' in library_data: + raise serializers.ValidationError(str(library_data['errors'])) + validated_data['library'] = library_data + validated_data['actor'] = actor + return validated_data + + def create(self, validated_data): + library = models.Library.objects.get_or_create( + url=validated_data['library']['id'], + defaults={ + 'actor': validated_data['actor'], + 'follow': validated_data['follow'], + 'tracks_count': validated_data['library']['totalItems'], + 'federation_enabled': validated_data['federation_enabled'], + 'autoimport': validated_data['autoimport'], + 'download_files': validated_data['download_files'], + } + )[0] + return library + + class FollowSerializer(serializers.Serializer): id = serializers.URLField() object = serializers.URLField() @@ -163,6 +223,20 @@ class FollowSerializer(serializers.Serializer): return ret +class APIFollowSerializer(serializers.ModelSerializer): + class Meta: + model = models.Follow + fields = [ + 'uuid', + 'id', + 'approved', + 'creation_date', + 'modification_date', + 'actor', + 'target', + ] + + class AcceptFollowSerializer(serializers.Serializer): id = serializers.URLField() actor = serializers.URLField() @@ -244,7 +318,7 @@ class UndoFollowSerializer(serializers.Serializer): } def save(self): - self.validated_data['follow'].delete() + return self.validated_data['follow'].delete() class ActorWebfingerSerializer(serializers.Serializer): @@ -365,9 +439,10 @@ class PaginatedCollectionSerializer(serializers.Serializer): conf['items'], conf.get('page_size', 20) ) - first = set_query_parameter(conf['id'], page=1) + first = funkwhale_utils.set_query_parameter(conf['id'], page=1) current = first - last = set_query_parameter(conf['id'], page=paginator.num_pages) + last = funkwhale_utils.set_query_parameter( + conf['id'], page=paginator.num_pages) d = { 'id': conf['id'], 'actor': conf['actor'].url, @@ -394,9 +469,12 @@ class CollectionPageSerializer(serializers.Serializer): def to_representation(self, conf): page = conf['page'] - first = set_query_parameter(conf['id'], page=1) - last = set_query_parameter(conf['id'], page=page.paginator.num_pages) - id = set_query_parameter(conf['id'], page=page.number) + first = funkwhale_utils.set_query_parameter( + conf['id'], page=1) + last = funkwhale_utils.set_query_parameter( + conf['id'], page=page.paginator.num_pages) + id = funkwhale_utils.set_query_parameter( + conf['id'], page=page.number) d = { 'id': id, 'partOf': conf['id'], @@ -417,11 +495,11 @@ class CollectionPageSerializer(serializers.Serializer): } if page.has_previous(): - d['prev'] = set_query_parameter( + d['prev'] = funkwhale_utils.set_query_parameter( conf['id'], page=page.previous_page_number()) if page.has_next(): - d['next'] = set_query_parameter( + d['next'] = funkwhale_utils.set_query_parameter( conf['id'], page=page.next_page_number()) if self.context.get('include_ap_context', True): diff --git a/api/funkwhale_api/federation/views.py b/api/funkwhale_api/federation/views.py index 2d422047298d382a603952d5d9b6c1cab33ef73e..623fd574f1bf1cd73299043da49c1a582bffc6a3 100644 --- a/api/funkwhale_api/federation/views.py +++ b/api/funkwhale_api/federation/views.py @@ -179,26 +179,7 @@ class LibraryViewSet(viewsets.GenericViewSet): @transaction.atomic def create(self, request, *args, **kwargs): - try: - actor_url = request.data['actor_url'] - except KeyError: - raise ValidationError('Missing actor_url') - - try: - actor = actors.get_actor(actor_url) - library_data = library.get_library_data(actor.url) - except Exception as e: - raise ValidationError('Error while fetching actor and library') - - library_actor = actors.SYSTEM_ACTORS['library'].get_actor_instance() - follow, created = models.Follow.objects.get_or_create( - actor=library_actor, - target=actor, - ) - serializer = serializers.FollowSerializer(follow) - activity.deliver( - serializer.data, - on_behalf_of=library_actor, - to=[actor.url] - ) - return response.Response({}, status=201) + serializer = serializers.APILibraryCreateSerializer(data=request.data) + serializer.is_valid(raise_exception=True) + library = serializer.save() + return response.Response(serializer.data, status=201) diff --git a/api/tests/federation/test_serializers.py b/api/tests/federation/test_serializers.py index e6eca0a42c98724b5c23233bcfc3cf00b4371402..8086a00592c710b2b8ee59e00112cb89bd563c9a 100644 --- a/api/tests/federation/test_serializers.py +++ b/api/tests/federation/test_serializers.py @@ -619,3 +619,46 @@ def test_collection_serializer_to_ap(factories): collection, context={'actor': library, 'id': 'https://test.id'}) assert serializer.data == expected + + +def test_api_library_create_serializer_save(factories, r_mock): + library_actor = actors.SYSTEM_ACTORS['library'].get_actor_instance() + actor = factories['federation.Actor']() + follow = factories['federation.Follow']( + target=actor, + actor=library_actor, + ) + actor_data = serializers.ActorSerializer(actor).data + actor_data['url'] = [{ + 'href': 'https://test.library', + 'name': 'library', + 'type': 'Link', + }] + library_conf = { + 'id': 'https://test.library', + 'items': range(10), + 'actor': actor, + 'page_size': 5, + } + library_data = serializers.PaginatedCollectionSerializer(library_conf).data + r_mock.get(actor.url, json=actor_data) + r_mock.get('https://test.library', json=library_data) + data = { + 'actor': actor.url, + 'autoimport': False, + 'federation_enabled': True, + 'download_files': False, + } + + serializer = serializers.APILibraryCreateSerializer(data=data) + assert serializer.is_valid(raise_exception=True) is True + library = serializer.save() + follow = models.Follow.objects.get( + target=actor, actor=library_actor, approved=None) + + assert library.autoimport is data['autoimport'] + assert library.federation_enabled is data['federation_enabled'] + assert library.download_files is data['download_files'] + assert library.tracks_count == 10 + assert library.actor == actor + assert library.follow == follow diff --git a/api/tests/federation/test_views.py b/api/tests/federation/test_views.py index bd174f721b5fa133be4b80926fd7855a462002b5..99d42566190e1ad4832e467dc348ceeca932d86d 100644 --- a/api/tests/federation/test_views.py +++ b/api/tests/federation/test_views.py @@ -4,6 +4,7 @@ from django.core.paginator import Paginator import pytest from funkwhale_api.federation import actors +from funkwhale_api.federation import activity from funkwhale_api.federation import models from funkwhale_api.federation import serializers from funkwhale_api.federation import utils @@ -182,22 +183,37 @@ def test_can_scan_library(superuser_api_client, mocker): scan.assert_called_once_with('test@test.library') -def test_follow_library_manually(superuser_api_client, mocker, factories): +def test_follow_library(superuser_api_client, mocker, factories, r_mock): library_actor = actors.SYSTEM_ACTORS['library'].get_actor_instance() - actor = factories['federation.Actor'](manually_approves_followers=True) + actor = factories['federation.Actor']() follow = {'test': 'follow'} - deliver = mocker.patch( - 'funkwhale_api.federation.activity.deliver') - actor_get = mocker.patch( - 'funkwhale_api.federation.actors.get_actor', - return_value=actor) - library_get = mocker.patch( - 'funkwhale_api.federation.library.get_library_data', - return_value={}) + on_commit = mocker.patch( + 'funkwhale_api.common.utils.on_commit') + actor_data = serializers.ActorSerializer(actor).data + actor_data['url'] = [{ + 'href': 'https://test.library', + 'name': 'library', + 'type': 'Link', + }] + library_conf = { + 'id': 'https://test.library', + 'items': range(10), + 'actor': actor, + 'page_size': 5, + } + library_data = serializers.PaginatedCollectionSerializer(library_conf).data + r_mock.get(actor.url, json=actor_data) + r_mock.get('https://test.library', json=library_data) + data = { + 'actor': actor.url, + 'autoimport': False, + 'federation_enabled': True, + 'download_files': False, + } url = reverse('api:v1:federation:libraries-list') response = superuser_api_client.post( - url, {'actor_url': actor.url}) + url, data) assert response.status_code == 201 @@ -206,8 +222,13 @@ def test_follow_library_manually(superuser_api_client, mocker, factories): target=actor, approved=None, ) + library = follow.library + + assert response.data == serializers.APILibraryCreateSerializer( + library).data - deliver.assert_called_once_with( + on_commit.assert_called_once_with( + activity.deliver, serializers.FollowSerializer(follow).data, on_behalf_of=library_actor, to=[actor.url]