feat: SuperAdmin bootstrap, i18n sync fix and AssetAssignment ORM fix

- Fixed AttributeError in User model (added region_code, preferred_language)
- Fixed InvalidRequestError in AssetAssignment (added organization relationship)
- Configured STATIC_DIR for translation sync
- Applied Alembic migrations for user schema updates
This commit is contained in:
2026-02-10 21:01:58 +00:00
parent e255fea3a5
commit 425f598fa3
51 changed files with 1753 additions and 204 deletions

View File

@@ -1,79 +1,115 @@
from fastapi import APIRouter, Depends, HTTPException, status
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy import select
from typing import List
from sqlalchemy import select, func
from typing import List, Any, Dict
from datetime import datetime, timedelta
from app.db.session import get_db
from app.api import deps
from app.models.user import User, UserRole
from app.models.system_settings import SystemSetting # ÚJ import
from app.models.gamification import PointRule, LevelConfig, RegionalSetting
from app.models.translation import Translation
from app.services.translation_service import TranslationService
from app.models.identity import User, UserRole
from app.models.system_config 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
# Feltételezve, hogy a JSON-alapú TranslationService-ed már készen van
from app.services.translation_service import TranslationService
router = APIRouter()
def check_admin_access(current_user: User, required_roles: List[UserRole]):
if current_user.role not in required_roles:
# --- 🛡️ ADMIN JOGOSULTSÁG ELLENŐRZŐ ---
async def check_admin_access(current_user: User = Depends(deps.get_current_active_user)):
if current_user.role not in [UserRole.admin, UserRole.superadmin]:
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail="Nincs jogosultságod ehhez a művelethez."
status_code=status.HTTP_403_FORBIDDEN,
detail="Admin jogosultság szükséges!"
)
return current_user
# --- ⚙️ ÚJ: DINAMIKUS RENDSZERBEÁLLÍTÁSOK (Pl. Jármű limit) ---
# --- 1. SENTINEL: NÉGY SZEM ELV (Approval System) ---
@router.get("/settings", response_model=List[dict])
async def get_all_system_settings(
db: AsyncSession = Depends(get_db),
current_user: User = Depends(deps.get_current_user)
@router.get("/pending-actions", response_model=List[PendingActionResponse])
async def list_pending_actions(
db: AsyncSession = Depends(deps.get_db),
admin: User = Depends(check_admin_access)
):
"""Az összes globális rendszerbeállítás listázása."""
check_admin_access(current_user, [UserRole.SUPERUSER])
result = await db.execute(select(SystemSetting))
settings = result.scalars().all()
return [{"key": s.key, "value": s.value, "description": s.description} for s in settings]
"""Jóváhagyásra váró kritikus kérések listázása."""
stmt = select(PendingAction).where(PendingAction.status == ActionStatus.pending)
result = await db.execute(stmt)
return result.scalars().all()
@router.post("/approve/{action_id}")
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 (második admin által)."""
try:
await security_service.approve_action(db, admin.id, action_id)
return {"status": "success", "message": "Művelet végrehajtva."}
except Exception as e:
raise HTTPException(status_code=400, detail=str(e))
# --- 2. SENTINEL: BIZTONSÁGI ÖSSZEGZÉS ---
@router.get("/security-status", response_model=SecurityStatusResponse)
async def get_security_status(
db: AsyncSession = Depends(deps.get_db),
admin: User = Depends(check_admin_access)
):
"""Rendszerállapot: Zárolt júzerek és kritikus események."""
day_ago = datetime.now() - timedelta(days=1)
crit_count = (await db.execute(select(func.count(AuditLog.id)).where(
AuditLog.severity.in_([LogSeverity.critical, LogSeverity.emergency]),
AuditLog.timestamp >= day_ago
))).scalar() or 0
locked_count = (await db.execute(select(func.count(User.id)).where(
User.is_active == False, User.is_deleted == False
))).scalar() or 0
return {
"total_pending": (await db.execute(select(func.count(PendingAction.id)).where(PendingAction.status == ActionStatus.pending))).scalar() or 0,
"critical_logs_last_24h": crit_count,
"emergency_locks_active": locked_count
}
# --- 3. RENDSZERBEÁLLÍTÁSOK (Dynamic Config) ---
@router.get("/settings")
async def get_settings(db: AsyncSession = Depends(deps.get_db), admin: User = Depends(check_admin_access)):
"""Minden globális paraméter (Gamification, Limitek stb.) lekérése."""
result = await db.execute(select(SystemParameter))
return result.scalars().all()
@router.put("/settings/{key}")
async def update_system_setting(
key: str,
new_value: int, # Később lehet JSON is, ha komplexebb a beállítás
db: AsyncSession = Depends(get_db),
current_user: User = Depends(deps.get_current_user)
):
"""Egy adott beállítás (pl. FREE_VEHICLE_LIMIT) módosítása."""
check_admin_access(current_user, [UserRole.SUPERUSER])
async def update_setting(key: str, value: Any, db: AsyncSession = Depends(deps.get_db), admin: User = Depends(check_admin_access)):
"""Paraméter módosítása és Audit Log generálása."""
stmt = select(SystemParameter).where(SystemParameter.key == key)
param = (await db.execute(stmt)).scalar_one_or_none()
if not param:
raise HTTPException(status_code=404, detail="Nincs ilyen beállítás.")
result = await db.execute(select(SystemSetting).where(SystemSetting.key == key))
setting = result.scalar_one_or_none()
old_val = param.value
param.value = value
if not setting:
raise HTTPException(status_code=404, detail="Beállítás nem található")
setting.value = new_value
await security_service.log_event(
db, admin.id, action="SETTING_CHANGE", severity=LogSeverity.warning,
old_data={key: old_val}, new_data={key: value}
)
await db.commit()
return {"status": "success", "key": key, "new_value": new_value}
return {"status": "success", "key": key, "new_value": value}
# --- 🌍 JSON FORDÍTÁSOK KEZELÉSE ---
# --- 🌍 FORDÍTÁSOK KEZELÉSE (Meglévő kódod) ---
@router.post("/translations", status_code=status.HTTP_201_CREATED)
async def add_translation_draft(
key: str, lang: str, value: str,
db: AsyncSession = Depends(get_db),
current_user: User = Depends(deps.get_current_user)
@router.post("/translations/sync")
async def sync_translations_to_json(
db: AsyncSession = Depends(deps.get_db),
admin: User = Depends(check_admin_access)
):
check_admin_access(current_user, [UserRole.SUPERUSER, UserRole.REGIONAL_ADMIN])
new_t = Translation(key=key, lang_code=lang, value=value, is_published=False)
db.add(new_t)
await db.commit()
return {"message": "Fordítás piszkozatként mentve. Ne felejtsd el publikálni!"}
@router.post("/translations/publish")
async def publish_translations(
db: AsyncSession = Depends(get_db),
current_user: User = Depends(deps.get_current_user)
):
check_admin_access(current_user, [UserRole.SUPERUSER, UserRole.REGIONAL_ADMIN])
await TranslationService.publish_all(db)
return {"message": "Sikeres publikálás! A változások minden szerveren élesedtek."}
"""Szinkronizálja az adatbázisban tárolt fordításokat a JSON fájlokba."""
# A TranslationService-ben kell megírni a fájlbaíró logikát
await TranslationService.export_to_json(db)
return {"message": "JSON nyelvi fájlok frissítve."}