diff --git a/management-ui/index.html b/management-ui/index.html index dde16aa..c821ffc 100644 --- a/management-ui/index.html +++ b/management-ui/index.html @@ -4,7 +4,7 @@ - Vite + Vue + TS + Snoop
diff --git a/management-ui/src/customcompometns/AdminUserDropdonw.vue b/management-ui/src/customcompometns/AdminUserDropdonw.vue index 4ad8cfc..26079d6 100644 --- a/management-ui/src/customcompometns/AdminUserDropdonw.vue +++ b/management-ui/src/customcompometns/AdminUserDropdonw.vue @@ -1,44 +1,69 @@ + \ No newline at end of file diff --git a/management-ui/src/customcompometns/DeleteUserDialog.vue b/management-ui/src/customcompometns/DeleteUserDialog.vue index 414a6ff..c56fbc9 100644 --- a/management-ui/src/customcompometns/DeleteUserDialog.vue +++ b/management-ui/src/customcompometns/DeleteUserDialog.vue @@ -12,10 +12,8 @@ import { import { defineProps, defineEmits, type PropType } from 'vue' const props = defineProps({ - modelValue: { - type: Boolean as PropType, - required: true, - }, + modelValue: { type: Boolean as PropType, required: true }, + loading: { type: Boolean as PropType, default: false }, }) const emit = defineEmits<{ (e: 'update:modelValue', v: boolean): void @@ -36,10 +34,12 @@ const emit = defineEmits<{ - Cancel + Cancel Delete + > + {{ props.loading ? 'Deleting…' : 'Delete' }} diff --git a/management-ui/src/customcompometns/Navbar.vue b/management-ui/src/customcompometns/Navbar.vue index 91e80ab..fd7141a 100644 --- a/management-ui/src/customcompometns/Navbar.vue +++ b/management-ui/src/customcompometns/Navbar.vue @@ -1,57 +1,73 @@ \ No newline at end of file diff --git a/server/internal/handlers/users.go b/server/internal/handlers/users.go index bfb7b92..ac959d0 100644 --- a/server/internal/handlers/users.go +++ b/server/internal/handlers/users.go @@ -96,3 +96,30 @@ func (h *UsersHandler) Create(c *gin.Context) { } 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) +} diff --git a/server/internal/router/router.go b/server/internal/router/router.go index f3c3408..3bb05ac 100644 --- a/server/internal/router/router.go +++ b/server/internal/router/router.go @@ -41,6 +41,7 @@ func Build(db *gorm.DB, minio *minio.Client, cfg *config.Config) *gin.Engine { r.POST("/users/:id/set_role", authMW, adminOnly, usersH.SetRole) r.GET("/users", authMW, adminOnly, usersH.List) r.POST("/users/create", authMW, adminOnly, usersH.Create) + r.DELETE("/users/:id", authMW, adminOnly, usersH.Delete) r.GET("/devices", authMW, middleware.DeviceAccessFilter(), devH.List) r.POST("/devices/create", authMW, devH.Create)