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) }