promo_handler.go 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211
  1. package admin
  2. import (
  3. "strconv"
  4. "strings"
  5. "time"
  6. "github.com/Wei-Shaw/sub2api/internal/handler/dto"
  7. "github.com/Wei-Shaw/sub2api/internal/pkg/pagination"
  8. "github.com/Wei-Shaw/sub2api/internal/pkg/response"
  9. "github.com/Wei-Shaw/sub2api/internal/service"
  10. "github.com/gin-gonic/gin"
  11. )
  12. // PromoHandler handles admin promo code management
  13. type PromoHandler struct {
  14. promoService *service.PromoService
  15. }
  16. // NewPromoHandler creates a new admin promo handler
  17. func NewPromoHandler(promoService *service.PromoService) *PromoHandler {
  18. return &PromoHandler{
  19. promoService: promoService,
  20. }
  21. }
  22. // CreatePromoCodeRequest represents create promo code request
  23. type CreatePromoCodeRequest struct {
  24. Code string `json:"code"` // 可选,为空则自动生成
  25. BonusAmount float64 `json:"bonus_amount" binding:"required,min=0"` // 赠送余额
  26. MaxUses int `json:"max_uses" binding:"min=0"` // 最大使用次数,0=无限
  27. ExpiresAt *int64 `json:"expires_at"` // 过期时间戳(秒)
  28. Notes string `json:"notes"` // 备注
  29. }
  30. // UpdatePromoCodeRequest represents update promo code request
  31. type UpdatePromoCodeRequest struct {
  32. Code *string `json:"code"`
  33. BonusAmount *float64 `json:"bonus_amount" binding:"omitempty,min=0"`
  34. MaxUses *int `json:"max_uses" binding:"omitempty,min=0"`
  35. Status *string `json:"status" binding:"omitempty,oneof=active disabled"`
  36. ExpiresAt *int64 `json:"expires_at"`
  37. Notes *string `json:"notes"`
  38. }
  39. // List handles listing all promo codes with pagination
  40. // GET /api/v1/admin/promo-codes
  41. func (h *PromoHandler) List(c *gin.Context) {
  42. page, pageSize := response.ParsePagination(c)
  43. status := c.Query("status")
  44. search := strings.TrimSpace(c.Query("search"))
  45. if len(search) > 100 {
  46. search = search[:100]
  47. }
  48. params := pagination.PaginationParams{
  49. Page: page,
  50. PageSize: pageSize,
  51. SortBy: c.DefaultQuery("sort_by", "created_at"),
  52. SortOrder: c.DefaultQuery("sort_order", "desc"),
  53. }
  54. codes, paginationResult, err := h.promoService.List(c.Request.Context(), params, status, search)
  55. if err != nil {
  56. response.ErrorFrom(c, err)
  57. return
  58. }
  59. out := make([]dto.PromoCode, 0, len(codes))
  60. for i := range codes {
  61. out = append(out, *dto.PromoCodeFromService(&codes[i]))
  62. }
  63. response.Paginated(c, out, paginationResult.Total, page, pageSize)
  64. }
  65. // GetByID handles getting a promo code by ID
  66. // GET /api/v1/admin/promo-codes/:id
  67. func (h *PromoHandler) GetByID(c *gin.Context) {
  68. codeID, err := strconv.ParseInt(c.Param("id"), 10, 64)
  69. if err != nil {
  70. response.BadRequest(c, "Invalid promo code ID")
  71. return
  72. }
  73. code, err := h.promoService.GetByID(c.Request.Context(), codeID)
  74. if err != nil {
  75. response.ErrorFrom(c, err)
  76. return
  77. }
  78. response.Success(c, dto.PromoCodeFromService(code))
  79. }
  80. // Create handles creating a new promo code
  81. // POST /api/v1/admin/promo-codes
  82. func (h *PromoHandler) Create(c *gin.Context) {
  83. var req CreatePromoCodeRequest
  84. if err := c.ShouldBindJSON(&req); err != nil {
  85. response.BadRequest(c, "Invalid request: "+err.Error())
  86. return
  87. }
  88. input := &service.CreatePromoCodeInput{
  89. Code: req.Code,
  90. BonusAmount: req.BonusAmount,
  91. MaxUses: req.MaxUses,
  92. Notes: req.Notes,
  93. }
  94. if req.ExpiresAt != nil {
  95. t := time.Unix(*req.ExpiresAt, 0)
  96. input.ExpiresAt = &t
  97. }
  98. code, err := h.promoService.Create(c.Request.Context(), input)
  99. if err != nil {
  100. response.ErrorFrom(c, err)
  101. return
  102. }
  103. response.Success(c, dto.PromoCodeFromService(code))
  104. }
  105. // Update handles updating a promo code
  106. // PUT /api/v1/admin/promo-codes/:id
  107. func (h *PromoHandler) Update(c *gin.Context) {
  108. codeID, err := strconv.ParseInt(c.Param("id"), 10, 64)
  109. if err != nil {
  110. response.BadRequest(c, "Invalid promo code ID")
  111. return
  112. }
  113. var req UpdatePromoCodeRequest
  114. if err := c.ShouldBindJSON(&req); err != nil {
  115. response.BadRequest(c, "Invalid request: "+err.Error())
  116. return
  117. }
  118. input := &service.UpdatePromoCodeInput{
  119. Code: req.Code,
  120. BonusAmount: req.BonusAmount,
  121. MaxUses: req.MaxUses,
  122. Status: req.Status,
  123. Notes: req.Notes,
  124. }
  125. if req.ExpiresAt != nil {
  126. if *req.ExpiresAt == 0 {
  127. // 0 表示清除过期时间
  128. input.ExpiresAt = &time.Time{}
  129. } else {
  130. t := time.Unix(*req.ExpiresAt, 0)
  131. input.ExpiresAt = &t
  132. }
  133. }
  134. code, err := h.promoService.Update(c.Request.Context(), codeID, input)
  135. if err != nil {
  136. response.ErrorFrom(c, err)
  137. return
  138. }
  139. response.Success(c, dto.PromoCodeFromService(code))
  140. }
  141. // Delete handles deleting a promo code
  142. // DELETE /api/v1/admin/promo-codes/:id
  143. func (h *PromoHandler) Delete(c *gin.Context) {
  144. codeID, err := strconv.ParseInt(c.Param("id"), 10, 64)
  145. if err != nil {
  146. response.BadRequest(c, "Invalid promo code ID")
  147. return
  148. }
  149. err = h.promoService.Delete(c.Request.Context(), codeID)
  150. if err != nil {
  151. response.ErrorFrom(c, err)
  152. return
  153. }
  154. response.Success(c, gin.H{"message": "Promo code deleted successfully"})
  155. }
  156. // GetUsages handles getting usage records for a promo code
  157. // GET /api/v1/admin/promo-codes/:id/usages
  158. func (h *PromoHandler) GetUsages(c *gin.Context) {
  159. codeID, err := strconv.ParseInt(c.Param("id"), 10, 64)
  160. if err != nil {
  161. response.BadRequest(c, "Invalid promo code ID")
  162. return
  163. }
  164. page, pageSize := response.ParsePagination(c)
  165. params := pagination.PaginationParams{
  166. Page: page,
  167. PageSize: pageSize,
  168. }
  169. usages, paginationResult, err := h.promoService.ListUsages(c.Request.Context(), codeID, params)
  170. if err != nil {
  171. response.ErrorFrom(c, err)
  172. return
  173. }
  174. out := make([]dto.PromoCodeUsage, 0, len(usages))
  175. for i := range usages {
  176. out = append(out, *dto.PromoCodeUsageFromService(&usages[i]))
  177. }
  178. response.Paginated(c, out, paginationResult.Total, page, pageSize)
  179. }