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

230 lines
12 KiB
Python
Executable File

from fastapi import FastAPI, HTTPException, UploadFile, File, Form
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
import shutil
from datetime import date
import uuid
from dotenv import load_dotenv
load_dotenv()
# --- KONFIGURÁCIÓ ---
# Itt adjuk meg, hova mentsen. Később ezt a mappát csatoljuk a NAS-hoz!
UPLOAD_DIR = "/app/frontend/uploads"
os.makedirs(UPLOAD_DIR, exist_ok=True)
# Demo Árfolyamok (Később API-ból jön)
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
# --- SEGÉDFÜGGVÉNYEK ---
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})
# --- ÚJ VÉGPONTOK ---
# 1. KÖLTSÉG + FÁJL FELTÖLTÉS (Multipart Form)
@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(...), # Dátum stringként jön Formból
description: str = Form(""),
file: UploadFile = File(None) # Opcionális fájl
):
user_id = 1
document_path = None
# A) Fájl mentése (NAS Előkészítés)
if file:
file_ext = file.filename.split(".")[-1]
unique_name = f"{uuid.uuid4()}.{file_ext}"
file_path = os.path.join(UPLOAD_DIR, unique_name)
with open(file_path, "wb") as buffer:
shutil.copyfileobj(file.file, buffer)
# A Frontendnek relatív útvonal kell: /uploads/nev.jpg
document_path = f"uploads/{unique_name}"
async with AsyncSessionLocal() as session:
async with session.begin():
# B) Adatbázis mentés
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
})
# C) 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} + Doc")
return {"status": "success"}
# 2. ADATLAP LEKÉRÉSE (Valutaváltással!)
@app.get("/api/vehicle/{vehicle_id}")
async def get_vehicle_details(vehicle_id: int):
user_id = 1
async with AsyncSessionLocal() as session:
# Alapadatok lekérése
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")
# --- OKOS KÖLTSÉGSZÁMOLÁS ---
current_year = date.today().year
# Lekérjük az összes idei tételt, függetlenül a pénznemtől
q_costs = text("""
SELECT amount, currency
FROM data.costs
WHERE vehicle_id = :vid AND EXTRACT(YEAR FROM date) = :year
""")
costs_res = await session.execute(q_costs, {"vid": vehicle_id, "year": current_year})
costs = costs_res.fetchall()
total_cost = 0.0
user_curr = car.default_currency # pl. HUF
for c in costs:
if c.currency == user_curr:
total_cost += float(c.amount)
# Konverzió
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) # Ismeretlen pénznem, hozzáadjuk simán (vagy hiba)
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 # Ez már a konvertált összeg!
}
@app.get("/api/vehicle/{vehicle_id}/history")
async def get_vehicle_history(vehicle_id: int):
async with AsyncSessionLocal() as session:
# Itt is lekérjük a document_url-t!
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
# --- MARADÉK VÉGPONTOK (Rövidítve a hely miatt) ---
@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/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.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"}