173 lines
5.5 KiB
Go
173 lines
5.5 KiB
Go
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)
|
|
}
|