
- Set up project structure with FastAPI and SQLite - Implement user authentication with JWT - Create models for learning content (subjects, lessons, quizzes) - Add progress tracking and gamification features - Implement comprehensive API documentation - Add error handling and validation - Set up proper logging and health check endpoint
177 lines
5.9 KiB
Python
177 lines
5.9 KiB
Python
from __future__ import annotations
|
|
|
|
import json
|
|
from datetime import datetime
|
|
from typing import List, Optional
|
|
|
|
from sqlalchemy import func
|
|
from sqlalchemy.orm import Session
|
|
|
|
from app.models.achievement import Achievement, AchievementType, UserAchievement
|
|
from app.models.progress import ProgressStatus, UserProgress
|
|
from app.schemas.achievement import AchievementCreate, AchievementUpdate, UserAchievementCreate
|
|
from app.services.user import user as user_service
|
|
from app.utils.db import CRUDBase
|
|
|
|
|
|
class CRUDAchievement(CRUDBase[Achievement, AchievementCreate, AchievementUpdate]):
|
|
def get_by_type(
|
|
self, db: Session, *, achievement_type: AchievementType, skip: int = 0, limit: int = 100
|
|
) -> List[Achievement]:
|
|
"""
|
|
Get achievements by type.
|
|
"""
|
|
return (
|
|
db.query(Achievement)
|
|
.filter(Achievement.type == achievement_type)
|
|
.offset(skip)
|
|
.limit(limit)
|
|
.all()
|
|
)
|
|
|
|
def get_active(self, db: Session, *, skip: int = 0, limit: int = 100) -> List[Achievement]:
|
|
"""
|
|
Get active achievements.
|
|
"""
|
|
return (
|
|
db.query(Achievement)
|
|
.filter(Achievement.is_active == True)
|
|
.offset(skip)
|
|
.limit(limit)
|
|
.all()
|
|
)
|
|
|
|
|
|
class CRUDUserAchievement(CRUDBase[UserAchievement, UserAchievementCreate, UserAchievementCreate]):
|
|
def get_by_user(
|
|
self, db: Session, *, user_id: int, skip: int = 0, limit: int = 100
|
|
) -> List[UserAchievement]:
|
|
"""
|
|
Get a user's achievements.
|
|
"""
|
|
return (
|
|
db.query(UserAchievement)
|
|
.filter(UserAchievement.user_id == user_id)
|
|
.offset(skip)
|
|
.limit(limit)
|
|
.all()
|
|
)
|
|
|
|
def get_by_user_achievement(
|
|
self, db: Session, *, user_id: int, achievement_id: int
|
|
) -> Optional[UserAchievement]:
|
|
"""
|
|
Get a specific user achievement.
|
|
"""
|
|
return (
|
|
db.query(UserAchievement)
|
|
.filter(
|
|
UserAchievement.user_id == user_id, UserAchievement.achievement_id == achievement_id
|
|
)
|
|
.first()
|
|
)
|
|
|
|
def award_achievement(
|
|
self, db: Session, *, user_id: int, achievement_id: int
|
|
) -> UserAchievement:
|
|
"""
|
|
Award an achievement to a user.
|
|
"""
|
|
# Check if user already has this achievement
|
|
existing = self.get_by_user_achievement(db, user_id=user_id, achievement_id=achievement_id)
|
|
|
|
if existing:
|
|
return existing
|
|
|
|
# Get the achievement to award points
|
|
achievement = db.query(Achievement).filter(Achievement.id == achievement_id).first()
|
|
|
|
# Create new user achievement
|
|
user_achievement = UserAchievement(
|
|
user_id=user_id, achievement_id=achievement_id, earned_at=datetime.utcnow()
|
|
)
|
|
db.add(user_achievement)
|
|
|
|
# Award points to user
|
|
if achievement and achievement.points > 0:
|
|
user_service.add_points(db, user_id=user_id, points=achievement.points)
|
|
|
|
db.commit()
|
|
db.refresh(user_achievement)
|
|
return user_achievement
|
|
|
|
def check_achievements(self, db: Session, *, user_id: int) -> List[UserAchievement]:
|
|
"""
|
|
Check and award achievements based on user's progress.
|
|
"""
|
|
# Get all active achievements
|
|
active_achievements = db.query(Achievement).filter(Achievement.is_active == True).all()
|
|
|
|
# Get user's current achievements
|
|
user_achievements = (
|
|
db.query(UserAchievement.achievement_id)
|
|
.filter(UserAchievement.user_id == user_id)
|
|
.all()
|
|
)
|
|
already_earned = [ua[0] for ua in user_achievements]
|
|
|
|
# Get user data for achievement criteria
|
|
user = user_service.get(db, id=user_id)
|
|
|
|
# Counters for various achievement criteria
|
|
completed_lessons_count = (
|
|
db.query(func.count(UserProgress.id))
|
|
.filter(
|
|
UserProgress.user_id == user_id, UserProgress.status == ProgressStatus.COMPLETED
|
|
)
|
|
.scalar()
|
|
or 0
|
|
)
|
|
|
|
# List to store newly awarded achievements
|
|
newly_awarded = []
|
|
|
|
# Check each achievement
|
|
for achievement in active_achievements:
|
|
# Skip if already earned
|
|
if achievement.id in already_earned:
|
|
continue
|
|
|
|
# Parse criteria
|
|
try:
|
|
criteria = json.loads(achievement.criteria)
|
|
except json.JSONDecodeError:
|
|
# Skip if criteria is invalid JSON
|
|
continue
|
|
|
|
# Check different types of achievements
|
|
awarded = False
|
|
|
|
if achievement.type == AchievementType.COMPLETION:
|
|
# Completion achievements (e.g., complete X lessons)
|
|
if criteria.get("type") == "lessons_completed" and "count" in criteria:
|
|
if completed_lessons_count >= criteria["count"]:
|
|
awarded = True
|
|
|
|
elif achievement.type == AchievementType.MILESTONE:
|
|
# Milestone achievements (e.g., reach level X, earn Y points)
|
|
if criteria.get("type") == "reach_level" and "level" in criteria:
|
|
if user.level >= criteria["level"]:
|
|
awarded = True
|
|
elif criteria.get("type") == "earn_points" and "points" in criteria:
|
|
if user.points >= criteria["points"]:
|
|
awarded = True
|
|
|
|
# Award the achievement if criteria met
|
|
if awarded:
|
|
user_achievement = self.award_achievement(
|
|
db, user_id=user_id, achievement_id=achievement.id
|
|
)
|
|
newly_awarded.append(user_achievement)
|
|
|
|
return newly_awarded
|
|
|
|
|
|
achievement = CRUDAchievement(Achievement)
|
|
user_achievement = CRUDUserAchievement(UserAchievement)
|