Initial commit: Robot ökoszisztéma v2.0 - Stabilizált jármű és szerviz robotok
This commit is contained in:
180
code-server-config/data/User/History/-3487e1e/0MBT.py
Executable file
180
code-server-config/data/User/History/-3487e1e/0MBT.py
Executable file
@@ -0,0 +1,180 @@
|
||||
from fastapi import FastAPI, HTTPException, Request
|
||||
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): 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
|
||||
|
||||
class IssueReport(BaseModel):
|
||||
vehicle_id: int
|
||||
description: str
|
||||
is_critical: bool
|
||||
|
||||
# --- SEGÉDFÜGGVÉNY: AUDIT LOG ÍRÁSA ---
|
||||
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 ---
|
||||
|
||||
@app.get("/api/my_vehicles")
|
||||
async def get_my_garage():
|
||||
user_id = 1
|
||||
async with AsyncSessionLocal() as session:
|
||||
# Most már lekérjük a státuszt is!
|
||||
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 # <--- ÚJ MEZŐK
|
||||
} 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 -- <--- ÚJ
|
||||
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
|
||||
}
|
||||
|
||||
# --- ÚJ: HIBA BEJELENTÉS (LOGOLÁSSAL) ---
|
||||
@app.post("/api/report_issue")
|
||||
async def report_issue(data: IssueReport):
|
||||
user_id = 1 # Demo User
|
||||
async with AsyncSessionLocal() as session:
|
||||
async with session.begin():
|
||||
# 1. Frissítjük a járművet
|
||||
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})
|
||||
|
||||
# 2. ÍRUNK A NAPLÓBA (AUDIT LOG)
|
||||
await create_audit_log(
|
||||
session, user_id, "ISSUE_REPORT", data.vehicle_id,
|
||||
details=f"Hiba: {data.description}",
|
||||
old_val="OK", new_val=new_status
|
||||
)
|
||||
|
||||
return {"status": "success", "message": "Hiba naplózva és rögzítve."}
|
||||
|
||||
# --- MARADÉK (REGISZTER, FLOTTA, MEGHÍVÁS) ---
|
||||
# (Ezeket most rövidítve hagyom, de a fájlban benne kell lenniük a korábbi verzió szerint)
|
||||
@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"))
|
||||
return [{"id": r.id, "brand": r.brand, "model": r.model_name, "category": r.category} for r in result.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:
|
||||
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})
|
||||
# LOGOLÁS ITT IS!
|
||||
await create_audit_log(session, 1, "REGISTER_VEHICLE", vid, "Jármű regisztrálva")
|
||||
return {"status": "success"}
|
||||
|
||||
@app.get("/api/fleet/members")
|
||||
async def get_team_members():
|
||||
async with AsyncSessionLocal() as session:
|
||||
res = 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"))
|
||||
return [{"email": r.email, "role": r.role, "joined_at": r.joined_at, "country": r.country} for r in res.fetchall()]
|
||||
|
||||
@app.post("/api/fleet/invite")
|
||||
async def invite_member(data: InviteRequest):
|
||||
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 (:e, 1, :r, :l, :t, NOW() + INTERVAL '7 days')"), {"e": data.email, "r": data.role, "l": data.access_level, "t": 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"}
|
||||
177
code-server-config/data/User/History/-3487e1e/2JOI.py
Executable file
177
code-server-config/data/User/History/-3487e1e/2JOI.py
Executable file
@@ -0,0 +1,177 @@
|
||||
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"}
|
||||
140
code-server-config/data/User/History/-3487e1e/7zSH.py
Executable file
140
code-server-config/data/User/History/-3487e1e/7zSH.py
Executable file
@@ -0,0 +1,140 @@
|
||||
from fastapi import FastAPI, HTTPException
|
||||
from fastapi.responses import FileResponse
|
||||
from pydantic import BaseModel, validator, EmailStr
|
||||
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 # Token generáláshoz
|
||||
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: EmailStr
|
||||
role: str # 'DRIVER', 'FLEET_MANAGER'
|
||||
access_level: str # 'FULL', 'LOG_ONLY'
|
||||
|
||||
# --- 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()]
|
||||
|
||||
@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"}
|
||||
|
||||
# --- ÚJ FLOTTA VÉGPONTOK ---
|
||||
|
||||
# 1. Csapat lista
|
||||
@app.get("/api/fleet/members")
|
||||
async def get_team_members():
|
||||
user_id = 1 # Demo Boss
|
||||
async with AsyncSessionLocal() as session:
|
||||
# Lekérjük a fleet_members táblát összekötve a users-el
|
||||
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()]
|
||||
|
||||
# 2. Meghívó küldése
|
||||
@app.post("/api/fleet/invite")
|
||||
async def invite_member(data: InviteRequest):
|
||||
user_id = 1
|
||||
token = str(uuid.uuid4()) # Generálunk egy egyedi kódot
|
||||
|
||||
async with AsyncSessionLocal() as session:
|
||||
async with session.begin():
|
||||
# MENTÉS az invitations táblába
|
||||
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
|
||||
})
|
||||
|
||||
# Itt küldenénk az EMAILT a valóságban
|
||||
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"}
|
||||
167
code-server-config/data/User/History/-3487e1e/DXvc.py
Executable file
167
code-server-config/data/User/History/-3487e1e/DXvc.py
Executable file
@@ -0,0 +1,167 @@
|
||||
from fastapi import FastAPI, HTTPException, UploadFile, File, Form
|
||||
from fastapi.responses import FileResponse, JSONResponse
|
||||
from pydantic import BaseModel, validator
|
||||
from typing import Optional, List, Dict
|
||||
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, datetime
|
||||
import uuid
|
||||
import traceback
|
||||
from dotenv import load_dotenv
|
||||
|
||||
load_dotenv()
|
||||
|
||||
# MAPPA & DB KONFIG
|
||||
UPLOAD_DIR = "/app/frontend/uploads"
|
||||
try: os.makedirs(UPLOAD_DIR, exist_ok=True)
|
||||
except: pass
|
||||
|
||||
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"
|
||||
class InviteRequest(BaseModel): email: str; role: str; access_level: str
|
||||
|
||||
# --- SEGÉDEK ---
|
||||
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: pass
|
||||
|
||||
# --- ÚJ VÉGPONT: KÖLTSÉG TÍPUSOK LEKÉRÉSE (DB-BŐL!) ---
|
||||
@app.get("/api/ref/cost_types")
|
||||
async def get_cost_types():
|
||||
async with AsyncSessionLocal() as session:
|
||||
# Lekérjük az összes aktív típust
|
||||
result = await session.execute(text("SELECT code, name, parent_code FROM ref.cost_types WHERE is_active = TRUE ORDER BY sort_order, name"))
|
||||
rows = result.fetchall()
|
||||
|
||||
# Fát építünk a Frontendnek (Szülő -> Gyerekek)
|
||||
tree = {}
|
||||
# 1. lépés: Szülők
|
||||
for r in rows:
|
||||
if r.parent_code is None:
|
||||
tree[r.code] = {"label": r.name, "subs": {}}
|
||||
|
||||
# 2. lépés: Gyerekek
|
||||
for r in rows:
|
||||
if r.parent_code and r.parent_code in tree:
|
||||
tree[r.parent_code]["subs"][r.code] = r.name
|
||||
|
||||
return tree
|
||||
|
||||
# --- TÖBBI VÉGPONT (Változatlan, csak a Cost mentésnél validálhatnánk, de most egyszerűsítünk) ---
|
||||
|
||||
@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)):
|
||||
try:
|
||||
user_id = 1; document_path = None
|
||||
try: real_date = datetime.strptime(date_str, "%Y-%m-%d").date()
|
||||
except: return JSONResponse(status_code=400, content={"detail": "Dátum hiba"})
|
||||
|
||||
if file:
|
||||
try:
|
||||
ext = file.filename.split(".")[-1]; u_name = f"{uuid.uuid4()}.{ext}"; f_path = os.path.join(UPLOAD_DIR, u_name)
|
||||
with open(f_path, "wb") as b: shutil.copyfileobj(file.file, b)
|
||||
document_path = f"uploads/{u_name}"
|
||||
except: pass
|
||||
|
||||
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": real_date, "mil": mileage, "desc": description, "doc": document_path})
|
||||
res = await session.execute(text("SELECT start_mileage FROM data.vehicle_history WHERE vehicle_id = :vid AND end_date IS NULL"), {"vid": vehicle_id})
|
||||
cur = res.scalar() or 0
|
||||
if mileage > cur:
|
||||
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, "ADD_COST", vehicle_id, f"{cost_type}: {amount}")
|
||||
return {"status": "success"}
|
||||
except Exception as e: return JSONResponse(status_code=500, content={"detail": str(e)})
|
||||
|
||||
@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")
|
||||
curr_year = date.today().year
|
||||
costs = (await session.execute(text("SELECT amount, currency FROM data.costs WHERE vehicle_id = :vid AND EXTRACT(YEAR FROM date) = :year"), {"vid": vehicle_id, "year": curr_year})).fetchall()
|
||||
total = 0.0
|
||||
for c in costs:
|
||||
if c.currency == car.default_currency: total += float(c.amount)
|
||||
elif c.currency == 'EUR' and car.default_currency == 'HUF': total += float(c.amount) * EXCHANGE_RATES["EUR_TO_HUF"]
|
||||
elif c.currency == 'HUF' and car.default_currency == 'EUR': total += float(c.amount) * EXCHANGE_RATES["HUF_TO_EUR"]
|
||||
else: total += 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}
|
||||
|
||||
@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
|
||||
|
||||
# --- MARADÉK (Register, Fleet stb.) ---
|
||||
@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():
|
||||
await session.execute(text("UPDATE data.vehicles SET status = :st, current_issue = :desc WHERE id = :vid"), {"st": 'CRITICAL' if data.is_critical else 'WARNING', "desc": data.description, "vid": data.vehicle_id})
|
||||
await create_audit_log(session, 1, "ISSUE_REPORT", data.vehicle_id, data.description, "OK", 'CRITICAL')
|
||||
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"}
|
||||
230
code-server-config/data/User/History/-3487e1e/HFdy.py
Executable file
230
code-server-config/data/User/History/-3487e1e/HFdy.py
Executable file
@@ -0,0 +1,230 @@
|
||||
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"}
|
||||
227
code-server-config/data/User/History/-3487e1e/HYjM.py
Executable file
227
code-server-config/data/User/History/-3487e1e/HYjM.py
Executable file
@@ -0,0 +1,227 @@
|
||||
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, datetime # <--- FONTOS: datetime importálva
|
||||
import uuid
|
||||
import traceback
|
||||
from dotenv import load_dotenv
|
||||
|
||||
load_dotenv()
|
||||
|
||||
# MAPPA KONFIGURÁCIÓ
|
||||
UPLOAD_DIR = "/app/frontend/uploads"
|
||||
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 ---
|
||||
|
||||
@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)
|
||||
):
|
||||
try:
|
||||
user_id = 1
|
||||
document_path = None
|
||||
|
||||
# 1. DÁTUM KONVERTÁLÁSA (EZ VOLT A HIBA OKA!)
|
||||
# A Frontend stringet küld ("2026-01-20"), mi átalakítjuk Date objektummá
|
||||
try:
|
||||
real_date = datetime.strptime(date_str, "%Y-%m-%d").date()
|
||||
except ValueError:
|
||||
return JSONResponse(status_code=400, content={"detail": f"Hibás dátum formátum: {date_str}"})
|
||||
|
||||
# 2. 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)
|
||||
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}")
|
||||
# Nem állunk meg, csak logoljuk, a költség attól még létrejöhet kép nélkül is
|
||||
document_path = None
|
||||
|
||||
# 3. 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": real_date, # <--- ITT MÁR A KONVERTÁLT DÁTUMOT HASZNÁLJUK
|
||||
"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:
|
||||
error_msg = traceback.format_exc()
|
||||
print(f"KRITIKUS HIBA: {error_msg}")
|
||||
return JSONResponse(status_code=500, content={"detail": f"Szerver hiba: {str(e)}"})
|
||||
|
||||
# --- EGYÉB VÉGPONTOK ---
|
||||
|
||||
@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"}
|
||||
94
code-server-config/data/User/History/-3487e1e/MoK7.py
Executable file
94
code-server-config/data/User/History/-3487e1e/MoK7.py
Executable file
@@ -0,0 +1,94 @@
|
||||
from fastapi import FastAPI, HTTPException, UploadFile, File, 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
|
||||
from jose import JWTError, jwt
|
||||
import bcrypt # Közvetlen bcrypt használata a passlib helyett a hiba elkerülésére
|
||||
import os, uuid, traceback
|
||||
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()
|
||||
|
||||
# --- JAVÍTOTT TITKOSÍTÁS (Passlib bug kikerülése) ---
|
||||
def get_password_hash(password: str):
|
||||
pwd_bytes = password.encode('utf-8')
|
||||
# A bcrypt korlátja 72 byte, vágjuk le ha hosszabb (biztonsági best practice)
|
||||
if len(pwd_bytes) > 72:
|
||||
pwd_bytes = pwd_bytes[:72]
|
||||
salt = bcrypt.gensalt()
|
||||
return bcrypt.hashpw(pwd_bytes, salt).decode('utf-8')
|
||||
|
||||
def verify_password(plain_password: str, hashed_password: str):
|
||||
pwd_bytes = plain_password.encode('utf-8')
|
||||
if len(pwd_bytes) > 72:
|
||||
pwd_bytes = pwd_bytes[:72]
|
||||
return bcrypt.checkpw(pwd_bytes, hashed_password.encode('utf-8'))
|
||||
|
||||
async def get_current_user(token: str = Depends(oauth2_scheme)):
|
||||
try:
|
||||
payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
|
||||
user_id: str = payload.get("sub")
|
||||
if user_id is None: raise HTTPException(status_code=401)
|
||||
return int(user_id)
|
||||
except Exception: raise HTTPException(status_code=401)
|
||||
|
||||
@app.post("/api/auth/register")
|
||||
async def register(email: str = Form(...), password: str = Form(...)):
|
||||
try:
|
||||
async with AsyncSessionLocal() as session:
|
||||
async with session.begin():
|
||||
hashed = get_password_hash(password)
|
||||
await session.execute(
|
||||
text("INSERT INTO data.users (email, password_hash) VALUES (:e, :p)"),
|
||||
{"e": email, "p": hashed}
|
||||
)
|
||||
return {"status": "success"}
|
||||
except Exception as e:
|
||||
print(f"REGISZTRÁCIÓS HIBA: {str(e)}")
|
||||
return JSONResponse(status_code=500, content={"detail": f"Adatbázis hiba: {str(e)}"})
|
||||
|
||||
@app.post("/api/auth/login")
|
||||
async def login(form_data: OAuth2PasswordRequestForm = Depends()):
|
||||
async with AsyncSessionLocal() as session:
|
||||
res = await session.execute(text("SELECT id, password_hash FROM data.users WHERE email = :e"), {"e": form_data.username})
|
||||
user = res.fetchone()
|
||||
if not user or not verify_password(form_data.password, user.password_hash):
|
||||
raise HTTPException(status_code=401, detail="Hibás email vagy jelszó")
|
||||
token = jwt.encode({"sub": str(user.id), "exp": datetime.utcnow() + timedelta(days=1)}, SECRET_KEY, algorithm=ALGORITHM)
|
||||
return {"access_token": token, "token_type": "bearer"}
|
||||
|
||||
@app.get("/api/my_vehicles")
|
||||
async def my_vehicles(user_id: 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 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")
|
||||
res = await session.execute(q, {"uid": user_id})
|
||||
return [dict(r._mapping) for r in res.fetchall()]
|
||||
|
||||
@app.get("/api/ref/cost_types")
|
||||
async def cost_types():
|
||||
async with AsyncSessionLocal() as session:
|
||||
res = await session.execute(text("SELECT code, name, parent_code FROM ref.cost_types"))
|
||||
rows = res.fetchall()
|
||||
tree = {}
|
||||
for r in rows:
|
||||
if not r.parent_code: tree[r.code] = {"label": r.name, "subs": {}}
|
||||
for r in rows:
|
||||
if r.parent_code and r.parent_code in tree: tree[r.parent_code]["subs"][r.code] = r.name
|
||||
return tree
|
||||
|
||||
@app.get("/")
|
||||
async def index(): return FileResponse("/app/frontend/index.html")
|
||||
76
code-server-config/data/User/History/-3487e1e/TFdq.py
Executable file
76
code-server-config/data/User/History/-3487e1e/TFdq.py
Executable file
@@ -0,0 +1,76 @@
|
||||
from fastapi import FastAPI, HTTPException, UploadFile, File, 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
|
||||
from jose import JWTError, jwt
|
||||
from passlib.context import CryptContext
|
||||
import os, uuid, shutil
|
||||
from dotenv import load_dotenv
|
||||
|
||||
load_dotenv()
|
||||
|
||||
SECRET_KEY = "SZUPER_TITKOS_KULCS_2026"
|
||||
ALGORITHM = "HS256"
|
||||
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
|
||||
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()
|
||||
|
||||
def get_password_hash(password): return pwd_context.hash(password)
|
||||
def verify_password(plain, hashed): return pwd_context.verify(plain, hashed)
|
||||
|
||||
async def get_current_user(token: str = Depends(oauth2_scheme)):
|
||||
try:
|
||||
payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
|
||||
user_id: str = payload.get("sub")
|
||||
if user_id is None: raise HTTPException(status_code=401)
|
||||
return int(user_id)
|
||||
except Exception: raise HTTPException(status_code=401)
|
||||
|
||||
@app.post("/api/auth/register")
|
||||
async def register(email: str = Form(...), password: str = Form(...)):
|
||||
async with AsyncSessionLocal() as session:
|
||||
async with session.begin():
|
||||
hashed = get_password_hash(password)
|
||||
await session.execute(text("INSERT INTO data.users (email, password_hash) VALUES (:e, :p)"), {"e": email, "p": hashed})
|
||||
return {"status": "success"}
|
||||
|
||||
@app.post("/api/auth/login")
|
||||
async def login(form_data: OAuth2PasswordRequestForm = Depends()):
|
||||
async with AsyncSessionLocal() as session:
|
||||
res = await session.execute(text("SELECT id, password_hash FROM data.users WHERE email = :e"), {"e": form_data.username})
|
||||
user = res.fetchone()
|
||||
if not user or not verify_password(form_data.password, user.password_hash):
|
||||
raise HTTPException(status_code=401, detail="Hibás adatok")
|
||||
token = jwt.encode({"sub": str(user.id), "exp": datetime.utcnow() + timedelta(days=1)}, SECRET_KEY, algorithm=ALGORITHM)
|
||||
return {"access_token": token, "token_type": "bearer"}
|
||||
|
||||
@app.get("/api/my_vehicles")
|
||||
async def my_vehicles(user_id: 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 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")
|
||||
res = await session.execute(q, {"uid": user_id})
|
||||
return [dict(r._mapping) for r in res.fetchall()]
|
||||
|
||||
@app.get("/api/ref/cost_types")
|
||||
async def cost_types():
|
||||
async with AsyncSessionLocal() as session:
|
||||
res = await session.execute(text("SELECT code, name, parent_code FROM ref.cost_types"))
|
||||
rows = res.fetchall()
|
||||
tree = {}
|
||||
for r in rows:
|
||||
if not r.parent_code: tree[r.code] = {"label": r.name, "subs": {}}
|
||||
for r in rows:
|
||||
if r.parent_code and r.parent_code in tree: tree[r.parent_code]["subs"][r.code] = r.name
|
||||
return tree
|
||||
|
||||
@app.get("/")
|
||||
async def index(): return FileResponse("/app/frontend/index.html")
|
||||
191
code-server-config/data/User/History/-3487e1e/VR1d.py
Executable file
191
code-server-config/data/User/History/-3487e1e/VR1d.py
Executable file
@@ -0,0 +1,191 @@
|
||||
from fastapi import FastAPI, HTTPException, Request
|
||||
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): 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
|
||||
|
||||
class IssueReport(BaseModel):
|
||||
vehicle_id: int
|
||||
description: str
|
||||
is_critical: bool
|
||||
|
||||
class IssueResolve(BaseModel):
|
||||
vehicle_id: int
|
||||
|
||||
# --- 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 ---
|
||||
|
||||
@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
|
||||
}
|
||||
|
||||
# 1. HIBA BEJELENTÉS
|
||||
@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'
|
||||
# Update Vehicle
|
||||
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})
|
||||
# Log
|
||||
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"}
|
||||
|
||||
# 2. HIBA JAVÍTÁS (EZ HIÁNYZOTT!)
|
||||
@app.post("/api/resolve_issue")
|
||||
async def resolve_issue(data: IssueResolve):
|
||||
user_id = 1
|
||||
async with AsyncSessionLocal() as session:
|
||||
async with session.begin():
|
||||
# Lekérjük a régi hibát a loghoz
|
||||
res = await session.execute(text("SELECT status, current_issue FROM data.vehicles WHERE id = :vid"), {"vid": data.vehicle_id})
|
||||
curr = res.fetchone()
|
||||
|
||||
# Visszaállítás
|
||||
await session.execute(text("UPDATE data.vehicles SET status = 'OK', current_issue = NULL WHERE id = :vid"),
|
||||
{"vid": data.vehicle_id})
|
||||
|
||||
# Logolás: Ki javította meg?
|
||||
await create_audit_log(session, user_id, "ISSUE_RESOLVED", data.vehicle_id,
|
||||
details="Probléma megoldva", old_val=curr.status if curr else "UNKNOWN", new_val="OK")
|
||||
|
||||
return {"status": "success", "message": "Jármű státusza helyreállítva!"}
|
||||
|
||||
# --- EGYÉB VÉGPONTOK (Register, Fleet...) ---
|
||||
@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"))
|
||||
return [{"id": r.id, "brand": r.brand, "model": r.model_name, "category": r.category} for r in result.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:
|
||||
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})
|
||||
await create_audit_log(session, 1, "REGISTER_VEHICLE", vid, "Jármű regisztrálva")
|
||||
return {"status": "success"}
|
||||
|
||||
@app.get("/api/fleet/members")
|
||||
async def get_team_members():
|
||||
async with AsyncSessionLocal() as session:
|
||||
res = 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"))
|
||||
return [{"email": r.email, "role": r.role, "joined_at": r.joined_at, "country": r.country} for r in res.fetchall()]
|
||||
|
||||
@app.post("/api/fleet/invite")
|
||||
async def invite_member(data: InviteRequest):
|
||||
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 (:e, 1, :r, :l, :t, NOW() + INTERVAL '7 days')"), {"e": data.email, "r": data.role, "l": data.access_level, "t": 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"}
|
||||
91
code-server-config/data/User/History/-3487e1e/WE3Y.py
Executable file
91
code-server-config/data/User/History/-3487e1e/WE3Y.py
Executable file
@@ -0,0 +1,91 @@
|
||||
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")
|
||||
195
code-server-config/data/User/History/-3487e1e/dYmD.py
Executable file
195
code-server-config/data/User/History/-3487e1e/dYmD.py
Executable file
@@ -0,0 +1,195 @@
|
||||
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
|
||||
|
||||
class CostCreate(BaseModel):
|
||||
vehicle_id: int
|
||||
cost_type: str
|
||||
amount: float
|
||||
currency: str
|
||||
date: date
|
||||
mileage: int
|
||||
description: Optional[str] = ""
|
||||
|
||||
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):
|
||||
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 ---
|
||||
|
||||
@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 = 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")
|
||||
|
||||
# 2. ÉVES KÖLTSÉG SZÁMÍTÁSA (Idei év)
|
||||
# Egyszerűség kedvéért most csak a User pénznemében lévőket adjuk össze
|
||||
# (Később itt lesz a valutaváltó logika)
|
||||
current_year = date.today().year
|
||||
q_cost = text("""
|
||||
SELECT COALESCE(SUM(amount), 0)
|
||||
FROM data.costs
|
||||
WHERE vehicle_id = :vid
|
||||
AND EXTRACT(YEAR FROM date) = :year
|
||||
AND currency = :curr
|
||||
""")
|
||||
res_cost = await session.execute(q_cost, {"vid": vehicle_id, "year": current_year, "curr": car.default_currency})
|
||||
year_cost = res_cost.scalar()
|
||||
|
||||
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": year_cost # <--- EZT HIÁNYOLTAD!
|
||||
}
|
||||
|
||||
# --- MARADÉK VÉGPONTOK (Cost, History, Report, Resolve, Fleet...) ---
|
||||
@app.post("/api/add_cost")
|
||||
async def add_cost(data: CostCreate):
|
||||
user_id = 1
|
||||
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) 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})
|
||||
|
||||
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})
|
||||
await create_audit_log(session, user_id, "MILEAGE_UPDATE", data.vehicle_id, f"Km: {data.mileage}")
|
||||
|
||||
await create_audit_log(session, user_id, "ADD_COST", data.vehicle_id, f"{data.cost_type}: {data.amount}")
|
||||
return {"status": "success"}
|
||||
|
||||
@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, '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, '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"}
|
||||
170
code-server-config/data/User/History/-3487e1e/eaD4.py
Executable file
170
code-server-config/data/User/History/-3487e1e/eaD4.py
Executable file
@@ -0,0 +1,170 @@
|
||||
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"}
|
||||
1
code-server-config/data/User/History/-3487e1e/entries.json
Executable file
1
code-server-config/data/User/History/-3487e1e/entries.json
Executable file
@@ -0,0 +1 @@
|
||||
{"version":1,"resource":"vscode-remote://192.168.100.43:8443/home/coder/project/backend/main.py","entries":[{"id":"7zSH.py","timestamp":1768944686120},{"id":"gVs3.py","timestamp":1768944898840},{"id":"eaD4.py","timestamp":1768945068633},{"id":"0MBT.py","timestamp":1768945735412},{"id":"VR1d.py","timestamp":1768945973442},{"id":"2JOI.py","timestamp":1768946205711},{"id":"dYmD.py","timestamp":1768946545949},{"id":"HFdy.py","timestamp":1768946891365},{"id":"zNYa.py","timestamp":1768947662698},{"id":"HYjM.py","timestamp":1768947811545},{"id":"DXvc.py","timestamp":1768948349423},{"id":"TFdq.py","timestamp":1768953406975},{"id":"MoK7.py","timestamp":1768953978619},{"id":"gleo.py","timestamp":1768954265874},{"id":"z1at.py","timestamp":1768954519769},{"id":"WE3Y.py","timestamp":1768954748699},{"id":"sEal.py","timestamp":1768954903048}]}
|
||||
134
code-server-config/data/User/History/-3487e1e/gVs3.py
Executable file
134
code-server-config/data/User/History/-3487e1e/gVs3.py
Executable file
@@ -0,0 +1,134 @@
|
||||
from fastapi import FastAPI, HTTPException
|
||||
from fastapi.responses import FileResponse
|
||||
from pydantic import BaseModel, validator
|
||||
# KIVETTÜK AZ EmailStr-t, mert hiányzik a library hozzá!
|
||||
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 # <--- JAVÍTVA: Sima string, nem EmailStr
|
||||
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()]
|
||||
|
||||
@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"}
|
||||
|
||||
# --- FLOTTA VÉGPONTOK ---
|
||||
|
||||
@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"}
|
||||
106
code-server-config/data/User/History/-3487e1e/gleo.py
Executable file
106
code-server-config/data/User/History/-3487e1e/gleo.py
Executable file
@@ -0,0 +1,106 @@
|
||||
from fastapi import FastAPI, HTTPException, UploadFile, File, 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 JWTError, jwt
|
||||
import bcrypt, os, uuid, traceback
|
||||
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()
|
||||
|
||||
# --- AUTH ---
|
||||
def get_password_hash(password: str):
|
||||
pwd_bytes = password.encode('utf-8')
|
||||
return bcrypt.hashpw(pwd_bytes[:72], bcrypt.gensalt()).decode('utf-8')
|
||||
|
||||
def verify_password(plain, hashed):
|
||||
return bcrypt.checkpw(plain.encode('utf-8')[:72], hashed.encode('utf-8'))
|
||||
|
||||
async def get_current_user(token: str = Depends(oauth2_scheme)):
|
||||
try:
|
||||
payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
|
||||
return int(payload.get("sub"))
|
||||
except: raise HTTPException(status_code=401)
|
||||
|
||||
# --- API ---
|
||||
@app.post("/api/auth/register")
|
||||
async def register(email: str = Form(...), password: str = Form(...)):
|
||||
async with AsyncSessionLocal() as session:
|
||||
async with session.begin():
|
||||
h = get_password_hash(password)
|
||||
await session.execute(text("INSERT INTO data.users (email, password_hash) VALUES (:e, :p)"), {"e": email, "p": h})
|
||||
return {"status": "success"}
|
||||
|
||||
@app.post("/api/auth/login")
|
||||
async def login(f: OAuth2PasswordRequestForm = Depends()):
|
||||
async with AsyncSessionLocal() as session:
|
||||
res = await session.execute(text("SELECT id, password_hash FROM data.users WHERE email = :e"), {"e": f.username})
|
||||
u = res.fetchone()
|
||||
if not u or not verify_password(f.password, u.password_hash): raise HTTPException(status_code=401)
|
||||
token = jwt.encode({"sub": str(u.id), "exp": datetime.utcnow() + timedelta(days=1)}, SECRET_KEY, algorithm=ALGORITHM)
|
||||
return {"access_token": token, "token_type": "bearer"}
|
||||
|
||||
@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 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.get("/api/vehicles")
|
||||
async def all_models():
|
||||
async with AsyncSessionLocal() as session:
|
||||
q = text("SELECT vm.id, m.name as brand, vm.model_name as model FROM ref.vehicle_models vm JOIN ref.vehicle_makes m ON vm.make_id = m.id ORDER BY brand, model")
|
||||
res = await session.execute(q)
|
||||
return [dict(r._mapping) for r in res.fetchall()]
|
||||
|
||||
class VehicleReg(BaseModel): model_id: int; vin: str; plate: str; mileage: int; purchase_date: date
|
||||
|
||||
@app.post("/api/register")
|
||||
async def register_vehicle(d: VehicleReg, uid: int = Depends(get_current_user)):
|
||||
async with AsyncSessionLocal() as session:
|
||||
async with session.begin():
|
||||
# Új jármű vagy meglévő
|
||||
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', :sd, :sm)"), {"vid": vid, "uid": uid, "sd": d.purchase_date, "sm": d.mileage})
|
||||
return {"status": "success"}
|
||||
|
||||
@app.get("/api/vehicle/{vid}")
|
||||
async def get_details(vid: int, uid: int = Depends(get_current_user)):
|
||||
async with AsyncSessionLocal() as session:
|
||||
q = text("SELECT v.id, v.current_plate as plate, m.name as brand, mo.model_name as model, vh.start_mileage as mileage, v.status, u.default_currency as currency 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")
|
||||
res = await session.execute(q, {"vid": vid, "uid": uid})
|
||||
car = res.fetchone()
|
||||
if not car: raise HTTPException(status_code=404)
|
||||
|
||||
# Idei költség
|
||||
c_q = text("SELECT SUM(amount) FROM data.costs WHERE vehicle_id = :vid AND EXTRACT(YEAR FROM date) = 2026")
|
||||
cost = (await session.execute(c_q, {"vid": vid})).scalar() or 0
|
||||
|
||||
return {**dict(car._mapping), "year_cost": cost}
|
||||
|
||||
@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(...), 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, mileage_at_cost) VALUES (:vid, :uid, :type, :amt, :curr, :date, :mil)"),
|
||||
{"vid": vehicle_id, "uid": uid, "type": cost_type, "amt": amount, "curr": currency, "date": date_str, "mil": mileage})
|
||||
return {"status": "success"}
|
||||
|
||||
@app.get("/")
|
||||
async def index(): return FileResponse("/app/frontend/index.html")
|
||||
81
code-server-config/data/User/History/-3487e1e/sEal.py
Executable file
81
code-server-config/data/User/History/-3487e1e/sEal.py
Executable file
@@ -0,0 +1,81 @@
|
||||
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, traceback
|
||||
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()
|
||||
|
||||
# --- AUTH ---
|
||||
def get_password_hash(p): return bcrypt.hashpw(p.encode('utf-8')[:72], bcrypt.gensalt()).decode('utf-8')
|
||||
def verify_password(p, h): return bcrypt.checkpw(p.encode('utf-8')[:72], h.encode('utf-8'))
|
||||
|
||||
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)
|
||||
|
||||
@app.post("/api/auth/register")
|
||||
async def register(email: str = Form(...), password: str = Form(...)):
|
||||
try:
|
||||
async with AsyncSessionLocal() as session:
|
||||
async with session.begin():
|
||||
await session.execute(text("INSERT INTO data.users (email, password_hash) VALUES (:e, :p)"),
|
||||
{"e": email, "p": get_password_hash(password)})
|
||||
return {"status": "success"}
|
||||
except Exception as e:
|
||||
return JSONResponse(status_code=400, content={"detail": "Email már létezik vagy adatbázis hiba."})
|
||||
|
||||
@app.post("/api/auth/login")
|
||||
async def login(f: OAuth2PasswordRequestForm = Depends()):
|
||||
async with AsyncSessionLocal() as session:
|
||||
res = await session.execute(text("SELECT id, password_hash FROM data.users WHERE email = :e"), {"e": f.username})
|
||||
u = res.fetchone()
|
||||
if not u or not verify_password(f.password, u.password_hash): raise HTTPException(status_code=401, detail="Hibás adatok")
|
||||
t = jwt.encode({"sub": str(u.id), "exp": datetime.utcnow() + timedelta(days=1)}, SECRET_KEY, algorithm=ALGORITHM)
|
||||
return {"access_token": t, "token_type": "bearer"}
|
||||
|
||||
# --- DATA ---
|
||||
@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)
|
||||
h = {}
|
||||
for r in res:
|
||||
if r.category not in h: h[r.category] = {}
|
||||
if r.brand not in h[r.category]: h[r.category][r.brand] = []
|
||||
h[r.category][r.brand].append({"id": r.model_id, "name": r.model_name})
|
||||
return h
|
||||
|
||||
@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()}
|
||||
|
||||
@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 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.get("/")
|
||||
async def index(): return FileResponse("/app/frontend/index.html")
|
||||
97
code-server-config/data/User/History/-3487e1e/z1at.py
Executable file
97
code-server-config/data/User/History/-3487e1e/z1at.py
Executable file
@@ -0,0 +1,97 @@
|
||||
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()
|
||||
|
||||
# --- AUTH HELPER ---
|
||||
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)
|
||||
|
||||
# --- DINAMIKUS METAADATOK (Ez az újdonság!) ---
|
||||
@app.get("/api/meta/vehicle-hierarchy")
|
||||
async def get_hierarchy():
|
||||
async with AsyncSessionLocal() as session:
|
||||
# Lekérjük az összes kategóriát, márkát és modellt egyben
|
||||
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, vm.model_name
|
||||
""")
|
||||
res = await session.execute(q)
|
||||
rows = res.fetchall()
|
||||
|
||||
hierarchy = {}
|
||||
for r in rows:
|
||||
cat = r.category
|
||||
brand = r.brand
|
||||
if cat not in hierarchy: hierarchy[cat] = {}
|
||||
if brand not in hierarchy[cat]: hierarchy[cat][brand] = []
|
||||
hierarchy[cat][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()}
|
||||
|
||||
# --- CORE API ---
|
||||
@app.post("/api/auth/login")
|
||||
async def login(f: OAuth2PasswordRequestForm = Depends()):
|
||||
async with AsyncSessionLocal() as session:
|
||||
res = await session.execute(text("SELECT id, password_hash FROM data.users WHERE email = :e"), {"e": f.username})
|
||||
u = res.fetchone()
|
||||
if not u or not bcrypt.checkpw(f.password.encode('utf-8')[:72], u.password_hash.encode('utf-8')):
|
||||
raise HTTPException(status_code=401)
|
||||
t = jwt.encode({"sub": str(u.id), "exp": datetime.utcnow() + timedelta(days=1)}, SECRET_KEY, algorithm=ALGORITHM)
|
||||
return {"access_token": t, "token_type": "bearer"}
|
||||
|
||||
@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
|
||||
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()]
|
||||
|
||||
class VehicleReg(BaseModel): model_id: int; vin: str; plate: str; mileage: int; purchase_date: date
|
||||
|
||||
@app.post("/api/register")
|
||||
async def register_vehicle(d: VehicleReg, 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', :sd, :sm)"), {"vid": vid, "uid": uid, "sd": d.purchase_date, "sm": d.mileage})
|
||||
return {"status": "success"}
|
||||
|
||||
@app.get("/")
|
||||
async def index(): return FileResponse("/app/frontend/index.html")
|
||||
217
code-server-config/data/User/History/-3487e1e/zNYa.py
Executable file
217
code-server-config/data/User/History/-3487e1e/zNYa.py
Executable file
@@ -0,0 +1,217 @@
|
||||
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"}
|
||||
Reference in New Issue
Block a user