Files
service-finder/code-server-config/data/User/History/-3487e1e/zNYa.py

217 lines
12 KiB
Python
Executable File

from fastapi import FastAPI, HTTPException, UploadFile, File, Form
from fastapi.responses import FileResponse, JSONResponse
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
import shutil
from datetime import date
import uuid
import traceback # Hogy lássuk a hibát
from dotenv import load_dotenv
load_dotenv()
# MAPPA KONFIGURÁCIÓ
UPLOAD_DIR = "/app/frontend/uploads"
# Megpróbáljuk létrehozni, ha nem menne, a scriptünk már megoldotta
try:
os.makedirs(UPLOAD_DIR, exist_ok=True)
except Exception as e:
print(f"HIBA a mappa létrehozásakor: {e}")
EXCHANGE_RATES = { "EUR_TO_HUF": 400.0, "HUF_TO_EUR": 0.0025 }
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
class VehicleRegister(BaseModel):
model_id: int; vin: str; plate: str; mileage: int; purchase_date: date; role: str = "OWNER"
@validator('vin')
def clean_vin(cls, v): return v.upper().replace("-", "").replace(" ", "")
@validator('plate')
def clean_plate(cls, v): return v.upper().replace("-", "").replace(" ", "")
@validator('mileage')
def positive_mileage(cls, v): return v if v >= 0 else 0
class InviteRequest(BaseModel):
email: str; role: str; access_level: str
# --- LOG HELPER ---
async def create_audit_log(session, user_id, event, target_id, details, old_val=None, new_val=None):
try:
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})
except Exception as e:
print(f"AUDIT LOG HIBA: {e}")
# --- API VÉGPONTOK (HIBATŰRŐ MÓDBAN) ---
@app.post("/api/add_cost")
async def add_cost(
vehicle_id: int = Form(...),
cost_type: str = Form(...),
amount: float = Form(...),
currency: str = Form(...),
mileage: int = Form(...),
date_str: str = Form(...),
description: str = Form(""),
file: UploadFile = File(None)
):
# ITT A LÉNYEG: TRY-EXCEPT BLOKK
try:
user_id = 1
document_path = None
# 1. Fájl mentése
if file:
try:
file_ext = file.filename.split(".")[-1]
unique_name = f"{uuid.uuid4()}.{file_ext}"
file_path = os.path.join(UPLOAD_DIR, unique_name)
print(f"Fájl mentése ide: {file_path}") # Debug log
with open(file_path, "wb") as buffer:
shutil.copyfileobj(file.file, buffer)
document_path = f"uploads/{unique_name}"
except Exception as file_error:
print(f"FÁJL MENTÉSI HIBA: {file_error}")
return JSONResponse(status_code=500, content={"detail": f"Nem sikerült a fájlt menteni: {str(file_error)}"})
# 2. Adatbázis mentés
async with AsyncSessionLocal() as session:
async with session.begin():
await session.execute(text("""
INSERT INTO data.costs (vehicle_id, user_id, cost_type, amount, currency, date, mileage_at_cost, description, document_url)
VALUES (:vid, :uid, :type, :amt, :curr, :date, :mil, :desc, :doc)
"""), {
"vid": vehicle_id, "uid": user_id, "type": cost_type, "amt": amount,
"curr": currency, "date": date_str, "mil": mileage, "desc": description,
"doc": document_path
})
# Km frissítés
res = await session.execute(text("SELECT start_mileage FROM data.vehicle_history WHERE vehicle_id = :vid AND end_date IS NULL"), {"vid": vehicle_id})
current = res.scalar() or 0
if mileage > current:
await session.execute(text("UPDATE data.vehicle_history SET start_mileage = :mil WHERE vehicle_id = :vid AND end_date IS NULL"), {"mil": mileage, "vid": vehicle_id})
await create_audit_log(session, user_id, "MILEAGE_UPDATE", vehicle_id, f"Km: {mileage}")
await create_audit_log(session, user_id, "ADD_COST", vehicle_id, f"{cost_type}: {amount} {currency}")
return {"status": "success"}
except Exception as e:
# Ha bármi baj van, nem omlunk össze, hanem visszaszólunk a frontendnek!
error_msg = traceback.format_exc()
print(f"KRITIKUS HIBA A SZERVEREN: {error_msg}")
return JSONResponse(status_code=500, content={"detail": f"Szerver hiba: {str(e)}"})
# --- TÖBBI VÉGPONT (Változatlan, de működő) ---
@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")
current_year = date.today().year
q_costs = text("SELECT amount, currency FROM data.costs WHERE vehicle_id = :vid AND EXTRACT(YEAR FROM date) = :year")
costs = (await session.execute(q_costs, {"vid": vehicle_id, "year": current_year})).fetchall()
total_cost = 0.0
user_curr = car.default_currency
for c in costs:
if c.currency == user_curr: total_cost += float(c.amount)
elif c.currency == 'EUR' and user_curr == 'HUF': total_cost += float(c.amount) * EXCHANGE_RATES["EUR_TO_HUF"]
elif c.currency == 'HUF' and user_curr == 'EUR': total_cost += float(c.amount) * EXCHANGE_RATES["HUF_TO_EUR"]
else: total_cost += float(c.amount)
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, "year_cost": total_cost}
@app.get("/api/vehicle/{vehicle_id}/history")
async def get_vehicle_history(vehicle_id: int):
async with AsyncSessionLocal() as session:
costs = (await session.execute(text("SELECT id, cost_type as type, amount, currency, date, mileage_at_cost as mileage, description, document_url, 'COST' as source FROM data.costs WHERE vehicle_id = :vid ORDER BY date DESC"), {"vid": vehicle_id})).fetchall()
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, '' as document_url, '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})).fetchall()
combined = [dict(r._mapping) for r in costs] + [dict(r._mapping) for r in logs]
combined.sort(key=lambda x: x['date'], reverse=True)
return combined
@app.get("/api/my_vehicles")
async def get_my_garage():
async with AsyncSessionLocal() as session:
res = await session.execute(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 = 1 AND vh.end_date IS NULL ORDER BY vh.start_date DESC"))
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 res.fetchall()]
@app.post("/api/report_issue")
async def report_issue(data: IssueReport):
async with AsyncSessionLocal() as session:
async with session.begin():
st = 'CRITICAL' if data.is_critical else 'WARNING'
await session.execute(text("UPDATE data.vehicles SET status = :st, current_issue = :desc WHERE id = :vid"), {"st": st, "desc": data.description, "vid": data.vehicle_id})
await create_audit_log(session, 1, "ISSUE_REPORT", data.vehicle_id, data.description, "OK", st)
return {"status": "success"}
@app.post("/api/resolve_issue")
async def resolve_issue(data: IssueResolve):
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, 1, "ISSUE_RESOLVED", data.vehicle_id, "Megjavítva", "WARNING", "OK")
return {"status": "success"}
@app.get("/api/vehicles")
async def get_vehicles():
async with AsyncSessionLocal() as session:
return [{"id": r.id, "brand": r.brand, "model": r.model_name, "category": r.category} for r in (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"))).fetchall()]
@app.post("/api/register")
async def register_vehicle(data: VehicleRegister):
async with AsyncSessionLocal() as session:
async with session.begin():
res = await session.execute(text("SELECT id FROM data.vehicles WHERE vin = :vin"), {"vin": data.vin})
vid = res.scalar()
if not vid: vid = (await session.execute(text("INSERT INTO data.vehicles (model_id, vin, current_plate) VALUES (:mid, :vin, :plt) RETURNING id"), {"mid": data.model_id, "vin": data.vin, "plt": data.plate})).scalar()
await session.execute(text("INSERT INTO data.vehicle_history (vehicle_id, user_id, role, start_date, start_mileage) VALUES (:vid, 1, :role, :sd, :sm)"), {"vid": vid, "role": data.role, "sd": data.purchase_date, "sm": data.mileage})
await create_audit_log(session, 1, "REGISTER_VEHICLE", vid, "Regisztrálva")
return {"status": "success"}
@app.get("/api/fleet/members")
async def get_team_members():
async with AsyncSessionLocal() as session:
return [{"email": r.email, "role": r.role, "joined_at": r.joined_at, "country": r.country} for r in (await session.execute(text("SELECT u.email, fm.role, fm.joined_at, u.country FROM data.fleet_members fm JOIN data.users u ON fm.user_id = u.id WHERE fm.owner_id = 1"))).fetchall()]
@app.post("/api/fleet/invite")
async def invite_member(data: InviteRequest):
async with AsyncSessionLocal() as session:
async with session.begin():
await session.execute(text("INSERT INTO data.invitations (email, inviter_id, role, access_level, token, expires_at) VALUES (:e, 1, :r, :l, :t, NOW() + INTERVAL '7 days')"), {"e": data.email, "r": data.role, "l": data.access_level, "t": str(uuid.uuid4())})
return {"status": "success"}
@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"}