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 CoreConfig(AppConfig):
default_auto_field = "django.db.models.BigAutoField"
name = "libretime_api.core"
verbose_name = "LibreTime Core API"

View file

@ -0,0 +1,7 @@
from .auth import LoginAttempt, Session, UserToken
from .country import Country
from .preference import Preference, StreamSetting
from .role import ADMIN, DJ, GUEST, PROGRAM_MANAGER, USER_TYPES
from .service import ServiceRegister
from .user import User, UserManager
from .worker import CeleryTask, ThirdPartyTrackReference

View file

@ -0,0 +1,37 @@
from django.db import models
class UserToken(models.Model):
user = models.ForeignKey("User", models.DO_NOTHING)
action = models.CharField(max_length=255)
token = models.CharField(unique=True, max_length=40)
created = models.DateTimeField()
def get_owner(self):
return self.user
class Meta:
managed = False
db_table = "cc_subjs_token"
class Session(models.Model):
sessid = models.CharField(primary_key=True, max_length=32)
userid = models.ForeignKey(
"User", models.DO_NOTHING, db_column="userid", blank=True, null=True
)
login = models.CharField(max_length=255, blank=True, null=True)
ts = models.DateTimeField(blank=True, null=True)
class Meta:
managed = False
db_table = "cc_sess"
class LoginAttempt(models.Model):
ip = models.CharField(primary_key=True, max_length=32)
attempts = models.IntegerField(blank=True, null=True)
class Meta:
managed = False
db_table = "cc_login_attempts"

View file

@ -0,0 +1,10 @@
from django.db import models
class Country(models.Model):
isocode = models.CharField(primary_key=True, max_length=3)
name = models.CharField(max_length=255)
class Meta:
managed = False
db_table = "cc_country"

View file

@ -0,0 +1,24 @@
from django.db import models
class Preference(models.Model):
subjid = models.ForeignKey(
"User", models.DO_NOTHING, db_column="subjid", blank=True, null=True
)
keystr = models.CharField(unique=True, max_length=255, blank=True, null=True)
valstr = models.TextField(blank=True, null=True)
class Meta:
managed = False
db_table = "cc_pref"
unique_together = (("subjid", "keystr"),)
class StreamSetting(models.Model):
keyname = models.CharField(primary_key=True, max_length=64)
value = models.CharField(max_length=255, blank=True, null=True)
type = models.CharField(max_length=16)
class Meta:
managed = False
db_table = "cc_stream_setting"

View file

@ -0,0 +1,11 @@
GUEST = "G"
DJ = "H"
PROGRAM_MANAGER = "P"
ADMIN = "A"
USER_TYPES = {
GUEST: "Guest",
DJ: "DJ",
PROGRAM_MANAGER: "Program Manager",
ADMIN: "Admin",
}

View file

@ -0,0 +1,10 @@
from django.db import models
class ServiceRegister(models.Model):
name = models.CharField(primary_key=True, max_length=32)
ip = models.CharField(max_length=45)
class Meta:
managed = False
db_table = "cc_service_register"

View file

