package handlers import ( "net/http" "strconv" "strings" "github.com/gin-gonic/gin" "gorm.io/gorm" "smoop-api/internal/crypto" "smoop-api/internal/dto" "smoop-api/internal/models" ) type UsersHandler struct { db *gorm.DB } func NewUsersHandler(db *gorm.DB) *UsersHandler { return &UsersHandler{db: db} } func (h *UsersHandler) Profile(c *gin.Context) { claims := MustClaims(c) uid := ClaimUserID(claims) var u models.User if err := h.db.First(&u, uid).Error; err != nil { c.JSON(http.StatusNotFound, gin.H{"error": "user not found"}) return } c.JSON(http.StatusOK, dto.MapUser(u)) } func (h *UsersHandler) SetRole(c *gin.Context) { idStr := c.Param("id") uid, _ := strconv.Atoi(idStr) var u models.User if err := h.db.First(&u, uid).Error; err != nil { c.JSON(http.StatusNotFound, gin.H{"error": "user not found"}) return } var req dto.UserRoleDto if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } u.Role = req.Role if err := h.db.Save(&u).Error; err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "save failed"}) return } c.JSON(http.StatusCreated, dto.MapUser(u)) } func (h *UsersHandler) List(c *gin.Context) { var users []models.User if err := h.db.Order("id asc").Find(&users).Error; err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "query failed"}) return } out := make([]dto.UserDto, 0, len(users)) for _, u := range users { out = append(out, dto.MapUser(u)) } c.JSON(http.StatusOK, out) } // POST /users/create (admin) — create user with given role func (h *UsersHandler) Create(c *gin.Context) { var req dto.CreateUserDto if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } role := models.Role(strings.ToLower(req.Role)) if role != models.RoleAdmin && role != models.RoleUser { c.JSON(http.StatusBadRequest, gin.H{"error": "invalid role"}) return } hash, err := crypto.Hash(req.Password, crypto.DefaultArgon2) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "hash error"}) return } u := models.User{Username: req.Username, Password: hash, Role: role} if err := h.db.Create(&u).Error; err != nil { // hint duplicate username e := strings.ToLower(err.Error()) if strings.Contains(e, "duplicate") || strings.Contains(e, "unique") || strings.Contains(e, "exists") { c.JSON(http.StatusBadRequest, gin.H{"error": "username already exists"}) return } c.JSON(http.StatusInternalServerError, gin.H{"error": "create failed"}) return } c.JSON(http.StatusCreated, dto.MapUser(u)) } // DELETE /users/:id (admin) — delete user and clear device relations func (h *UsersHandler) Delete(c *gin.Context) { idStr := c.Param("id") id, _ := strconv.Atoi(idStr) if id <= 0 { c.JSON(http.StatusBadRequest, gin.H{"error": "invalid id"}) return } var u models.User if err := h.db.Preload("Devices").First(&u, id).Error; err != nil { c.JSON(http.StatusNotFound, gin.H{"error": "user not found"}) return } // optional safeguard: prevent self-delete; uncomment if desired // if ClaimUserID(MustClaims(c)) == u.ID { c.JSON(http.StatusBadRequest, gin.H{"error":"cannot delete yourself"}); return } if err := h.db.Transaction(func(tx *gorm.DB) error { if err := tx.Model(&u).Association("Devices").Clear(); err != nil { return err } return tx.Delete(&u).Error }); err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "delete failed"}) return } c.Status(http.StatusNoContent) }