package handlers import ( "net/http" "strconv" "time" "github.com/gin-gonic/gin" "github.com/trackeep/backend/models" "gorm.io/gorm" ) type CommunityHandler struct { db *gorm.DB } func NewCommunityHandler(db *gorm.DB) *CommunityHandler { return &CommunityHandler{db: db} } // === CHALLENGE HANDLERS === // GetChallenges returns all challenges with filtering func (h *CommunityHandler) GetChallenges(c *gin.Context) { var challenges []models.Challenge query := h.db.Preload("Creator").Preload("Tags") // Filter by status if status := c.Query("status"); status != "" { query = query.Where("status = ?", status) } else { // Default to active challenges for public view query = query.Where("status = ? AND is_public = ?", "active", true) } // Filter by category if category := c.Query("category"); category != "" { query = query.Where("category = ?", category) } // Filter by difficulty if difficulty := c.Query("difficulty"); difficulty != "" { query = query.Where("difficulty = ?", difficulty) } // Filter by featured if featured := c.Query("featured"); featured == "true" { query = query.Where("is_featured = ?", true) } // Search by title or description if search := c.Query("search"); search != "" { query = query.Where("title ILIKE ? OR description ILIKE ?", "%"+search+"%", "%"+search+"%") } // Sort by sortBy := c.DefaultQuery("sort", "created_at") switch sortBy { case "participants": query = query.Order("participant_count DESC") case "completion_rate": query = query.Order("completion_rate DESC") case "start_date": query = query.Order("start_date ASC") case "created_at": query = query.Order("created_at DESC") default: query = query.Order("created_at DESC") } // Pagination page, _ := strconv.Atoi(c.DefaultQuery("page", "1")) limit, _ := strconv.Atoi(c.DefaultQuery("limit", "20")) offset := (page - 1) * limit var total int64 query.Model(&models.Challenge{}).Count(&total) if err := query.Offset(offset).Limit(limit).Find(&challenges).Error; err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to fetch challenges"}) return } c.JSON(http.StatusOK, gin.H{ "challenges": challenges, "pagination": gin.H{ "page": page, "limit": limit, "total": total, "pages": (total + int64(limit) - 1) / int64(limit), }, }) } // GetChallenge returns a specific challenge func (h *CommunityHandler) GetChallenge(c *gin.Context) { id := c.Param("id") var challenge models.Challenge if err := h.db.Preload("Creator").Preload("Tags").Preload("Milestones").Preload("Resources").First(&challenge, id).Error; err != nil { if err == gorm.ErrRecordNotFound { c.JSON(http.StatusNotFound, gin.H{"error": "Challenge not found"}) return } c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to fetch challenge"}) return } c.JSON(http.StatusOK, challenge) } // CreateChallenge creates a new challenge func (h *CommunityHandler) CreateChallenge(c *gin.Context) { userID := c.GetUint("user_id") var challenge models.Challenge if err := c.ShouldBindJSON(&challenge); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } challenge.CreatorID = userID if err := h.db.Create(&challenge).Error; err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to create challenge"}) return } c.JSON(http.StatusCreated, challenge) } // JoinChallenge allows a user to join a challenge func (h *CommunityHandler) JoinChallenge(c *gin.Context) { userID := c.GetUint("user_id") challengeID := c.Param("id") // Check if challenge exists and is active var challenge models.Challenge if err := h.db.First(&challenge, challengeID).Error; err != nil { c.JSON(http.StatusNotFound, gin.H{"error": "Challenge not found"}) return } if challenge.Status != "active" { c.JSON(http.StatusBadRequest, gin.H{"error": "Challenge is not active"}) return } // Check if user is already a participant var existingParticipant models.ChallengeParticipant if err := h.db.Where("challenge_id = ? AND user_id = ?", challengeID, userID).First(&existingParticipant).Error; err == nil { c.JSON(http.StatusConflict, gin.H{"error": "You are already participating in this challenge"}) return } // Check if challenge has max participants limit if challenge.MaxParticipants != nil { var participantCount int64 h.db.Model(&models.ChallengeParticipant{}).Where("challenge_id = ?", challengeID).Count(&participantCount) if participantCount >= int64(*challenge.MaxParticipants) { c.JSON(http.StatusBadRequest, gin.H{"error": "Challenge has reached maximum participants"}) return } } // Create participant participant := models.ChallengeParticipant{ ChallengeID: challenge.ID, UserID: userID, Status: "joined", StartedAt: &time.Time{}, } *participant.StartedAt = time.Now() if err := h.db.Create(&participant).Error; err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to join challenge"}) return } // Update challenge participant count h.db.Model(&challenge).UpdateColumn("participant_count", gorm.Expr("participant_count + 1")) c.JSON(http.StatusCreated, participant) } // GetMyChallenges returns current user's challenge participations func (h *CommunityHandler) GetMyChallenges(c *gin.Context) { userID := c.GetUint("user_id") var participations []models.ChallengeParticipant if err := h.db.Preload("Challenge").Preload("Challenge.Creator").Where("user_id = ?", userID).Find(&participations).Error; err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to fetch your challenges"}) return } c.JSON(http.StatusOK, participations) } // UpdateChallengeProgress updates a user's progress in a challenge func (h *CommunityHandler) UpdateChallengeProgress(c *gin.Context) { userID := c.GetUint("user_id") challengeID := c.Param("id") var req struct { Progress float64 `json:"progress" binding:"required,min=0,max=100"` Notes string `json:"notes"` } if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } var participant models.ChallengeParticipant if err := h.db.Where("challenge_id = ? AND user_id = ?", challengeID, userID).First(&participant).Error; err != nil { c.JSON(http.StatusNotFound, gin.H{"error": "Challenge participation not found"}) return } // Update progress participant.Progress = req.Progress participant.Notes = req.Notes participant.LastActivityAt = &time.Time{} *participant.LastActivityAt = time.Now() // Update status based on progress if req.Progress >= 100 && participant.Status != "completed" { participant.Status = "completed" participant.CompletedAt = &time.Time{} *participant.CompletedAt = time.Now() } else if req.Progress > 0 && participant.Status == "joined" { participant.Status = "in_progress" } if err := h.db.Save(&participant).Error; err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to update progress"}) return } // Update challenge completion count and rate h.db.Model(&models.Challenge{}).Where("id = ?", challengeID).UpdateColumn("completion_count", gorm.Expr("(SELECT COUNT(*) FROM challenge_participants WHERE challenge_id = ? AND status = 'completed')", challengeID)) c.JSON(http.StatusOK, participant) } // === MENTORSHIP HANDLERS === // GetMentorshipRequests returns mentorship requests for the current user func (h *CommunityHandler) GetMentorshipRequests(c *gin.Context) { userID := c.GetUint("user_id") role := c.Query("role") // sent, received var requests []models.MentorshipRequest query := h.db.Preload("FromUser").Preload("ToUser") if role == "sent" { query = query.Where("from_user_id = ?", userID) } else if role == "received" { query = query.Where("to_user_id = ?", userID) } else { query = query.Where("from_user_id = ? OR to_user_id = ?", userID, userID) } if err := query.Order("created_at DESC").Find(&requests).Error; err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to fetch mentorship requests"}) return } c.JSON(http.StatusOK, requests) } // CreateMentorshipRequest creates a new mentorship request func (h *CommunityHandler) CreateMentorshipRequest(c *gin.Context) { userID := c.GetUint("user_id") var request models.MentorshipRequest if err := c.ShouldBindJSON(&request); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } request.FromUserID = userID // Calculate match score (simplified version) request.MatchScore = calculateMatchScore(request) if err := h.db.Create(&request).Error; err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to create mentorship request"}) return } c.JSON(http.StatusCreated, request) } // RespondToMentorshipRequest responds to a mentorship request func (h *CommunityHandler) RespondToMentorshipRequest(c *gin.Context) { userID := c.GetUint("user_id") requestID := c.Param("id") var req struct { Status string `json:"status" binding:"required"` // accepted, rejected Response string `json:"response"` } if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } var request models.MentorshipRequest if err := h.db.First(&request, requestID).Error; err != nil { c.JSON(http.StatusNotFound, gin.H{"error": "Mentorship request not found"}) return } // Check if user is the recipient if request.ToUserID != userID { c.JSON(http.StatusForbidden, gin.H{"error": "You can only respond to requests sent to you"}) return } if request.Status != "pending" { c.JSON(http.StatusBadRequest, gin.H{"error": "Request has already been responded to"}) return } // Update request request.Status = req.Status request.Response = req.Response request.RespondedAt = &time.Time{} *request.RespondedAt = time.Now() if err := h.db.Save(&request).Error; err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to respond to request"}) return } // If accepted, create mentorship if req.Status == "accepted" { mentorship := models.Mentorship{ MentorID: request.FromUserID, MenteeID: request.ToUserID, Category: request.Category, Description: request.Description, Goals: request.Goals, StartDate: time.Now(), Status: "active", IsPaid: request.IsPaid, Rate: request.Rate, Currency: request.Currency, SessionLimit: request.Duration, } if err := h.db.Create(&mentorship).Error; err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to create mentorship"}) return } } c.JSON(http.StatusOK, request) } // GetMyMentorships returns current user's mentorships func (h *CommunityHandler) GetMyMentorships(c *gin.Context) { userID := c.GetUint("user_id") role := c.Query("role") // mentor, mentee var mentorships []models.Mentorship query := h.db.Preload("Mentor").Preload("Mentee") if role == "mentor" { query = query.Where("mentor_id = ?", userID) } else if role == "mentee" { query = query.Where("mentee_id = ?", userID) } else { query = query.Where("mentor_id = ? OR mentee_id = ?", userID, userID) } if err := query.Order("created_at DESC").Find(&mentorships).Error; err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to fetch mentorships"}) return } c.JSON(http.StatusOK, mentorships) } // CreateMentorshipSession creates a new mentoring session func (h *CommunityHandler) CreateMentorshipSession(c *gin.Context) { userID := c.GetUint("user_id") mentorshipID := c.Param("id") // Check if user is part of this mentorship var mentorship models.Mentorship if err := h.db.First(&mentorship, mentorshipID).Error; err != nil { c.JSON(http.StatusNotFound, gin.H{"error": "Mentorship not found"}) return } if mentorship.MentorID != userID && mentorship.MenteeID != userID { c.JSON(http.StatusForbidden, gin.H{"error": "You are not part of this mentorship"}) return } var session models.MentorshipSession if err := c.ShouldBindJSON(&session); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } session.MentorshipID = mentorship.ID if err := h.db.Create(&session).Error; err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to create session"}) return } c.JSON(http.StatusCreated, session) } // GetMentorshipSessions returns sessions for a mentorship func (h *CommunityHandler) GetMentorshipSessions(c *gin.Context) { userID := c.GetUint("user_id") mentorshipID := c.Param("id") // Check if user is part of this mentorship var mentorship models.Mentorship if err := h.db.First(&mentorship, mentorshipID).Error; err != nil { c.JSON(http.StatusNotFound, gin.H{"error": "Mentorship not found"}) return } if mentorship.MentorID != userID && mentorship.MenteeID != userID { c.JSON(http.StatusForbidden, gin.H{"error": "You are not part of this mentorship"}) return } var sessions []models.MentorshipSession if err := h.db.Where("mentorship_id = ?", mentorshipID).Order("scheduled_for DESC").Find(&sessions).Error; err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to fetch sessions"}) return } c.JSON(http.StatusOK, sessions) } // GetCommunityStats returns community statistics func (h *CommunityHandler) GetCommunityStats(c *gin.Context) { var stats struct { ActiveChallenges int64 `json:"active_challenges"` TotalParticipants int64 `json:"total_participants"` ActiveMentorships int64 `json:"active_mentorships"` TotalMentorshipHours float64 `json:"total_mentorship_hours"` PendingRequests int64 `json:"pending_requests"` CompletedChallenges int64 `json:"completed_challenges"` } h.db.Model(&models.Challenge{}).Where("status = ?", "active").Count(&stats.ActiveChallenges) h.db.Model(&models.ChallengeParticipant{}).Count(&stats.TotalParticipants) h.db.Model(&models.Mentorship{}).Where("status = ?", "active").Count(&stats.ActiveMentorships) h.db.Model(&models.Mentorship{}).Select("COALESCE(SUM(total_hours), 0)").Row().Scan(&stats.TotalMentorshipHours) h.db.Model(&models.MentorshipRequest{}).Where("status = ?", "pending").Count(&stats.PendingRequests) h.db.Model(&models.ChallengeParticipant{}).Where("status = ?", "completed").Count(&stats.CompletedChallenges) c.JSON(http.StatusOK, stats) } // Helper function to calculate match score (simplified version) func calculateMatchScore(request models.MentorshipRequest) float64 { // This is a simplified version - in production, you'd use more sophisticated matching // based on skills, experience, availability, preferences, etc. score := 0.5 // Base score // Add points for detailed description if len(request.Description) > 100 { score += 0.1 } // Add points for clear goals if len(request.Goals) > 50 { score += 0.1 } // Add points for specified duration if request.Duration > 0 { score += 0.1 } // Add points for availability if len(request.Availability) > 20 { score += 0.1 } if score > 1.0 { score = 1.0 } return score }