feat: HR Portal - Complete Multi-Tenant System with Redis Session Storage

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>
This commit is contained in:
2026-02-23 20:12:43 +08:00
commit 360533393f
386 changed files with 70353 additions and 0 deletions

185
scripts/setup-db-simple.ps1 Normal file
View File

@@ -0,0 +1,185 @@
# ====================================
# HR Portal - 簡化版資料庫設定
# ====================================
param(
[string]$DBHost = "10.1.0.254",
[string]$DBName = "hr_portal",
[string]$DBUser = "hr_user",
[string]$DBPassword = "",
[string]$PostgresPassword = "",
[string]$ContainerName = "postgres"
)
Write-Host "=== HR Portal 資料庫設定 ===" -ForegroundColor Cyan
Write-Host ""
# 檢查必要參數
if (-not $DBPassword) {
$DBPassword = Read-Host "請輸入 hr_user 的密碼" -AsSecureString
$DBPassword = [Runtime.InteropServices.Marshal]::PtrToStringAuto(
[Runtime.InteropServices.Marshal]::SecureStringToBSTR($DBPassword)
)
}
if (-not $PostgresPassword) {
$PostgresPassword = Read-Host "請輸入 postgres 超級用戶密碼" -AsSecureString
$PostgresPassword = [Runtime.InteropServices.Marshal]::PtrToStringAuto(
[Runtime.InteropServices.Marshal]::SecureStringToBSTR($PostgresPassword)
)
}
Write-Host ""
Write-Host "設定資訊:" -ForegroundColor Yellow
Write-Host " 資料庫主機: $DBHost"
Write-Host " 資料庫名稱: $DBName"
Write-Host " 資料庫用戶: $DBUser"
Write-Host " 容器名稱: $ContainerName"
Write-Host ""
$continue = Read-Host "是否繼續? (y/N)"
if ($continue -ne 'y' -and $continue -ne 'Y') {
Write-Host "已取消" -ForegroundColor Yellow
exit 0
}
Write-Host ""
# === 步驟 1: 創建用戶 ===
Write-Host "步驟 1/4: 創建資料庫用戶..." -ForegroundColor Green
$createUserSQL = @"
DO `$`$
BEGIN
IF NOT EXISTS (SELECT FROM pg_user WHERE usename = '$DBUser') THEN
CREATE USER $DBUser WITH PASSWORD '$DBPassword';
RAISE NOTICE 'User $DBUser created';
ELSE
RAISE NOTICE 'User $DBUser already exists';
END IF;
END
`$`$;
"@
$createUserCmd = "echo `"$createUserSQL`" | docker exec -i $ContainerName psql -U postgres"
try {
ssh ubuntu@$DBHost $createUserCmd
Write-Host "✓ 用戶創建完成" -ForegroundColor Green
} catch {
Write-Host "✗ 用戶創建失敗: $_" -ForegroundColor Red
exit 1
}
Write-Host ""
# === 步驟 2: 創建資料庫 ===
Write-Host "步驟 2/4: 創建資料庫..." -ForegroundColor Green
$createDbCmd = "docker exec $ContainerName psql -U postgres -c `"CREATE DATABASE $DBName OWNER $DBUser;`" 2>&1 || echo 'Database may already exist'"
try {
ssh ubuntu@$DBHost $createDbCmd
Write-Host "✓ 資料庫創建完成" -ForegroundColor Green
} catch {
Write-Host "⚠ 資料庫可能已存在" -ForegroundColor Yellow
}
Write-Host ""
# === 步驟 3: 授予權限 ===
Write-Host "步驟 3/4: 授予權限..." -ForegroundColor Green
$grantSQL = @"
GRANT ALL PRIVILEGES ON DATABASE $DBName TO $DBUser;
GRANT ALL PRIVILEGES ON SCHEMA public TO $DBUser;
ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT ALL ON TABLES TO $DBUser;
ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT ALL ON SEQUENCES TO $DBUser;
"@
$grantCmd = "echo `"$grantSQL`" | docker exec -i $ContainerName psql -U postgres -d $DBName"
try {
ssh ubuntu@$DBHost $grantCmd
Write-Host "✓ 權限授予完成" -ForegroundColor Green
} catch {
Write-Host "✗ 權限授予失敗: $_" -ForegroundColor Red
}
Write-Host ""
# === 步驟 4: 執行 Schema ===
Write-Host "步驟 4/4: 執行資料庫 Schema..." -ForegroundColor Green
$schemaFile = Join-Path $PSScriptRoot "init-db.sql"
if (Test-Path $schemaFile) {
Write-Host " 讀取 Schema 檔案..." -ForegroundColor Gray
# 上傳 SQL 檔案到遠端
scp $schemaFile ubuntu@${DBHost}:/tmp/hr-portal-schema.sql
# 執行 SQL
$execSchemaCmd = "docker exec -i $ContainerName psql -U $DBUser -d $DBName < /tmp/hr-portal-schema.sql"
try {
ssh ubuntu@$DBHost $execSchemaCmd
Write-Host "✓ Schema 執行完成" -ForegroundColor Green
# 清理臨時檔案
ssh ubuntu@$DBHost "rm /tmp/hr-portal-schema.sql"
} catch {
Write-Host "✗ Schema 執行失敗: $_" -ForegroundColor Red
exit 1
}
} else {
Write-Host "✗ 找不到 Schema 檔案: $schemaFile" -ForegroundColor Red
exit 1
}
Write-Host ""
# === 驗證 ===
Write-Host "驗證資料庫設定..." -ForegroundColor Yellow
$verifyCmd = "docker exec $ContainerName psql -U $DBUser -d $DBName -c '\dt' 2>&1"
try {
$tables = ssh ubuntu@$DBHost $verifyCmd
if ($tables -match "business_units|employees|email_accounts") {
Write-Host "✓ 資料表已成功建立" -ForegroundColor Green
Write-Host ""
Write-Host "找到的資料表:" -ForegroundColor Cyan
Write-Host $tables -ForegroundColor Gray
} else {
Write-Host "⚠ 資料表可能未正確建立" -ForegroundColor Yellow
}
} catch {
Write-Host "⚠ 無法驗證資料表" -ForegroundColor Yellow
}
Write-Host ""
Write-Host "=== 設定完成! ===" -ForegroundColor Green
Write-Host ""
# 顯示連接字串
Write-Host "資料庫連接資訊:" -ForegroundColor Cyan
Write-Host " Host: $DBHost"
Write-Host " Port: 5432"
Write-Host " Database: $DBName"
Write-Host " User: $DBUser"
Write-Host " Password: $DBPassword"
Write-Host ""
$databaseUrl = "postgresql://${DBUser}:${DBPassword}@${DBHost}:5432/${DBName}"
Write-Host "DATABASE_URL:" -ForegroundColor Yellow
Write-Host " $databaseUrl" -ForegroundColor White
Write-Host ""
Write-Host "下一步:" -ForegroundColor Green
Write-Host " 1. 複製上面的 DATABASE_URL"
Write-Host " 2. 編輯 backend\.env"
Write-Host " 3. 設定 DATABASE_URL 環境變數"
Write-Host " 4. 啟動後端: uvicorn app.main:app --reload"
Write-Host ""