feat: 重构内容列表接口,使用过滤器结构体简化参数传递;更新相关服务和测试用例
This commit is contained in:
@@ -25,17 +25,12 @@ type Content struct{}
|
||||
// @Param sort query string false "Sort order" Enums(latest, hot, price_asc)
|
||||
// @Param page query int false "Page number"
|
||||
// @Success 200 {object} requests.Pager{items=[]dto.ContentItem}
|
||||
// @Bind keyword query
|
||||
// @Bind genre query
|
||||
// @Bind tenantId query
|
||||
// @Bind sort query
|
||||
// @Bind page query
|
||||
// @Bind filter query
|
||||
func (c *Content) List(
|
||||
ctx fiber.Ctx,
|
||||
keyword, genre, tenantId, sort string,
|
||||
page int,
|
||||
filter *dto.ContentListFilter,
|
||||
) (*requests.Pager, error) {
|
||||
return services.Content.List(ctx.Context(), keyword, genre, tenantId, sort, page)
|
||||
return services.Content.List(ctx.Context(), filter)
|
||||
}
|
||||
|
||||
// Get content detail
|
||||
|
||||
@@ -1,5 +1,15 @@
|
||||
package dto
|
||||
|
||||
import "quyun/v2/app/requests"
|
||||
|
||||
type ContentListFilter struct {
|
||||
requests.Pagination
|
||||
Keyword string `query:"keyword"`
|
||||
Genre string `query:"genre"`
|
||||
TenantID string `query:"tenantId"`
|
||||
Sort string `query:"sort"`
|
||||
}
|
||||
|
||||
type ContentItem struct {
|
||||
ID string `json:"id"`
|
||||
Title string `json:"title"`
|
||||
|
||||
@@ -69,13 +69,9 @@ func (r *Routes) Register(router fiber.Router) {
|
||||
))
|
||||
// Register routes for controller: Content
|
||||
r.log.Debugf("Registering route: Get /v1/contents -> content.List")
|
||||
router.Get("/v1/contents"[len(r.Path()):], DataFunc5(
|
||||
router.Get("/v1/contents"[len(r.Path()):], DataFunc1(
|
||||
r.content.List,
|
||||
QueryParam[string]("keyword"),
|
||||
QueryParam[string]("genre"),
|
||||
QueryParam[string]("tenantId"),
|
||||
QueryParam[string]("sort"),
|
||||
QueryParam[int]("page"),
|
||||
Query[dto.ContentListFilter]("filter"),
|
||||
))
|
||||
r.log.Debugf("Registering route: Get /v1/contents/:id -> content.Get")
|
||||
router.Get("/v1/contents/:id"[len(r.Path()):], DataFunc1(
|
||||
|
||||
@@ -56,7 +56,7 @@ func (s *common) Upload(ctx context.Context, file *multipart.FileHeader, typeArg
|
||||
Provider: "mock",
|
||||
Bucket: "default",
|
||||
ObjectKey: objectKey,
|
||||
Meta: types.NewJSONType(fields.MediaAssetMeta{
|
||||
Meta: types.NewJSONType(fields.MediaAssetMeta{
|
||||
Size: file.Size,
|
||||
// MimeType?
|
||||
}),
|
||||
|
||||
@@ -18,19 +18,19 @@ import (
|
||||
// @provider
|
||||
type content struct{}
|
||||
|
||||
func (s *content) List(ctx context.Context, keyword, genre, tenantId, sort string, page int) (*requests.Pager, error) {
|
||||
func (s *content) List(ctx context.Context, filter *content_dto.ContentListFilter) (*requests.Pager, error) {
|
||||
tbl, q := models.ContentQuery.QueryContext(ctx)
|
||||
|
||||
// Filters
|
||||
q = q.Where(tbl.Status.Eq(consts.ContentStatusPublished))
|
||||
if keyword != "" {
|
||||
q = q.Where(tbl.Title.Like("%" + keyword + "%"))
|
||||
if filter.Keyword != "" {
|
||||
q = q.Where(tbl.Title.Like("%" + filter.Keyword + "%"))
|
||||
}
|
||||
if genre != "" {
|
||||
q = q.Where(tbl.Genre.Eq(genre))
|
||||
if filter.Genre != "" {
|
||||
q = q.Where(tbl.Genre.Eq(filter.Genre))
|
||||
}
|
||||
if tenantId != "" {
|
||||
tid := cast.ToInt64(tenantId)
|
||||
if filter.TenantID != "" {
|
||||
tid := cast.ToInt64(filter.TenantID)
|
||||
q = q.Where(tbl.TenantID.Eq(tid))
|
||||
}
|
||||
|
||||
@@ -38,7 +38,7 @@ func (s *content) List(ctx context.Context, keyword, genre, tenantId, sort strin
|
||||
q = q.Preload(tbl.Author)
|
||||
|
||||
// Sort
|
||||
switch sort {
|
||||
switch filter.Sort {
|
||||
case "hot":
|
||||
q = q.Order(tbl.Views.Desc())
|
||||
case "price_asc":
|
||||
@@ -48,13 +48,14 @@ func (s *content) List(ctx context.Context, keyword, genre, tenantId, sort strin
|
||||
}
|
||||
|
||||
// Pagination
|
||||
p := requests.Pagination{Page: int64(page), Limit: 10}
|
||||
// Use embedded pagination directly
|
||||
filter.Pagination.Format()
|
||||
total, err := q.Count()
|
||||
if err != nil {
|
||||
return nil, errorx.ErrDatabaseError.WithCause(err)
|
||||
}
|
||||
|
||||
list, err := q.Offset(int(p.Offset())).Limit(int(p.Limit)).Find()
|
||||
list, err := q.Offset(int(filter.Pagination.Offset())).Limit(int(filter.Pagination.Limit)).Find()
|
||||
if err != nil {
|
||||
return nil, errorx.ErrDatabaseError.WithCause(err)
|
||||
}
|
||||
@@ -66,12 +67,9 @@ func (s *content) List(ctx context.Context, keyword, genre, tenantId, sort strin
|
||||
}
|
||||
|
||||
return &requests.Pager{
|
||||
Pagination: requests.Pagination{
|
||||
Page: p.Page,
|
||||
Limit: p.Limit,
|
||||
},
|
||||
Total: total,
|
||||
Items: data,
|
||||
Pagination: filter.Pagination,
|
||||
Total: total,
|
||||
Items: data,
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
|
||||
"quyun/v2/app/commands/testx"
|
||||
content_dto "quyun/v2/app/http/v1/dto"
|
||||
"quyun/v2/app/requests"
|
||||
"quyun/v2/database"
|
||||
"quyun/v2/database/models"
|
||||
"quyun/v2/pkg/consts"
|
||||
@@ -65,7 +66,14 @@ func (s *ContentTestSuite) Test_List() {
|
||||
models.ContentQuery.WithContext(ctx).Create(c1, c2)
|
||||
|
||||
Convey("should list only published contents", func() {
|
||||
res, err := Content.List(ctx, "", "", "1", "", 1)
|
||||
filter := &content_dto.ContentListFilter{
|
||||
TenantID: "1",
|
||||
Pagination: requests.Pagination{
|
||||
Page: 1,
|
||||
Limit: 10,
|
||||
},
|
||||
}
|
||||
res, err := Content.List(ctx, filter)
|
||||
So(err, ShouldBeNil)
|
||||
So(res.Total, ShouldEqual, 1)
|
||||
items := res.Items.([]content_dto.ContentItem)
|
||||
|
||||
@@ -98,9 +98,9 @@ func (s *order) Create(ctx context.Context, form *transaction_dto.OrderCreateFor
|
||||
Status: consts.OrderStatusCreated,
|
||||
Currency: consts.Currency(price.Currency), // price.Currency is consts.Currency in DB? Yes.
|
||||
AmountOriginal: price.PriceAmount,
|
||||
AmountDiscount: 0, // Calculate discount if needed
|
||||
AmountPaid: price.PriceAmount, // Expected to pay
|
||||
IdempotencyKey: uuid.NewString(), // Should be from client ideally
|
||||
AmountDiscount: 0, // Calculate discount if needed
|
||||
AmountPaid: price.PriceAmount, // Expected to pay
|
||||
IdempotencyKey: uuid.NewString(), // Should be from client ideally
|
||||
Snapshot: types.NewJSONType(fields.OrdersSnapshot{}), // Populate details
|
||||
}
|
||||
|
||||
@@ -159,7 +159,6 @@ func (s *order) payWithBalance(ctx context.Context, o *models.Order) (*transacti
|
||||
info, err := tx.User.WithContext(ctx).
|
||||
Where(tx.User.ID.Eq(o.UserID), tx.User.Balance.Gte(o.AmountPaid)).
|
||||
Update(tx.User.Balance, gorm.Expr("balance - ?", o.AmountPaid))
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -218,7 +217,6 @@ func (s *order) payWithBalance(ctx context.Context, o *models.Order) (*transacti
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
if _, ok := err.(*errorx.AppError); ok {
|
||||
return nil, err
|
||||
|
||||
@@ -64,10 +64,10 @@ func (s *tenant) GetPublicProfile(ctx context.Context, id string) (*tenant_dto.T
|
||||
return &tenant_dto.TenantProfile{
|
||||
ID: cast.ToString(t.ID),
|
||||
Name: t.Name,
|
||||
Avatar: "", // From config
|
||||
Cover: "", // From config
|
||||
Bio: "", // From config
|
||||
Description: "", // From config
|
||||
Avatar: "", // From config
|
||||
Cover: "", // From config
|
||||
Bio: "", // From config
|
||||
Description: "", // From config
|
||||
CertType: "personal", // Mock
|
||||
Stats: tenant_dto.Stats{
|
||||
Followers: int(followers),
|
||||
|
||||
Reference in New Issue
Block a user