Add updateUser and deleteUser API client methods with PATCH and DELETE endpoints. Add updatePolicy and deletePolicy API client methods. Add email field to User type. Add Actions column to users and policies tables with Edit and Delete buttons. Implement inline edit forms for users and policies with state management for editing mode. Add update and delete mutations with query invalidation on success. Add error notices
166 lines
4.8 KiB
Go
166 lines
4.8 KiB
Go
package user
|
|
|
|
import (
|
|
"encoding/json"
|
|
"net/http"
|
|
|
|
"github.com/go-chi/chi/v5"
|
|
"github.com/google/uuid"
|
|
|
|
"nexavpn/backend/internal/apiutil"
|
|
"nexavpn/backend/internal/audit"
|
|
"nexavpn/backend/internal/requestctx"
|
|
)
|
|
|
|
type Handler struct {
|
|
service *Service
|
|
audit *audit.Service
|
|
}
|
|
|
|
func NewHandler(service *Service, auditService *audit.Service) *Handler {
|
|
return &Handler{service: service, audit: auditService}
|
|
}
|
|
|
|
func (h *Handler) List(w http.ResponseWriter, r *http.Request) {
|
|
users, err := h.service.List(r.Context())
|
|
if err != nil {
|
|
apiutil.Error(w, http.StatusInternalServerError, "users_list_failed", "unable to list users")
|
|
return
|
|
}
|
|
|
|
apiutil.JSON(w, http.StatusOK, users)
|
|
}
|
|
|
|
func (h *Handler) Create(w http.ResponseWriter, r *http.Request) {
|
|
var input CreateRequest
|
|
if err := json.NewDecoder(r.Body).Decode(&input); err != nil {
|
|
apiutil.Error(w, http.StatusBadRequest, "invalid_json", "invalid request body")
|
|
return
|
|
}
|
|
if input.Role == "" {
|
|
input.Role = "user"
|
|
}
|
|
|
|
created, err := h.service.Create(r.Context(), input)
|
|
if err != nil {
|
|
apiutil.Error(w, http.StatusInternalServerError, "user_create_failed", "unable to create user")
|
|
return
|
|
}
|
|
|
|
if claims, ok := requestctx.ClaimsFromContext(r.Context()); ok {
|
|
_ = h.audit.Record(r.Context(), audit.Entry{
|
|
ActorUserID: &claims.UserID,
|
|
EntityType: "user",
|
|
EntityID: &created.ID,
|
|
EventType: "admin.user.created",
|
|
Status: "success",
|
|
Message: "admin created user",
|
|
Metadata: map[string]any{
|
|
"username": created.Username,
|
|
},
|
|
})
|
|
}
|
|
apiutil.JSON(w, http.StatusCreated, created)
|
|
}
|
|
|
|
func (h *Handler) Disable(w http.ResponseWriter, r *http.Request) {
|
|
targetID, err := uuid.Parse(chi.URLParam(r, "id"))
|
|
if err != nil {
|
|
apiutil.Error(w, http.StatusBadRequest, "invalid_user_id", "invalid user id")
|
|
return
|
|
}
|
|
if err := h.service.SetActive(r.Context(), targetID.String(), false); err != nil {
|
|
apiutil.Error(w, http.StatusBadRequest, "user_disable_failed", "unable to disable user")
|
|
return
|
|
}
|
|
if claims, ok := requestctx.ClaimsFromContext(r.Context()); ok {
|
|
_ = h.audit.Record(r.Context(), audit.Entry{
|
|
ActorUserID: &claims.UserID,
|
|
EntityType: "user",
|
|
EntityID: &targetID,
|
|
EventType: "admin.user.disabled",
|
|
Status: "success",
|
|
Message: "admin disabled user",
|
|
})
|
|
}
|
|
apiutil.JSON(w, http.StatusOK, map[string]any{"ok": true})
|
|
}
|
|
|
|
func (h *Handler) Update(w http.ResponseWriter, r *http.Request) {
|
|
targetID, err := uuid.Parse(chi.URLParam(r, "id"))
|
|
if err != nil {
|
|
apiutil.Error(w, http.StatusBadRequest, "invalid_user_id", "invalid user id")
|
|
return
|
|
}
|
|
|
|
var input UpdateRequest
|
|
if err := json.NewDecoder(r.Body).Decode(&input); err != nil {
|
|
apiutil.Error(w, http.StatusBadRequest, "invalid_json", "invalid request body")
|
|
return
|
|
}
|
|
|
|
updated, err := h.service.Update(r.Context(), targetID.String(), input)
|
|
if err != nil {
|
|
apiutil.Error(w, http.StatusInternalServerError, "user_update_failed", "unable to update user")
|
|
return
|
|
}
|
|
|
|
if claims, ok := requestctx.ClaimsFromContext(r.Context()); ok {
|
|
_ = h.audit.Record(r.Context(), audit.Entry{
|
|
ActorUserID: &claims.UserID,
|
|
EntityType: "user",
|
|
EntityID: &targetID,
|
|
EventType: "admin.user.updated",
|
|
Status: "success",
|
|
Message: "admin updated user",
|
|
})
|
|
}
|
|
apiutil.JSON(w, http.StatusOK, updated)
|
|
}
|
|
|
|
func (h *Handler) Delete(w http.ResponseWriter, r *http.Request) {
|
|
targetID, err := uuid.Parse(chi.URLParam(r, "id"))
|
|
if err != nil {
|
|
apiutil.Error(w, http.StatusBadRequest, "invalid_user_id", "invalid user id")
|
|
return
|
|
}
|
|
if err := h.service.Delete(r.Context(), targetID.String()); err != nil {
|
|
apiutil.Error(w, http.StatusInternalServerError, "user_delete_failed", "unable to delete user")
|
|
return
|
|
}
|
|
if claims, ok := requestctx.ClaimsFromContext(r.Context()); ok {
|
|
_ = h.audit.Record(r.Context(), audit.Entry{
|
|
ActorUserID: &claims.UserID,
|
|
EntityType: "user",
|
|
EntityID: &targetID,
|
|
EventType: "admin.user.deleted",
|
|
Status: "success",
|
|
Message: "admin deleted user",
|
|
})
|
|
}
|
|
apiutil.JSON(w, http.StatusOK, map[string]any{"ok": true})
|
|
}
|
|
|
|
func (h *Handler) Enable(w http.ResponseWriter, r *http.Request) {
|
|
targetID, err := uuid.Parse(chi.URLParam(r, "id"))
|
|
if err != nil {
|
|
apiutil.Error(w, http.StatusBadRequest, "invalid_user_id", "invalid user id")
|
|
return
|
|
}
|
|
if err := h.service.SetActive(r.Context(), targetID.String(), true); err != nil {
|
|
apiutil.Error(w, http.StatusBadRequest, "user_enable_failed", "unable to enable user")
|
|
return
|
|
}
|
|
if claims, ok := requestctx.ClaimsFromContext(r.Context()); ok {
|
|
_ = h.audit.Record(r.Context(), audit.Entry{
|
|
ActorUserID: &claims.UserID,
|
|
EntityType: "user",
|
|
EntityID: &targetID,
|
|
EventType: "admin.user.enabled",
|
|
Status: "success",
|
|
Message: "admin enabled user",
|
|
})
|
|
}
|
|
apiutil.JSON(w, http.StatusOK, map[string]any{"ok": true})
|
|
}
|