331 lines
9.6 KiB
Python
331 lines
9.6 KiB
Python
#!/usr/bin/env python3
|
||
# -*- coding: utf-8 -*-
|
||
"""
|
||
通知系統 API 路由
|
||
|
||
Author: PANJIT IT Team
|
||
Created: 2024-01-28
|
||
Modified: 2024-01-28
|
||
"""
|
||
|
||
from flask import Blueprint, jsonify, request, g
|
||
from app.utils.decorators import jwt_login_required
|
||
from sqlalchemy import desc, and_, or_
|
||
from datetime import datetime, timedelta
|
||
from app import db
|
||
from app.models import Notification, NotificationType, User
|
||
from app.utils.response import create_taiwan_response
|
||
# 移除不需要的導入
|
||
|
||
# 建立藍圖
|
||
notification_bp = Blueprint('notification', __name__, url_prefix='/notifications')
|
||
|
||
|
||
@notification_bp.route('', methods=['GET'])
|
||
@jwt_login_required
|
||
def get_notifications():
|
||
"""獲取當前用戶的通知列表"""
|
||
try:
|
||
# 獲取當前用戶
|
||
current_user_id = g.current_user_id
|
||
|
||
# 獲取查詢參數
|
||
page = request.args.get('page', 1, type=int)
|
||
per_page = min(request.args.get('per_page', 20, type=int), 100)
|
||
status_filter = request.args.get('status', 'all')
|
||
type_filter = request.args.get('type', None)
|
||
|
||
# 建構查詢
|
||
query = Notification.query.filter_by(user_id=current_user_id)
|
||
|
||
# 只顯示未過期的通知
|
||
query = query.filter(or_(
|
||
Notification.expires_at.is_(None),
|
||
Notification.expires_at > datetime.now()
|
||
))
|
||
|
||
# 過濾狀態
|
||
if status_filter == 'unread':
|
||
query = query.filter_by(is_read=False)
|
||
elif status_filter == 'read':
|
||
query = query.filter_by(is_read=True)
|
||
|
||
# 過濾類型
|
||
if type_filter:
|
||
query = query.filter_by(type=type_filter)
|
||
|
||
# 排序 - 未讀在前,然後按時間排序
|
||
query = query.order_by(Notification.is_read.asc(), desc(Notification.created_at))
|
||
|
||
# 分頁
|
||
paginated = query.paginate(
|
||
page=page, per_page=per_page, error_out=False
|
||
)
|
||
|
||
# 獲取未讀數量
|
||
unread_count = Notification.query.filter_by(
|
||
user_id=current_user_id,
|
||
is_read=False
|
||
).filter(or_(
|
||
Notification.expires_at.is_(None),
|
||
Notification.expires_at > datetime.now()
|
||
)).count()
|
||
|
||
return jsonify(create_taiwan_response(
|
||
success=True,
|
||
data={
|
||
'notifications': [n.to_dict() for n in paginated.items],
|
||
'pagination': {
|
||
'total': paginated.total,
|
||
'page': page,
|
||
'per_page': per_page,
|
||
'pages': paginated.pages
|
||
},
|
||
'unread_count': unread_count
|
||
},
|
||
message='獲取通知列表成功'
|
||
))
|
||
|
||
except Exception as e:
|
||
return jsonify(create_taiwan_response(
|
||
success=False,
|
||
error=f'獲取通知失敗:{str(e)}'
|
||
)), 500
|
||
|
||
|
||
@notification_bp.route('/<notification_id>', methods=['GET'])
|
||
@jwt_login_required
|
||
def get_notification(notification_id):
|
||
"""獲取單個通知詳情"""
|
||
try:
|
||
current_user_id = g.current_user_id
|
||
|
||
# 查找通知
|
||
notification = Notification.query.filter_by(
|
||
notification_uuid=notification_id,
|
||
user_id=current_user_id
|
||
).first()
|
||
|
||
if not notification:
|
||
return jsonify(create_taiwan_response(
|
||
success=False,
|
||
error='通知不存在'
|
||
)), 404
|
||
|
||
# 自動標記為已讀
|
||
if not notification.is_read:
|
||
notification.mark_as_read()
|
||
db.session.commit()
|
||
|
||
return jsonify(create_taiwan_response(
|
||
success=True,
|
||
data=notification.to_dict(),
|
||
message='獲取通知成功'
|
||
))
|
||
|
||
except Exception as e:
|
||
return jsonify(create_taiwan_response(
|
||
success=False,
|
||
error=f'獲取通知失敗:{str(e)}'
|
||
)), 500
|
||
|
||
|
||
@notification_bp.route('/<notification_id>/read', methods=['POST'])
|
||
@jwt_login_required
|
||
def mark_notification_read(notification_id):
|
||
"""標記通知為已讀"""
|
||
try:
|
||
current_user_id = g.current_user_id
|
||
|
||
# 查找通知
|
||
notification = Notification.query.filter_by(
|
||
notification_uuid=notification_id,
|
||
user_id=current_user_id
|
||
).first()
|
||
|
||
if not notification:
|
||
return jsonify(create_taiwan_response(
|
||
success=False,
|
||
error='通知不存在'
|
||
)), 404
|
||
|
||
# 標記為已讀
|
||
notification.mark_as_read()
|
||
db.session.commit()
|
||
|
||
return jsonify(create_taiwan_response(
|
||
success=True,
|
||
message='標記已讀成功'
|
||
))
|
||
|
||
except Exception as e:
|
||
return jsonify(create_taiwan_response(
|
||
success=False,
|
||
error=f'標記已讀失敗:{str(e)}'
|
||
)), 500
|
||
|
||
|
||
@notification_bp.route('/read-all', methods=['POST'])
|
||
@jwt_login_required
|
||
def mark_all_read():
|
||
"""標記所有通知為已讀"""
|
||
try:
|
||
current_user_id = g.current_user_id
|
||
|
||
# 取得所有未讀通知
|
||
unread_notifications = Notification.query.filter_by(
|
||
user_id=current_user_id,
|
||
is_read=False
|
||
).filter(or_(
|
||
Notification.expires_at.is_(None),
|
||
Notification.expires_at > datetime.now()
|
||
)).all()
|
||
|
||
# 標記為已讀
|
||
for notification in unread_notifications:
|
||
notification.mark_as_read()
|
||
|
||
db.session.commit()
|
||
|
||
return jsonify(create_taiwan_response(
|
||
success=True,
|
||
data={'marked_count': len(unread_notifications)},
|
||
message=f'已標記 {len(unread_notifications)} 個通知為已讀'
|
||
))
|
||
|
||
except Exception as e:
|
||
return jsonify(create_taiwan_response(
|
||
success=False,
|
||
error=f'標記全部已讀失敗:{str(e)}'
|
||
)), 500
|
||
|
||
|
||
@notification_bp.route('/<notification_id>', methods=['DELETE'])
|
||
@jwt_login_required
|
||
def delete_notification(notification_id):
|
||
"""刪除通知"""
|
||
try:
|
||
current_user_id = g.current_user_id
|
||
|
||
# 查找通知
|
||
notification = Notification.query.filter_by(
|
||
notification_uuid=notification_id,
|
||
user_id=current_user_id
|
||
).first()
|
||
|
||
if not notification:
|
||
return jsonify(create_taiwan_response(
|
||
success=False,
|
||
error='通知不存在'
|
||
)), 404
|
||
|
||
# 刪除通知
|
||
db.session.delete(notification)
|
||
db.session.commit()
|
||
|
||
return jsonify(create_taiwan_response(
|
||
success=True,
|
||
message='刪除通知成功'
|
||
))
|
||
|
||
except Exception as e:
|
||
db.session.rollback()
|
||
return jsonify(create_taiwan_response(
|
||
success=False,
|
||
error=f'刪除通知失敗:{str(e)}'
|
||
)), 500
|
||
|
||
|
||
@notification_bp.route('/clear', methods=['POST'])
|
||
@jwt_login_required
|
||
def clear_read_notifications():
|
||
"""清空所有已讀通知"""
|
||
try:
|
||
current_user_id = g.current_user_id
|
||
|
||
# 刪除所有已讀通知
|
||
deleted_count = Notification.query.filter_by(
|
||
user_id=current_user_id,
|
||
is_read=True
|
||
).delete()
|
||
|
||
db.session.commit()
|
||
|
||
return jsonify(create_taiwan_response(
|
||
success=True,
|
||
data={'deleted_count': deleted_count},
|
||
message=f'已清除 {deleted_count} 個已讀通知'
|
||
))
|
||
|
||
except Exception as e:
|
||
db.session.rollback()
|
||
return jsonify(create_taiwan_response(
|
||
success=False,
|
||
error=f'清除通知失敗:{str(e)}'
|
||
)), 500
|
||
|
||
|
||
@notification_bp.route('/test', methods=['POST'])
|
||
@jwt_login_required
|
||
def create_test_notification():
|
||
"""創建測試通知(開發用)"""
|
||
try:
|
||
current_user_id = g.current_user_id
|
||
|
||
# 創建測試通知
|
||
test_notification = create_notification(
|
||
user_id=current_user_id,
|
||
title="測試通知",
|
||
message=f"這是一個測試通知,創建於 {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}",
|
||
notification_type=NotificationType.INFO
|
||
)
|
||
|
||
return jsonify(create_taiwan_response(
|
||
success=True,
|
||
data=test_notification.to_dict(),
|
||
message='測試通知已創建'
|
||
))
|
||
|
||
except Exception as e:
|
||
return jsonify(create_taiwan_response(
|
||
success=False,
|
||
error=f'創建測試通知失敗:{str(e)}'
|
||
)), 500
|
||
|
||
|
||
# 工具函數:創建通知
|
||
def create_notification(user_id, title, message, notification_type=NotificationType.INFO,
|
||
job_uuid=None, extra_data=None):
|
||
"""
|
||
創建通知的工具函數
|
||
|
||
Args:
|
||
user_id: 用戶ID
|
||
title: 通知標題
|
||
message: 通知內容
|
||
notification_type: 通知類型
|
||
job_uuid: 關聯的任務UUID(可選)
|
||
extra_data: 額外數據(可選)
|
||
|
||
Returns:
|
||
Notification: 創建的通知對象
|
||
"""
|
||
try:
|
||
notification = Notification(
|
||
user_id=user_id,
|
||
type=notification_type.value,
|
||
title=title,
|
||
message=message,
|
||
job_uuid=job_uuid,
|
||
extra_data=extra_data,
|
||
link=f"/job/{job_uuid}" if job_uuid else None
|
||
)
|
||
|
||
db.session.add(notification)
|
||
db.session.commit()
|
||
|
||
return notification
|
||
|
||
except Exception as e:
|
||
db.session.rollback()
|
||
raise e |