kopia lustrzana https://dev.funkwhale.audio/funkwhale/funkwhale
feat(back):trigger public library follow on user follow
rodzic
b41c7fbaf1
commit
4db52025b9
|
@ -9,6 +9,7 @@ from rest_framework.exceptions import NotFound as RestNotFound
|
|||
from funkwhale_api.common import preferences
|
||||
from funkwhale_api.common import utils as common_utils
|
||||
from funkwhale_api.common.permissions import ConditionalAuthentication
|
||||
from funkwhale_api.federation import actors
|
||||
from funkwhale_api.music import models as music_models
|
||||
from funkwhale_api.music import serializers as music_serializers
|
||||
from funkwhale_api.music import views as music_views
|
||||
|
@ -359,6 +360,15 @@ class UserFollowViewSet(
|
|||
def perform_create(self, serializer):
|
||||
follow = serializer.save(actor=self.request.user.actor)
|
||||
routes.outbox.dispatch({"type": "Follow"}, context={"follow": follow})
|
||||
if not follow.target.is_local:
|
||||
public_lib = utils.get_or_create_buildin_actor_library(
|
||||
follow.target, privacy_level="everyone"
|
||||
)
|
||||
lib_follow = models.LibraryFollow.objects.create(
|
||||
actor=actors.get_service_actor(),
|
||||
target=public_lib,
|
||||
)
|
||||
routes.outbox.dispatch({"type": "Follow"}, context={"follow": lib_follow})
|
||||
|
||||
@transaction.atomic
|
||||
def perform_destroy(self, instance):
|
||||
|
|
|
@ -290,7 +290,52 @@ def can_manage(obj_owner, actor):
|
|||
return False
|
||||
|
||||
|
||||
<<<<<<< HEAD
|
||||
def update_actor_privacy(actor, privacy_level):
|
||||
actor.track_favorites.update(privacy_level=privacy_level)
|
||||
actor.listenings.update(privacy_level=privacy_level)
|
||||
# to do : trigger federation privacy_level downgrade #2336
|
||||
=======
|
||||
class BuildInLibException(Exception):
|
||||
pass
|
||||
|
||||
|
||||
def get_or_create_builtin_actor_library(actor, privacy_level):
|
||||
from funkwhale_api.music import models as music_models
|
||||
|
||||
from . import actors
|
||||
|
||||
service_actor = actors.get_service_actor()
|
||||
auth = signing.get_auth(service_actor.private_key, service_actor.private_key_id)
|
||||
response = session.get_session().get(
|
||||
f"https://{actor.domain}/api/v2/federation/music/libraries",
|
||||
auth=auth,
|
||||
params={
|
||||
"actor": actor.preferred_username,
|
||||
"privacy_level": privacy_level,
|
||||
"name": privacy_level,
|
||||
},
|
||||
headers={
|
||||
"Accept": "application/activity+json",
|
||||
"Content-Type": "application/activity+json",
|
||||
},
|
||||
)
|
||||
response.raise_for_status()
|
||||
data = response.json()
|
||||
if len(data["results"]) == 0:
|
||||
raise BuildInLibException(
|
||||
f"Could not find built-in lib {privacy_level} for actor {actor}"
|
||||
)
|
||||
elif not len(data["results"]) == 1:
|
||||
raise BuildInLibException(
|
||||
f"Too many built-in lib {privacy_level} for actor {actor}"
|
||||
)
|
||||
else:
|
||||
lib, created = music_models.Library.objects.get_or_create(
|
||||
actor=actor,
|
||||
playlist__isnull=True,
|
||||
privacy_level="everyone",
|
||||
name="everyone",
|
||||
)
|
||||
return lib
|
||||
>>>>>>> afc77ed5e (feat(back):trigger public library follow on user follow)
|
||||
|
|
|
@ -14,6 +14,7 @@ from funkwhale_api.favorites import models as favorites_models
|
|||
from funkwhale_api.federation import utils as federation_utils
|
||||
from funkwhale_api.history import models as history_models
|
||||
from funkwhale_api.moderation import models as moderation_models
|
||||
from funkwhale_api.music import filters as music_filters
|
||||
from funkwhale_api.music import models as music_models
|
||||
from funkwhale_api.music import utils as music_utils
|
||||
from funkwhale_api.playlists import models as playlists_models
|
||||
|
@ -380,7 +381,10 @@ def has_playlist_access(request, playlist):
|
|||
|
||||
|
||||
class MusicLibraryViewSet(
|
||||
FederationMixin, mixins.RetrieveModelMixin, viewsets.GenericViewSet
|
||||
FederationMixin,
|
||||
mixins.RetrieveModelMixin,
|
||||
viewsets.GenericViewSet,
|
||||
mixins.ListModelMixin,
|
||||
):
|
||||
authentication_classes = [authentication.SignatureAuthentication]
|
||||
renderer_classes = renderers.get_ap_renderers()
|
||||
|
@ -391,6 +395,8 @@ class MusicLibraryViewSet(
|
|||
.select_related("actor")
|
||||
.filter(channel=None)
|
||||
)
|
||||
|
||||
filterset_class = music_filters.LibraryFilter
|
||||
lookup_field = "uuid"
|
||||
|
||||
def retrieve(self, request, *args, **kwargs):
|
||||
|
|
|
@ -387,7 +387,18 @@ class LibraryFilter(filters.FilterSet):
|
|||
distinct=True,
|
||||
library_field="pk",
|
||||
)
|
||||
actor = filters.CharFilter(method="filter_actor")
|
||||
|
||||
class Meta:
|
||||
model = models.Library
|
||||
fields = ["privacy_level"]
|
||||
|
||||
def filter_actor(self, queryset, name, value):
|
||||
# supports username or username@domain
|
||||
if "@" in value:
|
||||
username, domain = value.split("@", 1)
|
||||
return queryset.filter(
|
||||
actor__preferred_username=username,
|
||||
actor__domain_id=domain,
|
||||
)
|
||||
return queryset.filter(actor__preferred_username=value)
|
||||
|
|
|
@ -1,9 +1,17 @@
|
|||
import datetime
|
||||
from unittest.mock import Mock
|
||||
|
||||
import pytest
|
||||
from django.urls import reverse
|
||||
|
||||
from funkwhale_api.federation import api_serializers, serializers, tasks, views
|
||||
from funkwhale_api.federation import (
|
||||
actors,
|
||||
api_serializers,
|
||||
models,
|
||||
serializers,
|
||||
tasks,
|
||||
views,
|
||||
)
|
||||
|
||||
|
||||
def test_user_can_list_their_library_follows(factories, logged_in_api_client):
|
||||
|
@ -457,3 +465,33 @@ def test_user_can_accept_or_reject_own_received_follows(
|
|||
mocked_dispatch.assert_called_once_with(
|
||||
{"type": action.title()}, context={"follow": follow}
|
||||
)
|
||||
|
||||
|
||||
def test_following_using_trigger_service_actor_lib_follow(
|
||||
factories, logged_in_api_client, mocker
|
||||
):
|
||||
target_actor = factories["federation.Actor"]()
|
||||
lib = factories["music.Library"](actor=target_actor, privacy_level="everyone")
|
||||
mocked_dispatch = mocker.patch(
|
||||
"funkwhale_api.federation.activity.OutboxRouter.dispatch"
|
||||
)
|
||||
mock_session = Mock()
|
||||
mock_response = Mock()
|
||||
mock_response.raise_for_status.return_value = None
|
||||
mock_response.json.side_effect = [
|
||||
{"results": [serializers.LibrarySerializer(lib).data]}
|
||||
]
|
||||
mock_session.get.return_value = mock_response
|
||||
mocker.patch(
|
||||
"funkwhale_api.federation.utils.session.get_session",
|
||||
return_value=mock_session,
|
||||
)
|
||||
lib.delete()
|
||||
url = reverse("api:v1:federation:user-follows-list")
|
||||
logged_in_api_client.user.create_actor()
|
||||
logged_in_api_client.post(url, {"target": target_actor.fid})
|
||||
|
||||
service_follow = models.LibraryFollow.objects.get(actor=actors.get_service_actor())
|
||||
mocked_dispatch.assert_called_with(
|
||||
{"type": "Follow"}, context={"follow": service_follow}
|
||||
)
|
||||
|
|
|
@ -204,6 +204,22 @@ sequenceDiagram
|
|||
|
||||
When a **requesting user** unfollows a **target user**, the UI must update to visually indicate that the action has succeeded. All activities relating to the **target user** must be visually hidden.
|
||||
|
||||
### Get user upload to remote pod
|
||||
|
||||
When you follow a user you expect to have access to its public and followers content.
|
||||
|
||||
#### Public content (#2422)
|
||||
|
||||
- When create a `UserFollow` we also send a `LibraryFollow` for the public user Library, using the service actor of the local pod. This will allow the remote pod to get access to the content.
|
||||
- When deleting a `UserFollow` we keep the service actor follow, in case other users in the pod use the remote metadata.
|
||||
|
||||
#### Followers content (#2536)
|
||||
|
||||
As a user I want to share uploads with all my followers.
|
||||
|
||||
- Create a new built-in library with `followers` privacy_level
|
||||
- Add the `followers` privacy_level in `ManageUploads` vue component
|
||||
|
||||
## Availability
|
||||
|
||||
- [x] App frontend
|
||||
|
|
Ładowanie…
Reference in New Issue