Files
awoooi/apps/api/src/services/notifications/base.py
OG T 749b8bc554 fix(api): 修復時區 import 排序與未使用變數 lint 錯誤
- 修正 import 順序 (standard → third-party → local)
- 修復 datetime/timedelta 未定義錯誤
- 移除未使用的 imports

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-03-25 09:26:58 +08:00

166 lines
4.0 KiB
Python

"""
Notification Provider Base Interface
=====================================
Phase 6: leWOOOgo Output Plugins
設計原則:
1. 抽象介面 - 所有 Provider 必須實作 send()
2. 統一訊息格式 - NotificationMessage
3. 結果追蹤 - NotificationResult
"""
from abc import ABC, abstractmethod
from dataclasses import dataclass, field
from datetime import datetime
from enum import Enum
from typing import Any
from src.utils.timezone import now_taipei
class NotificationStatus(str, Enum):
"""通知狀態"""
SUCCESS = "success"
FAILED = "failed"
SKIPPED = "skipped"
class ExecutionStatus(str, Enum):
"""執行狀態"""
SUCCESS = "success"
FAILED = "failed"
DRY_RUN_BLOCKED = "dry_run_blocked"
PENDING = "pending"
@dataclass
class NotificationMessage:
"""
通知訊息統一格式
所有 Provider 都從這個格式轉換成各自的 API 格式
"""
# 執行結果
execution_status: ExecutionStatus
# 核心資訊
action_title: str
action_description: str
approval_id: str
# 簽核資訊
signers: list[dict[str, str]] = field(default_factory=list) # [{"name": "CTO", "comment": "..."}]
required_signatures: int = 1
# 影響範圍 (Blast Radius)
affected_pods: int = 0
estimated_downtime: str = "N/A"
related_services: list[str] = field(default_factory=list)
data_impact: str = "none"
# 執行細節
namespace: str = "default"
operation_type: str = "unknown"
duration_ms: int | None = None
error_message: str | None = None
# AI 分析
risk_level: str = "medium"
ai_provider: str = "unknown"
confidence: float | None = None
# 時間戳
timestamp: datetime = field(default_factory=lambda: now_taipei())
@property
def status_emoji(self) -> str:
"""狀態 Emoji"""
if self.execution_status == ExecutionStatus.SUCCESS:
return ""
elif self.execution_status == ExecutionStatus.FAILED:
return ""
elif self.execution_status == ExecutionStatus.DRY_RUN_BLOCKED:
return "🛡️"
return ""
@property
def status_text(self) -> str:
"""狀態文字"""
if self.execution_status == ExecutionStatus.SUCCESS:
return "任務執行成功"
elif self.execution_status == ExecutionStatus.FAILED:
return "執行失敗"
elif self.execution_status == ExecutionStatus.DRY_RUN_BLOCKED:
return "Dry-Run 攔截"
return "等待中"
@property
def risk_emoji(self) -> str:
"""風險等級 Emoji"""
if self.risk_level == "critical":
return "🔴"
elif self.risk_level == "medium":
return "🟡"
return "🟢"
@property
def signers_display(self) -> str:
"""簽核者顯示文字"""
if not self.signers:
return ""
return ", ".join([s.get("name", "Unknown") for s in self.signers])
@dataclass
class NotificationResult:
"""通知發送結果"""
status: NotificationStatus
provider: str
message: str
response_data: dict[str, Any] | None = None
error: str | None = None
timestamp: datetime = field(default_factory=lambda: now_taipei())
class NotificationProvider(ABC):
"""
通知提供者抽象介面
所有 Output Plugin 必須實作此介面
"""
@property
@abstractmethod
def name(self) -> str:
"""Provider 名稱"""
pass
@property
@abstractmethod
def enabled(self) -> bool:
"""是否啟用"""
pass
@abstractmethod
async def send(self, message: NotificationMessage) -> NotificationResult:
"""
發送通知
Args:
message: 統一格式的通知訊息
Returns:
NotificationResult: 發送結果
"""
pass
@abstractmethod
async def test_connection(self) -> bool:
"""
測試連線
Returns:
bool: 是否連線成功
"""
pass