201 előtti mentés
This commit is contained in:
@@ -472,4 +472,455 @@ async def get_leaderboard_top10(
|
||||
current_level=stats.current_level
|
||||
)
|
||||
)
|
||||
return leaderboard
|
||||
return leaderboard
|
||||
|
||||
|
||||
# --- QUIZ ENDPOINTS FOR DAILY QUIZ GAMIFICATION ---
|
||||
|
||||
@router.get("/quiz/daily")
|
||||
async def get_daily_quiz(
|
||||
db: AsyncSession = Depends(get_db),
|
||||
current_user: User = Depends(get_current_user)
|
||||
):
|
||||
"""
|
||||
Returns daily quiz questions for the user.
|
||||
Checks if user has already played today.
|
||||
"""
|
||||
# Check if user has already played today
|
||||
today = datetime.now().date()
|
||||
stmt = select(PointsLedger).where(
|
||||
PointsLedger.user_id == current_user.id,
|
||||
func.date(PointsLedger.created_at) == today,
|
||||
PointsLedger.reason.ilike("%quiz%")
|
||||
)
|
||||
result = await db.execute(stmt)
|
||||
already_played = result.scalar_one_or_none()
|
||||
|
||||
if already_played:
|
||||
raise HTTPException(
|
||||
status_code=400,
|
||||
detail="You have already played the daily quiz today. Try again tomorrow."
|
||||
)
|
||||
|
||||
# Return quiz questions (for now, using mock questions - in production these would come from a database)
|
||||
quiz_questions = [
|
||||
{
|
||||
"id": 1,
|
||||
"question": "Melyik alkatrész felelős a motor levegő‑üzemanyag keverékének szabályozásáért?",
|
||||
"options": ["Generátor", "Lambda‑szonda", "Féktárcsa", "Olajszűrő"],
|
||||
"correctAnswer": 1,
|
||||
"explanation": "A lambda‑szonda méri a kipufogógáz oxigéntartalmát, és ezen alapul a befecskendezés."
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"question": "Mennyi ideig érvényes egy gépjármű műszaki vizsgája Magyarországon?",
|
||||
"options": ["1 év", "2 év", "4 év", "6 év"],
|
||||
"correctAnswer": 1,
|
||||
"explanation": "A személygépkocsik műszaki vizsgája 2 évre érvényes, kivéve az újonnan forgalomba helyezett autókat."
|
||||
},
|
||||
{
|
||||
"id": 3,
|
||||
"question": "Melyik anyag NEM része a hibrid autók akkumulátorának?",
|
||||
"options": ["Lítium", "Nikkel", "Ólom", "Kobalt"],
|
||||
"correctAnswer": 2,
|
||||
"explanation": "A hibrid és elektromos autók akkumulátoraiban általában lítium, nikkel és kobalt található, ólom az ólom‑savas akkukban van."
|
||||
}
|
||||
]
|
||||
|
||||
return {
|
||||
"questions": quiz_questions,
|
||||
"total_questions": len(quiz_questions),
|
||||
"date": today.isoformat()
|
||||
}
|
||||
|
||||
|
||||
@router.post("/quiz/answer")
|
||||
async def submit_quiz_answer(
|
||||
question_id: int = Body(...),
|
||||
selected_option: int = Body(...),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
current_user: User = Depends(get_current_user)
|
||||
):
|
||||
"""
|
||||
Submit answer to a quiz question and award points if correct.
|
||||
"""
|
||||
# Check if user has already played today
|
||||
today = datetime.now().date()
|
||||
stmt = select(PointsLedger).where(
|
||||
PointsLedger.user_id == current_user.id,
|
||||
func.date(PointsLedger.created_at) == today,
|
||||
PointsLedger.reason.ilike("%quiz%")
|
||||
)
|
||||
result = await db.execute(stmt)
|
||||
already_played = result.scalar_one_or_none()
|
||||
|
||||
if already_played:
|
||||
raise HTTPException(
|
||||
status_code=400,
|
||||
detail="You have already played the daily quiz today. Try again tomorrow."
|
||||
)
|
||||
|
||||
# Mock quiz data - in production this would come from a database
|
||||
quiz_data = {
|
||||
1: {"correct_answer": 1, "points": 10, "explanation": "A lambda‑szonda méri a kipufogógáz oxigéntartalmát, és ezen alapul a befecskendezés."},
|
||||
2: {"correct_answer": 1, "points": 10, "explanation": "A személygépkocsik műszaki vizsgája 2 évre érvényes, kivéve az újonnan forgalomba helyezett autókat."},
|
||||
3: {"correct_answer": 2, "points": 10, "explanation": "A hibrid és elektromos autók akkumulátoraiban általában lítium, nikkel és kobalt található, ólom az ólom‑savas akkukban van."}
|
||||
}
|
||||
|
||||
if question_id not in quiz_data:
|
||||
raise HTTPException(status_code=404, detail="Question not found")
|
||||
|
||||
question_info = quiz_data[question_id]
|
||||
is_correct = selected_option == question_info["correct_answer"]
|
||||
|
||||
# Award points if correct
|
||||
if is_correct:
|
||||
# Update user stats
|
||||
stats_stmt = select(UserStats).where(UserStats.user_id == current_user.id)
|
||||
stats_result = await db.execute(stats_stmt)
|
||||
user_stats = stats_result.scalar_one_or_none()
|
||||
|
||||
if not user_stats:
|
||||
# Create user stats if they don't exist
|
||||
user_stats = UserStats(
|
||||
user_id=current_user.id,
|
||||
total_xp=question_info["points"],
|
||||
current_level=1
|
||||
)
|
||||
db.add(user_stats)
|
||||
else:
|
||||
user_stats.total_xp += question_info["points"]
|
||||
|
||||
# Add points ledger entry
|
||||
points_ledger = PointsLedger(
|
||||
user_id=current_user.id,
|
||||
points=question_info["points"],
|
||||
reason=f"Daily quiz correct answer - Question {question_id}",
|
||||
created_at=datetime.now()
|
||||
)
|
||||
db.add(points_ledger)
|
||||
|
||||
await db.commit()
|
||||
|
||||
return {
|
||||
"is_correct": is_correct,
|
||||
"correct_answer": question_info["correct_answer"],
|
||||
"points_awarded": question_info["points"] if is_correct else 0,
|
||||
"explanation": question_info["explanation"]
|
||||
}
|
||||
|
||||
|
||||
@router.post("/quiz/complete")
|
||||
async def complete_daily_quiz(
|
||||
db: AsyncSession = Depends(get_db),
|
||||
current_user: User = Depends(get_current_user)
|
||||
):
|
||||
"""
|
||||
Mark daily quiz as completed for today.
|
||||
This prevents the user from playing again today.
|
||||
"""
|
||||
today = datetime.now().date()
|
||||
|
||||
# Check if already completed today
|
||||
stmt = select(PointsLedger).where(
|
||||
PointsLedger.user_id == current_user.id,
|
||||
func.date(PointsLedger.created_at) == today,
|
||||
PointsLedger.reason == "Daily quiz completed"
|
||||
)
|
||||
result = await db.execute(stmt)
|
||||
already_completed = result.scalar_one_or_none()
|
||||
|
||||
if already_completed:
|
||||
raise HTTPException(
|
||||
status_code=400,
|
||||
detail="Daily quiz already marked as completed today."
|
||||
)
|
||||
|
||||
# Add completion entry
|
||||
completion_ledger = PointsLedger(
|
||||
user_id=current_user.id,
|
||||
points=0,
|
||||
reason="Daily quiz completed",
|
||||
created_at=datetime.now()
|
||||
)
|
||||
db.add(completion_ledger)
|
||||
await db.commit()
|
||||
|
||||
return {"message": "Daily quiz marked as completed for today."}
|
||||
|
||||
|
||||
@router.get("/quiz/stats")
|
||||
async def get_quiz_stats(
|
||||
db: AsyncSession = Depends(get_db),
|
||||
current_user: User = Depends(get_current_user)
|
||||
):
|
||||
"""
|
||||
Get user's quiz statistics including points, streak, and last played date.
|
||||
"""
|
||||
# Get user stats
|
||||
stats_stmt = select(UserStats).where(UserStats.user_id == current_user.id)
|
||||
stats_result = await db.execute(stats_stmt)
|
||||
user_stats = stats_result.scalar_one_or_none()
|
||||
|
||||
# Get quiz points from ledger
|
||||
points_stmt = select(func.sum(PointsLedger.points)).where(
|
||||
PointsLedger.user_id == current_user.id,
|
||||
PointsLedger.reason.ilike("%quiz%")
|
||||
)
|
||||
points_result = await db.execute(points_stmt)
|
||||
quiz_points = points_result.scalar() or 0
|
||||
|
||||
# Get last played date
|
||||
last_played_stmt = select(PointsLedger.created_at).where(
|
||||
PointsLedger.user_id == current_user.id,
|
||||
PointsLedger.reason.ilike("%quiz%")
|
||||
).order_by(desc(PointsLedger.created_at)).limit(1)
|
||||
last_played_result = await db.execute(last_played_stmt)
|
||||
last_played = last_played_result.scalar()
|
||||
|
||||
# Calculate streak (simplified - in production would be more sophisticated)
|
||||
streak = 0
|
||||
if last_played:
|
||||
# Simple streak calculation - check last 7 days
|
||||
streak = 1 # Placeholder
|
||||
|
||||
return {
|
||||
"total_quiz_points": quiz_points,
|
||||
"total_xp": user_stats.total_xp if user_stats else 0,
|
||||
"current_level": user_stats.current_level if user_stats else 1,
|
||||
"last_played": last_played.isoformat() if last_played else None,
|
||||
"current_streak": streak,
|
||||
"can_play_today": not await has_played_today(db, current_user.id)
|
||||
}
|
||||
|
||||
|
||||
async def has_played_today(db: AsyncSession, user_id: int) -> bool:
|
||||
"""Check if user has already played quiz today."""
|
||||
today = datetime.now().date()
|
||||
stmt = select(PointsLedger).where(
|
||||
PointsLedger.user_id == user_id,
|
||||
func.date(PointsLedger.created_at) == today,
|
||||
PointsLedger.reason.ilike("%quiz%")
|
||||
)
|
||||
result = await db.execute(stmt)
|
||||
return result.scalar_one_or_none() is not None
|
||||
|
||||
|
||||
# --- BADGE/TROPHY ENDPOINTS ---
|
||||
|
||||
@router.get("/badges")
|
||||
async def get_all_badges(
|
||||
db: AsyncSession = Depends(get_db)
|
||||
):
|
||||
"""
|
||||
Get all available badges in the system.
|
||||
"""
|
||||
stmt = select(Badge)
|
||||
result = await db.execute(stmt)
|
||||
badges = result.scalars().all()
|
||||
|
||||
return [
|
||||
{
|
||||
"id": badge.id,
|
||||
"name": badge.name,
|
||||
"description": badge.description,
|
||||
"icon_url": badge.icon_url
|
||||
}
|
||||
for badge in badges
|
||||
]
|
||||
|
||||
|
||||
@router.get("/my-badges")
|
||||
async def get_my_badges(
|
||||
db: AsyncSession = Depends(get_db),
|
||||
current_user: User = Depends(get_current_user)
|
||||
):
|
||||
"""
|
||||
Get badges earned by the current user.
|
||||
"""
|
||||
stmt = (
|
||||
select(UserBadge, Badge)
|
||||
.join(Badge, UserBadge.badge_id == Badge.id)
|
||||
.where(UserBadge.user_id == current_user.id)
|
||||
.order_by(desc(UserBadge.earned_at))
|
||||
)
|
||||
result = await db.execute(stmt)
|
||||
user_badges = result.all()
|
||||
|
||||
return [
|
||||
{
|
||||
"badge_id": badge.id,
|
||||
"badge_name": badge.name,
|
||||
"badge_description": badge.description,
|
||||
"badge_icon_url": badge.icon_url,
|
||||
"earned_at": user_badge.earned_at.isoformat() if user_badge.earned_at else None
|
||||
}
|
||||
for user_badge, badge in user_badges
|
||||
]
|
||||
|
||||
|
||||
@router.post("/badges/award/{badge_id}")
|
||||
async def award_badge_to_user(
|
||||
badge_id: int,
|
||||
user_id: int = Body(None),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
current_user: User = Depends(get_current_user)
|
||||
):
|
||||
"""
|
||||
Award a badge to a user (admin only or automated system).
|
||||
"""
|
||||
# Check if badge exists
|
||||
badge_stmt = select(Badge).where(Badge.id == badge_id)
|
||||
badge_result = await db.execute(badge_stmt)
|
||||
badge = badge_result.scalar_one_or_none()
|
||||
|
||||
if not badge:
|
||||
raise HTTPException(status_code=404, detail="Badge not found")
|
||||
|
||||
# Determine target user (default to current user if not specified)
|
||||
target_user_id = user_id if user_id else current_user.id
|
||||
|
||||
# Check if user already has this badge
|
||||
existing_stmt = select(UserBadge).where(
|
||||
UserBadge.user_id == target_user_id,
|
||||
UserBadge.badge_id == badge_id
|
||||
)
|
||||
existing_result = await db.execute(existing_stmt)
|
||||
existing = existing_result.scalar_one_or_none()
|
||||
|
||||
if existing:
|
||||
raise HTTPException(status_code=400, detail="User already has this badge")
|
||||
|
||||
# Award the badge
|
||||
user_badge = UserBadge(
|
||||
user_id=target_user_id,
|
||||
badge_id=badge_id,
|
||||
earned_at=datetime.now()
|
||||
)
|
||||
db.add(user_badge)
|
||||
|
||||
# Also add points for earning a badge
|
||||
points_ledger = PointsLedger(
|
||||
user_id=target_user_id,
|
||||
points=50, # Points for earning a badge
|
||||
reason=f"Badge earned: {badge.name}",
|
||||
created_at=datetime.now()
|
||||
)
|
||||
db.add(points_ledger)
|
||||
|
||||
# Update user stats
|
||||
stats_stmt = select(UserStats).where(UserStats.user_id == target_user_id)
|
||||
stats_result = await db.execute(stats_stmt)
|
||||
user_stats = stats_result.scalar_one_or_none()
|
||||
|
||||
if user_stats:
|
||||
user_stats.total_xp += 50
|
||||
else:
|
||||
user_stats = UserStats(
|
||||
user_id=target_user_id,
|
||||
total_xp=50,
|
||||
current_level=1
|
||||
)
|
||||
db.add(user_stats)
|
||||
|
||||
await db.commit()
|
||||
|
||||
return {
|
||||
"message": f"Badge '{badge.name}' awarded to user",
|
||||
"badge_id": badge.id,
|
||||
"badge_name": badge.name,
|
||||
"points_awarded": 50
|
||||
}
|
||||
|
||||
|
||||
@router.get("/achievements")
|
||||
async def get_achievements_progress(
|
||||
db: AsyncSession = Depends(get_db),
|
||||
current_user: User = Depends(get_current_user)
|
||||
):
|
||||
"""
|
||||
Get user's progress on various achievements (combines badges and other metrics).
|
||||
"""
|
||||
# Get user badges
|
||||
badges_stmt = select(UserBadge.badge_id).where(UserBadge.user_id == current_user.id)
|
||||
badges_result = await db.execute(badges_stmt)
|
||||
user_badge_ids = [row[0] for row in badges_result.all()]
|
||||
|
||||
# Get all badges
|
||||
all_badges_stmt = select(Badge)
|
||||
all_badges_result = await db.execute(all_badges_stmt)
|
||||
all_badges = all_badges_result.scalars().all()
|
||||
|
||||
# Get user stats
|
||||
stats_stmt = select(UserStats).where(UserStats.user_id == current_user.id)
|
||||
stats_result = await db.execute(stats_stmt)
|
||||
user_stats = stats_result.scalar_one_or_none()
|
||||
|
||||
# Define achievement categories
|
||||
achievements = []
|
||||
|
||||
# Badge-based achievements
|
||||
for badge in all_badges:
|
||||
achievements.append({
|
||||
"id": f"badge_{badge.id}",
|
||||
"title": badge.name,
|
||||
"description": badge.description,
|
||||
"icon_url": badge.icon_url,
|
||||
"is_earned": badge.id in user_badge_ids,
|
||||
"category": "badge",
|
||||
"progress": 100 if badge.id in user_badge_ids else 0
|
||||
})
|
||||
|
||||
# XP-based achievements
|
||||
xp_levels = [
|
||||
{"title": "Novice", "xp_required": 100, "description": "Earn 100 XP"},
|
||||
{"title": "Apprentice", "xp_required": 500, "description": "Earn 500 XP"},
|
||||
{"title": "Expert", "xp_required": 2000, "description": "Earn 2000 XP"},
|
||||
{"title": "Master", "xp_required": 5000, "description": "Earn 5000 XP"},
|
||||
]
|
||||
|
||||
current_xp = user_stats.total_xp if user_stats else 0
|
||||
for level in xp_levels:
|
||||
progress = min((current_xp / level["xp_required"]) * 100, 100)
|
||||
achievements.append({
|
||||
"id": f"xp_{level['xp_required']}",
|
||||
"title": level["title"],
|
||||
"description": level["description"],
|
||||
"icon_url": None,
|
||||
"is_earned": current_xp >= level["xp_required"],
|
||||
"category": "xp",
|
||||
"progress": progress
|
||||
})
|
||||
|
||||
# Quiz-based achievements
|
||||
quiz_points_stmt = select(func.sum(PointsLedger.points)).where(
|
||||
PointsLedger.user_id == current_user.id,
|
||||
PointsLedger.reason.ilike("%quiz%")
|
||||
)
|
||||
quiz_points_result = await db.execute(quiz_points_stmt)
|
||||
quiz_points = quiz_points_result.scalar() or 0
|
||||
|
||||
quiz_achievements = [
|
||||
{"title": "Quiz Beginner", "points_required": 50, "description": "Earn 50 quiz points"},
|
||||
{"title": "Quiz Enthusiast", "points_required": 200, "description": "Earn 200 quiz points"},
|
||||
{"title": "Quiz Master", "points_required": 500, "description": "Earn 500 quiz points"},
|
||||
]
|
||||
|
||||
for achievement in quiz_achievements:
|
||||
progress = min((quiz_points / achievement["points_required"]) * 100, 100)
|
||||
achievements.append({
|
||||
"id": f"quiz_{achievement['points_required']}",
|
||||
"title": achievement["title"],
|
||||
"description": achievement["description"],
|
||||
"icon_url": None,
|
||||
"is_earned": quiz_points >= achievement["points_required"],
|
||||
"category": "quiz",
|
||||
"progress": progress
|
||||
})
|
||||
|
||||
return {
|
||||
"achievements": achievements,
|
||||
"total_achievements": len(achievements),
|
||||
"earned_count": sum(1 for a in achievements if a["is_earned"]),
|
||||
"progress_percentage": round((sum(1 for a in achievements if a["is_earned"]) / len(achievements)) * 100, 1) if achievements else 0
|
||||
}
|
||||
Reference in New Issue
Block a user