避免 roles.write 绕过全部 can();定价读写挂 membership.plans 权限;去掉快捷封禁双路径并让 ban/unban 走 lifecycle。 Co-authored-by: Cursor <cursoragent@cursor.com>
210 lines
6.4 KiB
Go
210 lines
6.4 KiB
Go
package handler
|
|
|
|
import (
|
|
"errors"
|
|
"net/http"
|
|
"strconv"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
"github.com/google/uuid"
|
|
|
|
"github.com/yuxingu/digital-psychology/apps/api/internal/middleware"
|
|
"github.com/yuxingu/digital-psychology/apps/api/internal/service/admin"
|
|
"github.com/yuxingu/digital-psychology/apps/api/pkg/response"
|
|
)
|
|
|
|
func (h *AdminHandler) registerSystem(authed *gin.RouterGroup) {
|
|
authed.POST("/users/:id/ban", middleware.RequireAdminPermission(h.Svc, admin.PermUsersStatusWrite), h.BanUser)
|
|
authed.POST("/users/:id/unban", middleware.RequireAdminPermission(h.Svc, admin.PermUsersStatusWrite), h.UnbanUser)
|
|
authed.GET("/admins", h.ListAdmins)
|
|
authed.PATCH("/admins/:id", h.PatchAdmin)
|
|
authed.GET("/push-jobs", h.ListPushJobs)
|
|
authed.POST("/push-jobs", h.CreatePushJob)
|
|
authed.PATCH("/push-jobs/:id", h.PatchPushJob)
|
|
}
|
|
|
|
func (h *AdminHandler) BanUser(c *gin.Context) {
|
|
adminID, ok := middleware.AdminIDFromContext(c)
|
|
if !ok {
|
|
response.Fail(c, http.StatusUnauthorized, 40101, "admin auth required")
|
|
return
|
|
}
|
|
uid, err := uuid.Parse(c.Param("id"))
|
|
if err != nil {
|
|
response.Fail(c, http.StatusBadRequest, 40050, "invalid user id")
|
|
return
|
|
}
|
|
if err := h.Svc.BanUser(c.Request.Context(), adminID, uid); err != nil {
|
|
if errors.Is(err, admin.ErrUserNotFound) {
|
|
response.Fail(c, http.StatusNotFound, 40401, "user not found")
|
|
return
|
|
}
|
|
if errors.Is(err, admin.ErrReasonRequired) || errors.Is(err, admin.ErrInvalidStatusEdge) {
|
|
response.Fail(c, http.StatusBadRequest, 40000, err.Error())
|
|
return
|
|
}
|
|
response.Fail(c, http.StatusInternalServerError, 50040, "ban failed")
|
|
return
|
|
}
|
|
response.OK(c, gin.H{"ok": true})
|
|
}
|
|
|
|
func (h *AdminHandler) UnbanUser(c *gin.Context) {
|
|
adminID, ok := middleware.AdminIDFromContext(c)
|
|
if !ok {
|
|
response.Fail(c, http.StatusUnauthorized, 40101, "admin auth required")
|
|
return
|
|
}
|
|
uid, err := uuid.Parse(c.Param("id"))
|
|
if err != nil {
|
|
response.Fail(c, http.StatusBadRequest, 40050, "invalid user id")
|
|
return
|
|
}
|
|
if err := h.Svc.UnbanUser(c.Request.Context(), adminID, uid); err != nil {
|
|
if errors.Is(err, admin.ErrUserNotFound) {
|
|
response.Fail(c, http.StatusNotFound, 40401, "user not found")
|
|
return
|
|
}
|
|
if errors.Is(err, admin.ErrReasonRequired) || errors.Is(err, admin.ErrInvalidStatusEdge) {
|
|
response.Fail(c, http.StatusBadRequest, 40000, err.Error())
|
|
return
|
|
}
|
|
response.Fail(c, http.StatusInternalServerError, 50041, "unban failed")
|
|
return
|
|
}
|
|
response.OK(c, gin.H{"ok": true})
|
|
}
|
|
|
|
func (h *AdminHandler) ListAdmins(c *gin.Context) {
|
|
adminID, ok := middleware.AdminIDFromContext(c)
|
|
if !ok {
|
|
response.Fail(c, http.StatusUnauthorized, 40101, "admin auth required")
|
|
return
|
|
}
|
|
items, err := h.Svc.ListAdmins(c.Request.Context(), adminID)
|
|
if err != nil {
|
|
if errors.Is(err, admin.ErrForbidden) {
|
|
response.Fail(c, http.StatusForbidden, 40301, "forbidden")
|
|
return
|
|
}
|
|
response.Fail(c, http.StatusInternalServerError, 50042, "list admins failed")
|
|
return
|
|
}
|
|
response.OK(c, gin.H{"items": items})
|
|
}
|
|
|
|
func (h *AdminHandler) PatchAdmin(c *gin.Context) {
|
|
adminID, ok := middleware.AdminIDFromContext(c)
|
|
if !ok {
|
|
response.Fail(c, http.StatusUnauthorized, 40101, "admin auth required")
|
|
return
|
|
}
|
|
targetID, err := uuid.Parse(c.Param("id"))
|
|
if err != nil {
|
|
response.Fail(c, http.StatusBadRequest, 40051, "invalid admin id")
|
|
return
|
|
}
|
|
var body struct {
|
|
Role string `json:"role"`
|
|
}
|
|
if err := c.ShouldBindJSON(&body); err != nil || body.Role == "" {
|
|
response.Fail(c, http.StatusBadRequest, 40052, "role required")
|
|
return
|
|
}
|
|
if err := h.Svc.UpdateAdminRole(c.Request.Context(), adminID, targetID, body.Role); err != nil {
|
|
if errors.Is(err, admin.ErrForbidden) {
|
|
response.Fail(c, http.StatusForbidden, 40301, "forbidden")
|
|
return
|
|
}
|
|
if errors.Is(err, admin.ErrInvalidRole) {
|
|
response.Fail(c, http.StatusBadRequest, 40053, "invalid role")
|
|
return
|
|
}
|
|
if errors.Is(err, admin.ErrLastSuper) {
|
|
response.Fail(c, http.StatusConflict, 40901, "cannot demote last super")
|
|
return
|
|
}
|
|
if errors.Is(err, admin.ErrAdminNotFound) {
|
|
response.Fail(c, http.StatusNotFound, 40420, "admin not found")
|
|
return
|
|
}
|
|
response.Fail(c, http.StatusInternalServerError, 50043, "patch admin failed")
|
|
return
|
|
}
|
|
response.OK(c, gin.H{"ok": true})
|
|
}
|
|
|
|
func (h *AdminHandler) ListPushJobs(c *gin.Context) {
|
|
limit, _ := strconv.Atoi(c.DefaultQuery("limit", "20"))
|
|
offset, _ := strconv.Atoi(c.DefaultQuery("offset", "0"))
|
|
items, err := h.Svc.ListPushJobs(c.Request.Context(), limit, offset)
|
|
if err != nil {
|
|
response.Fail(c, http.StatusInternalServerError, 50044, "list push jobs failed")
|
|
return
|
|
}
|
|
response.OK(c, gin.H{"items": items})
|
|
}
|
|
|
|
func (h *AdminHandler) CreatePushJob(c *gin.Context) {
|
|
adminID, ok := middleware.AdminIDFromContext(c)
|
|
if !ok {
|
|
response.Fail(c, http.StatusUnauthorized, 40101, "admin auth required")
|
|
return
|
|
}
|
|
var body struct {
|
|
Title string `json:"title"`
|
|
Body string `json:"body"`
|
|
Audience string `json:"audience"`
|
|
}
|
|
if err := c.ShouldBindJSON(&body); err != nil || body.Title == "" {
|
|
response.Fail(c, http.StatusBadRequest, 40054, "title required")
|
|
return
|
|
}
|
|
job, err := h.Svc.CreatePushJob(c.Request.Context(), adminID, body.Title, body.Body, body.Audience)
|
|
if err != nil {
|
|
if errors.Is(err, admin.ErrInvalidPush) {
|
|
response.Fail(c, http.StatusBadRequest, 40055, "invalid push job")
|
|
return
|
|
}
|
|
response.Fail(c, http.StatusInternalServerError, 50045, "create push job failed")
|
|
return
|
|
}
|
|
response.OK(c, job)
|
|
}
|
|
|
|
func (h *AdminHandler) PatchPushJob(c *gin.Context) {
|
|
adminID, ok := middleware.AdminIDFromContext(c)
|
|
if !ok {
|
|
response.Fail(c, http.StatusUnauthorized, 40101, "admin auth required")
|
|
return
|
|
}
|
|
jobID, err := uuid.Parse(c.Param("id"))
|
|
if err != nil {
|
|
response.Fail(c, http.StatusBadRequest, 40056, "invalid job id")
|
|
return
|
|
}
|
|
var body struct {
|
|
Title *string `json:"title"`
|
|
Body *string `json:"body"`
|
|
Status *string `json:"status"`
|
|
}
|
|
if err := c.ShouldBindJSON(&body); err != nil {
|
|
response.Fail(c, http.StatusBadRequest, 40057, "invalid body")
|
|
return
|
|
}
|
|
job, err := h.Svc.UpdatePushJob(c.Request.Context(), adminID, jobID, body.Title, body.Body, body.Status)
|
|
if err != nil {
|
|
if errors.Is(err, admin.ErrPushNotFound) {
|
|
response.Fail(c, http.StatusNotFound, 40430, "push job not found")
|
|
return
|
|
}
|
|
if errors.Is(err, admin.ErrInvalidPush) {
|
|
response.Fail(c, http.StatusBadRequest, 40058, "invalid push job")
|
|
return
|
|
}
|
|
response.Fail(c, http.StatusInternalServerError, 50046, "patch push job failed")
|
|
return
|
|
}
|
|
response.OK(c, job)
|
|
}
|