- 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>
172 lines
4.4 KiB
Python
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
|