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:
parent
87d2da9d84
commit
fce988aef1
120 changed files with 1499 additions and 1078 deletions
0
api/libretime_api/storage/__init__.py
Normal file
0
api/libretime_api/storage/__init__.py
Normal file
7
api/libretime_api/storage/apps.py
Normal file
7
api/libretime_api/storage/apps.py
Normal 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"
|
4
api/libretime_api/storage/models/__init__.py
Normal file
4
api/libretime_api/storage/models/__init__.py
Normal file
|
@ -0,0 +1,4 @@
|
|||
from .cloud_file import CloudFile
|
||||
from .file import File
|
||||
from .storage import MusicDir
|
||||
from .track_type import TrackType
|
13
api/libretime_api/storage/models/cloud_file.py
Normal file
13
api/libretime_api/storage/models/cloud_file.py
Normal 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"
|
94
api/libretime_api/storage/models/file.py
Normal file
94
api/libretime_api/storage/models/file.py
Normal 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"),
|
||||
]
|
12
api/libretime_api/storage/models/storage.py
Normal file
12
api/libretime_api/storage/models/storage.py
Normal 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"
|
12
api/libretime_api/storage/models/track_type.py
Normal file
12
api/libretime_api/storage/models/track_type.py
Normal 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"
|
9
api/libretime_api/storage/router.py
Normal file
9
api/libretime_api/storage/router.py
Normal 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)
|
4
api/libretime_api/storage/serializers/__init__.py
Normal file
4
api/libretime_api/storage/serializers/__init__.py
Normal file
|
@ -0,0 +1,4 @@
|
|||
from .cloud_file import CloudFileSerializer
|
||||
from .file import FileSerializer
|
||||
from .storage import MusicDirSerializer
|
||||
from .track_type import TrackTypeSerializer
|
9
api/libretime_api/storage/serializers/cloud_file.py
Normal file
9
api/libretime_api/storage/serializers/cloud_file.py
Normal file
|
@ -0,0 +1,9 @@
|
|||
from rest_framework import serializers
|
||||
|
||||
from ..models import CloudFile
|
||||
|
||||
|
||||
class CloudFileSerializer(serializers.HyperlinkedModelSerializer):
|
||||
class Meta:
|
||||
model = CloudFile
|
||||
fields = "__all__"
|
11
api/libretime_api/storage/serializers/file.py
Normal file
11
api/libretime_api/storage/serializers/file.py
Normal 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__"
|
9
api/libretime_api/storage/serializers/storage.py
Normal file
9
api/libretime_api/storage/serializers/storage.py
Normal file
|
@ -0,0 +1,9 @@
|
|||
from rest_framework import serializers
|
||||
|
||||
from ..models import MusicDir
|
||||
|
||||
|
||||
class MusicDirSerializer(serializers.HyperlinkedModelSerializer):
|
||||
class Meta:
|
||||
model = MusicDir
|
||||
fields = "__all__"
|
9
api/libretime_api/storage/serializers/track_type.py
Normal file
9
api/libretime_api/storage/serializers/track_type.py
Normal file
|
@ -0,0 +1,9 @@
|
|||
from rest_framework import serializers
|
||||
|
||||
from ..models import TrackType
|
||||
|
||||
|
||||
class TrackTypeSerializer(serializers.HyperlinkedModelSerializer):
|
||||
class Meta:
|
||||
model = TrackType
|
||||
fields = "__all__"
|
0
api/libretime_api/storage/tests/__init__.py
Normal file
0
api/libretime_api/storage/tests/__init__.py
Normal file
0
api/libretime_api/storage/tests/views/__init__.py
Normal file
0
api/libretime_api/storage/tests/views/__init__.py
Normal file
42
api/libretime_api/storage/tests/views/test_file.py
Normal file
42
api/libretime_api/storage/tests/views/test_file.py
Normal 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)
|
4
api/libretime_api/storage/views/__init__.py
Normal file
4
api/libretime_api/storage/views/__init__.py
Normal file
|
@ -0,0 +1,4 @@
|
|||
from .cloud_file import CloudFileViewSet
|
||||
from .file import FileViewSet
|
||||
from .storage import MusicDirViewSet
|
||||
from .track_type import TrackTypeViewSet
|
10
api/libretime_api/storage/views/cloud_file.py
Normal file
10
api/libretime_api/storage/views/cloud_file.py
Normal 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"
|
33
api/libretime_api/storage/views/file.py
Normal file
33
api/libretime_api/storage/views/file.py
Normal 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
|
10
api/libretime_api/storage/views/storage.py
Normal file
10
api/libretime_api/storage/views/storage.py
Normal 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"
|
10
api/libretime_api/storage/views/track_type.py
Normal file
10
api/libretime_api/storage/views/track_type.py
Normal 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"
|
Loading…
Add table
Add a link
Reference in a new issue