Initial commit - Migrated to Dev environment
This commit is contained in:
BIN
backend/app/api/v1/endpoints/__pycache__/auth.cpython-312.pyc
Executable file
BIN
backend/app/api/v1/endpoints/__pycache__/auth.cpython-312.pyc
Executable file
Binary file not shown.
BIN
backend/app/api/v1/endpoints/__pycache__/billing.cpython-312.pyc
Executable file
BIN
backend/app/api/v1/endpoints/__pycache__/billing.cpython-312.pyc
Executable file
Binary file not shown.
BIN
backend/app/api/v1/endpoints/__pycache__/expenses.cpython-312.pyc
Executable file
BIN
backend/app/api/v1/endpoints/__pycache__/expenses.cpython-312.pyc
Executable file
Binary file not shown.
BIN
backend/app/api/v1/endpoints/__pycache__/fleet.cpython-312.pyc
Executable file
BIN
backend/app/api/v1/endpoints/__pycache__/fleet.cpython-312.pyc
Executable file
Binary file not shown.
BIN
backend/app/api/v1/endpoints/__pycache__/reports.cpython-312.pyc
Executable file
BIN
backend/app/api/v1/endpoints/__pycache__/reports.cpython-312.pyc
Executable file
Binary file not shown.
BIN
backend/app/api/v1/endpoints/__pycache__/users.cpython-312.pyc
Executable file
BIN
backend/app/api/v1/endpoints/__pycache__/users.cpython-312.pyc
Executable file
Binary file not shown.
BIN
backend/app/api/v1/endpoints/__pycache__/vehicles.cpython-312.pyc
Executable file
BIN
backend/app/api/v1/endpoints/__pycache__/vehicles.cpython-312.pyc
Executable file
Binary file not shown.
79
backend/app/api/v1/endpoints/admin.py
Executable file
79
backend/app/api/v1/endpoints/admin.py
Executable file
@@ -0,0 +1,79 @@
|
||||
from fastapi import APIRouter, Depends, HTTPException, status
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from sqlalchemy import select
|
||||
from typing import List
|
||||
|
||||
from app.db.session import get_db
|
||||
from app.api import deps
|
||||
from app.models.user import User, UserRole
|
||||
from app.models.system_settings import SystemSetting # ÚJ import
|
||||
from app.models.gamification import PointRule, LevelConfig, RegionalSetting
|
||||
from app.models.translation import Translation
|
||||
from app.services.translation_service import TranslationService
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
def check_admin_access(current_user: User, required_roles: List[UserRole]):
|
||||
if current_user.role not in required_roles:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_403_FORBIDDEN,
|
||||
detail="Nincs jogosultságod ehhez a művelethez."
|
||||
)
|
||||
|
||||
# --- ⚙️ ÚJ: DINAMIKUS RENDSZERBEÁLLÍTÁSOK (Pl. Jármű limit) ---
|
||||
|
||||
@router.get("/settings", response_model=List[dict])
|
||||
async def get_all_system_settings(
|
||||
db: AsyncSession = Depends(get_db),
|
||||
current_user: User = Depends(deps.get_current_user)
|
||||
):
|
||||
"""Az összes globális rendszerbeállítás listázása."""
|
||||
check_admin_access(current_user, [UserRole.SUPERUSER])
|
||||
result = await db.execute(select(SystemSetting))
|
||||
settings = result.scalars().all()
|
||||
return [{"key": s.key, "value": s.value, "description": s.description} for s in settings]
|
||||
|
||||
@router.put("/settings/{key}")
|
||||
async def update_system_setting(
|
||||
key: str,
|
||||
new_value: int, # Később lehet JSON is, ha komplexebb a beállítás
|
||||
db: AsyncSession = Depends(get_db),
|
||||
current_user: User = Depends(deps.get_current_user)
|
||||
):
|
||||
"""Egy adott beállítás (pl. FREE_VEHICLE_LIMIT) módosítása."""
|
||||
check_admin_access(current_user, [UserRole.SUPERUSER])
|
||||
|
||||
result = await db.execute(select(SystemSetting).where(SystemSetting.key == key))
|
||||
setting = result.scalar_one_or_none()
|
||||
|
||||
if not setting:
|
||||
raise HTTPException(status_code=404, detail="Beállítás nem található")
|
||||
|
||||
setting.value = new_value
|
||||
await db.commit()
|
||||
return {"status": "success", "key": key, "new_value": new_value}
|
||||
|
||||
|
||||
# --- 🌍 FORDÍTÁSOK KEZELÉSE (Meglévő kódod) ---
|
||||
|
||||
@router.post("/translations", status_code=status.HTTP_201_CREATED)
|
||||
async def add_translation_draft(
|
||||
key: str, lang: str, value: str,
|
||||
db: AsyncSession = Depends(get_db),
|
||||
current_user: User = Depends(deps.get_current_user)
|
||||
):
|
||||
check_admin_access(current_user, [UserRole.SUPERUSER, UserRole.REGIONAL_ADMIN])
|
||||
new_t = Translation(key=key, lang_code=lang, value=value, is_published=False)
|
||||
db.add(new_t)
|
||||
await db.commit()
|
||||
return {"message": "Fordítás piszkozatként mentve. Ne felejtsd el publikálni!"}
|
||||
|
||||
@router.post("/translations/publish")
|
||||
async def publish_translations(
|
||||
db: AsyncSession = Depends(get_db),
|
||||
current_user: User = Depends(deps.get_current_user)
|
||||
):
|
||||
check_admin_access(current_user, [UserRole.SUPERUSER, UserRole.REGIONAL_ADMIN])
|
||||
await TranslationService.publish_all(db)
|
||||
return {"message": "Sikeres publikálás! A változások minden szerveren élesedtek."}
|
||||
|
||||
91
backend/app/api/v1/endpoints/auth.py
Executable file
91
backend/app/api/v1/endpoints/auth.py
Executable file
@@ -0,0 +1,91 @@
|
||||
from fastapi import APIRouter, Depends, HTTPException, Request
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from sqlalchemy import select, text
|
||||
from datetime import datetime, timedelta
|
||||
import hashlib, secrets
|
||||
|
||||
from app.db.session import get_db
|
||||
from app.models.user import User
|
||||
from app.core.security import get_password_hash
|
||||
from app.services.email_manager import email_manager
|
||||
from app.services.config_service import config
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
@router.post("/register")
|
||||
async def register(
|
||||
request: Request,
|
||||
email: str,
|
||||
password: str,
|
||||
first_name: str,
|
||||
last_name: str,
|
||||
db: AsyncSession = Depends(get_db)
|
||||
):
|
||||
ip = request.client.host
|
||||
|
||||
# 1. BOT-VÉDELEM
|
||||
throttle_min = await config.get_setting('registration_throttle_minutes', default=10)
|
||||
check_throttle = await db.execute(text("""
|
||||
SELECT count(*) FROM data.audit_logs
|
||||
WHERE ip_address = :ip AND action = 'USER_REGISTERED' AND created_at > :t
|
||||
"""), {'ip': ip, 't': datetime.utcnow() - timedelta(minutes=int(throttle_min))})
|
||||
|
||||
if check_throttle.scalar() > 0:
|
||||
raise HTTPException(status_code=429, detail="Túl sok próbálkozás. Várj pár percet!")
|
||||
|
||||
# 2. REGISZTRÁCIÓ
|
||||
res = await db.execute(select(User).where(User.email == email))
|
||||
if res.scalars().first():
|
||||
raise HTTPException(status_code=400, detail="Ez az email már foglalt.")
|
||||
|
||||
new_user = User(
|
||||
email=email,
|
||||
hashed_password=get_password_hash(password),
|
||||
first_name=first_name,
|
||||
last_name=last_name,
|
||||
is_active=False
|
||||
)
|
||||
db.add(new_user)
|
||||
await db.flush()
|
||||
|
||||
# 3. TOKEN & LOG
|
||||
raw_token = secrets.token_urlsafe(48)
|
||||
token_hash = hashlib.sha256(raw_token.encode()).hexdigest()
|
||||
await db.execute(text("""
|
||||
INSERT INTO data.verification_tokens (user_id, token_hash, token_type, expires_at)
|
||||
VALUES (:u, :t, 'email_verify', :e)
|
||||
"""), {'u': new_user.id, 't': token_hash, 'e': datetime.utcnow() + timedelta(days=2)})
|
||||
|
||||
await db.execute(text("""
|
||||
INSERT INTO data.audit_logs (user_id, action, endpoint, method, ip_address)
|
||||
VALUES (:u, 'USER_REGISTERED', '/register', 'POST', :ip)
|
||||
"""), {'u': new_user.id, 'ip': ip})
|
||||
|
||||
# 4. EMAIL KÜLDÉS
|
||||
verify_link = f"http://{request.headers.get('host')}/api/v1/auth/verify?token={raw_token}"
|
||||
email_body = f"<h1>Szia {first_name}!</h1><p>Aktiváld a fiókod: <a href='{verify_link}'>{verify_link}</a></p>"
|
||||
|
||||
await email_manager.send_email(
|
||||
recipient=email,
|
||||
subject="Regisztráció megerősítése",
|
||||
body=email_body,
|
||||
email_type="registration",
|
||||
user_id=new_user.id
|
||||
)
|
||||
|
||||
await db.commit()
|
||||
return {"message": "Sikeres regisztráció! Ellenőrizd az email fiókodat."}
|
||||
|
||||
@router.get("/verify")
|
||||
async def verify_account(token: str, db: AsyncSession = Depends(get_db)):
|
||||
token_hash = hashlib.sha256(token.encode()).hexdigest()
|
||||
query = text("SELECT user_id FROM data.verification_tokens WHERE token_hash = :t AND is_used = False")
|
||||
res = await db.execute(query, {'t': token_hash})
|
||||
row = res.fetchone()
|
||||
if not row:
|
||||
raise HTTPException(status_code=400, detail="Érvénytelen aktiváló link")
|
||||
|
||||
await db.execute(text("UPDATE data.users SET is_active = True WHERE id = :id"), {'id': row[0]})
|
||||
await db.execute(text("UPDATE data.verification_tokens SET is_used = True WHERE token_hash = :t"), {'t': token_hash})
|
||||
await db.commit()
|
||||
return {"message": "Fiók aktiválva!"}
|
||||
125
backend/app/api/v1/endpoints/billing.py
Executable file
125
backend/app/api/v1/endpoints/billing.py
Executable file
@@ -0,0 +1,125 @@
|
||||
from fastapi import APIRouter, Depends, HTTPException, Query
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from sqlalchemy import text
|
||||
from app.api.deps import get_db, get_current_user
|
||||
from typing import List, Dict
|
||||
import secrets
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
# 1. EGYENLEG LEKÉRDEZÉSE (A felhasználó Széfjéhez kötve)
|
||||
@router.get("/balance")
|
||||
async def get_balance(db: AsyncSession = Depends(get_db), current_user = Depends(get_current_user)):
|
||||
"""
|
||||
Visszaadja a felhasználó aktuális kreditegyenlegét és a Széfje (Cége) nevét.
|
||||
"""
|
||||
query = text("""
|
||||
SELECT
|
||||
uc.balance,
|
||||
c.name as company_name
|
||||
FROM data.user_credits uc
|
||||
JOIN data.companies c ON uc.user_id = c.owner_id
|
||||
WHERE uc.user_id = :user_id
|
||||
LIMIT 1
|
||||
""")
|
||||
result = await db.execute(query, {"user_id": current_user.id})
|
||||
row = result.fetchone()
|
||||
|
||||
if not row:
|
||||
return {
|
||||
"company_name": "Privát Széf",
|
||||
"balance": 0.0,
|
||||
"currency": "Credit"
|
||||
}
|
||||
|
||||
return {
|
||||
"company_name": row.company_name,
|
||||
"balance": float(row.balance),
|
||||
"currency": "Credit"
|
||||
}
|
||||
|
||||
# 2. TRANZAKCIÓS ELŐZMÉNYEK
|
||||
@router.get("/history")
|
||||
async def get_history(db: AsyncSession = Depends(get_db), current_user = Depends(get_current_user)):
|
||||
"""
|
||||
Kilistázza a kreditmozgásokat (bevételek, költések, voucherek).
|
||||
"""
|
||||
query = text("""
|
||||
SELECT amount, reason, created_at
|
||||
FROM data.credit_transactions
|
||||
WHERE user_id = :user_id
|
||||
ORDER BY created_at DESC
|
||||
""")
|
||||
result = await db.execute(query, {"user_id": current_user.id})
|
||||
return [dict(row._mapping) for row in result.fetchall()]
|
||||
|
||||
# 3. VOUCHER BEVÁLTÁS (A rendszer gazdaságának motorja)
|
||||
@router.post("/vouchers/redeem")
|
||||
async def redeem_voucher(code: str, db: AsyncSession = Depends(get_db), current_user = Depends(get_current_user)):
|
||||
"""
|
||||
Bevált egy kódot, és jóváírja az értékét a felhasználó egyenlegén.
|
||||
"""
|
||||
# 1. Voucher ellenőrzése
|
||||
check_query = text("""
|
||||
SELECT id, value, is_used, expires_at
|
||||
FROM data.vouchers
|
||||
WHERE code = :code AND is_used = False AND (expires_at > now() OR expires_at IS NULL)
|
||||
""")
|
||||
res = await db.execute(check_query, {"code": code.strip().upper()})
|
||||
voucher = res.fetchone()
|
||||
|
||||
if not voucher:
|
||||
raise HTTPException(status_code=400, detail="Érvénytelen, lejárt vagy már felhasznált kód.")
|
||||
|
||||
# 2. Egyenleg frissítése (vagy létrehozása, ha még nincs sor a user_credits-ben)
|
||||
update_balance = text("""
|
||||
INSERT INTO data.user_credits (user_id, balance)
|
||||
VALUES (:u, :v)
|
||||
ON CONFLICT (user_id) DO UPDATE SET balance = data.user_credits.balance + :v
|
||||
""")
|
||||
await db.execute(update_balance, {"u": current_user.id, "v": voucher.value})
|
||||
|
||||
# 3. Tranzakció naplózása
|
||||
log_transaction = text("""
|
||||
INSERT INTO data.credit_transactions (user_id, amount, reason)
|
||||
VALUES (:u, :v, :r)
|
||||
""")
|
||||
await db.execute(log_transaction, {
|
||||
"u": current_user.id,
|
||||
"v": voucher.value,
|
||||
"r": f"Voucher beváltva: {code}"
|
||||
})
|
||||
|
||||
# 4. Voucher megjelölése felhasználtként
|
||||
await db.execute(text("""
|
||||
UPDATE data.vouchers
|
||||
SET is_used = True, used_by = :u, used_at = now()
|
||||
WHERE id = :vid
|
||||
"""), {"u": current_user.id, "vid": voucher.id})
|
||||
|
||||
await db.commit()
|
||||
return {"status": "success", "added_value": float(voucher.value), "message": "Kredit jóváírva!"}
|
||||
|
||||
# 4. ADMIN: VOUCHER GENERÁLÁS (Csak Neked)
|
||||
@router.post("/vouchers/generate", include_in_schema=True)
|
||||
async def generate_vouchers(
|
||||
count: int = 1,
|
||||
value: float = 500.0,
|
||||
batch_name: str = "ADMIN_GEN",
|
||||
db: AsyncSession = Depends(get_db)
|
||||
):
|
||||
"""
|
||||
Tömeges voucher generálás az admin felületről.
|
||||
"""
|
||||
generated_codes = []
|
||||
for _ in range(count):
|
||||
# Generálunk egy SF-XXXX-XXXX formátumú kódot
|
||||
code = f"SF-{secrets.token_hex(3).upper()}-{secrets.token_hex(3).upper()}"
|
||||
await db.execute(text("""
|
||||
INSERT INTO data.vouchers (code, value, batch_id, expires_at)
|
||||
VALUES (:c, :v, :b, now() + interval '90 days')
|
||||
"""), {"c": code, "v": value, "b": batch_name})
|
||||
generated_codes.append(code)
|
||||
|
||||
await db.commit()
|
||||
return {"batch": batch_name, "count": count, "codes": generated_codes}
|
||||
51
backend/app/api/v1/endpoints/expenses.py
Executable file
51
backend/app/api/v1/endpoints/expenses.py
Executable file
@@ -0,0 +1,51 @@
|
||||
from fastapi import APIRouter, Depends, HTTPException
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from sqlalchemy import text
|
||||
from app.api.deps import get_db, get_current_user
|
||||
from pydantic import BaseModel
|
||||
from datetime import date
|
||||
from typing import Optional
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
class ExpenseCreate(BaseModel):
|
||||
vehicle_id: str
|
||||
category: str # Pl: REFUELING, SERVICE, INSURANCE
|
||||
amount: float
|
||||
date: date
|
||||
odometer_value: Optional[float] = None
|
||||
description: Optional[str] = None
|
||||
|
||||
@router.post("/add")
|
||||
async def add_expense(
|
||||
expense: ExpenseCreate,
|
||||
db: AsyncSession = Depends(get_db),
|
||||
current_user = Depends(get_current_user)
|
||||
):
|
||||
"""
|
||||
Új költség rögzítése egy járműhöz.
|
||||
"""
|
||||
# 1. Ellenőrizzük, hogy a jármű létezik-e
|
||||
query = text("SELECT id FROM data.vehicles WHERE id = :v_id")
|
||||
res = await db.execute(query, {"v_id": expense.vehicle_id})
|
||||
if not res.fetchone():
|
||||
raise HTTPException(status_code=404, detail="Jármű nem található.")
|
||||
|
||||
# 2. Beszúrás a vehicle_expenses táblába
|
||||
insert_query = text("""
|
||||
INSERT INTO data.vehicle_expenses
|
||||
(vehicle_id, category, amount, date, odometer_value, description)
|
||||
VALUES (:v_id, :cat, :amt, :date, :odo, :desc)
|
||||
""")
|
||||
|
||||
await db.execute(insert_query, {
|
||||
"v_id": expense.vehicle_id,
|
||||
"cat": expense.category,
|
||||
"amt": expense.amount,
|
||||
"date": expense.date,
|
||||
"odo": expense.odometer_value,
|
||||
"desc": expense.description
|
||||
})
|
||||
|
||||
await db.commit()
|
||||
return {"status": "success", "message": "Költség rögzítve."}
|
||||
46
backend/app/api/v1/endpoints/fleet.py
Executable file
46
backend/app/api/v1/endpoints/fleet.py
Executable file
@@ -0,0 +1,46 @@
|
||||
from fastapi import APIRouter, Depends, HTTPException
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from sqlalchemy import select, text
|
||||
from app.api.deps import get_db, get_current_user
|
||||
from app.models.vehicle import Vehicle
|
||||
from app.models.company import CompanyMember
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
@router.get("/vehicles")
|
||||
async def get_my_vehicles(db: AsyncSession = Depends(get_db), current_user = Depends(get_current_user)):
|
||||
# Megkeressük a cégeket (széfeket), amikhez a felhasználónak köze van
|
||||
company_query = select(CompanyMember.company_id).where(CompanyMember.user_id == current_user.id)
|
||||
company_res = await db.execute(company_query)
|
||||
company_ids = company_res.scalars().all()
|
||||
|
||||
if not company_ids:
|
||||
return []
|
||||
|
||||
# Lekérjük az összes járművet, ami ezekhez a cégekhez tartozik
|
||||
query = select(Vehicle).where(Vehicle.current_company_id.in_(company_ids))
|
||||
result = await db.execute(query)
|
||||
return result.scalars().all()
|
||||
|
||||
@router.post("/vehicles")
|
||||
async def add_vehicle(vehicle_in: dict, db: AsyncSession = Depends(get_db), current_user = Depends(get_current_user)):
|
||||
# Itt a meglévő logika fut tovább, de a Vehicle-t a user alapértelmezett cégéhez kötjük
|
||||
# Először lekérjük a user "owner" típusú cégét
|
||||
org_query = text("SELECT company_id FROM data.company_members WHERE user_id = :uid AND role = 'owner' LIMIT 1")
|
||||
org_res = await db.execute(org_query, {"uid": current_user.id})
|
||||
company_id = org_res.scalar()
|
||||
|
||||
if not company_id:
|
||||
raise HTTPException(status_code=404, detail="Nem található saját széf a jármű rögzítéséhez.")
|
||||
|
||||
# Új jármű létrehozása az új modell alapján
|
||||
new_vehicle = Vehicle(
|
||||
current_company_id=company_id,
|
||||
brand_id=vehicle_in.get("brand_id"),
|
||||
model_name=vehicle_in.get("model_name"),
|
||||
identification_number=vehicle_in.get("vin"),
|
||||
license_plate=vehicle_in.get("license_plate")
|
||||
)
|
||||
db.add(new_vehicle)
|
||||
await db.commit()
|
||||
return {"status": "success", "vehicle_id": str(new_vehicle.id)}
|
||||
54
backend/app/api/v1/endpoints/gamification.py
Executable file
54
backend/app/api/v1/endpoints/gamification.py
Executable file
@@ -0,0 +1,54 @@
|
||||
from fastapi import APIRouter, Depends, HTTPException
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from sqlalchemy import select
|
||||
from typing import List
|
||||
|
||||
from app.db.session import get_db
|
||||
from app.api import deps
|
||||
from app.models.user import User
|
||||
from app.models.gamification import UserStats, UserBadge, Badge
|
||||
from app.schemas.social import UserStatSchema, BadgeSchema # Itt feltételezzük, hogy a sémákat már létrehoztad
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
@router.get("/my-stats", response_model=UserStatSchema)
|
||||
async def get_my_stats(
|
||||
db: AsyncSession = Depends(get_db),
|
||||
current_user: User = Depends(deps.get_current_user)
|
||||
):
|
||||
"""
|
||||
A bejelentkezett felhasználó aktuális pontszámának és szintjének lekérése.
|
||||
"""
|
||||
result = await db.execute(
|
||||
select(UserStats).where(UserStats.user_id == current_user.id)
|
||||
)
|
||||
stats = result.scalar_one_or_none()
|
||||
|
||||
if not stats:
|
||||
# Ha még nincs statisztika (új user), visszaadunk egy alapértelmezett kezdő állapotot
|
||||
return {
|
||||
"total_points": 0,
|
||||
"current_level": 1,
|
||||
"last_updated": None
|
||||
}
|
||||
|
||||
return stats
|
||||
|
||||
@router.get("/my-badges", response_model=List[BadgeSchema])
|
||||
async def get_my_badges(
|
||||
db: AsyncSession = Depends(get_db),
|
||||
current_user: User = Depends(deps.get_current_user)
|
||||
):
|
||||
"""
|
||||
A felhasználó által eddig megszerzett összes jelvény listázása.
|
||||
"""
|
||||
# Összekapcsoljuk a Badge és UserBadge táblákat
|
||||
query = (
|
||||
select(Badge.name, Badge.description, UserBadge.earned_at)
|
||||
.join(UserBadge, Badge.id == UserBadge.badge_id)
|
||||
.where(UserBadge.user_id == current_user.id)
|
||||
)
|
||||
|
||||
result = await db.execute(query)
|
||||
# Az .all() itt Tuple-öket ad vissza, amiket a Pydantic automatikusan validál
|
||||
return result.all()
|
||||
12
backend/app/api/v1/endpoints/providers.py
Executable file
12
backend/app/api/v1/endpoints/providers.py
Executable file
@@ -0,0 +1,12 @@
|
||||
from fastapi import APIRouter, Depends
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from app.db.session import get_db
|
||||
from app.schemas.social import ServiceProviderCreate, ServiceProviderResponse
|
||||
from app.services.social_service import create_service_provider
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
@router.post("/", response_model=ServiceProviderResponse)
|
||||
async def add_provider(provider_data: ServiceProviderCreate, db: AsyncSession = Depends(get_db)):
|
||||
user_id = 2
|
||||
return await create_service_provider(db, provider_data, user_id)
|
||||
50
backend/app/api/v1/endpoints/reports.py
Executable file
50
backend/app/api/v1/endpoints/reports.py
Executable file
@@ -0,0 +1,50 @@
|
||||
from fastapi import APIRouter, Depends
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from sqlalchemy import text
|
||||
from app.api.deps import get_db, get_current_user
|
||||
|
||||
router = APIRouter() # EZ HIÁNYZOTT!
|
||||
|
||||
@router.get("/summary/{vehicle_id}")
|
||||
async def get_vehicle_summary(vehicle_id: str, db: AsyncSession = Depends(get_db), current_user = Depends(get_current_user)):
|
||||
"""
|
||||
Összesített jelentés egy járműhöz: kategóriánkénti költségek.
|
||||
"""
|
||||
query = text("""
|
||||
SELECT
|
||||
category,
|
||||
SUM(amount) as total_amount,
|
||||
COUNT(*) as transaction_count
|
||||
FROM data.vehicle_expenses
|
||||
WHERE vehicle_id = :v_id
|
||||
GROUP BY category
|
||||
""")
|
||||
|
||||
result = await db.execute(query, {"v_id": vehicle_id})
|
||||
rows = result.fetchall()
|
||||
|
||||
total_cost = sum(row.total_amount for row in rows) if rows else 0
|
||||
|
||||
return {
|
||||
"vehicle_id": vehicle_id,
|
||||
"total_cost": float(total_cost),
|
||||
"breakdown": [dict(row._mapping) for row in rows]
|
||||
}
|
||||
|
||||
@router.get("/trends/{vehicle_id}")
|
||||
async def get_monthly_trends(vehicle_id: str, db: AsyncSession = Depends(get_db), current_user = Depends(get_current_user)):
|
||||
"""
|
||||
Visszaadja az utolsó 6 hónap költéseit havi bontásban.
|
||||
"""
|
||||
query = text("""
|
||||
SELECT
|
||||
TO_CHAR(date, 'YYYY-MM') as month,
|
||||
SUM(amount) as monthly_total
|
||||
FROM data.vehicle_expenses
|
||||
WHERE vehicle_id = :v_id
|
||||
GROUP BY month
|
||||
ORDER BY month DESC
|
||||
LIMIT 6
|
||||
""")
|
||||
result = await db.execute(query, {"v_id": vehicle_id})
|
||||
return [dict(row._mapping) for row in result.fetchall()]
|
||||
72
backend/app/api/v1/endpoints/search.py
Executable file
72
backend/app/api/v1/endpoints/search.py
Executable file
@@ -0,0 +1,72 @@
|
||||
from fastapi import APIRouter, Depends, HTTPException
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from sqlalchemy import text
|
||||
from app.db.session import get_db
|
||||
from app.api.deps import get_current_user
|
||||
from app.services.matching_service import matching_service
|
||||
from app.services.config_service import config
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
@router.get("/match")
|
||||
async def match_service(
|
||||
lat: float,
|
||||
lng: float,
|
||||
radius: int = 20,
|
||||
db: AsyncSession = Depends(get_db),
|
||||
current_user = Depends(get_current_user)
|
||||
):
|
||||
# 1. SQL lekérdezés: Haversine-formula a távolság számításhoz
|
||||
# 6371 a Föld sugara km-ben
|
||||
query = text("""
|
||||
SELECT
|
||||
o.id,
|
||||
o.name,
|
||||
ol.latitude,
|
||||
ol.longitude,
|
||||
ol.label as location_name,
|
||||
(6371 * 2 * ASIN(SQRT(
|
||||
POWER(SIN((RADIANS(ol.latitude) - RADIANS(:lat)) / 2), 2) +
|
||||
COS(RADIANS(:lat)) * COS(RADIANS(ol.latitude)) *
|
||||
POWER(SIN((RADIANS(ol.longitude) - RADIANS(:lng)) / 2), 2)
|
||||
))) AS distance
|
||||
FROM data.organizations o
|
||||
JOIN data.organization_locations ol ON o.id = ol.organization_id
|
||||
WHERE o.org_type = 'SERVICE'
|
||||
AND o.is_active = True
|
||||
HAVING
|
||||
(6371 * 2 * ASIN(SQRT(
|
||||
POWER(SIN((RADIANS(ol.latitude) - RADIANS(:lat)) / 2), 2) +
|
||||
COS(RADIANS(:lat)) * COS(RADIANS(ol.latitude)) *
|
||||
POWER(SIN((RADIANS(ol.longitude) - RADIANS(:lng)) / 2), 2)
|
||||
))) <= :radius
|
||||
ORDER BY distance ASC
|
||||
""")
|
||||
|
||||
result = await db.execute(query, {"lat": lat, "lng": lng, "radius": radius})
|
||||
|
||||
# Adatok átalakítása a MatchingService számára (mock rating-et adunk hozzá, amíg nincs review tábla)
|
||||
services_to_rank = []
|
||||
for row in result.all():
|
||||
services_to_rank.append({
|
||||
"id": row.id,
|
||||
"name": row.name,
|
||||
"distance": row.distance,
|
||||
"rating": 4.5, # Alapértelmezett, amíg nincs kész az értékelési rendszer
|
||||
"tier": "gold" if row.id == 1 else "free" # Példa logika
|
||||
})
|
||||
|
||||
if not services_to_rank:
|
||||
return {"status": "no_results", "message": "Nem található szerviz a megadott körzetben."}
|
||||
|
||||
# 2. Limit lekérése a beállításokból
|
||||
limit = await config.get_setting('match_limit_default', default=5)
|
||||
|
||||
# 3. Okos rangsorolás (Admin súlyozás alapján)
|
||||
ranked_results = await matching_service.rank_services(services_to_rank)
|
||||
|
||||
return {
|
||||
"user_location": {"lat": lat, "lng": lng},
|
||||
"radius_km": radius,
|
||||
"results": ranked_results[:limit]
|
||||
}
|
||||
15
backend/app/api/v1/endpoints/social.py
Executable file
15
backend/app/api/v1/endpoints/social.py
Executable file
@@ -0,0 +1,15 @@
|
||||
from fastapi import APIRouter, Depends
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from app.db.session import get_db
|
||||
from app.services.social_service import vote_for_provider, get_leaderboard
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
@router.get("/leaderboard")
|
||||
async def read_leaderboard(limit: int = 10, db: AsyncSession = Depends(get_db)):
|
||||
return await get_leaderboard(db, limit)
|
||||
|
||||
@router.post("/vote/{provider_id}")
|
||||
async def provider_vote(provider_id: int, vote_value: int, db: AsyncSession = Depends(get_db)):
|
||||
user_id = 2
|
||||
return await vote_for_provider(db, user_id, provider_id, vote_value)
|
||||
16
backend/app/api/v1/endpoints/users.py
Executable file
16
backend/app/api/v1/endpoints/users.py
Executable file
@@ -0,0 +1,16 @@
|
||||
from fastapi import APIRouter, Depends
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from app.api.deps import get_db, get_current_user
|
||||
from app.schemas.user import UserResponse
|
||||
from app.models.user import User
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
@router.get("/me", response_model=UserResponse)
|
||||
async def read_users_me(
|
||||
db: AsyncSession = Depends(get_db),
|
||||
current_user: User = Depends(get_current_user),
|
||||
):
|
||||
"""Visszaadja a bejelentkezett felhasználó profilját"""
|
||||
return current_user
|
||||
23
backend/app/api/v1/endpoints/vehicle_search.py
Executable file
23
backend/app/api/v1/endpoints/vehicle_search.py
Executable file
@@ -0,0 +1,23 @@
|
||||
from fastapi import APIRouter, Depends, Query
|
||||
from sqlalchemy.orm import Session
|
||||
from app.db.session import get_db
|
||||
from app.models.vehicle import VehicleBrand # Feltételezve, hogy létezik a modell
|
||||
from typing import List
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
@router.get("/search/brands")
|
||||
def search_brands(q: str = Query(..., min_length=2), db: Session = Depends(get_db)):
|
||||
# 1. KERESÉS A SAJÁT ADATBÁZISBAN
|
||||
results = db.query(VehicleBrand).filter(
|
||||
VehicleBrand.name.ilike(f"%{q}%"),
|
||||
VehicleBrand.is_active == True
|
||||
).limit(10).all()
|
||||
|
||||
# 2. HA NINCS TALÁLAT, INDÍTHATJUK A BOT-OT (Logikai váz)
|
||||
if not results:
|
||||
# Itt hívnánk meg a Discovery Bot-ot async módon
|
||||
# discovery_bot.find_brand_remotely(q)
|
||||
return {"status": "not_found", "message": "Nincs találat, a Bot elindult keresni...", "data": []}
|
||||
|
||||
return {"status": "success", "data": results}
|
||||
59
backend/app/api/v1/endpoints/vehicles.py
Executable file
59
backend/app/api/v1/endpoints/vehicles.py
Executable file
@@ -0,0 +1,59 @@
|
||||
from fastapi import APIRouter, Depends, Query, HTTPException
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from sqlalchemy import text
|
||||
from app.api.deps import get_db, get_current_user
|
||||
from typing import List, Dict, Optional
|
||||
from app.models.vehicle import Vehicle
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
@router.get("/search/brands")
|
||||
async def search_brands(q: str = Query(..., min_length=2), db: AsyncSession = Depends(get_db)):
|
||||
query = text("""
|
||||
SELECT id, name, slug, country_of_origin
|
||||
FROM data.vehicle_brands
|
||||
WHERE (name ILIKE :q OR slug ILIKE :q) AND is_active = true
|
||||
ORDER BY name ASC LIMIT 10
|
||||
""")
|
||||
# 1. Megvárjuk az execute-ot
|
||||
result = await db.execute(query, {"q": f"%{q}%"})
|
||||
# 2. Külön hívjuk a fetchall-t az eredményen
|
||||
rows = result.fetchall()
|
||||
|
||||
brand_list = [dict(row._mapping) for row in rows]
|
||||
if not brand_list:
|
||||
return {"status": "discovery_mode", "data": []}
|
||||
return {"status": "success", "data": brand_list}
|
||||
|
||||
@router.get("/search/providers")
|
||||
async def search_providers(q: str = Query(..., min_length=2), db: AsyncSession = Depends(get_db)):
|
||||
query = text("""
|
||||
SELECT id, name, technical_rating_pct, location_city, service_type
|
||||
FROM data.service_providers
|
||||
WHERE (name ILIKE :q OR service_type ILIKE :q) AND is_active = true
|
||||
ORDER BY technical_rating_pct DESC LIMIT 15
|
||||
""")
|
||||
result = await db.execute(query, {"q": f"%{q}%"})
|
||||
rows = result.fetchall()
|
||||
return {"status": "success", "data": [dict(row._mapping) for row in rows]}
|
||||
|
||||
@router.post("/register")
|
||||
async def register_user_vehicle(data: dict, db: AsyncSession = Depends(get_db), current_user = Depends(get_current_user)):
|
||||
company_res = await db.execute(text("SELECT id FROM data.companies WHERE owner_id = :u LIMIT 1"), {"u": current_user.id})
|
||||
company = company_res.fetchone()
|
||||
if not company:
|
||||
raise HTTPException(status_code=404, detail="Széf nem található.")
|
||||
|
||||
new_vehicle = Vehicle(
|
||||
current_company_id=company.id,
|
||||
brand_id=data.get("brand_id"),
|
||||
model_name=data.get("model_name"),
|
||||
engine_spec_id=data.get("engine_spec_id"),
|
||||
identification_number=data.get("vin"),
|
||||
license_plate=data.get("plate"),
|
||||
tracking_mode=data.get("tracking_mode", "km"),
|
||||
total_real_usage=data.get("current_odo", 0)
|
||||
)
|
||||
db.add(new_vehicle)
|
||||
await db.commit()
|
||||
return {"status": "success", "vehicle_id": str(new_vehicle.id)}
|
||||
Reference in New Issue
Block a user