feat: Add comprehensive admin user management system
- Add user status toggle (activate/deactivate) functionality - Add user deletion with post/comment transfer to admin - Implement safety checks for admin protection - Add interactive JavaScript for user management actions - Update admin users interface with action buttons - Add confirmation dialogs for destructive operations - Update README with new admin features and capabilities - Add database migration utility for future updates Features: - Toggle user active/inactive status - Delete users with content preservation - Transfer all posts/comments to admin on user deletion - Prevent admin self-modification and deletion - AJAX-powered interface with real-time feedback - Comprehensive error handling and user notifications
This commit is contained in:
@@ -190,6 +190,80 @@ def user_detail(user_id):
|
||||
user_posts=user_posts,
|
||||
user_comments=user_comments)
|
||||
|
||||
@admin.route('/users/<int:user_id>/toggle-status', methods=['POST'])
|
||||
@login_required
|
||||
@admin_required
|
||||
def toggle_user_status(user_id):
|
||||
"""Toggle user active/inactive status"""
|
||||
user = User.query.get_or_404(user_id)
|
||||
|
||||
# Prevent self-modification if current user is admin
|
||||
if user.is_admin and user.id == current_user.id:
|
||||
return jsonify({'success': False, 'error': 'Cannot deactivate your own admin account'})
|
||||
|
||||
# Toggle status
|
||||
user.is_active = not user.is_active
|
||||
status = "activated" if user.is_active else "deactivated"
|
||||
|
||||
try:
|
||||
db.session.commit()
|
||||
return jsonify({
|
||||
'success': True,
|
||||
'message': f'User {user.nickname} has been {status}',
|
||||
'is_active': user.is_active
|
||||
})
|
||||
except Exception as e:
|
||||
db.session.rollback()
|
||||
return jsonify({'success': False, 'error': f'Failed to update user status: {str(e)}'})
|
||||
|
||||
@admin.route('/users/<int:user_id>/delete', methods=['DELETE', 'POST'])
|
||||
@login_required
|
||||
@admin_required
|
||||
def delete_user(user_id):
|
||||
"""Delete user and transfer their posts to admin"""
|
||||
user = User.query.get_or_404(user_id)
|
||||
|
||||
# Prevent self-deletion
|
||||
if user.id == current_user.id:
|
||||
return jsonify({'success': False, 'error': 'Cannot delete your own account'})
|
||||
|
||||
# Prevent deletion of other admins
|
||||
if user.is_admin:
|
||||
return jsonify({'success': False, 'error': 'Cannot delete admin accounts'})
|
||||
|
||||
try:
|
||||
# Transfer all user's posts to current admin
|
||||
user_posts = Post.query.filter_by(author_id=user.id).all()
|
||||
for post in user_posts:
|
||||
post.author_id = current_user.id
|
||||
|
||||
# Transfer all user's comments to current admin
|
||||
user_comments = Comment.query.filter_by(author_id=user.id).all()
|
||||
for comment in user_comments:
|
||||
comment.author_id = current_user.id
|
||||
|
||||
# Delete user's likes (they will be orphaned)
|
||||
Like.query.filter_by(user_id=user.id).delete()
|
||||
|
||||
# Delete user's page views
|
||||
PageView.query.filter_by(user_id=user.id).delete()
|
||||
|
||||
username = user.nickname
|
||||
user_posts_count = len(user_posts)
|
||||
|
||||
# Delete the user
|
||||
db.session.delete(user)
|
||||
db.session.commit()
|
||||
|
||||
return jsonify({
|
||||
'success': True,
|
||||
'message': f'User {username} has been deleted. {user_posts_count} posts transferred to {current_user.nickname}.'
|
||||
})
|
||||
|
||||
except Exception as e:
|
||||
db.session.rollback()
|
||||
return jsonify({'success': False, 'error': f'Failed to delete user: {str(e)}'})
|
||||
|
||||
@admin.route('/analytics')
|
||||
@login_required
|
||||
@admin_required
|
||||
|
||||
@@ -74,8 +74,17 @@
|
||||
<i class="fas fa-eye"></i>
|
||||
</a>
|
||||
{% if not user.is_admin or current_user.id != user.id %}
|
||||
<button class="btn btn-sm btn-outline-warning" title="Toggle Status" disabled>
|
||||
<i class="fas fa-toggle-on"></i>
|
||||
<button class="btn btn-sm btn-outline-warning"
|
||||
title="{{ 'Deactivate User' if user.is_active else 'Activate User' }}"
|
||||
onclick="toggleUserStatus({{ user.id }}, '{{ user.nickname }}', {{ user.is_active|lower }})">
|
||||
<i class="fas fa-{{ 'toggle-on' if user.is_active else 'toggle-off' }}"></i>
|
||||
</button>
|
||||
{% endif %}
|
||||
{% if not user.is_admin and current_user.id != user.id %}
|
||||
<button class="btn btn-sm btn-outline-danger"
|
||||
title="Delete User"
|
||||
onclick="confirmDeleteUser({{ user.id }}, '{{ user.nickname }}', {{ user.posts.count() }})">
|
||||
<i class="fas fa-trash"></i>
|
||||
</button>
|
||||
{% endif %}
|
||||
</div>
|
||||
@@ -134,4 +143,88 @@
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<script>
|
||||
function toggleUserStatus(userId, nickname, currentStatus) {
|
||||
const action = currentStatus ? 'deactivate' : 'activate';
|
||||
const message = `Are you sure you want to ${action} user "${nickname}"?`;
|
||||
|
||||
if (confirm(message)) {
|
||||
fetch(`/admin/users/${userId}/toggle-status`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
}
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.success) {
|
||||
showAlert(data.message, 'success');
|
||||
setTimeout(() => {
|
||||
location.reload();
|
||||
}, 1000);
|
||||
} else {
|
||||
showAlert(data.message || 'An error occurred', 'danger');
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error:', error);
|
||||
showAlert('An error occurred while updating user status', 'danger');
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function confirmDeleteUser(userId, nickname, postsCount) {
|
||||
let message = `Are you sure you want to delete user "${nickname}"?`;
|
||||
if (postsCount > 0) {
|
||||
message += `\n\nThis user has ${postsCount} post(s) that will be transferred to you as the admin.`;
|
||||
}
|
||||
message += '\n\nThis action cannot be undone.';
|
||||
|
||||
if (confirm(message)) {
|
||||
fetch(`/admin/users/${userId}/delete`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
}
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.success) {
|
||||
showAlert(data.message, 'success');
|
||||
setTimeout(() => {
|
||||
location.reload();
|
||||
}, 1000);
|
||||
} else {
|
||||
showAlert(data.message || 'An error occurred', 'danger');
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error:', error);
|
||||
showAlert('An error occurred while deleting user', 'danger');
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function showAlert(message, type) {
|
||||
// Create alert container if it doesn't exist
|
||||
let alertContainer = document.querySelector('.alert-container');
|
||||
if (!alertContainer) {
|
||||
alertContainer = document.createElement('div');
|
||||
alertContainer.className = 'alert-container mt-3';
|
||||
document.querySelector('.container-fluid').insertBefore(
|
||||
alertContainer,
|
||||
document.querySelector('.container-fluid').firstChild
|
||||
);
|
||||
}
|
||||
|
||||
alertContainer.innerHTML = `
|
||||
<div class="alert alert-${type} alert-dismissible fade show" role="alert">
|
||||
${message}
|
||||
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
</script>
|
||||
|
||||
{% endblock %}
|
||||
|
||||
Reference in New Issue
Block a user