feat: Initial commit - Task Reporter incident response system

Complete implementation of the production line incident response system (生產線異常即時反應系統) including:

Backend (FastAPI):
- User authentication with AD integration and session management
- Chat room management (create, list, update, members, roles)
- Real-time messaging via WebSocket (typing indicators, reactions)
- File storage with MinIO (upload, download, image preview)

Frontend (React + Vite):
- Authentication flow with token management
- Room list with filtering, search, and pagination
- Real-time chat interface with WebSocket
- File upload with drag-and-drop and image preview
- Member management and room settings
- Breadcrumb navigation
- 53 unit tests (Vitest)

Specifications:
- authentication: AD auth, sessions, JWT tokens
- chat-room: rooms, members, templates
- realtime-messaging: WebSocket, messages, reactions
- file-storage: MinIO integration, file management
- frontend-core: React SPA structure

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

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
egg
2025-12-01 17:42:52 +08:00
commit c8966477b9
135 changed files with 23269 additions and 0 deletions

View File

@@ -0,0 +1,158 @@
"""File validation utilities"""
import magic
from fastapi import UploadFile, HTTPException
from typing import Set
import logging
logger = logging.getLogger(__name__)
# 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"
}
# File size limits (bytes)
IMAGE_MAX_SIZE = 10 * 1024 * 1024 # 10MB
DOCUMENT_MAX_SIZE = 20 * 1024 * 1024 # 20MB
LOG_MAX_SIZE = 5 * 1024 * 1024 # 5MB
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", IMAGE_MAX_SIZE)
elif mime_type in DOCUMENT_TYPES:
return ("document", DOCUMENT_MAX_SIZE)
elif mime_type in LOG_TYPES:
return ("log", LOG_MAX_SIZE)
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)