serialize user, add account endpoint

This commit is contained in:
Simon 2025-02-12 17:31:59 +07:00
parent 5498958b36
commit 5772ab3e68
No known key found for this signature in database
GPG Key ID: 2C15AA5E89985DD4
5 changed files with 132 additions and 46 deletions

View File

@ -1,5 +1,7 @@
"""serializer for account model""" """serializer for account model"""
# pylint: disable=abstract-method
from rest_framework import serializers from rest_framework import serializers
from user.models import Account from user.models import Account
@ -18,3 +20,45 @@ class AccountSerializer(serializers.ModelSerializer):
"user_permissions", "user_permissions",
"last_login", "last_login",
) )
class UserMeConfigSerializer(serializers.Serializer):
"""serialize user me config"""
stylesheet = serializers.CharField()
page_size = serializers.IntegerField()
sort_by = serializers.ChoiceField(
choices=[
"published",
"downloaded",
"views",
"likes",
"duration",
"filesize",
]
)
sort_order = serializers.ChoiceField(choices=["asc", "desc"])
view_style_home = serializers.ChoiceField(choices=["grid", "list"])
view_style_channel = serializers.ChoiceField(choices=["grid", "list"])
view_style_downloads = serializers.ChoiceField(choices=["grid", "list"])
view_style_playlist = serializers.ChoiceField(choices=["grid", "list"])
grid_items = serializers.IntegerField()
hide_watched = serializers.BooleanField()
show_ignored_only = serializers.BooleanField()
show_subed_only = serializers.BooleanField()
show_help_text = serializers.BooleanField()
def __init__(self, *args, required=True, **kwargs):
super().__init__(*args, **kwargs)
# Override 'required' for all fields if provided
for field in self.fields.values():
field.required = required
class LoginSerializer(serializers.Serializer):
"""serialize login"""
username = serializers.CharField()
password = serializers.CharField()
remember_me = serializers.ChoiceField(choices=["on", "off"], default="off")

View File