@ -0,0 +1,128 @@
import hashlib
from django.contrib.auth.models import AbstractBaseUser, BaseUserManager, Permission
from django.db import models
from ...permission_constants import GROUPS
from .role import ADMIN, USER_TYPES
USER_TYPE_CHOICES = ()
for item in USER_TYPES.items():
USER_TYPE_CHOICES = USER_TYPE_CHOICES + (item,)
class UserManager(BaseUserManager):
def create_user(self, username, type, email, first_name, last_name, password):
user = self.model(
username=username,
type=type,
email=email,
first_name=first_name,
last_name=last_name,
)
user.set_password(password)
user.save(using=self._db)
return user
def create_superuser(self, username, email, first_name, last_name, password):
user = self.create_user(username, "A", email, first_name, last_name, password)
return user
def get_by_natural_key(self, username):
return self.get(username=username)
class User(AbstractBaseUser):
username = models.CharField(db_column="login", unique=True, max_length=255)
password = models.CharField(
db_column="pass", max_length=255
) # Field renamed because it was a Python reserved word.
type = models.CharField(max_length=1, choices=USER_TYPE_CHOICES)
first_name = models.CharField(max_length=255)
last_name = models.CharField(max_length=255)
last_login = models.DateTimeField(db_column="lastlogin", blank=True, null=True)
lastfail = models.DateTimeField(blank=True, null=True)
skype_contact = models.CharField(max_length=1024, blank=True, null=True)
jabber_contact = models.CharField(max_length=1024, blank=True, null=True)
email = models.CharField(max_length=1024, blank=True, null=True)
cell_phone = models.CharField(max_length=1024, blank=True, null=True)
login_attempts = models.IntegerField(blank=True, null=True)
USERNAME_FIELD = "username"
EMAIL_FIELD = "email"
REQUIRED_FIELDS = ["type", "email", "first_name", "last_name"]
objects = UserManager()
def get_full_name(self):
return f"{self.first_name} {self.last_name}"
def get_short_name(self):
return self.first_name
def set_password(self, password):
if not password:
self.set_unusable_password()
else:
self.password = hashlib.md5(password.encode()).hexdigest()
def is_staff(self):
return self.type == ADMIN
def check_password(self, password):
if self.has_usable_password():
test_password = hashlib.md5(password.encode()).hexdigest()
return test_password == self.password
return False
"""
The following methods have to be re-implemented here, as PermissionsMixin
assumes that the User class has a 'group' attribute, which LibreTime does
not currently provide. Once Django starts managing the Database
(managed = True), then this can be replaced with
django.contrib.auth.models.PermissionMixin.
"""
def is_superuser(self):
return self.type == ADMIN
def get_user_permissions(self, obj=None):
"""
Users do not have permissions directly, only through groups
"""
return []
def get_group_permissions(self, obj=None):
permissions = GROUPS[self.type]
if obj:
obj_name = obj.__class__.__name__.lower()
permissions = [perm for perm in permissions if obj_name in perm]
# get permissions objects
q = models.Q()
for perm in permissions:
q = q | models.Q(codename=perm)
return list(Permission.objects.filter(q))
def get_all_permissions(self, obj=None):
return self.get_user_permissions(obj) + self.get_group_permissions(obj)
def has_perm(self, perm, obj=None):
if self.is_superuser():
return True
if not perm:
return False
permissions = self.get_all_permissions(obj)
try:
permission = Permission.objects.get(codename=perm)
return permission in permissions
except Permission.DoesNotExist:
return False
def has_perms(self, perm_list, obj=None):
result = True
for permission in perm_list:
result = result and self.has_perm(permission, obj)
return result
class Meta:
managed = False
db_table = "cc_subjs"

View file

@ -0,0 +1,27 @@
from django.db import models
class ThirdPartyTrackReference(models.Model):
service = models.CharField(max_length=256)
foreign_id = models.CharField(unique=True, max_length=256, blank=True, null=True)
file = models.ForeignKey("storage.File", models.DO_NOTHING, blank=True, null=True)
upload_time = models.DateTimeField(blank=True, null=True)
status = models.CharField(max_length=256, blank=True, null=True)
class Meta:
managed = False
db_table = "third_party_track_references"
class CeleryTask(models.Model):
task_id = models.CharField(max_length=256)
track_reference = models.ForeignKey(
"ThirdPartyTrackReference", models.DO_NOTHING, db_column="track_reference"
)
name = models.CharField(max_length=256, blank=True, null=True)
dispatch_time = models.DateTimeField(blank=True, null=True)
status = models.CharField(max_length=256)
class Meta:
managed = False
db_table = "celery_tasks"

View file

@ -0,0 +1,26 @@
from rest_framework import routers
from .views import (
CeleryTaskViewSet,
CountryViewSet,
LoginAttemptViewSet,
PreferenceViewSet,
ServiceRegisterViewSet,
SessionViewSet,
StreamSettingViewSet,
ThirdPartyTrackReferenceViewSet,
UserTokenViewSet,
UserViewSet,
)
router = routers.DefaultRouter()
router.register("countries", CountryViewSet)
router.register("login-attempts", LoginAttemptViewSet)
router.register("preferences", PreferenceViewSet)
router.register("service-registers", ServiceRegisterViewSet)
router.register("sessions", SessionViewSet)
router.register("stream-settings", StreamSettingViewSet)
router.register("users", UserViewSet)
router.register("user-tokens", UserTokenViewSet)
router.register("celery-tasks", CeleryTaskViewSet)
router.register("third-party-track-references", ThirdPartyTrackReferenceViewSet)

View file

@ -0,0 +1,6 @@
from .auth import LoginAttemptSerializer, SessionSerializer, UserTokenSerializer
from .country import CountrySerializer
from .preference import PreferenceSerializer, StreamSettingSerializer
from .service import ServiceRegisterSerializer
from .user import UserSerializer
from .worker import CeleryTaskSerializer, ThirdPartyTrackReferenceSerializer

View file

@ -0,0 +1,21 @@
from rest_framework import serializers
from ..models import LoginAttempt, Session, UserToken
class UserTokenSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = UserToken
fields = "__all__"
class SessionSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = Session
fields = "__all__"
class LoginAttemptSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = LoginAttempt
fields = "__all__"

View file

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

View file

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

View file

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

View file

@ -0,0 +1,20 @@
from django.contrib.auth import get_user_model
from rest_framework import serializers
class UserSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = get_user_model()
fields = [
"item_url",
"username",
"type",
"first_name",
"last_name",
"lastfail",
"skype_contact",
"jabber_contact",
"email",
"cell_phone",
"login_attempts",
]

