diff --git a/api/funkwhale_api/manage/filters.py b/api/funkwhale_api/manage/filters.py index e4cda18c..16ee5c16 100644 --- a/api/funkwhale_api/manage/filters.py +++ b/api/funkwhale_api/manage/filters.py @@ -1,4 +1,3 @@ - from django_filters import rest_framework as filters from funkwhale_api.common import fields @@ -37,3 +36,11 @@ class ManageUserFilterSet(filters.FilterSet): "permission_settings", "permission_federation", ] + + +class ManageInvitationFilterSet(filters.FilterSet): + q = fields.SearchFilter(search_fields=["owner__username", "code", "owner__email"]) + + class Meta: + model = users_models.Invitation + fields = ["q"] diff --git a/api/funkwhale_api/manage/serializers.py b/api/funkwhale_api/manage/serializers.py index 6e57db81..e8f1e328 100644 --- a/api/funkwhale_api/manage/serializers.py +++ b/api/funkwhale_api/manage/serializers.py @@ -78,6 +78,23 @@ class PermissionsSerializer(serializers.Serializer): return {"permissions": o} +class ManageUserSimpleSerializer(serializers.ModelSerializer): + class Meta: + model = users_models.User + fields = ( + "id", + "username", + "email", + "name", + "is_active", + "is_staff", + "is_superuser", + "date_joined", + "last_activity", + "privacy_level", + ) + + class ManageUserSerializer(serializers.ModelSerializer): permissions = PermissionsSerializer(source="*") @@ -115,3 +132,23 @@ class ManageUserSerializer(serializers.ModelSerializer): update_fields=["permission_{}".format(p) for p in permissions.keys()] ) return instance + + +class ManageInvitationSerializer(serializers.ModelSerializer): + users = ManageUserSimpleSerializer(many=True, required=False) + owner = ManageUserSimpleSerializer(required=False) + code = serializers.CharField(required=False, allow_null=True) + + class Meta: + model = users_models.Invitation + fields = ("id", "owner", "code", "expiration_date", "creation_date", "users") + read_only_fields = ["id", "expiration_date", "owner", "creation_date", "users"] + + def validate_code(self, value): + if not value: + return value + if users_models.Invitation.objects.filter(code=value.lower()).exists(): + raise serializers.ValidationError( + "An invitation with this code already exists" + ) + return value diff --git a/api/funkwhale_api/manage/urls.py b/api/funkwhale_api/manage/urls.py index f208fb85..3d4e15db 100644 --- a/api/funkwhale_api/manage/urls.py +++ b/api/funkwhale_api/manage/urls.py @@ -7,6 +7,7 @@ library_router = routers.SimpleRouter() library_router.register(r"track-files", views.ManageTrackFileViewSet, "track-files") users_router = routers.SimpleRouter() users_router.register(r"users", views.ManageUserViewSet, "users") +users_router.register(r"invitations", views.ManageInvitationViewSet, "invitations") urlpatterns = [ url(r"^library/", include((library_router.urls, "instance"), namespace="library")), diff --git a/api/funkwhale_api/manage/views.py b/api/funkwhale_api/manage/views.py index f9b78ef8..803f8db7 100644 --- a/api/funkwhale_api/manage/views.py +++ b/api/funkwhale_api/manage/views.py @@ -62,3 +62,27 @@ class ManageUserViewSet( context = super().get_serializer_context() context["default_permissions"] = preferences.get("users__default_permissions") return context + + +class ManageInvitationViewSet( + mixins.CreateModelMixin, + mixins.ListModelMixin, + mixins.RetrieveModelMixin, + mixins.UpdateModelMixin, + mixins.DestroyModelMixin, + viewsets.GenericViewSet, +): + queryset = ( + users_models.Invitation.objects.all() + .order_by("-id") + .prefetch_related("users") + .select_related("owner") + ) + serializer_class = serializers.ManageInvitationSerializer + filter_class = filters.ManageInvitationFilterSet + permission_classes = (HasUserPermission,) + required_permissions = ["settings"] + ordering_fields = ["creation_date", "expiration_date"] + + def perform_create(self, serializer): + serializer.save(owner=self.request.user) diff --git a/api/tests/manage/test_views.py b/api/tests/manage/test_views.py index a72bcf5a..d54fca5d 100644 --- a/api/tests/manage/test_views.py +++ b/api/tests/manage/test_views.py @@ -9,6 +9,7 @@ from funkwhale_api.manage import serializers, views [ (views.ManageTrackFileViewSet, ["library"], "and"), (views.ManageUserViewSet, ["settings"], "and"), + (views.ManageInvitationViewSet, ["settings"], "and"), ], ) def test_permissions(assert_user_permission, view, permissions, operator): @@ -42,3 +43,23 @@ def test_user_view(factories, superuser_api_client, mocker): assert response.data["count"] == len(users) assert response.data["results"] == expected + + +def test_invitation_view(factories, superuser_api_client, mocker): + invitations = factories["users.Invitation"].create_batch(size=5) + qs = invitations[0].__class__.objects.order_by("-id") + url = reverse("api:v1:manage:users:invitations-list") + + response = superuser_api_client.get(url, {"sort": "-id"}) + expected = serializers.ManageInvitationSerializer(qs, many=True).data + + assert response.data["count"] == len(invitations) + assert response.data["results"] == expected + + +def test_invitation_view_create(factories, superuser_api_client, mocker): + url = reverse("api:v1:manage:users:invitations-list") + response = superuser_api_client.post(url) + + assert response.status_code == 201 + assert superuser_api_client.user.invitations.latest("id") is not None diff --git a/front/src/components/Sidebar.vue b/front/src/components/Sidebar.vue index 03ea4ee0..87c374a3 100644 --- a/front/src/components/Sidebar.vue +++ b/front/src/components/Sidebar.vue @@ -99,7 +99,7 @@ + :to="{name: 'manage.users.users.list'}"> {{ $t('Users') }} diff --git a/front/src/components/manage/users/InvitationForm.vue b/front/src/components/manage/users/InvitationForm.vue new file mode 100644 index 00000000..ffd5a7d1 --- /dev/null +++ b/front/src/components/manage/users/InvitationForm.vue @@ -0,0 +1,82 @@ + + + + + diff --git a/front/src/components/manage/users/InvitationsTable.vue b/front/src/components/manage/users/InvitationsTable.vue new file mode 100644 index 00000000..e9b46cc2 --- /dev/null +++ b/front/src/components/manage/users/InvitationsTable.vue @@ -0,0 +1,180 @@ + + + diff --git a/front/src/components/manage/users/UsersTable.vue b/front/src/components/manage/users/UsersTable.vue index 5658583c..855fbe2b 100644 --- a/front/src/components/manage/users/UsersTable.vue +++ b/front/src/components/manage/users/UsersTable.vue @@ -45,7 +45,7 @@