feat: 添加用户优惠券列表接口及相关数据结构

This commit is contained in:
2025-12-30 17:53:27 +08:00
parent dbfb08ed37
commit 2c633dac0f
7 changed files with 208 additions and 2 deletions

View File

@@ -286,6 +286,32 @@ components:
realname:
type: string
UserCouponItem:
type: object
properties:
id:
type: string
coupon_id:
type: string
title:
type: string
description:
type: string
type:
type: string
enum: [fix_amount, discount]
value:
type: integer
min_order_amount:
type: integer
start_at:
type: string
end_at:
type: string
status:
type: string
enum: [unused, used, expired]
# --- Upload ---
UploadResult:
type: object
@@ -787,6 +813,84 @@ paths:
time:
type: string
/me/coupons:
get:
summary: List user coupons
parameters:
- name: status
in: query
schema:
type: string
enum: [unused, used, expired]
responses:
'200':
content:
application/json:
schema:
type: array
items:
$ref: '#/components/schemas/UserCouponItem'
# ============================
# Storage (Presigned)
# ============================
/storage/{key}:
put:
summary: Upload file (Presigned)
tags: [Storage]
parameters:
- name: key
in: path
required: true
schema:
type: string
- name: expires
in: query
required: true
schema:
type: string
- name: sign
in: query
required: true
schema:
type: string
requestBody:
content:
application/octet-stream:
schema:
type: string
format: binary
responses:
'200':
description: Upload successful
get:
summary: Download file (Presigned)
tags: [Storage]
parameters:
- name: key
in: path
required: true
schema:
type: string
- name: expires
in: query
required: true
schema:
type: string
- name: sign
in: query
required: true
schema:
type: string
responses:
'200':
description: Download file
content:
application/octet-stream:
schema:
type: string
format: binary
# ============================
# Transaction
# ============================
@@ -806,6 +910,8 @@ paths:
quantity:
type: integer
default: 1
user_coupon_id:
type: string
responses:
'200':
content:
@@ -864,6 +970,24 @@ paths:
type: string
enum: [unpaid, paid, completed]
/webhook/payment/notify:
post:
summary: Payment Webhook
tags: [Transaction]
requestBody:
content:
application/json:
schema:
type: object
properties:
order_id:
type: string
external_id:
type: string
responses:
'200':
description: Success
# ============================
# Creator Center
# ============================

View File

@@ -0,0 +1,14 @@
package dto
type UserCouponItem struct {
ID string `json:"id"`
CouponID string `json:"coupon_id"`
Title string `json:"title"`
Description string `json:"description"`
Type string `json:"type"`
Value int64 `json:"value"`
MinOrderAmount int64 `json:"min_order_amount"`
StartAt string `json:"start_at"`
EndAt string `json:"end_at"`
Status string `json:"status"`
}

View File

@@ -237,6 +237,11 @@ func (r *Routes) Register(router fiber.Router) {
router.Get("/v1/me"[len(r.Path()):], DataFunc0(
r.user.Me,
))
r.log.Debugf("Registering route: Get /v1/me/coupons -> user.MyCoupons")
router.Get("/v1/me/coupons"[len(r.Path()):], DataFunc1(
r.user.MyCoupons,
QueryParam[string]("status"),
))
r.log.Debugf("Registering route: Get /v1/me/favorites -> user.Favorites")
router.Get("/v1/me/favorites"[len(r.Path()):], DataFunc0(
r.user.Favorites,

View File

@@ -241,3 +241,18 @@ func (u *User) Following(ctx fiber.Ctx) ([]dto.TenantProfile, error) {
func (u *User) Notifications(ctx fiber.Ctx, typeArg string, page int) (*requests.Pager, error) {
return services.Notification.List(ctx.Context(), page, typeArg)
}
// List my coupons
//
// @Router /v1/me/coupons [get]
// @Summary List coupons
// @Description List my coupons
// @Tags UserCenter
// @Accept json
// @Produce json
// @Param status query string false "Status (unused, used, expired)"
// @Success 200 {array} dto.UserCouponItem
// @Bind status query
func (u *User) MyCoupons(ctx fiber.Ctx, status string) ([]dto.UserCouponItem, error) {
return services.Coupon.ListUserCoupons(ctx.Context(), status)
}

View File

@@ -5,12 +5,61 @@ import (
"time"
"quyun/v2/app/errorx"
coupon_dto "quyun/v2/app/http/v1/dto"
"quyun/v2/database/models"
"quyun/v2/pkg/consts"
"github.com/spf13/cast"
)
// @provider
type coupon struct{}
func (s *coupon) ListUserCoupons(ctx context.Context, status string) ([]coupon_dto.UserCouponItem, error) {
userID := ctx.Value(consts.CtxKeyUser)
if userID == nil {
return nil, errorx.ErrUnauthorized
}
uid := cast.ToInt64(userID)
tbl, q := models.UserCouponQuery.QueryContext(ctx)
q = q.Where(tbl.UserID.Eq(uid))
if status != "" {
q = q.Where(tbl.Status.Eq(status))
}
list, err := q.Order(tbl.CreatedAt.Desc()).Find()
if err != nil {
return nil, errorx.ErrDatabaseError.WithCause(err)
}
var res []coupon_dto.UserCouponItem
for _, v := range list {
c, _ := models.CouponQuery.WithContext(ctx).Where(models.CouponQuery.ID.Eq(v.CouponID)).First()
item := coupon_dto.UserCouponItem{
ID: cast.ToString(v.ID),
CouponID: cast.ToString(v.CouponID),
Status: v.Status,
}
if c != nil {
item.Title = c.Title
item.Description = c.Description
item.Type = c.Type
item.Value = c.Value
item.MinOrderAmount = c.MinOrderAmount
if !c.StartAt.IsZero() {
item.StartAt = c.StartAt.Format(time.RFC3339)
}
if !c.EndAt.IsZero() {
item.EndAt = c.EndAt.Format(time.RFC3339)
}
}
res = append(res, item)
}
return res, nil
}
// Validate checks if a coupon can be used for an order and returns the discount amount
func (s *coupon) Validate(ctx context.Context, userID, userCouponID, amount int64) (int64, error) {
uc, err := models.UserCouponQuery.WithContext(ctx).Where(models.UserCouponQuery.ID.Eq(userCouponID)).First()

View File

@@ -108,4 +108,4 @@ func (s *CouponTestSuite) Test_CouponFlow() {
So(ucReload.OrderID, ShouldEqual, oid)
})
})
}
}

View File

@@ -157,7 +157,6 @@ func (s *order) Create(ctx context.Context, form *transaction_dto.OrderCreateFor
return nil
})
if err != nil {
if _, ok := err.(*errorx.AppError); ok {
return nil, err