Files

170 lines
6.8 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()
# DB Config
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 VehicleRegister(BaseModel):
model_id: int
vin: str
plate: str
mileage: int
purchase_date: date
role: str = "OWNER"
@validator('vin')
def clean_vin(cls, v):
if len(v) < 5: raise ValueError('Túl rövid alvázszám')
return v.upper().replace("-", "").replace(" ", "")
@validator('plate')
def clean_plate(cls, v):
return v.upper().replace("-", "").replace(" ", "")
@validator('mileage')
def positive_mileage(cls, v):
if v < 0: raise ValueError('Negatív km nem lehet')
return v
class InviteRequest(BaseModel):
email: str
role: str
access_level: str
# --- VÉGPONTOK ---
@app.get("/api/vehicles")
async def get_vehicles():
async with AsyncSessionLocal() as session:
result = 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, vm.model_name
"""))
return [{"id": r.id, "brand": r.brand, "model": r.model_name, "category": r.category} for r in result.fetchall()]
@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
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} for r in result.fetchall()]
# ÚJ: RÉSZLETES ADATLAP LEKÉRÉSE
@app.get("/api/vehicle/{vehicle_id}")
async def get_vehicle_details(vehicle_id: int):
user_id = 1
async with AsyncSessionLocal() as session:
# 1. Alapadatok
q_basic = 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 -- A user pénzneme
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_basic = await session.execute(q_basic, {"vid": vehicle_id, "uid": user_id})
car = res_basic.fetchone()
if not car:
raise HTTPException(status_code=404, detail="Jármű nem található vagy nincs hozzáférése")
# 2. Utolsó ismert km óraállás (a history-ból vagy költségekből)
# Egyelőre visszaadjuk a start_mileage-t, később itt számolunk
current_km = car.start_mileage
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": current_km,
"currency": car.default_currency
}
@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:
ins = text("INSERT INTO data.vehicles (model_id, vin, current_plate) VALUES (:mid, :vin, :plt) RETURNING id")
r = await session.execute(ins, {"mid": data.model_id, "vin": data.vin, "plt": data.plate})
vid = r.scalar()
hist = text("INSERT INTO data.vehicle_history (vehicle_id, user_id, role, start_date, start_mileage) VALUES (:vid, 1, :role, :sd, :sm)")
await session.execute(hist, {"vid": vid, "role": data.role, "sd": data.purchase_date, "sm": data.mileage})
return {"status": "success"}
@app.get("/api/fleet/members")
async def get_team_members():
user_id = 1
async with AsyncSessionLocal() as session:
query = 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 = :uid
""")
result = await session.execute(query, {"uid": user_id})
return [{"email": r.email, "role": r.role, "joined_at": r.joined_at, "country": r.country} for r in result.fetchall()]
@app.post("/api/fleet/invite")
async def invite_member(data: InviteRequest):
user_id = 1
token = str(uuid.uuid4())
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 (:email, :uid, :role, :lvl, :token, NOW() + INTERVAL '7 days')
"""), {
"email": data.email, "uid": user_id, "role": data.role,
"lvl": data.access_level, "token": token
})
print(f"📧 EMAIL KÜLDÉSE: {data.email} -> Link: /invite/{token}")
return {"status": "success", "message": "Meghívó elküldve!", "debug_token": token}
@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"}