Files
Task_Reporter/app/modules/file_storage/validators.py
egg 92834dbe0e feat: Migrate to MySQL and add unified environment configuration
## Database Migration (SQLite → MySQL)
- Add Alembic migration framework
- Add 'tr_' prefix to all tables to avoid conflicts in shared database
- Remove SQLite support, use MySQL exclusively
- Add pymysql driver dependency
- Change ad_token column to Text type for long JWT tokens

## Unified Environment Configuration
- Centralize all hardcoded settings to environment variables
- Backend: Extend Settings class in app/core/config.py
- Frontend: Use Vite environment variables (import.meta.env)
- Docker: Move credentials to environment variables
- Update .env.example files with comprehensive documentation

## Test Organization
- Move root-level test files to tests/ directory:
  - test_chat_room.py → tests/test_chat_room.py
  - test_websocket.py → tests/test_websocket.py
  - test_realtime_implementation.py → tests/test_realtime_implementation.py
- Fix path references in test_realtime_implementation.py

Breaking Changes:
- CORS now requires explicit origins (no more wildcard)
- All database tables renamed with 'tr_' prefix
- SQLite no longer supported

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-07 14:15:11 +08:00

157 lines
3.7 KiB
Python

"""File validation utilities"""
import magic
from fastapi import UploadFile, HTTPException
from typing import Set
import logging
from app.core.config import get_settings
logger = logging.getLogger(__name__)
settings = get_settings()
# MIME type whitelists
IMAGE_TYPES: Set[str] = {
"image/jpeg",
"image/png",
"image/gif"
}
DOCUMENT_TYPES: Set[str] = {
"application/pdf"
}
LOG_TYPES: Set[str] = {
"text/plain",
"text/csv"
}
def detect_mime_type(file_data: bytes) -> str:
"""
Detect MIME type from file content using python-magic
Args:
file_data: First chunk of file data
Returns:
MIME type string
"""
try:
mime = magic.Magic(mime=True)
return mime.from_buffer(file_data)
except Exception as e:
logger.error(f"Failed to detect MIME type: {e}")
return "application/octet-stream"
def validate_file_type(file: UploadFile, allowed_types: Set[str]) -> str:
"""
Validate file MIME type using actual file content
Args:
file: FastAPI UploadFile object
allowed_types: Set of allowed MIME types
Returns:
Detected MIME type
Raises:
HTTPException if file type is not allowed
"""
# Read first 2048 bytes to detect MIME type
file.file.seek(0)
header = file.file.read(2048)
file.file.seek(0)
# Detect actual MIME type from content
detected_mime = detect_mime_type(header)
if detected_mime not in allowed_types:
raise HTTPException(
status_code=400,
detail=f"File type not allowed: {detected_mime}. Allowed types: {', '.join(allowed_types)}"
)
return detected_mime
def validate_file_size(file: UploadFile, max_size: int):
"""
Validate file size
Args:
file: FastAPI UploadFile object
max_size: Maximum allowed size in bytes
Raises:
HTTPException if file exceeds max size
"""
# Seek to end to get file size
file.file.seek(0, 2) # 2 = SEEK_END
file_size = file.file.tell()
file.file.seek(0) # Reset to beginning
if file_size > max_size:
max_mb = max_size / (1024 * 1024)
actual_mb = file_size / (1024 * 1024)
raise HTTPException(
status_code=413,
detail=f"File size exceeds limit: {actual_mb:.2f}MB > {max_mb:.2f}MB"
)
return file_size
def get_file_type_and_limits(mime_type: str) -> tuple[str, int]:
"""
Determine file type category and size limit from MIME type
Args:
mime_type: MIME type string
Returns:
Tuple of (file_type, max_size)
Raises:
HTTPException if MIME type not recognized
"""
if mime_type in IMAGE_TYPES:
return ("image", settings.get_image_max_size_bytes())
elif mime_type in DOCUMENT_TYPES:
return ("document", settings.get_document_max_size_bytes())
elif mime_type in LOG_TYPES:
return ("log", settings.get_log_max_size_bytes())
else:
raise HTTPException(
status_code=400,
detail=f"Unsupported file type: {mime_type}"
)
def validate_upload_file(file: UploadFile) -> tuple[str, str, int]:
"""
Validate uploaded file (type and size)
Args:
file: FastAPI UploadFile object
Returns:
Tuple of (file_type, mime_type, file_size)
Raises:
HTTPException if validation fails
"""
# Combine all allowed types
all_allowed_types = IMAGE_TYPES | DOCUMENT_TYPES | LOG_TYPES
# Validate MIME type
mime_type = validate_file_type(file, all_allowed_types)
# Get file type category and max size
file_type, max_size = get_file_type_and_limits(mime_type)
# Validate file size
file_size = validate_file_size(file, max_size)
return (file_type, mime_type, file_size)