feat(api): add /info and /stream/* endpoints

This commit is contained in:
jo 2022-08-24 10:38:53 +02:00 committed by Kyle Robbertze
parent 5bf62dd9cb
commit 12d2d4b15a
15 changed files with 324 additions and 173 deletions

View File

@ -8,6 +8,9 @@ class ApiClient(AbstractApiClient):
super().__init__(base_url=base_url) super().__init__(base_url=base_url)
self.session.headers.update({"Authorization": f"Api-Key {api_key}"}) self.session.headers.update({"Authorization": f"Api-Key {api_key}"})
def get_info(self, **kwargs) -> Response:
return self._request("GET", "/api/v2/info", **kwargs)
def get_version(self, **kwargs) -> Response: def get_version(self, **kwargs) -> Response:
return self._request("GET", "/api/v2/version", **kwargs) return self._request("GET", "/api/v2/version", **kwargs)
@ -31,3 +34,9 @@ class ApiClient(AbstractApiClient):
def download_file(self, item_id: int, **kwargs) -> Response: def download_file(self, item_id: int, **kwargs) -> Response:
return self._request("GET", f"/api/v2/files/{item_id}/download", **kwargs) return self._request("GET", f"/api/v2/files/{item_id}/download", **kwargs)
def get_stream_preferences(self, **kwargs) -> Response:
return self._request("GET", "/api/v2/stream/preferences", **kwargs)
def get_stream_state(self, **kwargs) -> Response:
return self._request("GET", "/api/v2/stream/state", **kwargs)

View File

@ -1,4 +1,45 @@
from enum import Enum
from django.db import models from django.db import models
from pydantic import BaseModel
class SitePreferences(BaseModel):
station_name: str
class MessageFormatKind(int, Enum):
ARTIST_TITLE = 0
SHOW_ARTIST_TITLE = 1
RADIO_SHOW = 2
class StreamPreferences(BaseModel):
input_fade_transition: float
message_format: MessageFormatKind
message_offline: str
# input_auto_switch_off: bool
# input_auto_switch_on: bool
# input_main_user: str
# input_main_password: str
# replay_gain_enabled: bool
# replay_gain_offset: float
# track_fade_in: float
# track_fade_out: float
# track_fade_transition: float
class StreamState(BaseModel):
input_main_connected: bool
input_main_streaming: bool
input_show_connected: bool
input_show_streaming: bool
schedule_streaming: bool
class SitePreferenceManager(models.Manager):
def get_queryset(self):
return super().get_queryset().filter(user__isnull=True)
class Preference(models.Model): class Preference(models.Model):
@ -22,6 +63,36 @@ class Preference(models.Model):
db_column="valstr", db_column="valstr",
) )
objects = models.Manager()
site = SitePreferenceManager()
@classmethod
def get_site_preferences(cls) -> SitePreferences:
entries = dict(cls.site.values_list("key", "value"))
return SitePreferences(
station_name=entries.get("station_name") or "LibreTime",
)
@classmethod
def get_stream_preferences(cls) -> StreamPreferences:
entries = dict(cls.site.values_list("key", "value"))
return StreamPreferences(
input_fade_transition=float(entries.get("default_transition_fade") or 0.0),
message_format=int(entries.get("stream_label_format") or 0),
message_offline=entries.get("off_air_meta") or "Offline",
)
@classmethod
def get_stream_state(cls) -> StreamState:
entries = dict(cls.site.values_list("key", "value"))
return StreamState(
input_main_connected=entries.get("master_dj") == "true",
input_main_streaming=entries.get("master_dj_switch") == "on",
input_show_connected=entries.get("live_dj") == "true",
input_show_streaming=entries.get("live_dj_switch") == "on",
schedule_streaming=entries.get("scheduled_play_switch") == "on",
)
class Meta: class Meta:
managed = False managed = False
db_table = "cc_pref" db_table = "cc_pref"

View File

@ -3,10 +3,12 @@ from rest_framework import routers
from .views import ( from .views import (
CeleryTaskViewSet, CeleryTaskViewSet,
InfoView,
LoginAttemptViewSet, LoginAttemptViewSet,
PreferenceViewSet, PreferenceViewSet,
ServiceRegisterViewSet, ServiceRegisterViewSet,
StreamSettingViewSet, StreamPreferencesView,
StreamStateView,
ThirdPartyTrackReferenceViewSet, ThirdPartyTrackReferenceViewSet,
UserTokenViewSet, UserTokenViewSet,
UserViewSet, UserViewSet,
@ -17,7 +19,6 @@ router = routers.DefaultRouter(trailing_slash=False)
router.register("login-attempts", LoginAttemptViewSet) router.register("login-attempts", LoginAttemptViewSet)
router.register("preferences", PreferenceViewSet) router.register("preferences", PreferenceViewSet)
router.register("service-registers", ServiceRegisterViewSet) router.register("service-registers", ServiceRegisterViewSet)
router.register("stream-settings", StreamSettingViewSet)
router.register("users", UserViewSet) router.register("users", UserViewSet)
router.register("user-tokens", UserTokenViewSet) router.register("user-tokens", UserTokenViewSet)
router.register("celery-tasks", CeleryTaskViewSet) router.register("celery-tasks", CeleryTaskViewSet)
@ -25,5 +26,8 @@ router.register("third-party-track-references", ThirdPartyTrackReferenceViewSet)
urls = [ urls = [
*router.urls, *router.urls,
path("info", InfoView.as_view()),
path("version", VersionView.as_view()), path("version", VersionView.as_view()),
path("stream/preferences", StreamPreferencesView.as_view()),
path("stream/state", StreamStateView.as_view()),
] ]

View File

@ -1,6 +1,7 @@
from .auth import LoginAttemptSerializer, UserTokenSerializer from .auth import LoginAttemptSerializer, UserTokenSerializer
from .info import VersionSerializer from .info import InfoSerializer, VersionSerializer
from .preference import PreferenceSerializer, StreamSettingSerializer from .preference import PreferenceSerializer
from .service import ServiceRegisterSerializer from .service import ServiceRegisterSerializer
from .stream import StreamPreferencesSerializer, StreamStateSerializer
from .user import UserSerializer from .user import UserSerializer
from .worker import CeleryTaskSerializer, ThirdPartyTrackReferenceSerializer from .worker import CeleryTaskSerializer, ThirdPartyTrackReferenceSerializer

View File

@ -3,4 +3,9 @@ from rest_framework import serializers
# pylint: disable=abstract-method # pylint: disable=abstract-method
class VersionSerializer(serializers.Serializer): class VersionSerializer(serializers.Serializer):
api_version = serializers.CharField() api_version = serializers.CharField(read_only=True)
# pylint: disable=abstract-method
class InfoSerializer(serializers.Serializer):
station_name = serializers.CharField(read_only=True)

View File

@ -1,15 +1,9 @@
from rest_framework import serializers from rest_framework import serializers
from ..models import Preference, StreamSetting from ..models import Preference
class PreferenceSerializer(serializers.ModelSerializer): class PreferenceSerializer(serializers.ModelSerializer):
class Meta: class Meta:
model = Preference model = Preference
fields = "__all__" fields = "__all__"
class StreamSettingSerializer(serializers.ModelSerializer):
class Meta:
model = StreamSetting
fields = "__all__"

View File

@ -0,0 +1,17 @@
from rest_framework import serializers
# pylint: disable=abstract-method
class StreamPreferencesSerializer(serializers.Serializer):
input_fade_transition = serializers.FloatField(read_only=True)
message_format = serializers.IntegerField(read_only=True)
message_offline = serializers.CharField(read_only=True)
# pylint: disable=abstract-method
class StreamStateSerializer(serializers.Serializer):
input_main_connected = serializers.BooleanField(read_only=True)
input_main_streaming = serializers.BooleanField(read_only=True)
input_show_connected = serializers.BooleanField(read_only=True)
input_show_streaming = serializers.BooleanField(read_only=True)
schedule_streaming = serializers.BooleanField(read_only=True)

View File

@ -0,0 +1,31 @@
from libretime_api.core.models.preference import Preference
# pylint: disable=invalid-name,unused-argument
def test_preference_get_site_preferences(db):
result = Preference.get_site_preferences()
assert result.dict() == {
"station_name": "LibreTime",
}
# pylint: disable=invalid-name,unused-argument
def test_preference_get_stream_preferences(db):
result = Preference.get_stream_preferences()
assert result.dict() == {
"input_fade_transition": 0.0,
"message_format": 0,
"message_offline": "LibreTime - offline",
}
# pylint: disable=invalid-name,unused-argument
def test_preference_get_stream_state(db):
result = Preference.get_stream_state()
assert result.dict() == {
"input_main_connected": False,
"input_main_streaming": False,
"input_show_connected": False,
"input_show_streaming": False,
"schedule_streaming": True,
}

View File

@ -0,0 +1,19 @@
from rest_framework.test import APIClient
# pylint: disable=invalid-name,unused-argument
def test_version_get(db, api_client: APIClient):
response = api_client.get("/api/v2/version")
assert response.status_code == 200
assert response.json() == {
"api_version": "2.0.0",
}
# pylint: disable=invalid-name,unused-argument
def test_info_get(db, api_client: APIClient):
response = api_client.get("/api/v2/info")
assert response.status_code == 200
assert response.json() == {
"station_name": "LibreTime",
}

View File

@ -0,0 +1,25 @@
from rest_framework.test import APIClient
# pylint: disable=invalid-name,unused-argument
def test_stream_preferences_get(db, api_client: APIClient):
response = api_client.get("/api/v2/stream/preferences")
assert response.status_code == 200
assert response.json() == {
"input_fade_transition": 0.0,
"message_format": 0,
"message_offline": "LibreTime - offline",
}
# pylint: disable=invalid-name,unused-argument
def test_stream_state_get(db, api_client: APIClient):
response = api_client.get("/api/v2/stream/state")
assert response.status_code == 200
assert response.json() == {
"input_main_connected": False,
"input_main_streaming": False,
"input_show_connected": False,
"input_show_streaming": False,
"schedule_streaming": True,
}

View File

@ -1,6 +1,7 @@
from .auth import LoginAttemptViewSet, UserTokenViewSet from .auth import LoginAttemptViewSet, UserTokenViewSet
from .info import VersionView from .info import InfoView, VersionView
from .preference import PreferenceViewSet, StreamSettingViewSet from .preference import PreferenceViewSet
from .service import ServiceRegisterViewSet from .service import ServiceRegisterViewSet
from .stream import StreamPreferencesView, StreamStateView
from .user import UserViewSet from .user import UserViewSet
from .worker import CeleryTaskViewSet, ThirdPartyTrackReferenceViewSet from .worker import CeleryTaskViewSet, ThirdPartyTrackReferenceViewSet

View File

@ -3,7 +3,8 @@ from rest_framework.permissions import AllowAny
from rest_framework.response import Response from rest_framework.response import Response
from rest_framework.views import APIView from rest_framework.views import APIView
from ..serializers import VersionSerializer from ..models import Preference
from ..serializers import InfoSerializer, VersionSerializer
class VersionView(APIView): class VersionView(APIView):
@ -12,3 +13,18 @@ class VersionView(APIView):
def get(self, request): def get(self, request):
return Response({"api_version": settings.API_VERSION}) return Response({"api_version": settings.API_VERSION})
class InfoView(APIView):
permission_classes = [AllowAny]
serializer_class = InfoSerializer
def get(self, request):
data = Preference.get_site_preferences()
return Response(
data.dict(
include={
"station_name",
}
)
)

View File

@ -1,16 +1,10 @@
from rest_framework import viewsets from rest_framework import viewsets
from ..models import Preference, StreamSetting from ..models import Preference
from ..serializers import PreferenceSerializer, StreamSettingSerializer from ..serializers import PreferenceSerializer
class PreferenceViewSet(viewsets.ModelViewSet): class PreferenceViewSet(viewsets.ModelViewSet):
queryset = Preference.objects.all() queryset = Preference.objects.all()
serializer_class = PreferenceSerializer serializer_class = PreferenceSerializer
model_permission_name = "preference" model_permission_name = "preference"
class StreamSettingViewSet(viewsets.ModelViewSet):
queryset = StreamSetting.objects.all()
serializer_class = StreamSettingSerializer
model_permission_name = "streamsetting"

View File

@ -0,0 +1,44 @@
from rest_framework import views
from rest_framework.response import Response
from ...permissions import IsSystemTokenOrUser
from ..models import Preference
from ..serializers import StreamPreferencesSerializer, StreamStateSerializer
class StreamPreferencesView(views.APIView):
permission_classes = [IsSystemTokenOrUser]
serializer_class = StreamPreferencesSerializer
model_permission_name = "streamsetting"
def get(self, request):
data = Preference.get_stream_preferences()
return Response(
data.dict(
include={
"input_fade_transition",
"message_format",
"message_offline",
}
)
)
class StreamStateView(views.APIView):
permission_classes = [IsSystemTokenOrUser]
serializer_class = StreamStateSerializer
model_permission_name = "streamsetting"
def get(self, request):
data = Preference.get_stream_state()
return Response(
data.dict(
include={
"input_main_connected",
"input_main_streaming",
"input_show_connected",
"input_show_streaming",
"schedule_streaming",
}
)
)

View File

@ -467,6 +467,22 @@ paths:
responses: responses:
"204": "204":
description: No response body description: No response body
/api/v2/info:
get:
operationId: info_retrieve
tags:
- info
security:
- cookieAuth: []
- basicAuth: []
- {}
responses:
"200":
content:
application/json:
schema:
$ref: "#/components/schemas/Info"
description: ""
/api/v2/libraries: /api/v2/libraries:
get: get:
operationId: libraries_list operationId: libraries_list
@ -4326,11 +4342,11 @@ paths:
responses: responses:
"204": "204":
description: No response body description: No response body
/api/v2/stream-settings: /api/v2/stream/preferences:
get: get:
operationId: stream_settings_list operationId: stream_preferences_retrieve
tags: tags:
- stream-settings - stream
security: security:
- cookieAuth: [] - cookieAuth: []
- basicAuth: [] - basicAuth: []
@ -4339,48 +4355,13 @@ paths:
content: content:
application/json: application/json:
schema: schema:
type: array $ref: "#/components/schemas/StreamPreferences"
items:
$ref: "#/components/schemas/StreamSetting"
description: "" description: ""
post: /api/v2/stream/state:
operationId: stream_settings_create
tags:
- stream-settings
requestBody:
content:
application/json:
schema:
$ref: "#/components/schemas/StreamSetting"
application/x-www-form-urlencoded:
schema:
$ref: "#/components/schemas/StreamSetting"
multipart/form-data:
schema:
$ref: "#/components/schemas/StreamSetting"
required: true
security:
- cookieAuth: []
- basicAuth: []
responses:
"201":
content:
application/json:
schema:
$ref: "#/components/schemas/StreamSetting"
description: ""
/api/v2/stream-settings/{key}:
get: get:
operationId: stream_settings_retrieve operationId: stream_state_retrieve
parameters:
- in: path
name: key
schema:
type: string
description: A unique value identifying this stream setting.
required: true
tags: tags:
- stream-settings - stream
security: security:
- cookieAuth: [] - cookieAuth: []
- basicAuth: [] - basicAuth: []
@ -4389,90 +4370,8 @@ paths:
content: content:
application/json: application/json:
schema: schema:
$ref: "#/components/schemas/StreamSetting" $ref: "#/components/schemas/StreamState"
description: "" description: ""
put:
operationId: stream_settings_update
parameters:
- in: path
name: key
schema:
type: string
description: A unique value identifying this stream setting.
required: true
tags:
- stream-settings
requestBody:
content:
application/json:
schema:
$ref: "#/components/schemas/StreamSetting"
application/x-www-form-urlencoded:
schema:
$ref: "#/components/schemas/StreamSetting"
multipart/form-data:
schema:
$ref: "#/components/schemas/StreamSetting"
required: true
security:
- cookieAuth: []
- basicAuth: []
responses:
"200":
content:
application/json:
schema:
$ref: "#/components/schemas/StreamSetting"
description: ""
patch:
operationId: stream_settings_partial_update
parameters:
- in: path
name: key
schema:
type: string
description: A unique value identifying this stream setting.
required: true
tags:
- stream-settings
requestBody:
content:
application/json:
schema:
$ref: "#/components/schemas/PatchedStreamSetting"
application/x-www-form-urlencoded:
schema:
$ref: "#/components/schemas/PatchedStreamSetting"
multipart/form-data:
schema:
$ref: "#/components/schemas/PatchedStreamSetting"
security:
- cookieAuth: []
- basicAuth: []
responses:
"200":
content:
application/json:
schema:
$ref: "#/components/schemas/StreamSetting"
description: ""
delete:
operationId: stream_settings_destroy
parameters:
- in: path
name: key
schema:
type: string
description: A unique value identifying this stream setting.
required: true
tags:
- stream-settings
security:
- cookieAuth: []
- basicAuth: []
responses:
"204":
description: No response body
/api/v2/third-party-track-references: /api/v2/third-party-track-references:
get: get:
operationId: third_party_track_references_list operationId: third_party_track_references_list
@ -5684,6 +5583,14 @@ components:
- id - id
- override_album - override_album
- podcast - podcast
Info:
type: object
properties:
station_name:
type: string
readOnly: true
required:
- station_name
Library: Library:
type: object type: object
properties: properties:
@ -6708,19 +6615,6 @@ components:
readOnly: true readOnly: true
podcast: podcast:
type: integer type: integer
PatchedStreamSetting:
type: object
properties:
key:
type: string
maxLength: 64
raw_value:
type: string
nullable: true
maxLength: 255
type:
type: string
maxLength: 16
PatchedThirdPartyTrackReference: PatchedThirdPartyTrackReference:
type: object type: object
properties: properties:
@ -7579,22 +7473,47 @@ components:
required: required:
- id - id
- podcast - podcast
StreamSetting: StreamPreferences:
type: object type: object
properties: properties:
key: input_fade_transition:
type: number
format: double
readOnly: true
message_format:
type: integer
readOnly: true
message_offline:
type: string type: string
maxLength: 64 readOnly: true
raw_value:
type: string
nullable: true
maxLength: 255
type:
type: string
maxLength: 16
required: required:
- key - input_fade_transition
- type - message_format
- message_offline
StreamState:
type: object
properties:
input_main_connected:
type: boolean
readOnly: true
input_main_streaming:
type: boolean
readOnly: true
input_show_connected:
type: boolean
readOnly: true
input_show_streaming:
type: boolean
readOnly: true
schedule_streaming:
type: boolean
readOnly: true
required:
- input_main_connected
- input_main_streaming
- input_show_connected
- input_show_streaming
- schedule_streaming
ThirdPartyTrackReference: ThirdPartyTrackReference:
type: object type: object
properties: properties:
@ -7714,6 +7633,7 @@ components:
properties: properties:
api_version: api_version:
type: string type: string
readOnly: true
required: required:
- api_version - api_version
Webstream: Webstream: