Files
hr-portal/.gitea/workflows/ci-cd.yml
Porsche Chen 360533393f 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>
2026-02-23 20:12:43 +08:00

295 lines
8.5 KiB
YAML

name: HR Portal CI/CD
on:
push:
branches:
- main # 生產環境
- dev # 測試環境
pull_request:
branches:
- main
- dev
env:
REGISTRY: git.lab.taipei
IMAGE_NAME_BACKEND: porscheworld/hr-portal-backend
IMAGE_NAME_FRONTEND: porscheworld/hr-portal-frontend
jobs:
# ==============================================
# 測試階段 - 後端
# ==============================================
test-backend:
name: Test Backend (FastAPI)
runs-on: ubuntu-latest
services:
postgres:
image: postgres:16-alpine
env:
POSTGRES_DB: hr_portal_test
POSTGRES_USER: test_user
POSTGRES_PASSWORD: test_password
ports:
- 5432:5432
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
steps:
- name: Checkout Code
uses: actions/checkout@v4
- name: Set up Python 3.11
uses: actions/setup-python@v4
with:
python-version: '3.11'
cache: 'pip'
- name: Install Dependencies
working-directory: ./backend
run: |
pip install --upgrade pip
pip install -r requirements.txt
- name: Run Tests with Coverage
working-directory: ./backend
env:
DATABASE_HOST: localhost
DATABASE_PORT: 5432
DATABASE_NAME: hr_portal_test
DATABASE_USER: test_user
DATABASE_PASSWORD: test_password
run: |
pytest tests/ --cov=app --cov-report=term-missing --cov-report=xml
- name: Upload Coverage to Codecov
uses: codecov/codecov-action@v3
with:
file: ./backend/coverage.xml
flags: backend
name: backend-coverage
# ==============================================
# 測試階段 - 前端
# ==============================================
test-frontend:
name: Test Frontend (Next.js)
runs-on: ubuntu-latest
steps:
- name: Checkout Code
uses: actions/checkout@v4
- name: Set up Node.js 18
uses: actions/setup-node@v4
with:
node-version: '18'
cache: 'npm'
cache-dependency-path: ./frontend/package-lock.json
- name: Install Dependencies
working-directory: ./frontend
run: npm ci
- name: Run Lint
working-directory: ./frontend
run: npm run lint
- name: Run Type Check
working-directory: ./frontend
run: npx tsc --noEmit
- name: Build
working-directory: ./frontend
run: npm run build
# ==============================================
# 建置與推送映像 - 後端
# ==============================================
build-backend:
name: Build Backend Image
needs: test-backend
runs-on: ubuntu-latest
if: github.event_name == 'push'
steps:
- name: Checkout Code
uses: actions/checkout@v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Login to Gitea Container Registry
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ secrets.GITEA_USERNAME }}
password: ${{ secrets.GITEA_TOKEN }}
- name: Extract metadata
id: meta
uses: docker/metadata-action@v5
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME_BACKEND }}
tags: |
type=ref,event=branch
type=sha,prefix={{branch}}-
type=raw,value=latest,enable={{is_default_branch}}
- name: Build and Push
uses: docker/build-push-action@v5
with:
context: ./backend
file: ./backend/Dockerfile
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=registry,ref=${{ env.REGISTRY }}/${{ env.IMAGE_NAME_BACKEND }}:buildcache
cache-to: type=registry,ref=${{ env.REGISTRY }}/${{ env.IMAGE_NAME_BACKEND }}:buildcache,mode=max
# ==============================================
# 建置與推送映像 - 前端
# ==============================================
build-frontend:
name: Build Frontend Image
needs: test-frontend
runs-on: ubuntu-latest
if: github.event_name == 'push'
steps:
- name: Checkout Code
uses: actions/checkout@v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Login to Gitea Container Registry
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ secrets.GITEA_USERNAME }}
password: ${{ secrets.GITEA_TOKEN }}
- name: Extract metadata
id: meta
uses: docker/metadata-action@v5
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME_FRONTEND }}
tags: |
type=ref,event=branch
type=sha,prefix={{branch}}-
type=raw,value=latest,enable={{is_default_branch}}
- name: Build and Push
uses: docker/build-push-action@v5
with:
context: ./frontend
file: ./frontend/Dockerfile
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=registry,ref=${{ env.REGISTRY }}/${{ env.IMAGE_NAME_FRONTEND }}:buildcache
cache-to: type=registry,ref=${{ env.REGISTRY }}/${{ env.IMAGE_NAME_FRONTEND }}:buildcache,mode=max
# ==============================================
# 部署到測試環境 (dev 分支)
# ==============================================
deploy-testing:
name: Deploy to Testing Environment
needs: [build-backend, build-frontend]
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/dev'
environment:
name: testing
url: https://test.hr.ease.taipei
steps:
- name: Checkout Code
uses: actions/checkout@v4
- name: Deploy to Testing Server
uses: appleboy/ssh-action@v1.0.0
with:
host: ${{ secrets.DEPLOY_HOST }}
username: ${{ secrets.DEPLOY_USER }}
key: ${{ secrets.DEPLOY_SSH_KEY }}
port: 22
script: |
cd /opt/deployments/hr-portal-test
# 拉取最新映像
docker-compose -f docker-compose.prod.yml pull
# 重啟服務
docker-compose -f docker-compose.prod.yml up -d
# 清理舊映像
docker image prune -f
# 檢查服務狀態
docker-compose -f docker-compose.prod.yml ps
# ==============================================
# 部署到生產環境 (main 分支)
# ==============================================
deploy-production:
name: Deploy to Production Environment
needs: [build-backend, build-frontend]
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/main'
environment:
name: production
url: https://hr.ease.taipei
steps:
- name: Checkout Code
uses: actions/checkout@v4
- name: Deploy to Production Server
uses: appleboy/ssh-action@v1.0.0
with:
host: ${{ secrets.DEPLOY_HOST }}
username: ${{ secrets.DEPLOY_USER }}
key: ${{ secrets.DEPLOY_SSH_KEY }}
port: 22
script: |
cd /opt/deployments/hr-portal-prod
# 拉取最新映像
docker-compose -f docker-compose.prod.yml pull
# 執行資料庫備份
docker exec hr-portal-postgres-prod pg_dump -U hr_admin hr_portal | gzip > backup-$(date +%Y%m%d-%H%M%S).sql.gz
# 滾動更新 (零停機部署)
docker-compose -f docker-compose.prod.yml up -d --no-deps --build backend
sleep 10
docker-compose -f docker-compose.prod.yml up -d --no-deps --build frontend
# 清理舊映像
docker image prune -f
# 檢查服務狀態
docker-compose -f docker-compose.prod.yml ps
# 健康檢查
curl -f https://hr-api.ease.taipei/health || exit 1
# ==============================================
# 通知
# ==============================================
notify:
name: Send Notification
needs: [deploy-testing, deploy-production]
runs-on: ubuntu-latest
if: always()
steps:
- name: Send Notification
run: |
echo "Deployment completed for branch: ${{ github.ref_name }}"
# TODO: 整合 Email 或 Slack 通知