init
This commit is contained in:
0
api/__init__.py
Normal file
0
api/__init__.py
Normal file
300
api/statistics.py
Normal file
300
api/statistics.py
Normal file
@@ -0,0 +1,300 @@
|
||||
from flask import Blueprint, request, jsonify, make_response
|
||||
from flask_jwt_extended import get_jwt_identity
|
||||
from models import db, Task, User
|
||||
from auth import login_required, admin_required
|
||||
from datetime import datetime, timedelta
|
||||
from sqlalchemy import func, and_
|
||||
import csv
|
||||
from io import StringIO
|
||||
|
||||
statistics_bp = Blueprint('statistics', __name__)
|
||||
|
||||
# 个人统计
|
||||
@statistics_bp.route('/statistics/user/<int:user_id>', methods=['GET'])
|
||||
@login_required
|
||||
def get_user_statistics(user_id):
|
||||
current_user_id = int(get_jwt_identity())
|
||||
current_user = db.session.get(User, current_user_id)
|
||||
|
||||
# 非管理员只能查看自己的统计
|
||||
if current_user.role != 'admin' and current_user_id != user_id:
|
||||
return jsonify({'error': '无权查看他人统计'}), 403
|
||||
|
||||
date_from = request.args.get('date_from')
|
||||
date_to = request.args.get('date_to')
|
||||
group_id = request.args.get('group_id', type=int)
|
||||
|
||||
query = Task.query.filter_by(claimed_by=user_id, status='completed')
|
||||
|
||||
if group_id:
|
||||
query = query.filter_by(group_id=group_id)
|
||||
|
||||
if date_from:
|
||||
try:
|
||||
date_from_obj = datetime.strptime(date_from, '%Y-%m-%d')
|
||||
query = query.filter(Task.completed_at >= date_from_obj)
|
||||
except:
|
||||
pass
|
||||
|
||||
if date_to:
|
||||
try:
|
||||
date_to_obj = datetime.strptime(date_to, '%Y-%m-%d')
|
||||
date_to_obj = date_to_obj.replace(hour=23, minute=59, second=59)
|
||||
query = query.filter(Task.completed_at <= date_to_obj)
|
||||
except:
|
||||
pass
|
||||
|
||||
# 总完成数
|
||||
total_completed = query.count()
|
||||
|
||||
# 按日期分组统计
|
||||
daily_stats = db.session.query(
|
||||
func.date(Task.completed_at).label('date'),
|
||||
func.count(Task.id).label('count')
|
||||
).filter(
|
||||
Task.claimed_by == user_id,
|
||||
Task.status == 'completed'
|
||||
)
|
||||
|
||||
if date_from:
|
||||
daily_stats = daily_stats.filter(Task.completed_at >= date_from_obj)
|
||||
if date_to:
|
||||
daily_stats = daily_stats.filter(Task.completed_at <= date_to_obj)
|
||||
|
||||
daily_stats = daily_stats.group_by(func.date(Task.completed_at)).all()
|
||||
|
||||
# 当前认领未完成
|
||||
claimed_pending = Task.query.filter_by(
|
||||
claimed_by=user_id,
|
||||
status='claimed'
|
||||
).count()
|
||||
|
||||
user = db.session.get(User, user_id)
|
||||
|
||||
return jsonify({
|
||||
'user': {
|
||||
'id': user.id,
|
||||
'username': user.username
|
||||
},
|
||||
'total_completed': total_completed,
|
||||
'claimed_pending': claimed_pending,
|
||||
'daily_stats': [
|
||||
{
|
||||
'date': str(stat.date),
|
||||
'count': stat.count
|
||||
} for stat in daily_stats
|
||||
]
|
||||
})
|
||||
|
||||
|
||||
# 组别排行榜
|
||||
@statistics_bp.route('/statistics/leaderboard', methods=['GET'])
|
||||
@login_required
|
||||
def get_leaderboard():
|
||||
group_id = request.args.get('group_id', type=int)
|
||||
period = request.args.get('period', 'monthly') # daily/monthly
|
||||
|
||||
now = datetime.utcnow()
|
||||
|
||||
if period == 'daily':
|
||||
start_date = now.replace(hour=0, minute=0, second=0, microsecond=0)
|
||||
else: # monthly
|
||||
start_date = now.replace(day=1, hour=0, minute=0, second=0, microsecond=0)
|
||||
|
||||
query = db.session.query(
|
||||
User.id,
|
||||
User.username,
|
||||
func.count(Task.id).label('completed_count')
|
||||
).join(
|
||||
Task, Task.claimed_by == User.id
|
||||
).filter(
|
||||
Task.status == 'completed',
|
||||
Task.completed_at >= start_date
|
||||
)
|
||||
|
||||
if group_id:
|
||||
query = query.filter(Task.group_id == group_id)
|
||||
|
||||
leaderboard = query.group_by(User.id, User.username).order_by(
|
||||
func.count(Task.id).desc()
|
||||
).limit(20).all()
|
||||
|
||||
return jsonify({
|
||||
'period': period,
|
||||
'leaderboard': [
|
||||
{
|
||||
'rank': idx + 1,
|
||||
'user_id': row.id,
|
||||
'username': row.username,
|
||||
'completed_count': row.completed_count
|
||||
} for idx, row in enumerate(leaderboard)
|
||||
]
|
||||
})
|
||||
|
||||
|
||||
# 整体统计(支持管理员和普通用户)
|
||||
@statistics_bp.route('/statistics/overview', methods=['GET'])
|
||||
@login_required
|
||||
def get_overview():
|
||||
current_user_id = get_jwt_identity()
|
||||
current_user = db.session.get(User, current_user_id)
|
||||
group_id = request.args.get('group_id', type=int, default=1)
|
||||
|
||||
# 获取时间范围参数
|
||||
date_from = request.args.get('date_from')
|
||||
date_to = request.args.get('date_to')
|
||||
|
||||
# 基础查询条件
|
||||
base_filter = [Task.group_id == group_id]
|
||||
|
||||
# 普通用户只能看自己的数据
|
||||
if current_user.role != 'admin':
|
||||
base_filter.append(Task.claimed_by == current_user_id)
|
||||
|
||||
# 解析时间范围
|
||||
date_from_obj = None
|
||||
date_to_obj = None
|
||||
if date_from:
|
||||
try:
|
||||
date_from_obj = datetime.strptime(date_from, '%Y-%m-%d')
|
||||
except:
|
||||
pass
|
||||
if date_to:
|
||||
try:
|
||||
date_to_obj = datetime.strptime(date_to, '%Y-%m-%d')
|
||||
date_to_obj = date_to_obj.replace(hour=23, minute=59, second=59)
|
||||
except:
|
||||
pass
|
||||
|
||||
# 各状态任务数量
|
||||
status_query = db.session.query(
|
||||
Task.status,
|
||||
func.count(Task.id).label('count')
|
||||
).filter(and_(*base_filter)).group_by(Task.status)
|
||||
status_counts = status_query.all()
|
||||
|
||||
# 今日完成
|
||||
today = datetime.utcnow().replace(hour=0, minute=0, second=0, microsecond=0)
|
||||
today_filter = base_filter + [
|
||||
Task.status == 'completed',
|
||||
Task.completed_at >= today
|
||||
]
|
||||
today_completed = Task.query.filter(and_(*today_filter)).count()
|
||||
|
||||
# 本月完成
|
||||
month_start = datetime.utcnow().replace(day=1, hour=0, minute=0, second=0, microsecond=0)
|
||||
month_filter = base_filter + [
|
||||
Task.status == 'completed',
|
||||
Task.completed_at >= month_start
|
||||
]
|
||||
month_completed = Task.query.filter(and_(*month_filter)).count()
|
||||
|
||||
# 趋势图(根据时间范围或默认最近7天)
|
||||
if date_from_obj:
|
||||
trend_start = date_from_obj
|
||||
else:
|
||||
trend_start = datetime.utcnow() - timedelta(days=7)
|
||||
|
||||
trend_filter = base_filter + [
|
||||
Task.status == 'completed',
|
||||
Task.completed_at >= trend_start
|
||||
]
|
||||
if date_to_obj:
|
||||
trend_filter.append(Task.completed_at <= date_to_obj)
|
||||
|
||||
trend = db.session.query(
|
||||
func.date(Task.completed_at).label('date'),
|
||||
func.count(Task.id).label('count')
|
||||
).filter(and_(*trend_filter)).group_by(func.date(Task.completed_at)).all()
|
||||
# 计算时间范围内的总完成数
|
||||
completed_filter = base_filter + [Task.status == 'completed']
|
||||
if date_from_obj:
|
||||
completed_filter.append(Task.completed_at >= date_from_obj)
|
||||
if date_to_obj:
|
||||
completed_filter.append(Task.completed_at <= date_to_obj)
|
||||
total_completed = Task.query.filter(and_(*completed_filter)).count()
|
||||
|
||||
# 当前认领未完成数
|
||||
claimed_filter = base_filter + [Task.status == 'claimed']
|
||||
claimed_pending = Task.query.filter(and_(*claimed_filter)).count()
|
||||
return jsonify({
|
||||
'status_counts': {row.status: row.count for row in status_counts},
|
||||
'today_completed': today_completed,
|
||||
'month_completed': month_completed,
|
||||
'total_completed': total_completed,
|
||||
'claimed_pending': claimed_pending,
|
||||
'is_admin': current_user.role == 'admin',
|
||||
'trend': [
|
||||
{'date': str(row.date), 'count': row.count} for row in trend
|
||||
]
|
||||
})
|
||||
|
||||
|
||||
# 导出统计数据(CSV格式)
|
||||
@statistics_bp.route('/statistics/export', methods=['GET'])
|
||||
@login_required
|
||||
def export_statistics():
|
||||
current_user_id = get_jwt_identity()
|
||||
current_user = db.session.get(User, current_user_id)
|
||||
group_id = request.args.get('group_id', type=int, default=1)
|
||||
date_from = request.args.get('date_from')
|
||||
date_to = request.args.get('date_to')
|
||||
|
||||
# 构建查询条件
|
||||
query = Task.query.filter_by(group_id=group_id, status='completed')
|
||||
|
||||
# 普通用户只能导出自己的数据
|
||||
if current_user.role != 'admin':
|
||||
query = query.filter_by(claimed_by=current_user_id)
|
||||
|
||||
# 时间范围筛选
|
||||
if date_from:
|
||||
try:
|
||||
date_from_obj = datetime.strptime(date_from, '%Y-%m-%d')
|
||||
query = query.filter(Task.completed_at >= date_from_obj)
|
||||
except:
|
||||
pass
|
||||
|
||||
if date_to:
|
||||
try:
|
||||
date_to_obj = datetime.strptime(date_to, '%Y-%m-%d')
|
||||
date_to_obj = date_to_obj.replace(hour=23, minute=59, second=59)
|
||||
query = query.filter(Task.completed_at <= date_to_obj)
|
||||
except:
|
||||
pass
|
||||
|
||||
tasks = query.order_by(Task.completed_at.desc()).all()
|
||||
|
||||
# 生成CSV
|
||||
output = StringIO()
|
||||
writer = csv.writer(output)
|
||||
|
||||
# 写入表头
|
||||
writer.writerow(['任务ID', '剧集名称', '优先级', '认领人', '种子ID', '完成时间', '认领时间', '耗时(小时)'])
|
||||
|
||||
# 写入数据
|
||||
for task in tasks:
|
||||
duration = ''
|
||||
if task.claimed_at and task.completed_at:
|
||||
delta = task.completed_at - task.claimed_at
|
||||
duration = f'{delta.total_seconds() / 3600:.1f}'
|
||||
|
||||
claimer_name = task.claimer.username if task.claimer else '-'
|
||||
|
||||
writer.writerow([
|
||||
task.id,
|
||||
task.series_name,
|
||||
task.priority or '-',
|
||||
claimer_name,
|
||||
task.torrent_id or '-',
|
||||
task.completed_at.strftime('%Y-%m-%d %H:%M:%S') if task.completed_at else '-',
|
||||
task.claimed_at.strftime('%Y-%m-%d %H:%M:%S') if task.claimed_at else '-',
|
||||
duration
|
||||
])
|
||||
|
||||
# 创建响应
|
||||
response = make_response(output.getvalue())
|
||||
response.headers['Content-Type'] = 'text/csv; charset=utf-8-sig'
|
||||
response.headers['Content-Disposition'] = f'attachment; filename=statistics_{datetime.now().strftime("%Y%m%d_%H%M%S")}.csv'
|
||||
|
||||
return response
|
||||
250
api/tasks.py
Normal file
250
api/tasks.py
Normal file
@@ -0,0 +1,250 @@
|
||||
from flask import Blueprint, request, jsonify
|
||||
from flask_jwt_extended import get_jwt_identity
|
||||
from models import db, Task, TaskLog, Group, User
|
||||
from auth import login_required, admin_required, group_member_required, get_current_user
|
||||
from datetime import datetime
|
||||
from sqlalchemy import func, and_
|
||||
|
||||
tasks_bp = Blueprint('tasks', __name__)
|
||||
|
||||
# 获取任务列表
|
||||
@tasks_bp.route('/groups/<int:group_id>/tasks', methods=['GET'])
|
||||
@login_required
|
||||
def get_tasks(group_id):
|
||||
status = request.args.get('status') # pending/claimed/completed
|
||||
user_id = request.args.get('user_id', type=int)
|
||||
page = request.args.get('page', 1, type=int)
|
||||
per_page = request.args.get('per_page', 20, type=int)
|
||||
|
||||
query = Task.query.filter_by(group_id=group_id)
|
||||
|
||||
if status:
|
||||
query = query.filter_by(status=status)
|
||||
|
||||
if user_id:
|
||||
query = query.filter_by(claimed_by=user_id)
|
||||
|
||||
query = query.order_by(Task.series_date.desc(), Task.created_at.desc())
|
||||
|
||||
pagination = query.paginate(page=page, per_page=per_page, error_out=False)
|
||||
|
||||
tasks = []
|
||||
for task in pagination.items:
|
||||
tasks.append({
|
||||
'id': task.id,
|
||||
'series_name': task.series_name,
|
||||
'series_link': task.series_link,
|
||||
'series_date': task.series_date.strftime('%Y-%m-%d') if task.series_date else None,
|
||||
'priority': task.priority,
|
||||
'status': task.status,
|
||||
'claimed_by': task.claimer.username if task.claimer else None,
|
||||
'claimed_by_id': task.claimed_by,
|
||||
'claimed_at': task.claimed_at.strftime('%Y-%m-%d %H:%M') if task.claimed_at else None,
|
||||
'claim_note': task.claim_note,
|
||||
'torrent_id': task.torrent_id,
|
||||
'complete_note': task.complete_note,
|
||||
'completed_at': task.completed_at.strftime('%Y-%m-%d %H:%M') if task.completed_at else None,
|
||||
'creator': task.creator.username,
|
||||
'created_at': task.created_at.strftime('%Y-%m-%d %H:%M')
|
||||
})
|
||||
|
||||
return jsonify({
|
||||
'tasks': tasks,
|
||||
'total': pagination.total,
|
||||
'page': page,
|
||||
'pages': pagination.pages
|
||||
})
|
||||
|
||||
|
||||
# 创建任务(管理员)
|
||||
@tasks_bp.route('/groups/<int:group_id>/tasks', methods=['POST'])
|
||||
@admin_required
|
||||
def create_task(group_id):
|
||||
data = request.json
|
||||
|
||||
# 验证必填字段
|
||||
if not data.get('series_name'):
|
||||
return jsonify({'error': '剧集名称不能为空'}), 400
|
||||
|
||||
if not data.get('series_date'):
|
||||
return jsonify({'error': '剧集更新日期不能为空'}), 400
|
||||
|
||||
try:
|
||||
series_date = datetime.strptime(data['series_date'], '%Y-%m-%d').date()
|
||||
except:
|
||||
return jsonify({'error': '日期格式错误'}), 400
|
||||
|
||||
user_id = get_jwt_identity()
|
||||
|
||||
task = Task(
|
||||
group_id=group_id,
|
||||
series_name=data['series_name'],
|
||||
series_link=data.get('series_link'),
|
||||
series_date=series_date,
|
||||
priority=data.get('priority', '中'),
|
||||
created_by=user_id
|
||||
)
|
||||
|
||||
db.session.add(task)
|
||||
db.session.flush()
|
||||
|
||||
# 记录日志
|
||||
log = TaskLog(
|
||||
task_id=task.id,
|
||||
user_id=user_id,
|
||||
action='create',
|
||||
comment=f'创建任务:{task.series_name}'
|
||||
)
|
||||
db.session.add(log)
|
||||
db.session.commit()
|
||||
|
||||
return jsonify({'message': '任务创建成功', 'task_id': task.id}), 201
|
||||
|
||||
|
||||
# 认领任务
|
||||
@tasks_bp.route('/tasks/<int:task_id>/claim', methods=['POST'])
|
||||
@login_required
|
||||
def claim_task(task_id):
|
||||
task = db.session.get(Task, task_id)
|
||||
if not task:
|
||||
return jsonify({'error': '任务不存在'}), 404
|
||||
|
||||
if task.status != 'pending':
|
||||
return jsonify({'error': '该任务已被认领或已完成'}), 400
|
||||
|
||||
user_id = get_jwt_identity()
|
||||
data = request.json or {}
|
||||
|
||||
# 使用数据库锁防止并发认领
|
||||
task = db.session.query(Task).filter_by(id=task_id, status='pending').with_for_update().first()
|
||||
if not task:
|
||||
return jsonify({'error': '任务已被他人认领'}), 400
|
||||
|
||||
task.status = 'claimed'
|
||||
task.claimed_by = user_id
|
||||
task.claimed_at = datetime.utcnow()
|
||||
task.claim_note = data.get('claim_note')
|
||||
|
||||
log = TaskLog(
|
||||
task_id=task_id,
|
||||
user_id=user_id,
|
||||
action='claim',
|
||||
comment=data.get('claim_note')
|
||||
)
|
||||
db.session.add(log)
|
||||
db.session.commit()
|
||||
|
||||
return jsonify({'message': '认领成功'})
|
||||
|
||||
|
||||
# 完成任务
|
||||
# 完成任务
|
||||
@tasks_bp.route('/tasks/<int:task_id>/complete', methods=['POST'])
|
||||
@login_required
|
||||
def complete_task(task_id):
|
||||
task = db.session.get(Task, task_id)
|
||||
if not task:
|
||||
return jsonify({'error': '任务不存在'}), 404
|
||||
|
||||
user_id = get_jwt_identity()
|
||||
|
||||
# 确保类型一致,转换为整数比较
|
||||
if task.claimed_by != int(user_id):
|
||||
return jsonify({'error': f'只能完成自己认领的任务 (任务认领者: {task.claimed_by}, 当前用户: {user_id})'}), 403
|
||||
|
||||
if task.status != 'claimed':
|
||||
return jsonify({'error': '任务状态错误'}), 400
|
||||
|
||||
data = request.json
|
||||
if not data.get('torrent_id'):
|
||||
return jsonify({'error': '种子ID不能为空'}), 400
|
||||
|
||||
task.status = 'completed'
|
||||
task.torrent_id = data['torrent_id']
|
||||
task.complete_note = data.get('complete_note')
|
||||
task.completed_at = datetime.utcnow()
|
||||
|
||||
log = TaskLog(
|
||||
task_id=task_id,
|
||||
user_id=int(user_id),
|
||||
action='complete',
|
||||
comment=f'种子ID: {data["torrent_id"]}'
|
||||
)
|
||||
db.session.add(log)
|
||||
db.session.commit()
|
||||
|
||||
return jsonify({'message': '任务完成'})
|
||||
|
||||
|
||||
|
||||
# 取消认领(用户自己)
|
||||
@tasks_bp.route('/tasks/<int:task_id>/cancel-claim', methods=['POST'])
|
||||
@login_required
|
||||
def cancel_claim(task_id):
|
||||
task = db.session.get(Task, task_id)
|
||||
if not task:
|
||||
return jsonify({'error': '任务不存在'}), 404
|
||||
|
||||
user_id = get_jwt_identity()
|
||||
|
||||
if task.claimed_by != user_id:
|
||||
return jsonify({'error': '只能取消自己认领的任务'}), 403
|
||||
|
||||
if task.status != 'claimed':
|
||||
return jsonify({'error': '任务状态错误'}), 400
|
||||
|
||||
task.status = 'pending'
|
||||
task.claimed_by = None
|
||||
task.claimed_at = None
|
||||
task.claim_note = None
|
||||
|
||||
log = TaskLog(
|
||||
task_id=task_id,
|
||||
user_id=user_id,
|
||||
action='cancel_claim',
|
||||
comment='取消认领'
|
||||
)
|
||||
db.session.add(log)
|
||||
db.session.commit()
|
||||
|
||||
return jsonify({'message': '已取消认领'})
|
||||
|
||||
|
||||
# 删除任务(管理员)
|
||||
@tasks_bp.route('/tasks/<int:task_id>', methods=['DELETE'])
|
||||
@admin_required
|
||||
def delete_task(task_id):
|
||||
task = db.session.get(Task, task_id)
|
||||
if not task:
|
||||
return jsonify({'error': '任务不存在'}), 404
|
||||
|
||||
db.session.delete(task)
|
||||
db.session.commit()
|
||||
|
||||
return jsonify({'message': '任务已删除'})
|
||||
|
||||
|
||||
# 获取任务详情
|
||||
@tasks_bp.route('/tasks/<int:task_id>', methods=['GET'])
|
||||
@login_required
|
||||
def get_task(task_id):
|
||||
task = db.session.get(Task, task_id)
|
||||
if not task:
|
||||
return jsonify({'error': '任务不存在'}), 404
|
||||
|
||||
return jsonify({
|
||||
'id': task.id,
|
||||
'series_name': task.series_name,
|
||||
'series_link': task.series_link,
|
||||
'series_date': task.series_date.strftime('%Y-%m-%d'),
|
||||
'priority': task.priority,
|
||||
'status': task.status,
|
||||
'claimed_by': task.claimer.username if task.claimer else None,
|
||||
'claimed_at': task.claimed_at.strftime('%Y-%m-%d %H:%M') if task.claimed_at else None,
|
||||
'claim_note': task.claim_note,
|
||||
'torrent_id': task.torrent_id,
|
||||
'complete_note': task.complete_note,
|
||||
'completed_at': task.completed_at.strftime('%Y-%m-%d %H:%M') if task.completed_at else None,
|
||||
'creator': task.creator.username,
|
||||
'created_at': task.created_at.strftime('%Y-%m-%d %H:%M')
|
||||
})
|
||||
199
api/user_management.py
Normal file
199
api/user_management.py
Normal file
@@ -0,0 +1,199 @@
|
||||
from flask import Blueprint, request, jsonify
|
||||
from flask_jwt_extended import get_jwt_identity
|
||||
from models import db, User
|
||||
from auth import login_required, admin_required
|
||||
from datetime import datetime
|
||||
|
||||
user_mgmt_bp = Blueprint('user_management', __name__)
|
||||
|
||||
# 用户注册
|
||||
@user_mgmt_bp.route('/register', methods=['POST'])
|
||||
def register():
|
||||
data = request.json
|
||||
|
||||
if not all([data.get('username'), data.get('email'), data.get('password')]):
|
||||
return jsonify({'error': '用户名、邮箱和密码不能为空'}), 400
|
||||
|
||||
if User.query.filter_by(username=data['username']).first():
|
||||
return jsonify({'error': '用户名已存在'}), 400
|
||||
|
||||
if User.query.filter_by(email=data['email']).first():
|
||||
return jsonify({'error': '邮箱已被注册'}), 400
|
||||
|
||||
user = User(
|
||||
username=data['username'],
|
||||
email=data['email'],
|
||||
uid=data.get('uid'),
|
||||
status='pending'
|
||||
)
|
||||
user.set_password(data['password'])
|
||||
|
||||
db.session.add(user)
|
||||
db.session.commit()
|
||||
|
||||
return jsonify({'message': '注册成功,请等待管理员审核'}), 201
|
||||
|
||||
# 获取用户列表(管理员)
|
||||
@user_mgmt_bp.route('/users', methods=['GET'])
|
||||
@admin_required
|
||||
def get_users():
|
||||
status = request.args.get('status')
|
||||
page = request.args.get('page', 1, type=int)
|
||||
per_page = request.args.get('per_page', 20, type=int)
|
||||
|
||||
query = User.query
|
||||
if status:
|
||||
query = query.filter_by(status=status)
|
||||
|
||||
pagination = query.order_by(User.created_at.desc()).paginate(
|
||||
page=page, per_page=per_page, error_out=False
|
||||
)
|
||||
|
||||
return jsonify({
|
||||
'users': [{
|
||||
'id': u.id,
|
||||
'username': u.username,
|
||||
'email': u.email,
|
||||
'uid': u.uid,
|
||||
'role': u.role,
|
||||
'status': u.status,
|
||||
'tags': u.tags,
|
||||
'note': u.note,
|
||||
'created_at': u.created_at.strftime('%Y-%m-%d %H:%M'),
|
||||
'approved_at': u.approved_at.strftime('%Y-%m-%d %H:%M') if u.approved_at else None
|
||||
} for u in pagination.items],
|
||||
'total': pagination.total,
|
||||
'page': page,
|
||||
'pages': pagination.pages
|
||||
})
|
||||
|
||||
# 审核用户(管理员)
|
||||
@user_mgmt_bp.route('/users/<int:user_id>/approve', methods=['POST'])
|
||||
@admin_required
|
||||
def approve_user(user_id):
|
||||
user = db.session.get(User, user_id)
|
||||
if not user:
|
||||
return jsonify({'error': '用户不存在'}), 404
|
||||
|
||||
data = request.json or {}
|
||||
action = data.get('action') # approve/reject
|
||||
|
||||
if action == 'approve':
|
||||
user.status = 'active'
|
||||
user.approved_at = datetime.utcnow()
|
||||
user.approved_by = get_jwt_identity()
|
||||
db.session.commit()
|
||||
return jsonify({'message': '已通过审核'})
|
||||
elif action == 'reject':
|
||||
db.session.delete(user)
|
||||
db.session.commit()
|
||||
return jsonify({'message': '已拒绝申请'})
|
||||
|
||||
return jsonify({'error': '无效的操作'}), 400
|
||||
|
||||
# 编辑用户(管理员)
|
||||
@user_mgmt_bp.route('/users/<int:user_id>', methods=['PUT'])
|
||||
@admin_required
|
||||
def update_user(user_id):
|
||||
current_user_id = get_jwt_identity()
|
||||
|
||||
if current_user_id == user_id:
|
||||
return jsonify({'error': '不能修改自己的信息'}), 403
|
||||
|
||||
user = db.session.get(User, user_id)
|
||||
if not user:
|
||||
return jsonify({'error': '用户不存在'}), 404
|
||||
|
||||
data = request.json
|
||||
|
||||
if 'email' in data:
|
||||
user.email = data['email']
|
||||
if 'uid' in data:
|
||||
user.uid = data['uid']
|
||||
if 'role' in data:
|
||||
user.role = data['role']
|
||||
if 'status' in data:
|
||||
user.status = data['status']
|
||||
if 'tags' in data:
|
||||
user.tags = data['tags']
|
||||
if 'note' in data:
|
||||
user.note = data['note']
|
||||
|
||||
db.session.commit()
|
||||
return jsonify({'message': '用户信息已更新'})
|
||||
|
||||
# 删除用户(管理员)
|
||||
@user_mgmt_bp.route('/users/<int:user_id>', methods=['DELETE'])
|
||||
@admin_required
|
||||
def delete_user(user_id):
|
||||
current_user_id = get_jwt_identity()
|
||||
|
||||
if current_user_id == user_id:
|
||||
return jsonify({'error': '不能删除自己'}), 403
|
||||
|
||||
user = db.session.get(User, user_id)
|
||||
if not user:
|
||||
return jsonify({'error': '用户不存在'}), 404
|
||||
|
||||
db.session.delete(user)
|
||||
db.session.commit()
|
||||
return jsonify({'message': '用户已删除'})
|
||||
|
||||
# 修改密码(用户自己)
|
||||
@user_mgmt_bp.route('/users/change-password', methods=['POST'])
|
||||
@login_required
|
||||
def change_password():
|
||||
user_id = get_jwt_identity()
|
||||
user = db.session.get(User, user_id)
|
||||
|
||||
data = request.json
|
||||
old_password = data.get('old_password')
|
||||
new_password = data.get('new_password')
|
||||
|
||||
if not old_password or not new_password:
|
||||
return jsonify({'error': '旧密码和新密码不能为空'}), 400
|
||||
|
||||
if not user.check_password(old_password):
|
||||
return jsonify({'error': '旧密码错误'}), 400
|
||||
|
||||
user.set_password(new_password)
|
||||
db.session.commit()
|
||||
|
||||
return jsonify({'message': '密码修改成功'})
|
||||
|
||||
# 获取个人信息
|
||||
@user_mgmt_bp.route('/users/profile', methods=['GET'])
|
||||
@login_required
|
||||
def get_profile():
|
||||
user_id = get_jwt_identity()
|
||||
user = db.session.get(User, user_id)
|
||||
|
||||
return jsonify({
|
||||
'id': user.id,
|
||||
'username': user.username,
|
||||
'email': user.email,
|
||||
'uid': user.uid,
|
||||
'role': user.role,
|
||||
'status': user.status,
|
||||
'created_at': user.created_at.strftime('%Y-%m-%d %H:%M')
|
||||
})
|
||||
|
||||
# 更新个人信息
|
||||
@user_mgmt_bp.route('/users/profile', methods=['PUT'])
|
||||
@login_required
|
||||
def update_profile():
|
||||
user_id = get_jwt_identity()
|
||||
user = db.session.get(User, user_id)
|
||||
|
||||
data = request.json
|
||||
|
||||
if 'email' in data:
|
||||
if User.query.filter(User.email == data['email'], User.id != user_id).first():
|
||||
return jsonify({'error': '邮箱已被使用'}), 400
|
||||
user.email = data['email']
|
||||
|
||||
if 'uid' in data:
|
||||
user.uid = data['uid']
|
||||
|
||||
db.session.commit()
|
||||
return jsonify({'message': '个人信息已更新'})
|
||||
81
api/users.py
Normal file
81
api/users.py
Normal file
@@ -0,0 +1,81 @@
|
||||
from flask import Blueprint, request, jsonify
|
||||
from flask_jwt_extended import create_access_token, jwt_required, get_jwt_identity
|
||||
from models import User, db
|
||||
from datetime import timedelta
|
||||
|
||||
users_bp = Blueprint('users', __name__)
|
||||
|
||||
@users_bp.route('/login', methods=['POST'])
|
||||
def login():
|
||||
data = request.json
|
||||
user = User.query.filter_by(username=data['username']).first()
|
||||
|
||||
if not user or not user.check_password(data['password']):
|
||||
return jsonify({'error': '用户名或密码错误'}), 401
|
||||
|
||||
if user.status != 'active':
|
||||
return jsonify({'error': '账号未激活或已被禁用'}), 403
|
||||
|
||||
access_token = create_access_token(
|
||||
identity=str(user.id),
|
||||
expires_delta=timedelta(hours=24)
|
||||
)
|
||||
|
||||
return jsonify({
|
||||
'access_token': access_token,
|
||||
'user': {
|
||||
'id': user.id,
|
||||
'username': user.username,
|
||||
'role': user.role
|
||||
}
|
||||
})
|
||||
|
||||
@users_bp.route('/me', methods=['GET'])
|
||||
@jwt_required()
|
||||
def get_current_user():
|
||||
user_id = get_jwt_identity()
|
||||
user = db.session.get(User, user_id)
|
||||
|
||||
if not user:
|
||||
return jsonify({'error': '用户不存在'}), 404
|
||||
|
||||
return jsonify({
|
||||
'id': user.id,
|
||||
'username': user.username,
|
||||
'email': user.email,
|
||||
'role': user.role,
|
||||
'status': user.status
|
||||
})
|
||||
|
||||
@users_bp.route('/users', methods=['POST'])
|
||||
@jwt_required()
|
||||
def create_user():
|
||||
current_user_id = get_jwt_identity()
|
||||
current_user = db.session.get(User, current_user_id)
|
||||
|
||||
if current_user.role != 'admin':
|
||||
return jsonify({'error': '权限不足'}), 403
|
||||
|
||||
data = request.json
|
||||
|
||||
if not data.get('username') or not data.get('password'):
|
||||
return jsonify({'error': '用户名和密码不能为空'}), 400
|
||||
|
||||
if User.query.filter_by(username=data['username']).first():
|
||||
return jsonify({'error': '用户名已存在'}), 400
|
||||
|
||||
user = User(
|
||||
username=data['username'],
|
||||
email=data.get('email'),
|
||||
role=data.get('role', 'user')
|
||||
)
|
||||
user.set_password(data['password'])
|
||||
db.session.add(user)
|
||||
db.session.commit()
|
||||
|
||||
return jsonify({
|
||||
'id': user.id,
|
||||
'username': user.username,
|
||||
'email': user.email,
|
||||
'role': user.role
|
||||
}), 201
|
||||
Reference in New Issue
Block a user