from fastapi import FastAPI, HTTPException from fastapi.responses import FileResponse from pydantic import BaseModel, validator from typing import Optional, List from sqlalchemy.ext.asyncio import create_async_engine, AsyncSession from sqlalchemy.orm import sessionmaker from sqlalchemy import text import os from datetime import date import uuid from dotenv import load_dotenv load_dotenv() 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") # --- MODELLEK --- class IssueReport(BaseModel): vehicle_id: int description: str is_critical: bool class IssueResolve(BaseModel): vehicle_id: int # ÚJ: KÖLTSÉG MODELL class CostCreate(BaseModel): vehicle_id: int cost_type: str # FUEL, SERVICE, INSURANCE, TAX, OTHER amount: float currency: str # HUF, EUR date: date mileage: int description: Optional[str] = "" # --- LOGOLÁS --- async def create_audit_log(session, user_id, event, target_id, details, old_val=None, new_val=None): await session.execute(text(""" INSERT INTO data.audit_logs (user_id, event_type, target_id, details, old_value, new_value) VALUES (:uid, :evt, :tid, :det, :old, :new) """), {"uid": user_id, "evt": event, "tid": target_id, "det": details, "old": old_val, "new": new_val}) # --- VÉGPONTOK --- # 1. KÖLTSÉG HOZZÁADÁSA (Okos Km frissítéssel!) @app.post("/api/add_cost") async def add_cost(data: CostCreate): user_id = 1 async with AsyncSessionLocal() as session: async with session.begin(): # Költség mentése await session.execute(text(""" INSERT INTO data.costs (vehicle_id, user_id, cost_type, amount, currency, date, mileage_at_cost, description) VALUES (:vid, :uid, :type, :amt, :curr, :date, :mil, :desc) """), { "vid": data.vehicle_id, "uid": user_id, "type": data.cost_type, "amt": data.amount, "curr": data.currency, "date": data.date, "mil": data.mileage, "desc": data.description }) # KM ÓRA AUTOMATIKUS FRISSÍTÉSE (Ha a megadott km nagyobb, mint a jelenlegi) # Először lekérjük a jelenlegit res = await session.execute(text("SELECT start_mileage FROM data.vehicle_history WHERE vehicle_id = :vid AND end_date IS NULL"), {"vid": data.vehicle_id}) current = res.scalar() or 0 if data.mileage > current: await session.execute(text(""" UPDATE data.vehicle_history SET start_mileage = :mil WHERE vehicle_id = :vid AND end_date IS NULL """), {"mil": data.mileage, "vid": data.vehicle_id}) # Opcionális: Logoljuk a km frissítést is await create_audit_log(session, user_id, "MILEAGE_UPDATE", data.vehicle_id, f"Km frissítve költség rögzítésekor: {data.mileage}") await create_audit_log(session, user_id, "ADD_COST", data.vehicle_id, f"{data.cost_type}: {data.amount} {data.currency}") return {"status": "success"} # 2. SZERVIZKÖNYV LEKÉRÉSE (Történet) @app.get("/api/vehicle/{vehicle_id}/history") async def get_vehicle_history(vehicle_id: int): user_id = 1 async with AsyncSessionLocal() as session: # Lekérjük a költségeket res_costs = await session.execute(text(""" SELECT id, cost_type as type, amount, currency, date, mileage_at_cost as mileage, description, 'COST' as source FROM data.costs WHERE vehicle_id = :vid ORDER BY date DESC """), {"vid": vehicle_id}) costs = res_costs.fetchall() # Lekérjük az Audit Log eseményeket (Hiba, Javítás) res_logs = await session.execute(text(""" SELECT id, event_type as type, 0 as amount, '' as currency, created_at::date as date, 0 as mileage, details as description, 'LOG' as source FROM data.audit_logs WHERE target_id = :vid AND event_type IN ('ISSUE_REPORT', 'ISSUE_RESOLVED') ORDER BY created_at DESC """), {"vid": vehicle_id}) logs = res_logs.fetchall() # Összefésüljük a két listát Pythonban és dátum szerint rendezzük combined = [] for r in costs: combined.append(dict(r._mapping)) for r in logs: combined.append(dict(r._mapping)) # Rendezés dátum szerint csökkenőbe (legújabb elöl) combined.sort(key=lambda x: x['date'], reverse=True) return combined # --- KORÁBBI VÉGPONTOK (Rövidítve a hely miatt, de ezek kellenek!) --- @app.get("/api/my_vehicles") async def get_my_garage(): user_id = 1 async with AsyncSessionLocal() as session: query = text(""" SELECT v.id as vehicle_id, v.vin, v.current_plate, m.name as brand, mo.model_name, mo.category, vh.start_mileage, vh.role, v.status, v.current_issue 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 AND vh.end_date IS NULL ORDER BY vh.start_date DESC """) result = await session.execute(query, {"uid": user_id}) return [{"vehicle_id": r.vehicle_id, "brand": r.brand, "model": r.model_name, "plate": r.current_plate, "category": r.category, "role": r.role, "status": r.status, "current_issue": r.current_issue} for r in result.fetchall()] @app.get("/api/vehicle/{vehicle_id}") async def get_vehicle_details(vehicle_id: int): user_id = 1 async with AsyncSessionLocal() as session: q = text(""" SELECT v.id, v.vin, v.current_plate, v.production_year, m.name as brand, mo.model_name, mo.category, vh.role, vh.start_date, vh.start_mileage, u.default_currency, v.status, v.current_issue 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 JOIN data.users u ON vh.user_id = u.id WHERE v.id = :vid AND vh.user_id = :uid AND vh.end_date IS NULL """) res = await session.execute(q, {"vid": vehicle_id, "uid": user_id}) car = res.fetchone() if not car: raise HTTPException(status_code=404, detail="Nincs adat") return {"id": car.id, "brand": car.brand, "model": car.model_name, "plate": car.current_plate, "vin": car.vin, "role": car.role, "start_date": car.start_date, "mileage": car.start_mileage, "currency": car.default_currency, "status": car.status, "current_issue": car.current_issue} @app.post("/api/report_issue") async def report_issue(data: IssueReport): user_id = 1 async with AsyncSessionLocal() as session: async with session.begin(): new_status = 'CRITICAL' if data.is_critical else 'WARNING' await session.execute(text("UPDATE data.vehicles SET status = :st, current_issue = :desc WHERE id = :vid"), {"st": new_status, "desc": data.description, "vid": data.vehicle_id}) await create_audit_log(session, user_id, "ISSUE_REPORT", data.vehicle_id, details=data.description, old_val="OK", new_val=new_status) return {"status": "success"} @app.post("/api/resolve_issue") async def resolve_issue(data: IssueResolve): user_id = 1 async with AsyncSessionLocal() as session: async with session.begin(): await session.execute(text("UPDATE data.vehicles SET status = 'OK', current_issue = NULL WHERE id = :vid"), {"vid": data.vehicle_id}) await create_audit_log(session, user_id, "ISSUE_RESOLVED", data.vehicle_id, details="Probléma megoldva", old_val="WARNING", new_val="OK") return {"status": "success", "message": "Jármű státusza helyreállítva!"} @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"}