Initial commit: KPI Management System Backend
Features: - FastAPI backend with JWT authentication - MySQL database with SQLAlchemy ORM - KPI workflow: draft → pending → approved → evaluation → completed - Ollama LLM API integration for AI features - Gitea API integration for version control - Complete API endpoints for KPI, dashboard, notifications Tables: KPI_D_* prefix naming convention 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
1
app/core/__init__.py
Normal file
1
app/core/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
# Core module
|
||||
52
app/core/config.py
Normal file
52
app/core/config.py
Normal file
@@ -0,0 +1,52 @@
|
||||
"""
|
||||
應用程式設定
|
||||
"""
|
||||
from functools import lru_cache
|
||||
from pydantic_settings import BaseSettings
|
||||
|
||||
|
||||
class Settings(BaseSettings):
|
||||
"""應用程式設定"""
|
||||
|
||||
# App
|
||||
APP_NAME: str = "KPI Management System"
|
||||
DEBUG: bool = True
|
||||
|
||||
# Database (MySQL)
|
||||
DB_HOST: str = "mysql.theaken.com"
|
||||
DB_PORT: int = 33306
|
||||
DB_NAME: str = "db_A102"
|
||||
DB_USER: str = "A102"
|
||||
DB_PASSWORD: str = "Bb123456"
|
||||
|
||||
@property
|
||||
def DATABASE_URL(self) -> str:
|
||||
return f"mysql+pymysql://{self.DB_USER}:{self.DB_PASSWORD}@{self.DB_HOST}:{self.DB_PORT}/{self.DB_NAME}"
|
||||
|
||||
# JWT
|
||||
SECRET_KEY: str = "your-super-secret-key-change-in-production"
|
||||
ALGORITHM: str = "HS256"
|
||||
ACCESS_TOKEN_EXPIRE_MINUTES: int = 30
|
||||
REFRESH_TOKEN_EXPIRE_DAYS: int = 7
|
||||
|
||||
# Ollama LLM API
|
||||
OLLAMA_API_URL: str = "https://ollama_pjapi.theaken.com"
|
||||
OLLAMA_DEFAULT_MODEL: str = "qwen2.5:3b"
|
||||
|
||||
# Gitea
|
||||
GITEA_URL: str = "https://gitea.theaken.com"
|
||||
GITEA_USER: str = "donald"
|
||||
GITEA_TOKEN: str = "9e0a888d1a25bde9cf2ad5dff2bb7ee6d68d6ff0"
|
||||
|
||||
class Config:
|
||||
env_file = ".env"
|
||||
case_sensitive = True
|
||||
|
||||
|
||||
@lru_cache
|
||||
def get_settings() -> Settings:
|
||||
"""取得設定單例"""
|
||||
return Settings()
|
||||
|
||||
|
||||
settings = get_settings()
|
||||
39
app/core/database.py
Normal file
39
app/core/database.py
Normal file
@@ -0,0 +1,39 @@
|
||||
"""
|
||||
資料庫連線設定 (MySQL)
|
||||
"""
|
||||
from sqlalchemy import create_engine
|
||||
from sqlalchemy.orm import sessionmaker, declarative_base
|
||||
|
||||
from app.core.config import settings
|
||||
|
||||
# 建立資料庫引擎 (MySQL)
|
||||
engine = create_engine(
|
||||
settings.DATABASE_URL,
|
||||
pool_pre_ping=True,
|
||||
pool_size=10,
|
||||
max_overflow=20,
|
||||
pool_recycle=3600, # MySQL 連線回收
|
||||
)
|
||||
|
||||
# 建立 Session 工廠
|
||||
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
|
||||
|
||||
# 建立 Base 類別
|
||||
Base = declarative_base()
|
||||
|
||||
|
||||
def get_db():
|
||||
"""
|
||||
取得資料庫 Session
|
||||
用於 FastAPI 依賴注入
|
||||
"""
|
||||
db = SessionLocal()
|
||||
try:
|
||||
yield db
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
|
||||
def init_db():
|
||||
"""初始化資料庫(建立所有表)"""
|
||||
Base.metadata.create_all(bind=engine)
|
||||
83
app/core/security.py
Normal file
83
app/core/security.py
Normal file
@@ -0,0 +1,83 @@
|
||||
"""
|
||||
安全模組:JWT 認證與密碼雜湊
|
||||
"""
|
||||
from datetime import datetime, timedelta
|
||||
from typing import Optional
|
||||
|
||||
from jose import JWTError, jwt
|
||||
from passlib.context import CryptContext
|
||||
|
||||
from app.core.config import settings
|
||||
|
||||
# 密碼雜湊上下文
|
||||
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
|
||||
|
||||
|
||||
def verify_password(plain_password: str, hashed_password: str) -> bool:
|
||||
"""驗證密碼"""
|
||||
return pwd_context.verify(plain_password, hashed_password)
|
||||
|
||||
|
||||
def get_password_hash(password: str) -> str:
|
||||
"""產生密碼雜湊"""
|
||||
return pwd_context.hash(password)
|
||||
|
||||
|
||||
def create_access_token(data: dict, expires_delta: Optional[timedelta] = None) -> str:
|
||||
"""
|
||||
建立 Access Token
|
||||
|
||||
Args:
|
||||
data: Token 內容 (通常包含 sub: user_id)
|
||||
expires_delta: 過期時間
|
||||
|
||||
Returns:
|
||||
JWT Token 字串
|
||||
"""
|
||||
to_encode = data.copy()
|
||||
|
||||
if expires_delta:
|
||||
expire = datetime.utcnow() + expires_delta
|
||||
else:
|
||||
expire = datetime.utcnow() + timedelta(minutes=settings.ACCESS_TOKEN_EXPIRE_MINUTES)
|
||||
|
||||
to_encode.update({"exp": expire, "type": "access"})
|
||||
encoded_jwt = jwt.encode(to_encode, settings.SECRET_KEY, algorithm=settings.ALGORITHM)
|
||||
|
||||
return encoded_jwt
|
||||
|
||||
|
||||
def create_refresh_token(data: dict) -> str:
|
||||
"""
|
||||
建立 Refresh Token
|
||||
|
||||
Args:
|
||||
data: Token 內容
|
||||
|
||||
Returns:
|
||||
JWT Token 字串
|
||||
"""
|
||||
to_encode = data.copy()
|
||||
expire = datetime.utcnow() + timedelta(days=settings.REFRESH_TOKEN_EXPIRE_DAYS)
|
||||
|
||||
to_encode.update({"exp": expire, "type": "refresh"})
|
||||
encoded_jwt = jwt.encode(to_encode, settings.SECRET_KEY, algorithm=settings.ALGORITHM)
|
||||
|
||||
return encoded_jwt
|
||||
|
||||
|
||||
def decode_token(token: str) -> Optional[dict]:
|
||||
"""
|
||||
解碼 Token
|
||||
|
||||
Args:
|
||||
token: JWT Token 字串
|
||||
|
||||
Returns:
|
||||
Token 內容或 None (解碼失敗)
|
||||
"""
|
||||
try:
|
||||
payload = jwt.decode(token, settings.SECRET_KEY, algorithms=[settings.ALGORITHM])
|
||||
return payload
|
||||
except JWTError:
|
||||
return None
|
||||
Reference in New Issue
Block a user