- 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>
171 lines
5 KiB
Python
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"
|
|
}
|