""" Scheduler API Routes 處理排程任務的管理和監控功能 """ from flask import Blueprint, request, jsonify, current_app from flask_jwt_extended import jwt_required, get_jwt_identity from datetime import datetime, date, timedelta from sqlalchemy import and_, or_ from models import ( db, TodoItem, TodoItemResponsible, TodoItemFollower, TodoUserPref, TodoAuditLog ) from utils.logger import get_logger from utils.email_service import EmailService from utils.notification_service import NotificationService from tasks_simple import send_daily_reminders, send_weekly_summary, cleanup_old_logs import json scheduler_bp = Blueprint('scheduler', __name__) logger = get_logger(__name__) @scheduler_bp.route('/trigger-daily-reminders', methods=['POST']) @jwt_required() def trigger_daily_reminders(): """手動觸發每日提醒(管理員功能)""" try: identity = get_jwt_identity() # TODO: 實作管理員權限檢查 # 這裡應該檢查用戶是否為管理員 # 直接執行任務 result = send_daily_reminders() # 記錄稽核日誌 audit = TodoAuditLog( actor_ad=identity, todo_id=None, action='MANUAL_REMINDER', detail={ 'result': result, 'triggered_by': identity } ) db.session.add(audit) db.session.commit() logger.info(f"Daily reminders executed manually by {identity}") return jsonify({ 'message': '每日提醒任務已執行', 'result': result }), 200 except Exception as e: logger.error(f"Error triggering daily reminders: {str(e)}") return jsonify({'error': '觸發每日提醒失敗'}), 500 @scheduler_bp.route('/trigger-weekly-summary', methods=['POST']) @jwt_required() def trigger_weekly_summary(): """手動觸發週報發送(管理員功能)""" try: identity = get_jwt_identity() # TODO: 實作管理員權限檢查 # 直接執行任務 result = send_weekly_summary() # 記錄稽核日誌 audit = TodoAuditLog( actor_ad=identity, todo_id=None, action='MANUAL_SUMMARY', detail={ 'result': result, 'triggered_by': identity } ) db.session.add(audit) db.session.commit() logger.info(f"Weekly summary executed manually by {identity}") return jsonify({ 'message': '週報發送任務已執行', 'result': result }), 200 except Exception as e: logger.error(f"Error triggering weekly summary: {str(e)}") return jsonify({'error': '觸發週報發送失敗'}), 500 @scheduler_bp.route('/task-status/', methods=['GET']) @jwt_required() def get_task_status(task_id): """取得任務狀態(簡化版本)""" try: # 在簡化版本中,任務是同步執行的,所以狀態總是 completed return jsonify({ 'task_id': task_id, 'status': 'completed', 'message': '任務已同步執行完成' }), 200 except Exception as e: logger.error(f"Error getting task status: {str(e)}") return jsonify({'error': '取得任務狀態失敗'}), 500 @scheduler_bp.route('/scheduled-jobs', methods=['GET']) @jwt_required() def get_scheduled_jobs(): """取得排程任務列表和狀態""" try: # 這裡可以返回 Celery Beat 的排程資訊 # 簡化版本,返回配置的排程任務 jobs = [ { 'name': 'daily-reminders', 'description': '每日提醒郵件', 'schedule': '每日早上9點', 'status': 'active', 'last_run': None # TODO: 從 Celery 取得實際執行時間 }, { 'name': 'weekly-summary', 'description': '每週摘要報告', 'schedule': '每週一早上9點', 'status': 'active', 'last_run': None # TODO: 從 Celery 取得實際執行時間 }, { 'name': 'cleanup-logs', 'description': '清理舊日誌', 'schedule': '每週執行一次', 'status': 'active', 'last_run': None # TODO: 從 Celery 取得實際執行時間 } ] return jsonify({'jobs': jobs}), 200 except Exception as e: logger.error(f"Error getting scheduled jobs: {str(e)}") return jsonify({'error': '取得排程任務列表失敗'}), 500 @scheduler_bp.route('/statistics', methods=['GET']) @jwt_required() def get_scheduler_statistics(): """取得排程系統統計資訊""" try: identity = get_jwt_identity() # 統計最近一週的自動化任務執行記錄 week_ago = datetime.utcnow() - timedelta(days=7) auto_tasks = TodoAuditLog.query.filter( and_( TodoAuditLog.actor_ad == 'system', TodoAuditLog.created_at >= week_ago, TodoAuditLog.action.in_(['DAILY_REMINDER', 'WEEKLY_SUMMARY']) ) ).all() # 統計手動觸發的任務 manual_tasks = TodoAuditLog.query.filter( and_( TodoAuditLog.created_at >= week_ago, TodoAuditLog.action.in_(['MANUAL_REMINDER', 'MANUAL_SUMMARY']) ) ).all() # 統計郵件發送情況 email_stats = {} for task in auto_tasks: if task.detail: task_type = task.action.lower() if 'emails_sent' in task.detail: if task_type not in email_stats: email_stats[task_type] = {'count': 0, 'emails': 0} email_stats[task_type]['count'] += 1 email_stats[task_type]['emails'] += task.detail['emails_sent'] statistics = { 'recent_activity': { 'auto_tasks_count': len(auto_tasks), 'manual_tasks_count': len(manual_tasks), 'email_stats': email_stats }, 'system_health': { 'celery_status': 'running', # TODO: 實際檢查 Celery 狀態 'redis_status': 'connected', # TODO: 實際檢查 Redis 狀態 'last_daily_reminder': None, # TODO: 從記錄中取得 'last_weekly_summary': None # TODO: 從記錄中取得 } } return jsonify(statistics), 200 except Exception as e: logger.error(f"Error getting scheduler statistics: {str(e)}") return jsonify({'error': '取得排程統計資訊失敗'}), 500 @scheduler_bp.route('/preview-reminders', methods=['GET']) @jwt_required() def preview_reminders(): """預覽即將發送的提醒郵件""" try: today = date.today() tomorrow = today + timedelta(days=1) # 查找明日到期的待辦事項 due_tomorrow = db.session.query(TodoItem).filter( and_( TodoItem.due_date == tomorrow, TodoItem.status != 'DONE' ) ).all() # 查找已逾期的待辦事項 overdue = db.session.query(TodoItem).filter( and_( TodoItem.due_date < today, TodoItem.status != 'DONE' ) ).all() # 統計會收到提醒的使用者 notification_service = NotificationService() due_tomorrow_recipients = set() overdue_recipients = set() for todo in due_tomorrow: recipients = notification_service.get_notification_recipients(todo) due_tomorrow_recipients.update(recipients) for todo in overdue: recipients = notification_service.get_notification_recipients(todo) overdue_recipients.update(recipients) preview = { 'due_tomorrow': { 'todos_count': len(due_tomorrow), 'recipients_count': len(due_tomorrow_recipients), 'todos': [todo.to_dict() for todo in due_tomorrow[:5]] # 只顯示前5個 }, 'overdue': { 'todos_count': len(overdue), 'recipients_count': len(overdue_recipients), 'todos': [todo.to_dict() for todo in overdue[:5]] # 只顯示前5個 }, 'total_emails': len(due_tomorrow_recipients) + len(overdue_recipients) } return jsonify(preview), 200 except Exception as e: logger.error(f"Error previewing reminders: {str(e)}") return jsonify({'error': '預覽提醒郵件失敗'}), 500