solventum-image-metadata/backend/app/models/file.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

172 lines
4.4 KiB
Python

"""
Pydantic Models for File Operations
Request/Response schemas for file upload, metadata, etc.
"""
from pydantic import BaseModel, Field
from typing import Optional, List, Dict, Any
from datetime import datetime
# ===== File Upload Models =====
class FileUploadResponse(BaseModel):
"""Response after file upload"""
file_id: str
filename: str
filepath: str
file_type: str
size: int
uploaded_at: str
current_metadata: Dict[str, Optional[str]]
suggested_metadata: Dict[str, Optional[str]]
metadata_source: str
class UploadSessionResponse(BaseModel):
"""Response with session ID and uploaded files"""
success: bool
session_id: str
files: List[FileUploadResponse]
message: Optional[str] = None
# ===== Metadata Models =====
class MetadataUpdate(BaseModel):
"""Metadata update request"""
title: str = Field(..., max_length=200, description="Title (required)")
subject: Optional[str] = Field(None, max_length=300, description="Subject")
keywords: Optional[str] = Field(None, max_length=500, description="Keywords")
author: Optional[str] = Field(None, max_length=100, description="Author")
copyright: Optional[str] = Field(None, max_length=150, description="Copyright")
comments: Optional[str] = Field(None, max_length=500, description="Comments")
custom_fields: Optional[Dict[str, str]] = Field(None, description="Custom metadata fields")
class FileMetadataUpdate(BaseModel):
"""Update metadata for a single file"""
session_id: str
file_index: int
metadata: MetadataUpdate
class BatchMetadataUpdate(BaseModel):
"""Update metadata for multiple files"""
session_id: str
file_indices: List[int]
metadata: MetadataUpdate
class MetadataUpdateResponse(BaseModel):
"""Response after metadata update"""
success: bool
file_id: str
filename: str
verified: bool
message: str
# ===== Download Models =====
class BatchDownloadRequest(BaseModel):
"""Request to download multiple files as ZIP"""
session_id: str
file_indices: List[int]
# ===== Import/Excel Models =====
class ImportFileResponse(BaseModel):
"""Response after importing metadata file"""
success: bool
import_session_id: str
filename: str
import_type: str # 'csv', 'excel', 'json'
columns: Optional[List[str]] = None
sheet_names: Optional[List[str]] = None # For Excel only
sample_data: Optional[List[Dict[str, Any]]] = None
row_count: Optional[int] = None
class ColumnMapping(BaseModel):
"""Column mapping configuration"""
source_column: str
target_field: str # 'filename', 'title', 'subject', 'keywords', 'author', etc.
confidence: Optional[float] = None
class ImportMappingConfig(BaseModel):
"""Import mapping configuration"""
import_session_id: str
sheet_name: Optional[str] = None # For Excel
column_mappings: List[ColumnMapping]
class ExcelSheetPreviewRequest(BaseModel):
"""Request to preview Excel sheet"""
excel_session_id: str
sheet_name: str
# ===== Template Models =====
class TemplateCreate(BaseModel):
"""Create new template"""
name: str = Field(..., max_length=100)
title: str = Field(..., max_length=500)
subject: Optional[str] = Field(None, max_length=500)
keywords: Optional[str] = Field(None, max_length=500)
description: Optional[str] = Field(None, max_length=1000)
class TemplateApply(BaseModel):
"""Apply template to files"""
session_id: str
template_name: str
file_indices: List[int]
custom_vars: Optional[Dict[str, str]] = None
class TemplatePreview(BaseModel):
"""Preview template output"""
title: str
subject: Optional[str] = None
keywords: Optional[str] = None
sample_filename: str = "example.pdf"
custom_vars: Optional[Dict[str, str]] = None
class TemplateResponse(BaseModel):
"""Template data response"""
name: str
title: str
subject: Optional[str] = None
keywords: Optional[str] = None
description: Optional[str] = None
# ===== Session Cleanup =====
class SessionCleanupRequest(BaseModel):
"""Request to cleanup session files"""
session_id: str
# ===== Stats Models =====
class StorageStats(BaseModel):
"""Storage statistics"""
total_files: int
total_size_bytes: int
total_size_mb: float
total_users: int
class UserActivity(BaseModel):
"""User activity log entry"""
id: int
user_id: int
action: str
details: Optional[str]
timestamp: str