aboutsummaryrefslogtreecommitdiff
path: root/apps
diff options
context:
space:
mode:
authorBobby <[email protected]>2024-12-24 06:50:59 -0500
committerBobby <[email protected]>2024-12-24 06:50:59 -0500
commit6df9f0dc40501e8f55bcc883dfe5be65e60d3c3d (patch)
tree273148d564c11ae46e9f96c0231ce57427f5591f /apps
parent4feba2452a151ed999d52d4a0d53b0b0584bf70e (diff)
downloadthatcomputerscientist-6df9f0dc40501e8f55bcc883dfe5be65e60d3c3d.tar.xz
thatcomputerscientist-6df9f0dc40501e8f55bcc883dfe5be65e60d3c3d.zip
bucket load of things
Diffstat (limited to 'apps')
-rw-r--r--apps/administration/urls.py26
-rw-r--r--apps/administration/views.py189
-rw-r--r--apps/anime/views.py155
3 files changed, 317 insertions, 53 deletions
diff --git a/apps/administration/urls.py b/apps/administration/urls.py
index d4781c66..1aa0146f 100644
--- a/apps/administration/urls.py
+++ b/apps/administration/urls.py
@@ -3,4 +3,28 @@ from django.urls import path
from . import views
app_name = "administration"
-urlpatterns = []
+urlpatterns = [
+ path(
+ "storage-buckets/", views.manage_storage_buckets, name="manage_storage_buckets"
+ ),
+ path(
+ "storage-buckets/create-folder/",
+ views.create_folder,
+ name="manage_storage_bucket_create_folder",
+ ),
+ path(
+ "storage-buckets/upload-file/",
+ views.upload_file,
+ name="manage_storage_bucket_upload_file",
+ ),
+ path(
+ "storage-buckets/<str:bucket_name>/",
+ views.view_single_bucket,
+ name="view_single_bucket",
+ ),
+ path(
+ "storage-buckets/<str:bucket_name>/<path:object_path>/",
+ views.view_object_path,
+ name="view_object_path",
+ ),
+]
diff --git a/apps/administration/views.py b/apps/administration/views.py
index 91ea44a2..bfe2cc57 100644
--- a/apps/administration/views.py
+++ b/apps/administration/views.py
@@ -1,3 +1,192 @@
+import os
+from uuid import uuid4
+
+from django.contrib.auth.decorators import login_required, user_passes_test
+from django.http import JsonResponse
+from django.views.decorators.http import require_http_methods
+from django.core.files.storage import default_storage
+from internal.admin_utilities import MinioFileManager
from django.shortcuts import render
+
# Create your views here.
+@login_required
+@user_passes_test(lambda u: u.is_superuser)
+def manage_storage_buckets(request):
+ file_manager = MinioFileManager()
+ buckets = file_manager.list_buckets()
+
+ context = {
+ "buckets": buckets,
+ }
+
+ return render(
+ request, "en/administration/manage_storage_buckets_home.html", context
+ )
+
+
+@login_required
+@user_passes_test(lambda u: u.is_superuser)
+def view_single_bucket(request, bucket_name):
+ file_manager = MinioFileManager()
+ objects = file_manager.list_objects(bucket_name)
+ for obj in objects:
+ file_type = obj["path"].split(".")[-1]
+ obj["file_type"] = file_type.lower()
+
+ context = {
+ "bucket_name": bucket_name,
+ "objects": objects,
+ }
+
+ return render(
+ request, "en/administration/manage_storage_buckets_bucket.html", context
+ )
+
+
+@login_required
+@user_passes_test(lambda u: u.is_superuser)
+def view_object_path(request, bucket_name, object_path):
+ file_manager = MinioFileManager()
+ objects = file_manager.list_objects(bucket_name, object_path)
+ for obj in objects:
+ file_type = obj["path"].split(".")[-1]
+ obj["file_type"] = file_type.lower()
+
+ object_path_s = ""
+ if "/" in object_path:
+ object_path_s = object_path.split("/")[-2]
+ else:
+ object_path = f"{object_path}/"
+
+ up_url = f"/admin/storage-buckets/{bucket_name}/{object_path_s}"
+ context = {
+ "bucket_name": bucket_name,
+ "object_path": object_path,
+ "objects": objects,
+ "up_url": up_url,
+ }
+
+ return render(
+ request, "en/administration/manage_storage_buckets_bucket.html", context
+ )
+
+
+@login_required
+@user_passes_test(lambda u: u.is_superuser)
+@require_http_methods(["POST"])
+def create_folder(request):
+ """
+ Create a folder in a specified bucket.
+ Requires staff permissions.
+ """
+ try:
+ # For Django, parse POST data differently
+ bucket_name = request.POST.get("bucket")
+ folder_path = request.POST.get("folder_path", "").strip("/")
+
+ # Additional debug logging
+ print(f"Bucket: {bucket_name}")
+ print(f"Folder Path: {folder_path}")
+ print(f"Request POST: {request.POST}")
+ print(f"Request body: {request.body}")
+
+ # Validate inputs
+ if not bucket_name or not folder_path:
+ return JsonResponse(
+ {"status": "error", "message": "Bucket and folder path are required"},
+ status=400,
+ )
+
+ # Initialize file manager without user context
+ file_manager = MinioFileManager()
+
+ # Ensure folder path ends with a slash for Minio
+ full_folder_path = f"{folder_path}/"
+
+ # Attempt to create folder
+ success = file_manager.create_folder(
+ bucket=bucket_name, folder_path=full_folder_path
+ )
+
+ if success:
+ return JsonResponse(
+ {
+ "status": "success",
+ "message": "Folder created successfully",
+ "folder_path": folder_path,
+ }
+ )
+ else:
+ return JsonResponse(
+ {"status": "error", "message": "Failed to create folder"}, status=500
+ )
+
+ except Exception as e:
+ # Log the full error for debugging
+ import traceback
+
+ traceback.print_exc()
+ return JsonResponse({"status": "error", "message": str(e)}, status=500)
+
+
+@login_required
+@require_http_methods(["POST"])
+def upload_file(request):
+ """
+ Upload a file to a specified bucket.
+ Requires user to be logged in.
+ """
+ try:
+ # Get uploaded file
+ uploaded_file = request.FILES.get("file")
+ bucket_name = request.POST.get("bucket")
+ current_path = request.POST.get("current_path", "")
+
+ # Validate inputs
+ if not uploaded_file or not bucket_name:
+ return JsonResponse(
+ {"status": "error", "message": "File and bucket are required"},
+ status=400,
+ )
+
+ # Generate unique filename
+ file_ext = os.path.splitext(uploaded_file.name)[1]
+ unique_filename = f"{uuid4()}{file_ext}"
+
+ # Construct full path
+ full_path = (
+ os.path.join(current_path, unique_filename)
+ if current_path
+ else unique_filename
+ )
+
+ # Temporarily save file to local storage
+ temp_file_path = default_storage.save(f"temp/{unique_filename}", uploaded_file)
+
+ # Initialize file manager without user context
+ file_manager = MinioFileManager()
+
+ # Upload file
+ success = file_manager.upload_file(
+ bucket=bucket_name, file_path=temp_file_path, object_name=full_path
+ )
+
+ # Clean up temporary file
+ default_storage.delete(temp_file_path)
+
+ if success:
+ return JsonResponse(
+ {
+ "status": "success",
+ "message": "File uploaded successfully",
+ "filename": unique_filename,
+ }
+ )
+ else:
+ return JsonResponse(
+ {"status": "error", "message": "Failed to upload file"}, status=500
+ )
+
+ except Exception as e:
+ return JsonResponse({"status": "error", "message": str(e)}, status=500)
diff --git a/apps/anime/views.py b/apps/anime/views.py
index c1012d09..fe1e90dc 100644
--- a/apps/anime/views.py
+++ b/apps/anime/views.py
@@ -2,10 +2,8 @@ import os
from django.urls import reverse
import requests
from django.shortcuts import redirect, render
-from django.views.decorators.cache import cache_page
-from functools import wraps
-from django.core.cache import cache
from thatcomputerscientist.utils import i18npatterns
+from internal.cache_utils import cache_data
genres = [
"Action",
@@ -43,6 +41,24 @@ CONSUMET_BASE_URL = os.getenv("CONSUMET_URL")
ZORO_URL = os.getenv("ZORO_URL")
+def sort_mapper(sort_by, order):
+ sort_mappings = {
+ "popularity": "POPULARITY",
+ "trending": "TRENDING",
+ "start_date": "START_DATE",
+ "end_date": "END_DATE",
+ "score": "SCORE",
+ "favourites": "FAVOURITES",
+ "title": "TITLE_ROMAJI",
+ }
+
+ if sort_by not in sort_mappings or order not in ["asc", "desc"]:
+ return None
+
+ return f"{sort_mappings[sort_by]}{'_DESC' if order == 'desc' else ''}"
+
+
+@cache_data(timeout=60 * 60, prefix="anime_data")
def get_anime(anime_id, dub=False):
provider = ANIME_PROVIDER_MAP.get(anime_id, "zoro")
params = {"dub": "true"} if dub else {}
@@ -79,6 +95,25 @@ def get_anime(anime_id, dub=False):
return data
+def find_optimal_server(episode_id, dub):
+ params = {"animeEpisodeId": episode_id}
+ response = requests.get(f"{ZORO_URL}/api/v2/hianime/episode/servers", params=params)
+ response = response.json()
+ if "message" in response:
+ return None
+
+ response = response["data"]
+ if dub and "dub" in response and len(response["dub"]) > 0:
+ return response["dub"][0]["serverName"]
+ elif len(response["sub"]) > 0 and "sub" in response:
+ return response["sub"][0]["serverName"]
+ elif len(response["raw"]) > 0:
+ return response["raw"][0]["serverName"]
+ else:
+ return None
+
+
+@cache_data(timeout=60 * 60 * 24 * 7, prefix="streaming_data")
def get_anime_streaming_data(anime_id, current_episode, dub=False):
provider = ANIME_PROVIDER_MAP.get(anime_id, "zoro")
current_episode_id = current_episode.get("id")
@@ -92,34 +127,52 @@ def get_anime_streaming_data(anime_id, current_episode, dub=False):
response = requests.get(
f"{ZORO_URL}/api/v2/hianime/episode/sources", params=params
)
- return response.json()
+ if response.status_code == 200:
+ return response.json()
+ else:
+ server = find_optimal_server(episode_id, dub)
+ params["server"] = server
+ response = requests.get(
+ f"{ZORO_URL}/api/v2/hianime/episode/sources", params=params
+ )
+ if response.status_code == 200:
+ return response.json()
+ else:
+ ANIME_PROVIDER_MAP[anime_id] = "gogoanime"
+ return get_anime_streaming_data(anime_id, current_episode, dub)
else:
response = requests.get(
f"{CONSUMET_BASE_URL}/meta/anilist/watch/{current_episode_id}",
)
- return response.json()
+ data = {
+ "tracks": [],
+ "intro": {"start": 0, "end": 0},
+ "outro": {"start": 0, "end": 0},
+ "sources": [],
+ "anilistID": 0,
+ "malID": 0,
+ }
+
+ if not "message" in response.json():
+ default_source = next(
+ (s for s in response.json()["sources"] if s["quality"] == "default"),
+ None,
+ )
+ data["sources"].append({"url": default_source["url"], "type": "hls"})
+ else:
+ data["sources"].append(
+ {
+ "url": "",
+ "type": "",
+ }
+ )
-def sort_mapper(sort_by, order):
- sort_mappings = {
- "popularity": "POPULARITY",
- "trending": "TRENDING",
- "start_date": "START_DATE",
- "end_date": "END_DATE",
- "score": "SCORE",
- "favourites": "FAVOURITES",
- "title": "TITLE_ROMAJI",
- }
-
- if sort_by not in sort_mappings or order not in ["asc", "desc"]:
- return None
-
- return f"{sort_mappings[sort_by]}{'_DESC' if order == 'desc' else ''}"
+ return {"data": data}
-def anime_results(
- sort="trending", order="desc", genre="", query="", page=1, per_page=12, status=""
-):
+@cache_data(timeout=60 * 60, prefix="anime_results")
+def anime_results(**kwargs):
supported_status = [
"releasing",
"not_yet_released",
@@ -127,6 +180,15 @@ def anime_results(
"finished",
"hiatus",
]
+
+ sort = kwargs.get("sort", "trending")
+ order = kwargs.get("order", "desc")
+ genre = kwargs.get("genre", "")
+ query = kwargs.get("query", "")
+ page = kwargs.get("page", 1)
+ per_page = kwargs.get("per_page", 12)
+ status = kwargs.get("status", "")
+
params = {
"page": page,
"perPage": per_page,
@@ -144,27 +206,6 @@ def anime_results(
return response.json()
-def cache_anime_page(timeout=60 * 15):
- def decorator(view_func):
- @wraps(view_func)
- def _wrapped_view(request, anime_id, e=None, *args, **kwargs):
- cache_key = f"anime_page:{anime_id}:ep{e}:dub{request.COOKIES.get('anime_dub', False)}"
- cached_response = cache.get(cache_key)
-
- if cached_response:
- return cached_response
-
- response = view_func(request, anime_id, e, *args, **kwargs)
- if response.status_code == 200:
- cache.set(cache_key, response, timeout)
- return response
-
- return _wrapped_view
-
- return decorator
-
-
-# @cache_page(60 * 15)
def home(request):
LANGUAGE_CODE = i18npatterns(request.LANGUAGE_CODE)
request.meta.update({"title": "Anime: Home"})
@@ -211,7 +252,6 @@ def search(request):
return render(request, f"{LANGUAGE_CODE}/anime/search.html", context)
-# @cache_anime_page(timeout=60 * 15)
def anime(request, anime_id, e=None):
dub = request.COOKIES.get("anime_dub", False)
anime_data = get_anime(anime_id, dub)
@@ -243,25 +283,36 @@ def anime(request, anime_id, e=None):
return redirect(
reverse("anime:anime", kwargs={"anime_id": anime_id, "e": 1})
)
+ anime_data["current_episode"] = (
+ anime_data.get("episodes", [])[e - 1] if e else None
+ )
- anime_data["current_episode"] = anime_data.get("episodes", [])[e - 1] if e else None
anime_data["totalEpisodes"] = (
len(episode_numbers)
if not anime_data["totalEpisodes"]
else anime_data["totalEpisodes"]
)
- streaming_data = get_anime_streaming_data(
- anime_id, anime_data["current_episode"], dub
- )
+ if "current_episode" in anime_data:
+ streaming_data = get_anime_streaming_data(
+ anime_id, anime_data["current_episode"], dub
+ )
+ print(streaming_data)
+ streaming_data["data"]["sources"] = streaming_data["data"]["sources"][0]
+ else:
+ streaming_data = {}
+
LANGUAGE_CODE = i18npatterns(request.LANGUAGE_CODE)
request.meta.update(
{"title": f"Anime: {anime_data.get('title', {}).get('romaji')}"}
)
- streaming_data["data"]["sources"] = streaming_data["data"]["sources"][0]
- print(streaming_data)
+ context = {
+ "anime": anime_data,
+ "streaming_data": streaming_data,
+ }
+
return render(
request,
f"{LANGUAGE_CODE}/anime/anime.html",
- {"anime": anime_data, "streaming_data": streaming_data},
+ context,
)