From eafd691dab081d148739fb888e3e37510e41d058 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8E=8B=E6=80=A7=E9=A9=8A?= Date: Tue, 25 Mar 2025 09:28:05 +0000 Subject: [PATCH] feat: add product func without testing --- pkg/domain/repository/tags.go | 4 + pkg/repository/product_tags.go | 27 ++ pkg/repository/product_tags_biinding.go | 10 + pkg/usecase/product.go | 351 ++++++++++++++++++++++-- 4 files changed, 366 insertions(+), 26 deletions(-) diff --git a/pkg/domain/repository/tags.go b/pkg/domain/repository/tags.go index 81fcd07..e21e470 100644 --- a/pkg/domain/repository/tags.go +++ b/pkg/domain/repository/tags.go @@ -27,6 +27,8 @@ type Base interface { // List 根據查詢條件取得 Tag 資料列表 // 回傳值分別為資料列表與符合條件的總筆數 List(ctx context.Context, params TagQueryParams) ([]*entity.Tags, int64, error) + // GetByIDs 根據查詢條件取得 Tag 資料列表 + GetByIDs(ctx context.Context, ids []string) ([]*entity.Tags, error) } // TagQueryParams 為查詢 Tags 時的參數結構 @@ -52,6 +54,8 @@ type TagBindingRepo interface { BindTags(ctx context.Context, binding []*entity.TagsBindingTable) error // UnbindTag 刪除一筆綁定資料 UnbindTag(ctx context.Context, tagID, referenceID string) error + // UnbindTagByReferenceID 刪除一筆綁定資料 + UnbindTagByReferenceID(ctx context.Context, referenceID string) error // GetBindingsByReference 根據參照 ID 取得所有綁定資料 GetBindingsByReference(ctx context.Context, referenceID string) ([]*entity.TagsBindingTable, error) // ListTagBinding 根據查詢條件取得 tag binding 的資料列表 diff --git a/pkg/repository/product_tags.go b/pkg/repository/product_tags.go index 514bfac..eb035a5 100644 --- a/pkg/repository/product_tags.go +++ b/pkg/repository/product_tags.go @@ -172,6 +172,33 @@ func (repo *TagsRepository) List(ctx context.Context, params repository.TagQuery return tags, count, nil } +func (repo *TagsRepository) GetByIDs(ctx context.Context, ids []string) ([]*entity.Tags, error) { + // 轉換字串 ID 為 ObjectID + objectIDs := make([]primitive.ObjectID, 0, len(ids)) + for _, id := range ids { + oid, err := primitive.ObjectIDFromHex(id) + if err != nil { + continue + } + objectIDs = append(objectIDs, oid) + } + if len(objectIDs) == 0 { + return nil, nil + } + + // 構建查詢過濾器 + filter := bson.M{"_id": bson.M{"$in": objectIDs}} + + // 查詢符合條件的文件 + var results []*entity.Tags + err := repo.Tags.GetClient().Find(ctx, &results, filter) + if err != nil { + return nil, err + } + + return results, nil +} + func (repo *TagsRepository) IndexTags20250317001UP(ctx context.Context) (*mongo.Cursor, error) { // 等價於 db.account.createIndex({"create_at": 1}) repo.Tags.PopulateIndex(ctx, "show_type", 1, false) diff --git a/pkg/repository/product_tags_biinding.go b/pkg/repository/product_tags_biinding.go index b2224bc..398b11e 100644 --- a/pkg/repository/product_tags_biinding.go +++ b/pkg/repository/product_tags_biinding.go @@ -42,6 +42,16 @@ func (repo *TagsRepository) UnbindTag(ctx context.Context, tagID, referenceID st return nil } +func (repo *TagsRepository) UnbindTagByReferenceID(ctx context.Context, referenceID string) error { + filter := bson.M{"reference_id": referenceID} + _, err := repo.TageBinding.GetClient().DeleteMany(ctx, filter) + if err != nil { + return err + } + + return nil +} + func (repo *TagsRepository) GetBindingsByReference(ctx context.Context, referenceID string) ([]*entity.TagsBindingTable, error) { var result []*entity.TagsBindingTable filter := bson.M{"reference_id": referenceID} diff --git a/pkg/usecase/product.go b/pkg/usecase/product.go index 5db24ea..9c66e06 100644 --- a/pkg/usecase/product.go +++ b/pkg/usecase/product.go @@ -91,6 +91,15 @@ func (use *ProductUseCase) Create(ctx context.Context, product *usecase.Product) insert.CustomFields = cf } + // 綁定 Tags + tagsBinding := make([]*entity.TagsBindingTable, 0, len(product.Tags)) + for _, tag := range product.Tags { + tagsBinding = append(tagsBinding, &entity.TagsBindingTable{ + ReferenceID: insert.ID.Hex(), + TagID: tag, + }) + } + // Transaction 設定:只做必要寫入 opts := options.Transaction().SetReadConcern(readconcern.Local()) err := use.ProductRepo.Transaction(ctx, func(sessCtx mongo.SessionContext) (any, error) { @@ -128,24 +137,17 @@ func (use *ProductUseCase) Create(ctx context.Context, product *usecase.Product) return nil, e } - // 過濾 Tag - // 綁定 Tags - //for _, tag := range product.Tags { - // if err := use.TagBinding.BindTag(sessCtx, &entity.TagsBindingTable{ - // ReferenceID: insert.ID.Hex(), - // TagID: tag, - // }); err != nil { - // _ = errs.DBErrorL(logx.WithContext(ctx), - // []logx.LogField{ - // {Key: "ReferenceID", Value: insert.ID.Hex()}, - // {Key: "TagID", Value: tag}, - // {Key: "func", Value: "TagBinding.BindTag"}, - // {Key: "err", Value: err.Error()}, - // }, "") - // - // continue - // } - //} + if err := use.TagBinding.BindTags(sessCtx, tagsBinding); err != nil { + e := errs.DBErrorL(logx.WithContext(ctx), + []logx.LogField{ + {Key: "ReferenceID", Value: insert.ID.Hex()}, + {Key: "tags", Value: product.Tags}, + {Key: "func", Value: "TagBinding.BindTag"}, + {Key: "err", Value: err.Error()}, + }, "failed to binding product tags") + + return nil, e + } return nil, nil }, opts) @@ -158,23 +160,320 @@ func (use *ProductUseCase) Create(ctx context.Context, product *usecase.Product) } func (use *ProductUseCase) Update(ctx context.Context, id string, product *usecase.Product) error { - //TODO implement me - panic("implement me") + // 資料前處理:準備 entity.Product 實體 + update := &repository.ProductUpdateParams{} + + if product.Title != nil { + update.Title = product.Title + } + if product.IsPublished != nil { + update.IsPublished = product.IsPublished + } + if product.Category != nil { + update.Category = product.Category + } + if product.ShortTitle != nil { + update.ShortTitle = product.ShortTitle + } + if product.Details != nil { + update.Details = product.Details + } + if product.ShortDescription != nil { + update.ShortDescription = *product.ShortDescription + } + if product.Slug != nil { + update.Slug = product.Slug + } + if product.Amount != 0 { + update.Amount = &product.Amount + } + if product.StartTime != nil { + st := utils.Rfc3339ToUnix(utils.ToValue(product.StartTime)) + update.StartTime = &st + } + if product.EndTime != nil { + et := utils.Rfc3339ToUnix(utils.ToValue(product.EndTime)) + update.EndTime = &et + } + if len(product.Media) > 0 { + medias := make([]entity.Media, 0, len(product.Media)) + for _, m := range product.Media { + medias = append(medias, entity.Media{ + Sort: m.Sort, + URL: m.URL, + Type: m.Type, + }) + } + update.Media = medias + } + if len(product.CustomFields) > 0 { + cf := make([]entity.CustomFields, 0, len(product.CustomFields)) + for _, field := range product.CustomFields { + cf = append(cf, entity.CustomFields{ + Key: field.Key, + Value: field.Value, + }) + } + update.CustomFields = cf + } + + // 綁定 Tags + tagsBinding := make([]*entity.TagsBindingTable, 0, len(product.Tags)) + for _, tag := range product.Tags { + tagsBinding = append(tagsBinding, &entity.TagsBindingTable{ + ReferenceID: id, + TagID: tag, + }) + } + + // Transaction 設定:只做必要寫入 + opts := options.Transaction().SetReadConcern(readconcern.Local()) + err := use.ProductRepo.Transaction(ctx, func(sessCtx mongo.SessionContext) (any, error) { + _, err := use.ProductRepo.Update(sessCtx, id, update) + if err != nil { + e := errs.DBErrorL(logx.WithContext(ctx), + []logx.LogField{ + {Key: "req", Value: product}, + {Key: "func", Value: "ProductRepo.Update"}, + {Key: "err", Value: err.Error()}, + }, + "failed to update product") + + return nil, e + } + + if err := use.TagBinding.UnbindTagByReferenceID(sessCtx, id); err != nil { + e := errs.DBErrorL(logx.WithContext(ctx), + []logx.LogField{ + {Key: "ReferenceID", Value: id}, + {Key: "func", Value: "TagBinding.UnbindTagByReferenceID"}, + {Key: "err", Value: err.Error()}, + }, "failed to unbind tags") + + return nil, e + } + + if err := use.TagBinding.BindTags(sessCtx, tagsBinding); err != nil { + e := errs.DBErrorL(logx.WithContext(ctx), + []logx.LogField{ + {Key: "ReferenceID", Value: id}, + {Key: "tags", Value: product.Tags}, + {Key: "func", Value: "TagBinding.BindTags"}, + {Key: "err", Value: err.Error()}, + }, "failed to binding product tags") + + return nil, e + } + + return nil, nil + }, opts) + if err != nil { + return err + } + + return nil } func (use *ProductUseCase) Delete(ctx context.Context, id string) error { - //TODO implement me - panic("implement me") + // Transaction 設定:只做必要寫入 + opts := options.Transaction().SetReadConcern(readconcern.Local()) + err := use.ProductRepo.Transaction(ctx, func(sessCtx mongo.SessionContext) (any, error) { + err := use.ProductRepo.Delete(sessCtx, id) + if err != nil { + e := errs.DBErrorL(logx.WithContext(ctx), + []logx.LogField{ + {Key: "ReferenceID", Value: id}, + {Key: "func", Value: "ProductRepo.Delete"}, + {Key: "err", Value: err.Error()}, + }, "failed to delete product") + + return nil, e + } + + err = use.TagRepo.UnbindTagByReferenceID(sessCtx, id) + if err != nil { + e := errs.DBErrorL(logx.WithContext(ctx), + []logx.LogField{ + {Key: "ReferenceID", Value: id}, + {Key: "func", Value: "TagBinding.UnbindTagByReferenceID"}, + {Key: "err", Value: err.Error()}, + }, "failed to unbind tags") + + return nil, e + } + + return nil, nil + }, opts) + if err != nil { + return err + } + + return nil } func (use *ProductUseCase) Get(ctx context.Context, id string) (*usecase.ProductResp, error) { - //TODO implement me - panic("implement me") + product, err := use.ProductRepo.FindOneByID(ctx, id) + if err != nil { + e := errs.DBErrorL(logx.WithContext(ctx), + []logx.LogField{ + {Key: "product_id", Value: id}, + {Key: "func", Value: "ProductRepo.FindOneByID"}, + {Key: "err", Value: err.Error()}, + }, "failed to find product") + + return nil, e + } + productStatistics, err := use.ProductStatisticsRepo.GetByID(ctx, id) + if err != nil { + e := errs.DBErrorL(logx.WithContext(ctx), + []logx.LogField{ + {Key: "product_id", Value: id}, + {Key: "func", Value: "ProductStatisticsRepo.GetByID"}, + {Key: "err", Value: err.Error()}, + }, "failed to get product statistics") + + return nil, e + } + + tags, err := use.TagRepo.GetBindingsByReference(ctx, id) + if err != nil { + e := errs.DBErrorL(logx.WithContext(ctx), + []logx.LogField{ + {Key: "product_id", Value: id}, + {Key: "func", Value: "TagRepo.GetBindingsByReference"}, + {Key: "err", Value: err.Error()}, + }, "failed to get tags") + + return nil, e + } + + t := make([]string, 0, len(tags)) + for _, item := range tags { + t = append(t, item.TagID) + } + + tagsInfo, err := use.TagRepo.GetByIDs(ctx, t) + if err != nil { + e := errs.DBErrorL(logx.WithContext(ctx), + []logx.LogField{ + {Key: "product_id", Value: id}, + {Key: "func", Value: "TagRepo.GetByIDs"}, + {Key: "err", Value: err.Error()}, + }, "failed to get tags") + + return nil, e + } + // 組合資料 + result := &usecase.ProductResp{ + ID: product.ID.Hex(), + UID: product.UID, + Title: product.Title, + ShortDescription: product.ShortDescription, + IsPublished: product.IsPublished, + Amount: product.Amount, + Category: product.Category, + Orders: productStatistics.Orders, + AverageRating: productStatistics.AverageRating, + FansCount: productStatistics.FansCount, + UpdatedAt: utils.UnixToRfc3339(product.UpdatedAt), + CreatedAt: utils.UnixToRfc3339(product.CreatedAt), + } + + for _, tag := range tagsInfo { + item := usecase.Tags{ + ID: tag.ID.Hex(), + Types: tag.Types, + Name: tag.Name, + ShowType: tag.ShowType, + UpdatedAt: utils.UnixToRfc3339(tag.UpdatedAt), + CreatedAt: utils.UnixToRfc3339(tag.CreatedAt), + } + if tag.Cover != nil { + item.Cover = *tag.Cover + } + result.Tags = append(result.Tags, item) + } + + if len(product.Media) > 0 { + medias := make([]usecase.Media, 0, len(product.Media)) + for _, m := range product.Media { + medias = append(medias, usecase.Media{ + Sort: m.Sort, + URL: m.URL, + Type: m.Type, + }) + } + result.Media = medias + } + if len(product.CustomFields) > 0 { + cf := make([]usecase.CustomFields, 0, len(product.CustomFields)) + for _, field := range product.CustomFields { + cf = append(cf, usecase.CustomFields{ + Key: field.Key, + Value: field.Value, + }) + } + result.CustomFields = cf + } + + if product.Slug != nil { + result.Slug = *product.Slug + } + if product.ShortTitle != nil { + result.ShortTitle = *product.ShortTitle + } + if product.Details != nil { + result.Details = *product.Details + } + if product.StartTime != nil { + result.StartTime = utils.UnixToRfc3339(*product.StartTime) + } + if product.EndTime != nil { + result.EndTime = utils.UnixToRfc3339(*product.EndTime) + } + if productStatistics.OrdersUpdateTime > 0 { + result.OrdersUpdateTime = utils.UnixToRfc3339(productStatistics.OrdersUpdateTime) + } + if productStatistics.FansCountUpdateTime > 0 { + result.FansCountUpdateTime = utils.UnixToRfc3339(productStatistics.FansCountUpdateTime) + } + if productStatistics.AverageRatingUpdateTime > 0 { + result.AverageRatingUpdateTime = utils.UnixToRfc3339(productStatistics.AverageRatingUpdateTime) + } + + return result, nil } func (use *ProductUseCase) List(ctx context.Context, data usecase.ProductQueryParams) ([]*usecase.ProductResp, int64, error) { - //TODO implement me - panic("implement me") + query := &repository.ProductQueryParams{ + PageSize: data.PageSize, + PageIndex: data.PageIndex, + } + + if data.Slug != nil { + query.Slug = data.Slug + } + if data.UID != nil { + query.UID = data.UID + } + if data.IsPublished != nil { + query.IsPublished = data.IsPublished + } + if data.Category != nil { + query.Category = data.Category + } + if data.StartTime != nil { + query.StartTime = data.StartTime + } + + EndTime * int64 // 結束時間(Unix 時間戳) + Slug * string // URL 後綴 + + product, i, err := use.ProductRepo.ListProduct(ctx, &repository.ProductQueryParams{}) + if err != nil { + return nil, 0, err + } + } func (use *ProductUseCase) IncOrders(ctx context.Context, productID string, count int64) error {