See #192: replaced old stats endpoint with nodeinfo
This commit is contained in:
parent
e31bed050e
commit
b4ad7a4a71
|
@ -68,3 +68,31 @@ class RavenEnabled(types.BooleanPreference):
|
||||||
'Wether error reporting to a Sentry instance using raven is enabled'
|
'Wether error reporting to a Sentry instance using raven is enabled'
|
||||||
' for front-end errors'
|
' for front-end errors'
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@global_preferences_registry.register
|
||||||
|
class InstanceNodeinfoEnabled(types.BooleanPreference):
|
||||||
|
show_in_api = False
|
||||||
|
section = instance
|
||||||
|
name = 'nodeinfo_enabled'
|
||||||
|
default = True
|
||||||
|
verbose_name = 'Enable nodeinfo endpoint'
|
||||||
|
help_text = (
|
||||||
|
'This endpoint is needed for your about page to work.'
|
||||||
|
'It\'s also helpful for the various monitoring '
|
||||||
|
'tools that map and analyzize the fediverse, '
|
||||||
|
'but you can disable it completely if needed.'
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@global_preferences_registry.register
|
||||||
|
class InstanceNodeinfoStatsEnabled(types.BooleanPreference):
|
||||||
|
show_in_api = False
|
||||||
|
section = instance
|
||||||
|
name = 'nodeinfo_stats_enabled'
|
||||||
|
default = True
|
||||||
|
verbose_name = 'Enable usage and library stats in nodeinfo endpoint'
|
||||||
|
help_text = (
|
||||||
|
'Disable this f you don\'t want to share usage and library statistics'
|
||||||
|
'in the nodeinfo endpoint but don\'t want to disable it completely.'
|
||||||
|
)
|
||||||
|
|
|
@ -0,0 +1,74 @@
|
||||||
|
import memoize.djangocache
|
||||||
|
|
||||||
|
import funkwhale_api
|
||||||
|
from funkwhale_api.common import preferences
|
||||||
|
|
||||||
|
from . import stats
|
||||||
|
|
||||||
|
|
||||||
|
store = memoize.djangocache.Cache('default')
|
||||||
|
memo = memoize.Memoizer(store, namespace='instance:stats')
|
||||||
|
|
||||||
|
|
||||||
|
def get():
|
||||||
|
share_stats = preferences.get('instance__nodeinfo_stats_enabled')
|
||||||
|
data = {
|
||||||
|
'version': '2.0',
|
||||||
|
'software': {
|
||||||
|
'name': 'funkwhale',
|
||||||
|
'version': funkwhale_api.__version__
|
||||||
|
},
|
||||||
|
'protocols': ['activitypub'],
|
||||||
|
'services': {
|
||||||
|
'inbound': [],
|
||||||
|
'outbound': []
|
||||||
|
},
|
||||||
|
'openRegistrations': preferences.get('users__registration_enabled'),
|
||||||
|
'usage': {
|
||||||
|
'users': {
|
||||||
|
'total': 0,
|
||||||
|
},
|
||||||
|
'localPosts': 0,
|
||||||
|
'localComments': 0,
|
||||||
|
},
|
||||||
|
'metadata': {
|
||||||
|
'shortDescription': preferences.get('instance__short_description'),
|
||||||
|
'longDescription': preferences.get('instance__long_description'),
|
||||||
|
'name': preferences.get('instance__name'),
|
||||||
|
'library': {
|
||||||
|
'federationEnabled': preferences.get('federation__enabled'),
|
||||||
|
'federationNeedsApproval': preferences.get('federation__music_needs_approval'),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if share_stats:
|
||||||
|
getter = memo(
|
||||||
|
lambda: stats.get(),
|
||||||
|
max_age=600
|
||||||
|
)
|
||||||
|
statistics = getter()
|
||||||
|
data['usage']['users']['total'] = statistics['users']
|
||||||
|
data['metadata']['library']['tracks'] = {
|
||||||
|
'total': statistics['tracks'],
|
||||||
|
}
|
||||||
|
data['metadata']['library']['artists'] = {
|
||||||
|
'total': statistics['artists'],
|
||||||
|
}
|
||||||
|
data['metadata']['library']['albums'] = {
|
||||||
|
'total': statistics['albums'],
|
||||||
|
}
|
||||||
|
data['metadata']['library']['music'] = {
|
||||||
|
'hours': statistics['music_duration']
|
||||||
|
}
|
||||||
|
|
||||||
|
data['metadata']['usage'] = {
|
||||||
|
'favorites': {
|
||||||
|
'tracks': {
|
||||||
|
'total': statistics['track_favorites'],
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'listenings': {
|
||||||
|
'total': statistics['listenings']
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return data
|
|
@ -1,11 +1,9 @@
|
||||||
from django.conf.urls import url
|
from django.conf.urls import url
|
||||||
from django.views.decorators.cache import cache_page
|
|
||||||
|
|
||||||
from . import views
|
from . import views
|
||||||
|
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
|
url(r'^nodeinfo/$', views.NodeInfo.as_view(), name='nodeinfo'),
|
||||||
url(r'^settings/$', views.InstanceSettings.as_view(), name='settings'),
|
url(r'^settings/$', views.InstanceSettings.as_view(), name='settings'),
|
||||||
url(r'^stats/$',
|
|
||||||
cache_page(60 * 5)(views.InstanceStats.as_view()), name='stats'),
|
|
||||||
]
|
]
|
||||||
|
|
|
@ -4,6 +4,9 @@ from rest_framework.response import Response
|
||||||
from dynamic_preferences.api import serializers
|
from dynamic_preferences.api import serializers
|
||||||
from dynamic_preferences.registries import global_preferences_registry
|
from dynamic_preferences.registries import global_preferences_registry
|
||||||
|
|
||||||
|
from funkwhale_api.common import preferences
|
||||||
|
|
||||||
|
from . import nodeinfo
|
||||||
from . import stats
|
from . import stats
|
||||||
|
|
||||||
|
|
||||||
|
@ -27,10 +30,12 @@ class InstanceSettings(views.APIView):
|
||||||
return Response(data, status=200)
|
return Response(data, status=200)
|
||||||
|
|
||||||
|
|
||||||
class InstanceStats(views.APIView):
|
class NodeInfo(views.APIView):
|
||||||
permission_classes = []
|
permission_classes = []
|
||||||
authentication_classes = []
|
authentication_classes = []
|
||||||
|
|
||||||
def get(self, request, *args, **kwargs):
|
def get(self, request, *args, **kwargs):
|
||||||
data = stats.get()
|
if not preferences.get('instance__nodeinfo_enabled'):
|
||||||
|
return Response(status=404)
|
||||||
|
data = nodeinfo.get()
|
||||||
return Response(data, status=200)
|
return Response(data, status=200)
|
||||||
|
|
|
@ -0,0 +1,107 @@
|
||||||
|
from django.urls import reverse
|
||||||
|
|
||||||
|
import funkwhale_api
|
||||||
|
|
||||||
|
from funkwhale_api.instance import nodeinfo
|
||||||
|
|
||||||
|
|
||||||
|
def test_nodeinfo_dump(preferences, mocker):
|
||||||
|
preferences['instance__nodeinfo_stats_enabled'] = True
|
||||||
|
stats = {
|
||||||
|
'users': 1,
|
||||||
|
'tracks': 2,
|
||||||
|
'albums': 3,
|
||||||
|
'artists': 4,
|
||||||
|
'track_favorites': 5,
|
||||||
|
'music_duration': 6,
|
||||||
|
'listenings': 7,
|
||||||
|
}
|
||||||
|
mocker.patch('funkwhale_api.instance.stats.get', return_value=stats)
|
||||||
|
|
||||||
|
expected = {
|
||||||
|
'version': '2.0',
|
||||||
|
'software': {
|
||||||
|
'name': 'funkwhale',
|
||||||
|
'version': funkwhale_api.__version__
|
||||||
|
},
|
||||||
|
'protocols': ['activitypub'],
|
||||||
|
'services': {
|
||||||
|
'inbound': [],
|
||||||
|
'outbound': []
|
||||||
|
},
|
||||||
|
'openRegistrations': preferences['users__registration_enabled'],
|
||||||
|
'usage': {
|
||||||
|
'users': {
|
||||||
|
'total': stats['users'],
|
||||||
|
},
|
||||||
|
'localPosts': 0,
|
||||||
|
'localComments': 0,
|
||||||
|
},
|
||||||
|
'metadata': {
|
||||||
|
'shortDescription': preferences['instance__short_description'],
|
||||||
|
'longDescription': preferences['instance__long_description'],
|
||||||
|
'name': preferences['instance__name'],
|
||||||
|
'library': {
|
||||||
|
'federationEnabled': preferences['federation__enabled'],
|
||||||
|
'federationNeedsApproval': preferences['federation__music_needs_approval'],
|
||||||
|
'tracks': {
|
||||||
|
'total': stats['tracks'],
|
||||||
|
},
|
||||||
|
'artists': {
|
||||||
|
'total': stats['artists'],
|
||||||
|
},
|
||||||
|
'albums': {
|
||||||
|
'total': stats['albums'],
|
||||||
|
},
|
||||||
|
'music': {
|
||||||
|
'hours': stats['music_duration']
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'usage': {
|
||||||
|
'favorites': {
|
||||||
|
'tracks': {
|
||||||
|
'total': stats['track_favorites'],
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'listenings': {
|
||||||
|
'total': stats['listenings']
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assert nodeinfo.get() == expected
|
||||||
|
|
||||||
|
|
||||||
|
def test_nodeinfo_dump_stats_disabled(preferences, mocker):
|
||||||
|
preferences['instance__nodeinfo_stats_enabled'] = False
|
||||||
|
|
||||||
|
expected = {
|
||||||
|
'version': '2.0',
|
||||||
|
'software': {
|
||||||
|
'name': 'funkwhale',
|
||||||
|
'version': funkwhale_api.__version__
|
||||||
|
},
|
||||||
|
'protocols': ['activitypub'],
|
||||||
|
'services': {
|
||||||
|
'inbound': [],
|
||||||
|
'outbound': []
|
||||||
|
},
|
||||||
|
'openRegistrations': preferences['users__registration_enabled'],
|
||||||
|
'usage': {
|
||||||
|
'users': {
|
||||||
|
'total': 0,
|
||||||
|
},
|
||||||
|
'localPosts': 0,
|
||||||
|
'localComments': 0,
|
||||||
|
},
|
||||||
|
'metadata': {
|
||||||
|
'shortDescription': preferences['instance__short_description'],
|
||||||
|
'longDescription': preferences['instance__long_description'],
|
||||||
|
'name': preferences['instance__name'],
|
||||||
|
'library': {
|
||||||
|
'federationEnabled': preferences['federation__enabled'],
|
||||||
|
'federationNeedsApproval': preferences['federation__music_needs_approval'],
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assert nodeinfo.get() == expected
|
|
@ -3,16 +3,6 @@ from django.urls import reverse
|
||||||
from funkwhale_api.instance import stats
|
from funkwhale_api.instance import stats
|
||||||
|
|
||||||
|
|
||||||
def test_can_get_stats_via_api(db, api_client, mocker):
|
|
||||||
stats = {
|
|
||||||
'foo': 'bar'
|
|
||||||
}
|
|
||||||
mocker.patch('funkwhale_api.instance.stats.get', return_value=stats)
|
|
||||||
url = reverse('api:v1:instance:stats')
|
|
||||||
response = api_client.get(url)
|
|
||||||
assert response.data == stats
|
|
||||||
|
|
||||||
|
|
||||||
def test_get_users(mocker):
|
def test_get_users(mocker):
|
||||||
mocker.patch(
|
mocker.patch(
|
||||||
'funkwhale_api.users.models.User.objects.count', return_value=42)
|
'funkwhale_api.users.models.User.objects.count', return_value=42)
|
||||||
|
|
|
@ -0,0 +1,22 @@
|
||||||
|
from django.urls import reverse
|
||||||
|
|
||||||
|
|
||||||
|
def test_nodeinfo_endpoint(db, api_client, mocker):
|
||||||
|
payload = {
|
||||||
|
'test': 'test'
|
||||||
|
}
|
||||||
|
mocked_nodeinfo = mocker.patch(
|
||||||
|
'funkwhale_api.instance.nodeinfo.get', return_value=payload)
|
||||||
|
url = reverse('api:v1:instance:nodeinfo')
|
||||||
|
response = api_client.get(url)
|
||||||
|
|
||||||
|
assert response.status_code == 200
|
||||||
|
assert response.data == payload
|
||||||
|
|
||||||
|
|
||||||
|
def test_nodeinfo_endpoint_disabled(db, api_client, preferences):
|
||||||
|
preferences['instance__nodeinfo_enabled'] = False
|
||||||
|
url = reverse('api:v1:instance:nodeinfo')
|
||||||
|
response = api_client.get(url)
|
||||||
|
|
||||||
|
assert response.status_code == 404
|
Loading…
Reference in New Issue