#!/usr/bin/env python3 # -*- coding: utf-8 -*- """ 通知模板服務 管理通知模板的 CRUD 操作和模板渲染 """ import os from sqlalchemy import create_engine from database.manager import DatabaseManager from database.manager import ensure_metadata_initialized 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: ensure_metadata_initialized(_engine, use_postgres_lock=str(_engine.url).startswith('postgresql')) 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': '定期報告'} ]