2da2a5bb27
- Route /api/admin avec middleware require_admin - Liste utilisateurs avec statut, role, dates - Actions: activer/desactiver, promouvoir/rétrograder admin, supprimer - Dashboard stats (utilisateurs, téléchargements) - Template admin_panel.html avec table responsive - Champ is_admin ajoute au modele User - Migration automatique colonne is_admin - Protection: impossible de modifier son propre compte Closes #16
166 lines
5.9 KiB
Python
166 lines
5.9 KiB
Python
"""
|
|
Admin panel routes for Ohm Stream Downloader API.
|
|
"""
|
|
|
|
from datetime import datetime
|
|
from typing import Optional, List
|
|
|
|
from fastapi import APIRouter, Depends, HTTPException, Request, Response
|
|
from fastapi.templating import Jinja2Templates
|
|
from sqlmodel import Session, select
|
|
|
|
from app.database import get_session, engine
|
|
from app.models.auth import User, UserTable
|
|
from app.routers.router_auth import get_current_user_from_token
|
|
|
|
router = APIRouter(prefix="/api/admin", tags=["admin"])
|
|
templates = Jinja2Templates(directory="templates")
|
|
|
|
|
|
async def require_admin(current_user: User = Depends(get_current_user_from_token)) -> User:
|
|
"""Dependency that requires the current user to be an admin."""
|
|
if not current_user.is_admin:
|
|
raise HTTPException(status_code=403, detail="Admin access required")
|
|
return current_user
|
|
|
|
|
|
@router.get("/users")
|
|
async def list_users(
|
|
current_user: User = Depends(require_admin),
|
|
):
|
|
"""List all users (admin only)"""
|
|
with Session(engine) as session:
|
|
statement = select(UserTable)
|
|
users = session.exec(statement).all()
|
|
return {
|
|
"users": [
|
|
{
|
|
"id": u.id,
|
|
"username": u.username,
|
|
"email": u.email,
|
|
"full_name": u.full_name,
|
|
"is_active": u.is_active,
|
|
"is_admin": u.is_admin,
|
|
"created_at": u.created_at.isoformat() if u.created_at else None,
|
|
"last_login": u.last_login.isoformat() if u.last_login else None,
|
|
}
|
|
for u in users
|
|
],
|
|
"total": len(users),
|
|
}
|
|
|
|
|
|
@router.get("/stats")
|
|
async def get_admin_stats(
|
|
current_user: User = Depends(require_admin),
|
|
):
|
|
"""Get admin dashboard statistics"""
|
|
from app.download_manager import DownloadManager
|
|
from main import download_manager
|
|
|
|
with Session(engine) as session:
|
|
total_users = len(session.exec(select(UserTable)).all())
|
|
active_users = len(session.exec(select(UserTable).where(UserTable.is_active == True)).all())
|
|
admin_users = len(session.exec(select(UserTable).where(UserTable.is_admin == True)).all())
|
|
|
|
tasks = download_manager.get_all_tasks()
|
|
total_downloads = len(tasks)
|
|
completed_downloads = len([t for t in tasks if t.status == "completed"])
|
|
active_downloads = len([t for t in tasks if t.status in ("downloading", "pending")])
|
|
|
|
return {
|
|
"users": {
|
|
"total": total_users,
|
|
"active": active_users,
|
|
"admins": admin_users,
|
|
},
|
|
"downloads": {
|
|
"total": total_downloads,
|
|
"completed": completed_downloads,
|
|
"active": active_downloads,
|
|
},
|
|
}
|
|
|
|
|
|
@router.put("/users/{user_id}/toggle-active")
|
|
async def toggle_user_active(
|
|
user_id: str,
|
|
response: Response,
|
|
current_user: User = Depends(require_admin),
|
|
):
|
|
"""Activate or deactivate a user"""
|
|
with Session(engine) as session:
|
|
user = session.exec(select(UserTable).where(UserTable.id == user_id)).first()
|
|
if not user:
|
|
raise HTTPException(status_code=404, detail="User not found")
|
|
if user.id == current_user.id:
|
|
raise HTTPException(status_code=400, detail="Cannot modify your own account")
|
|
user.is_active = not user.is_active
|
|
session.add(user)
|
|
session.commit()
|
|
status = "active" if user.is_active else "inactive"
|
|
response.headers["HX-Trigger"] = f'{{"show-toast": {{"message": "User {user.username} is now {status}", "type": "success"}}}}'
|
|
return {"id": user_id, "is_active": user.is_active}
|
|
|
|
|
|
@router.put("/users/{user_id}/toggle-admin")
|
|
async def toggle_user_admin(
|
|
user_id: str,
|
|
response: Response,
|
|
current_user: User = Depends(require_admin),
|
|
):
|
|
"""Promote or demote a user to/from admin"""
|
|
with Session(engine) as session:
|
|
user = session.exec(select(UserTable).where(UserTable.id == user_id)).first()
|
|
if not user:
|
|
raise HTTPException(status_code=404, detail="User not found")
|
|
if user.id == current_user.id:
|
|
raise HTTPException(status_code=400, detail="Cannot modify your own admin status")
|
|
user.is_admin = not user.is_admin
|
|
session.add(user)
|
|
session.commit()
|
|
role = "admin" if user.is_admin else "user"
|
|
response.headers["HX-Trigger"] = f'{{"show-toast": {{"message": "{user.username} is now {role}", "type": "success"}}}}'
|
|
return {"id": user_id, "is_admin": user.is_admin}
|
|
|
|
|
|
@router.delete("/users/{user_id}")
|
|
async def delete_user(
|
|
user_id: str,
|
|
response: Response,
|
|
current_user: User = Depends(require_admin),
|
|
):
|
|
"""Delete a user"""
|
|
with Session(engine) as session:
|
|
user = session.exec(select(UserTable).where(UserTable.id == user_id)).first()
|
|
if not user:
|
|
raise HTTPException(status_code=404, detail="User not found")
|
|
if user.id == current_user.id:
|
|
raise HTTPException(status_code=400, detail="Cannot delete your own account")
|
|
username = user.username
|
|
session.delete(user)
|
|
session.commit()
|
|
response.headers["HX-Trigger"] = f'{{"show-toast": {{"message": "User {username} deleted", "type": "info"}}}}'
|
|
return {"deleted": user_id}
|
|
|
|
|
|
@router.get("/ui")
|
|
async def get_admin_ui(
|
|
request: Request,
|
|
current_user: Optional[User] = Depends(get_current_user_from_token),
|
|
):
|
|
"""Get admin panel UI"""
|
|
if current_user is None or not current_user.is_admin:
|
|
from app.routers.router_auth import get_optional_user
|
|
return templates.TemplateResponse(
|
|
"components/login_prompt.html", {"request": request}
|
|
)
|
|
|
|
with Session(engine) as session:
|
|
users = session.exec(select(UserTable)).all()
|
|
|
|
return templates.TemplateResponse(
|
|
"components/admin_panel.html",
|
|
{"request": request, "users": users, "current_user": current_user},
|
|
)
|