Files
Trackeep/backend/handlers/learning_path.go
T
Tomas Dvorak d27cf14110 first test
2026-02-08 14:14:55 +01:00

389 lines
11 KiB
Go

package handlers
import (
"net/http"
"strconv"
"time"
"github.com/gin-gonic/gin"
"github.com/trackeep/backend/config"
"github.com/trackeep/backend/models"
)
// GetLearningPaths handles GET /api/v1/learning-paths
func GetLearningPaths(c *gin.Context) {
db := config.GetDB()
var learningPaths []models.LearningPath
// Parse query parameters
category := c.Query("category")
difficulty := c.Query("difficulty")
featured := c.Query("featured")
search := c.Query("search")
query := db.Where("is_published = ?", true)
// Add filters
if category != "" {
query = query.Where("category = ?", category)
}
if difficulty != "" {
query = query.Where("difficulty = ?", difficulty)
}
if featured == "true" {
query = query.Where("is_featured = ?", true)
}
if search != "" {
query = query.Where("title ILIKE ? OR description ILIKE ?", "%"+search+"%", "%"+search+"%")
}
// Preload relationships
if err := query.Preload("Creator").Preload("Tags").Preload("Modules").Find(&learningPaths).Error; err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to fetch learning paths"})
return
}
c.JSON(http.StatusOK, learningPaths)
}
// GetLearningPath handles GET /api/v1/learning-paths/:id
func GetLearningPath(c *gin.Context) {
db := config.GetDB()
id, err := strconv.ParseUint(c.Param("id"), 10, 32)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid learning path ID"})
return
}
var learningPath models.LearningPath
if err := db.Where("id = ? AND is_published = ?", id, true).
Preload("Creator").
Preload("Tags").
Preload("Modules", "ORDER BY \"order\" ASC").
Preload("Modules.Resources", "ORDER BY \"order\" ASC").
First(&learningPath).Error; err != nil {
c.JSON(http.StatusNotFound, gin.H{"error": "Learning path not found"})
return
}
c.JSON(http.StatusOK, learningPath)
}
// CreateLearningPath handles POST /api/v1/learning-paths
func CreateLearningPath(c *gin.Context) {
db := config.GetDB()
var learningPath models.LearningPath
if err := c.ShouldBindJSON(&learningPath); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
// Get user ID from auth middleware
userID := c.GetUint("userID")
if userID == 0 {
c.JSON(http.StatusUnauthorized, gin.H{"error": "User not authenticated"})
return
}
learningPath.CreatorID = userID
// Create learning path
if err := db.Create(&learningPath).Error; err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to create learning path"})
return
}
// Preload relationships for response
db.Preload("Creator").Preload("Tags").First(&learningPath, learningPath.ID)
c.JSON(http.StatusCreated, learningPath)
}
// UpdateLearningPath handles PUT /api/v1/learning-paths/:id
func UpdateLearningPath(c *gin.Context) {
db := config.GetDB()
id, err := strconv.ParseUint(c.Param("id"), 10, 32)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid learning path ID"})
return
}
var learningPath models.LearningPath
userID := c.GetUint("userID")
if userID == 0 {
c.JSON(http.StatusUnauthorized, gin.H{"error": "User not authenticated"})
return
}
// Find existing learning path (creator or admin only)
if err := db.Where("id = ? AND creator_id = ?", id, userID).First(&learningPath).Error; err != nil {
c.JSON(http.StatusNotFound, gin.H{"error": "Learning path not found or no permission"})
return
}
// Update fields
var updateData models.LearningPath
if err := c.ShouldBindJSON(&updateData); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
if err := db.Model(&learningPath).Updates(updateData).Error; err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to update learning path"})
return
}
// Get updated learning path with relationships
db.Preload("Creator").Preload("Tags").Preload("Modules").First(&learningPath, learningPath.ID)
c.JSON(http.StatusOK, learningPath)
}
// DeleteLearningPath handles DELETE /api/v1/learning-paths/:id
func DeleteLearningPath(c *gin.Context) {
db := config.GetDB()
id, err := strconv.ParseUint(c.Param("id"), 10, 32)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid learning path ID"})
return
}
var learningPath models.LearningPath
userID := c.GetUint("userID")
if userID == 0 {
c.JSON(http.StatusUnauthorized, gin.H{"error": "User not authenticated"})
return
}
// Find and delete learning path (creator or admin only)
if err := db.Where("id = ? AND creator_id = ?", id, userID).First(&learningPath).Error; err != nil {
c.JSON(http.StatusNotFound, gin.H{"error": "Learning path not found or no permission"})
return
}
if err := db.Delete(&learningPath).Error; err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to delete learning path"})
return
}
c.JSON(http.StatusOK, gin.H{"message": "Learning path deleted successfully"})
}
// EnrollInLearningPath handles POST /api/v1/learning-paths/:id/enroll
func EnrollInLearningPath(c *gin.Context) {
db := config.GetDB()
pathID, err := strconv.ParseUint(c.Param("id"), 10, 32)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid learning path ID"})
return
}
userID := c.GetUint("userID")
if userID == 0 {
c.JSON(http.StatusUnauthorized, gin.H{"error": "User not authenticated"})
return
}
// Check if learning path exists
var learningPath models.LearningPath
if err := db.First(&learningPath, pathID).Error; err != nil {
c.JSON(http.StatusNotFound, gin.H{"error": "Learning path not found"})
return
}
// Check if already enrolled
var existingEnrollment models.Enrollment
if err := db.Where("user_id = ? AND learning_path_id = ?", userID, pathID).First(&existingEnrollment).Error; err == nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "Already enrolled in this learning path"})
return
}
// Create enrollment
enrollment := models.Enrollment{
UserID: userID,
LearningPathID: uint(pathID),
Status: "enrolled",
Progress: 0,
CompletedModules: []uint{},
}
if err := db.Create(&enrollment).Error; err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to enroll in learning path"})
return
}
// Update enrollment count
db.Model(&learningPath).UpdateColumn("enrollment_count", learningPath.EnrollmentCount + 1)
c.JSON(http.StatusCreated, enrollment)
}
// GetUserEnrollments handles GET /api/v1/enrollments
func GetUserEnrollments(c *gin.Context) {
db := config.GetDB()
userID := c.GetUint("userID")
if userID == 0 {
c.JSON(http.StatusUnauthorized, gin.H{"error": "User not authenticated"})
return
}
var enrollments []models.Enrollment
if err := db.Where("user_id = ?", userID).
Preload("LearningPath").
Preload("LearningPath.Creator").
Preload("LearningPath.Tags").
Find(&enrollments).Error; err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to fetch enrollments"})
return
}
c.JSON(http.StatusOK, enrollments)
}
// UpdateProgress handles PUT /api/v1/enrollments/:id/progress
func UpdateProgress(c *gin.Context) {
db := config.GetDB()
enrollmentID, err := strconv.ParseUint(c.Param("id"), 10, 32)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid enrollment ID"})
return
}
userID := c.GetUint("userID")
if userID == 0 {
c.JSON(http.StatusUnauthorized, gin.H{"error": "User not authenticated"})
return
}
var input struct {
ModuleID uint `json:"module_id"`
Status string `json:"status"`
Progress float64 `json:"progress"`
CompletedModules []uint `json:"completed_modules"`
}
if err := c.ShouldBindJSON(&input); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
// Find enrollment
var enrollment models.Enrollment
if err := db.Where("id = ? AND user_id = ?", enrollmentID, userID).First(&enrollment).Error; err != nil {
c.JSON(http.StatusNotFound, gin.H{"error": "Enrollment not found"})
return
}
// Update enrollment
now := time.Now()
if enrollment.Status == "enrolled" {
enrollment.StartedAt = &now
enrollment.Status = "in_progress"
}
enrollment.Progress = input.Progress
enrollment.CompletedModules = input.CompletedModules
enrollment.CurrentModuleID = &input.ModuleID
// Check if completed
if input.Progress >= 100 {
enrollment.Status = "completed"
enrollment.CompletedAt = &now
}
if err := db.Save(&enrollment).Error; err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to update progress"})
return
}
c.JSON(http.StatusOK, enrollment)
}
// RateLearningPath handles POST /api/v1/enrollments/:id/rate
func RateLearningPath(c *gin.Context) {
db := config.GetDB()
enrollmentID, err := strconv.ParseUint(c.Param("id"), 10, 32)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid enrollment ID"})
return
}
userID := c.GetUint("userID")
if userID == 0 {
c.JSON(http.StatusUnauthorized, gin.H{"error": "User not authenticated"})
return
}
var input struct {
Rating float64 `json:"rating" binding:"required,min=1,max=5"`
Review string `json:"review"`
}
if err := c.ShouldBindJSON(&input); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
// Find enrollment
var enrollment models.Enrollment
if err := db.Where("id = ? AND user_id = ?", enrollmentID, userID).First(&enrollment).Error; err != nil {
c.JSON(http.StatusNotFound, gin.H{"error": "Enrollment not found"})
return
}
// Update enrollment with rating
now := time.Now()
enrollment.Rating = &input.Rating
enrollment.Review = input.Review
enrollment.ReviewDate = &now
if err := db.Save(&enrollment).Error; err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to save rating"})
return
}
// Update learning path rating
var learningPath models.LearningPath
db.First(&learningPath, enrollment.LearningPathID)
// Recalculate average rating
var avgRating struct {
AvgRating float64
Count int
}
db.Model(&models.Enrollment{}).
Select("AVG(rating) as avg_rating, COUNT(*) as count").
Where("learning_path_id = ? AND rating IS NOT NULL", enrollment.LearningPathID).
Scan(&avgRating)
learningPath.Rating = avgRating.AvgRating
learningPath.ReviewCount = avgRating.Count
db.Save(&learningPath)
c.JSON(http.StatusOK, enrollment)
}
// GetLearningPathCategories handles GET /api/v1/learning-paths/categories
func GetLearningPathCategories(c *gin.Context) {
categories := []string{
"programming",
"web-development",
"mobile-development",
"data-science",
"machine-learning",
"cybersecurity",
"design",
"business",
"marketing",
"photography",
"music",
"writing",
"languages",
"other",
}
c.JSON(http.StatusOK, gin.H{"categories": categories})
}