from fastapi import FastAPI, HTTPException, UploadFile, File, Form, Depends from fastapi.responses import FileResponse, JSONResponse from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm from pydantic import BaseModel from sqlalchemy.ext.asyncio import create_async_engine, AsyncSession from sqlalchemy.orm import sessionmaker from sqlalchemy import text from datetime import datetime, timedelta from jose import JWTError, jwt import bcrypt # Közvetlen bcrypt használata a passlib helyett a hiba elkerülésére import os, uuid, traceback from dotenv import load_dotenv load_dotenv() SECRET_KEY = "SZUPER_TITKOS_KULCS_2026" ALGORITHM = "HS256" oauth2_scheme = OAuth2PasswordBearer(tokenUrl="api/auth/login") DATABASE_URL = os.getenv("DATABASE_URL", "").replace("postgresql://", "postgresql+asyncpg://") engine = create_async_engine(DATABASE_URL, pool_pre_ping=True) AsyncSessionLocal = sessionmaker(engine, class_=AsyncSession, expire_on_commit=False) app = FastAPI() # --- JAVÍTOTT TITKOSÍTÁS (Passlib bug kikerülése) --- def get_password_hash(password: str): pwd_bytes = password.encode('utf-8') # A bcrypt korlátja 72 byte, vágjuk le ha hosszabb (biztonsági best practice) if len(pwd_bytes) > 72: pwd_bytes = pwd_bytes[:72] salt = bcrypt.gensalt() return bcrypt.hashpw(pwd_bytes, salt).decode('utf-8') def verify_password(plain_password: str, hashed_password: str): pwd_bytes = plain_password.encode('utf-8') if len(pwd_bytes) > 72: pwd_bytes = pwd_bytes[:72] return bcrypt.checkpw(pwd_bytes, hashed_password.encode('utf-8')) async def get_current_user(token: str = Depends(oauth2_scheme)): try: payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM]) user_id: str = payload.get("sub") if user_id is None: raise HTTPException(status_code=401) return int(user_id) except Exception: raise HTTPException(status_code=401) @app.post("/api/auth/register") async def register(email: str = Form(...), password: str = Form(...)): try: async with AsyncSessionLocal() as session: async with session.begin(): hashed = get_password_hash(password) await session.execute( text("INSERT INTO data.users (email, password_hash) VALUES (:e, :p)"), {"e": email, "p": hashed} ) return {"status": "success"} except Exception as e: print(f"REGISZTRÁCIÓS HIBA: {str(e)}") return JSONResponse(status_code=500, content={"detail": f"Adatbázis hiba: {str(e)}"}) @app.post("/api/auth/login") async def login(form_data: OAuth2PasswordRequestForm = Depends()): async with AsyncSessionLocal() as session: res = await session.execute(text("SELECT id, password_hash FROM data.users WHERE email = :e"), {"e": form_data.username}) user = res.fetchone() if not user or not verify_password(form_data.password, user.password_hash): raise HTTPException(status_code=401, detail="Hibás email vagy jelszó") token = jwt.encode({"sub": str(user.id), "exp": datetime.utcnow() + timedelta(days=1)}, SECRET_KEY, algorithm=ALGORITHM) return {"access_token": token, "token_type": "bearer"} @app.get("/api/my_vehicles") async def my_vehicles(user_id: int = Depends(get_current_user)): async with AsyncSessionLocal() as session: q = text("SELECT v.id as vehicle_id, v.current_plate as plate, m.name as brand, mo.model_name as model, v.status FROM data.vehicle_history vh JOIN data.vehicles v ON vh.vehicle_id = v.id JOIN ref.vehicle_models mo ON v.model_id = mo.id JOIN ref.vehicle_makes m ON mo.make_id = m.id WHERE vh.user_id = :uid") res = await session.execute(q, {"uid": user_id}) return [dict(r._mapping) for r in res.fetchall()] @app.get("/api/ref/cost_types") async def cost_types(): async with AsyncSessionLocal() as session: res = await session.execute(text("SELECT code, name, parent_code FROM ref.cost_types")) rows = res.fetchall() tree = {} for r in rows: if not r.parent_code: tree[r.code] = {"label": r.name, "subs": {}} for r in rows: if r.parent_code and r.parent_code in tree: tree[r.parent_code]["subs"][r.code] = r.name return tree @app.get("/") async def index(): return FileResponse("/app/frontend/index.html")