Files

177 lines
8.4 KiB
Python
Executable File

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"}