Files
service-finder/backend/app/api/v1/endpoints/services.py
2026-03-23 21:43:40 +00:00

209 lines
7.6 KiB
Python
Executable File
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# /opt/docker/dev/service_finder/backend/app/api/v1/endpoints/services.py
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é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
}