mirror of
https://github.com/Dvorinka/Trackeep.git
synced 2026-06-03 20:12:58 +00:00
335 lines
9.7 KiB
Go
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
|
|
}
|