Files

91 lines
4.5 KiB
Python
Executable File

from fastapi import FastAPI, HTTPException, Form, Depends
from fastapi.responses import FileResponse, JSONResponse
from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm
from pydantic import BaseModel
from sqlalchemy.ext.asyncio import create_async_engine, AsyncSession
from sqlalchemy.orm import sessionmaker
from sqlalchemy import text
from datetime import datetime, timedelta, date
from jose import jwt
import bcrypt, os
from dotenv import load_dotenv
load_dotenv()
SECRET_KEY = "SZUPER_TITKOS_KULCS_2026"
ALGORITHM = "HS256"
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="api/auth/login")
DATABASE_URL = os.getenv("DATABASE_URL", "").replace("postgresql://", "postgresql+asyncpg://")
engine = create_async_engine(DATABASE_URL, pool_pre_ping=True)
AsyncSessionLocal = sessionmaker(engine, class_=AsyncSession, expire_on_commit=False)
app = FastAPI()
async def get_current_user(token: str = Depends(oauth2_scheme)):
try:
p = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
return int(p.get("sub"))
except: raise HTTPException(status_code=401)
# --- METAADATOK ---
@app.get("/api/meta/vehicle-hierarchy")
async def get_hierarchy():
async with AsyncSessionLocal() as session:
q = text("SELECT vm.category, m.name as brand, vm.id as model_id, vm.model_name FROM ref.vehicle_models vm JOIN ref.vehicle_makes m ON vm.make_id = m.id ORDER BY vm.category, m.name")
res = await session.execute(q)
hierarchy = {}
for r in res:
if r.category not in hierarchy: hierarchy[r.category] = {}
if r.brand not in hierarchy[r.category]: hierarchy[r.category][r.brand] = []
hierarchy[r.category][r.brand].append({"id": r.model_id, "name": r.model_name})
return hierarchy
@app.get("/api/meta/cost-types")
async def get_cost_types():
async with AsyncSessionLocal() as session:
res = await session.execute(text("SELECT code, name FROM ref.cost_types ORDER BY name"))
return {r.code: r.name for r in res.fetchall()}
# --- JÁRMŰ MŰVELETEK ---
@app.get("/api/my_vehicles")
async def my_vehicles(uid: int = Depends(get_current_user)):
async with AsyncSessionLocal() as session:
q = text("""
SELECT v.id as vehicle_id, v.current_plate as plate, m.name as brand, mo.model_name as model, v.status,
(SELECT SUM(amount) FROM data.costs WHERE vehicle_id = v.id) as total_cost
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
""")
res = await session.execute(q, {"uid": uid})
return [dict(r._mapping) for r in res.fetchall()]
@app.post("/api/register")
async def register_vehicle(d: dict, uid: int = Depends(get_current_user)):
async with AsyncSessionLocal() as session:
async with session.begin():
res = await session.execute(text("INSERT INTO data.vehicles (model_id, vin, current_plate) VALUES (:mid, :vin, :plt) RETURNING id"), {"mid": d['model_id'], "vin": d['vin'], "plt": d['plate']})
vid = res.scalar()
await session.execute(text("INSERT INTO data.vehicle_history (vehicle_id, user_id, role, start_date, start_mileage) VALUES (:vid, :uid, 'OWNER', CURRENT_DATE, :sm)"), {"vid": vid, "uid": uid, "sm": d['mileage']})
return {"status": "success"}
@app.delete("/api/vehicle/{vid}")
async def delete_vehicle(vid: int, uid: int = Depends(get_current_user)):
async with AsyncSessionLocal() as session:
async with session.begin():
# Soft delete: lezárjuk a history-t
await session.execute(text("UPDATE data.vehicle_history SET end_date = CURRENT_DATE WHERE vehicle_id = :vid AND user_id = :uid"), {"vid": vid, "uid": uid})
return {"status": "deleted"}
@app.post("/api/add_cost")
async def add_cost(d: dict, uid: int = Depends(get_current_user)):
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) VALUES (:vid, :uid, :type, :amt, 'HUF', CURRENT_DATE)"),
{"vid": d['vehicle_id'], "uid": uid, "type": d['type'], "amt": d['amount']})
return {"status": "success"}
@app.get("/")
async def index(): return FileResponse("/app/frontend/index.html")