fix: implement proper avatar upload and display

- Add file upload endpoint to auth router
- Mount static files for avatar serving
- Implement real file upload in frontend
- Add error handling and fallback for broken images
- Fix avatar persistence and state management
- Add loading states and proper error messages

Generated by Mistral Vibe.
Co-Authored-By: Mistral Vibe <vibe@mistral.ai>
This commit is contained in:
Mistral Vibe
2026-03-30 19:27:35 +02:00
parent 184a288b7f
commit b59eb584a6
4 changed files with 103 additions and 57 deletions

View File

@@ -1,9 +1,11 @@
"""RehearsalHub FastAPI application entry point."""
from contextlib import asynccontextmanager
import os
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
from fastapi.staticfiles import StaticFiles
from rehearsalhub.config import get_settings
from rehearsalhub.routers import (
@@ -64,6 +66,11 @@ def create_app() -> FastAPI:
async def health():
return {"status": "ok"}
# Mount static files for avatar uploads
upload_dir = "uploads/avatars"
os.makedirs(upload_dir, exist_ok=True)
app.mount("/api/static/avatars", StaticFiles(directory=upload_dir), name="avatars")
return app

View File

@@ -1,5 +1,7 @@
from fastapi import APIRouter, Depends, HTTPException, status
from fastapi import APIRouter, Depends, HTTPException, status, UploadFile, File
from sqlalchemy.ext.asyncio import AsyncSession
import os
import uuid
from rehearsalhub.db.engine import get_session
from rehearsalhub.db.models import Member
@@ -62,3 +64,44 @@ async def update_settings(
else:
member = current_member
return MemberRead.from_model(member)
@router.post("/me/avatar", response_model=MemberRead)
async def upload_avatar(
file: UploadFile = File(...),
session: AsyncSession = Depends(get_session),
current_member: Member = Depends(get_current_member),
):
"""Upload and set user avatar image."""
# Validate file type and size
if not file.content_type.startswith("image/"):
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail="Only image files are allowed"
)
# Create uploads directory if it doesn't exist
upload_dir = "uploads/avatars"
os.makedirs(upload_dir, exist_ok=True)
# Generate unique filename
file_ext = file.filename.split(".")[-1] if "." in file.filename else "jpg"
filename = f"{uuid.uuid4()}.{file_ext}"
file_path = f"{upload_dir}/{filename}"
# Save file
try:
with open(file_path, "wb") as buffer:
buffer.write(await file.read())
except Exception as e:
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail=f"Failed to save avatar: {str(e)}"
)
# Update member's avatar URL
repo = MemberRepository(session)
avatar_url = f"/api/static/avatars/{filename}"
member = await repo.update(current_member, avatar_url=avatar_url)
return MemberRead.from_model(member)