Major Features: - ✅ Multi-tenant architecture (tenant isolation) - ✅ Employee CRUD with lifecycle management (onboarding/offboarding) - ✅ Department tree structure with email domain management - ✅ Company info management (single-record editing) - ✅ System functions CRUD (permission management) - ✅ Email account management (multi-account per employee) - ✅ Keycloak SSO integration (auth.lab.taipei) - ✅ Redis session storage (10.1.0.254:6379) - Solves Cookie 4KB limitation - Cross-system session sharing - Sliding expiration (8 hours) - Automatic token refresh Technical Stack: Backend: - FastAPI + SQLAlchemy - PostgreSQL 16 (10.1.0.20:5433) - Keycloak Admin API integration - Docker Mailserver integration (SSH) - Alembic migrations Frontend: - Next.js 14 (App Router) - NextAuth 4 with Keycloak Provider - Redis session storage (ioredis) - Tailwind CSS Infrastructure: - Redis 7 (10.1.0.254:6379) - Session + Cache - Keycloak 26.1.0 (auth.lab.taipei) - Docker Mailserver (10.1.0.254) Architecture Highlights: - Session管理由 Keycloak + Redis 統一控制 - 支援多系統 (HR/WebMail/Calendar/Drive/Office) 共享 session - Token 自動刷新,異質服務整合 - 未來可無縫遷移到雲端 Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
304 lines
9.0 KiB
Python
304 lines
9.0 KiB
Python
"""
|
|
System Functions API
|
|
系統功能明細 CRUD API
|
|
"""
|
|
from typing import List, Optional
|
|
from fastapi import APIRouter, Depends, HTTPException, status, Query
|
|
from sqlalchemy.orm import Session
|
|
from sqlalchemy import and_, or_
|
|
|
|
from app.db.session import get_db
|
|
from app.models.system_function import SystemFunction
|
|
from app.schemas.system_function import (
|
|
SystemFunctionCreate,
|
|
SystemFunctionUpdate,
|
|
SystemFunctionResponse,
|
|
SystemFunctionListResponse
|
|
)
|
|
from app.api.deps import get_pagination_params
|
|
from app.schemas.base import PaginationParams
|
|
|
|
router = APIRouter()
|
|
|
|
|
|
@router.get("", response_model=SystemFunctionListResponse)
|
|
def get_system_functions(
|
|
function_type: Optional[int] = Query(None, description="功能類型 (1:node, 2:function)"),
|
|
upper_function_id: Optional[int] = Query(None, description="上層功能代碼"),
|
|
is_mana: Optional[bool] = Query(None, description="系統管理"),
|
|
is_active: Optional[bool] = Query(None, description="啟用(預設顯示全部)"),
|
|
search: Optional[str] = Query(None, description="搜尋 (code or name)"),
|
|
pagination: PaginationParams = Depends(get_pagination_params),
|
|
db: Session = Depends(get_db),
|
|
):
|
|
"""
|
|
取得系統功能列表
|
|
|
|
- 支援分頁
|
|
- 支援篩選 (function_type, upper_function_id, is_mana, is_active)
|
|
- 支援搜尋 (code or name)
|
|
"""
|
|
query = db.query(SystemFunction)
|
|
|
|
# 篩選條件
|
|
filters = []
|
|
if function_type is not None:
|
|
filters.append(SystemFunction.function_type == function_type)
|
|
if upper_function_id is not None:
|
|
filters.append(SystemFunction.upper_function_id == upper_function_id)
|
|
if is_mana is not None:
|
|
filters.append(SystemFunction.is_mana == is_mana)
|
|
if is_active is not None:
|
|
filters.append(SystemFunction.is_active == is_active)
|
|
|
|
# 搜尋
|
|
if search:
|
|
filters.append(
|
|
or_(
|
|
SystemFunction.code.ilike(f"%{search}%"),
|
|
SystemFunction.name.ilike(f"%{search}%")
|
|
)
|
|
)
|
|
|
|
if filters:
|
|
query = query.filter(and_(*filters))
|
|
|
|
# 排序 (依照 order 排序)
|
|
query = query.order_by(SystemFunction.order.asc())
|
|
|
|
# 計算總數
|
|
total = query.count()
|
|
|
|
# 分頁
|
|
offset = (pagination.page - 1) * pagination.page_size
|
|
items = query.offset(offset).limit(pagination.page_size).all()
|
|
|
|
return SystemFunctionListResponse(
|
|
total=total,
|
|
items=items,
|
|
page=pagination.page,
|
|
page_size=pagination.page_size
|
|
)
|
|
|
|
|
|
@router.get("/{function_id}", response_model=SystemFunctionResponse)
|
|
def get_system_function(
|
|
function_id: int,
|
|
db: Session = Depends(get_db),
|
|
):
|
|
"""
|
|
取得單一系統功能
|
|
"""
|
|
function = db.query(SystemFunction).filter(SystemFunction.id == function_id).first()
|
|
|
|
if not function:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_404_NOT_FOUND,
|
|
detail=f"System function not found: {function_id}"
|
|
)
|
|
|
|
return function
|
|
|
|
|
|
@router.post("", response_model=SystemFunctionResponse, status_code=status.HTTP_201_CREATED)
|
|
def create_system_function(
|
|
function_in: SystemFunctionCreate,
|
|
db: Session = Depends(get_db),
|
|
):
|
|
"""
|
|
建立系統功能
|
|
|
|
驗證規則:
|
|
- function_type=1 (node) 時, module_code 不能輸入
|
|
- function_type=2 (function) 時, module_code 和 module_functions 為必填
|
|
- upper_function_id 必須是 function_type=1 且 is_active=1 的功能, 或 0 (初始層)
|
|
"""
|
|
# 驗證 upper_function_id
|
|
if function_in.upper_function_id > 0:
|
|
parent = db.query(SystemFunction).filter(
|
|
SystemFunction.id == function_in.upper_function_id,
|
|
SystemFunction.function_type == 1,
|
|
SystemFunction.is_active == True
|
|
).first()
|
|
|
|
if not parent:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_400_BAD_REQUEST,
|
|
detail=f"Invalid upper_function_id: {function_in.upper_function_id} "
|
|
"(must be function_type=1 and is_active=1)"
|
|
)
|
|
|
|
# 檢查 code 是否重複
|
|
existing = db.query(SystemFunction).filter(SystemFunction.code == function_in.code).first()
|
|
if existing:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_400_BAD_REQUEST,
|
|
detail=f"System function code already exists: {function_in.code}"
|
|
)
|
|
|
|
# 建立資料
|
|
db_function = SystemFunction(**function_in.model_dump())
|
|
db.add(db_function)
|
|
db.commit()
|
|
db.refresh(db_function)
|
|
|
|
return db_function
|
|
|
|
|
|
@router.put("/{function_id}", response_model=SystemFunctionResponse)
|
|
def update_system_function(
|
|
function_id: int,
|
|
function_in: SystemFunctionUpdate,
|
|
db: Session = Depends(get_db),
|
|
):
|
|
"""
|
|
更新系統功能 (完整更新)
|
|
"""
|
|
# 查詢現有資料
|
|
db_function = db.query(SystemFunction).filter(SystemFunction.id == function_id).first()
|
|
|
|
if not db_function:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_404_NOT_FOUND,
|
|
detail=f"System function not found: {function_id}"
|
|
)
|
|
|
|
# 更新資料
|
|
update_data = function_in.model_dump(exclude_unset=True)
|
|
|
|
for field, value in update_data.items():
|
|
setattr(db_function, field, value)
|
|
|
|
db.commit()
|
|
db.refresh(db_function)
|
|
|
|
return db_function
|
|
|
|
|
|
@router.patch("/{function_id}", response_model=SystemFunctionResponse)
|
|
def patch_system_function(
|
|
function_id: int,
|
|
function_in: SystemFunctionUpdate,
|
|
db: Session = Depends(get_db),
|
|
):
|
|
"""
|
|
更新系統功能 (部分更新)
|
|
"""
|
|
return update_system_function(function_id, function_in, db)
|
|
|
|
|
|
@router.delete("/{function_id}", status_code=status.HTTP_204_NO_CONTENT)
|
|
def delete_system_function(
|
|
function_id: int,
|
|
db: Session = Depends(get_db),
|
|
):
|
|
"""
|
|
刪除系統功能 (實際上是軟刪除, 設定 is_active=False)
|
|
"""
|
|
db_function = db.query(SystemFunction).filter(SystemFunction.id == function_id).first()
|
|
|
|
if not db_function:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_404_NOT_FOUND,
|
|
detail=f"System function not found: {function_id}"
|
|
)
|
|
|
|
# 軟刪除
|
|
db_function.is_active = False
|
|
db.commit()
|
|
|
|
return None
|
|
|
|
|
|
@router.delete("/{function_id}/hard", status_code=status.HTTP_204_NO_CONTENT)
|
|
def hard_delete_system_function(
|
|
function_id: int,
|
|
db: Session = Depends(get_db),
|
|
):
|
|
"""
|
|
永久刪除系統功能 (硬刪除)
|
|
|
|
⚠️ 警告: 此操作無法復原
|
|
"""
|
|
db_function = db.query(SystemFunction).filter(SystemFunction.id == function_id).first()
|
|
|
|
if not db_function:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_404_NOT_FOUND,
|
|
detail=f"System function not found: {function_id}"
|
|
)
|
|
|
|
# 硬刪除
|
|
db.delete(db_function)
|
|
db.commit()
|
|
|
|
return None
|
|
|
|
|
|
@router.get("/menu/tree", response_model=List[dict])
|
|
def get_menu_tree(
|
|
is_sysmana: bool = Query(False, description="是否為系統管理公司"),
|
|
db: Session = Depends(get_db),
|
|
):
|
|
"""
|
|
取得功能列表樹狀結構 (用於前端選單顯示)
|
|
|
|
根據 is_sysmana 過濾功能:
|
|
- is_sysmana=true: 返回所有功能 (包含 is_mana=true 的系統管理功能)
|
|
- is_sysmana=false: 只返回 is_mana=false 的一般功能
|
|
|
|
返回格式:
|
|
[
|
|
{
|
|
"id": 10,
|
|
"code": "system_managements",
|
|
"name": "系統管理後台",
|
|
"function_type": 1,
|
|
"order": 100,
|
|
"function_icon": "",
|
|
"module_code": null,
|
|
"module_functions": [],
|
|
"children": [
|
|
{
|
|
"id": 11,
|
|
"code": "system_settings",
|
|
"name": "系統資料設定",
|
|
"function_type": 2,
|
|
...
|
|
}
|
|
]
|
|
}
|
|
]
|
|
"""
|
|
# 查詢條件
|
|
query = db.query(SystemFunction).filter(SystemFunction.is_active == True)
|
|
|
|
# 如果不是系統管理公司,過濾掉 is_mana=true 的功能
|
|
if not is_sysmana:
|
|
query = query.filter(SystemFunction.is_mana == False)
|
|
|
|
# 排序
|
|
functions = query.order_by(SystemFunction.order.asc()).all()
|
|
|
|
# 建立樹狀結構
|
|
def build_tree(parent_id: int = 0) -> List[dict]:
|
|
tree = []
|
|
for func in functions:
|
|
if func.upper_function_id == parent_id:
|
|
node = {
|
|
"id": func.id,
|
|
"code": func.code,
|
|
"name": func.name,
|
|
"function_type": func.function_type,
|
|
"order": func.order,
|
|
"function_icon": func.function_icon or "",
|
|
"module_code": func.module_code,
|
|
"module_functions": func.module_functions or [],
|
|
"description": func.description or "",
|
|
"children": build_tree(func.id) if func.function_type == 1 else []
|
|
}
|
|
tree.append(node)
|
|
return tree
|
|
|
|
return build_tree(0)
|