同步更新:重构路由、服务模块,更新前端构建
This commit is contained in:
293
routes/api_user.py
Normal file
293
routes/api_user.py
Normal file
@@ -0,0 +1,293 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
from __future__ import annotations
|
||||
|
||||
import database
|
||||
import email_service
|
||||
from app_logger import get_logger
|
||||
from app_security import require_ip_not_locked, validate_email
|
||||
from flask import Blueprint, jsonify, request
|
||||
from flask_login import current_user, login_required
|
||||
from routes.pages import render_app_spa_or_legacy
|
||||
from services.state import safe_iter_task_status_items
|
||||
|
||||
logger = get_logger("app")
|
||||
|
||||
api_user_bp = Blueprint("api_user", __name__)
|
||||
|
||||
|
||||
@api_user_bp.route("/api/announcements/active", methods=["GET"])
|
||||
@login_required
|
||||
def get_active_announcement():
|
||||
"""获取当前用户应展示的公告(若无则返回announcement=null)"""
|
||||
try:
|
||||
user_id = int(current_user.id)
|
||||
except Exception:
|
||||
return jsonify({"announcement": None})
|
||||
|
||||
announcement = database.get_active_announcement_for_user(user_id)
|
||||
if not announcement:
|
||||
return jsonify({"announcement": None})
|
||||
|
||||
return jsonify(
|
||||
{
|
||||
"announcement": {
|
||||
"id": announcement.get("id"),
|
||||
"title": announcement.get("title", ""),
|
||||
"content": announcement.get("content", ""),
|
||||
"created_at": announcement.get("created_at"),
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@api_user_bp.route("/api/announcements/<int:announcement_id>/dismiss", methods=["POST"])
|
||||
@login_required
|
||||
def dismiss_announcement(announcement_id):
|
||||
"""用户永久关闭某条公告(本次公告不再弹窗)"""
|
||||
try:
|
||||
user_id = int(current_user.id)
|
||||
except Exception:
|
||||
return jsonify({"error": "请先登录"}), 401
|
||||
|
||||
announcement = database.get_announcement_by_id(announcement_id)
|
||||
if not announcement:
|
||||
return jsonify({"error": "公告不存在"}), 404
|
||||
|
||||
database.dismiss_announcement_for_user(user_id, announcement_id)
|
||||
return jsonify({"success": True})
|
||||
|
||||
|
||||
@api_user_bp.route("/api/feedback", methods=["POST"])
|
||||
@login_required
|
||||
def submit_feedback():
|
||||
"""用户提交Bug反馈"""
|
||||
data = request.get_json()
|
||||
title = data.get("title", "").strip()
|
||||
description = data.get("description", "").strip()
|
||||
contact = data.get("contact", "").strip()
|
||||
|
||||
if not title or not description:
|
||||
return jsonify({"error": "标题和描述不能为空"}), 400
|
||||
|
||||
if len(title) > 100:
|
||||
return jsonify({"error": "标题不能超过100个字符"}), 400
|
||||
|
||||
if len(description) > 2000:
|
||||
return jsonify({"error": "描述不能超过2000个字符"}), 400
|
||||
|
||||
user_info = database.get_user_by_id(current_user.id)
|
||||
username = user_info["username"] if user_info else f"用户{current_user.id}"
|
||||
|
||||
feedback_id = database.create_bug_feedback(
|
||||
user_id=current_user.id,
|
||||
username=username,
|
||||
title=title,
|
||||
description=description,
|
||||
contact=contact,
|
||||
)
|
||||
|
||||
return jsonify({"message": "反馈提交成功", "id": feedback_id})
|
||||
|
||||
|
||||
@api_user_bp.route("/api/feedback", methods=["GET"])
|
||||
@login_required
|
||||
def get_my_feedbacks():
|
||||
"""获取当前用户的反馈列表"""
|
||||
feedbacks = database.get_user_feedbacks(current_user.id)
|
||||
return jsonify(feedbacks)
|
||||
|
||||
|
||||
@api_user_bp.route("/api/user/vip", methods=["GET"])
|
||||
@login_required
|
||||
def get_current_user_vip():
|
||||
"""获取当前用户VIP信息"""
|
||||
vip_info = database.get_user_vip_info(current_user.id)
|
||||
user_info = database.get_user_by_id(current_user.id)
|
||||
vip_info["username"] = user_info["username"] if user_info else "Unknown"
|
||||
return jsonify(vip_info)
|
||||
|
||||
|
||||
@api_user_bp.route("/api/user/password", methods=["POST"])
|
||||
@login_required
|
||||
def change_user_password():
|
||||
"""用户修改自己的密码"""
|
||||
data = request.get_json()
|
||||
current_password = data.get("current_password")
|
||||
new_password = data.get("new_password")
|
||||
|
||||
if not current_password or not new_password:
|
||||
return jsonify({"error": "请填写完整信息"}), 400
|
||||
|
||||
if len(new_password) < 6:
|
||||
return jsonify({"error": "新密码至少6位"}), 400
|
||||
|
||||
user = database.get_user_by_id(current_user.id)
|
||||
if not user:
|
||||
return jsonify({"error": "用户不存在"}), 404
|
||||
|
||||
username = user.get("username", "")
|
||||
if not username or not database.verify_user(username, current_password):
|
||||
return jsonify({"error": "当前密码错误"}), 400
|
||||
|
||||
if database.admin_reset_user_password(current_user.id, new_password):
|
||||
return jsonify({"success": True})
|
||||
return jsonify({"error": "密码更新失败"}), 500
|
||||
|
||||
|
||||
@api_user_bp.route("/api/user/email", methods=["GET"])
|
||||
@login_required
|
||||
def get_user_email():
|
||||
"""获取当前用户的邮箱信息"""
|
||||
user = database.get_user_by_id(current_user.id)
|
||||
if not user:
|
||||
return jsonify({"error": "用户不存在"}), 404
|
||||
|
||||
return jsonify({"email": user.get("email", ""), "email_verified": user.get("email_verified", False)})
|
||||
|
||||
|
||||
@api_user_bp.route("/api/user/bind-email", methods=["POST"])
|
||||
@login_required
|
||||
@require_ip_not_locked
|
||||
def bind_user_email():
|
||||
"""发送邮箱绑定验证邮件"""
|
||||
data = request.get_json()
|
||||
email = data.get("email", "").strip().lower()
|
||||
|
||||
if not email or not validate_email(email):
|
||||
return jsonify({"error": "请输入有效的邮箱地址"}), 400
|
||||
|
||||
settings = email_service.get_email_settings()
|
||||
if not settings.get("enabled", False):
|
||||
return jsonify({"error": "邮件功能未启用,请联系管理员"}), 400
|
||||
|
||||
existing_user = database.get_user_by_email(email)
|
||||
if existing_user and existing_user["id"] != current_user.id:
|
||||
return jsonify({"error": "该邮箱已被其他用户绑定"}), 400
|
||||
|
||||
user = database.get_user_by_id(current_user.id)
|
||||
if not user:
|
||||
return jsonify({"error": "用户不存在"}), 404
|
||||
|
||||
if user.get("email") == email and user.get("email_verified"):
|
||||
return jsonify({"error": "该邮箱已绑定并验证"}), 400
|
||||
|
||||
result = email_service.send_bind_email_verification(user_id=current_user.id, email=email, username=user["username"])
|
||||
|
||||
if result["success"]:
|
||||
return jsonify({"success": True, "message": "验证邮件已发送,请查收"})
|
||||
return jsonify({"error": result["error"]}), 500
|
||||
|
||||
|
||||
@api_user_bp.route("/api/verify-bind-email/<token>")
|
||||
def verify_bind_email(token):
|
||||
"""验证邮箱绑定Token"""
|
||||
result = email_service.verify_bind_email_token(token)
|
||||
|
||||
if result:
|
||||
user_id = result["user_id"]
|
||||
email = result["email"]
|
||||
|
||||
if database.update_user_email(user_id, email, verified=True):
|
||||
spa_initial_state = {
|
||||
"page": "verify_result",
|
||||
"success": True,
|
||||
"title": "邮箱绑定成功",
|
||||
"message": f"邮箱 {email} 已成功绑定到您的账号!",
|
||||
"primary_label": "返回登录",
|
||||
"primary_url": "/login",
|
||||
"redirect_url": "/login",
|
||||
"redirect_seconds": 5,
|
||||
}
|
||||
return render_app_spa_or_legacy("verify_success.html", spa_initial_state=spa_initial_state)
|
||||
|
||||
error_message = "邮箱绑定失败,请重试"
|
||||
spa_initial_state = {
|
||||
"page": "verify_result",
|
||||
"success": False,
|
||||
"title": "绑定失败",
|
||||
"error_message": error_message,
|
||||
"primary_label": "返回登录",
|
||||
"primary_url": "/login",
|
||||
}
|
||||
return render_app_spa_or_legacy(
|
||||
"verify_failed.html",
|
||||
legacy_context={"error_message": error_message},
|
||||
spa_initial_state=spa_initial_state,
|
||||
)
|
||||
|
||||
error_message = "验证链接已过期或无效,请重新发送验证邮件"
|
||||
spa_initial_state = {
|
||||
"page": "verify_result",
|
||||
"success": False,
|
||||
"title": "链接无效",
|
||||
"error_message": error_message,
|
||||
"primary_label": "返回登录",
|
||||
"primary_url": "/login",
|
||||
}
|
||||
return render_app_spa_or_legacy(
|
||||
"verify_failed.html",
|
||||
legacy_context={"error_message": error_message},
|
||||
spa_initial_state=spa_initial_state,
|
||||
)
|
||||
|
||||
|
||||
@api_user_bp.route("/api/user/unbind-email", methods=["POST"])
|
||||
@login_required
|
||||
def unbind_user_email():
|
||||
"""解绑用户邮箱"""
|
||||
user = database.get_user_by_id(current_user.id)
|
||||
if not user:
|
||||
return jsonify({"error": "用户不存在"}), 404
|
||||
|
||||
if not user.get("email"):
|
||||
return jsonify({"error": "当前未绑定邮箱"}), 400
|
||||
|
||||
if database.update_user_email(current_user.id, None, verified=False):
|
||||
return jsonify({"success": True, "message": "邮箱已解绑"})
|
||||
return jsonify({"error": "解绑失败"}), 500
|
||||
|
||||
|
||||
@api_user_bp.route("/api/user/email-notify", methods=["GET"])
|
||||
@login_required
|
||||
def get_user_email_notify():
|
||||
"""获取用户邮件通知偏好"""
|
||||
enabled = database.get_user_email_notify(current_user.id)
|
||||
return jsonify({"enabled": enabled})
|
||||
|
||||
|
||||
@api_user_bp.route("/api/user/email-notify", methods=["POST"])
|
||||
@login_required
|
||||
def update_user_email_notify():
|
||||
"""更新用户邮件通知偏好"""
|
||||
data = request.get_json()
|
||||
enabled = data.get("enabled", True)
|
||||
|
||||
if database.update_user_email_notify(current_user.id, enabled):
|
||||
return jsonify({"success": True})
|
||||
return jsonify({"error": "更新失败"}), 500
|
||||
|
||||
|
||||
@api_user_bp.route("/api/run_stats", methods=["GET"])
|
||||
@login_required
|
||||
def get_run_stats():
|
||||
"""获取当前用户的运行统计"""
|
||||
user_id = current_user.id
|
||||
|
||||
stats = database.get_user_run_stats(user_id)
|
||||
|
||||
current_running = 0
|
||||
for _, info in safe_iter_task_status_items():
|
||||
if info.get("user_id") == user_id and info.get("status") == "运行中":
|
||||
current_running += 1
|
||||
|
||||
return jsonify(
|
||||
{
|
||||
"today_completed": stats.get("completed", 0),
|
||||
"current_running": current_running,
|
||||
"today_failed": stats.get("failed", 0),
|
||||
"today_items": stats.get("total_items", 0),
|
||||
"today_attachments": stats.get("total_attachments", 0),
|
||||
}
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user