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