Files
service-finder/backend/app/scripts/unified_db_audit.py
2026-03-13 10:22:41 +00:00

133 lines
5.8 KiB
Python

import asyncio
import os
import sys
import importlib.util
from pathlib import Path
from sqlalchemy import inspect, text
from sqlalchemy.ext.asyncio import create_async_engine
from sqlalchemy.dialects.postgresql import JSONB, ENUM, NUMERIC
# Elérési utak beállítása
BASE_DIR = Path(__file__).resolve().parents[2]
sys.path.append(str(BASE_DIR))
try:
from app.database import Base, engine
from app.core.config import settings
except ImportError as e:
print(f"❌ Hiba az alapvető importoknál: {e}")
sys.exit(1)
def dynamic_import_models(models_dir: Path):
"""
Automatikusan bejárja az app/models mappát és beimportál minden .py fájlt,
hogy a Base.metadata.tables feltöltődjön.
"""
print(f"🔍 Modellek dinamikus felderítése itt: {models_dir}...")
count = 0
for root, _, files in os.walk(models_dir):
for file in files:
if file.endswith(".py") and file != "__init__.py":
full_path = Path(root) / file
# Modul név képzése (pl. app.models.identity.user)
rel_path = full_path.relative_to(BASE_DIR)
module_name = str(rel_path).replace(os.sep, ".").replace(".py", "")
try:
spec = importlib.util.spec_from_file_location(module_name, full_path)
module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(module)
count += 1
except Exception as e:
print(f" ⚠️ Nem sikerült importálni: {module_name} -> {e}")
print(f"{count} modell fájl sikeresen betöltve a memóriába.\n")
async def run_unified_audit():
# 1. Modellek betöltése
models_path = BASE_DIR / "app" / "models"
dynamic_import_models(models_path)
print(f"🔗 Kapcsolódás az adatbázishoz: {settings.POSTGRES_DB}")
async with engine.connect() as conn:
inspector = await conn.run_sync(inspect)
all_db_schemas = await conn.run_sync(lambda c: inspector.get_schema_names())
# Kigyűjtjük a modellekben definiált sémákat
expected_schemas = sorted({t.schema for t in Base.metadata.sorted_tables if t.schema})
mismatches = 0
suggestions = []
for sc in expected_schemas:
print(f"\n--- 🛰️ DOMAIN AUDIT: '{sc}' ---")
if sc not in all_db_schemas:
print(f"❌ KRITIKUS: A(z) '{sc}' séma hiányzik!")
mismatches += 1
continue
db_tables = await conn.run_sync(lambda c: inspector.get_table_names(schema=sc))
model_tables = [t for t in Base.metadata.sorted_tables if t.schema == sc]
for table in model_tables:
t_name = table.name
if t_name not in db_tables:
print(f"❌ HIÁNYZÓ TÁBLA: {sc}.{t_name}")
mismatches += 1
suggestions.append(f"-- Hozd létre a táblát: {sc}.{t_name}")
continue
# Oszlopok lekérése a DB-ből
db_cols = {c['name']: c for c in await conn.run_sync(
lambda c: inspector.get_columns(t_name, schema=sc)
)}
# Oszlopok lekérése a Modellből
for col in table.columns:
if col.name not in db_cols:
print(f"⚠️ HIÁNYZÓ OSZLOP: {sc}.{t_name}.{col.name}")
mismatches += 1
suggestions.append(f"ALTER TABLE {sc}.{t_name} ADD COLUMN {col.name} {col.type};")
else:
# MÉLY TÍPUS ELLENŐRZÉS
db_col = db_cols[col.name]
db_type_str = str(db_col['type']).upper()
# 1. JSONB Ellenőrzés
if isinstance(col.type, JSONB) and "JSONB" not in db_type_str:
print(f"🔬 TÍPUS ELTÉRÉS [JSONB]: {sc}.{t_name}.{col.name} (DB: {db_type_str})")
mismatches += 1
# 2. NUMERIC Precizitás
elif isinstance(col.type, NUMERIC):
m_prec, m_scale = col.type.precision, col.type.scale
d_prec, d_scale = db_col['type'].precision, db_col['type'].scale
if m_prec != d_prec or m_scale != d_scale:
print(f"🔬 TÍPUS ELTÉRÉS [NUMERIC]: {sc}.{t_name}.{col.name} (Kód: {m_prec},{m_scale} vs DB: {d_prec},{d_scale})")
mismatches += 1
# 3. ENUM Ellenőrzés
elif isinstance(col.type, ENUM):
enum_name = col.type.name
res = await conn.execute(text(
"SELECT EXISTS (SELECT 1 FROM pg_type WHERE typname = :name)"),
{"name": enum_name}
)
if not res.scalar():
print(f"🔬 HIÁNYZÓ ENUM TÍPUS: {enum_name} ({sc}.{t_name}.{col.name})")
mismatches += 1
print(f"{sc}.{t_name:30} | Átvizsgálva.")
print("\n" + "="*50)
if mismatches == 0:
print("✨ GRATULÁLOK! A fájlrendszer és az adatbázis szinkronban van. ✨")
else:
print(f"⚠️ ÖSSZESEN {mismatches} ELTÉRÉS TALÁLHATÓ!")
print("\nJAVÍTÁSI JAVASLATOK (Copy-Paste SQL):")
for s in suggestions:
print(f" {s}")
print("="*50 + "\n")
if __name__ == "__main__":
asyncio.run(run_unified_audit())