Files
NexaVPN/backend/internal/policy/handler.go
nessi cf65dc0e41 feat: add update and delete operations for users and policies in admin interface
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
2026-03-17 20:49:38 +01:00

121 lines
3.3 KiB
Go

package policy
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) {
items, err := h.service.List(r.Context())
if err != nil {
apiutil.Error(w, http.StatusInternalServerError, "policies_list_failed", "unable to list policies")
return
}
apiutil.JSON(w, http.StatusOK, items)
}
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
}
claims, ok := requestctx.ClaimsFromContext(r.Context())
if !ok {
apiutil.Error(w, http.StatusUnauthorized, "unauthorized", "missing auth claims")
return
}
item, err := h.service.Create(r.Context(), claims.UserID, input)
if err != nil {
apiutil.Error(w, http.StatusInternalServerError, "policy_create_failed", "unable to create policy")
return
}
_ = h.audit.Record(r.Context(), audit.Entry{
ActorUserID: &claims.UserID,
EntityType: "policy",
EntityID: &item.ID,
EventType: "admin.policy.created",
Status: "success",
Message: "admin created policy",
Metadata: map[string]any{
"name": item.Name,
},
})
apiutil.JSON(w, http.StatusCreated, item)
}
func (h *Handler) Update(w http.ResponseWriter, r *http.Request) {
policyID, err := uuid.Parse(chi.URLParam(r, "id"))
if err != nil {
apiutil.Error(w, http.StatusBadRequest, "invalid_policy_id", "invalid policy 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
}
item, err := h.service.Update(r.Context(), policyID, input)
if err != nil {
apiutil.Error(w, http.StatusInternalServerError, "policy_update_failed", "unable to update policy")
return
}
if claims, ok := requestctx.ClaimsFromContext(r.Context()); ok {
_ = h.audit.Record(r.Context(), audit.Entry{
ActorUserID: &claims.UserID,
EntityType: "policy",
EntityID: &policyID,
EventType: "admin.policy.updated",
Status: "success",
Message: "admin updated policy",
})
}
apiutil.JSON(w, http.StatusOK, item)
}
func (h *Handler) Delete(w http.ResponseWriter, r *http.Request) {
policyID, err := uuid.Parse(chi.URLParam(r, "id"))
if err != nil {
apiutil.Error(w, http.StatusBadRequest, "invalid_policy_id", "invalid policy id")
return
}
if err := h.service.Delete(r.Context(), policyID); err != nil {
apiutil.Error(w, http.StatusInternalServerError, "policy_delete_failed", "unable to delete policy")
return
}
if claims, ok := requestctx.ClaimsFromContext(r.Context()); ok {
_ = h.audit.Record(r.Context(), audit.Entry{
ActorUserID: &claims.UserID,
EntityType: "policy",
EntityID: &policyID,
EventType: "admin.policy.deleted",
Status: "success",
Message: "admin deleted policy",
})
}
apiutil.JSON(w, http.StatusOK, map[string]any{"ok": true})
}