208 lines
7.6 KiB
Python
Executable File
208 lines
7.6 KiB
Python
Executable File
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.services.config_service import ConfigService
|
||
from app.services.security_auditor import SecurityAuditorService
|
||
from app.models.marketplace.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()
|
||
|
||
# --- 🎯 SZERVIZ VADÁSZAT (Service Hunt) ---
|
||
@router.post("/hunt")
|
||
async def register_service_hunt(
|
||
name: str = Form(...),
|
||
lat: float = Form(...),
|
||
lng: float = Form(...),
|
||
current_user: User = Depends(get_current_user),
|
||
db: AsyncSession = Depends(get_db)
|
||
):
|
||
""" Ú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 marketplace.service_staging (name, fingerprint, status, city, submitted_by, raw_data)
|
||
VALUES (:n, :f, 'pending', 'Unknown', :user_id, jsonb_build_object('lat', CAST(:lat AS double precision), 'lng', CAST(:lng AS double precision)))
|
||
"""), {"n": name, "f": f"{name}-{lat}-{lng}", "lat": lat, "lng": lng, "user_id": current_user.id})
|
||
|
||
# MB 2.0 Gamification: Dinamikus pontszám a felfedezésért
|
||
reward_points = await ConfigService.get_int(db, "GAMIFICATION_HUNT_REWARD", 50)
|
||
await GamificationService.award_points(db, current_user.id, reward_points, f"Service Hunt: {name}")
|
||
await db.commit()
|
||
return {"status": "success", "message": "Discovery registered and points awarded."}
|
||
|
||
# --- ✅ SZERVIZ VALIDÁLÁS (Service Validation) ---
|
||
@router.post("/hunt/{staging_id}/validate")
|
||
async def validate_staged_service(
|
||
staging_id: int,
|
||
current_user: User = Depends(get_current_user),
|
||
db: AsyncSession = Depends(get_db)
|
||
):
|
||
"""
|
||
Validálja egy másik felhasználó által beküldött szerviz-jelöltet.
|
||
Növeli a validation_level-t 10-zel (max 80), adományoz 10 XP-t,
|
||
és növeli a places_validated számlálót a felhasználó statisztikáiban.
|
||
"""
|
||
# Anti-Cheat: Rapid Fire ellenőrzés
|
||
await SecurityAuditorService.check_rapid_fire_validation(db, current_user.id)
|
||
|
||
# 1. Keresd meg a staging rekordot
|
||
result = await db.execute(
|
||
text("SELECT id, submitted_by, validation_level FROM marketplace.service_staging WHERE id = :id"),
|
||
{"id": staging_id}
|
||
)
|
||
staging = result.fetchone()
|
||
if not staging:
|
||
raise HTTPException(status_code=404, detail="Staging record not found")
|
||
|
||
# 2. Ha a saját beküldését validálná, hiba
|
||
if staging.submitted_by == current_user.id:
|
||
raise HTTPException(status_code=400, detail="Cannot validate your own submission")
|
||
|
||
# 3. Növeld a validation_level-t 10-zel (max 80)
|
||
new_level = staging.validation_level + 10
|
||
if new_level > 80:
|
||
new_level = 80
|
||
|
||
# 4. UPDATE a validation_level és a status (ha elérte a 80-at, akkor "verified"?)
|
||
# Jelenleg csak a validation_level frissítése
|
||
await db.execute(
|
||
text("""
|
||
UPDATE marketplace.service_staging
|
||
SET validation_level = :new_level
|
||
WHERE id = :id
|
||
"""),
|
||
{"new_level": new_level, "id": staging_id}
|
||
)
|
||
|
||
# 5. Adományozz dinamikus XP-t a current_user-nek a GamificationService-en keresztül
|
||
validation_reward = await ConfigService.get_int(db, "GAMIFICATION_VALIDATE_REWARD", 10)
|
||
await GamificationService.award_points(db, current_user.id, validation_reward, f"Service Validation: staging #{staging_id}")
|
||
|
||
# 6. Növeld a current_user places_validated értékét a UserStats-ban
|
||
await db.execute(
|
||
text("""
|
||
UPDATE gamification.user_stats
|
||
SET places_validated = places_validated + 1
|
||
WHERE user_id = :user_id
|
||
"""),
|
||
{"user_id": current_user.id}
|
||
)
|
||
|
||
await db.commit()
|
||
|
||
return {
|
||
"status": "success",
|
||
"message": "Validation successful",
|
||
"validation_level": new_level,
|
||
"places_validated_incremented": True
|
||
}
|
||
|
||
# --- 🔍 SZERVIZ KERESŐ (Service Search) ---
|
||
@router.get("/search")
|
||
async def search_services(
|
||
expertise_key: Optional[str] = Query(None, description="Szakmai címke (pl. brake_service)"),
|
||
city: Optional[str] = Query(None, description="Város szűrés"),
|
||
min_confidence: int = Query(0, description="Minimum hitelességi szint (0-2)"),
|
||
db: AsyncSession = Depends(get_db)
|
||
):
|
||
""" Szakmai szempontú keresőmotor, ami a validált címkék alapján szűr. """
|
||
# Alap lekérdezés: Szervizek, akiknek van szakértelmük
|
||
query = select(ServiceProfile).join(ServiceProfile.expertises).join(ServiceExpertise.tag)
|
||
|
||
filters = []
|
||
if expertise_key:
|
||
filters.append(ExpertiseTag.key == expertise_key)
|
||
if city:
|
||
filters.append(ServiceProfile.city.ilike(f"%{city}%"))
|
||
if min_confidence > 0:
|
||
filters.append(ServiceExpertise.confidence_level >= min_confidence)
|
||
|
||
if filters:
|
||
query = query.where(and_(*filters))
|
||
|
||
result = await db.execute(query.distinct())
|
||
services = result.scalars().all()
|
||
|
||
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ékelheti‑e 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
|
||
} |