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>
145 lines
4.1 KiB
Python
145 lines
4.1 KiB
Python
"""Session management service
|
|
|
|
處理 user_sessions 資料庫操作:
|
|
- 建立/查詢/刪除 session
|
|
- 更新活動時間戳
|
|
- 管理 refresh 重試計數器
|
|
"""
|
|
import uuid
|
|
from datetime import datetime
|
|
from sqlalchemy.orm import Session
|
|
from app.modules.auth.models import UserSession
|
|
|
|
|
|
class SessionService:
|
|
"""Session management service"""
|
|
|
|
def create_session(
|
|
self,
|
|
db: Session,
|
|
username: str,
|
|
display_name: str,
|
|
ad_token: str,
|
|
encrypted_password: str,
|
|
ad_token_expires_at: datetime,
|
|
) -> UserSession:
|
|
"""Create new user session
|
|
|
|
Args:
|
|
db: Database session
|
|
username: User email from AD
|
|
display_name: Display name from AD
|
|
ad_token: AD API token
|
|
encrypted_password: Encrypted password for auto-refresh
|
|
ad_token_expires_at: AD token expiry datetime
|
|
|
|
Returns:
|
|
Created UserSession object
|
|
"""
|
|
# Generate unique internal token
|
|
internal_token = str(uuid.uuid4())
|
|
|
|
session = UserSession(
|
|
username=username,
|
|
display_name=display_name,
|
|
internal_token=internal_token,
|
|
ad_token=ad_token,
|
|
encrypted_password=encrypted_password,
|
|
ad_token_expires_at=ad_token_expires_at,
|
|
refresh_attempt_count=0,
|
|
last_activity=datetime.utcnow(),
|
|
)
|
|
|
|
db.add(session)
|
|
db.commit()
|
|
db.refresh(session)
|
|
|
|
return session
|
|
|
|
def get_session_by_token(self, db: Session, internal_token: str) -> UserSession | None:
|
|
"""Get session by internal token
|
|
|
|
Args:
|
|
db: Database session
|
|
internal_token: Internal session token (UUID)
|
|
|
|
Returns:
|
|
UserSession if found, None otherwise
|
|
"""
|
|
return db.query(UserSession).filter(UserSession.internal_token == internal_token).first()
|
|
|
|
def update_activity(self, db: Session, session_id: int) -> None:
|
|
"""Update last_activity timestamp
|
|
|
|
Args:
|
|
db: Database session
|
|
session_id: Session ID
|
|
"""
|
|
db.query(UserSession).filter(UserSession.id == session_id).update(
|
|
{"last_activity": datetime.utcnow()}
|
|
)
|
|
db.commit()
|
|
|
|
def update_ad_token(
|
|
self, db: Session, session_id: int, new_ad_token: str, new_expires_at: datetime
|
|
) -> None:
|
|
"""Update AD token after successful refresh
|
|
|
|
Args:
|
|
db: Database session
|
|
session_id: Session ID
|
|
new_ad_token: New AD token
|
|
new_expires_at: New expiry datetime
|
|
"""
|
|
db.query(UserSession).filter(UserSession.id == session_id).update(
|
|
{
|
|
"ad_token": new_ad_token,
|
|
"ad_token_expires_at": new_expires_at,
|
|
"refresh_attempt_count": 0, # Reset counter on success
|
|
}
|
|
)
|
|
db.commit()
|
|
|
|
def increment_refresh_attempts(self, db: Session, session_id: int) -> int:
|
|
"""Increment refresh attempt counter
|
|
|
|
Args:
|
|
db: Database session
|
|
session_id: Session ID
|
|
|
|
Returns:
|
|
New refresh_attempt_count value
|
|
"""
|
|
session = db.query(UserSession).filter(UserSession.id == session_id).first()
|
|
if session:
|
|
session.refresh_attempt_count += 1
|
|
db.commit()
|
|
return session.refresh_attempt_count
|
|
return 0
|
|
|
|
def reset_refresh_attempts(self, db: Session, session_id: int) -> None:
|
|
"""Reset refresh attempt counter
|
|
|
|
Args:
|
|
db: Database session
|
|
session_id: Session ID
|
|
"""
|
|
db.query(UserSession).filter(UserSession.id == session_id).update(
|
|
{"refresh_attempt_count": 0}
|
|
)
|
|
db.commit()
|
|
|
|
def delete_session(self, db: Session, session_id: int) -> None:
|
|
"""Delete session
|
|
|
|
Args:
|
|
db: Database session
|
|
session_id: Session ID
|
|
"""
|
|
db.query(UserSession).filter(UserSession.id == session_id).delete()
|
|
db.commit()
|
|
|
|
|
|
# Singleton instance
|
|
session_service = SessionService()
|