package handler import ( "github.com/gin-gonic/gin" "github.com/sundynix/pets-be/internal/middleware" "github.com/sundynix/pets-be/internal/model" "github.com/sundynix/pets-be/internal/service" appjwt "github.com/sundynix/pets-be/pkg/jwt" "github.com/sundynix/pets-be/pkg/response" ) type adminLoginReq struct { Username string `json:"username"` Password string `json:"password"` } // AdminLogin POST /api/admin/login func (h *Handler) AdminLogin(c *gin.Context) { var req adminLoginReq if err := c.ShouldBindJSON(&req); err != nil { response.FailParams(c, err.Error()) return } admin, err := h.svc.AdminLogin(req.Username, req.Password) if err != nil { response.Fail(c, 40100, "账号或密码错误") return } data, err := h.adminTokens(admin) if err != nil { response.FailErr(c, err) return } response.OK(c, data) } // adminTokens 签发后台的「access + refresh」令牌对。access token 2 小时。 func (h *Handler) adminTokens(admin *model.Admin) (gin.H, error) { token, err := h.jwt.Generate(admin.ID, appjwt.KindAdmin, admin.Username, admin.Role) if err != nil { return nil, err } refresh, refreshExp, err := h.svc.IssueRefreshToken(string(appjwt.KindAdmin), admin.ID) if err != nil { return nil, err } return gin.H{ "token": token, "refresh_token": refresh, "expires_in": h.jwt.ExpireSeconds(), "refresh_expires_at": refreshExp, "admin": admin, }, nil } // AdminRefresh POST /api/admin/refresh func (h *Handler) AdminRefresh(c *gin.Context) { var req refreshReq _ = c.ShouldBindJSON(&req) adminID, err := h.svc.ConsumeRefreshToken(req.RefreshToken, string(appjwt.KindAdmin)) if err != nil { response.Fail(c, 40100, err.Error()) return } admin, err := h.svc.GetAdmin(adminID) if err != nil { response.Fail(c, 40100, "登录已失效,请重新登录") return } if admin.Disabled { response.Fail(c, 40300, "账号已被禁用") return } data, err := h.adminTokens(admin) if err != nil { response.FailErr(c, err) return } response.OK(c, data) } // AdminLogout POST /api/admin/logout 作废当前刷新令牌 func (h *Handler) AdminLogout(c *gin.Context) { var req refreshReq _ = c.ShouldBindJSON(&req) h.svc.RevokeRefreshToken(req.RefreshToken, string(appjwt.KindAdmin)) response.OK(c, gin.H{"ok": true}) } // AdminMe GET /api/admin/me func (h *Handler) AdminMe(c *gin.Context) { admin, err := h.svc.GetAdmin(middleware.AdminID(c)) if err != nil { respondErr(c, err) return } response.OK(c, admin) } // AdminStats GET /api/admin/stats func (h *Handler) AdminStats(c *gin.Context) { stats, err := h.svc.Stats() if err != nil { response.FailErr(c, err) return } response.OK(c, stats) } type adminListReq struct { response.PageQuery Keyword string `form:"keyword"` Status string `form:"status"` Type string `form:"type"` } // AdminListUsers GET /api/admin/users func (h *Handler) AdminListUsers(c *gin.Context) { var req adminListReq _ = c.ShouldBindQuery(&req) req.Normalize() users, total, err := h.svc.ListUsers(req.Keyword, req.Type, req.Offset(), req.Limit()) if err != nil { response.FailErr(c, err) return } response.OK(c, response.NewPage(users, total, req.PageQuery)) } type disableReq struct { Disabled bool `json:"disabled"` } // AdminSetUserDisabled PUT /api/admin/users/:id/disabled func (h *Handler) AdminSetUserDisabled(c *gin.Context) { var req disableReq if err := c.ShouldBindJSON(&req); err != nil { response.FailParams(c, err.Error()) return } if err := h.svc.SetUserDisabled(idParam(c, "id"), req.Disabled); err != nil { response.FailErr(c, err) return } response.OK(c, gin.H{"ok": true}) } // AdminListPets GET /api/admin/pets func (h *Handler) AdminListPets(c *gin.Context) { var req adminListReq _ = c.ShouldBindQuery(&req) req.Normalize() pets, total, err := h.svc.ListPetsAdmin(req.Offset(), req.Limit()) if err != nil { response.FailErr(c, err) return } response.OK(c, response.NewPage(pets, total, req.PageQuery)) } // AdminListPosts GET /api/admin/posts func (h *Handler) AdminListPosts(c *gin.Context) { var req adminListReq _ = c.ShouldBindQuery(&req) req.Normalize() posts, total, err := h.svc.ListPostsAdmin(req.Status, req.Offset(), req.Limit()) if err != nil { response.FailErr(c, err) return } response.OK(c, response.NewPage(posts, total, req.PageQuery)) } type postStatusReq struct { Status string `json:"status"` } // AdminSetPostStatus PUT /api/admin/posts/:id/status func (h *Handler) AdminSetPostStatus(c *gin.Context) { var req postStatusReq if err := c.ShouldBindJSON(&req); err != nil { response.FailParams(c, err.Error()) return } if err := h.svc.SetPostStatus(idParam(c, "id"), req.Status); err != nil { respondErr(c, err) return } response.OK(c, gin.H{"ok": true}) } // AdminListComments GET /api/admin/comments func (h *Handler) AdminListComments(c *gin.Context) { var req adminListReq _ = c.ShouldBindQuery(&req) req.Normalize() comments, total, err := h.svc.ListCommentsAdmin(req.Offset(), req.Limit()) if err != nil { response.FailErr(c, err) return } response.OK(c, response.NewPage(comments, total, req.PageQuery)) } // AdminDeleteComment DELETE /api/admin/comments/:id func (h *Handler) AdminDeleteComment(c *gin.Context) { if err := h.svc.DeleteCommentAdmin(idParam(c, "id")); err != nil { response.FailErr(c, err) return } response.OK(c, gin.H{"ok": true}) } // AdminListArticles GET /api/admin/articles func (h *Handler) AdminListArticles(c *gin.Context) { var req adminListReq _ = c.ShouldBindQuery(&req) req.Normalize() articles, total, err := h.svc.ListArticlesAdmin(req.Offset(), req.Limit()) if err != nil { response.FailErr(c, err) return } response.OK(c, response.NewPage(articles, total, req.PageQuery)) } type articleReq struct { ID string `json:"id"` Icon string `json:"icon"` Title string `json:"title"` Description string `json:"description"` Content string `json:"content"` Category string `json:"category"` RelatedSheetType string `json:"related_sheet_type"` Published bool `json:"published"` } // AdminSaveArticle POST /api/admin/articles(id>0 为更新) func (h *Handler) AdminSaveArticle(c *gin.Context) { var req articleReq if err := c.ShouldBindJSON(&req); err != nil { response.FailParams(c, err.Error()) return } article := &model.Article{ Icon: req.Icon, Title: req.Title, Description: req.Description, Content: req.Content, Category: req.Category, RelatedSheetType: req.RelatedSheetType, Published: req.Published, } article.ID = req.ID if err := h.svc.SaveArticle(article); err != nil { response.FailErr(c, err) return } response.OK(c, article) } // AdminDeleteArticle DELETE /api/admin/articles/:id func (h *Handler) AdminDeleteArticle(c *gin.Context) { if err := h.svc.DeleteArticle(idParam(c, "id")); err != nil { respondErr(c, err) return } response.OK(c, gin.H{"ok": true}) } // AdminListPro GET /api/admin/memberships func (h *Handler) AdminListPro(c *gin.Context) { var req adminListReq _ = c.ShouldBindQuery(&req) req.Normalize() list, total, err := h.svc.ListProAdmin(req.Offset(), req.Limit()) if err != nil { response.FailErr(c, err) return } response.OK(c, response.NewPage(list, total, req.PageQuery)) } // AdminCareTemplateMeta GET /api/admin/care-templates/meta // 给后台下拉用:物种、阶段、可选关联记录/弹层类型、提醒类型 func (h *Handler) AdminCareTemplateMeta(c *gin.Context) { response.OK(c, gin.H{ "species": []gin.H{ {"key": model.SpeciesCat, "label": "猫猫"}, {"key": model.SpeciesDog, "label": "狗狗"}, }, "stages": service.CareStages, "task_sheets": []string{"", "weight", "poop", "food", "symptom", "vaccine", "medicine"}, "plan_sheets": []string{"", "weight", "vaccine", "medicine", "report", "taskDetail"}, "reminder_types": []string{"vaccine", "deworm", "weight", "monthlyReport"}, "priorities": []string{"", "重要"}, }) } // AdminGetCareTemplate GET /api/admin/care-templates?species=&stage= func (h *Handler) AdminGetCareTemplate(c *gin.Context) { data, err := h.svc.GetCareTemplate(c.Query("species"), c.Query("stage")) if err != nil { response.FailParams(c, err.Error()) return } response.OK(c, data) } type careTemplateReq struct { Species string `json:"species"` Stage string `json:"stage"` Items []model.CareTemplateItem `json:"items"` } // AdminSaveCareTemplate PUT /api/admin/care-templates func (h *Handler) AdminSaveCareTemplate(c *gin.Context) { var req careTemplateReq if err := c.ShouldBindJSON(&req); err != nil { response.FailParams(c, err.Error()) return } if err := h.svc.SaveCareTemplate(req.Species, req.Stage, req.Items); err != nil { response.FailErr(c, err) return } data, _ := h.svc.GetCareTemplate(req.Species, req.Stage) response.OK(c, data) } type careGenReq struct { Species string `json:"species"` Stage string `json:"stage"` } // AdminGenerateCareTemplate POST /api/admin/care-templates/generate // AI 生成草稿(不落库),未开启 AI 时回退内置默认 func (h *Handler) AdminGenerateCareTemplate(c *gin.Context) { var req careGenReq if err := c.ShouldBindJSON(&req); err != nil { response.FailParams(c, err.Error()) return } items, err := h.svc.GenerateCareTemplate(req.Species, req.Stage) if err != nil { response.FailParams(c, err.Error()) return } response.OK(c, gin.H{"species": req.Species, "stage": req.Stage, "items": items}) } // AdminGetCommunityBot GET /api/admin/community-bot func (h *Handler) AdminGetCommunityBot(c *gin.Context) { response.OK(c, h.svc.GetCommunityBotConfig()) } // AdminSaveCommunityBot PUT /api/admin/community-bot func (h *Handler) AdminSaveCommunityBot(c *gin.Context) { var req model.CommunityBotConfig if err := c.ShouldBindJSON(&req); err != nil { response.FailParams(c, err.Error()) return } saved, err := h.svc.SaveCommunityBotConfig(req) if err != nil { response.FailErr(c, err) return } response.OK(c, saved) } type communityGenReq struct { Count int `json:"count"` } // AdminGenerateCommunityPosts POST /api/admin/community-bot/generate func (h *Handler) AdminGenerateCommunityPosts(c *gin.Context) { var req communityGenReq if err := c.ShouldBindJSON(&req); err != nil { response.FailParams(c, err.Error()) return } if req.Count <= 0 { req.Count = 1 } if req.Count > 30 { req.Count = 30 } made, err := h.svc.GenerateCommunityPosts(req.Count) if err != nil { response.FailErr(c, err) return } response.OK(c, gin.H{"made": made}) } // AdminGetAIQuota GET /api/admin/ai-quota func (h *Handler) AdminGetAIQuota(c *gin.Context) { response.OK(c, h.svc.GetAIQuotaConfig()) } // AdminSaveAIQuota PUT /api/admin/ai-quota func (h *Handler) AdminSaveAIQuota(c *gin.Context) { var req model.AIQuotaConfig if err := c.ShouldBindJSON(&req); err != nil { response.FailParams(c, err.Error()) return } cfg, err := h.svc.SaveAIQuotaConfig(req) if err != nil { response.FailErr(c, err) return } response.OK(c, cfg) }