Rename operation ids

This commit is contained in:
wvffle 2022-09-25 13:57:22 +00:00 committed by Georg Krause
parent cbbf6c2c40
commit 68face201b
20 changed files with 14173 additions and 23 deletions

View File

@ -42,11 +42,18 @@ class CustomApplicationTokenExt(OpenApiAuthenticationExtension):
def custom_preprocessing_hook(endpoints): def custom_preprocessing_hook(endpoints):
filtered = [] filtered = []
# your modifications to the list of operations that are exposed in the schema # your modifications to the list of operations that are exposed in the schema
api_type = os.environ.get("API_TYPE", "v1") api_type = os.environ.get("API_TYPE", "v1")
for (path, path_regex, method, callback) in endpoints: for (path, path_regex, method, callback) in endpoints:
if path.startswith("/api/v1/providers"): if path.startswith("/api/v1/providers"):
continue continue
if path.startswith("/api/v1/users/users"):
continue
if path.startswith(f"/api/{api_type}"): if path.startswith(f"/api/{api_type}"):
filtered.append((path, path_regex, method, callback)) filtered.append((path, path_regex, method, callback))
return filtered return filtered

View File

@ -99,7 +99,7 @@ CELERY_TASK_ALWAYS_EAGER = False
CSRF_TRUSTED_ORIGINS = [o for o in ALLOWED_HOSTS] CSRF_TRUSTED_ORIGINS = [o for o in ALLOWED_HOSTS]
REST_FRAMEWORK["DEFAULT_SCHEMA_CLASS"] = "drf_spectacular.openapi.AutoSchema" REST_FRAMEWORK["DEFAULT_SCHEMA_CLASS"] = "funkwhale_api.schema.CustomAutoSchema"
SPECTACULAR_SETTINGS = { SPECTACULAR_SETTINGS = {
"TITLE": "Funkwhale API", "TITLE": "Funkwhale API",
"DESCRIPTION": open("Readme.md", "r").read(), "DESCRIPTION": open("Readme.md", "r").read(),

View File

@ -1,6 +1,8 @@
from rest_framework import viewsets from rest_framework import viewsets
from rest_framework.response import Response from rest_framework.response import Response
from drf_spectacular.utils import extend_schema
from funkwhale_api.common.permissions import ConditionalAuthentication from funkwhale_api.common.permissions import ConditionalAuthentication
from funkwhale_api.favorites.models import TrackFavorite from funkwhale_api.favorites.models import TrackFavorite
@ -13,6 +15,7 @@ class ActivityViewSet(viewsets.GenericViewSet):
permission_classes = [ConditionalAuthentication] permission_classes = [ConditionalAuthentication]
queryset = TrackFavorite.objects.none() queryset = TrackFavorite.objects.none()
@extend_schema(operation_id='get_activity')
def list(self, request, *args, **kwargs): def list(self, request, *args, **kwargs):
activity = utils.get_activity(user=request.user) activity = utils.get_activity(user=request.user)
serializer = self.serializer_class(activity, many=True) serializer = self.serializer_class(activity, many=True)

View File

@ -5,6 +5,8 @@ from rest_framework import permissions as rest_permissions
from rest_framework import response from rest_framework import response
from rest_framework import viewsets from rest_framework import viewsets
from drf_spectacular.utils import extend_schema, extend_schema_view
from django import http from django import http
from django.db import transaction from django.db import transaction
from django.db.models import Count, Prefetch, Q, Sum from django.db.models import Count, Prefetch, Q, Sum
@ -43,6 +45,12 @@ class ChannelsMixin(object):
return super().dispatch(request, *args, **kwargs) return super().dispatch(request, *args, **kwargs)
@extend_schema_view(
metedata_choices=extend_schema(operation_id='get_channel_metadata_choices'),
subscribe=extend_schema(operation_id='subscribe_channel'),
unsubscribe=extend_schema(operation_id='unsubscribe_channel'),
rss_subscribe=extend_schema(operation_id='subscribe_channel_rss'),
)
class ChannelViewSet( class ChannelViewSet(
ChannelsMixin, ChannelsMixin,
MultipleLookupDetailMixin, MultipleLookupDetailMixin,
@ -322,6 +330,7 @@ class SubscriptionsViewSet(
qs = super().get_queryset() qs = super().get_queryset()
return qs.filter(actor=self.request.user.actor) return qs.filter(actor=self.request.user.actor)
@extend_schema(operation_id='get_all_subscriptions')
@decorators.action(methods=["get"], detail=False) @decorators.action(methods=["get"], detail=False)
def all(self, request, *args, **kwargs): def all(self, request, *args, **kwargs):
""" """

View File

@ -5,6 +5,8 @@ from rest_framework import exceptions
from rest_framework import response from rest_framework import response
from rest_framework import status from rest_framework import status
from drf_spectacular.utils import extend_schema
from . import filters from . import filters
from . import models from . import models
from . import mutations as common_mutations from . import mutations as common_mutations
@ -87,6 +89,10 @@ def mutations_route(types):
) )
return response.Response(serializer.data, status=status.HTTP_201_CREATED) return response.Response(serializer.data, status=status.HTTP_201_CREATED)
return decorators.action( return extend_schema(methods=['post'], responses=serializers.APIMutationSerializer())(
methods=["get", "post"], detail=True, required_scope="edits" extend_schema(methods=['get'], responses=serializers.APIMutationSerializer(many=True))(
)(mutations) decorators.action(
methods=["get", "post"], detail=True, required_scope="edits"
)(mutations)
)
)

View File

@ -12,6 +12,8 @@ from rest_framework import response
from rest_framework import views from rest_framework import views
from rest_framework import viewsets from rest_framework import viewsets
from drf_spectacular.utils import extend_schema
from config import plugins from config import plugins
from funkwhale_api.users.oauth import permissions as oauth_permissions from funkwhale_api.users.oauth import permissions as oauth_permissions
@ -78,6 +80,7 @@ class MutationViewSet(
return super().perform_destroy(instance) return super().perform_destroy(instance)
@extend_schema(operation_id='approve_mutation')
@action(detail=True, methods=["post"]) @action(detail=True, methods=["post"])
@transaction.atomic @transaction.atomic
def approve(self, request, *args, **kwargs): def approve(self, request, *args, **kwargs):
@ -107,6 +110,7 @@ class MutationViewSet(
) )
return response.Response({}, status=200) return response.Response({}, status=200)
@extend_schema(operation_id='reject_mutation')
@action(detail=True, methods=["post"]) @action(detail=True, methods=["post"])
@transaction.atomic @transaction.atomic
def reject(self, request, *args, **kwargs): def reject(self, request, *args, **kwargs):
@ -201,6 +205,7 @@ class AttachmentViewSet(
class TextPreviewView(views.APIView): class TextPreviewView(views.APIView):
permission_classes = [] permission_classes = []
@extend_schema(operation_id='preview_text')
def post(self, request, *args, **kwargs): def post(self, request, *args, **kwargs):
payload = request.data payload = request.data
if "text" not in payload: if "text" not in payload:
@ -273,6 +278,7 @@ class PluginViewSet(mixins.ListModelMixin, viewsets.GenericViewSet):
user.plugins.filter(code=kwargs["pk"]).delete() user.plugins.filter(code=kwargs["pk"]).delete()
return response.Response(status=204) return response.Response(status=204)
@extend_schema(operation_id='enable_plugin')
@action(detail=True, methods=["post"]) @action(detail=True, methods=["post"])
def enable(self, request, *args, **kwargs): def enable(self, request, *args, **kwargs):
user = request.user user = request.user
@ -281,6 +287,7 @@ class PluginViewSet(mixins.ListModelMixin, viewsets.GenericViewSet):
plugins.enable_conf(kwargs["pk"], True, user) plugins.enable_conf(kwargs["pk"], True, user)
return response.Response({}, status=200) return response.Response({}, status=200)
@extend_schema(operation_id='disable_plugin')
@action(detail=True, methods=["post"]) @action(detail=True, methods=["post"])
def disable(self, request, *args, **kwargs): def disable(self, request, *args, **kwargs):
user = request.user user = request.user

View File

@ -2,6 +2,8 @@ from rest_framework import mixins, status, viewsets
from rest_framework.decorators import action from rest_framework.decorators import action
from rest_framework.response import Response from rest_framework.response import Response
from drf_spectacular.utils import extend_schema, extend_schema_view
from django.db.models import Prefetch from django.db.models import Prefetch
from funkwhale_api.activity import record from funkwhale_api.activity import record
@ -38,6 +40,7 @@ class TrackFavoriteViewSet(
return serializers.UserTrackFavoriteSerializer return serializers.UserTrackFavoriteSerializer
return serializers.UserTrackFavoriteWriteSerializer return serializers.UserTrackFavoriteWriteSerializer
@extend_schema(operation_id='favorite_track')
def create(self, request, *args, **kwargs): def create(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data) serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True) serializer.is_valid(raise_exception=True)
@ -67,6 +70,7 @@ class TrackFavoriteViewSet(
favorite = models.TrackFavorite.add(track=track, user=self.request.user) favorite = models.TrackFavorite.add(track=track, user=self.request.user)
return favorite return favorite
@extend_schema(operation_id='unfavorite_track')
@action(methods=["delete", "post"], detail=False) @action(methods=["delete", "post"], detail=False)
def remove(self, request, *args, **kwargs): def remove(self, request, *args, **kwargs):
try: try:
@ -77,6 +81,7 @@ class TrackFavoriteViewSet(
favorite.delete() favorite.delete()
return Response([], status=status.HTTP_204_NO_CONTENT) return Response([], status=status.HTTP_204_NO_CONTENT)
@extend_schema(operation_id='get_all_favorite_tracks')
@action(methods=["get"], detail=False) @action(methods=["get"], detail=False)
def all(self, request, *args, **kwargs): def all(self, request, *args, **kwargs):
""" """

View File

@ -10,6 +10,8 @@ from rest_framework import permissions
from rest_framework import response from rest_framework import response
from rest_framework import viewsets from rest_framework import viewsets
from drf_spectacular.utils import extend_schema, extend_schema_view
from funkwhale_api.common import preferences from funkwhale_api.common import preferences
from funkwhale_api.common import utils as common_utils from funkwhale_api.common import utils as common_utils
from funkwhale_api.common.permissions import ConditionalAuthentication from funkwhale_api.common.permissions import ConditionalAuthentication
@ -38,6 +40,13 @@ def update_follow(follow, approved):
routes.outbox.dispatch({"type": "Reject"}, context={"follow": follow}) routes.outbox.dispatch({"type": "Reject"}, context={"follow": follow})
@extend_schema_view(
list=extend_schema(operation_id='get_federation_library_follows'),
create=extend_schema(operation_id='create_federation_library_follow'),
)
# NOTE: For some weird reason, @extend_schema_view doesn't work with `retrieve` and `destroy` methods.
@extend_schema(operation_id='get_federation_library_follow', methods=['get'])
@extend_schema(operation_id='delete_federation_library_follow', methods=['delete'])
class LibraryFollowViewSet( class LibraryFollowViewSet(
mixins.CreateModelMixin, mixins.CreateModelMixin,
mixins.ListModelMixin, mixins.ListModelMixin,
@ -77,6 +86,7 @@ class LibraryFollowViewSet(
context["actor"] = self.request.user.actor context["actor"] = self.request.user.actor
return context return context
@extend_schema(operation_id='accept_federation_library_follow')
@decorators.action(methods=["post"], detail=True) @decorators.action(methods=["post"], detail=True)
def accept(self, request, *args, **kwargs): def accept(self, request, *args, **kwargs):
try: try:
@ -88,6 +98,7 @@ class LibraryFollowViewSet(
update_follow(follow, approved=True) update_follow(follow, approved=True)
return response.Response(status=204) return response.Response(status=204)
@extend_schema(operation_id='reject_federation_library_follow')
@decorators.action(methods=["post"], detail=True) @decorators.action(methods=["post"], detail=True)
def reject(self, request, *args, **kwargs): def reject(self, request, *args, **kwargs):
try: try:
@ -100,6 +111,7 @@ class LibraryFollowViewSet(
update_follow(follow, approved=False) update_follow(follow, approved=False)
return response.Response(status=204) return response.Response(status=204)
@extend_schema(operation_id='get_all_federation_library_follows')
@decorators.action(methods=["get"], detail=False) @decorators.action(methods=["get"], detail=False)
def all(self, request, *args, **kwargs): def all(self, request, *args, **kwargs):
""" """

View File

@ -5,6 +5,8 @@ from rest_framework import permissions
from rest_framework import response from rest_framework import response
from rest_framework import status from rest_framework import status
from drf_spectacular.utils import extend_schema
from funkwhale_api.common import utils as common_utils from funkwhale_api.common import utils as common_utils
from . import api_serializers from . import api_serializers
@ -42,8 +44,12 @@ def fetches_route():
serializer = api_serializers.FetchSerializer(fetch) serializer = api_serializers.FetchSerializer(fetch)
return response.Response(serializer.data, status=status.HTTP_201_CREATED) return response.Response(serializer.data, status=status.HTTP_201_CREATED)
return decorators.action( return extend_schema(methods=['post'], responses=api_serializers.FetchSerializer())(
methods=["get", "post"], extend_schema(methods=['get'], responses=api_serializers.FetchSerializer(many=True))(
detail=True, decorators.action(
permission_classes=[permissions.IsAuthenticated], methods=["get", "post"],
)(fetches) detail=True,
permission_classes=[permissions.IsAuthenticated],
)(fetches)
)
)

View File

@ -53,6 +53,7 @@ class InstanceSettings(generics.GenericAPIView):
] ]
return api_preferences return api_preferences
@extend_schema(operation_id='get_instance_settings')
def get(self, request): def get(self, request):
queryset = self.get_queryset() queryset = self.get_queryset()
data = GlobalPreferenceSerializer(queryset, many=True).data data = GlobalPreferenceSerializer(queryset, many=True).data
@ -121,6 +122,7 @@ class SpaManifest(views.APIView):
permission_classes = [] permission_classes = []
authentication_classes = [] authentication_classes = []
@extend_schema(operation_id='get_spa_manifest')
def get(self, request, *args, **kwargs): def get(self, request, *args, **kwargs):
existing_manifest = middleware.get_spa_file( existing_manifest = middleware.get_spa_file(
settings.FUNKWHALE_SPA_HTML_ROOT, "manifest.json" settings.FUNKWHALE_SPA_HTML_ROOT, "manifest.json"

View File

@ -1,6 +1,8 @@
from rest_framework import mixins, response, viewsets from rest_framework import mixins, response, viewsets
from rest_framework import decorators as rest_decorators from rest_framework import decorators as rest_decorators
from drf_spectacular.utils import extend_schema
from django.db import transaction from django.db import transaction
from django.db.models import Count, Prefetch, Q, Sum, OuterRef, Subquery from django.db.models import Count, Prefetch, Q, Sum, OuterRef, Subquery
from django.db.models.functions import Coalesce, Length from django.db.models.functions import Coalesce, Length
@ -93,6 +95,7 @@ class ManageArtistViewSet(
required_scope = "instance:libraries" required_scope = "instance:libraries"
ordering_fields = ["creation_date", "name"] ordering_fields = ["creation_date", "name"]
@extend_schema(operation_id='admin_get_library_artist_stats')
@rest_decorators.action(methods=["get"], detail=True) @rest_decorators.action(methods=["get"], detail=True)
def stats(self, request, *args, **kwargs): def stats(self, request, *args, **kwargs):
artist = self.get_object() artist = self.get_object()
@ -135,6 +138,7 @@ class ManageAlbumViewSet(
required_scope = "instance:libraries" required_scope = "instance:libraries"
ordering_fields = ["creation_date", "title", "release_date"] ordering_fields = ["creation_date", "title", "release_date"]
@extend_schema(operation_id='admin_get_library_album_stats')
@rest_decorators.action(methods=["get"], detail=True) @rest_decorators.action(methods=["get"], detail=True)
def stats(self, request, *args, **kwargs): def stats(self, request, *args, **kwargs):
album = self.get_object() album = self.get_object()
@ -196,6 +200,7 @@ class ManageTrackViewSet(
"disc_number", "disc_number",
] ]
@extend_schema(operation_id='admin_get_track_stats')
@rest_decorators.action(methods=["get"], detail=True) @rest_decorators.action(methods=["get"], detail=True)
def stats(self, request, *args, **kwargs): def stats(self, request, *args, **kwargs):
track = self.get_object() track = self.get_object()
@ -257,6 +262,7 @@ class ManageLibraryViewSet(
filterset_class = filters.ManageLibraryFilterSet filterset_class = filters.ManageLibraryFilterSet
required_scope = "instance:libraries" required_scope = "instance:libraries"
@extend_schema(operation_id='admin_get_library_stats')
@rest_decorators.action(methods=["get"], detail=True) @rest_decorators.action(methods=["get"], detail=True)
def stats(self, request, *args, **kwargs): def stats(self, request, *args, **kwargs):
library = self.get_object() library = self.get_object()
@ -424,6 +430,7 @@ class ManageDomainViewSet(
domain.refresh_from_db() domain.refresh_from_db()
return response.Response(domain.nodeinfo, status=200) return response.Response(domain.nodeinfo, status=200)
@extend_schema(operation_id='admin_get_federation_domain_stats')
@rest_decorators.action(methods=["get"], detail=True) @rest_decorators.action(methods=["get"], detail=True)
def stats(self, request, *args, **kwargs): def stats(self, request, *args, **kwargs):
domain = self.get_object() domain = self.get_object()
@ -468,6 +475,7 @@ class ManageActorViewSet(
return obj return obj
@extend_schema(operation_id='admin_get_account_stats')
@rest_decorators.action(methods=["get"], detail=True) @rest_decorators.action(methods=["get"], detail=True)
def stats(self, request, *args, **kwargs): def stats(self, request, *args, **kwargs):
obj = self.get_object() obj = self.get_object()
@ -709,6 +717,7 @@ class ManageChannelViewSet(
required_scope = "instance:libraries" required_scope = "instance:libraries"
ordering_fields = ["creation_date", "name"] ordering_fields = ["creation_date", "name"]
@extend_schema(operation_id='admin_get_channel_stats')
@rest_decorators.action(methods=["get"], detail=True) @rest_decorators.action(methods=["get"], detail=True)
def stats(self, request, *args, **kwargs): def stats(self, request, *args, **kwargs):
channel = self.get_object() channel = self.get_object()

View File

@ -16,6 +16,8 @@ from rest_framework import views, viewsets
from rest_framework.decorators import action from rest_framework.decorators import action
from rest_framework.response import Response from rest_framework.response import Response
from drf_spectacular.utils import extend_schema
import requests.exceptions import requests.exceptions
from funkwhale_api.common import decorators as common_decorators from funkwhale_api.common import decorators as common_decorators
@ -66,7 +68,9 @@ def get_libraries(filter_uploads):
serializer = federation_api_serializers.LibrarySerializer(qs, many=True) serializer = federation_api_serializers.LibrarySerializer(qs, many=True)
return Response(serializer.data) return Response(serializer.data)
return libraries return extend_schema(responses=federation_api_serializers.LibrarySerializer(many=True))(
action(methods=["get"], detail=True)(libraries)
)
def refetch_obj(obj, queryset): def refetch_obj(obj, queryset):
@ -167,13 +171,9 @@ class ArtistViewSet(
Prefetch("albums", queryset=albums), TAG_PREFETCH Prefetch("albums", queryset=albums), TAG_PREFETCH
) )
libraries = action(methods=["get"], detail=True)( libraries = get_libraries(lambda o, uploads: uploads.filter(
get_libraries( Q(track__artist=o) | Q(track__album__artist=o)
filter_uploads=lambda o, uploads: uploads.filter( ))
Q(track__artist=o) | Q(track__album__artist=o)
)
)
)
class AlbumViewSet( class AlbumViewSet(
@ -231,9 +231,7 @@ class AlbumViewSet(
Prefetch("tracks", queryset=tracks), TAG_PREFETCH Prefetch("tracks", queryset=tracks), TAG_PREFETCH
) )
libraries = action(methods=["get"], detail=True)( libraries = get_libraries(lambda o, uploads: uploads.filter(track__album=o))
get_libraries(filter_uploads=lambda o, uploads: uploads.filter(track__album=o))
)
def get_serializer_class(self): def get_serializer_class(self):
if self.action in ["create"]: if self.action in ["create"]:
@ -430,9 +428,7 @@ class TrackViewSet(
) )
return queryset.prefetch_related(TAG_PREFETCH) return queryset.prefetch_related(TAG_PREFETCH)
libraries = action(methods=["get"], detail=True)( libraries = get_libraries(lambda o, uploads: uploads.filter(track=o))
get_libraries(filter_uploads=lambda o, uploads: uploads.filter(track=o))
)
def get_serializer_context(self): def get_serializer_context(self):
context = super().get_serializer_context() context = super().get_serializer_context()
@ -744,6 +740,7 @@ class UploadViewSet(
qs = qs.playable_by(actor) qs = qs.playable_by(actor)
return qs return qs
@extend_schema(operation_id='get_upload_metadata')
@action(methods=["get"], detail=True, url_path="audio-file-metadata") @action(methods=["get"], detail=True, url_path="audio-file-metadata")
def audio_file_metadata(self, request, *args, **kwargs): def audio_file_metadata(self, request, *args, **kwargs):
upload = self.get_object() upload = self.get_object()
@ -802,6 +799,7 @@ class Search(views.APIView):
required_scope = "libraries" required_scope = "libraries"
anonymous_policy = "setting" anonymous_policy = "setting"
@extend_schema(operation_id='get_search_results')
def get(self, request, *args, **kwargs): def get(self, request, *args, **kwargs):
query = request.GET.get("query", request.GET.get("q", "")) or "" query = request.GET.get("query", request.GET.get("q", "")) or ""
query = query.strip() query = query.strip()

View File

@ -1,9 +1,12 @@
from django.db import transaction from django.db import transaction
from django.db.models import Count from django.db.models import Count
from rest_framework import exceptions, mixins, viewsets from rest_framework import exceptions, mixins, viewsets
from rest_framework.decorators import action from rest_framework.decorators import action
from rest_framework.response import Response from rest_framework.response import Response
from drf_spectacular.utils import extend_schema
from funkwhale_api.common import fields, permissions from funkwhale_api.common import fields, permissions
from funkwhale_api.music import utils as music_utils from funkwhale_api.music import utils as music_utils
from funkwhale_api.users.oauth import permissions as oauth_permissions from funkwhale_api.users.oauth import permissions as oauth_permissions
@ -38,6 +41,7 @@ class PlaylistViewSet(
filterset_class = filters.PlaylistFilter filterset_class = filters.PlaylistFilter
ordering_fields = ("id", "name", "creation_date", "modification_date") ordering_fields = ("id", "name", "creation_date", "modification_date")
@extend_schema(responses=serializers.PlaylistTrackSerializer(many=True))
@action(methods=["get"], detail=True) @action(methods=["get"], detail=True)
def tracks(self, request, *args, **kwargs): def tracks(self, request, *args, **kwargs):
playlist = self.get_object() playlist = self.get_object()
@ -48,6 +52,7 @@ class PlaylistViewSet(
data = {"count": len(plts), "results": serializer.data} data = {"count": len(plts), "results": serializer.data}
return Response(data, status=200) return Response(data, status=200)
@extend_schema(operation_id="add_to_playlist", request=serializers.PlaylistAddManySerializer)
@action(methods=["post"], detail=True) @action(methods=["post"], detail=True)
@transaction.atomic @transaction.atomic
def add(self, request, *args, **kwargs): def add(self, request, *args, **kwargs):
@ -72,6 +77,7 @@ class PlaylistViewSet(
data = {"count": len(plts), "results": serializer.data} data = {"count": len(plts), "results": serializer.data}
return Response(data, status=201) return Response(data, status=201)
@extend_schema(operation_id="clear_playlist")
@action(methods=["delete"], detail=True) @action(methods=["delete"], detail=True)
@transaction.atomic @transaction.atomic
def clear(self, request, *args, **kwargs): def clear(self, request, *args, **kwargs):
@ -93,6 +99,7 @@ class PlaylistViewSet(
), ),
) )
@extend_schema(operation_id="remove_from_playlist")
@action(methods=["post", "delete"], detail=True) @action(methods=["post", "delete"], detail=True)
@transaction.atomic @transaction.atomic
def remove(self, request, *args, **kwargs): def remove(self, request, *args, **kwargs):
@ -111,6 +118,7 @@ class PlaylistViewSet(
return Response(status=204) return Response(status=204)
@extend_schema(operation_id="reorder_track_in_playlist")
@action(methods=["post"], detail=True) @action(methods=["post"], detail=True)
@transaction.atomic @transaction.atomic
def move(self, request, *args, **kwargs): def move(self, request, *args, **kwargs):

View File

@ -1,8 +1,11 @@
from django.db.models import Q from django.db.models import Q
from rest_framework import mixins, status, viewsets from rest_framework import mixins, status, viewsets
from rest_framework.decorators import action from rest_framework.decorators import action
from rest_framework.response import Response from rest_framework.response import Response
from drf_spectacular.utils import extend_schema
from funkwhale_api.common import permissions as common_permissions from funkwhale_api.common import permissions as common_permissions
from funkwhale_api.music.serializers import TrackSerializer from funkwhale_api.music.serializers import TrackSerializer
from funkwhale_api.music import utils as music_utils from funkwhale_api.music import utils as music_utils
@ -63,6 +66,7 @@ class RadioViewSet(
) )
return Response(serializer.data) return Response(serializer.data)
@extend_schema(operation_id='validate_radio')
@action(methods=["post"], detail=False) @action(methods=["post"], detail=False)
def validate(self, request, *args, **kwargs): def validate(self, request, *args, **kwargs):
try: try:
@ -124,6 +128,7 @@ class RadioSessionTrackViewSet(mixins.CreateModelMixin, viewsets.GenericViewSet)
queryset = models.RadioSessionTrack.objects.all() queryset = models.RadioSessionTrack.objects.all()
permission_classes = [] permission_classes = []
@extend_schema(operation_id='get_next_radio_track')
def create(self, request, *args, **kwargs): def create(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data) serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True) serializer.is_valid(raise_exception=True)

View File

@ -0,0 +1,69 @@
from drf_spectacular.openapi import AutoSchema
from pluralizer import Pluralizer
import re
class CustomAutoSchema(AutoSchema):
method_mapping = {
'get': 'get',
'post': 'create',
'put': 'update',
'patch': 'partial_update',
'delete': 'delete',
}
pluralizer = Pluralizer()
def get_operation_id(self):
# Modified operation id getter from
# https://github.com/tfranzel/drf-spectacular/blob/6973aa48f4ff08f7f33799d50c288fcc79ea8076/drf_spectacular/openapi.py#L424-L441
tokenized_path = self._tokenize_path()
# replace dashes as they can be problematic later in code generation
tokenized_path = [t.replace('-', '_') for t in tokenized_path]
# replace plural forms with singular forms
tokenized_path = [self.pluralizer.singular(t) for t in tokenized_path]
if not tokenized_path:
tokenized_path.append('root')
model = tokenized_path.pop()
if self.method == 'GET' and self._is_list_view():
action = 'get'
model = self.pluralizer.plural(model)
else:
action = self.method_mapping[self.method.lower()]
if re.search(r'<drf_format_suffix\w*:\w+>', self.path_regex):
tokenized_path.append('formatted')
# rename `get_radio_radio_track` to `get_radio_track`
if len(tokenized_path) > 1 and tokenized_path[1] == 'radio' and tokenized_path[1] == 'radio':
tokenized_path.pop(0)
# rename `get_manage_channel` to `admin_get_channel`
elif len(tokenized_path) > 0 and tokenized_path[0] == 'manage':
tokenized_path.pop(0)
# rename `get_manage_library_album` to `admin_get_album`
if len(tokenized_path) > 0 and tokenized_path[0] == 'library':
tokenized_path.pop(0)
# rename `get_manage_user_users` to `admin_get_users`
elif len(tokenized_path) > 0 and tokenized_path[0] == 'user':
tokenized_path.pop(0)
# rename `get_manage_moderation_note` to `moderation_get_note`
elif len(tokenized_path) > 0 and tokenized_path[0] == 'moderation':
tokenized_path.pop(0)
return '_'.join(['moderation', action] + tokenized_path + [model])
return '_'.join(['admin', action] + tokenized_path + [model])
return '_'.join([action] + tokenized_path + [model])

View File

@ -4,9 +4,12 @@ import urllib.parse
from django import http from django import http
from django.utils import timezone from django.utils import timezone
from django.db.models import Q from django.db.models import Q
from rest_framework import mixins, permissions, response, views, viewsets from rest_framework import mixins, permissions, response, views, viewsets
from rest_framework.decorators import action from rest_framework.decorators import action
from drf_spectacular.utils import extend_schema
from oauth2_provider import exceptions as oauth2_exceptions from oauth2_provider import exceptions as oauth2_exceptions
from oauth2_provider import views as oauth_views from oauth2_provider import views as oauth_views
from oauth2_provider.settings import oauth2_settings from oauth2_provider.settings import oauth2_settings
@ -83,6 +86,7 @@ class ApplicationViewSet(
qs = qs.filter(user=self.request.user) qs = qs.filter(user=self.request.user)
return qs return qs
@extend_schema(operation_id='refresh_oauth_token')
@action( @action(
detail=True, detail=True,
methods=["post"], methods=["post"],

View File

@ -12,6 +12,8 @@ from rest_framework import viewsets
from rest_framework.decorators import action from rest_framework.decorators import action
from rest_framework.response import Response from rest_framework.response import Response
from drf_spectacular.utils import extend_schema
from funkwhale_api.common import authentication from funkwhale_api.common import authentication
from funkwhale_api.common import preferences from funkwhale_api.common import preferences
from funkwhale_api.common import throttling from funkwhale_api.common import throttling
@ -19,6 +21,7 @@ from funkwhale_api.common import throttling
from . import models, serializers, tasks from . import models, serializers, tasks
@extend_schema(operation_id='register', methods=['post'])
class RegisterView(registration_views.RegisterView): class RegisterView(registration_views.RegisterView):
serializer_class = serializers.RegisterSerializer serializer_class = serializers.RegisterSerializer
permission_classes = [] permission_classes = []
@ -43,18 +46,22 @@ class RegisterView(registration_views.RegisterView):
return user return user
@extend_schema(operation_id='verify_email')
class VerifyEmailView(registration_views.VerifyEmailView): class VerifyEmailView(registration_views.VerifyEmailView):
action = "verify-email" action = "verify-email"
@extend_schema(operation_id='change_password')
class PasswordChangeView(rest_auth_views.PasswordChangeView): class PasswordChangeView(rest_auth_views.PasswordChangeView):
action = "password-change" action = "password-change"
@extend_schema(operation_id='reset_password')
class PasswordResetView(rest_auth_views.PasswordResetView): class PasswordResetView(rest_auth_views.PasswordResetView):
action = "password-reset" action = "password-reset"
@extend_schema(operation_id='confirm_password_reset')
class PasswordResetConfirmView(rest_auth_views.PasswordResetConfirmView): class PasswordResetConfirmView(rest_auth_views.PasswordResetConfirmView):
action = "password-reset-confirm" action = "password-reset-confirm"
@ -66,6 +73,8 @@ class UserViewSet(mixins.UpdateModelMixin, viewsets.GenericViewSet):
lookup_value_regex = r"[a-zA-Z0-9-_.]+" lookup_value_regex = r"[a-zA-Z0-9-_.]+"
required_scope = "profile" required_scope = "profile"
@extend_schema(operation_id='get_authenticated_user', methods=['get'])
@extend_schema(operation_id='delete_authenticated_user', methods=['delete'])
@action(methods=["get", "delete"], detail=False) @action(methods=["get", "delete"], detail=False)
def me(self, request, *args, **kwargs): def me(self, request, *args, **kwargs):
"""Return information about the current user or delete it""" """Return information about the current user or delete it"""
@ -80,6 +89,7 @@ class UserViewSet(mixins.UpdateModelMixin, viewsets.GenericViewSet):
serializer = serializers.MeSerializer(request.user) serializer = serializers.MeSerializer(request.user)
return Response(serializer.data) return Response(serializer.data)
@extend_schema(operation_id='update_settings')
@action(methods=["post"], detail=False, url_name="settings", url_path="settings") @action(methods=["post"], detail=False, url_name="settings", url_path="settings")
def set_settings(self, request, *args, **kwargs): def set_settings(self, request, *args, **kwargs):
"""Return information about the current user or delete it""" """Return information about the current user or delete it"""
@ -111,6 +121,7 @@ class UserViewSet(mixins.UpdateModelMixin, viewsets.GenericViewSet):
data = {"subsonic_api_token": self.request.user.subsonic_api_token} data = {"subsonic_api_token": self.request.user.subsonic_api_token}
return Response(data) return Response(data)
@extend_schema(operation_id='change_email')
@action( @action(
methods=["post"], methods=["post"],
required_scope="security", required_scope="security",
@ -138,6 +149,8 @@ class UserViewSet(mixins.UpdateModelMixin, viewsets.GenericViewSet):
return super().partial_update(request, *args, **kwargs) return super().partial_update(request, *args, **kwargs)
@extend_schema(operation_id='login')
@action(methods=['post'], detail=False)
def login(request): def login(request):
throttling.check_request(request, "login") throttling.check_request(request, "login")
if request.method != "POST": if request.method != "POST":
@ -157,6 +170,8 @@ def login(request):
return response return response
@extend_schema(operation_id='logout')
@action(methods=['post'], detail=False)
def logout(request): def logout(request):
if request.method != "POST": if request.method != "POST":
return http.HttpResponse(status=405) return http.HttpResponse(status=405)

594
api/operations.json Normal file
View File

@ -0,0 +1,594 @@
{
"/api/v1/activity/": {
"get": "get_activity"
},
"/api/v1/albums/": {
"get": "get_albums",
"post": "create_album"
},
"/api/v1/albums/{id}/": {
"get": "get_album",
"delete": "delete_album"
},
"/api/v1/albums/{id}/fetches/": {
"get": "get_album_fetches",
"post": "create_album_fetch"
},
"/api/v1/albums/{id}/libraries/": {
"get": "get_album_libraries"
},
"/api/v1/albums/{id}/mutations/": {
"get": "get_album_mutations",
"post": "create_album_mutation"
},
"/api/v1/artists/": {
"get": "get_artists"
},
"/api/v1/artists/{id}/": {
"get": "get_artist"
},
"/api/v1/artists/{id}/fetches/": {
"get": "get_artist_fetches",
"post": "create_artist_fetch"
},
"/api/v1/artists/{id}/libraries/": {
"get": "get_artist_libraries"
},
"/api/v1/artists/{id}/mutations/": {
"get": "get_artist_mutations",
"post": "create_artist_mutation"
},
"/api/v1/attachments/": {
"post": "create_attachment"
},
"/api/v1/attachments/{uuid}/": {
"get": "get_attachment",
"delete": "delete_attachment"
},
"/api/v1/attachments/{uuid}/proxy/": {
"get": "get_attachment_proxy"
},
"/api/v1/auth/password/change/": {
"post": "change_password"
},
"/api/v1/auth/password/reset/": {
"post": "reset_password"
},
"/api/v1/auth/password/reset/confirm/": {
"post": "confirm_password_reset"
},
"/api/v1/auth/registration/": {
"post": "register"
},
"/api/v1/auth/registration/change-password/": {
"post": "change_password_2"
},
"/api/v1/auth/registration/verify-email/": {
"post": "verify_email"
},
"/api/v1/auth/user/": {
"get": "get_auth_user",
"put": "update_auth_user",
"patch": "partial_update_auth_user"
},
"/api/v1/channels/": {
"get": "get_channels",
"post": "create_channel"
},
"/api/v1/channels/{composite}/": {
"get": "get_channel",
"put": "update_channel",
"patch": "partial_update_channel",
"delete": "delete_channel"
},
"/api/v1/channels/{composite}/rss/": {
"get": "get_channel_rss"
},
"/api/v1/channels/{composite}/subscribe/": {
"post": "subscribe_channel"
},
"/api/v1/channels/{composite}/unsubscribe/": {
"post": "unsubscribe_channel_2",
"delete": "unsubscribe_channel"
},
"/api/v1/channels/metadata-choices/": {
"get": "get_channel_metadata_choices"
},
"/api/v1/channels/rss-subscribe/": {
"post": "subscribe_channel_rss"
},
"/api/v1/favorites/tracks/": {
"get": "get_favorite_tracks",
"post": "favorite_track"
},
"/api/v1/favorites/tracks/{id}/": {
"delete": "delete_favorite_track"
},
"/api/v1/favorites/tracks/all/": {
"get": "get_all_favorite_tracks"
},
"/api/v1/favorites/tracks/remove/": {
"post": "unfavorite_track_2",
"delete": "unfavorite_track"
},
"/api/v1/federation/actors/{full_username}/": {
"get": "get_federation_actor"
},
"/api/v1/federation/actors/{full_username}/libraries/": {
"get": "get_federation_actor_library"
},
"/api/v1/federation/domains/": {
"get": "get_federation_domains"
},
"/api/v1/federation/domains/{name}/": {
"get": "get_federation_domain"
},
"/api/v1/federation/fetches/": {
"post": "create_federation_fetch"
},
"/api/v1/federation/fetches/{id}/": {
"get": "get_federation_fetch"
},
"/api/v1/federation/follows/library/": {
"get": "get_federation_library_follows",
"post": "create_federation_library_follow"
},
"/api/v1/federation/follows/library/{uuid}/": {
"get": "get_federation_library_follow",
"delete": "delete_federation_library_follow"
},
"/api/v1/federation/follows/library/{uuid}/accept/": {
"post": "accept_federation_library_follow"
},
"/api/v1/federation/follows/library/{uuid}/reject/": {
"post": "reject_federation_library_follow"
},
"/api/v1/federation/follows/library/all/": {
"get": "get_all_federation_library_follows"
},
"/api/v1/federation/inbox/": {
"get": "get_federation_inboxes"
},
"/api/v1/federation/inbox/{id}/": {
"get": "get_federation_inbox",
"put": "update_federation_inbox",
"patch": "partial_update_federation_inbox"
},
"/api/v1/federation/inbox/action/": {
"post": "create_federation_inbox_action"
},
"/api/v1/federation/libraries/{uuid}/": {
"get": "get_federation_library"
},
"/api/v1/federation/libraries/{uuid}/scan/": {
"post": "create_federation_library_scan"
},
"/api/v1/federation/libraries/fetch/": {
"post": "create_federation_library_fetch"
},
"/api/v1/history/listenings/": {
"get": "get_history_listenings",
"post": "create_history_listening"
},
"/api/v1/history/listenings/{id}/": {
"get": "get_history_listening"
},
"/api/v1/instance/admin/settings/": {
"get": "get_instance_admin_settings"
},
"/api/v1/instance/admin/settings/{id}/": {
"get": "get_instance_admin_setting",
"put": "update_instance_admin_setting",
"patch": "partial_update_instance_admin_setting"
},
"/api/v1/instance/admin/settings/bulk/": {
"post": "create_instance_admin_setting_bulk"
},
"/api/v1/instance/nodeinfo/2.0/": {
"get": "get_instance_nodeinfo_2.0"
},
"/api/v1/instance/settings/": {
"get": "get_instance_settings"
},
"/api/v1/instance/spa-manifest.json": {
"get": "get_spa_manifest"
},
"/api/v1/libraries/": {
"get": "get_libraries",
"post": "create_library"
},
"/api/v1/libraries/{uuid}/": {
"get": "get_library",
"put": "update_library",
"patch": "partial_update_library",
"delete": "delete_library"
},
"/api/v1/libraries/{uuid}/follows/": {
"get": "get_library_follow"
},
"/api/v1/libraries/fs-import/": {
"get": "get_library_fs_import",
"post": "create_library_fs_import",
"delete": "delete_library_fs_import"
},
"/api/v1/licenses/": {
"get": "get_licenses"
},
"/api/v1/licenses/{code}/": {
"get": "get_license"
},
"/api/v1/listen/{uuid}/": {
"get": "get_listen"
},
"/api/v1/manage/accounts/": {
"get": "admin_get_accounts"
},
"/api/v1/manage/accounts/{id}/": {
"get": "admin_get_account"
},
"/api/v1/manage/accounts/{id}/stats/": {
"get": "admin_get_account_stats"
},
"/api/v1/manage/accounts/action/": {
"post": "admin_create_account_action"
},
"/api/v1/manage/channels/": {
"get": "admin_get_channels"
},
"/api/v1/manage/channels/{composite}/": {
"get": "admin_get_channel",
"delete": "admin_delete_channel"
},
"/api/v1/manage/channels/{composite}/stats/": {
"get": "admin_get_channel_stats"
},
"/api/v1/manage/federation/domains/": {
"get": "admin_get_federation_domains",
"post": "admin_create_federation_domain"
},
"/api/v1/manage/federation/domains/{name}/": {
"get": "admin_get_federation_domain",
"put": "admin_update_federation_domain",
"patch": "admin_partial_update_federation_domain"
},
"/api/v1/manage/federation/domains/{name}/nodeinfo/": {
"get": "admin_get_federation_domain_nodeinfo"
},
"/api/v1/manage/federation/domains/{name}/stats/": {
"get": "admin_get_federation_domain_stats"
},
"/api/v1/manage/federation/domains/action/": {
"post": "admin_create_federation_domain_action"
},
"/api/v1/manage/library/albums/": {
"get": "admin_get_albums"
},
"/api/v1/manage/library/albums/{id}/": {
"get": "admin_get_album",
"delete": "admin_delete_album"
},
"/api/v1/manage/library/albums/{id}/stats/": {
"get": "admin_get_library_album_stats"
},
"/api/v1/manage/library/albums/action/": {
"post": "admin_create_album_action"
},
"/api/v1/manage/library/artists/": {
"get": "admin_get_artists"
},
"/api/v1/manage/library/artists/{id}/": {
"get": "admin_get_artist",
"delete": "admin_delete_artist"
},
"/api/v1/manage/library/artists/{id}/stats/": {
"get": "admin_get_library_artist_stats"
},
"/api/v1/manage/library/artists/action/": {
"post": "admin_create_artist_action"
},
"/api/v1/manage/library/libraries/": {
"get": "admin_get_libraries"
},
"/api/v1/manage/library/libraries/{uuid}/": {
"get": "admin_get_library",
"put": "admin_update_library",
"patch": "admin_partial_update_library",
"delete": "admin_delete_library"
},
"/api/v1/manage/library/libraries/{uuid}/stats/": {
"get": "admin_get_library_stats"
},
"/api/v1/manage/library/libraries/action/": {
"post": "admin_create_library_action"
},
"/api/v1/manage/library/tracks/": {
"get": "admin_get_tracks"
},
"/api/v1/manage/library/tracks/{id}/": {
"get": "admin_get_track",
"delete": "admin_delete_track"
},
"/api/v1/manage/library/tracks/{id}/stats/": {
"get": "admin_get_track_stats"
},
"/api/v1/manage/library/tracks/action/": {
"post": "admin_create_track_action"
},
"/api/v1/manage/library/uploads/": {
"get": "admin_get_uploads"
},
"/api/v1/manage/library/uploads/{uuid}/": {
"get": "admin_get_upload",
"delete": "admin_delete_upload"
},
"/api/v1/manage/library/uploads/action/": {
"post": "admin_create_upload_action"
},
"/api/v1/manage/moderation/instance-policies/": {
"get": "moderation_get_instance_policies",
"post": "moderation_create_instance_policy"
},
"/api/v1/manage/moderation/instance-policies/{id}/": {
"get": "moderation_get_instance_policy",
"put": "moderation_update_instance_policy",
"patch": "moderation_partial_update_instance_policy",
"delete": "moderation_delete_instance_policy"
},
"/api/v1/manage/moderation/notes/": {
"get": "moderation_get_notes",
"post": "moderation_create_note"
},
"/api/v1/manage/moderation/notes/{uuid}/": {
"get": "moderation_get_note",
"delete": "moderation_delete_note"
},
"/api/v1/manage/moderation/reports/": {
"get": "moderation_get_reports"
},
"/api/v1/manage/moderation/reports/{uuid}/": {
"get": "moderation_get_report",
"put": "moderation_update_report",
"patch": "moderation_partial_update_report"
},
"/api/v1/manage/moderation/requests/": {
"get": "moderation_get_requests"
},
"/api/v1/manage/moderation/requests/{uuid}/": {
"get": "moderation_get_request",
"put": "moderation_update_request",
"patch": "moderation_partial_update_request"
},
"/api/v1/manage/tags/": {
"get": "admin_get_tags",
"post": "admin_create_tag"
},
"/api/v1/manage/tags/{name}/": {
"get": "admin_get_tag",
"delete": "admin_delete_tag"
},
"/api/v1/manage/tags/action/": {
"post": "admin_create_tag_action"
},
"/api/v1/manage/users/invitations/": {
"get": "admin_get_invitations",
"post": "admin_create_invitation"
},
"/api/v1/manage/users/invitations/{id}/": {
"get": "admin_get_invitation",
"put": "admin_update_invitation",
"patch": "admin_partial_update_invitation"
},
"/api/v1/manage/users/invitations/action/": {
"post": "admin_create_invitation_action"
},
"/api/v1/manage/users/users/": {
"get": "admin_get_users"
},
"/api/v1/manage/users/users/{id}/": {
"get": "admin_get_user",
"put": "admin_update_user",
"patch": "admin_partial_update_user"
},
"/api/v1/moderation/content-filters/": {
"get": "get_moderation_content_filters",
"post": "create_moderation_content_filter"
},
"/api/v1/moderation/content-filters/{uuid}/": {
"get": "get_moderation_content_filter",
"delete": "delete_moderation_content_filter"
},
"/api/v1/moderation/reports/": {
"post": "create_moderation_report"
},
"/api/v1/mutations/": {
"get": "get_mutations"
},
"/api/v1/mutations/{uuid}/": {
"get": "get_mutation",
"delete": "delete_mutation"
},
"/api/v1/mutations/{uuid}/approve/": {
"post": "approve_mutation"
},
"/api/v1/mutations/{uuid}/reject/": {
"post": "reject_mutation"
},
"/api/v1/oauth/apps/": {
"get": "get_oauth_apps",
"post": "create_oauth_app"
},
"/api/v1/oauth/apps/{client_id}/": {
"get": "get_oauth_app",
"put": "update_oauth_app",
"patch": "partial_update_oauth_app",
"delete": "delete_oauth_app"
},
"/api/v1/oauth/apps/{client_id}/refresh-token/": {
"post": "refresh_oauth_token"
},
"/api/v1/oauth/authorize/": {
"get": "get_oauth_authorize",
"post": "create_oauth_authorize",
"put": "update_oauth_authorize"
},
"/api/v1/oauth/grants/": {
"get": "get_oauth_grants"
},
"/api/v1/oauth/grants/{client_id}/": {
"get": "get_oauth_grant",
"delete": "delete_oauth_grant"
},
"/api/v1/oembed/": {
"get": "get_oembed"
},
"/api/v1/playlists/": {
"get": "get_playlists",
"post": "create_playlist"
},
"/api/v1/playlists/{id}/": {
"get": "get_playlist",
"put": "update_playlist",
"patch": "partial_update_playlist",
"delete": "delete_playlist"
},
"/api/v1/playlists/{id}/add/": {
"post": "add_to_playlist"
},
"/api/v1/playlists/{id}/clear/": {
"delete": "clear_playlist"
},
"/api/v1/playlists/{id}/move/": {
"post": "reorder_track_in_playlist"
},
"/api/v1/playlists/{id}/remove/": {
"post": "remove_from_playlist_2",
"delete": "remove_from_playlist"
},
"/api/v1/playlists/{id}/tracks/": {
"get": "get_playlist_tracks"
},
"/api/v1/plugins/": {
"get": "get_plugins",
"post": "create_plugin"
},
"/api/v1/plugins/{id}/": {
"get": "get_plugin"
},
"/api/v1/plugins/{id}/disable/": {
"post": "disable_plugin"
},
"/api/v1/plugins/{id}/enable/": {
"post": "enable_plugin"
},
"/api/v1/plugins/{id}/scan/": {
"post": "create_plugin_scan"
},
"/api/v1/radios/radios/": {
"get": "get_radio_radios",
"post": "create_radio_radio"
},
"/api/v1/radios/radios/{id}/": {
"get": "get_radio_radio",
"put": "update_radio_radio",
"patch": "partial_update_radio_radio",
"delete": "delete_radio_radio"
},
"/api/v1/radios/radios/{id}/tracks/": {
"get": "get_radio_track"
},
"/api/v1/radios/radios/filters/": {
"get": "get_radio_filter"
},
"/api/v1/radios/radios/validate/": {
"post": "validate_radio"
},
"/api/v1/radios/sessions/": {
"post": "create_radio_session"
},
"/api/v1/radios/sessions/{id}/": {
"get": "get_radio_session"
},
"/api/v1/radios/tracks/": {
"post": "get_next_radio_track"
},
"/api/v1/rate-limit/": {
"get": "get_rate_limit"
},
"/api/v1/search": {
"get": "get_search_results"
},
"/api/v1/stream/{uuid}/": {
"get": "get_stream"
},
"/api/v1/subscriptions/": {
"get": "get_subscriptions"
},
"/api/v1/subscriptions/{uuid}/": {
"get": "get_subscription"
},
"/api/v1/subscriptions/all/": {
"get": "get_all_subscriptions"
},
"/api/v1/tags/": {
"get": "get_tags"
},
"/api/v1/tags/{name}/": {
"get": "get_tag"
},
"/api/v1/text-preview/": {
"post": "preview_text"
},
"/api/v1/tracks/": {
"get": "get_tracks"
},
"/api/v1/tracks/{id}/": {
"get": "get_track",
"delete": "delete_track"
},
"/api/v1/tracks/{id}/fetches/": {
"get": "get_track_fetches",
"post": "create_track_fetch"
},
"/api/v1/tracks/{id}/libraries/": {
"get": "get_track_libraries"
},
"/api/v1/tracks/{id}/mutations/": {
"get": "get_track_mutations",
"post": "create_track_mutation"
},
"/api/v1/uploads/": {
"get": "get_uploads",
"post": "create_upload"
},
"/api/v1/uploads/{uuid}/": {
"get": "get_upload",
"put": "update_upload",
"patch": "partial_update_upload",
"delete": "delete_upload"
},
"/api/v1/uploads/{uuid}/audio-file-metadata/": {
"get": "get_upload_metadata"
},
"/api/v1/uploads/action/": {
"post": "create_upload_action"
},
"/api/v1/users/{username}/": {
"put": "update_user",
"patch": "partial_update_user"
},
"/api/v1/users/{username}/subsonic-token/": {
"get": "get_user_subsonic_token",
"post": "create_user_subsonic_token",
"delete": "delete_user_subsonic_token"
},
"/api/v1/users/change-email/": {
"post": "change_email"
},
"/api/v1/users/me/": {
"get": "get_authenticated_user",
"delete": "delete_authenticated_user"
},
"/api/v1/users/settings/": {
"post": "update_settings"
}
}

View File

@ -58,6 +58,7 @@ django-cache-memoize = "0.1.10"
requests-http-message-signatures = "==0.3.1" requests-http-message-signatures = "==0.3.1"
drf-spectacular = "==0.23.1" drf-spectacular = "==0.23.1"
sentry-sdk = "==1.9.8" sentry-sdk = "==1.9.8"
pluralizer = "==1.2.0"
[tool.poetry.dev-dependencies] [tool.poetry.dev-dependencies]
flake8 = "==3.9.2" flake8 = "==3.9.2"

13390
api/schema.yml Normal file

File diff suppressed because it is too large Load Diff