feat(api): split api into multiple apps (#1626)

Fixes #1622

- split the api into 4 apps: core, history, schedule, storage
- exploded the settings into testing/prod
This commit is contained in:
Jonas L 2022-04-04 14:38:50 +02:00 committed by GitHub
parent 87d2da9d84
commit fce988aef1
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
120 changed files with 1499 additions and 1078 deletions

View file

View file

@ -0,0 +1,7 @@
from django.apps import AppConfig
class StorageConfig(AppConfig):
default_auto_field = "django.db.models.BigAutoField"
name = "libretime_api.storage"
verbose_name = "LibreTime Storage API"

View file

@ -0,0 +1,4 @@
from .cloud_file import CloudFile
from .file import File
from .storage import MusicDir
from .track_type import TrackType

View file

@ -0,0 +1,13 @@
from django.db import models
class CloudFile(models.Model):
storage_backend = models.CharField(max_length=512)
resource_id = models.TextField()
filename = models.ForeignKey(
"File", models.DO_NOTHING, blank=True, null=True, db_column="cc_file_id"
)
class Meta:
managed = False
db_table = "cloud_file"

View file

@ -0,0 +1,94 @@
from django.db import models
class File(models.Model):
name = models.CharField(max_length=255)
mime = models.CharField(max_length=255)
ftype = models.CharField(max_length=128)
directory = models.ForeignKey(
"MusicDir", models.DO_NOTHING, db_column="directory", blank=True, null=True
)
filepath = models.TextField(blank=True, null=True)
import_status = models.IntegerField()
currently_accessing = models.IntegerField(db_column="currentlyaccessing")
edited_by = models.ForeignKey(
"core.User",
models.DO_NOTHING,
db_column="editedby",
blank=True,
null=True,
related_name="edited_files",
)
mtime = models.DateTimeField(blank=True, null=True)
utime = models.DateTimeField(blank=True, null=True)
lptime = models.DateTimeField(blank=True, null=True)
md5 = models.CharField(max_length=32, blank=True, null=True)
track_title = models.CharField(max_length=512, blank=True, null=True)
artist_name = models.CharField(max_length=512, blank=True, null=True)
bit_rate = models.IntegerField(blank=True, null=True)
sample_rate = models.IntegerField(blank=True, null=True)
format = models.CharField(max_length=128, blank=True, null=True)
length = models.DurationField(blank=True, null=True)
album_title = models.CharField(max_length=512, blank=True, null=True)
genre = models.CharField(max_length=64, blank=True, null=True)
comments = models.TextField(blank=True, null=True)
year = models.CharField(max_length=16, blank=True, null=True)
track_number = models.IntegerField(blank=True, null=True)
channels = models.IntegerField(blank=True, null=True)
url = models.CharField(max_length=1024, blank=True, null=True)
bpm = models.IntegerField(blank=True, null=True)
rating = models.CharField(max_length=8, blank=True, null=True)
encoded_by = models.CharField(max_length=255, blank=True, null=True)
disc_number = models.CharField(max_length=8, blank=True, null=True)
mood = models.CharField(max_length=64, blank=True, null=True)
label = models.CharField(max_length=512, blank=True, null=True)
composer = models.CharField(max_length=512, blank=True, null=True)
encoder = models.CharField(max_length=64, blank=True, null=True)
checksum = models.CharField(max_length=256, blank=True, null=True)
lyrics = models.TextField(blank=True, null=True)
orchestra = models.CharField(max_length=512, blank=True, null=True)
conductor = models.CharField(max_length=512, blank=True, null=True)
lyricist = models.CharField(max_length=512, blank=True, null=True)
original_lyricist = models.CharField(max_length=512, blank=True, null=True)
radio_station_name = models.CharField(max_length=512, blank=True, null=True)
info_url = models.CharField(max_length=512, blank=True, null=True)
artist_url = models.CharField(max_length=512, blank=True, null=True)
audio_source_url = models.CharField(max_length=512, blank=True, null=True)
radio_station_url = models.CharField(max_length=512, blank=True, null=True)
buy_this_url = models.CharField(max_length=512, blank=True, null=True)
isrc_number = models.CharField(max_length=512, blank=True, null=True)
catalog_number = models.CharField(max_length=512, blank=True, null=True)
original_artist = models.CharField(max_length=512, blank=True, null=True)
copyright = models.CharField(max_length=512, blank=True, null=True)
report_datetime = models.CharField(max_length=32, blank=True, null=True)
report_location = models.CharField(max_length=512, blank=True, null=True)
report_organization = models.CharField(max_length=512, blank=True, null=True)
subject = models.CharField(max_length=512, blank=True, null=True)
contributor = models.CharField(max_length=512, blank=True, null=True)
language = models.CharField(max_length=512, blank=True, null=True)
file_exists = models.BooleanField(blank=True, null=True)
replay_gain = models.DecimalField(
max_digits=8, decimal_places=2, blank=True, null=True
)
owner = models.ForeignKey("core.User", models.DO_NOTHING, blank=True, null=True)
cuein = models.DurationField(blank=True, null=True)
cueout = models.DurationField(blank=True, null=True)
silan_check = models.BooleanField(blank=True, null=True)
hidden = models.BooleanField(blank=True, null=True)
is_scheduled = models.BooleanField(blank=True, null=True)
is_playlist = models.BooleanField(blank=True, null=True)
filesize = models.IntegerField()
description = models.CharField(max_length=512, blank=True, null=True)
artwork = models.CharField(max_length=512, blank=True, null=True)
track_type = models.CharField(max_length=16, blank=True, null=True)
def get_owner(self):
return self.owner
class Meta:
managed = False
db_table = "cc_files"
permissions = [
("change_own_file", "Change the files where they are the owner"),
("delete_own_file", "Delete the files where they are the owner"),
]

View file

@ -0,0 +1,12 @@
from django.db import models
class MusicDir(models.Model):
directory = models.TextField(unique=True, blank=True, null=True)
type = models.CharField(max_length=255, blank=True, null=True)
exists = models.BooleanField(blank=True, null=True)
watched = models.BooleanField(blank=True, null=True)
class Meta:
managed = False
db_table = "cc_music_dirs"

View file

@ -0,0 +1,12 @@
from django.db import models
class TrackType(models.Model):
code = models.CharField(max_length=16, unique=True)
type_name = models.CharField(max_length=255, blank=True, null=True)
description = models.CharField(max_length=255, blank=True, null=True)
visibility = models.BooleanField(blank=True, default=True)
class Meta:
managed = False
db_table = "cc_track_types"

View file

@ -0,0 +1,9 @@
from rest_framework import routers
from .views import CloudFileViewSet, FileViewSet, MusicDirViewSet, TrackTypeViewSet
router = routers.DefaultRouter()
router.register("files", FileViewSet)
router.register("music-dirs", MusicDirViewSet)
router.register("cloud-files", CloudFileViewSet)
router.register("track-types", TrackTypeViewSet)

View file

@ -0,0 +1,4 @@
from .cloud_file import CloudFileSerializer
from .file import FileSerializer
from .storage import MusicDirSerializer
from .track_type import TrackTypeSerializer

View file

@ -0,0 +1,9 @@
from rest_framework import serializers
from ..models import CloudFile
class CloudFileSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = CloudFile
fields = "__all__"

View file

@ -0,0 +1,11 @@
from rest_framework import serializers
from ..models import File
class FileSerializer(serializers.HyperlinkedModelSerializer):
id = serializers.IntegerField(read_only=True)
class Meta:
model = File
fields = "__all__"

View file

@ -0,0 +1,9 @@
from rest_framework import serializers
from ..models import MusicDir
class MusicDirSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = MusicDir
fields = "__all__"

View file

@ -0,0 +1,9 @@
from rest_framework import serializers
from ..models import TrackType
class TrackTypeSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = TrackType
fields = "__all__"

View file

@ -0,0 +1,42 @@
import os
from django.conf import settings
from model_bakery import baker
from rest_framework.test import APITestCase
from ...._fixtures import AUDIO_FILENAME, fixture_path
class TestFileViewSet(APITestCase):
@classmethod
def setUpTestData(cls):
cls.path = "/api/v2/files/{id}/download/"
cls.token = settings.CONFIG.general.api_key
def test_invalid(self):
path = self.path.format(id="a")
self.client.credentials(HTTP_AUTHORIZATION=f"Api-Key {self.token}")
response = self.client.get(path)
self.assertEqual(response.status_code, 400)
def test_does_not_exist(self):
path = self.path.format(id="1")
self.client.credentials(HTTP_AUTHORIZATION=f"Api-Key {self.token}")
response = self.client.get(path)
self.assertEqual(response.status_code, 404)
def test_exists(self):
music_dir = baker.make(
"storage.MusicDir",
directory=str(fixture_path),
)
f = baker.make(
"storage.File",
directory=music_dir,
mime="audio/mp3",
filepath=AUDIO_FILENAME,
)
path = self.path.format(id=str(f.pk))
self.client.credentials(HTTP_AUTHORIZATION=f"Api-Key {self.token}")
response = self.client.get(path)
self.assertEqual(response.status_code, 200)

View file

@ -0,0 +1,4 @@
from .cloud_file import CloudFileViewSet
from .file import FileViewSet
from .storage import MusicDirViewSet
from .track_type import TrackTypeViewSet

View file

@ -0,0 +1,10 @@
from rest_framework import viewsets
from ..models import CloudFile
from ..serializers import CloudFileSerializer
class CloudFileViewSet(viewsets.ModelViewSet):
queryset = CloudFile.objects.all()
serializer_class = CloudFileSerializer
model_permission_name = "cloudfile"

View file

@ -0,0 +1,33 @@
import os
from django.http import FileResponse
from django.shortcuts import get_object_or_404
from rest_framework import status, viewsets
from rest_framework.decorators import action
from rest_framework.response import Response
from ..models import File
from ..serializers import FileSerializer
class FileViewSet(viewsets.ModelViewSet):
queryset = File.objects.all()
serializer_class = FileSerializer
model_permission_name = "file"
@action(detail=True, methods=["GET"])
def download(self, request, pk=None):
if pk is None:
return Response("No file requested", status=status.HTTP_400_BAD_REQUEST)
try:
pk = int(pk)
except ValueError:
return Response(
"File ID should be an integer", status=status.HTTP_400_BAD_REQUEST
)
filename = get_object_or_404(File, pk=pk)
directory = filename.directory
path = os.path.join(directory.directory, filename.filepath)
response = FileResponse(open(path, "rb"), content_type=filename.mime)
return response

View file

@ -0,0 +1,10 @@
from rest_framework import viewsets
from ..models import MusicDir
from ..serializers import MusicDirSerializer
class MusicDirViewSet(viewsets.ModelViewSet):
queryset = MusicDir.objects.all()
serializer_class = MusicDirSerializer
model_permission_name = "musicdir"

View file

@ -0,0 +1,10 @@
from rest_framework import viewsets
from ..models import TrackType
from ..serializers import TrackTypeSerializer
class TrackTypeViewSet(viewsets.ModelViewSet):
queryset = TrackType.objects.all()
serializer_class = TrackTypeSerializer
model_permission_name = "tracktype"