219 lines
5.9 KiB
Go
219 lines
5.9 KiB
Go
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.First(&u, id).Error; err != nil {
|
|
c.JSON(http.StatusNotFound, gin.H{"error": "user not found"})
|
|
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)
|
|
}
|
|
|
|
// GET /users/:id — fetch any user's profile by id
|
|
func (h *UsersHandler) GetProfile(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.First(&u, id).Error; err != nil {
|
|
c.JSON(http.StatusNotFound, gin.H{"error": "user not found"})
|
|
return
|
|
}
|
|
c.JSON(http.StatusOK, dto.MapUser(u))
|
|
}
|
|
|
|
// PUT /users/:id (admin) — update username, password and/or role
|
|
func (h *UsersHandler) Update(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.First(&u, id).Error; err != nil {
|
|
c.JSON(http.StatusNotFound, gin.H{"error": "user not found"})
|
|
return
|
|
}
|
|
|
|
var req dto.UpdateUserDto
|
|
if err := c.ShouldBindJSON(&req); err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
|
|
updated := false
|
|
|
|
// --- Update username ---
|
|
if strings.TrimSpace(req.Username) != "" {
|
|
name := strings.TrimSpace(req.Username)
|
|
if name == "" {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "username cannot be empty"})
|
|
return
|
|
}
|
|
u.Username = name
|
|
updated = true
|
|
}
|
|
|
|
// --- Update password ---
|
|
if req.Password != "" {
|
|
if len(req.Password) < 4 {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "password too short"})
|
|
return
|
|
}
|
|
hash, err := crypto.Hash(req.Password, crypto.DefaultArgon2)
|
|
if err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "hash error"})
|
|
return
|
|
}
|
|
u.Password = hash
|
|
updated = true
|
|
}
|
|
|
|
// --- Update role ---
|
|
if strings.TrimSpace(req.Role) != "" {
|
|
role := strings.ToLower(strings.TrimSpace(req.Role))
|
|
if role != "admin" && role != "user" {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid role"})
|
|
return
|
|
}
|
|
u.Role = models.Role(role)
|
|
updated = true
|
|
}
|
|
|
|
if !updated {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "nothing to update"})
|
|
return
|
|
}
|
|
|
|
if err := h.db.Save(&u).Error; err != nil {
|
|
// detect 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": "update failed"})
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, dto.MapUser(u))
|
|
}
|