package handler import ( "encoding/json" "errors" "net/http" "github.com/go-chi/chi/v5" "github.com/prabhupadasays/spsays-bff/internal/middleware" "github.com/prabhupadasays/spsays-bff/internal/rbac" "github.com/prabhupadasays/spsays-bff/internal/repository" "github.com/prabhupadasays/spsays-bff/internal/service" "github.com/prabhupadasays/spsays-bff/pkg/response" ) // StaffHandler serves /admin/me and the superadmin-only staff and role screens. type StaffHandler struct { svc *service.StaffService } func NewStaffHandler(svc *service.StaffService) *StaffHandler { return &StaffHandler{svc: svc} } func writeStaffError(w http.ResponseWriter, err error) { switch { case errors.Is(err, repository.ErrNotStaff), errors.Is(err, repository.ErrRoleNotFound), errors.Is(err, repository.ErrUserGone): response.Error(w, http.StatusNotFound, err.Error()) case errors.Is(err, service.ErrStaffSelf), errors.Is(err, service.ErrLastSuperadmin), errors.Is(err, service.ErrRoleSystem), errors.Is(err, repository.ErrRoleInUse), errors.Is(err, repository.ErrRoleKeyTaken): response.Error(w, http.StatusConflict, err.Error()) case errors.Is(err, service.ErrRoleBadPermission), errors.Is(err, service.ErrRoleName), errors.Is(err, service.ErrRoleNoPermissions): response.Error(w, http.StatusBadRequest, err.Error()) default: response.Error(w, http.StatusInternalServerError, "staff request failed") } } // Me — GET /admin/me. The caller's role and effective permissions, which the // panel uses to hide what they can't do. Hiding is a courtesy: every route // enforces its own permission regardless. func (h *StaffHandler) Me(w http.ResponseWriter, r *http.Request) { s := middleware.Staff(r.Context()) response.JSON(w, http.StatusOK, service.Me{ RoleKey: s.RoleKey, RoleName: s.RoleName, Permissions: s.Perms.List(), }) } // Catalog — GET /admin/permissions. What a custom role may be built from. func (h *StaffHandler) Catalog(w http.ResponseWriter, _ *http.Request) { response.JSON(w, http.StatusOK, rbac.Catalog) } func (h *StaffHandler) List(w http.ResponseWriter, r *http.Request) { list, err := h.svc.List(r.Context()) if err != nil { writeStaffError(w, err) return } response.JSON(w, http.StatusOK, list) } type grantReq struct { Handle string `json:"handle"` RoleID string `json:"role_id"` } // Grant — POST /admin/staff func (h *StaffHandler) Grant(w http.ResponseWriter, r *http.Request) { var req grantReq if err := json.NewDecoder(r.Body).Decode(&req); err != nil || req.Handle == "" || req.RoleID == "" { response.Error(w, http.StatusBadRequest, "handle and role_id are required") return } m, err := h.svc.Grant(r.Context(), middleware.UserID(r.Context()), req.Handle, req.RoleID) if err != nil { writeStaffError(w, err) return } response.JSON(w, http.StatusOK, m) } // ChangeRole — PATCH /admin/staff/{id} func (h *StaffHandler) ChangeRole(w http.ResponseWriter, r *http.Request) { var req struct { RoleID string `json:"role_id"` } if err := json.NewDecoder(r.Body).Decode(&req); err != nil || req.RoleID == "" { response.Error(w, http.StatusBadRequest, "role_id is required") return } m, err := h.svc.ChangeRole(r.Context(), middleware.UserID(r.Context()), chi.URLParam(r, "id"), req.RoleID) if err != nil { writeStaffError(w, err) return } response.JSON(w, http.StatusOK, m) } // Revoke — DELETE /admin/staff/{id} func (h *StaffHandler) Revoke(w http.ResponseWriter, r *http.Request) { if err := h.svc.Revoke(r.Context(), middleware.UserID(r.Context()), chi.URLParam(r, "id")); err != nil { writeStaffError(w, err) return } response.NoContent(w) } func (h *StaffHandler) setBlocked(w http.ResponseWriter, r *http.Request, blocked bool) { m, err := h.svc.SetBlocked(r.Context(), middleware.UserID(r.Context()), chi.URLParam(r, "id"), blocked) if err != nil { writeStaffError(w, err) return } response.JSON(w, http.StatusOK, m) } // Block — POST /admin/staff/{id}/block ; Unblock — DELETE the same. func (h *StaffHandler) Block(w http.ResponseWriter, r *http.Request) { h.setBlocked(w, r, true) } func (h *StaffHandler) Unblock(w http.ResponseWriter, r *http.Request) { h.setBlocked(w, r, false) } func (h *StaffHandler) Roles(w http.ResponseWriter, r *http.Request) { roles, err := h.svc.Roles(r.Context()) if err != nil { writeStaffError(w, err) return } response.JSON(w, http.StatusOK, roles) } type adminRoleReq struct { Name string `json:"name"` Description string `json:"description"` Permissions []string `json:"permissions"` } func (h *StaffHandler) CreateRole(w http.ResponseWriter, r *http.Request) { var req adminRoleReq if err := json.NewDecoder(r.Body).Decode(&req); err != nil { response.Error(w, http.StatusBadRequest, "invalid JSON body") return } ro, err := h.svc.CreateRole(r.Context(), req.Name, req.Description, req.Permissions) if err != nil { writeStaffError(w, err) return } response.JSON(w, http.StatusCreated, ro) } func (h *StaffHandler) UpdateRole(w http.ResponseWriter, r *http.Request) { var req adminRoleReq if err := json.NewDecoder(r.Body).Decode(&req); err != nil { response.Error(w, http.StatusBadRequest, "invalid JSON body") return } ro, err := h.svc.UpdateRole(r.Context(), chi.URLParam(r, "id"), req.Name, req.Description, req.Permissions) if err != nil { writeStaffError(w, err) return } response.JSON(w, http.StatusOK, ro) } func (h *StaffHandler) DeleteRole(w http.ResponseWriter, r *http.Request) { if err := h.svc.DeleteRole(r.Context(), chi.URLParam(r, "id")); err != nil { writeStaffError(w, err) return } response.NoContent(w) }