from fastapi import FastAPI, HTTPException from fastapi.staticfiles import StaticFiles from fastapi.responses import FileResponse from pydantic import BaseModel, validator from typing import Optional from sqlalchemy.ext.asyncio import create_async_engine, AsyncSession from sqlalchemy.orm import sessionmaker from sqlalchemy import text import os from datetime import date from dotenv import load_dotenv load_dotenv() # --- 1. ADATBÁZIS KAPCSOLAT --- raw_url = os.getenv("DATABASE_URL") if not raw_url: raw_url = "postgresql://admin:PASSWORD_111@postgres-db:5432/service_finder" fixed_url = raw_url.replace("postgresql://", "postgresql+asyncpg://").replace("/service_finder_db", "/service_finder") engine = create_async_engine(fixed_url) AsyncSessionLocal = sessionmaker(engine, class_=AsyncSession, expire_on_commit=False) app = FastAPI(title="Service Finder API") # --- 2. ADATMODELLEK (VALIDÁCIÓVAL) --- class VehicleRegister(BaseModel): model_id: int vin: str # Alvázszám (KÖTELEZŐ!) plate: str # Rendszám mileage: int # Km óra purchase_date: date role: str = "OWNER" # Alapértelmezett: Tulajdonos # Adatőr: Automatikus nagybetűsítés és tisztítás @validator('vin') def clean_vin(cls, v): if len(v) < 5: raise ValueError('Az alvázszám túl rövid!') return v.upper().replace("-", "").replace(" ", "") @validator('plate') def clean_plate(cls, v): return v.upper().replace("-", "").replace(" ", "") @validator('mileage') def positive_mileage(cls, v): if v < 0: raise ValueError('A kilométer nem lehet negatív!') return v # --- 3. VÉGPONTOK --- # Lista lekérése (Katalógus) @app.get("/api/vehicles") async def get_vehicles(): async with AsyncSessionLocal() as session: result = await session.execute(text(""" SELECT vm.id, m.name as brand, vm.model_name, vm.category FROM ref.vehicle_models vm JOIN ref.vehicle_makes m ON vm.make_id = m.id ORDER BY m.name, vm.model_name """)) return [{"id": r.id, "brand": r.brand, "model": r.model_name, "category": r.category} for r in result.fetchall()] # ÚJ: JÁRMŰ REGISZTRÁCIÓ (A Nagy Logika) @app.post("/api/register") async def register_vehicle(data: VehicleRegister): async with AsyncSessionLocal() as session: async with session.begin(): # Tranzakció indítása try: # 1. Megnézzük, létezik-e már ez a VAS (Alvázszám alapján)? check_query = text("SELECT id FROM data.vehicles WHERE vin = :vin") result = await session.execute(check_query, {"vin": data.vin}) existing_car_id = result.scalar() vehicle_id = existing_car_id # 2. HA NEM LÉTEZIK -> Létrehozzuk a VASAT if not vehicle_id: print(f"🆕 Új autó az adatbázisban: {data.vin}") insert_car = text(""" INSERT INTO data.vehicles (model_id, vin, current_plate) VALUES (:mid, :vin, :plt) RETURNING id """) res = await session.execute(insert_car, { "mid": data.model_id, "vin": data.vin, "plt": data.plate }) vehicle_id = res.scalar() else: print(f"♻️ Létező autó átvétele: {data.vin} (ID: {vehicle_id})") # Itt később lezárhatjuk az előző tulajdonos history-ját! # 3. BEJEGYZÉS A TÖRTÉNELEMBE (HISTORY) # Ez köti össze a User-t (most fixen ID=1) az Autóval insert_history = text(""" INSERT INTO data.vehicle_history (vehicle_id, user_id, role, start_date, start_mileage) VALUES (:vid, 1, :role, :s_date, :s_mil) """) await session.execute(insert_history, { "vid": vehicle_id, "role": data.role, "s_date": data.purchase_date, "s_mil": data.mileage }) return {"status": "success", "message": "Jármű sikeresen rögzítve a flottában!"} except Exception as e: print(f"❌ Hiba: {e}") raise HTTPException(status_code=500, detail=str(e)) # Frontend kiszolgálása @app.get("/") async def serve_frontend(): if os.path.exists("/app/frontend/index.html"): return FileResponse("/app/frontend/index.html") return {"error": "Frontend hiányzik"}