Files
service-finder/backend/app/api/v1/endpoints/admin.py

164 lines
6.4 KiB
Python
Executable File

from fastapi import APIRouter, Depends, HTTPException, status
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy import select, func, text, delete
from typing import List, Any, Dict, Optional
from datetime import datetime, timedelta
from app.api import deps
from app.models.identity import User, UserRole
from app.models.system import SystemParameter
from app.models.security import PendingAction, ActionStatus
from app.models.history import AuditLog, LogSeverity
from app.schemas.admin_security import PendingActionResponse, SecurityStatusResponse
from app.services.security_service import security_service
from app.services.translation_service import TranslationService
from pydantic import BaseModel
class ConfigUpdate(BaseModel):
key: str
value: Any
scope_level: str = "global"
scope_id: Optional[str] = None
category: str = "general"
router = APIRouter()
# --- 🛡️ ADMIN JOGOSULTSÁG ELLENŐRZŐ ---
async def check_admin_access(current_user: User = Depends(deps.get_current_active_user)):
"""Szigorú hozzáférés-ellenőrzés: Csak Admin vagy Superadmin."""
if current_user.role not in [UserRole.admin, UserRole.superadmin]:
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail="Sentinel jogosultság szükséges a művelethez!"
)
return current_user
# --- 🛰️ 1. SENTINEL: RENDSZERÁLLAPOT ÉS MONITORING ---
@router.get("/health-monitor", tags=["Sentinel Monitoring"])
async def get_system_health(
db: AsyncSession = Depends(deps.get_db),
admin: User = Depends(check_admin_access)
):
"""
Rendszer pulzusának ellenőrzése (pgAdmin nélkül).
Látod a felhasználók eloszlását, az eszközök számát és a kritikus hibákat.
"""
stats = {}
# Adatbázis statisztikák (Dynamic counts)
user_stats = await db.execute(text("SELECT subscription_plan, count(*) FROM data.users GROUP BY subscription_plan"))
stats["user_distribution"] = {row[0]: row[1] for row in user_stats}
asset_count = await db.execute(text("SELECT count(*) FROM data.assets"))
stats["total_assets"] = asset_count.scalar()
org_count = await db.execute(text("SELECT count(*) FROM data.organizations"))
stats["total_organizations"] = org_count.scalar()
# Biztonsági státusz (Kritikus logok az elmúlt 24 órában)
day_ago = datetime.now() - timedelta(days=1)
crit_logs = await db.execute(select(func.count(AuditLog.id)).where(
AuditLog.severity.in_([LogSeverity.critical, LogSeverity.emergency]),
AuditLog.timestamp >= day_ago
))
stats["critical_alerts_24h"] = crit_logs.scalar() or 0
return stats
# --- ⚖️ 2. SENTINEL: NÉGY SZEM ELV (Approval System) ---
@router.get("/pending-actions", response_model=List[PendingActionResponse], tags=["Sentinel Security"])
async def list_pending_actions(
db: AsyncSession = Depends(deps.get_db),
admin: User = Depends(check_admin_access)
):
"""Jóváhagyásra váró kritikus kérések listázása (pl. törlések, rang-emelések)."""
stmt = select(PendingAction).where(PendingAction.status == ActionStatus.pending)
result = await db.execute(stmt)
return result.scalars().all()
@router.post("/approve/{action_id}", tags=["Sentinel Security"])
async def approve_action(
action_id: int,
db: AsyncSession = Depends(deps.get_db),
admin: User = Depends(check_admin_access)
):
"""Művelet véglegesítése. Csak egy második admin hagyhatja jóvá az első kérését."""
try:
await security_service.approve_action(db, admin.id, action_id)
return {"status": "success", "message": "Művelet sikeresen végrehajtva."}
except Exception as e:
raise HTTPException(status_code=400, detail=str(e))
# --- ⚙️ 3. DINAMIKUS KONFIGURÁCIÓ (Hierarchical Config) ---
@router.get("/parameters", tags=["Dynamic Configuration"])
async def list_all_parameters(
db: AsyncSession = Depends(deps.get_db),
admin: User = Depends(check_admin_access)
):
"""Minden globális és lokális paraméter (Limitek, XP szorzók stb.) lekérése."""
result = await db.execute(select(SystemParameter))
return result.scalars().all()
@router.post("/parameters", tags=["Dynamic Configuration"])
async def set_parameter(
config: ConfigUpdate, # <--- Most már egy objektumot várunk a Body-ban
db: AsyncSession = Depends(deps.get_db),
admin: User = Depends(check_admin_access)
):
"""
Paraméter beállítása. A Swaggerben most már látsz egy JSON ablakot a 'value' számára!
"""
query = text("""
INSERT INTO data.system_parameters (key, value, scope_level, scope_id, category, last_modified_by)
VALUES (:key, :val, :sl, :sid, :cat, :user)
ON CONFLICT (key, scope_level, scope_id)
DO UPDATE SET
value = EXCLUDED.value,
category = EXCLUDED.category,
last_modified_by = EXCLUDED.last_modified_by,
updated_at = now()
""")
await db.execute(query, {
"key": config.key,
"val": config.value, # Itt bármilyen komplex JSON-t átadhatsz
"sl": config.scope_level,
"sid": config.scope_id,
"cat": config.category,
"user": admin.email
})
await db.commit()
return {"status": "success", "message": f"'{config.key}' frissítve."}
@router.delete("/parameters/{key}", tags=["Dynamic Configuration"])
async def delete_parameter(
key: str,
scope_level: str = "global",
scope_id: Optional[str] = None,
db: AsyncSession = Depends(deps.get_db),
admin: User = Depends(check_admin_access)
):
"""Egy adott konfiguráció törlése (visszaállás az eggyel magasabb szintű alapértelmezésre)."""
stmt = delete(SystemParameter).where(
SystemParameter.key == key,
SystemParameter.scope_level == scope_level,
SystemParameter.scope_id == scope_id
)
await db.execute(stmt)
await db.commit()
return {"status": "success", "message": "Konfiguráció törölve."}
# --- 🌍 4. UTILITY: FORDÍTÁSOK ---
@router.post("/translations/sync", tags=["System Utilities"])
async def sync_translations_to_json(
db: AsyncSession = Depends(deps.get_db),
admin: User = Depends(check_admin_access)
):
"""Szinkronizálja az adatbázisban tárolt fordításokat a JSON fájlokba."""
await TranslationService.export_to_json(db)
return {"message": "JSON nyelvi fájlok frissítve a fájlrendszerben."}