From fba77afec1fb94dca97af0224b0789b70a58c178 Mon Sep 17 00:00:00 2001 From: Rogee Date: Mon, 29 Dec 2025 15:04:55 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E9=87=8D=E6=9E=84=E5=86=85=E5=AE=B9?= =?UTF-8?q?=E5=88=97=E8=A1=A8=E6=8E=A5=E5=8F=A3=EF=BC=8C=E4=BD=BF=E7=94=A8?= =?UTF-8?q?=E8=BF=87=E6=BB=A4=E5=99=A8=E7=BB=93=E6=9E=84=E4=BD=93=E7=AE=80?= =?UTF-8?q?=E5=8C=96=E5=8F=82=E6=95=B0=E4=BC=A0=E9=80=92=EF=BC=9B=E6=9B=B4?= =?UTF-8?q?=E6=96=B0=E7=9B=B8=E5=85=B3=E6=9C=8D=E5=8A=A1=E5=92=8C=E6=B5=8B?= =?UTF-8?q?=E8=AF=95=E7=94=A8=E4=BE=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/app/errorx/app_error.go | 4 ++-- backend/app/http/v1/content.go | 11 +++------- backend/app/http/v1/dto/content.go | 10 ++++++++++ backend/app/http/v1/routes.gen.go | 8 ++------ backend/app/services/common.go | 8 ++++---- backend/app/services/content.go | 30 +++++++++++++--------------- backend/app/services/content_test.go | 10 +++++++++- backend/app/services/creator.go | 2 +- backend/app/services/order.go | 12 +++++------ backend/app/services/super.go | 2 +- backend/app/services/super_test.go | 4 ++-- backend/app/services/tenant.go | 20 +++++++++---------- backend/app/services/wallet.go | 2 +- backend/app/services/wallet_test.go | 2 +- 14 files changed, 65 insertions(+), 60 deletions(-) diff --git a/backend/app/errorx/app_error.go b/backend/app/errorx/app_error.go index dd341b1..e16f17d 100644 --- a/backend/app/errorx/app_error.go +++ b/backend/app/errorx/app_error.go @@ -38,7 +38,7 @@ func (e *AppError) copy() *AppError { func (e *AppError) WithCause(err error) *AppError { newErr := e.copy() newErr.originalErr = err - + // 记录调用者位置 if _, file, line, ok := runtime.Caller(1); ok { newErr.file = fmt.Sprintf("%s:%d", file, line) @@ -90,4 +90,4 @@ func NewError(code ErrorCode, statusCode int, message string) *AppError { Message: message, StatusCode: statusCode, } -} \ No newline at end of file +} diff --git a/backend/app/http/v1/content.go b/backend/app/http/v1/content.go index f7e8608..fcf41aa 100644 --- a/backend/app/http/v1/content.go +++ b/backend/app/http/v1/content.go @@ -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 diff --git a/backend/app/http/v1/dto/content.go b/backend/app/http/v1/dto/content.go index fe0910a..2b89263 100644 --- a/backend/app/http/v1/dto/content.go +++ b/backend/app/http/v1/dto/content.go @@ -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"` diff --git a/backend/app/http/v1/routes.gen.go b/backend/app/http/v1/routes.gen.go index 360f9c6..4dcea09 100644 --- a/backend/app/http/v1/routes.gen.go +++ b/backend/app/http/v1/routes.gen.go @@ -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( diff --git a/backend/app/services/common.go b/backend/app/services/common.go index 63f4c9a..8d55690 100644 --- a/backend/app/services/common.go +++ b/backend/app/services/common.go @@ -30,7 +30,7 @@ func (s *common) Upload(ctx context.Context, file *multipart.FileHeader, typeArg objectKey := uuid.NewString() + "_" + file.Filename url := "http://mock-storage/" + objectKey - // Determine TenantID. + // Determine TenantID. // Uploads usually happen in context of a tenant? Or personal? // For now assume user's owned tenant if any, or 0. // MediaAsset has TenantID (NOT NULL). @@ -41,7 +41,7 @@ func (s *common) Upload(ctx context.Context, file *multipart.FileHeader, typeArg tid = t.ID } // If no tenant, and TenantID is NOT NULL, we have a problem for regular users uploading avatar? - // Users avatar is URL string in `users` table. + // Users avatar is URL string in `users` table. // MediaAssets table is for TENANT content. // If this is for user avatar upload, maybe we don't use MediaAssets? // But `upload` endpoint is generic. @@ -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? }), @@ -73,4 +73,4 @@ func (s *common) Upload(ctx context.Context, file *multipart.FileHeader, typeArg Size: file.Size, MimeType: file.Header.Get("Content-Type"), }, nil -} \ No newline at end of file +} diff --git a/backend/app/services/content.go b/backend/app/services/content.go index d5908c3..2f402ff 100644 --- a/backend/app/services/content.go +++ b/backend/app/services/content.go @@ -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 } diff --git a/backend/app/services/content_test.go b/backend/app/services/content_test.go index cc3be2f..36942ff 100644 --- a/backend/app/services/content_test.go +++ b/backend/app/services/content_test.go @@ -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) diff --git a/backend/app/services/creator.go b/backend/app/services/creator.go index e4c8e00..5b92fcf 100644 --- a/backend/app/services/creator.go +++ b/backend/app/services/creator.go @@ -275,4 +275,4 @@ func (s *creator) getTenantID(ctx context.Context) (int64, error) { return 0, errorx.ErrDatabaseError.WithCause(err) } return t.ID, nil -} \ No newline at end of file +} diff --git a/backend/app/services/order.go b/backend/app/services/order.go index 6df85fa..75502fd 100644 --- a/backend/app/services/order.go +++ b/backend/app/services/order.go @@ -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 } @@ -197,7 +196,7 @@ func (s *order) payWithBalance(ctx context.Context, o *models.Order) (*transacti if err != nil { return err } - + ledger := &models.TenantLedger{ TenantID: o.TenantID, UserID: t.UserID, // Owner @@ -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 @@ -243,4 +241,4 @@ func (s *order) toUserOrderDTO(o *models.Order) user_dto.Order { Amount: float64(o.AmountPaid) / 100.0, CreateTime: o.CreatedAt.Format(time.RFC3339), } -} \ No newline at end of file +} diff --git a/backend/app/services/super.go b/backend/app/services/super.go index 0465924..d1bda2d 100644 --- a/backend/app/services/super.go +++ b/backend/app/services/super.go @@ -282,4 +282,4 @@ func (s *super) UserStatuses(ctx context.Context) ([]requests.KV, error) { func (s *super) TenantStatuses(ctx context.Context) ([]requests.KV, error) { return consts.TenantStatusItems(), nil -} \ No newline at end of file +} diff --git a/backend/app/services/super_test.go b/backend/app/services/super_test.go index 93a77bc..fe7707d 100644 --- a/backend/app/services/super_test.go +++ b/backend/app/services/super_test.go @@ -49,7 +49,7 @@ func (s *SuperTestSuite) Test_ListUsers() { res, err := Super.ListUsers(ctx, 1, 10, "") So(err, ShouldBeNil) So(res.Total, ShouldEqual, 2) - + items := res.Items.([]super_dto.UserItem) So(items[0].Username, ShouldEqual, "user2") // Desc order }) @@ -88,4 +88,4 @@ func (s *SuperTestSuite) Test_CreateTenant() { So(t.Status, ShouldEqual, consts.TenantStatusVerified) }) }) -} \ No newline at end of file +} diff --git a/backend/app/services/tenant.go b/backend/app/services/tenant.go index d9a0e6a..602734d 100644 --- a/backend/app/services/tenant.go +++ b/backend/app/services/tenant.go @@ -20,11 +20,11 @@ type tenant struct{} func (s *tenant) GetPublicProfile(ctx context.Context, id string) (*tenant_dto.TenantProfile, error) { // id could be Code or ID. Try Code first, then ID. tbl, q := models.TenantQuery.QueryContext(ctx) - + // Try to find by code or ID var t *models.Tenant var err error - + // Assume id is ID for simplicity if numeric, or try both. if cast.ToInt64(id) > 0 { t, err = q.Where(tbl.ID.Eq(cast.ToInt64(id))).First() @@ -48,7 +48,7 @@ func (s *tenant) GetPublicProfile(ctx context.Context, id string) (*tenant_dto.T var likes int64 // Sum content likes // Mock likes for now or fetch - + // IsFollowing isFollowing := false userID := ctx.Value(consts.CtxKeyUser) @@ -60,14 +60,14 @@ func (s *tenant) GetPublicProfile(ctx context.Context, id string) (*tenant_dto.T // Config parsing (Unused for now as we don't map to bio yet) // config := t.Config.Data() - + 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), @@ -99,7 +99,7 @@ func (s *tenant) Follow(ctx context.Context, id string) error { Role: types.Array[consts.TenantUserRole]{consts.TenantUserRoleMember}, Status: consts.UserStatusVerified, } - + count, _ := models.TenantUserQuery.WithContext(ctx).Where(models.TenantUserQuery.TenantID.Eq(tid), models.TenantUserQuery.UserID.Eq(uid)).Count() if count > 0 { return nil // Already following @@ -124,4 +124,4 @@ func (s *tenant) Unfollow(ctx context.Context, id string) error { return errorx.ErrDatabaseError.WithCause(err) } return nil -} \ No newline at end of file +} diff --git a/backend/app/services/wallet.go b/backend/app/services/wallet.go index e014675..8ca97c7 100644 --- a/backend/app/services/wallet.go +++ b/backend/app/services/wallet.go @@ -108,4 +108,4 @@ func (s *wallet) Recharge(ctx context.Context, form *user_dto.RechargeForm) (*us PayParams: "mock_recharge_url", OrderID: cast.ToString(order.ID), }, nil -} \ No newline at end of file +} diff --git a/backend/app/services/wallet_test.go b/backend/app/services/wallet_test.go index 0d59374..e34fac5 100644 --- a/backend/app/services/wallet_test.go +++ b/backend/app/services/wallet_test.go @@ -62,7 +62,7 @@ func (s *WalletTestSuite) Test_GetWallet() { So(err, ShouldBeNil) So(res.Balance, ShouldEqual, 50.0) So(len(res.Transactions), ShouldEqual, 2) - + // Order by CreatedAt Desc types := []string{res.Transactions[0].Type, res.Transactions[1].Type} So(types, ShouldContain, "income")