feat: Multi-user system with PostgreSQL - WIP temporary save

This commit is contained in:
TIANHE
2026-01-14 05:29:55 +08:00
parent 996e3b38fe
commit 61a5e5e6aa
68 changed files with 91057 additions and 1920 deletions
+317
View File
@@ -0,0 +1,317 @@
"""
User Management API Routes
Provides endpoints for user CRUD operations, role management, etc.
Only accessible by admin users.
"""
from flask import Blueprint, request, jsonify, g
from app.services.user_service import get_user_service
from app.utils.auth import login_required, admin_required
from app.utils.logger import get_logger
logger = get_logger(__name__)
user_bp = Blueprint('user_manage', __name__)
@user_bp.route('/list', methods=['GET'])
@login_required
@admin_required
def list_users():
"""
List all users (admin only).
Query params:
page: int (default 1)
page_size: int (default 20, max 100)
"""
try:
page = request.args.get('page', 1, type=int)
page_size = request.args.get('page_size', 20, type=int)
page_size = min(100, max(1, page_size))
result = get_user_service().list_users(page=page, page_size=page_size)
return jsonify({
'code': 1,
'msg': 'success',
'data': result
})
except Exception as e:
logger.error(f"list_users failed: {e}")
return jsonify({'code': 0, 'msg': str(e), 'data': None}), 500
@user_bp.route('/detail', methods=['GET'])
@login_required
@admin_required
def get_user_detail():
"""Get user detail by ID (admin only)"""
try:
user_id = request.args.get('id', type=int)
if not user_id:
return jsonify({'code': 0, 'msg': 'Missing user id', 'data': None}), 400
user = get_user_service().get_user_by_id(user_id)
if not user:
return jsonify({'code': 0, 'msg': 'User not found', 'data': None}), 404
return jsonify({
'code': 1,
'msg': 'success',
'data': user
})
except Exception as e:
logger.error(f"get_user_detail failed: {e}")
return jsonify({'code': 0, 'msg': str(e), 'data': None}), 500
@user_bp.route('/create', methods=['POST'])
@login_required
@admin_required
def create_user():
"""
Create a new user (admin only).
Request body:
username: str (required)
password: str (required)
email: str (optional)
nickname: str (optional)
role: str (optional, default 'user')
"""
try:
data = request.get_json() or {}
user_id = get_user_service().create_user(data)
return jsonify({
'code': 1,
'msg': 'User created successfully',
'data': {'id': user_id}
})
except ValueError as e:
return jsonify({'code': 0, 'msg': str(e), 'data': None}), 400
except Exception as e:
logger.error(f"create_user failed: {e}")
return jsonify({'code': 0, 'msg': str(e), 'data': None}), 500
@user_bp.route('/update', methods=['PUT'])
@login_required
@admin_required
def update_user():
"""
Update user information (admin only).
Query params:
id: int (required)
Request body:
email: str (optional)
nickname: str (optional)
role: str (optional)
status: str (optional)
"""
try:
user_id = request.args.get('id', type=int)
if not user_id:
return jsonify({'code': 0, 'msg': 'Missing user id', 'data': None}), 400
data = request.get_json() or {}
success = get_user_service().update_user(user_id, data)
if success:
return jsonify({'code': 1, 'msg': 'User updated successfully', 'data': None})
else:
return jsonify({'code': 0, 'msg': 'Update failed', 'data': None}), 400
except Exception as e:
logger.error(f"update_user failed: {e}")
return jsonify({'code': 0, 'msg': str(e), 'data': None}), 500
@user_bp.route('/delete', methods=['DELETE'])
@login_required
@admin_required
def delete_user():
"""Delete a user (admin only)"""
try:
user_id = request.args.get('id', type=int)
if not user_id:
return jsonify({'code': 0, 'msg': 'Missing user id', 'data': None}), 400
# Prevent deleting self
if hasattr(g, 'user_id') and g.user_id == user_id:
return jsonify({'code': 0, 'msg': 'Cannot delete yourself', 'data': None}), 400
success = get_user_service().delete_user(user_id)
if success:
return jsonify({'code': 1, 'msg': 'User deleted successfully', 'data': None})
else:
return jsonify({'code': 0, 'msg': 'Delete failed', 'data': None}), 400
except Exception as e:
logger.error(f"delete_user failed: {e}")
return jsonify({'code': 0, 'msg': str(e), 'data': None}), 500
@user_bp.route('/reset-password', methods=['POST'])
@login_required
@admin_required
def reset_user_password():
"""
Reset a user's password (admin only).
Request body:
user_id: int (required)
new_password: str (required)
"""
try:
data = request.get_json() or {}
user_id = data.get('user_id')
new_password = data.get('new_password', '')
if not user_id:
return jsonify({'code': 0, 'msg': 'Missing user_id', 'data': None}), 400
if len(new_password) < 6:
return jsonify({'code': 0, 'msg': 'Password must be at least 6 characters', 'data': None}), 400
success = get_user_service().reset_password(user_id, new_password)
if success:
return jsonify({'code': 1, 'msg': 'Password reset successfully', 'data': None})
else:
return jsonify({'code': 0, 'msg': 'Reset failed', 'data': None}), 400
except ValueError as e:
return jsonify({'code': 0, 'msg': str(e), 'data': None}), 400
except Exception as e:
logger.error(f"reset_user_password failed: {e}")
return jsonify({'code': 0, 'msg': str(e), 'data': None}), 500
@user_bp.route('/roles', methods=['GET'])
@login_required
@admin_required
def get_roles():
"""Get available roles and their permissions"""
service = get_user_service()
roles = []
for role in service.ROLES:
roles.append({
'id': role,
'name': role.capitalize(),
'permissions': service.get_user_permissions(role)
})
return jsonify({
'code': 1,
'msg': 'success',
'data': {'roles': roles}
})
# Self-service endpoints (accessible by any logged-in user)
@user_bp.route('/profile', methods=['GET'])
@login_required
def get_profile():
"""Get current user's profile"""
try:
user_id = getattr(g, 'user_id', None)
if not user_id:
return jsonify({'code': 0, 'msg': 'Not authenticated', 'data': None}), 401
user = get_user_service().get_user_by_id(user_id)
if not user:
return jsonify({'code': 0, 'msg': 'User not found', 'data': None}), 404
# Add permissions
user['permissions'] = get_user_service().get_user_permissions(user.get('role', 'user'))
return jsonify({
'code': 1,
'msg': 'success',
'data': user
})
except Exception as e:
logger.error(f"get_profile failed: {e}")
return jsonify({'code': 0, 'msg': str(e), 'data': None}), 500
@user_bp.route('/profile/update', methods=['PUT'])
@login_required
def update_profile():
"""
Update current user's profile (limited fields).
Request body:
nickname: str (optional)
email: str (optional)
avatar: str (optional)
"""
try:
user_id = getattr(g, 'user_id', None)
if not user_id:
return jsonify({'code': 0, 'msg': 'Not authenticated', 'data': None}), 401
data = request.get_json() or {}
# Only allow updating certain fields for self-service
allowed = {}
for field in ['nickname', 'email', 'avatar']:
if field in data:
allowed[field] = data[field]
if not allowed:
return jsonify({'code': 0, 'msg': 'No valid fields to update', 'data': None}), 400
success = get_user_service().update_user(user_id, allowed)
if success:
return jsonify({'code': 1, 'msg': 'Profile updated successfully', 'data': None})
else:
return jsonify({'code': 0, 'msg': 'Update failed', 'data': None}), 400
except Exception as e:
logger.error(f"update_profile failed: {e}")
return jsonify({'code': 0, 'msg': str(e), 'data': None}), 500
@user_bp.route('/change-password', methods=['POST'])
@login_required
def change_password():
"""
Change current user's password.
Request body:
old_password: str (required)
new_password: str (required)
"""
try:
user_id = getattr(g, 'user_id', None)
if not user_id:
return jsonify({'code': 0, 'msg': 'Not authenticated', 'data': None}), 401
data = request.get_json() or {}
old_password = data.get('old_password', '')
new_password = data.get('new_password', '')
if not old_password or not new_password:
return jsonify({'code': 0, 'msg': 'Both old and new password required', 'data': None}), 400
if len(new_password) < 6:
return jsonify({'code': 0, 'msg': 'New password must be at least 6 characters', 'data': None}), 400
success = get_user_service().change_password(user_id, old_password, new_password)
if success:
return jsonify({'code': 1, 'msg': 'Password changed successfully', 'data': None})
else:
return jsonify({'code': 0, 'msg': 'Old password incorrect', 'data': None}), 400
except ValueError as e:
return jsonify({'code': 0, 'msg': str(e), 'data': None}), 400
except Exception as e:
logger.error(f"change_password failed: {e}")
return jsonify({'code': 0, 'msg': str(e), 'data': None}), 500