261 lines
8.9 KiB
Python
261 lines
8.9 KiB
Python
"""
|
|
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/<task_id>', 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 |