feat: update tags
This commit is contained in:
		
						commit
						84e608f4bb
					
				
							
								
								
									
										1
									
								
								go.mod
								
								
								
								
							
							
						
						
									
										1
									
								
								go.mod
								
								
								
								
							|  | @ -3,6 +3,7 @@ module code.30cm.net/digimon/app-cloudep-product-service | ||||||
| go 1.24.0 | go 1.24.0 | ||||||
| 
 | 
 | ||||||
| require ( | require ( | ||||||
|  | 	code.30cm.net/digimon/library-go/errs v1.2.14 | ||||||
| 	code.30cm.net/digimon/library-go/mongo v0.0.9 | 	code.30cm.net/digimon/library-go/mongo v0.0.9 | ||||||
| 	github.com/alicebob/miniredis/v2 v2.34.0 | 	github.com/alicebob/miniredis/v2 v2.34.0 | ||||||
| 	github.com/shopspring/decimal v1.4.0 | 	github.com/shopspring/decimal v1.4.0 | ||||||
|  |  | ||||||
							
								
								
									
										2
									
								
								go.sum
								
								
								
								
							
							
						
						
									
										2
									
								
								go.sum
								
								
								
								
							|  | @ -1,3 +1,5 @@ | ||||||
|  | code.30cm.net/digimon/library-go/errs v1.2.14 h1:Un9wcIIjjJW8D2i0ISf8ibzp9oNT4OqLsaSKW0T4RJU= | ||||||
|  | code.30cm.net/digimon/library-go/errs v1.2.14/go.mod h1:Hs4v7SbXNggDVBGXSYsFMjkii1qLF+rugrIpWePN4/o= | ||||||
| code.30cm.net/digimon/library-go/mongo v0.0.9 h1:fPciIE5B85tXpLg8aeVQqKVbLnfpVAk9xbMu7pE2tVw= | code.30cm.net/digimon/library-go/mongo v0.0.9 h1:fPciIE5B85tXpLg8aeVQqKVbLnfpVAk9xbMu7pE2tVw= | ||||||
| code.30cm.net/digimon/library-go/mongo v0.0.9/go.mod h1:KBVKz/Ci5IheI77BgZxPUeKkaGvDy8fV8EDHSCOLIO4= | code.30cm.net/digimon/library-go/mongo v0.0.9/go.mod h1:KBVKz/Ci5IheI77BgZxPUeKkaGvDy8fV8EDHSCOLIO4= | ||||||
| dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk= | dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk= | ||||||
|  |  | ||||||
|  | @ -48,8 +48,8 @@ type TagModifyParams struct { | ||||||
| 
 | 
 | ||||||
| // TagBindingRepo 定義與 tag binding (TagsBindingTable) 資料表相關的操作
 | // TagBindingRepo 定義與 tag binding (TagsBindingTable) 資料表相關的操作
 | ||||||
| type TagBindingRepo interface { | type TagBindingRepo interface { | ||||||
| 	// BindTag 建立一筆 tag 與其他資料(例如專案)的綁定關係
 | 	// BindTags 建立一筆 tag 與其他資料(例如專案)的綁定關係
 | ||||||
| 	BindTag(ctx context.Context, binding *entity.TagsBindingTable) error | 	BindTags(ctx context.Context, binding []*entity.TagsBindingTable) error | ||||||
| 	// UnbindTag 刪除一筆綁定資料
 | 	// UnbindTag 刪除一筆綁定資料
 | ||||||
| 	UnbindTag(ctx context.Context, tagID, referenceID string) error | 	UnbindTag(ctx context.Context, tagID, referenceID string) error | ||||||
| 	// GetBindingsByReference 根據參照 ID 取得所有綁定資料
 | 	// GetBindingsByReference 根據參照 ID 取得所有綁定資料
 | ||||||
|  |  | ||||||
|  | @ -12,15 +12,22 @@ import ( | ||||||
| 	"go.mongodb.org/mongo-driver/mongo/options" | 	"go.mongodb.org/mongo-driver/mongo/options" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| func (repo *TagsRepository) BindTag(ctx context.Context, data *entity.TagsBindingTable) error { | func (repo *TagsRepository) BindTags(ctx context.Context, data []*entity.TagsBindingTable) error { | ||||||
| 	if data.ID.IsZero() { |  | ||||||
| 	now := time.Now().UTC().UnixNano() | 	now := time.Now().UTC().UnixNano() | ||||||
| 		data.ID = primitive.NewObjectID() | 	docs := make([]any, 0, len(data)) | ||||||
| 		data.CreatedAt = now |  | ||||||
| 		data.UpdatedAt = now |  | ||||||
| 	} |  | ||||||
| 
 | 
 | ||||||
| 	_, err := repo.TageBinding.GetClient().InsertOne(ctx, data) | 	for i := range data { | ||||||
|  | 		if data[i].ID.IsZero() { | ||||||
|  | 			data[i].ID = primitive.NewObjectID() | ||||||
|  | 			data[i].CreatedAt = now | ||||||
|  | 			data[i].UpdatedAt = now | ||||||
|  | 		} | ||||||
|  | 		docs = append(docs, data[i]) | ||||||
|  | 	} | ||||||
|  | 	if len(docs) == 0 { | ||||||
|  | 		return nil | ||||||
|  | 	} | ||||||
|  | 	_, err := repo.TageBinding.GetClient().InsertMany(ctx, docs) | ||||||
| 
 | 
 | ||||||
| 	return err | 	return err | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -545,7 +545,7 @@ func TestBindTag(t *testing.T) { | ||||||
| 	for _, tc := range tests { | 	for _, tc := range tests { | ||||||
| 		tc := tc // capture range variable
 | 		tc := tc // capture range variable
 | ||||||
| 		t.Run(tc.name, func(t *testing.T) { | 		t.Run(tc.name, func(t *testing.T) { | ||||||
| 			err := repo.BindTag(ctx, tc.inputBinding) | 			err := repo.BindTags(ctx, []*entity.TagsBindingTable{tc.inputBinding}) | ||||||
| 			if tc.expectError { | 			if tc.expectError { | ||||||
| 				require.Error(t, err) | 				require.Error(t, err) | ||||||
| 			} else { | 			} else { | ||||||
|  | @ -581,11 +581,7 @@ func TestGetBindingsByReference(t *testing.T) { | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	// 插入資料
 | 	// 插入資料
 | ||||||
| 	err = repo.BindTag(ctx, binding1) | 	err = repo.BindTags(ctx, []*entity.TagsBindingTable{binding1, binding2, binding3}) | ||||||
| 	require.NoError(t, err) |  | ||||||
| 	err = repo.BindTag(ctx, binding2) |  | ||||||
| 	require.NoError(t, err) |  | ||||||
| 	err = repo.BindTag(ctx, binding3) |  | ||||||
| 	require.NoError(t, err) | 	require.NoError(t, err) | ||||||
| 
 | 
 | ||||||
| 	tests := []struct { | 	tests := []struct { | ||||||
|  | @ -665,14 +661,8 @@ func TestListTagBinding(t *testing.T) { | ||||||
| 		UpdatedAt:   2500, | 		UpdatedAt:   2500, | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	// 插入綁定資料
 | 	// 插入資料
 | ||||||
| 	err = repo.BindTag(ctx, binding1) | 	err = repo.BindTags(ctx, []*entity.TagsBindingTable{binding1, binding2, binding3, binding4}) | ||||||
| 	require.NoError(t, err) |  | ||||||
| 	err = repo.BindTag(ctx, binding2) |  | ||||||
| 	require.NoError(t, err) |  | ||||||
| 	err = repo.BindTag(ctx, binding3) |  | ||||||
| 	require.NoError(t, err) |  | ||||||
| 	err = repo.BindTag(ctx, binding4) |  | ||||||
| 	require.NoError(t, err) | 	require.NoError(t, err) | ||||||
| 
 | 
 | ||||||
| 	// 測試案例
 | 	// 測試案例
 | ||||||
|  | @ -767,7 +757,7 @@ func TestUnbindTag(t *testing.T) { | ||||||
| 		ReferenceID: "ref-001", | 		ReferenceID: "ref-001", | ||||||
| 		TagID:       "tag-001", | 		TagID:       "tag-001", | ||||||
| 	} | 	} | ||||||
| 	err = repo.BindTag(ctx, binding) | 	err = repo.BindTags(ctx, []*entity.TagsBindingTable{binding}) | ||||||
| 	require.NoError(t, err) | 	require.NoError(t, err) | ||||||
| 
 | 
 | ||||||
| 	tests := []struct { | 	tests := []struct { | ||||||
|  |  | ||||||
|  | @ -68,6 +68,7 @@ func SetupTestProductRepository(db string) (repository.ProductRepository, func() | ||||||
| func TestListProduct(t *testing.T) { | func TestListProduct(t *testing.T) { | ||||||
| 	model, tearDown, err := SetupTestProductRepository("testDB") | 	model, tearDown, err := SetupTestProductRepository("testDB") | ||||||
| 	defer tearDown() | 	defer tearDown() | ||||||
|  | 	fmt.Println("ddddddddddddddddddddd", err.Error()) | ||||||
| 	assert.NoError(t, err) | 	assert.NoError(t, err) | ||||||
| 
 | 
 | ||||||
| 	now := time.Now() | 	now := time.Now() | ||||||
|  |  | ||||||
|  | @ -4,7 +4,13 @@ import ( | ||||||
| 	"code.30cm.net/digimon/app-cloudep-product-service/pkg/domain/entity" | 	"code.30cm.net/digimon/app-cloudep-product-service/pkg/domain/entity" | ||||||
| 	"code.30cm.net/digimon/app-cloudep-product-service/pkg/domain/repository" | 	"code.30cm.net/digimon/app-cloudep-product-service/pkg/domain/repository" | ||||||
| 	"code.30cm.net/digimon/app-cloudep-product-service/pkg/domain/usecase" | 	"code.30cm.net/digimon/app-cloudep-product-service/pkg/domain/usecase" | ||||||
|  | 	"code.30cm.net/digimon/app-cloudep-product-service/pkg/utils" | ||||||
|  | 	"code.30cm.net/digimon/library-go/errs" | ||||||
| 	"context" | 	"context" | ||||||
|  | 	"github.com/zeromicro/go-zero/core/logx" | ||||||
|  | 	"go.mongodb.org/mongo-driver/mongo" | ||||||
|  | 	"go.mongodb.org/mongo-driver/mongo/options" | ||||||
|  | 	"go.mongodb.org/mongo-driver/mongo/readconcern" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| type ProductUseCaseParam struct { | type ProductUseCaseParam struct { | ||||||
|  | @ -25,19 +31,130 @@ func MustProductUseCase(param ProductUseCaseParam) usecase.ProductUseCase { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (use *ProductUseCase) Create(ctx context.Context, product *usecase.Product) error { | func (use *ProductUseCase) Create(ctx context.Context, product *usecase.Product) error { | ||||||
| 	//use.ProductRepo.Transaction()
 | 	// 資料前處理:準備 entity.Product 實體
 | ||||||
| 	insert := &entity.Product{ | 	insert := &entity.Product{} | ||||||
| 		UID:        *product.UID, | 
 | ||||||
| 		Title:      *product.Title, | 	if product.UID != nil { | ||||||
| 		ShortTitle: product.ShortTitle, | 		insert.UID = *product.UID | ||||||
|  | 	} | ||||||
|  | 	if product.Title != nil { | ||||||
|  | 		insert.Title = *product.Title | ||||||
|  | 	} | ||||||
|  | 	if product.IsPublished != nil { | ||||||
|  | 		insert.IsPublished = *product.IsPublished | ||||||
|  | 	} | ||||||
|  | 	if product.Category != nil { | ||||||
|  | 		insert.Category = *product.Category | ||||||
|  | 	} | ||||||
|  | 	if product.ShortTitle != nil { | ||||||
|  | 		insert.ShortTitle = product.ShortTitle | ||||||
|  | 	} | ||||||
|  | 	if product.Details != nil { | ||||||
|  | 		insert.Details = product.Details | ||||||
|  | 	} | ||||||
|  | 	if product.ShortDescription != nil { | ||||||
|  | 		insert.ShortDescription = *product.ShortDescription | ||||||
|  | 	} | ||||||
|  | 	if product.Slug != nil { | ||||||
|  | 		insert.Slug = product.Slug | ||||||
|  | 	} | ||||||
|  | 	if product.Amount != 0 { | ||||||
|  | 		insert.Amount = product.Amount | ||||||
|  | 	} | ||||||
|  | 	if product.StartTime != nil { | ||||||
|  | 		st := utils.Rfc3339ToUnix(utils.ToValue(product.StartTime)) | ||||||
|  | 		insert.StartTime = &st | ||||||
|  | 	} | ||||||
|  | 	if product.EndTime != nil { | ||||||
|  | 		et := utils.Rfc3339ToUnix(utils.ToValue(product.EndTime)) | ||||||
|  | 		insert.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, | ||||||
|  | 			}) | ||||||
|  | 		} | ||||||
|  | 		insert.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, | ||||||
|  | 			}) | ||||||
|  | 		} | ||||||
|  | 		insert.CustomFields = cf | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	err := use.ProductRepo.Insert(ctx, insert) | 	// Transaction 設定:只做必要寫入
 | ||||||
|  | 	opts := options.Transaction().SetReadConcern(readconcern.Local()) | ||||||
|  | 	err := use.ProductRepo.Transaction(ctx, func(sessCtx mongo.SessionContext) (any, error) { | ||||||
|  | 		// 插入 Product
 | ||||||
|  | 		if err := use.ProductRepo.Insert(sessCtx, insert); err != nil { | ||||||
|  | 			e := errs.DBErrorL(logx.WithContext(ctx), | ||||||
|  | 				[]logx.LogField{ | ||||||
|  | 					{Key: "req", Value: product}, | ||||||
|  | 					{Key: "func", Value: "ProductRepo.Insert"}, | ||||||
|  | 					{Key: "err", Value: err.Error()}, | ||||||
|  | 				}, | ||||||
|  | 				"failed to create product") | ||||||
|  | 
 | ||||||
|  | 			return nil, e | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		// 插入 Product 統計資料
 | ||||||
|  | 		if err := use.ProductStatisticsRepo.Create(sessCtx, &entity.ProductStatistics{ | ||||||
|  | 			ProductID:               insert.ID.Hex(), | ||||||
|  | 			Orders:                  0, | ||||||
|  | 			OrdersUpdateTime:        0, | ||||||
|  | 			AverageRating:           0, | ||||||
|  | 			AverageRatingUpdateTime: 0, | ||||||
|  | 			FansCount:               0, | ||||||
|  | 			FansCountUpdateTime:     0, | ||||||
|  | 		}); err != nil { | ||||||
|  | 			e := errs.DBErrorL(logx.WithContext(ctx), | ||||||
|  | 				[]logx.LogField{ | ||||||
|  | 					{Key: "ProductID", Value: insert.ID.Hex()}, | ||||||
|  | 					{Key: "func", Value: "ProductStatisticsRepo.Create"}, | ||||||
|  | 					{Key: "err", Value: err.Error()}, | ||||||
|  | 				}, | ||||||
|  | 				"failed to create product statistics") | ||||||
|  | 
 | ||||||
|  | 			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
 | ||||||
|  | 		//	}
 | ||||||
|  | 		//}
 | ||||||
|  | 
 | ||||||
|  | 		return nil, nil | ||||||
|  | 	}, opts) | ||||||
|  | 
 | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return err | 		return err | ||||||
| 	} | 	} | ||||||
| 	//TODO implement me
 | 
 | ||||||
| 	panic("implement me") | 	return nil | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (use *ProductUseCase) Update(ctx context.Context, id string, product *usecase.Product) error { | func (use *ProductUseCase) Update(ctx context.Context, id string, product *usecase.Product) error { | ||||||
|  |  | ||||||
|  | @ -0,0 +1,28 @@ | ||||||
|  | package utils | ||||||
|  | 
 | ||||||
|  | import "time" | ||||||
|  | 
 | ||||||
|  | func UnixToRfc3339(t int64) string { | ||||||
|  | 	res := time.Unix(0, t).UTC() | ||||||
|  | 
 | ||||||
|  | 	return res.Format(time.RFC3339) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func Rfc3339ToUnix(rfc3339 string) int64 { | ||||||
|  | 	// 解析 RFC3339 格式的時間
 | ||||||
|  | 	t, err := time.Parse(time.RFC3339, rfc3339) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return 0 | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	// 轉換為 Unix Nano (納秒)
 | ||||||
|  | 	return t.UTC().UnixNano() | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func ToValue[T any](ptr *T) T { | ||||||
|  | 	if ptr == nil { | ||||||
|  | 		var zero T | ||||||
|  | 		return zero | ||||||
|  | 	} | ||||||
|  | 	return *ptr | ||||||
|  | } | ||||||
		Loading…
	
		Reference in New Issue