feat(ECR-009): Ops-D order filters, plan display prices, refund status

LOOP-RUN-002 sample: admin order multi-filter, membership display
price catalog, read-only refund_status. No real payment or RBAC.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
jackyu66git
2026-08-07 02:45:32 +08:00
co-authored by Cursor
parent 7ab9add5dd
commit ca3b13554c
37 changed files with 2169 additions and 25 deletions
+49 -1
View File
@@ -9,6 +9,7 @@ import (
"github.com/google/uuid"
"github.com/yuxingu/digital-psychology/apps/api/internal/middleware"
"github.com/yuxingu/digital-psychology/apps/api/internal/repository"
"github.com/yuxingu/digital-psychology/apps/api/internal/service/admin"
"github.com/yuxingu/digital-psychology/apps/api/internal/service/analytics"
"github.com/yuxingu/digital-psychology/apps/api/pkg/response"
@@ -35,6 +36,8 @@ func (h *AdminHandler) Register(api *gin.RouterGroup) {
authed.POST("/users/:id/membership/grant", h.GrantMembership)
authed.POST("/users/:id/ask-quota/grant", h.GrantAskQuota)
authed.GET("/orders", h.ListOrders)
authed.GET("/membership/plan-prices", h.ListPlanPrices)
authed.PUT("/membership/plan-prices", h.PutPlanPrices)
authed.GET("/audit-logs", h.ListAudit)
authed.GET("/analytics/overview", h.AnalyticsOverview)
authed.GET("/analytics/pages", h.AnalyticsPages)
@@ -189,7 +192,16 @@ func (h *AdminHandler) GrantAskQuota(c *gin.Context) {
func (h *AdminHandler) ListOrders(c *gin.Context) {
limit, _ := strconv.Atoi(c.DefaultQuery("limit", "20"))
offset, _ := strconv.Atoi(c.DefaultQuery("offset", "0"))
items, err := h.Svc.ListOrders(c.Request.Context(), limit, offset)
f := admin.OrderFilterFromQuery(
c.Query("user_id"),
c.Query("status"),
c.Query("kind"),
c.Query("from"),
c.Query("to"),
limit,
offset,
)
items, err := h.Svc.ListOrders(c.Request.Context(), f)
if err != nil {
response.Fail(c, http.StatusInternalServerError, 50015, "list orders failed")
return
@@ -197,6 +209,42 @@ func (h *AdminHandler) ListOrders(c *gin.Context) {
response.OK(c, gin.H{"items": items})
}
func (h *AdminHandler) ListPlanPrices(c *gin.Context) {
items, err := h.Svc.ListPlanPrices(c.Request.Context())
if err != nil {
response.Fail(c, http.StatusInternalServerError, 50017, "list plan prices failed")
return
}
response.OK(c, gin.H{"items": items})
}
func (h *AdminHandler) PutPlanPrices(c *gin.Context) {
adminID, ok := middleware.AdminIDFromContext(c)
if !ok {
response.Fail(c, http.StatusUnauthorized, 40101, "admin auth required")
return
}
var body struct {
Items []struct {
Plan string `json:"plan"`
DisplayCents int `json:"display_cents"`
} `json:"items"`
}
if err := c.ShouldBindJSON(&body); err != nil || len(body.Items) == 0 {
response.Fail(c, http.StatusBadRequest, 40040, "items required")
return
}
prices := make([]repository.PlanPrice, 0, len(body.Items))
for _, it := range body.Items {
prices = append(prices, repository.PlanPrice{Plan: it.Plan, DisplayCents: it.DisplayCents})
}
if err := h.Svc.UpsertPlanPrices(c.Request.Context(), adminID, prices); err != nil {
response.Fail(c, http.StatusBadRequest, 40041, "upsert plan prices failed")
return
}
response.OK(c, gin.H{"ok": true})
}
func (h *AdminHandler) ListAudit(c *gin.Context) {
limit, _ := strconv.Atoi(c.DefaultQuery("limit", "20"))
offset, _ := strconv.Atoi(c.DefaultQuery("offset", "0"))