View file

@ -0,0 +1,15 @@
from rest_framework import serializers
from ..models import CeleryTask, ThirdPartyTrackReference
class ThirdPartyTrackReferenceSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = ThirdPartyTrackReference
fields = "__all__"
class CeleryTaskSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = CeleryTask
fields = "__all__"

View file

View file

@ -0,0 +1,46 @@
from django.apps import apps
from rest_framework.test import APITestCase
from ....permission_constants import GROUPS
from ...models import DJ, GUEST, User
class TestUserManager(APITestCase):
def test_create_user(self):
user = User.objects.create_user(
"test",
email="test@example.com",
password="test",
type=DJ,
first_name="test",
last_name="user",
)
db_user = User.objects.get(pk=user.pk)
self.assertEqual(db_user.username, user.username)
def test_create_superuser(self):
user = User.objects.create_superuser(
"test",
email="test@example.com",
password="test",
first_name="test",
last_name="user",
)
db_user = User.objects.get(pk=user.pk)
self.assertEqual(db_user.username, user.username)
class TestUser(APITestCase):
def test_guest_get_group_perms(self):
user = User.objects.create_user(
"test",
email="test@example.com",
password="test",
type=GUEST,
first_name="test",
last_name="user",
)
permissions = user.get_group_permissions()
# APIRoot permission hardcoded in the check as it isn't a Permission object
str_perms = [p.codename for p in permissions] + ["view_apiroot"]
self.assertCountEqual(str_perms, GROUPS[GUEST])

View file

@ -0,0 +1,7 @@
from .auth import LoginAttemptViewSet, SessionViewSet, UserTokenViewSet
from .country import CountryViewSet
from .preference import PreferenceViewSet, StreamSettingViewSet
from .service import ServiceRegisterViewSet
from .user import UserViewSet
from .version import version
from .worker import CeleryTaskViewSet, ThirdPartyTrackReferenceViewSet

View file

@ -0,0 +1,22 @@
from rest_framework import viewsets
from ..models import LoginAttempt, Session, UserToken
from ..serializers import LoginAttemptSerializer, SessionSerializer, UserTokenSerializer
class UserTokenViewSet(viewsets.ModelViewSet):
queryset = UserToken.objects.all()
serializer_class = UserTokenSerializer
model_permission_name = "usertoken"
class SessionViewSet(viewsets.ModelViewSet):
queryset = Session.objects.all()
serializer_class = SessionSerializer
model_permission_name = "session"
class LoginAttemptViewSet(viewsets.ModelViewSet):
queryset = LoginAttempt.objects.all()
serializer_class = LoginAttemptSerializer
model_permission_name = "loginattempt"

View file

@ -0,0 +1,10 @@
from rest_framework import viewsets
from ..models import Country
from ..serializers import CountrySerializer
class CountryViewSet(viewsets.ModelViewSet):
queryset = Country.objects.all()
serializer_class = CountrySerializer
model_permission_name = "country"

View file

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

View file

@ -0,0 +1,10 @@
from rest_framework import viewsets
from ..models import ServiceRegister
from ..serializers import ServiceRegisterSerializer
class ServiceRegisterViewSet(viewsets.ModelViewSet):
queryset = ServiceRegister.objects.all()
serializer_class = ServiceRegisterSerializer
model_permission_name = "serviceregister"

View file

@ -0,0 +1,12 @@
from django.contrib.auth import get_user_model
from rest_framework import viewsets
from ...permissions import IsAdminOrOwnUser
from ..serializers import UserSerializer
class UserViewSet(viewsets.ModelViewSet):
queryset = get_user_model().objects.all()
serializer_class = UserSerializer
permission_classes = [IsAdminOrOwnUser]
model_permission_name = "user"

View file

@ -0,0 +1,10 @@
from django.conf import settings
from rest_framework.decorators import api_view, permission_classes
from rest_framework.permissions import AllowAny
from rest_framework.response import Response
@api_view(["GET"])
@permission_classes((AllowAny,))
def version(request, *args, **kwargs):
return Response({"api_version": settings.API_VERSION})

View file

@ -0,0 +1,16 @@
from rest_framework import viewsets
from ..models import CeleryTask, ThirdPartyTrackReference
from ..serializers import CeleryTaskSerializer, ThirdPartyTrackReferenceSerializer
class ThirdPartyTrackReferenceViewSet(viewsets.ModelViewSet):
queryset = ThirdPartyTrackReference.objects.all()
serializer_class = ThirdPartyTrackReferenceSerializer
model_permission_name = "thirdpartytrackreference"
class CeleryTaskViewSet(viewsets.ModelViewSet):
queryset = CeleryTask.objects.all()
serializer_class = CeleryTaskSerializer
model_permission_name = "celerytask"