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:
@@ -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"))
|
||||
|
||||
Reference in New Issue
Block a user