Files
Tomas Dvorak d27cf14110 first test
2026-02-08 14:14:55 +01:00

335 lines
9.7 KiB
Go

package handlers
import (
"net/http"
"strconv"
"time"
"github.com/gin-gonic/gin"
"github.com/trackeep/backend/models"
"gorm.io/gorm"
)
// LearningProgressHandler handles learning progress operations
type LearningProgressHandler struct {
db *gorm.DB
}
// NewLearningProgressHandler creates a new learning progress handler
func NewLearningProgressHandler(db *gorm.DB) *LearningProgressHandler {
return &LearningProgressHandler{db: db}
}
// UpdateLearningProgress updates learning analytics when user interacts with course
func (h *LearningProgressHandler) UpdateLearningProgress(c *gin.Context) {
userID := c.GetUint("user_id")
var req struct {
CourseID uint `json:"course_id" binding:"required"`
TimeSpent float64 `json:"time_spent"` // in hours
Progress float64 `json:"progress"` // percentage 0-100
ModuleID *uint `json:"module_id,omitempty"`
QuizScore *float64 `json:"quiz_score,omitempty"`
Skills []string `json:"skills,omitempty"`
}
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
// Get course information
var course models.Course
if err := h.db.First(&course, req.CourseID).Error; err != nil {
c.JSON(http.StatusNotFound, gin.H{"error": "Course not found"})
return
}
// Get or create learning analytics
var learningAnalytics models.LearningAnalytics
err := h.db.Where("user_id = ? AND course_id = ?", userID, req.CourseID).
Preload("Course").
First(&learningAnalytics).Error
if err != nil {
// Create new learning analytics
averageScore := 0.0
if req.QuizScore != nil {
averageScore = *req.QuizScore
}
learningAnalytics = models.LearningAnalytics{
UserID: userID,
CourseID: req.CourseID,
StartDate: time.Now(),
LastAccessed: time.Now(),
TimeSpent: req.TimeSpent,
Progress: req.Progress,
ModulesCompleted: 0,
TotalModules: course.ModuleCount,
AverageScore: averageScore,
StreakDays: 1,
SkillsAcquired: req.Skills,
}
if err := h.db.Create(&learningAnalytics).Error; err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to create learning analytics"})
return
}
} else {
// Update existing analytics
learningAnalytics.LastAccessed = time.Now()
learningAnalytics.TimeSpent += req.TimeSpent
learningAnalytics.Progress = req.Progress
// Update modules completed if progress increased
if req.ModuleID != nil {
learningAnalytics.ModulesCompleted++
}
// Update quiz scores
if req.QuizScore != nil {
if learningAnalytics.QuizScores == nil {
learningAnalytics.QuizScores = []float64{*req.QuizScore}
} else {
learningAnalytics.QuizScores = append(learningAnalytics.QuizScores, *req.QuizScore)
}
// Calculate average score
sum := 0.0
for _, score := range learningAnalytics.QuizScores {
sum += score
}
learningAnalytics.AverageScore = sum / float64(len(learningAnalytics.QuizScores))
}
// Update skills
if len(req.Skills) > 0 {
skillsMap := make(map[string]bool)
for _, skill := range learningAnalytics.SkillsAcquired {
skillsMap[skill] = true
}
for _, skill := range req.Skills {
if !skillsMap[skill] {
learningAnalytics.SkillsAcquired = append(learningAnalytics.SkillsAcquired, skill)
skillsMap[skill] = true
}
}
}
// Update streak
learningAnalytics.StreakDays = h.calculateLearningStreak(userID, req.CourseID)
if err := h.db.Save(&learningAnalytics).Error; err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to update learning analytics"})
return
}
}
// Check if course is completed
if learningAnalytics.Progress >= 100 && !learningAnalytics.CourseCompleted {
learningAnalytics.CourseCompleted = true
learningAnalytics.CompletedAt = &time.Time{}
*learningAnalytics.CompletedAt = time.Now()
h.db.Save(&learningAnalytics)
// Update enrollment if exists
var enrollment models.Enrollment
if err := h.db.Where("user_id = ? AND course_id = ?", userID, req.CourseID).
First(&enrollment).Error; err == nil {
enrollment.CompletedAt = learningAnalytics.CompletedAt
enrollment.Status = "completed"
h.db.Save(&enrollment)
}
}
c.JSON(http.StatusOK, learningAnalytics)
}
// GetLearningProgress returns detailed learning progress for a user
func (h *LearningProgressHandler) GetLearningProgress(c *gin.Context) {
userID := c.GetUint("user_id")
var learningAnalytics []models.LearningAnalytics
if err := h.db.Where("user_id = ?", userID).
Preload("Course").
Order("last_accessed DESC").
Find(&learningAnalytics).Error; err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to fetch learning progress"})
return
}
// Calculate overall statistics
totalCourses := len(learningAnalytics)
completedCourses := 0
inProgressCourses := 0
totalTimeSpent := 0.0
totalSkills := make(map[string]bool)
for _, la := range learningAnalytics {
totalTimeSpent += la.TimeSpent
if la.Progress >= 100 {
completedCourses++
} else if la.Progress > 0 {
inProgressCourses++
}
for _, skill := range la.SkillsAcquired {
totalSkills[skill] = true
}
}
// Get recent activity
var recentActivity []models.LearningAnalytics
if err := h.db.Where("user_id = ? AND last_accessed >= ?",
userID, time.Now().AddDate(0, 0, -7)).
Preload("Course").
Order("last_accessed DESC").
Find(&recentActivity).Error; err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to fetch recent activity"})
return
}
// Convert skills map to slice
skillsList := make([]string, 0, len(totalSkills))
for skill := range totalSkills {
skillsList = append(skillsList, skill)
}
c.JSON(http.StatusOK, gin.H{
"learning_progress": learningAnalytics,
"statistics": gin.H{
"total_courses": totalCourses,
"completed_courses": completedCourses,
"in_progress_courses": inProgressCourses,
"total_time_spent": totalTimeSpent,
"total_skills": len(skillsList),
"skills_acquired": skillsList,
},
"recent_activity": recentActivity,
})
}
// GetCourseProgress returns detailed progress for a specific course
func (h *LearningProgressHandler) GetCourseProgress(c *gin.Context) {
userID := c.GetUint("user_id")
courseID, err := strconv.ParseUint(c.Param("courseId"), 10, 32)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid course ID"})
return
}
var learningAnalytics models.LearningAnalytics
if err := h.db.Where("user_id = ? AND course_id = ?", userID, courseID).
Preload("Course").
First(&learningAnalytics).Error; err != nil {
c.JSON(http.StatusNotFound, gin.H{"error": "Course progress not found"})
return
}
// Get detailed module progress if available
var moduleProgress []gin.H
// This would be implemented based on your course structure
// For now, return placeholder data
c.JSON(http.StatusOK, gin.H{
"course_progress": learningAnalytics,
"module_progress": moduleProgress,
"insights": h.generateLearningInsights(learningAnalytics),
})
}
// MarkCourseCompleted marks a course as completed
func (h *LearningProgressHandler) MarkCourseCompleted(c *gin.Context) {
userID := c.GetUint("user_id")
courseID, err := strconv.ParseUint(c.Param("courseId"), 10, 32)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid course ID"})
return
}
var learningAnalytics models.LearningAnalytics
if err := h.db.Where("user_id = ? AND course_id = ?", userID, courseID).
First(&learningAnalytics).Error; err != nil {
c.JSON(http.StatusNotFound, gin.H{"error": "Course progress not found"})
return
}
learningAnalytics.Progress = 100
learningAnalytics.CourseCompleted = true
completedAt := time.Now()
learningAnalytics.CompletedAt = &completedAt
if err := h.db.Save(&learningAnalytics).Error; err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to mark course as completed"})
return
}
// Update enrollment
var enrollment models.Enrollment
if err := h.db.Where("user_id = ? AND course_id = ?", userID, courseID).
First(&enrollment).Error; err == nil {
enrollment.CompletedAt = &completedAt
enrollment.Status = "completed"
h.db.Save(&enrollment)
}
c.JSON(http.StatusOK, gin.H{
"message": "Course marked as completed",
"course_progress": learningAnalytics,
})
}
// Helper functions
func (h *LearningProgressHandler) calculateLearningStreak(userID uint, courseID uint) int {
// Calculate consecutive days with learning activity for this course
streak := 0
currentDate := time.Now().Truncate(24 * time.Hour)
for i := 0; i < 365; i++ { // Check up to a year back
checkDate := currentDate.AddDate(0, 0, -i)
var count int64
h.db.Model(&models.LearningAnalytics{}).
Where("user_id = ? AND course_id = ? AND DATE(last_accessed) = ?",
userID, courseID, checkDate.Format("2006-01-02")).
Count(&count)
if count > 0 {
streak++
} else {
break
}
}
return streak
}
func (h *LearningProgressHandler) generateLearningInsights(analytics models.LearningAnalytics) []string {
insights := []string{}
// Generate insights based on learning patterns
if analytics.TimeSpent > 50 {
insights = append(insights, "You've dedicated over 50 hours to this course!")
}
if analytics.StreakDays > 7 {
insights = append(insights, "Great consistency! You've maintained a learning streak for over a week.")
}
if analytics.AverageScore > 85 {
insights = append(insights, "Excellent quiz performance! You're mastering the material.")
}
if analytics.Progress > 80 && analytics.Progress < 100 {
insights = append(insights, "You're almost there! Just a bit more to complete this course.")
}
if len(analytics.SkillsAcquired) > 5 {
insights = append(insights, "You've acquired numerous skills from this course.")
}
return insights
}