307 lines
8.9 KiB
Go
307 lines
8.9 KiB
Go
package handlers
|
|
|
|
import (
|
|
"fmt"
|
|
"net/http"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
"gorm.io/gorm"
|
|
|
|
"smoop-api/internal/dto"
|
|
"smoop-api/internal/models"
|
|
)
|
|
|
|
type DevicesHandler struct {
|
|
db *gorm.DB
|
|
}
|
|
|
|
func NewDevicesHandler(db *gorm.DB) *DevicesHandler { return &DevicesHandler{db: db} }
|
|
|
|
func (h *DevicesHandler) 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
|
|
}
|
|
|
|
// Get user context
|
|
userContext, exists := c.Get("user")
|
|
if !exists {
|
|
c.JSON(http.StatusUnauthorized, gin.H{"error": "unauthorized"})
|
|
return
|
|
}
|
|
|
|
user, ok := userContext.(UserContext)
|
|
if !ok {
|
|
c.JSON(http.StatusUnauthorized, gin.H{"error": "invalid user data"})
|
|
return
|
|
}
|
|
|
|
var total int64
|
|
var devs []models.Device
|
|
var err error
|
|
|
|
if user.Role == models.RoleAdmin {
|
|
// Admin user - show all devices
|
|
err = h.db.Model(&models.Device{}).Count(&total).Error
|
|
if err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "count query failed: " + err.Error()})
|
|
return
|
|
}
|
|
|
|
err = h.db.Preload("Users").Offset(offset).Limit(limit).Find(&devs).Error
|
|
} else {
|
|
err = h.db.Model(&models.Device{}).
|
|
Joins("INNER JOIN user_devices ON user_devices.id = devices.guid").
|
|
Where("user_devices.guid = ?", user.ID).
|
|
Count(&total).Error
|
|
|
|
if err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "count query failed: " + err.Error()})
|
|
return
|
|
}
|
|
|
|
err = h.db.Preload("Users").
|
|
Joins("INNER JOIN user_devices ON user_devices.id = devices.guid").
|
|
Where("user_devices.guid = ?", user.ID).
|
|
Offset(offset).Limit(limit).
|
|
Find(&devs).Error
|
|
}
|
|
|
|
if err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "query failed: " + err.Error()})
|
|
return
|
|
}
|
|
|
|
out := make([]dto.DeviceDto, 0, len(devs))
|
|
for _, d := range devs {
|
|
out = append(out, dto.MapDevice(d))
|
|
}
|
|
|
|
c.JSON(http.StatusOK, dto.DeviceListDto{Devices: out, Offset: offset, Limit: limit, Total: total})
|
|
}
|
|
|
|
func (h *DevicesHandler) Create(c *gin.Context) {
|
|
var req dto.CreateDeviceDto
|
|
if err := c.ShouldBindJSON(&req); err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
d := models.Device{GUID: req.GUID, Name: req.Name}
|
|
if err := h.db.Create(&d).Error; err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "device exists?"})
|
|
return
|
|
}
|
|
// Optional initial user assignments
|
|
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(&d).Association("Users").Append(&users); err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "link failed"})
|
|
return
|
|
}
|
|
}
|
|
// Return with users
|
|
var withUsers models.Device
|
|
if err := h.db.Preload("Users").Where("guid = ?", d.GUID).First(&withUsers).Error; err != nil {
|
|
c.JSON(http.StatusCreated, dto.DeviceDto{GUID: d.GUID, Name: d.Name})
|
|
return
|
|
}
|
|
c.JSON(http.StatusCreated, dto.MapDevice(withUsers))
|
|
}
|
|
|
|
func (h *DevicesHandler) Rename(c *gin.Context) {
|
|
guid := c.Param("guid")
|
|
var d models.Device
|
|
if err := h.db.Where("guid = ?", guid).First(&d).Error; err != nil {
|
|
c.JSON(http.StatusNotFound, gin.H{"error": "device not found"})
|
|
return
|
|
}
|
|
var req dto.RenameDeviceDto
|
|
if err := c.ShouldBindJSON(&req); err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
d.Name = req.Name
|
|
if err := h.db.Save(&d).Error; err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "save failed"})
|
|
return
|
|
}
|
|
c.JSON(http.StatusCreated, dto.DeviceDto{GUID: d.GUID, Name: d.Name})
|
|
}
|
|
|
|
func (h *DevicesHandler) AddToUser(c *gin.Context) {
|
|
guid := c.Param("guid")
|
|
var d models.Device
|
|
if err := h.db.Where("guid = ?", guid).First(&d).Error; err != nil {
|
|
c.JSON(http.StatusNotFound, gin.H{"error": "device not found"})
|
|
return
|
|
}
|
|
var req dto.EditDeviceToUserRelationDto
|
|
if err := c.ShouldBindJSON(&req); err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
ids := req.UserIDs
|
|
if req.UserID != 0 {
|
|
ids = append(ids, req.UserID)
|
|
}
|
|
if len(ids) == 0 {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "userIds or userId required"})
|
|
return
|
|
}
|
|
|
|
users, err := h.fetchUsers(ids)
|
|
if err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
if err := h.db.Model(&d).Association("Users").Append(&users); err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "link failed"})
|
|
return
|
|
}
|
|
var withUsers models.Device
|
|
_ = h.db.Preload("Users").Where("guid = ?", d.GUID).First(&withUsers).Error
|
|
c.JSON(http.StatusCreated, dto.MapDevice(withUsers))
|
|
}
|
|
|
|
// SetUsers replaces the users of a device with the provided list.
|
|
// Passing an empty list clears all assignments (covers the "no user assigned" case).
|
|
func (h *DevicesHandler) SetUsers(c *gin.Context) {
|
|
guid := c.Param("guid")
|
|
var d models.Device
|
|
if err := h.db.Where("guid = ?", guid).First(&d).Error; err != nil {
|
|
c.JSON(http.StatusNotFound, gin.H{"error": "device not found"})
|
|
return
|
|
}
|
|
var req dto.SetDeviceUsersDto
|
|
if err := c.ShouldBindJSON(&req); err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
// Load users (if any)
|
|
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
|
|
}
|
|
// Replace association: Clear() then Append(new)
|
|
if err := h.db.Model(&d).Association("Users").Clear(); err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "clear failed"})
|
|
return
|
|
}
|
|
if len(users) > 0 {
|
|
if err := h.db.Model(&d).Association("Users").Append(&users); err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "link failed"})
|
|
return
|
|
}
|
|
}
|
|
var withUsers models.Device
|
|
_ = h.db.Preload("Users").Where("guid = ?", d.GUID).First(&withUsers).Error
|
|
c.JSON(http.StatusCreated, dto.MapDevice(withUsers))
|
|
}
|
|
|
|
func (h *DevicesHandler) RemoveFromUser(c *gin.Context) {
|
|
guid := c.Param("guid")
|
|
var d models.Device
|
|
if err := h.db.Where("guid = ?", guid).First(&d).Error; err != nil {
|
|
c.JSON(http.StatusNotFound, gin.H{"error": "device not found"})
|
|
return
|
|
}
|
|
var req dto.RemoveDeviceUsersDto
|
|
_ = c.ShouldBindJSON(&req) // ignore error; we support query fallback
|
|
|
|
ids := make([]uint, 0, len(req.UserIDs)+1)
|
|
if req.UserID != 0 {
|
|
ids = append(ids, req.UserID)
|
|
}
|
|
if len(req.UserIDs) > 0 {
|
|
ids = append(ids, req.UserIDs...)
|
|
}
|
|
// query fallback
|
|
if len(ids) == 0 {
|
|
if q := strings.TrimSpace(c.Query("userId")); q != "" {
|
|
if n, err := strconv.Atoi(q); err == nil && n > 0 {
|
|
ids = append(ids, uint(n))
|
|
}
|
|
}
|
|
if q := strings.TrimSpace(c.Query("userIds")); q != "" {
|
|
for _, p := range strings.Split(q, ",") {
|
|
p = strings.TrimSpace(p)
|
|
if n, err := strconv.Atoi(p); err == nil && n > 0 {
|
|
ids = append(ids, uint(n))
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if len(ids) == 0 {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "userIds or userId required"})
|
|
return
|
|
}
|
|
|
|
users, err := h.fetchUsers(ids)
|
|
if err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
if len(users) == 0 {
|
|
c.JSON(http.StatusOK, dto.DeviceDto{GUID: d.GUID, Name: d.Name})
|
|
return
|
|
}
|
|
if err := h.db.Model(&d).Association("Users").Delete(&users); err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "unlink failed"})
|
|
return
|
|
}
|
|
var withUsers models.Device
|
|
_ = h.db.Preload("Users").Where("guid = ?", d.GUID).First(&withUsers).Error
|
|
c.JSON(http.StatusOK, dto.MapDevice(withUsers))
|
|
}
|
|
|
|
func (h *DevicesHandler) 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
|
|
}
|
|
|
|
func (h *DevicesHandler) ListCertsByDevice(c *gin.Context) {
|
|
guid := c.Param("guid")
|
|
var d models.Device
|
|
if err := h.db.Where("guid = ?", guid).First(&d).Error; err != nil {
|
|
c.JSON(http.StatusNotFound, gin.H{"error": "device not found"})
|
|
return
|
|
}
|
|
var list []models.DeviceCertificate
|
|
if err := h.db.Where("device_guid = ?", guid).Order("id desc").Find(&list).Error; err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "query failed"})
|
|
return
|
|
}
|
|
c.JSON(http.StatusOK, gin.H{"certs": list})
|
|
}
|