Save and retrieve playlists and epg guides from vercel blob storage

This commit is contained in:
2025-05-06 13:27:14 -05:00
parent a55695865e
commit beb1bdd8c0
8 changed files with 230 additions and 31 deletions

129
src/utils/admin.py Normal file
View File

@@ -0,0 +1,129 @@
import re
from pathlib import PurePath
from fastapi import HTTPException, UploadFile
import vercel_blob
from .database import get_blob_by_path
import logging
# Configure logging
logger = logging.getLogger(__name__)
ALLOWED_EXTENSIONS = {
'playlists': ('.m3u', '.m3u8'),
'epgs': ('.xml', '.xml.gz')
}
async def upload_blob_file(folder: str, file: UploadFile) -> dict:
"""
Upload a file to Vercel Blob storage with validation
Args:
folder (str): The folder name (e.g. 'playlists' or 'epgs')
file (UploadFile): The file to upload
Returns:
dict: Information about the uploaded blob
Raises:
HTTPException: If validation fails or upload fails
"""
try:
# Validate filename
if not file.filename or not file.filename.strip():
raise HTTPException(status_code=400, detail="Invalid filename provided")
# Sanitize filename
sanitized_filename = PurePath(file.filename).name
sanitized_filename = re.sub(r'[^a-zA-Z0-9_.-]', '_', sanitized_filename)
# Validate file extension
if not sanitized_filename.lower().endswith(ALLOWED_EXTENSIONS[folder]):
raise HTTPException(
status_code=400,
detail=f"Only {', '.join(ALLOWED_EXTENSIONS[folder])} files are allowed"
)
content = await file.read()
# Validate content size (10MB limit)
max_size = 10 * 1024 * 1024 # 10MB
if len(content) > max_size:
raise HTTPException(
status_code=413,
detail=f"File size exceeds {max_size//1024//1024}MB limit"
)
blob_info = vercel_blob.put(
path=f"{folder}/{sanitized_filename}",
data=content,
options={
"contentType": file.content_type or "application/octet-stream",
"access": 'public',
"addRandomSuffix": False
}
)
return {
"status": "uploaded",
"filename": sanitized_filename,
"url": blob_info['url'],
"size": len(content),
"content_type": blob_info.get('contentType')
}
except KeyError as e:
logger.error(f"Unexpected blob storage response: {str(e)}")
raise HTTPException(
status_code=500,
detail=f"Unexpected response format from blob storage: {str(e)}"
)
except HTTPException:
raise
except Exception as e:
logger.error(f"File upload failed: {str(e)}")
raise HTTPException(
status_code=500,
detail=f"File upload failed: {str(e)}"
)
def delete_blob_file(folder: str, filename: str) -> dict:
"""
Delete a file from Vercel Blob storage
Args:
folder (str): The folder name (e.g. 'playlists' or 'epgs')
filename (str): The filename to delete
Returns:
dict: Information about the deleted blob
Raises:
HTTPException: If the file is not found or deletion fails
"""
try:
# Sanitize filename for safety
sanitized_filename = PurePath(filename).name
sanitized_filename = re.sub(r'[^a-zA-Z0-9_.-]', '_', sanitized_filename)
# Get the blob information first to get the URL
blob_info = get_blob_by_path(folder, sanitized_filename)
# Delete using the blob URL
vercel_blob.delete(blob_info['url'])
return {
"status": "deleted",
"filename": sanitized_filename,
"url": blob_info['url']
}
except HTTPException as he:
# Re-raise HTTP exceptions (like 404 from get_blob_by_path)
logger.error(f"HTTP error while deleting blob: {str(he)}")
raise he
except Exception as e:
logger.error(f"Failed to delete blob: {str(e)}")
raise HTTPException(
status_code=500,
detail=f"Failed to delete file: {str(e)}"
)