210 lines
5.6 KiB
Go
210 lines
5.6 KiB
Go
|
|
package admin
|
|||
|
|
|
|||
|
|
import (
|
|||
|
|
"strconv"
|
|||
|
|
"strings"
|
|||
|
|
"time"
|
|||
|
|
|
|||
|
|
"github.com/Wei-Shaw/sub2api/internal/handler/dto"
|
|||
|
|
"github.com/Wei-Shaw/sub2api/internal/pkg/pagination"
|
|||
|
|
"github.com/Wei-Shaw/sub2api/internal/pkg/response"
|
|||
|
|
"github.com/Wei-Shaw/sub2api/internal/service"
|
|||
|
|
|
|||
|
|
"github.com/gin-gonic/gin"
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
// PromoHandler handles admin promo code management
|
|||
|
|
type PromoHandler struct {
|
|||
|
|
promoService *service.PromoService
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// NewPromoHandler creates a new admin promo handler
|
|||
|
|
func NewPromoHandler(promoService *service.PromoService) *PromoHandler {
|
|||
|
|
return &PromoHandler{
|
|||
|
|
promoService: promoService,
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// CreatePromoCodeRequest represents create promo code request
|
|||
|
|
type CreatePromoCodeRequest struct {
|
|||
|
|
Code string `json:"code"` // 可选,为空则自动生成
|
|||
|
|
BonusAmount float64 `json:"bonus_amount" binding:"required,min=0"` // 赠送余额
|
|||
|
|
MaxUses int `json:"max_uses" binding:"min=0"` // 最大使用次数,0=无限
|
|||
|
|
ExpiresAt *int64 `json:"expires_at"` // 过期时间戳(秒)
|
|||
|
|
Notes string `json:"notes"` // 备注
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// UpdatePromoCodeRequest represents update promo code request
|
|||
|
|
type UpdatePromoCodeRequest struct {
|
|||
|
|
Code *string `json:"code"`
|
|||
|
|
BonusAmount *float64 `json:"bonus_amount" binding:"omitempty,min=0"`
|
|||
|
|
MaxUses *int `json:"max_uses" binding:"omitempty,min=0"`
|
|||
|
|
Status *string `json:"status" binding:"omitempty,oneof=active disabled"`
|
|||
|
|
ExpiresAt *int64 `json:"expires_at"`
|
|||
|
|
Notes *string `json:"notes"`
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// List handles listing all promo codes with pagination
|
|||
|
|
// GET /api/v1/admin/promo-codes
|
|||
|
|
func (h *PromoHandler) List(c *gin.Context) {
|
|||
|
|
page, pageSize := response.ParsePagination(c)
|
|||
|
|
status := c.Query("status")
|
|||
|
|
search := strings.TrimSpace(c.Query("search"))
|
|||
|
|
if len(search) > 100 {
|
|||
|
|
search = search[:100]
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
params := pagination.PaginationParams{
|
|||
|
|
Page: page,
|
|||
|
|
PageSize: pageSize,
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
codes, paginationResult, err := h.promoService.List(c.Request.Context(), params, status, search)
|
|||
|
|
if err != nil {
|
|||
|
|
response.ErrorFrom(c, err)
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
out := make([]dto.PromoCode, 0, len(codes))
|
|||
|
|
for i := range codes {
|
|||
|
|
out = append(out, *dto.PromoCodeFromService(&codes[i]))
|
|||
|
|
}
|
|||
|
|
response.Paginated(c, out, paginationResult.Total, page, pageSize)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// GetByID handles getting a promo code by ID
|
|||
|
|
// GET /api/v1/admin/promo-codes/:id
|
|||
|
|
func (h *PromoHandler) GetByID(c *gin.Context) {
|
|||
|
|
codeID, err := strconv.ParseInt(c.Param("id"), 10, 64)
|
|||
|
|
if err != nil {
|
|||
|
|
response.BadRequest(c, "Invalid promo code ID")
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
code, err := h.promoService.GetByID(c.Request.Context(), codeID)
|
|||
|
|
if err != nil {
|
|||
|
|
response.ErrorFrom(c, err)
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
response.Success(c, dto.PromoCodeFromService(code))
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// Create handles creating a new promo code
|
|||
|
|
// POST /api/v1/admin/promo-codes
|
|||
|
|
func (h *PromoHandler) Create(c *gin.Context) {
|
|||
|
|
var req CreatePromoCodeRequest
|
|||
|
|
if err := c.ShouldBindJSON(&req); err != nil {
|
|||
|
|
response.BadRequest(c, "Invalid request: "+err.Error())
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
input := &service.CreatePromoCodeInput{
|
|||
|
|
Code: req.Code,
|
|||
|
|
BonusAmount: req.BonusAmount,
|
|||
|
|
MaxUses: req.MaxUses,
|
|||
|
|
Notes: req.Notes,
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if req.ExpiresAt != nil {
|
|||
|
|
t := time.Unix(*req.ExpiresAt, 0)
|
|||
|
|
input.ExpiresAt = &t
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
code, err := h.promoService.Create(c.Request.Context(), input)
|
|||
|
|
if err != nil {
|
|||
|
|
response.ErrorFrom(c, err)
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
response.Success(c, dto.PromoCodeFromService(code))
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// Update handles updating a promo code
|
|||
|
|
// PUT /api/v1/admin/promo-codes/:id
|
|||
|
|
func (h *PromoHandler) Update(c *gin.Context) {
|
|||
|
|
codeID, err := strconv.ParseInt(c.Param("id"), 10, 64)
|
|||
|
|
if err != nil {
|
|||
|
|
response.BadRequest(c, "Invalid promo code ID")
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
var req UpdatePromoCodeRequest
|
|||
|
|
if err := c.ShouldBindJSON(&req); err != nil {
|
|||
|
|
response.BadRequest(c, "Invalid request: "+err.Error())
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
input := &service.UpdatePromoCodeInput{
|
|||
|
|
Code: req.Code,
|
|||
|
|
BonusAmount: req.BonusAmount,
|
|||
|
|
MaxUses: req.MaxUses,
|
|||
|
|
Status: req.Status,
|
|||
|
|
Notes: req.Notes,
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if req.ExpiresAt != nil {
|
|||
|
|
if *req.ExpiresAt == 0 {
|
|||
|
|
// 0 表示清除过期时间
|
|||
|
|
input.ExpiresAt = nil
|
|||
|
|
} else {
|
|||
|
|
t := time.Unix(*req.ExpiresAt, 0)
|
|||
|
|
input.ExpiresAt = &t
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
code, err := h.promoService.Update(c.Request.Context(), codeID, input)
|
|||
|
|
if err != nil {
|
|||
|
|
response.ErrorFrom(c, err)
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
response.Success(c, dto.PromoCodeFromService(code))
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// Delete handles deleting a promo code
|
|||
|
|
// DELETE /api/v1/admin/promo-codes/:id
|
|||
|
|
func (h *PromoHandler) Delete(c *gin.Context) {
|
|||
|
|
codeID, err := strconv.ParseInt(c.Param("id"), 10, 64)
|
|||
|
|
if err != nil {
|
|||
|
|
response.BadRequest(c, "Invalid promo code ID")
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
err = h.promoService.Delete(c.Request.Context(), codeID)
|
|||
|
|
if err != nil {
|
|||
|
|
response.ErrorFrom(c, err)
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
response.Success(c, gin.H{"message": "Promo code deleted successfully"})
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// GetUsages handles getting usage records for a promo code
|
|||
|
|
// GET /api/v1/admin/promo-codes/:id/usages
|
|||
|
|
func (h *PromoHandler) GetUsages(c *gin.Context) {
|
|||
|
|
codeID, err := strconv.ParseInt(c.Param("id"), 10, 64)
|
|||
|
|
if err != nil {
|
|||
|
|
response.BadRequest(c, "Invalid promo code ID")
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
page, pageSize := response.ParsePagination(c)
|
|||
|
|
params := pagination.PaginationParams{
|
|||
|
|
Page: page,
|
|||
|
|
PageSize: pageSize,
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
usages, paginationResult, err := h.promoService.ListUsages(c.Request.Context(), codeID, params)
|
|||
|
|
if err != nil {
|
|||
|
|
response.ErrorFrom(c, err)
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
out := make([]dto.PromoCodeUsage, 0, len(usages))
|
|||
|
|
for i := range usages {
|
|||
|
|
out = append(out, *dto.PromoCodeUsageFromService(&usages[i]))
|
|||
|
|
}
|
|||
|
|
response.Paginated(c, out, paginationResult.Total, page, pageSize)
|
|||
|
|
}
|