Some checks failed
CD Pipeline / deploy (push) Failing after 59s
- 建立 Gitea Actions CD pipeline (.gitea/workflows/cd.yaml) - 部署模式: rsync Python 檔案至 188 → docker restart (volume mount) - Dockerfile/requirements 變動時自動重建 Docker image - 部署通知: Telegram (開始/成功/失敗) - 健康檢查: https://mo.wooo.work/health (最多 5 次重試) - 同步最新 CLAUDE.md / ADR-008 / memory (2026-04-19) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
167 lines
5.7 KiB
Python
167 lines
5.7 KiB
Python
#!/usr/bin/env python3
|
|
# -*- coding: utf-8 -*-
|
|
"""
|
|
通知模板服務
|
|
管理通知模板的 CRUD 操作和模板渲染
|
|
"""
|
|
|
|
import os
|
|
from sqlalchemy import create_engine
|
|
|
|
from database.manager import DatabaseManager
|
|
from database.notification_models import NotificationTemplate, DEFAULT_TEMPLATES, Base
|
|
from services.logger_manager import SystemLogger
|
|
|
|
sys_log = SystemLogger("NotificationService").get_logger()
|
|
|
|
# 初始化資料庫連線
|
|
try:
|
|
from config import DATABASE_PATH as CONFIG_DATABASE_PATH
|
|
_engine = create_engine(CONFIG_DATABASE_PATH)
|
|
except ImportError:
|
|
DATABASE_PATH = os.getenv('DATABASE_PATH', 'data/momo_database.db')
|
|
if DATABASE_PATH.startswith('postgresql://') or DATABASE_PATH.startswith('sqlite://'):
|
|
_engine = create_engine(DATABASE_PATH)
|
|
else:
|
|
_engine = create_engine(f'sqlite:///{DATABASE_PATH}')
|
|
|
|
|
|
def _init_notification_tables():
|
|
"""初始化通知模板資料表"""
|
|
try:
|
|
Base.metadata.create_all(_engine)
|
|
sys_log.info("[Notification] 通知模板表已初始化")
|
|
except Exception as e:
|
|
sys_log.error(f"[Notification] 初始化表失敗: {e}")
|
|
|
|
|
|
# 模組載入時自動初始化表
|
|
_init_notification_tables()
|
|
|
|
|
|
class NotificationTemplateService:
|
|
"""通知模板服務"""
|
|
|
|
@staticmethod
|
|
def init_default_templates():
|
|
"""初始化預設模板"""
|
|
db = DatabaseManager()
|
|
try:
|
|
with db.get_session() as session:
|
|
for template_data in DEFAULT_TEMPLATES:
|
|
existing = session.query(NotificationTemplate).filter_by(
|
|
code=template_data['code']
|
|
).first()
|
|
|
|
if not existing:
|
|
template = NotificationTemplate(**template_data)
|
|
session.add(template)
|
|
sys_log.info(f"[Notification] 新增預設模板: {template_data['code']}")
|
|
|
|
session.commit()
|
|
sys_log.info("[Notification] 預設模板初始化完成")
|
|
except Exception as e:
|
|
sys_log.error(f"[Notification] 初始化模板失敗: {e}")
|
|
|
|
@staticmethod
|
|
def get_all_templates(category=None):
|
|
"""取得所有模板"""
|
|
db = DatabaseManager()
|
|
try:
|
|
with db.get_session() as session:
|
|
query = session.query(NotificationTemplate)
|
|
if category:
|
|
query = query.filter_by(category=category)
|
|
templates = query.order_by(NotificationTemplate.category, NotificationTemplate.code).all()
|
|
return [t.to_dict() for t in templates]
|
|
except Exception as e:
|
|
sys_log.error(f"[Notification] 取得模板列表失敗: {e}")
|
|
return []
|
|
|
|
@staticmethod
|
|
def get_template_by_code(code):
|
|
"""根據代碼取得模板"""
|
|
db = DatabaseManager()
|
|
try:
|
|
with db.get_session() as session:
|
|
template = session.query(NotificationTemplate).filter_by(code=code).first()
|
|
if template:
|
|
return template.to_dict()
|
|
return None
|
|
except Exception as e:
|
|
sys_log.error(f"[Notification] 取得模板失敗: {e}")
|
|
return None
|
|
|
|
@staticmethod
|
|
def update_template(code, data):
|
|
"""更新模板"""
|
|
db = DatabaseManager()
|
|
try:
|
|
with db.get_session() as session:
|
|
template = session.query(NotificationTemplate).filter_by(code=code).first()
|
|
if not template:
|
|
return {'success': False, 'error': '模板不存在'}
|
|
|
|
# 更新允許的欄位
|
|
allowed_fields = ['name', 'title', 'body', 'emoji_prefix', 'channel', 'is_active']
|
|
for field in allowed_fields:
|
|
if field in data:
|
|
setattr(template, field, data[field])
|
|
|
|
session.commit()
|
|
sys_log.info(f"[Notification] 更新模板: {code}")
|
|
return {'success': True, 'template': template.to_dict()}
|
|
except Exception as e:
|
|
sys_log.error(f"[Notification] 更新模板失敗: {e}")
|
|
return {'success': False, 'error': str(e)}
|
|
|
|
@staticmethod
|
|
def render_template(code, variables=None):
|
|
"""渲染模板"""
|
|
if variables is None:
|
|
variables = {}
|
|
|
|
template = NotificationTemplateService.get_template_by_code(code)
|
|
if not template:
|
|
return None
|
|
|
|
try:
|
|
# 組合完整訊息
|
|
emoji = template.get('emoji_prefix', '')
|
|
title = template.get('title', '')
|
|
body = template.get('body', '')
|
|
|
|
# 替換變數
|
|
if title:
|
|
title = title.format(**variables)
|
|
if body:
|
|
body = body.format(**variables)
|
|
|
|
# 組合 Markdown 格式
|
|
message = f"{emoji} *{title}*\n\n{body}" if title else f"{emoji}\n{body}"
|
|
|
|
return {
|
|
'success': True,
|
|
'message': message,
|
|
'channel': template.get('channel', 'telegram')
|
|
}
|
|
except KeyError as e:
|
|
sys_log.warning(f"[Notification] 模板變數缺失: {e}")
|
|
return {
|
|
'success': False,
|
|
'error': f'缺少變數: {e}',
|
|
'template': template
|
|
}
|
|
except Exception as e:
|
|
sys_log.error(f"[Notification] 渲染模板失敗: {e}")
|
|
return {'success': False, 'error': str(e)}
|
|
|
|
@staticmethod
|
|
def get_categories():
|
|
"""取得所有分類"""
|
|
return [
|
|
{'code': 'system', 'name': '系統監控'},
|
|
{'code': 'business', 'name': '業務通知'},
|
|
{'code': 'report', 'name': '定期報告'}
|
|
]
|