494 lines
15 KiB
Python
Raw Permalink Normal View History

2024-07-21 12:07:06 +02:00
"""all app settings API views"""
2025-02-09 22:10:29 +07:00
from appsettings.serializers import (
AppConfigSerializer,
BackupFileSerializer,
CookieUpdateSerializer,
CookieValidationSerializer,
PoTokenSerializer,
SnapshotCreateResponseSerializer,
SnapshotItemSerializer,
SnapshotListSerializer,
SnapshotRestoreResponseSerializer,
TokenResponseSerializer,
)
2024-07-21 12:07:06 +02:00
from appsettings.src.backup import ElasticBackup
2024-07-22 22:37:49 +02:00
from appsettings.src.config import AppConfig
2024-07-21 12:07:06 +02:00
from appsettings.src.snapshot import ElasticSnapshot
2025-02-09 22:10:29 +07:00
from common.serializers import (
AsyncTaskResponseSerializer,
ErrorResponseSerializer,
)
2024-07-22 22:37:49 +02:00
from common.src.ta_redis import RedisArchivist
from common.views_base import AdminOnly, AdminWriteOnly, ApiBaseView
2025-01-28 14:57:10 +07:00
from django.conf import settings
2025-01-11 16:39:50 +07:00
from download.src.yt_dlp_base import CookieHandler, POTokenHandler
2025-02-09 22:10:29 +07:00
from drf_spectacular.utils import OpenApiResponse, extend_schema
2024-12-21 10:29:34 +07:00
from rest_framework.authtoken.models import Token
2024-07-21 12:07:06 +02:00
from rest_framework.response import Response
from task.src.task_manager import TaskCommand
from task.tasks import run_restore_backup
class BackupApiListView(ApiBaseView):
"""resolves to /api/appsettings/backup/
GET: returns list of available zip backups
POST: take zip backup now
"""
permission_classes = [AdminOnly]
task_name = "run_backup"
@staticmethod
2025-02-09 22:10:29 +07:00
@extend_schema(
responses={
200: OpenApiResponse(BackupFileSerializer(many=True)),
},
)
2024-07-21 12:07:06 +02:00
def get(request):
2025-02-09 22:10:29 +07:00
"""get list of available backup files"""
2024-07-21 12:07:06 +02:00
# pylint: disable=unused-argument
backup_files = ElasticBackup().get_all_backup_files()
2025-02-09 22:10:29 +07:00
serializer = BackupFileSerializer(backup_files, many=True)
return Response(serializer.data)
@extend_schema(
responses={
200: OpenApiResponse(AsyncTaskResponseSerializer()),
},
)
2024-07-21 12:07:06 +02:00
def post(self, request):
2025-02-09 22:10:29 +07:00
"""start new backup file task"""
2024-07-21 12:07:06 +02:00
# pylint: disable=unused-argument
response = TaskCommand().start(self.task_name)
message = {
"message": "backup task started",
"task_id": response["task_id"],
}
2025-02-09 22:10:29 +07:00
serializer = AsyncTaskResponseSerializer(message)
2024-07-21 12:07:06 +02:00
2025-02-09 22:10:29 +07:00
return Response(serializer.data)
2024-07-21 12:07:06 +02:00
class BackupApiView(ApiBaseView):
"""resolves to /api/appsettings/backup/<filename>/
GET: return a single backup
POST: restore backup
DELETE: delete backup
"""
permission_classes = [AdminOnly]
task_name = "restore_backup"
@staticmethod
2025-02-09 22:10:29 +07:00
@extend_schema(
responses={
200: OpenApiResponse(BackupFileSerializer()),
404: OpenApiResponse(
ErrorResponseSerializer(), description="file not found"
),
}
)
2024-07-21 12:07:06 +02:00
def get(request, filename):
"""get single backup"""
# pylint: disable=unused-argument
backup_file = ElasticBackup().build_backup_file_data(filename)
if not backup_file:
2025-02-09 22:10:29 +07:00
error = ErrorResponseSerializer({"error": "file not found"})
return Response(error.data, status=404)
2024-07-21 12:07:06 +02:00
2025-02-09 22:10:29 +07:00
serializer = BackupFileSerializer(backup_file)
2024-07-21 12:07:06 +02:00
2025-02-09 22:10:29 +07:00
return Response(serializer.data)
@extend_schema(
responses={
200: OpenApiResponse(AsyncTaskResponseSerializer()),
404: OpenApiResponse(
ErrorResponseSerializer(), description="file not found"
),
}
)
2024-07-21 12:07:06 +02:00
def post(self, request, filename):
2025-02-09 22:10:29 +07:00
"""start new task to restore backup file"""
2024-07-21 12:07:06 +02:00
# pylint: disable=unused-argument
2025-02-09 22:10:29 +07:00
backup_file = ElasticBackup().build_backup_file_data(filename)
if not backup_file:
error = ErrorResponseSerializer({"error": "file not found"})
return Response(error.data, status=404)
2024-07-21 12:07:06 +02:00
task = run_restore_backup.delay(filename)
message = {
"message": "backup restore task started",
"filename": filename,
"task_id": task.id,
}
return Response(message)
@staticmethod
2025-02-09 22:10:29 +07:00
@extend_schema(
responses={
204: OpenApiResponse(description="file deleted"),
404: OpenApiResponse(
ErrorResponseSerializer(), description="file not found"
),
}
)
2024-07-21 12:07:06 +02:00
def delete(request, filename):
"""delete backup file"""
# pylint: disable=unused-argument
backup_file = ElasticBackup().delete_file(filename)
if not backup_file:
2025-02-09 22:10:29 +07:00
error = ErrorResponseSerializer({"error": "file not found"})
return Response(error.data, status=404)
return Response(status=204)
2024-07-21 12:07:06 +02:00
2025-02-09 22:10:29 +07:00
class AppConfigApiView(ApiBaseView):
"""resolves to /api/appsettings/config/
GET: return app settings
POST: update app settings
"""
permission_classes = [AdminWriteOnly]
2025-02-09 22:10:29 +07:00
@staticmethod
@extend_schema(
responses={
200: OpenApiResponse(AppConfigSerializer()),
}
)
def get(request):
"""get app config"""
response = AppConfig().config
serializer = AppConfigSerializer(response)
return Response(serializer.data)
@staticmethod
@extend_schema(
request=AppConfigSerializer(),
responses={
200: OpenApiResponse(AppConfigSerializer()),
400: OpenApiResponse(
ErrorResponseSerializer(), description="Bad request"
),
},
)
def post(request):
2025-02-14 16:31:12 +07:00
"""update config values, allows partial update"""
serializer = AppConfigSerializer(data=request.data, partial=True)
2025-02-09 22:10:29 +07:00
serializer.is_valid(raise_exception=True)
validated_data = serializer.validated_data
updated_config = AppConfig().update_config(validated_data)
updated_serializer = AppConfigSerializer(updated_config)
return Response(updated_serializer.data)
2024-07-21 12:07:06 +02:00
class CookieView(ApiBaseView):
"""resolves to /api/appsettings/cookie/
GET: check if cookie is enabled
POST: verify validity of cookie
PUT: import cookie
2025-01-10 17:09:11 +07:00
DELETE: revoke the cookie
2024-07-21 12:07:06 +02:00
"""
permission_classes = [AdminOnly]
2025-02-09 22:10:29 +07:00
@extend_schema(
responses={
200: OpenApiResponse(CookieValidationSerializer()),
}
)
2025-01-10 17:09:11 +07:00
def get(self, request):
2025-02-09 22:10:29 +07:00
"""get cookie validation status"""
2024-07-21 12:07:06 +02:00
# pylint: disable=unused-argument
2025-01-10 17:09:11 +07:00
validation = self._get_cookie_validation()
2025-02-09 22:10:29 +07:00
serializer = CookieValidationSerializer(validation)
2024-07-21 12:07:06 +02:00
2025-02-09 22:10:29 +07:00
return Response(serializer.data)
2024-07-21 12:07:06 +02:00
2025-02-09 22:10:29 +07:00
@extend_schema(
responses={
200: OpenApiResponse(CookieValidationSerializer()),
}
)
2025-01-10 17:09:11 +07:00
def post(self, request):
2025-02-09 22:10:29 +07:00
"""validate cookie"""
2024-07-21 12:07:06 +02:00
# pylint: disable=unused-argument
config = AppConfig().config
2025-01-10 17:09:11 +07:00
_ = CookieHandler(config).validate()
validation = self._get_cookie_validation()
2025-02-09 22:10:29 +07:00
serializer = CookieValidationSerializer(validation)
return Response(serializer.data)
@extend_schema(
request=CookieUpdateSerializer(),
responses={
200: OpenApiResponse(CookieValidationSerializer()),
400: OpenApiResponse(
ErrorResponseSerializer(), description="Bad request"
),
},
)
2025-01-10 17:09:11 +07:00
def put(self, request):
2024-07-21 12:07:06 +02:00
"""handle put request"""
# pylint: disable=unused-argument
2025-02-09 22:10:29 +07:00
serializer = CookieUpdateSerializer(data=request.data)
serializer.is_valid(raise_exception=True)
validated_data = serializer.validated_data
cookie = validated_data.get("cookie")
2024-07-21 12:07:06 +02:00
if not cookie:
message = "missing cookie key in request data"
print(message)
2025-02-09 22:10:29 +07:00
error = ErrorResponseSerializer({"error": message})
return Response(error.data, status=400)
2024-07-21 12:07:06 +02:00
2025-01-28 14:57:10 +07:00
if settings.DEBUG:
print(f"[cookie] preview:\n\n{cookie[:300]}")
2025-02-09 22:10:29 +07:00
config = AppConfig().config
2024-07-21 12:07:06 +02:00
handler = CookieHandler(config)
handler.set_cookie(cookie)
validated = handler.validate()
if not validated:
2025-02-09 22:10:29 +07:00
message = "[cookie]: import failed, not valid"
print(message)
error = ErrorResponseSerializer({"error": message})
2024-07-21 12:07:06 +02:00
handler.revoke()
2025-02-09 22:10:29 +07:00
return Response(error.data, status=400)
2024-07-21 12:07:06 +02:00
2025-01-10 17:09:11 +07:00
validation = self._get_cookie_validation()
2025-02-09 22:10:29 +07:00
serializer = CookieValidationSerializer(validation)
2025-01-10 17:09:11 +07:00
2025-02-09 22:10:29 +07:00
return Response(serializer.data)
@extend_schema(
responses={
204: OpenApiResponse(description="Cookie revoked"),
},
)
2025-01-10 17:09:11 +07:00
def delete(self, request):
"""delete the cookie"""
config = AppConfig().config
handler = CookieHandler(config)
handler.revoke()
2025-02-09 22:10:29 +07:00
return Response(status=204)
2025-01-10 17:09:11 +07:00
@staticmethod
def _get_cookie_validation():
"""get current cookie validation"""
config = AppConfig().config
validation = RedisArchivist().get_message_dict("cookie:valid")
is_enabled = {"cookie_enabled": config["downloads"]["cookie_import"]}
validation.update(is_enabled)
return validation
2024-07-21 12:07:06 +02:00
2025-01-11 16:39:50 +07:00
class POTokenView(ApiBaseView):
"""handle PO token"""
permission_classes = [AdminOnly]
2025-02-09 22:10:29 +07:00
@extend_schema(
responses={
200: OpenApiResponse(PoTokenSerializer()),
404: OpenApiResponse(
ErrorResponseSerializer(), description="PO token not found"
),
}
)
2025-01-11 16:39:50 +07:00
def get(self, request):
2025-02-09 22:10:29 +07:00
"""get PO token"""
2025-01-11 16:39:50 +07:00
config = AppConfig().config
potoken = POTokenHandler(config).get()
2025-02-09 22:10:29 +07:00
if not potoken:
error = ErrorResponseSerializer({"error": "PO token not found"})
return Response(error.data, status=404)
2025-01-11 16:39:50 +07:00
2025-02-09 22:10:29 +07:00
serializer = PoTokenSerializer(data={"potoken": potoken})
serializer.is_valid(raise_exception=True)
return Response(serializer.data)
@extend_schema(
responses={
200: OpenApiResponse(PoTokenSerializer()),
400: OpenApiResponse(
ErrorResponseSerializer(), description="Bad request"
),
}
)
2025-01-11 16:39:50 +07:00
def post(self, request):
2025-02-09 22:10:29 +07:00
"""Update PO token"""
serializer = PoTokenSerializer(data=request.data)
serializer.is_valid(raise_exception=True)
validated_data = serializer.validated_data
if not validated_data:
error = ErrorResponseSerializer(
{"error": "missing PO token key in request data"}
)
return Response(error.data, status=400)
2025-01-11 16:39:50 +07:00
config = AppConfig().config
2025-02-09 22:10:29 +07:00
new_token = validated_data["potoken"]
2025-01-11 16:39:50 +07:00
POTokenHandler(config).set_token(new_token)
2025-02-09 22:10:29 +07:00
return Response(serializer.data)
2025-01-11 16:39:50 +07:00
2025-02-09 22:10:29 +07:00
@extend_schema(
responses={
204: OpenApiResponse(description="PO token revoked"),
},
)
2025-01-11 16:39:50 +07:00
def delete(self, request):
2025-02-09 22:10:29 +07:00
"""delete PO token"""
2025-01-11 16:39:50 +07:00
config = AppConfig().config
POTokenHandler(config).revoke_token()
2025-02-09 22:10:29 +07:00
return Response(status=204)
class SnapshotApiListView(ApiBaseView):
"""resolves to /api/appsettings/snapshot/
GET: returns snapshot config plus list of existing snapshots
POST: take snapshot now
"""
permission_classes = [AdminOnly]
@staticmethod
@extend_schema(
responses={
200: OpenApiResponse(SnapshotListSerializer()),
}
)
def get(request):
"""get available snapshots with metadata"""
# pylint: disable=unused-argument
snapshots = ElasticSnapshot().get_snapshot_stats()
serializer = SnapshotListSerializer(snapshots)
return Response(serializer.data)
@staticmethod
@extend_schema(
responses={
200: OpenApiResponse(SnapshotCreateResponseSerializer()),
}
)
def post(request):
"""take snapshot now"""
# pylint: disable=unused-argument
response = ElasticSnapshot().take_snapshot_now()
serializer = SnapshotCreateResponseSerializer(response)
return Response(serializer.data)
class SnapshotApiView(ApiBaseView):
"""resolves to /api/appsettings/snapshot/<snapshot-id>/
GET: return a single snapshot
POST: restore snapshot
DELETE: delete a snapshot
"""
permission_classes = [AdminOnly]
@staticmethod
@extend_schema(
responses={
200: OpenApiResponse(SnapshotItemSerializer()),
404: OpenApiResponse(
ErrorResponseSerializer(), description="snapshot not found"
),
}
)
def get(request, snapshot_id):
"""handle get request"""
# pylint: disable=unused-argument
snapshot = ElasticSnapshot().get_single_snapshot(snapshot_id)
if not snapshot:
error = ErrorResponseSerializer({"error": "snapshot not found"})
return Response(error.data, status=404)
serializer = SnapshotItemSerializer(snapshot)
return Response(serializer.data)
@staticmethod
@extend_schema(
responses={
200: OpenApiResponse(SnapshotRestoreResponseSerializer()),
400: OpenApiResponse(
ErrorResponseSerializer(), description="bad request"
),
}
)
def post(request, snapshot_id):
"""restore snapshot"""
# pylint: disable=unused-argument
response = ElasticSnapshot().restore_all(snapshot_id)
if not response:
error = ErrorResponseSerializer(
{"error": "failed to restore snapshot"}
)
return Response(error.data, status=400)
serializer = SnapshotRestoreResponseSerializer(response)
return Response(serializer.data)
@staticmethod
@extend_schema(
responses={
204: OpenApiResponse(description="delete snapshot from index"),
}
)
def delete(request, snapshot_id):
"""delete snapshot from index"""
# pylint: disable=unused-argument
response = ElasticSnapshot().delete_single_snapshot(snapshot_id)
if not response:
error = ErrorResponseSerializer(
{"error": "failed to delete snapshot"}
)
return Response(error.data, status=400)
return Response(status=204)
2025-01-11 16:39:50 +07:00
2024-07-21 12:07:06 +02:00
class TokenView(ApiBaseView):
"""resolves to /api/appsettings/token/
2025-02-09 22:10:29 +07:00
GET: get API token
2024-07-21 12:07:06 +02:00
DELETE: revoke the token
"""
permission_classes = [AdminOnly]
2024-08-10 19:36:33 +02:00
@staticmethod
2025-02-09 22:10:29 +07:00
@extend_schema(
responses={
200: OpenApiResponse(TokenResponseSerializer()),
}
)
2024-08-10 19:36:33 +02:00
def get(request):
2025-02-09 22:10:29 +07:00
"""get your API token"""
2024-12-21 10:29:34 +07:00
token, _ = Token.objects.get_or_create(user=request.user)
2025-02-09 22:10:29 +07:00
serializer = TokenResponseSerializer({"token": token.key})
return Response(serializer.data)
2024-08-10 19:36:33 +02:00
2024-07-21 12:07:06 +02:00
@staticmethod
2025-02-09 22:10:29 +07:00
@extend_schema(
responses={
204: OpenApiResponse(description="delete token"),
}
)
2024-07-21 12:07:06 +02:00
def delete(request):
2025-02-09 22:10:29 +07:00
"""delete your API token, new will get created on next get"""
2024-07-21 12:07:06 +02:00
print("revoke API token")
request.user.auth_token.delete()
2025-02-09 22:10:29 +07:00
return Response(status=204)