solventum-image-metadata/backend/app/api/metadata.py
SamoilenkoVadym 563d476a94 feat(backend): migrate from Flask to FastAPI with Redis sessions
- Create FastAPI application with async I/O
- Implement Redis session storage (fixes session loss on restart)
- Add JWT authentication with refresh tokens
- Add Microsoft SSO support via MSAL
- Copy all processors from src/ (100% reused, no changes)
- Create file upload/download endpoints
- Create metadata update endpoints
- Create template CRUD endpoints
- Add SQLAlchemy async database models
- Add Docker Compose configuration with Redis

Solves critical issues:
- Session management: Redis replaces in-memory dicts
- Scalability: Async FastAPI + microservices architecture
- File handling: Persistent storage with auto-cleanup

Key files:
- backend/app/main.py - FastAPI entry point
- backend/app/core/redis_client.py - Session store
- backend/app/core/auth.py - JWT authentication
- backend/app/api/* - All REST endpoints
- backend/app/processors/ - Reused from src/

Co-Authored-By: Claude Sonnet 4.5 (1M context) <noreply@anthropic.com>
2026-02-09 13:14:37 +00:00

171 lines
5 KiB
Python

"""
Metadata API Endpoints
Handles metadata updates and verification.
"""
from fastapi import APIRouter, Depends, HTTPException, Request, status
from sqlalchemy.ext.asyncio import AsyncSession
from app.core.auth import get_current_user_id
from app.core.database import get_db, AuditLogRepository
from app.core.redis_client import RedisSessionStore
from app.services.metadata_service import get_metadata_service, MetadataService
from app.models.file import (
FileMetadataUpdate,
BatchMetadataUpdate,
MetadataUpdateResponse
)
router = APIRouter()
@router.put("/{file_id}")
async def update_file_metadata(
file_id: str,
update_data: FileMetadataUpdate,
request: Request,
user_id: int = Depends(get_current_user_id),
db: AsyncSession = Depends(get_db),
metadata_service: MetadataService = Depends(get_metadata_service)
):
"""
Update metadata for a single file.
"""
# Get file session
redis: RedisSessionStore = request.app.state.redis
session_data = await redis.get_file_session(update_data.session_id)
if not session_data or session_data.get("user_id") != user_id:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail="Session not found or access denied"
)
# Get file from session
files = session_data.get("files", [])
if update_data.file_index >= len(files):
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail="Invalid file index"
)
file_info = files[update_data.file_index]
if file_info.get("file_id") != file_id:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail="File ID mismatch"
)
# Update metadata
success, message = await metadata_service.update_file_metadata(
filepath=file_info["filepath"],
metadata=update_data.metadata.dict(exclude_none=True)
)
if not success:
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail=message
)
# Update session with new metadata
file_info["suggested_metadata"] = update_data.metadata.dict(exclude_none=True)
files[update_data.file_index] = file_info
await redis.update_file_session(update_data.session_id, files)
# Log action
await AuditLogRepository.log_action(
db,
user_id=user_id,
action="metadata_update",
details=f"Updated metadata for file: {file_info['filename']}"
)
return MetadataUpdateResponse(
success=True,
file_id=file_id,
filename=file_info["filename"],
verified="verified" in message.lower(),
message=message
)
@router.post("/batch-update")
async def batch_update_metadata(
update_data: BatchMetadataUpdate,
request: Request,
user_id: int = Depends(get_current_user_id),
db: AsyncSession = Depends(get_db),
metadata_service: MetadataService = Depends(get_metadata_service)
):
"""
Update metadata for multiple files with same metadata.
"""
# Get file session
redis: RedisSessionStore = request.app.state.redis
session_data = await redis.get_file_session(update_data.session_id)
if not session_data or session_data.get("user_id") != user_id:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail="Session not found or access denied"
)
# Get files from session
files = session_data.get("files", [])
# Update each file
results = []
metadata_dict = update_data.metadata.dict(exclude_none=True)
for file_index in update_data.file_indices:
if file_index >= len(files):
continue
file_info = files[file_index]
try:
# Update metadata
success, message = await metadata_service.update_file_metadata(
filepath=file_info["filepath"],
metadata=metadata_dict
)
results.append({
"file_id": file_info["file_id"],
"filename": file_info["filename"],
"success": success,
"message": message
})
# Update session
if success:
file_info["suggested_metadata"] = metadata_dict
files[file_index] = file_info
except Exception as e:
results.append({
"file_id": file_info.get("file_id"),
"filename": file_info.get("filename"),
"success": False,
"message": str(e)
})
# Update session with new metadata
await redis.update_file_session(update_data.session_id, files)
# Log action
await AuditLogRepository.log_action(
db,
user_id=user_id,
action="batch_metadata_update",
details=f"Updated metadata for {len(update_data.file_indices)} files"
)
return {
"success": True,
"results": results,
"message": f"Updated {len(results)} files"
}