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,95 @@
"""Authentication API endpoints
提供:
- POST /api/auth/login - 使用者登入
- POST /api/auth/logout - 使用者登出
"""
from fastapi import APIRouter, Depends, HTTPException, status
from sqlalchemy.orm import Session
from app.core.database import get_db
from app.modules.auth.schemas import LoginRequest, LoginResponse, LogoutResponse, ErrorResponse
from app.modules.auth.services.ad_client import ad_auth_service
from app.modules.auth.services.encryption import encryption_service
from app.modules.auth.services.session_service import session_service
from fastapi import Header
from typing import Optional
router = APIRouter(prefix="/api/auth", tags=["Authentication"])
@router.post(
"/login",
response_model=LoginResponse,
responses={
401: {"model": ErrorResponse, "description": "Invalid credentials"},
503: {"model": ErrorResponse, "description": "Authentication service unavailable"},
},
)
async def login(request: LoginRequest, db: Session = Depends(get_db)):
"""使用者登入
流程:
1. 呼叫 AD API 驗證憑證
2. 加密密碼(用於自動刷新)
3. 生成 internal token (UUID)
4. 儲存 session 到資料庫
5. 回傳 internal token 和 display_name
"""
try:
# Step 1: Authenticate with AD API
ad_result = await ad_auth_service.authenticate(request.username, request.password)
except ValueError as e:
# Invalid credentials
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid credentials"
)
except ConnectionError as e:
# AD API unavailable
raise HTTPException(
status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
detail="Authentication service unavailable",
)
# Step 2: Encrypt password for future auto-refresh
encrypted_password = encryption_service.encrypt_password(request.password)
# Step 3 & 4: Generate internal token and create session
user_session = session_service.create_session(
db=db,
username=request.username,
display_name=ad_result["username"],
ad_token=ad_result["token"],
encrypted_password=encrypted_password,
ad_token_expires_at=ad_result["expires_at"],
)
# Step 5: Return internal token to client
return LoginResponse(token=user_session.internal_token, display_name=user_session.display_name)
@router.post(
"/logout",
response_model=LogoutResponse,
responses={401: {"model": ErrorResponse, "description": "No authentication token provided"}},
)
async def logout(authorization: Optional[str] = Header(None), db: Session = Depends(get_db)):
"""使用者登出
刪除 session 記錄,使 token 失效
"""
if not authorization or not authorization.startswith("Bearer "):
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED, detail="No authentication token provided"
)
# Extract token
internal_token = authorization.replace("Bearer ", "")
# Find and delete session
user_session = session_service.get_session_by_token(db, internal_token)
if user_session:
session_service.delete_session(db, user_session.id)
return LogoutResponse(message="Logout successful")