Update admin theme/layout and refresh README details.

Align the project baseline with the latest admin interface styling and layout structure while documenting setup and usage updates in README.
This commit is contained in:
vlad
2026-07-14 17:12:28 +03:00
commit 86cc3fa541
278 changed files with 19416 additions and 0 deletions
+24
View File
@@ -0,0 +1,24 @@
from fastapi import APIRouter, HTTPException, Query
from fastapi.responses import Response
from app.core.media_signing import verify_signed_media
from app.core.storage import download_object
router = APIRouter()
@router.get("/files/{file_path:path}")
async def get_media_file(
file_path: str,
expires: int = Query(...),
sig: str = Query(...),
):
if not file_path.startswith("avatars/"):
raise HTTPException(status_code=404, detail="FILE_NOT_FOUND")
if not verify_signed_media(file_path, expires, sig):
raise HTTPException(status_code=403, detail="INVALID_SIGNATURE")
stored = download_object(file_path)
if not stored:
raise HTTPException(status_code=404, detail="FILE_NOT_FOUND")
body, content_type = stored
return Response(content=body, media_type=content_type)
+5
View File
@@ -0,0 +1,5 @@
from pydantic import BaseModel
class AvatarUploadOut(BaseModel):
message: str
+62
View File
@@ -0,0 +1,62 @@
from __future__ import annotations
from io import BytesIO
from uuid import uuid4
from PIL import Image
from app.core.config import settings
from app.core.storage import upload_object
ALLOWED_MIME = {
"image/jpeg": ".jpg",
"image/png": ".png",
"image/webp": ".webp",
}
FORMAT_TO_MIME = {
"JPEG": "image/jpeg",
"PNG": "image/png",
"WEBP": "image/webp",
}
class AvatarValidationError(ValueError):
pass
def validate_and_process_avatar(content: bytes) -> tuple[str, bytes]:
if len(content) > settings.avatar_max_bytes:
raise AvatarValidationError("FILE_TOO_LARGE")
if not content:
raise AvatarValidationError("INVALID_IMAGE")
try:
with Image.open(BytesIO(content)) as image:
image.verify()
with Image.open(BytesIO(content)) as image:
mime = FORMAT_TO_MIME.get(image.format or "")
if mime not in ALLOWED_MIME:
raise AvatarValidationError("INVALID_MIME")
buffer = BytesIO()
if mime == "image/jpeg":
rgb = image.convert("RGB")
rgb.save(buffer, format="JPEG", quality=85, optimize=True)
elif mime == "image/png":
image.save(buffer, format="PNG", optimize=True)
else:
image.save(buffer, format="WEBP", quality=85, method=6)
return mime, buffer.getvalue()
except AvatarValidationError:
raise
except Exception as exc:
raise AvatarValidationError("INVALID_IMAGE") from exc
def upload_user_avatar(user_id: str, content: bytes) -> str:
mime, processed = validate_and_process_avatar(content)
extension = ALLOWED_MIME[mime]
key = f"avatars/{user_id}/{uuid4()}{extension}"
upload_object(key, processed, mime)
return f"/api/v1/media/files/{key}"