@ -143,6 +143,16 @@ class UserConfig:
return config return config
def update_config(self, to_update: dict) -> None:
"""update config object"""
data = {"doc": {"config": to_update}}
response, status = ElasticWrap(self.es_update_url).post(data)
if status < 200 or status > 299:
raise ValueError(f"Failed storing user value {status}: {response}")
for key, value in to_update.items():
print(f"User {self._user_id} value '{key}' change: to {value}")
def sync_defaults(self): def sync_defaults(self):
"""set initial defaults on 404""" """set initial defaults on 404"""
response, _ = ElasticWrap(self.es_url).post( response, _ = ElasticWrap(self.es_url).post(

View File

@ -6,5 +6,6 @@ from user import views
urlpatterns = [ urlpatterns = [
path("login/", views.LoginApiView.as_view(), name="api-user-login"), path("login/", views.LoginApiView.as_view(), name="api-user-login"),
path("logout/", views.LogoutApiView.as_view(), name="api-user-logout"), path("logout/", views.LogoutApiView.as_view(), name="api-user-logout"),
path("account/", views.UserAccountView.as_view(), name="api-user-account"),
path("me/", views.UserConfigView.as_view(), name="api-user-me"), path("me/", views.UserConfigView.as_view(), name="api-user-me"),
] ]

View File

@ -1,63 +1,75 @@
"""all user api views""" """all user api views"""
from common.serializers import ErrorResponseSerializer
from common.views import ApiBaseView from common.views import ApiBaseView
from django.contrib.auth import authenticate, login, logout from django.contrib.auth import authenticate, login, logout
from django.utils.decorators import method_decorator from django.utils.decorators import method_decorator
from django.views.decorators.csrf import csrf_exempt from django.views.decorators.csrf import csrf_exempt
from drf_spectacular.utils import OpenApiResponse, extend_schema
from rest_framework.permissions import AllowAny 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 user.models import Account from user.models import Account
from user.serializers import AccountSerializer from user.serializers import (
AccountSerializer,
LoginSerializer,
UserMeConfigSerializer,
)
from user.src.user_config import UserConfig from user.src.user_config import UserConfig
class UserAccountView(ApiBaseView):
"""resolves to /api/user/account/
GET: return current user account
"""
@extend_schema(
responses=AccountSerializer(),
)
def get(self, request):
"""get user account"""
user_id = request.user.id
account = Account.objects.get(id=user_id)
account_serializer = AccountSerializer(account)
return Response(account_serializer.data)
class UserConfigView(ApiBaseView): class UserConfigView(ApiBaseView):
"""resolves to /api/user/me/ """resolves to /api/user/me/
GET: return current user config GET: return current user config
POST: update user config POST: update user config
""" """
@extend_schema(responses=UserMeConfigSerializer())
def get(self, request): def get(self, request):
"""get config""" """get user config"""
user_id = request.user.id config = UserConfig(request.user.id).get_config()
account = Account.objects.get(id=user_id) serializer = UserMeConfigSerializer(config)
serializer = AccountSerializer(account)
response = serializer.data.copy()
config = UserConfig(user_id).get_config() return Response(serializer.data)
response.update({"config": config})
return Response(response)
@extend_schema(
request=UserMeConfigSerializer(required=False),
responses={
200: UserMeConfigSerializer(),
400: OpenApiResponse(
ErrorResponseSerializer(), description="Bad request"
),
},
)
def post(self, request): def post(self, request):
"""update config""" """update config, allows partial update"""
user_id = request.user.id
data = request.data
data_config = data.get("config") data_serializer = UserMeConfigSerializer(
if not data_config: data=request.data, required=False
message = { )
"status": "Bad Request", data_serializer.is_valid(raise_exception=True)
"message": "missing config key", validated_data = data_serializer.validated_data
} UserConfig(request.user.id).update_config(to_update=validated_data)
return Response(message, status=400) config = UserConfig(request.user.id).get_config()
serializer = UserMeConfigSerializer(config)
user_conf = UserConfig(user_id) return Response(serializer.data)
for key, value in data_config.items():
try:
user_conf.set_value(key, value)
except ValueError as err:
message = {
"status": "Bad Request",
"message": f"failed updating {key} to '{value}', {err}",
}
return Response(message, status=400)
response = user_conf.get_config()
response.update({"user_id": user_id})
return Response(response)
@method_decorator(csrf_exempt, name="dispatch") @method_decorator(csrf_exempt, name="dispatch")
@ -67,21 +79,37 @@ class LoginApiView(APIView):
""" """
permission_classes = [AllowAny] permission_classes = [AllowAny]
SEC_IN_DAY = 60 * 60 * 24
@extend_schema(
request=LoginSerializer(),
responses={204: OpenApiResponse(description="login successful")},
)
def post(self, request, *args, **kwargs): def post(self, request, *args, **kwargs):
"""post data""" """login with username and password"""
# pylint: disable=no-member # pylint: disable=no-member
data_serializer = LoginSerializer(data=request.data)
data_serializer.is_valid(raise_exception=True)
validated_data = data_serializer.validated_data
username = request.data.get("username") username = validated_data["username"]
password = request.data.get("password") password = validated_data["password"]
remember_me = validated_data.get("remember_me")
user = authenticate(request, username=username, password=password) user = authenticate(request, username=username, password=password)
if user is None:
error = ErrorResponseSerializer({"error": "Invalid credentials"})
return Response(error.data, status=400)
if remember_me == "on":
request.session.set_expiry(self.SEC_IN_DAY * 365)
else:
request.session.set_expiry(self.SEC_IN_DAY * 2)
print(f"expire session in {request.session.get_expiry_age()} secs")
if user is not None:
login(request, user) # Creates a session for the user login(request, user) # Creates a session for the user
return Response({"message": "Login successful"}, status=200) return Response(status=204)
return Response({"message": "Invalid credentials"}, status=400)
class LogoutApiView(ApiBaseView): class LogoutApiView(ApiBaseView):
@ -89,7 +117,10 @@ class LogoutApiView(ApiBaseView):
POST: handle logout POST: handle logout
""" """
@extend_schema(
responses={204: OpenApiResponse(description="logout successful")}
)
def post(self, request, *args, **kwargs): def post(self, request, *args, **kwargs):
"""logout on post request""" """logout user from session"""
logout(request) logout(request)
return Response({"message": "Successfully logged out."}, status=200) return Response(status=204)

View File

@ -53,8 +53,8 @@ class SponsorBlock:
print(f"{youtube_id}: get sponsorblock timestamps") print(f"{youtube_id}: get sponsorblock timestamps")
try: try:
response = requests.get(url, headers=headers, timeout=10) response = requests.get(url, headers=headers, timeout=10)
except requests.ReadTimeout: except (requests.ReadTimeout, requests.ConnectionError) as err:
print(f"{youtube_id}: sponsorblock API timeout") print(f"{youtube_id}: sponsorblock API error: {str(err)}")
return False return False
if not response.ok: if not response.ok: