refaktorálás javításai

This commit is contained in:
Roo
2026-03-13 10:22:41 +00:00
parent 2d8d23f469
commit f53e0b53df
140 changed files with 7316 additions and 4579 deletions

View File

@@ -7,20 +7,23 @@ from datetime import datetime, timedelta
from app.api import deps
from app.models.identity import User, UserRole # JAVÍTVA: Központi import
from app.models.system import SystemParameter
from app.models.system import SystemParameter, ParameterScope
from app.services.system_service import system_service
# JAVÍTVA: Security audit modellek
from app.models.audit import SecurityAuditLog, OperationalLog
# JAVÍTVA: Ezek a modellek a security.py-ból jönnek (ha ott vannak)
from app.models.security import PendingAction, ActionStatus
from app.services.security_service import security_service
from app.services.translation_service import TranslationService
from pydantic import BaseModel
from app.services.translation_service import TranslationService
from app.services.odometer_service import OdometerService
from pydantic import BaseModel, Field
from typing import Optional as Opt
class ConfigUpdate(BaseModel):
key: str
value: Any
scope_level: str = "global"
scope_level: ParameterScope = ParameterScope.GLOBAL
scope_id: Optional[str] = None
category: str = "general"
@@ -43,13 +46,13 @@ async def get_system_health(
stats = {}
# Adatbázis statisztikák (Nyers SQL marad, mert hatékony)
user_stats = await db.execute(text("SELECT subscription_plan, count(*) FROM data.users GROUP BY subscription_plan"))
user_stats = await db.execute(text("SELECT subscription_plan, count(*) FROM identity.users GROUP BY subscription_plan"))
stats["user_distribution"] = {row[0]: row[1] for row in user_stats}
asset_count = await db.execute(text("SELECT count(*) FROM data.assets"))
asset_count = await db.execute(text("SELECT count(*) FROM vehicle.assets"))
stats["total_assets"] = asset_count.scalar()
org_count = await db.execute(text("SELECT count(*) FROM data.organizations"))
org_count = await db.execute(text("SELECT count(*) FROM fleet.organizations"))
stats["total_organizations"] = org_count.scalar()
# JAVÍTVA: Biztonsági státusz az új SecurityAuditLog alapján
@@ -101,7 +104,7 @@ async def set_parameter(
admin: User = Depends(check_admin_access)
):
query = text("""
INSERT INTO data.system_parameters (key, value, scope_level, scope_id, category, last_modified_by)
INSERT INTO system.system_parameters (key, value, scope_level, scope_id, category, last_modified_by)
VALUES (:key, :val, :sl, :sid, :cat, :user)
ON CONFLICT (key, scope_level, scope_id)
DO UPDATE SET
@@ -122,10 +125,114 @@ async def set_parameter(
await db.commit()
return {"status": "success", "message": f"'{config.key}' frissítve."}
@router.get("/parameters/scoped", tags=["Dynamic Configuration"])
async def get_scoped_parameter(
key: str,
user_id: Optional[str] = None,
region_id: Optional[str] = None,
country_code: Optional[str] = None,
db: AsyncSession = Depends(deps.get_db),
admin: User = Depends(check_admin_access)
):
"""
Hierarchikus paraméterlekérdezés a következő prioritással:
User > Region > Country > Global.
"""
value = await system_service.get_scoped_parameter(
db, key, user_id, region_id, country_code, default=None
)
if value is None:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail=f"Paraméter '{key}' nem található a megadott scope-okban."
)
return {"key": key, "value": value}
@router.post("/translations/sync", tags=["System Utilities"])
async def sync_translations_to_json(
db: AsyncSession = Depends(deps.get_db),
admin: User = Depends(check_admin_access)
):
await TranslationService.export_to_json(db)
return {"message": "JSON fájlok frissítve."}
return {"message": "JSON fájlok frissítve."}
# ==================== SMART ODOMETER ADMIN API ====================
class OdometerStatsResponse(BaseModel):
vehicle_id: int
last_recorded_odometer: int
last_recorded_date: datetime
daily_avg_distance: float
estimated_current_odometer: float
confidence_score: float
manual_override_avg: Opt[float]
is_confidence_high: bool = Field(..., description="True ha confidence_score >= threshold")
class ManualOverrideRequest(BaseModel):
daily_avg: Opt[float] = Field(None, description="Napi átlagos kilométer (km/nap). Ha null, törli a manuális beállítást.")
@router.get("/odometer/{vehicle_id}", tags=["Smart Odometer"])
async def get_odometer_stats(
vehicle_id: int,
db: AsyncSession = Depends(deps.get_db),
admin: User = Depends(check_admin_access)
):
"""
Jármű kilométeróra statisztikáinak lekérése.
A rendszer automatikusan frissíti a statisztikákat, ha szükséges.
"""
# Frissítjük a statisztikákat
odometer_state = await OdometerService.update_vehicle_stats(db, vehicle_id)
if not odometer_state:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail=f"Jármű nem található ID: {vehicle_id}"
)
# Confidence threshold lekérése
confidence_threshold = await OdometerService.get_system_param(
db, 'ODOMETER_CONFIDENCE_THRESHOLD', 0.5
)
return OdometerStatsResponse(
vehicle_id=odometer_state.vehicle_id,
last_recorded_odometer=odometer_state.last_recorded_odometer,
last_recorded_date=odometer_state.last_recorded_date,
daily_avg_distance=float(odometer_state.daily_avg_distance),
estimated_current_odometer=float(odometer_state.estimated_current_odometer),
confidence_score=odometer_state.confidence_score,
manual_override_avg=float(odometer_state.manual_override_avg) if odometer_state.manual_override_avg else None,
is_confidence_high=odometer_state.confidence_score >= confidence_threshold
)
@router.patch("/odometer/{vehicle_id}", tags=["Smart Odometer"])
async def set_odometer_manual_override(
vehicle_id: int,
request: ManualOverrideRequest,
db: AsyncSession = Depends(deps.get_db),
admin: User = Depends(check_admin_access)
):
"""
Adminisztrátori manuális átlag beállítása a kilométeróra becsléshez.
Ha a user csal vagy hibás az adat, az admin ezzel felülírhatja az automatikus számítást.
"""
odometer_state = await OdometerService.set_manual_override(
db, vehicle_id, request.daily_avg
)
if not odometer_state:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail=f"Jármű nem található ID: {vehicle_id}"
)
action = "beállítva" if request.daily_avg is not None else "törölve"
return {
"status": "success",
"message": f"Manuális átlag {action}: {request.daily_avg} km/nap",
"vehicle_id": vehicle_id,
"manual_override_avg": odometer_state.manual_override_avg
}

View File

@@ -0,0 +1,196 @@
"""
Analytics API endpoints for TCO (Total Cost of Ownership) dashboard.
"""
import logging
import uuid
from typing import List, Optional
from fastapi import APIRouter, Depends, HTTPException, status
from sqlalchemy.ext.asyncio import AsyncSession
from app.api import deps
from app.schemas.analytics import TCOSummaryResponse, TCOErrorResponse
from app.services.analytics_service import TCOAnalytics
from app.models import Vehicle
from app.models.organization import OrganizationMember
logger = logging.getLogger(__name__)
router = APIRouter()
async def verify_vehicle_access(
vehicle_id: uuid.UUID,
db: AsyncSession,
current_user
) -> Vehicle:
"""
Verify that the current user has access to the vehicle (either as owner or via organization).
Raises HTTP 404 if vehicle not found, 403 if access denied.
"""
# 1. Check if vehicle exists
vehicle = await db.get(Vehicle, vehicle_id)
if not vehicle:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail=f"Vehicle with ID {vehicle_id} not found."
)
# 2. Check if user is superadmin (global access)
if current_user.role == "superadmin":
return vehicle
# 3. Check if user is member of the vehicle's organization
# (Vehicle.organization_id matches user's organization membership)
# First, get user's organization memberships
from sqlalchemy import select
stmt = select(OrganizationMember).where(
OrganizationMember.user_id == current_user.id,
OrganizationMember.organization_id == vehicle.organization_id
)
result = await db.execute(stmt)
membership = result.scalar_one_or_none()
if membership:
return vehicle
# 4. If user is not a member, check if they have fleet manager role with cross-org access
# (This could be extended based on RBAC)
# For now, deny access
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail="You do not have permission to access this vehicle's analytics."
)
@router.get(
"/{vehicle_id}/summary",
response_model=TCOSummaryResponse,
responses={
404: {"model": TCOErrorResponse, "description": "Vehicle not found"},
403: {"model": TCOErrorResponse, "description": "Access denied"},
500: {"model": TCOErrorResponse, "description": "Internal server error"},
},
summary="Get TCO summary for a vehicle",
description="Returns Total Cost of Ownership analytics for a specific vehicle, "
"including user-specific costs, lifetime costs, and benchmark comparisons."
)
async def get_tco_summary(
vehicle_id: uuid.UUID,
db: AsyncSession = Depends(deps.get_db),
current_user = Depends(deps.get_current_active_user),
):
"""
Retrieve TCO analytics for a vehicle.
Steps:
1. Verify user has access to the vehicle.
2. Use TCOAnalytics service to compute user TCO, lifetime TCO, and benchmark.
3. Transform results into the response schema.
"""
try:
# Access verification
vehicle = await verify_vehicle_access(vehicle_id, db, current_user)
analytics = TCOAnalytics()
# 1. User TCO (current user's organization)
user_tco_result = await analytics.get_user_tco(
db=db,
organization_id=current_user.organization_id or vehicle.organization_id,
currency_target="HUF",
include_categories=None, # all categories
)
# 2. Lifetime TCO (across all owners, anonymized)
lifetime_tco_result = await analytics.get_vehicle_lifetime_tco(
db=db,
vehicle_model_id=vehicle.vehicle_model_id,
currency_target="HUF",
anonymize=True,
)
# 3. Benchmark TCO (global benchmark for similar vehicles)
benchmark_result = await analytics.get_global_benchmark(
db=db,
vehicle_model_id=vehicle.vehicle_model_id,
currency_target="HUF",
)
# Transform results into schema objects
# Note: This is a simplified transformation; you may need to adapt based on actual service output.
user_tco_list = []
if "by_category" in user_tco_result:
for cat_code, cat_data in user_tco_result["by_category"].items():
# Calculate percentage
total = user_tco_result.get("total_amount", 0)
percentage = (cat_data["total"] / total * 100) if total > 0 else 0
user_tco_list.append({
"category_id": 0, # TODO: map from category code to ID
"category_code": cat_code,
"category_name": cat_data.get("name", cat_code),
"amount": cat_data["total"],
"currency": user_tco_result.get("currency", "HUF"),
"amount_huf": cat_data["total"], # already in HUF
"percentage": round(percentage, 2),
})
lifetime_tco_list = []
if "by_category" in lifetime_tco_result:
for cat_code, cat_data in lifetime_tco_result["by_category"].items():
total = lifetime_tco_result.get("total_lifetime_cost", 0)
percentage = (cat_data["total"] / total * 100) if total > 0 else 0
lifetime_tco_list.append({
"category_id": 0,
"category_code": cat_code,
"category_name": cat_data.get("name", cat_code),
"amount": cat_data["total"],
"currency": lifetime_tco_result.get("currency", "HUF"),
"amount_huf": cat_data["total"],
"percentage": round(percentage, 2),
})
benchmark_tco_list = []
if "by_category" in benchmark_result:
for cat_code, cat_data in benchmark_result["by_category"].items():
total = benchmark_result.get("total_cost_sum", 0)
percentage = (cat_data["average"] / total * 100) if total > 0 else 0
benchmark_tco_list.append({
"category_id": 0,
"category_code": cat_code,
"category_name": cat_data.get("name", cat_code),
"amount": cat_data["average"],
"currency": benchmark_result.get("currency", "HUF"),
"amount_huf": cat_data["average"],
"percentage": round(percentage, 2),
})
# Calculate cost per km if odometer data available
cost_per_km = None
if vehicle.odometer and vehicle.odometer > 0:
total_cost = user_tco_result.get("total_amount", 0)
cost_per_km = total_cost / vehicle.odometer
stats = {
"total_cost": user_tco_result.get("total_amount", 0),
"cost_per_km": cost_per_km,
"total_transactions": user_tco_result.get("total_transactions", 0),
"date_range": user_tco_result.get("date_range"),
}
return TCOSummaryResponse(
vehicle_id=vehicle_id,
user_tco=user_tco_list,
lifetime_tco=lifetime_tco_list,
benchmark_tco=benchmark_tco_list,
stats=stats,
)
except HTTPException:
raise
except Exception as e:
logger.exception(f"Unexpected error in TCO summary for vehicle {vehicle_id}: {e}")
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail=f"Internal server error: {str(e)}"
)

View File

@@ -10,7 +10,7 @@ router = APIRouter()
@router.post("/scan-registration")
async def scan_registration_document(file: UploadFile = File(...), db: AsyncSession = Depends(get_db), current_user: User = Depends(get_current_user)):
stmt_limit = text("SELECT (value->>:plan)::int FROM data.system_parameters WHERE key = 'VEHICLE_LIMIT'")
stmt_limit = text("SELECT (value->>:plan)::int FROM system.system_parameters WHERE key = 'VEHICLE_LIMIT'")
res = await db.execute(stmt_limit, {"plan": current_user.subscription_plan or "free"})
max_allowed = res.scalar() or 1

View File

@@ -0,0 +1,77 @@
# /opt/docker/dev/service_finder/backend/app/api/v1/endpoints/finance_admin.py
"""
Finance Admin API endpoints for managing Issuers with strict RBAC protection.
Only users with rank >= 90 (Superadmin/Finance Admin) can access these endpoints.
"""
from fastapi import APIRouter, Depends, HTTPException, status
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy import select
from typing import List
from app.api import deps
from app.models.identity import User, UserRole
from app.models.finance import Issuer
from app.schemas.finance import IssuerResponse, IssuerUpdate
router = APIRouter()
async def check_finance_admin_access(
current_user: User = Depends(deps.get_current_active_user)
):
"""
RBAC protection: only users with rank >= 90 (Superadmin/Finance Admin) can access.
In our system, this translates to role being 'superadmin' or 'admin'.
"""
if current_user.role not in [UserRole.superadmin, UserRole.admin]:
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail="Not enough permissions. Rank >= 90 (Superadmin/Finance Admin) required."
)
return current_user
@router.get("/", response_model=List[IssuerResponse], tags=["finance-admin"])
async def list_issuers(
db: AsyncSession = Depends(deps.get_db),
admin: User = Depends(check_finance_admin_access)
):
"""
List all Issuers (billing entities).
Only accessible by Superadmin/Finance Admin (rank >= 90).
"""
result = await db.execute(select(Issuer).order_by(Issuer.id))
issuers = result.scalars().all()
return issuers
@router.patch("/{issuer_id}", response_model=IssuerResponse, tags=["finance-admin"])
async def update_issuer(
issuer_id: int,
issuer_update: IssuerUpdate,
db: AsyncSession = Depends(deps.get_db),
admin: User = Depends(check_finance_admin_access)
):
"""
Update an Issuer's details (activate/deactivate, revenue limit, API config).
Only accessible by Superadmin/Finance Admin (rank >= 90).
"""
result = await db.execute(select(Issuer).where(Issuer.id == issuer_id))
issuer = result.scalar_one_or_none()
if not issuer:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail=f"Issuer with ID {issuer_id} not found."
)
# Update fields if provided
update_data = issuer_update.model_dump(exclude_unset=True)
for field, value in update_data.items():
setattr(issuer, field, value)
await db.commit()
await db.refresh(issuer)
return issuer

View File

@@ -15,7 +15,7 @@ async def get_vehicle_summary(vehicle_id: str, db: AsyncSession = Depends(get_db
category,
SUM(amount) as total_amount,
COUNT(*) as transaction_count
FROM data.vehicle_expenses
FROM vehicle.vehicle_expenses
WHERE vehicle_id = :v_id
GROUP BY category
""")
@@ -40,7 +40,7 @@ async def get_monthly_trends(vehicle_id: str, db: AsyncSession = Depends(get_db)
SELECT
TO_CHAR(date, 'YYYY-MM') as month,
SUM(amount) as monthly_total
FROM data.vehicle_expenses
FROM vehicle.vehicle_expenses
WHERE vehicle_id = :v_id
GROUP BY month
ORDER BY month DESC

View File

@@ -10,12 +10,12 @@ 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)):
# PostGIS alapú keresés a data.branches táblában (a régi locations helyett)
# PostGIS alapú keresés a fleet.branches táblában (a régi locations helyett)
query = text("""
SELECT o.id, o.name, b.city,
ST_Distance(b.location, ST_SetSRID(ST_MakePoint(:lng, :lat), 4326)::geography) / 1000 as distance
FROM data.organizations o
JOIN data.branches b ON o.id = b.organization_id
FROM fleet.organizations o
JOIN fleet.branches b ON o.id = b.organization_id
WHERE o.is_active = True AND b.is_active = True
AND ST_DWithin(b.location, ST_SetSRID(ST_MakePoint(:lng, :lat), 4326)::geography, :r * 1000)
ORDER BY distance ASC

View File

@@ -1,10 +1,18 @@
from fastapi import APIRouter, Depends, Form, Query, HTTPException
from fastapi import APIRouter, Depends, Form, Query, HTTPException, status
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy import select, and_, text
from typing import List, Optional
from app.db.session import get_db
from app.services.gamification_service import GamificationService
from app.models.service import ServiceProfile, ExpertiseTag, ServiceExpertise
from app.services.marketplace_service import (
create_verified_review,
get_service_reviews,
can_user_review_service
)
from app.schemas.social import ServiceReviewCreate, ServiceReviewResponse
from app.api.deps import get_current_user
from app.models.identity import User
router = APIRouter()
@@ -19,7 +27,7 @@ async def register_service_hunt(
""" Új szerviz-jelölt rögzítése a staging táblába jutalompontért. """
# Új szerviz-jelölt rögzítése
await db.execute(text("""
INSERT INTO data.service_staging (name, fingerprint, status, raw_data)
INSERT INTO marketplace.service_staging (name, fingerprint, status, raw_data)
VALUES (:n, :f, 'pending', jsonb_build_object('lat', :lat, 'lng', :lng))
"""), {"n": name, "f": f"{name}-{lat}-{lng}", "lat": lat, "lng": lng})
@@ -55,4 +63,76 @@ async def search_services(
result = await db.execute(query.distinct())
services = result.scalars().all()
return services
return services
# --- ⭐ VERIFIED SERVICE REVIEWS (Social 3 - #66) ---
@router.post("/{service_id}/reviews", response_model=ServiceReviewResponse, status_code=status.HTTP_201_CREATED)
async def create_service_review(
service_id: int,
review_data: ServiceReviewCreate,
current_user: User = Depends(get_current_user),
db: AsyncSession = Depends(get_db)
):
"""
Verifikált szerviz értékelés beküldése.
Csak igazolt pénzügyi tranzakció után lehetséges (transaction_id kötelező).
"""
try:
review = await create_verified_review(
db=db,
service_id=service_id,
user_id=current_user.id,
transaction_id=review_data.transaction_id,
review_data=review_data
)
return review
except ValueError as e:
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=str(e))
except IntegrityError as e:
raise HTTPException(status_code=status.HTTP_409_CONFLICT, detail=str(e))
@router.get("/{service_id}/reviews", response_model=dict)
async def list_service_reviews(
service_id: int,
skip: int = Query(0, ge=0),
limit: int = Query(20, ge=1, le=100),
verified_only: bool = Query(True),
db: AsyncSession = Depends(get_db)
):
"""
Szerviz értékeléseinek lapozható listázása.
"""
reviews, total = await get_service_reviews(
db=db,
service_id=service_id,
skip=skip,
limit=limit,
verified_only=verified_only
)
return {
"reviews": reviews,
"total": total,
"skip": skip,
"limit": limit
}
@router.get("/{service_id}/reviews/check")
async def check_review_eligibility(
service_id: int,
current_user: User = Depends(get_current_user),
db: AsyncSession = Depends(get_db)
):
"""
Ellenőrzi, hogy a felhasználó értékelhetie a szervizt.
"""
can_review, reason = await can_user_review_service(db, current_user.id, service_id)
return {
"can_review": can_review,
"reason": reason,
"user_id": current_user.id,
"service_id": service_id
}

View File

@@ -1,11 +1,14 @@
from fastapi import APIRouter, Depends
from sqlalchemy.ext.asyncio import AsyncSession
from typing import Dict, Any
from app.api.deps import get_db, get_current_user
from app.schemas.user import UserResponse
from app.models.user import User
from app.models.identity import User
from app.services.trust_engine import TrustEngine
router = APIRouter()
trust_engine = TrustEngine()
@router.get("/me", response_model=UserResponse)
async def read_users_me(
@@ -14,3 +17,26 @@ async def read_users_me(
):
"""Visszaadja a bejelentkezett felhasználó profilját"""
return current_user
@router.get("/me/trust")
async def get_user_trust(
db: AsyncSession = Depends(get_db),
current_user: User = Depends(get_current_user),
force_recalculate: bool = False,
) -> Dict[str, Any]:
"""
Visszaadja a felhasználó Gondos Gazda Index (Trust Score) értékét.
A számítás dinamikusan betölti a paramétereket a SystemParameter rendszerből
(Global/Country/Region/User hierarchia).
Paraméterek:
- force_recalculate: Ha True, akkor újraszámolja a trust score-t
(alapértelmezetten cache-elt értéket ad vissza, ha kevesebb mint 24 órája számoltuk)
"""
trust_data = await trust_engine.calculate_user_trust(
db=db,
user_id=current_user.id,
force_recalculate=force_recalculate
)
return trust_data

View File

@@ -0,0 +1,142 @@
"""
Jármű értékelési végpontok a Social 1 modulhoz.
"""
import uuid
from typing import List
from fastapi import APIRouter, Depends, HTTPException, status
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy import select, and_
from sqlalchemy.orm import selectinload
from app.db.session import get_db
from app.api.deps import get_current_user
from app.models.vehicle import VehicleUserRating
from app.models.vehicle_definitions import VehicleModelDefinition
from app.models.identity import User
from app.schemas.vehicle import VehicleRatingCreate, VehicleRatingResponse
router = APIRouter()
@router.post("/{vehicle_id}/ratings", response_model=VehicleRatingResponse, status_code=status.HTTP_201_CREATED)
async def create_vehicle_rating(
vehicle_id: int,
rating: VehicleRatingCreate,
db: AsyncSession = Depends(get_db),
current_user: User = Depends(get_current_user)
):
"""
Értékelés beküldése egy járműhöz.
Csak a jármű tulajdonosa (vagy jogosult felhasználó) értékelhet.
Egy felhasználó csak egyszer értékelhet egy adott járművet.
"""
# 1. Ellenőrizzük, hogy a jármű létezik-e
vehicle = await db.scalar(
select(VehicleModelDefinition).where(VehicleModelDefinition.id == vehicle_id)
)
if not vehicle:
raise HTTPException(status_code=404, detail="Jármű nem található")
# 2. Ellenőrizzük, hogy a felhasználó jogosult-e értékelni (jelenleg csak tulajdonos)
# TODO: Később kibővíthető más jogosultságokkal is
# Most feltételezzük, hogy mindenki értékelhet, de csak egyszer
# 3. Ellenőrizzük, hogy már létezik-e értékelés ettől a felhasználótól ehhez a járműhöz
existing_rating = await db.scalar(
select(VehicleUserRating).where(
and_(
VehicleUserRating.vehicle_id == vehicle_id,
VehicleUserRating.user_id == current_user.id
)
)
)
if existing_rating:
raise HTTPException(
status_code=status.HTTP_409_CONFLICT,
detail="Már értékelted ezt a járművet. Csak egy értékelés engedélyezett felhasználónként."
)
# 4. Hozzuk létre az új értékelést
new_rating = VehicleUserRating(
vehicle_id=vehicle_id,
user_id=current_user.id,
driving_experience=rating.driving_experience,
reliability=rating.reliability,
comfort=rating.comfort,
consumption_satisfaction=rating.consumption_satisfaction,
comment=rating.comment
)
db.add(new_rating)
await db.commit()
await db.refresh(new_rating)
# 5. Átlagpontszám számítása
average_score = new_rating.average_score
# 6. Válasz összeállítása
return VehicleRatingResponse(
id=new_rating.id,
vehicle_id=new_rating.vehicle_id,
user_id=new_rating.user_id,
driving_experience=new_rating.driving_experience,
reliability=new_rating.reliability,
comfort=new_rating.comfort,
consumption_satisfaction=new_rating.consumption_satisfaction,
comment=new_rating.comment,
average_score=average_score,
created_at=new_rating.created_at,
updated_at=new_rating.updated_at
)
@router.get("/{vehicle_id}/ratings", response_model=List[VehicleRatingResponse])
async def get_vehicle_ratings(
vehicle_id: int,
skip: int = 0,
limit: int = 100,
db: AsyncSession = Depends(get_db),
current_user: User = Depends(get_current_user)
):
"""
Az összes értékelés lekérése egy adott járműhöz.
"""
# Ellenőrizzük, hogy a jármű létezik-e
vehicle = await db.scalar(
select(VehicleModelDefinition).where(VehicleModelDefinition.id == vehicle_id)
)
if not vehicle:
raise HTTPException(status_code=404, detail="Jármű nem található")
# Lekérjük az értékeléseket
stmt = (
select(VehicleUserRating)
.where(VehicleUserRating.vehicle_id == vehicle_id)
.order_by(VehicleUserRating.created_at.desc())
.offset(skip)
.limit(limit)
)
result = await db.scalars(stmt)
ratings = result.all()
# Átalakítás válasz sémává
response_ratings = []
for rating in ratings:
response_ratings.append(
VehicleRatingResponse(
id=rating.id,
vehicle_id=rating.vehicle_id,
user_id=rating.user_id,
driving_experience=rating.driving_experience,
reliability=rating.reliability,
comfort=rating.comfort,
consumption_satisfaction=rating.consumption_satisfaction,
comment=rating.comment,
average_score=rating.average_score,
created_at=rating.created_at,
updated_at=rating.updated_at
)
)
return response_ratings