diff --git a/api/funkwhale_api/federation/models.py b/api/funkwhale_api/federation/models.py index ad4c0c7be..4b7730402 100644 --- a/api/funkwhale_api/federation/models.py +++ b/api/funkwhale_api/federation/models.py @@ -88,7 +88,7 @@ class Domain(models.Model): name = models.CharField(primary_key=True, max_length=255) creation_date = models.DateTimeField(default=timezone.now) nodeinfo_fetch_date = models.DateTimeField(default=None, null=True, blank=True) - nodeinfo = JSONField(default=empty_dict, max_length=50000) + nodeinfo = JSONField(default=empty_dict, max_length=50000, blank=True) objects = DomainQuerySet.as_manager() @@ -104,6 +104,38 @@ class Domain(models.Model): super().save(**kwargs) + def get_stats(self): + from funkwhale_api.music import models as music_models + + data = Domain.objects.filter(pk=self.pk).aggregate( + actors=models.Count("actors", distinct=True), + outbox_activities=models.Count("actors__outbox_activities", distinct=True), + libraries=models.Count("actors__libraries", distinct=True), + uploads=models.Count("actors__libraries__uploads", distinct=True), + received_library_follows=models.Count( + "actors__libraries__received_follows", distinct=True + ), + emitted_library_follows=models.Count( + "actors__library_follows", distinct=True + ), + ) + data["artists"] = music_models.Artist.objects.filter( + from_activity__actor__domain_id=self.pk + ).count() + data["albums"] = music_models.Album.objects.filter( + from_activity__actor__domain_id=self.pk + ).count() + data["tracks"] = music_models.Track.objects.filter( + from_activity__actor__domain_id=self.pk + ).count() + + uploads = music_models.Upload.objects.filter(library__actor__domain_id=self.pk) + data["media_total_size"] = uploads.aggregate(v=models.Sum("size"))["v"] or 0 + data["media_downloaded_size"] = ( + uploads.with_file().aggregate(v=models.Sum("size"))["v"] or 0 + ) + return data + class Actor(models.Model): ap_type = "Actor" diff --git a/api/funkwhale_api/manage/views.py b/api/funkwhale_api/manage/views.py index 99b3b41c4..cee9d537d 100644 --- a/api/funkwhale_api/manage/views.py +++ b/api/funkwhale_api/manage/views.py @@ -125,3 +125,8 @@ class ManageDomainViewSet( federation_tasks.update_domain_nodeinfo(domain_name=domain.name) domain.refresh_from_db() return response.Response(domain.nodeinfo, status=200) + + @detail_route(methods=["get"]) + def stats(self, request, *args, **kwargs): + domain = self.get_object() + return response.Response(domain.get_stats(), status=200) diff --git a/api/funkwhale_api/music/models.py b/api/funkwhale_api/music/models.py index 21ecb994c..1bc2a1436 100644 --- a/api/funkwhale_api/music/models.py +++ b/api/funkwhale_api/music/models.py @@ -617,6 +617,8 @@ class UploadQuerySet(models.QuerySet): def for_federation(self): return self.filter(import_status="finished", mimetype__startswith="audio/") + def with_file(self): + return self.exclude(audio_file=None).exclude(audio_file='') TRACK_FILE_IMPORT_STATUS_CHOICES = ( ("pending", "Pending"), diff --git a/api/tests/federation/test_models.py b/api/tests/federation/test_models.py index c3032817c..293675048 100644 --- a/api/tests/federation/test_models.py +++ b/api/tests/federation/test_models.py @@ -77,3 +77,23 @@ def test_external_domains(factories, settings): settings.FEDERATION_HOSTNAME = d1.pk assert list(models.Domain.objects.external()) == [d2] + + +def test_domain_stats(factories): + expected = { + "actors": 0, + "libraries": 0, + "tracks": 0, + "albums": 0, + "uploads": 0, + "artists": 0, + "outbox_activities": 0, + "received_library_follows": 0, + "emitted_library_follows": 0, + "media_total_size": 0, + "media_downloaded_size": 0, + } + + domain = factories["federation.Domain"]() + + assert domain.get_stats() == expected diff --git a/api/tests/manage/test_views.py b/api/tests/manage/test_views.py index 31e1075ed..95ffea46d 100644 --- a/api/tests/manage/test_views.py +++ b/api/tests/manage/test_views.py @@ -103,3 +103,14 @@ def test_domain_nodeinfo(factories, superuser_api_client, mocker): assert response.data == {"status": "ok", "payload": {"hello": "world"}} update_domain_nodeinfo.assert_called_once_with(domain_name=domain.name) + + +def test_domain_stats(factories, superuser_api_client, mocker): + domain = factories["federation.Domain"]() + get_stats = mocker.patch.object( + domain.__class__, "get_stats", return_value={"hello": "world"} + ) + url = reverse("api:v1:manage:federation:domains-stats", kwargs={"pk": domain.name}) + response = superuser_api_client.get(url) + assert response.status_code == 200 + assert response.data == {"hello": "world"}