package handlers import ( "fmt" "net/http" "strconv" "github.com/gin-gonic/gin" "gorm.io/gorm" "smoop-api/internal/dto" "smoop-api/internal/models" ) type TrackersHandler struct { db *gorm.DB } func NewTrackersHandler(db *gorm.DB) *TrackersHandler { return &TrackersHandler{db: db} } // GET /trackers — list trackers available for user (admin: all) func (h *TrackersHandler) List(c *gin.Context) { offset, _ := strconv.Atoi(c.DefaultQuery("offset", "0")) limit, _ := strconv.Atoi(c.DefaultQuery("limit", "50")) if limit <= 0 || limit > 200 { limit = 50 } // Default behavior (fallback if middleware not present): use user context isFilter := true var userID uint if v, ok := c.Get("filterTrackers"); ok { if b, ok2 := v.(bool); ok2 { isFilter = b } } if v, ok := c.Get("userID"); ok { if id, ok2 := v.(uint); ok2 { userID = id } } // Fallback to claims if middleware wasn’t applied if _, exists := c.Get("filterTrackers"); !exists { if uc, ok := GetUserContext(c); ok { if uc.Role == models.RoleAdmin { isFilter = false } else { isFilter = true userID = uc.ID } } else { c.JSON(http.StatusUnauthorized, gin.H{"error": "unauthorized"}) return } } var ( total int64 list []models.Tracker err error ) if !isFilter { // Admin: all trackers err = h.db.Model(&models.Tracker{}).Count(&total).Error if err == nil { err = h.db.Preload("Users").Offset(offset).Limit(limit).Find(&list).Error } } else { // Filtered by userID q := h.db.Model(&models.Tracker{}). Joins("JOIN user_trackers ut ON ut.tracker_guid = trackers.guid"). Where("ut.user_id = ?", userID) if err = q.Count(&total).Error; err == nil { err = h.db.Preload("Users"). Joins("JOIN user_trackers ut ON ut.tracker_guid = trackers.guid"). Where("ut.user_id = ?", userID). Offset(offset).Limit(limit). Find(&list).Error } } if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "query failed: " + err.Error()}) return } out := make([]dto.TrackerDto, 0, len(list)) for _, t := range list { out = append(out, dto.MapTracker(t)) } c.JSON(http.StatusOK, dto.TrackerListDto{ Trackers: out, Offset: offset, Limit: limit, Total: total, }) } // POST /trackers/create — create tracker; optional initial user assignments func (h *TrackersHandler) Create(c *gin.Context) { var req dto.CreateTrackerDto if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } t := models.Tracker{GUID: req.GUID, Name: req.Name} if err := h.db.Create(&t).Error; err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "tracker exists?"}) return } // optional users if len(req.UserIDs) > 0 { users, err := h.fetchUsers(req.UserIDs) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } if err := h.db.Model(&t).Association("Users").Append(&users); err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "link failed"}) return } } var withUsers models.Tracker if err := h.db.Preload("Users").Where("guid = ?", t.GUID).First(&withUsers).Error; err != nil { c.JSON(http.StatusCreated, dto.TrackerDto{GUID: t.GUID, Name: t.Name}) return } c.JSON(http.StatusCreated, dto.MapTracker(withUsers)) } // POST /trackers/:guid/rename — rename tracker func (h *TrackersHandler) Rename(c *gin.Context) { guid := c.Param("guid") var t models.Tracker if err := h.db.Where("guid = ?", guid).First(&t).Error; err != nil { c.JSON(http.StatusNotFound, gin.H{"error": "tracker not found"}) return } var req dto.RenameTrackerDto if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } t.Name = req.Name if err := h.db.Save(&t).Error; err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "save failed"}) return } c.JSON(http.StatusCreated, dto.TrackerDto{GUID: t.GUID, Name: t.Name}) } // POST /trackers/:guid/set_users — replace full user list (admin) func (h *TrackersHandler) SetUsers(c *gin.Context) { guid := c.Param("guid") var t models.Tracker if err := h.db.Where("guid = ?", guid).First(&t).Error; err != nil { c.JSON(http.StatusNotFound, gin.H{"error": "tracker not found"}) return } var req dto.SetTrackerUsersDto if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } users := []models.User{} if len(req.UserIDs) > 0 { found, err := h.fetchUsers(req.UserIDs) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } users = found } if err := h.db.Model(&t).Association("Users").Clear(); err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "clear failed"}) return } if len(users) > 0 { if err := h.db.Model(&t).Association("Users").Append(&users); err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "link failed"}) return } } var withUsers models.Tracker _ = h.db.Preload("Users").Where("guid = ?", t.GUID).First(&withUsers).Error c.JSON(http.StatusCreated, dto.MapTracker(withUsers)) } // local helper (same as devices.go) func (h *TrackersHandler) fetchUsers(ids []uint) ([]models.User, error) { unique := make(map[uint]struct{}, len(ids)) clean := make([]uint, 0, len(ids)) for _, id := range ids { if id != 0 { if _, ok := unique[id]; !ok { unique[id] = struct{}{} clean = append(clean, id) } } } if len(clean) == 0 { return nil, nil } var users []models.User if err := h.db.Find(&users, clean).Error; err != nil { return nil, err } if len(users) != len(clean) { return nil, fmt.Errorf("some users not found") } return users, nil }