You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
api/app/routes/admin.py

234 lines
7.8 KiB

from fastapi import APIRouter, HTTPException, status, Depends
from bson import ObjectId
import pymongo
from datetime import datetime, timedelta
import app.config as config
from app.models import User, HTTPError
from app.models.config import SystemConfig, DBConfig
from app.routes.auth import users_collection
from app.routes.utils import get_admin_user
from app.dto import UserAdminDTO
# Database setup
client = pymongo.MongoClient(config.MONGODB_URL, username=config.MONGODB_USERNAME, password=config.MONGODB_PASSWORD)
db = client[config.MONGODB_DATABASE]
pins_collection = db["pins"]
images_collection = db["images"]
friends_collection = db["friends"]
pin_permissions_collection = db["pin_permissions"]
config_collection = db["config"]
admin_router = APIRouter(
prefix="/admin",
tags=["Admin"]
)
def serialize_mongo_doc(doc):
"""Convertit un document MongoDB en dictionnaire sérialisable"""
if doc is None:
return None
result = {}
for key, value in doc.items():
if isinstance(value, ObjectId):
result[key] = str(value)
elif isinstance(value, dict):
result[key] = serialize_mongo_doc(value)
elif isinstance(value, list):
result[key] = [serialize_mongo_doc(item) if isinstance(item, dict) else str(item) if isinstance(item, ObjectId) else item for item in value]
else:
result[key] = value
return result
def load_config():
"""Charge la configuration depuis la base de données ou utilise les valeurs par défaut"""
db_config = config_collection.find_one({"_id": "system_config"})
if db_config:
# Mettre à jour les variables de configuration
config.MAX_IMAGE_SIZE = db_config["config"]["max_image_size"]
config.MAX_IMAGES_PER_PIN = db_config["config"]["max_images_per_pin"]
config.MAX_IMAGES_PER_USER = db_config["config"]["max_images_per_user"]
config.ALLOWED_MIME_TYPES = db_config["config"]["allowed_image_types"]
config.MAX_PINS_PER_USER = db_config["config"]["max_pins_per_user"]
config.MAX_FRIENDS_PER_USER = db_config["config"]["max_friends_per_user"]
else:
# Créer la configuration par défaut dans la base de données
default_config = DBConfig(
config=SystemConfig(**config.DEFAULT_CONFIG),
updated_at=datetime.now(),
updated_by="system"
)
config_collection.insert_one({
"_id": "system_config",
**default_config.model_dump()
})
# Charger la configuration au démarrage
load_config()
@admin_router.get(
path="/stats",
responses={401: {"model": HTTPError}, 403: {"model": HTTPError}}
)
async def get_stats(admin_user: User = Depends(get_admin_user)):
# Statistiques générales
total_users = users_collection.count_documents({})
total_pins = pins_collection.count_documents({})
total_images = images_collection.count_documents({})
total_friends = friends_collection.count_documents({"status": "accepted"})
# Statistiques des 30 derniers jours
thirty_days_ago = datetime.now() - timedelta(days=30)
new_users = users_collection.count_documents({
"created_at": {"$gte": thirty_days_ago}
})
new_pins = pins_collection.count_documents({
"created_at": {"$gte": thirty_days_ago}
})
new_images = images_collection.count_documents({
"metadata.created_at": {"$gte": thirty_days_ago.isoformat()}
})
# Top utilisateurs
top_users = list(users_collection.aggregate([
{"$lookup": {
"from": "pins",
"let": { "user_id": { "$toString": "$_id" } },
"pipeline": [
{ "$match": { "$expr": { "$eq": ["$user_id", "$$user_id"] } } }
],
"as": "pins"
}},
{"$project": {
"username": 1,
"pin_count": {"$size": "$pins"}
}},
{"$sort": {"pin_count": -1}},
{"$limit": 5}
]))
# Top pins (les plus partagés)
top_pins = list(pin_permissions_collection.aggregate([
{"$group": {
"_id": "$pin_id",
"share_count": {"$sum": 1}
}},
{"$sort": {"share_count": -1}},
{"$limit": 5},
{"$lookup": {
"from": "pins",
"localField": "_id",
"foreignField": "_id",
"as": "pin_info"
}},
{"$unwind": "$pin_info"},
{"$project": {
"pin_id": "$_id",
"share_count": 1,
"title": "$pin_info.title"
}}
]))
# Statistiques de stockage
total_storage = sum(
image["metadata"]["size"]
for image in images_collection.find({}, {"metadata.size": 1})
)
return {
"general": {
"total_users": total_users,
"total_pins": total_pins,
"total_images": total_images,
"total_friends": total_friends,
"total_storage_bytes": total_storage
},
"last_30_days": {
"new_users": new_users,
"new_pins": new_pins,
"new_images": new_images
},
"top_users": [serialize_mongo_doc(user) for user in top_users],
"top_shared_pins": [serialize_mongo_doc(pin) for pin in top_pins]
}
@admin_router.get(
path="/config",
response_model=SystemConfig,
responses={401: {"model": HTTPError}, 403: {"model": HTTPError}}
)
async def get_config(admin_user: User = Depends(get_admin_user)):
db_config = config_collection.find_one({"_id": "system_config"})
if not db_config:
return SystemConfig(**config.DEFAULT_CONFIG)
return SystemConfig(**db_config["config"])
@admin_router.patch(
path="/config",
response_model=SystemConfig,
responses={401: {"model": HTTPError}, 403: {"model": HTTPError}}
)
async def update_config(
new_config: SystemConfig,
admin_user: User = Depends(get_admin_user)
):
# Mettre à jour la configuration en base de données
db_config = DBConfig(
config=new_config,
updated_at=datetime.now(),
updated_by=admin_user.uid
)
config_collection.update_one(
{"_id": "system_config"},
{"$set": db_config.model_dump()},
upsert=True
)
# Mettre à jour les variables de configuration
config.MAX_IMAGE_SIZE = new_config.max_image_size
config.MAX_IMAGES_PER_PIN = new_config.max_images_per_pin
config.MAX_IMAGES_PER_USER = new_config.max_images_per_user
config.ALLOWED_MIME_TYPES = new_config.allowed_image_types
config.MAX_PINS_PER_USER = new_config.max_pins_per_user
config.MAX_FRIENDS_PER_USER = new_config.max_friends_per_user
return new_config
@admin_router.get(
path="/users",
responses={401: {"model": HTTPError}, 403: {"model": HTTPError}},
response_model=list[UserAdminDTO]
)
async def list_users(admin_user: User = Depends(get_admin_user)):
"""Liste tous les utilisateurs (sans le mot de passe) - Route admin uniquement"""
users = users_collection.find({}, {"password": 0}) # Exclure le mot de passe
users_list = []
for user in users:
user["uid"] = str(user["_id"])
user = UserAdminDTO(**user)
users_list.append(user)
return users_list
@admin_router.delete(
path="/user/{uid}",
responses={401: {"model": HTTPError}, 403: {"model": HTTPError}, 404: {"model": HTTPError}, 400: {"model": HTTPError}}
)
async def delete_user(uid: str, admin_user: User = Depends(get_admin_user)):
try:
ObjectId(uid)
except:
raise HTTPException(status_code=400, detail="UID invalide")
user = users_collection.find_one({"_id": ObjectId(uid)})
if not user:
raise HTTPException(status_code=404, detail="Utilisateur non trouvé")
users_collection.delete_one({"_id": ObjectId(uid)})
return {"message": "Utilisateur supprimé avec succès"}