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>
This commit is contained in:
egg
2025-12-07 14:15:11 +08:00
parent 1d5d4d447d
commit 92834dbe0e
39 changed files with 1558 additions and 136 deletions

View File

@@ -1,10 +1,12 @@
"""SQLAlchemy models for authentication
資料表結構:
- user_sessions: 儲存使用者 session 資料,包含加密密碼用於自動刷新
- users: 永久儲存使用者資訊 (用於報告生成時的姓名解析)
- tr_user_sessions: 儲存使用者 session 資料,包含加密密碼用於自動刷新
- tr_users: 永久儲存使用者資訊 (用於報告生成時的姓名解析)
Note: All tables use 'tr_' prefix to avoid conflicts in shared database.
"""
from sqlalchemy import Column, Integer, String, DateTime, Index
from sqlalchemy import Column, Integer, String, DateTime, Index, Text
from datetime import datetime
from app.core.database import Base
@@ -12,7 +14,7 @@ from app.core.database import Base
class UserSession(Base):
"""User session model with encrypted password for auto-refresh"""
__tablename__ = "user_sessions"
__tablename__ = "tr_user_sessions"
id = Column(Integer, primary_key=True, index=True)
username = Column(String(255), nullable=False, comment="User email from AD")
@@ -20,7 +22,7 @@ class UserSession(Base):
internal_token = Column(
String(255), unique=True, nullable=False, index=True, comment="Internal session token (UUID)"
)
ad_token = Column(String(500), nullable=False, comment="AD API token")
ad_token = Column(Text, nullable=False, comment="AD API token (JWT)")
encrypted_password = Column(String(500), nullable=False, comment="AES-256 encrypted password")
ad_token_expires_at = Column(DateTime, nullable=False, comment="AD token expiry time")
refresh_attempt_count = Column(
@@ -41,7 +43,7 @@ class User(Base):
- Tracking user metadata (office location, job title)
"""
__tablename__ = "users"
__tablename__ = "tr_users"
user_id = Column(
String(255), primary_key=True, comment="User email address (e.g., ymirliu@panjit.com.tw)"
@@ -64,5 +66,5 @@ class User(Base):
# Indexes
__table_args__ = (
Index("ix_users_display_name", "display_name"),
Index("ix_tr_users_display_name", "display_name"),
)

View File

@@ -18,7 +18,7 @@ class ADAuthService:
def __init__(self):
self.ad_api_url = settings.AD_API_URL
self._client = httpx.AsyncClient(timeout=10.0)
self._client = httpx.AsyncClient(timeout=float(settings.AD_API_TIMEOUT_SECONDS))
async def authenticate(self, username: str, password: str) -> Dict[str, any]:
"""Authenticate user with AD API