Compare commits

..

35 Commits

Author SHA1 Message Date
王性驊 a14747bbd7 add go lint 2025-04-10 15:59:30 +08:00
王性驊 8b0aa7a0db add product_item 2025-04-10 15:54:58 +08:00
王性驊 61870ec7a2 add product_item 2025-04-10 15:47:07 +08:00
王性驊 4806eea021 feat: update service 2025-04-09 22:55:58 +08:00
王性驊 748d0c0725 feat: update service 2025-04-09 22:46:53 +08:00
王性驊 1a32f58515 feat: add product item api 2025-04-09 17:29:56 +08:00
王性驊 5d4f15fe8c feat: add kyc api 2025-04-09 15:32:04 +08:00
王性驊 b5a12ceaa2 feat: add kyc api 2025-04-08 17:20:34 +08:00
王性驊 a2729b95ca feat: add tags api 2025-04-08 11:49:07 +08:00
王性驊 ae920e4f77 feat: add category api 2025-04-08 10:06:54 +08:00
王性驊 8d042000f5 feat: add category api 2025-04-08 10:06:40 +08:00
王性驊 e98ac37f1c 刪除 etc/product.yaml 2025-04-08 01:06:12 +00:00
王性驊 44152bf93a feat: add service 2025-04-08 08:51:58 +08:00
王性驊 7951f4b98a feat: go lint 2025-04-06 10:08:46 +08:00
王性驊 c4617956e5 feat: usecast test 2025-04-05 22:29:53 +08:00
王性驊 52905207c6 feat: usecast test 2025-04-04 17:33:48 +08:00
王性驊 39841cc168 feat: add category repository 2025-04-04 15:39:49 +08:00
王性驊 cb7e9fb5bb feat: add tags 2025-04-04 14:39:01 +08:00
王性驊 6f790d65b3 feat: add kyc test 2025-04-03 15:32:53 +08:00
王性驊 14678b986b feat: add kyc test 2025-04-03 15:32:14 +08:00
王性驊 37b8bf8a74 feat: create kyc repository 2025-04-01 17:44:57 +08:00
王性驊 bf8d532c6e feat: create kyc 2025-03-31 21:24:40 +08:00
王性驊 eafd691dab feat: add product func without testing 2025-03-25 09:28:05 +00:00
王性驊 84e608f4bb feat: update tags 2025-03-25 07:58:02 +00:00
王性驊 fa9b188811 feat: tmp uc 2025-03-25 07:31:33 +00:00
王性驊 8e1293f4b7 feat: update create product 2025-03-24 00:08:32 +08:00
王性驊 dba8d1deeb feat: tmp uc 2025-03-21 09:12:08 +00:00
王性驊 f84f58a460 feat: create product usecase 2025-03-21 11:12:41 +08:00
王性驊 9e3fa8ecd0 feat: update tags binding 2025-03-21 10:41:31 +08:00
王性驊 6e47895174 feat: add product tags index 2025-03-21 07:31:35 +08:00
王性驊 f6e936a760 feat: add product tags index 2025-03-20 18:44:01 +08:00
王性驊 2451bc4257 feat: product tags 2025-03-20 17:11:56 +08:00
王性驊 fdc0799fcc feat: product statustucs 2025-03-19 19:28:07 +08:00
王性驊 2e2bdecc48 feat: product item 2025-03-19 14:45:44 +08:00
王性驊 6f27ff3bbc feat: product repo 2025-03-17 10:08:22 +08:00
107 changed files with 18711 additions and 124 deletions

2
.gitignore vendored
View File

@ -1,3 +1,3 @@
.idea/ .idea/
etc/gateway.yaml etc/product.yaml
.DS_Store .DS_Store

View File

@ -14,9 +14,7 @@ linters:
disable-all: true disable-all: true
# Specifically enable linters we want to use. # Specifically enable linters we want to use.
enable: enable:
# - depguard
- errcheck - errcheck
# - godot
- gofmt - gofmt
- goimports - goimports
- gosimple - gosimple
@ -24,22 +22,15 @@ linters:
- ineffassign - ineffassign
- misspell - misspell
- revive - revive
# - staticcheck
- typecheck - typecheck
- unused - unused
# - wsl
- asasalint - asasalint
- asciicheck - asciicheck
- bidichk - bidichk
- bodyclose - bodyclose
# - containedctx
- contextcheck - contextcheck
# - cyclop
# - varnamelen
# - gci
- wastedassign - wastedassign
- whitespace - whitespace
# - wrapcheck
- thelper - thelper
- tparallel - tparallel
- unconvert - unconvert
@ -66,35 +57,26 @@ linters:
- nonamedreturns - nonamedreturns
- decorder - decorder
- dogsled - dogsled
# - dupl
- dupword - dupword
- durationcheck - durationcheck
- errchkjson - errchkjson
- errname - errname
- errorlint - errorlint
# - execinquery
- exhaustive - exhaustive
- exportloopref
- forbidigo - forbidigo
- forcetypeassert - forcetypeassert
# - gochecknoglobals
- gochecknoinits - gochecknoinits
- gocognit - gocognit
- goconst - goconst
- gocritic - gocritic
- gocyclo - gocyclo
# - godox
# - goerr113
# - gofumpt
- goheader - goheader
- gomoddirectives - gomoddirectives
# - gomodguard always failed
- goprintffuncname - goprintffuncname
- gosec - gosec
- grouper - grouper
- importas - importas
- interfacebloat - interfacebloat
# - ireturn
- lll - lll
- loggercheck - loggercheck
- maintidx - maintidx

View File

@ -21,7 +21,7 @@ fmt: # 格式優化
.PHONY: gen-rpc .PHONY: gen-rpc
gen-rpc: # 建立 rpc code gen-rpc: # 建立 rpc code
goctl rpc protoc ./generate/protobuf/product.proto -m --style=$(GO_ZERO_STYLE) --go_out=./gen_result/pb --go-grpc_out=./gen_result/pb --zrpc_out=. goctl rpc protoc ./generate/protobuf/product.proto -m --style=$(GO_ZERO_STYLE) --go_out=./gen_result/pb --go-grpc_out=./gen_result/pb --zrpc_out=. -m
go mod tidy go mod tidy
@echo "Generate core-api files successfully" @echo "Generate core-api files successfully"
@ -35,5 +35,10 @@ build-docker:
.PHONY: mock-gen .PHONY: mock-gen
mock-gen: # 建立 mock 資料 mock-gen: # 建立 mock 資料
#mockgen -source=./pkg/domain/repository/token.go -destination=./pkg/mock/repository/token.go -package=mock mockgen -source=./pkg/domain/repository/product.go -destination=./pkg/mock/repository/product.go -package=mock
mockgen -source=./pkg/domain/repository/product_item.go -destination=./pkg/mock/repository/product_item.go -package=mock
mockgen -source=./pkg/domain/repository/product_statistics.go -destination=./pkg/mock/repository/product_statistics.go -package=mock
mockgen -source=./pkg/domain/repository/tags.go -destination=./pkg/mock/repository/tags.go -package=mock
mockgen -source=./pkg/domain/repository/kyc.go -destination=./pkg/mock/repository/kyc.go -package=mock
mockgen -source=./pkg/domain/repository/category.go -destination=./pkg/mock/repository/category.go -package=mock
@echo "Generate mock files successfully" @echo "Generate mock files successfully"

View File

@ -1,7 +1,7 @@
########### ###########
# BUILDER # # BUILDER #
########### ###########
FROM golang:1.24.0 as builder FROM golang:1.24.2 AS builder
ARG VERSION ARG VERSION
ARG BUILT ARG BUILT
@ -25,14 +25,22 @@ RUN mkdir -p /root/.ssh && \
echo "$SSH_PRV_KEY" > /root/.ssh/id_rsa && \ echo "$SSH_PRV_KEY" > /root/.ssh/id_rsa && \
chmod 600 /root/.ssh/id_rsa chmod 600 /root/.ssh/id_rsa
RUN --mount=type=ssh go mod download RUN --mount=type=ssh go mod download
RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build \ RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build \
-ldflags "$FLAG" \ -ldflags "$FLAG" \
-o product -o product
##########
## upx ##
#########
# 壓縮執行黨,讓他變小
FROM gruebel/upx:latest as upx
WORKDIR /app
COPY --from=builder /app/product /app/product
# Compress the binary and copy it to final image
RUN upx --best --lzma /app/product
########## ##########
## FINAL # ## FINAL #
########## ##########
@ -40,7 +48,7 @@ RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build \
FROM gcr.io/distroless/static-debian11 FROM gcr.io/distroless/static-debian11
WORKDIR /app WORKDIR /app
COPY --from=builder /app/product /app/product COPY --from=upx /app/product /app/product
COPY --from=builder /app/etc/product.yaml /app/etc/product.yaml COPY --from=builder /app/etc/product.yaml /app/etc/product.yaml
EXPOSE 8080 EXPOSE 8080
CMD ["/app/product"] CMD ["/app/product"]

View File

@ -0,0 +1,104 @@
// Code generated by goctl. DO NOT EDIT.
// goctl 1.8.1
// Source: product.proto
package category_service
import (
"context"
"code.30cm.net/digimon/app-cloudep-product-service/gen_result/pb/product"
"github.com/zeromicro/go-zero/zrpc"
"google.golang.org/grpc"
)
type (
Category = product.Category
CategoryReq = product.CategoryReq
CreateCategoryReq = product.CreateCategoryReq
CreateKycReq = product.CreateKycReq
CreateProductItemRequest = product.CreateProductItemRequest
CreateTagsReq = product.CreateTagsReq
CustomField = product.CustomField
DeleteProductItemRequest = product.DeleteProductItemRequest
DeleteProductItemsByReferenceIDReq = product.DeleteProductItemsByReferenceIDReq
FindKycByIDReq = product.FindKycByIDReq
FindLatestKycByUIDReq = product.FindLatestKycByUIDReq
GetProductItemRequest = product.GetProductItemRequest
IncDecSalesCountRequest = product.IncDecSalesCountRequest
Kyc = product.Kyc
ListCategoryReq = product.ListCategoryReq
ListCategoryResp = product.ListCategoryResp
ListKycReq = product.ListKycReq
ListKycResp = product.ListKycResp
ListProductItemRequest = product.ListProductItemRequest
ListProductItemResponse = product.ListProductItemResponse
ListTagsReq = product.ListTagsReq
ListTagsResp = product.ListTagsResp
Media = product.Media
ModifyCategoryReq = product.ModifyCategoryReq
ModifyTagsReq = product.ModifyTagsReq
NoneReq = product.NoneReq
OKResp = product.OKResp
ProductItem = product.ProductItem
Tags = product.Tags
TagsReq = product.TagsReq
UpdateKycInfoReq = product.UpdateKycInfoReq
UpdateKycStatusReq = product.UpdateKycStatusReq
UpdateProductItemRequest = product.UpdateProductItemRequest
UpdateStatusRequest = product.UpdateStatusRequest
CategoryService interface {
// Create 建立 product 分類
Create(ctx context.Context, in *CreateCategoryReq, opts ...grpc.CallOption) (*OKResp, error)
// Modify 修改 product 分類名稱
Modify(ctx context.Context, in *ModifyCategoryReq, opts ...grpc.CallOption) (*OKResp, error)
// Delete 刪除 product 分類
Delete(ctx context.Context, in *CategoryReq, opts ...grpc.CallOption) (*OKResp, error)
// Get 取得 product 分類
Get(ctx context.Context, in *CategoryReq, opts ...grpc.CallOption) (*Category, error)
// List 建立 product 分類
List(ctx context.Context, in *ListCategoryReq, opts ...grpc.CallOption) (*ListCategoryResp, error)
}
defaultCategoryService struct {
cli zrpc.Client
}
)
func NewCategoryService(cli zrpc.Client) CategoryService {
return &defaultCategoryService{
cli: cli,
}
}
// Create 建立 product 分類
func (m *defaultCategoryService) Create(ctx context.Context, in *CreateCategoryReq, opts ...grpc.CallOption) (*OKResp, error) {
client := product.NewCategory_ServiceClient(m.cli.Conn())
return client.Create(ctx, in, opts...)
}
// Modify 修改 product 分類名稱
func (m *defaultCategoryService) Modify(ctx context.Context, in *ModifyCategoryReq, opts ...grpc.CallOption) (*OKResp, error) {
client := product.NewCategory_ServiceClient(m.cli.Conn())
return client.Modify(ctx, in, opts...)
}
// Delete 刪除 product 分類
func (m *defaultCategoryService) Delete(ctx context.Context, in *CategoryReq, opts ...grpc.CallOption) (*OKResp, error) {
client := product.NewCategory_ServiceClient(m.cli.Conn())
return client.Delete(ctx, in, opts...)
}
// Get 取得 product 分類
func (m *defaultCategoryService) Get(ctx context.Context, in *CategoryReq, opts ...grpc.CallOption) (*Category, error) {
client := product.NewCategory_ServiceClient(m.cli.Conn())
return client.Get(ctx, in, opts...)
}
// List 建立 product 分類
func (m *defaultCategoryService) List(ctx context.Context, in *ListCategoryReq, opts ...grpc.CallOption) (*ListCategoryResp, error) {
client := product.NewCategory_ServiceClient(m.cli.Conn())
return client.List(ctx, in, opts...)
}

View File

@ -0,0 +1,112 @@
// Code generated by goctl. DO NOT EDIT.
// goctl 1.8.1
// Source: product.proto
package kyc_service
import (
"context"
"code.30cm.net/digimon/app-cloudep-product-service/gen_result/pb/product"
"github.com/zeromicro/go-zero/zrpc"
"google.golang.org/grpc"
)
type (
Category = product.Category
CategoryReq = product.CategoryReq
CreateCategoryReq = product.CreateCategoryReq
CreateKycReq = product.CreateKycReq
CreateProductItemRequest = product.CreateProductItemRequest
CreateTagsReq = product.CreateTagsReq
CustomField = product.CustomField
DeleteProductItemRequest = product.DeleteProductItemRequest
DeleteProductItemsByReferenceIDReq = product.DeleteProductItemsByReferenceIDReq
FindKycByIDReq = product.FindKycByIDReq
FindLatestKycByUIDReq = product.FindLatestKycByUIDReq
GetProductItemRequest = product.GetProductItemRequest
IncDecSalesCountRequest = product.IncDecSalesCountRequest
Kyc = product.Kyc
ListCategoryReq = product.ListCategoryReq
ListCategoryResp = product.ListCategoryResp
ListKycReq = product.ListKycReq
ListKycResp = product.ListKycResp
ListProductItemRequest = product.ListProductItemRequest
ListProductItemResponse = product.ListProductItemResponse
ListTagsReq = product.ListTagsReq
ListTagsResp = product.ListTagsResp
Media = product.Media
ModifyCategoryReq = product.ModifyCategoryReq
ModifyTagsReq = product.ModifyTagsReq
NoneReq = product.NoneReq
OKResp = product.OKResp
ProductItem = product.ProductItem
Tags = product.Tags
TagsReq = product.TagsReq
UpdateKycInfoReq = product.UpdateKycInfoReq
UpdateKycStatusReq = product.UpdateKycStatusReq
UpdateProductItemRequest = product.UpdateProductItemRequest
UpdateStatusRequest = product.UpdateStatusRequest
KycService interface {
// Create 建立 KYC 資料
Create(ctx context.Context, in *CreateKycReq, opts ...grpc.CallOption) (*OKResp, error)
// FindLatestByUID 根據使用者 UID 查詢最新 KYC 紀錄
FindLatestByUid(ctx context.Context, in *FindLatestKycByUIDReq, opts ...grpc.CallOption) (*Kyc, error)
// FindByID 根據 KYC ID 查詢
FindById(ctx context.Context, in *FindKycByIDReq, opts ...grpc.CallOption) (*Kyc, error)
// List 分頁查詢 Kyc 清單(後台審核用)
List(ctx context.Context, in *ListKycReq, opts ...grpc.CallOption) (*ListKycResp, error)
// UpdateStatus 更新 Kyc 審核狀態與原因
UpdateStatus(ctx context.Context, in *UpdateKycStatusReq, opts ...grpc.CallOption) (*OKResp, error)
// Update 更新使用者的 Kyc尚未審核
Update(ctx context.Context, in *UpdateKycInfoReq, opts ...grpc.CallOption) (*OKResp, error)
}
defaultKycService struct {
cli zrpc.Client
}
)
func NewKycService(cli zrpc.Client) KycService {
return &defaultKycService{
cli: cli,
}
}
// Create 建立 KYC 資料
func (m *defaultKycService) Create(ctx context.Context, in *CreateKycReq, opts ...grpc.CallOption) (*OKResp, error) {
client := product.NewKyc_ServiceClient(m.cli.Conn())
return client.Create(ctx, in, opts...)
}
// FindLatestByUID 根據使用者 UID 查詢最新 KYC 紀錄
func (m *defaultKycService) FindLatestByUid(ctx context.Context, in *FindLatestKycByUIDReq, opts ...grpc.CallOption) (*Kyc, error) {
client := product.NewKyc_ServiceClient(m.cli.Conn())
return client.FindLatestByUid(ctx, in, opts...)
}
// FindByID 根據 KYC ID 查詢
func (m *defaultKycService) FindById(ctx context.Context, in *FindKycByIDReq, opts ...grpc.CallOption) (*Kyc, error) {
client := product.NewKyc_ServiceClient(m.cli.Conn())
return client.FindById(ctx, in, opts...)
}
// List 分頁查詢 Kyc 清單(後台審核用)
func (m *defaultKycService) List(ctx context.Context, in *ListKycReq, opts ...grpc.CallOption) (*ListKycResp, error) {
client := product.NewKyc_ServiceClient(m.cli.Conn())
return client.List(ctx, in, opts...)
}
// UpdateStatus 更新 Kyc 審核狀態與原因
func (m *defaultKycService) UpdateStatus(ctx context.Context, in *UpdateKycStatusReq, opts ...grpc.CallOption) (*OKResp, error) {
client := product.NewKyc_ServiceClient(m.cli.Conn())
return client.UpdateStatus(ctx, in, opts...)
}
// Update 更新使用者的 Kyc尚未審核
func (m *defaultKycService) Update(ctx context.Context, in *UpdateKycInfoReq, opts ...grpc.CallOption) (*OKResp, error) {
client := product.NewKyc_ServiceClient(m.cli.Conn())
return client.Update(ctx, in, opts...)
}

View File

@ -1,29 +0,0 @@
// Code generated by goctl. DO NOT EDIT.
// goctl 1.8.1
// Source: product.proto
package product
import (
"context"
"app-cloudep-product-service/gen_result/pb/product"
"github.com/zeromicro/go-zero/zrpc"
"google.golang.org/grpc"
)
type (
Product interface {
}
defaultProduct struct {
cli zrpc.Client
}
)
func NewProduct(cli zrpc.Client) Product {
return &defaultProduct{
cli: cli,
}
}

View File

@ -0,0 +1,136 @@
// Code generated by goctl. DO NOT EDIT.
// goctl 1.8.1
// Source: product.proto
package product_item_service
import (
"context"
"code.30cm.net/digimon/app-cloudep-product-service/gen_result/pb/product"
"github.com/zeromicro/go-zero/zrpc"
"google.golang.org/grpc"
)
type (
Category = product.Category
CategoryReq = product.CategoryReq
CreateCategoryReq = product.CreateCategoryReq
CreateKycReq = product.CreateKycReq
CreateProductItemRequest = product.CreateProductItemRequest
CreateTagsReq = product.CreateTagsReq
CustomField = product.CustomField
DeleteProductItemRequest = product.DeleteProductItemRequest
DeleteProductItemsByReferenceIDReq = product.DeleteProductItemsByReferenceIDReq
FindKycByIDReq = product.FindKycByIDReq
FindLatestKycByUIDReq = product.FindLatestKycByUIDReq
GetProductItemRequest = product.GetProductItemRequest
IncDecSalesCountRequest = product.IncDecSalesCountRequest
Kyc = product.Kyc
ListCategoryReq = product.ListCategoryReq
ListCategoryResp = product.ListCategoryResp
ListKycReq = product.ListKycReq
ListKycResp = product.ListKycResp
ListProductItemRequest = product.ListProductItemRequest
ListProductItemResponse = product.ListProductItemResponse
ListTagsReq = product.ListTagsReq
ListTagsResp = product.ListTagsResp
Media = product.Media
ModifyCategoryReq = product.ModifyCategoryReq
ModifyTagsReq = product.ModifyTagsReq
NoneReq = product.NoneReq
OKResp = product.OKResp
ProductItem = product.ProductItem
Tags = product.Tags
TagsReq = product.TagsReq
UpdateKycInfoReq = product.UpdateKycInfoReq
UpdateKycStatusReq = product.UpdateKycStatusReq
UpdateProductItemRequest = product.UpdateProductItemRequest
UpdateStatusRequest = product.UpdateStatusRequest
ProductItemService interface {
// Create 建立 ProductItem
Create(ctx context.Context, in *CreateProductItemRequest, opts ...grpc.CallOption) (*OKResp, error)
// GetProductItem 取得 ProductItem
Get(ctx context.Context, in *GetProductItemRequest, opts ...grpc.CallOption) (*ProductItem, error)
// ListByProductId 使用 ProductID 取得 ProductItems
ListByProductId(ctx context.Context, in *ListProductItemRequest, opts ...grpc.CallOption) (*ListProductItemResponse, error)
// Delete 刪除 Delete Product Item
Delete(ctx context.Context, in *DeleteProductItemRequest, opts ...grpc.CallOption) (*OKResp, error)
// DeleteByReferenceId 使用 ProductID 刪除所有 Item
DeleteByReferenceId(ctx context.Context, in *DeleteProductItemsByReferenceIDReq, opts ...grpc.CallOption) (*OKResp, error)
// IncSalesCount 增加賣出數量
IncSalesCount(ctx context.Context, in *IncDecSalesCountRequest, opts ...grpc.CallOption) (*OKResp, error)
// DecSalesCount 減少賣出數量
DecSalesCount(ctx context.Context, in *IncDecSalesCountRequest, opts ...grpc.CallOption) (*OKResp, error)
// Update 更新 Item
Update(ctx context.Context, in *UpdateProductItemRequest, opts ...grpc.CallOption) (*OKResp, error)
// UpdateStatus 更新 Item status
UpdateStatus(ctx context.Context, in *UpdateStatusRequest, opts ...grpc.CallOption) (*OKResp, error)
}
defaultProductItemService struct {
cli zrpc.Client
}
)
func NewProductItemService(cli zrpc.Client) ProductItemService {
return &defaultProductItemService{
cli: cli,
}
}
// Create 建立 ProductItem
func (m *defaultProductItemService) Create(ctx context.Context, in *CreateProductItemRequest, opts ...grpc.CallOption) (*OKResp, error) {
client := product.NewProduct_Item_ServiceClient(m.cli.Conn())
return client.Create(ctx, in, opts...)
}
// GetProductItem 取得 ProductItem
func (m *defaultProductItemService) Get(ctx context.Context, in *GetProductItemRequest, opts ...grpc.CallOption) (*ProductItem, error) {
client := product.NewProduct_Item_ServiceClient(m.cli.Conn())
return client.Get(ctx, in, opts...)
}
// ListByProductId 使用 ProductID 取得 ProductItems
func (m *defaultProductItemService) ListByProductId(ctx context.Context, in *ListProductItemRequest, opts ...grpc.CallOption) (*ListProductItemResponse, error) {
client := product.NewProduct_Item_ServiceClient(m.cli.Conn())
return client.ListByProductId(ctx, in, opts...)
}
// Delete 刪除 Delete Product Item
func (m *defaultProductItemService) Delete(ctx context.Context, in *DeleteProductItemRequest, opts ...grpc.CallOption) (*OKResp, error) {
client := product.NewProduct_Item_ServiceClient(m.cli.Conn())
return client.Delete(ctx, in, opts...)
}
// DeleteByReferenceId 使用 ProductID 刪除所有 Item
func (m *defaultProductItemService) DeleteByReferenceId(ctx context.Context, in *DeleteProductItemsByReferenceIDReq, opts ...grpc.CallOption) (*OKResp, error) {
client := product.NewProduct_Item_ServiceClient(m.cli.Conn())
return client.DeleteByReferenceId(ctx, in, opts...)
}
// IncSalesCount 增加賣出數量
func (m *defaultProductItemService) IncSalesCount(ctx context.Context, in *IncDecSalesCountRequest, opts ...grpc.CallOption) (*OKResp, error) {
client := product.NewProduct_Item_ServiceClient(m.cli.Conn())
return client.IncSalesCount(ctx, in, opts...)
}
// DecSalesCount 減少賣出數量
func (m *defaultProductItemService) DecSalesCount(ctx context.Context, in *IncDecSalesCountRequest, opts ...grpc.CallOption) (*OKResp, error) {
client := product.NewProduct_Item_ServiceClient(m.cli.Conn())
return client.DecSalesCount(ctx, in, opts...)
}
// Update 更新 Item
func (m *defaultProductItemService) Update(ctx context.Context, in *UpdateProductItemRequest, opts ...grpc.CallOption) (*OKResp, error) {
client := product.NewProduct_Item_ServiceClient(m.cli.Conn())
return client.Update(ctx, in, opts...)
}
// UpdateStatus 更新 Item status
func (m *defaultProductItemService) UpdateStatus(ctx context.Context, in *UpdateStatusRequest, opts ...grpc.CallOption) (*OKResp, error) {
client := product.NewProduct_Item_ServiceClient(m.cli.Conn())
return client.UpdateStatus(ctx, in, opts...)
}

View File

@ -0,0 +1,104 @@
// Code generated by goctl. DO NOT EDIT.
// goctl 1.8.1
// Source: product.proto
package tag_service
import (
"context"
"code.30cm.net/digimon/app-cloudep-product-service/gen_result/pb/product"
"github.com/zeromicro/go-zero/zrpc"
"google.golang.org/grpc"
)
type (
Category = product.Category
CategoryReq = product.CategoryReq
CreateCategoryReq = product.CreateCategoryReq
CreateKycReq = product.CreateKycReq
CreateProductItemRequest = product.CreateProductItemRequest
CreateTagsReq = product.CreateTagsReq
CustomField = product.CustomField
DeleteProductItemRequest = product.DeleteProductItemRequest
DeleteProductItemsByReferenceIDReq = product.DeleteProductItemsByReferenceIDReq
FindKycByIDReq = product.FindKycByIDReq
FindLatestKycByUIDReq = product.FindLatestKycByUIDReq
GetProductItemRequest = product.GetProductItemRequest
IncDecSalesCountRequest = product.IncDecSalesCountRequest
Kyc = product.Kyc
ListCategoryReq = product.ListCategoryReq
ListCategoryResp = product.ListCategoryResp
ListKycReq = product.ListKycReq
ListKycResp = product.ListKycResp
ListProductItemRequest = product.ListProductItemRequest
ListProductItemResponse = product.ListProductItemResponse
ListTagsReq = product.ListTagsReq
ListTagsResp = product.ListTagsResp
Media = product.Media
ModifyCategoryReq = product.ModifyCategoryReq
ModifyTagsReq = product.ModifyTagsReq
NoneReq = product.NoneReq
OKResp = product.OKResp
ProductItem = product.ProductItem
Tags = product.Tags
TagsReq = product.TagsReq
UpdateKycInfoReq = product.UpdateKycInfoReq
UpdateKycStatusReq = product.UpdateKycStatusReq
UpdateProductItemRequest = product.UpdateProductItemRequest
UpdateStatusRequest = product.UpdateStatusRequest
TagService interface {
// CreateTags 建立 tags
Create(ctx context.Context, in *CreateTagsReq, opts ...grpc.CallOption) (*OKResp, error)
// ModifyTags 修改 tags
Modify(ctx context.Context, in *ModifyTagsReq, opts ...grpc.CallOption) (*OKResp, error)
// DeleteTags 刪除tags
Delete(ctx context.Context, in *TagsReq, opts ...grpc.CallOption) (*OKResp, error)
// GetTags 取得 tags
Get(ctx context.Context, in *TagsReq, opts ...grpc.CallOption) (*Tags, error)
// ListTags 建立 tags
List(ctx context.Context, in *ListTagsReq, opts ...grpc.CallOption) (*ListTagsResp, error)
}
defaultTagService struct {
cli zrpc.Client
}
)
func NewTagService(cli zrpc.Client) TagService {
return &defaultTagService{
cli: cli,
}
}
// CreateTags 建立 tags
func (m *defaultTagService) Create(ctx context.Context, in *CreateTagsReq, opts ...grpc.CallOption) (*OKResp, error) {
client := product.NewTag_ServiceClient(m.cli.Conn())
return client.Create(ctx, in, opts...)
}
// ModifyTags 修改 tags
func (m *defaultTagService) Modify(ctx context.Context, in *ModifyTagsReq, opts ...grpc.CallOption) (*OKResp, error) {
client := product.NewTag_ServiceClient(m.cli.Conn())
return client.Modify(ctx, in, opts...)
}
// DeleteTags 刪除tags
func (m *defaultTagService) Delete(ctx context.Context, in *TagsReq, opts ...grpc.CallOption) (*OKResp, error) {
client := product.NewTag_ServiceClient(m.cli.Conn())
return client.Delete(ctx, in, opts...)
}
// GetTags 取得 tags
func (m *defaultTagService) Get(ctx context.Context, in *TagsReq, opts ...grpc.CallOption) (*Tags, error) {
client := product.NewTag_ServiceClient(m.cli.Conn())
return client.Get(ctx, in, opts...)
}
// ListTags 建立 tags
func (m *defaultTagService) List(ctx context.Context, in *ListTagsReq, opts ...grpc.CallOption) (*ListTagsResp, error) {
client := product.NewTag_ServiceClient(m.cli.Conn())
return client.List(ctx, in, opts...)
}

View File

@ -4,3 +4,26 @@ Etcd:
Hosts: Hosts:
- 127.0.0.1:2379 - 127.0.0.1:2379
Key: product.rpc Key: product.rpc
Cache:
- Host: 127.0.0.1:6379
type: node
CacheExpireTime: 1s
CacheWithNotFoundExpiry: 1s
Mongo:
Schema: mongodb+srv
Host: dev.pwj5m.mongodb.net
User: "service"
Password: "lReiYk7GRjH4RUqH"
Port: "27017"
Database: play_product
ReplicaName: "rs0"
MaxStaleness: 30m
MaxPoolSize: 30
MinPoolSize: 10
MaxConnIdleTime: 30m
Compressors:
- f
EnableStandardReadWriteSplitMode: true
ConnectTimeoutMs : 300

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -3,4 +3,321 @@ syntax = "proto3";
package product; package product;
option go_package="./product"; option go_package="./product";
service Product {}
// OKResp
message OKResp {}
// NoneReq
message NoneReq {}
// ====================== Category ======================
message CreateCategoryReq {
string name = 1;
}
message ModifyCategoryReq {
string id =1;
string name = 2;
}
message CategoryReq {
string id =1;
}
message Category {
string id =1;
string name =2;
int64 create_time=3;
int64 update_time=4;
}
message ListCategoryReq {
int64 page_index =1;
int64 page_size =2;
repeated string ids=3;
}
message ListCategoryResp {
int64 total =1;
repeated Category data=3;
}
service Category_Service {
// Create product
rpc Create(CreateCategoryReq) returns(OKResp);
// Modify product
rpc Modify(ModifyCategoryReq) returns(OKResp);
// Delete product
rpc Delete(CategoryReq) returns(OKResp);
// Get product
rpc Get(CategoryReq) returns(Category);
// List product
rpc List(ListCategoryReq) returns(ListCategoryResp);
}
// ====================== Tags Param ======================
message CreateTagsReq{
string types=1;
string name=2;
string show_type=3;
optional string cover=4;
}
message ModifyTagsReq{
string id=1;
optional string types=2;
optional string name=3;
optional string show_type=4;
optional string cover=5;
}
message TagsReq {
string id =1;
}
message Tags{
string id=1;
string types=2;
string name=3;
string show_type=4;
string cover=5;
int64 update_at=6;
int64 created_at=7;
}
message ListTagsReq {
int64 page_index =1;
int64 page_size =2;
repeated string ids=3;
optional string name=4;
optional string types=5;
optional string show_type=6;
}
message ListTagsResp {
int64 total =1;
repeated Tags data=3;
}
service Tag_Service {
// CreateTags tags
rpc Create(CreateTagsReq) returns(OKResp);
// ModifyTags tags
rpc Modify(ModifyTagsReq) returns(OKResp);
// DeleteTags tags
rpc Delete(TagsReq) returns(OKResp);
// GetTags tags
rpc Get(TagsReq) returns(Tags);
// ListTags tags
rpc List(ListTagsReq) returns(ListTagsResp);
}
// ====================== KYC Param ======================
message Kyc {
string id = 1; // MongoDB ObjectID
string uid = 2; // UID
string country_region = 3; // "TW", "JP", "US"...
string name = 4; //
string identification = 5; // or
string identification_type = 6; // ID ID_CARD, PASSPORT, RESIDENT_CERT
string address = 7; //
string postal_code = 8; // 使
string id_front_image = 9; // /
string id_back_image = 10; // /
string bank_statement_img = 11; //
string bank_code = 12; // SWIFT
string bank_name = 13; //
string branch_code = 14; //
string branch_name = 15; //
string bank_account = 16; //
string status = 17; // PENDING, APPROVED, REJECTED
string reject_reason = 18; //
int64 updated_at = 19; // timestamp
int64 created_at = 20; // timestamp
}
message CreateKycReq {
string uid = 1;
string country_region = 2;
string name = 3;
string identification = 4;
string identification_type = 5;
string address = 6;
string postal_code = 7;
string id_front_image = 8;
string id_back_image = 9;
string bank_statement_img = 10;
string bank_code = 11;
string bank_name = 12;
string branch_code = 13;
string branch_name = 14;
string bank_account = 15;
}
message FindLatestKycByUIDReq {
string uid = 1;
}
message FindKycByIDReq {
string id = 1;
}
message ListKycReq {
optional string uid = 1;
optional string country = 2;
optional string status = 3; // enum
int64 page_size = 4;
int64 page_index = 5;
bool sort_by_date = 6;
}
message ListKycResp {
repeated Kyc list = 1;
int64 total = 2;
}
message UpdateKycStatusReq {
string id = 1;
string status = 2; // enumPENDING, APPROVED, REJECTED
optional string reason = 3;
}
message UpdateKycInfoReq {
string id = 1;
optional string name = 2;
optional string identification = 3;
optional string identification_type = 4;
optional string address = 5;
optional string postal_code = 6;
optional string id_front_image = 7;
optional string id_back_image = 8;
optional string bank_statement_img = 9;
optional string bank_code = 10;
optional string bank_name = 11;
optional string branch_code = 12;
optional string branch_name = 13;
optional string bank_account = 14;
}
service Kyc_Service{
// Create KYC
rpc Create (CreateKycReq) returns (OKResp);
// FindLatestByUID 使 UID KYC
rpc FindLatestByUid (FindLatestKycByUIDReq) returns (Kyc);
// FindByID KYC ID
rpc FindById (FindKycByIDReq) returns (Kyc);
// List Kyc
rpc List (ListKycReq) returns (ListKycResp);
// UpdateStatus Kyc
rpc UpdateStatus (UpdateKycStatusReq) returns (OKResp);
// Update 使 Kyc
rpc Update (UpdateKycInfoReq) returns (OKResp);
}
// ====================== Product Item Param ======================
message ProductItem {
optional string id = 1;
string reference_id = 2;
string name = 3;
string description = 4;
string short_description = 5;
bool is_un_limit = 6;
bool is_free = 7;
uint64 stock = 8;
string price = 9;
string sku = 10;
string time_series = 11;
repeated Media media = 12;
string status = 13;
repeated CustomField freight = 14;
repeated CustomField custom_fields = 15;
uint64 sales_count = 16;
optional int64 updated_at = 17;
optional int64 created_at = 18;
}
message Media {
string url = 1;
string type = 2;
optional uint64 sort=3;
}
message CustomField {
string key = 1;
string value = 2;
}
message CreateProductItemRequest {
ProductItem item = 1;
}
message GetProductItemRequest {
string id = 1;
}
message ListProductItemRequest {
int64 page_size = 1;
int64 page_index = 2;
string reference_id = 3;
optional bool is_un_limit = 4;
optional bool is_free = 5;
optional string status = 6;
}
message ListProductItemResponse{
repeated ProductItem data = 1;
int64 total = 2;
}
message DeleteProductItemRequest {
repeated string id = 1;
}
message DeleteProductItemsByReferenceIDReq {
string id = 1;
}
message IncDecSalesCountRequest {
string id = 1;
uint64 count = 2;
}
message UpdateProductItemRequest {
string id = 1;
optional string name = 2;
optional string description = 3;
optional string short_description = 4;
optional bool is_un_limit = 5;
optional bool is_free = 6;
optional uint64 stock = 7;
optional string price = 8;
optional string sku = 9;
optional string time_series = 10;
repeated Media media = 11;
optional string status = 12;
repeated CustomField freight = 13;
repeated CustomField custom_fields = 14;
}
message UpdateStatusRequest {
string id = 1;
string status = 2;
}
service Product_Item_Service {
// Create ProductItem
rpc Create(CreateProductItemRequest) returns (OKResp);
// GetProductItem ProductItem
rpc Get(GetProductItemRequest) returns (ProductItem);
// ListByProductId 使 ProductID ProductItems
rpc ListByProductId(ListProductItemRequest) returns (ListProductItemResponse);
// Delete Delete Product Item
rpc Delete(DeleteProductItemRequest) returns (OKResp);
// DeleteByReferenceId 使 ProductID Item
rpc DeleteByReferenceId(DeleteProductItemsByReferenceIDReq) returns (OKResp);
// IncSalesCount
rpc IncSalesCount(IncDecSalesCountRequest) returns (OKResp);
// DecSalesCount
rpc DecSalesCount(IncDecSalesCountRequest) returns (OKResp);
// Update Item
rpc Update(UpdateProductItemRequest) returns (OKResp);
// UpdateStatus Item status
rpc UpdateStatus(UpdateStatusRequest) returns (OKResp);
}
// ====================== Product Param ======================

54
go.mod
View File

@ -1,25 +1,49 @@
module code.30cm.net/digimon/app-cloudep-product-service module code.30cm.net/digimon/app-cloudep-product-service
go 1.24.0 go 1.24.2
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/golang/snappy v0.0.4
github.com/shopspring/decimal v1.4.0
github.com/stretchr/testify v1.10.0
github.com/testcontainers/testcontainers-go v0.34.0
github.com/zeromicro/go-zero v1.8.1 github.com/zeromicro/go-zero v1.8.1
go.mongodb.org/mongo-driver v1.17.3
go.uber.org/mock v0.5.0
google.golang.org/grpc v1.71.0 google.golang.org/grpc v1.71.0
google.golang.org/protobuf v1.36.5 google.golang.org/protobuf v1.36.5
) )
require ( require (
dario.cat/mergo v1.0.0 // indirect
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect
github.com/Microsoft/go-winio v0.6.2 // indirect
github.com/alicebob/gopher-json v0.0.0-20230218143504-906a9b012302 // indirect
github.com/beorn7/perks v1.0.1 // indirect github.com/beorn7/perks v1.0.1 // indirect
github.com/cenkalti/backoff/v4 v4.3.0 // indirect github.com/cenkalti/backoff/v4 v4.3.0 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/containerd/containerd v1.7.18 // indirect
github.com/containerd/log v0.1.0 // indirect
github.com/containerd/platforms v0.2.1 // indirect
github.com/coreos/go-semver v0.3.1 // indirect github.com/coreos/go-semver v0.3.1 // indirect
github.com/coreos/go-systemd/v22 v22.5.0 // indirect github.com/coreos/go-systemd/v22 v22.5.0 // indirect
github.com/cpuguy83/dockercfg v0.3.2 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect github.com/davecgh/go-spew v1.1.1 // indirect
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
github.com/distribution/reference v0.6.0 // indirect
github.com/docker/docker v27.1.1+incompatible // indirect
github.com/docker/go-connections v0.5.0 // indirect
github.com/docker/go-units v0.5.0 // indirect
github.com/emicklei/go-restful/v3 v3.11.0 // indirect github.com/emicklei/go-restful/v3 v3.11.0 // indirect
github.com/fatih/color v1.18.0 // indirect github.com/fatih/color v1.18.0 // indirect
github.com/felixge/httpsnoop v1.0.4 // indirect
github.com/go-logr/logr v1.4.2 // indirect github.com/go-logr/logr v1.4.2 // indirect
github.com/go-logr/stdr v1.2.2 // indirect github.com/go-logr/stdr v1.2.2 // indirect
github.com/go-ole/go-ole v1.2.6 // indirect
github.com/go-openapi/jsonpointer v0.19.6 // indirect github.com/go-openapi/jsonpointer v0.19.6 // indirect
github.com/go-openapi/jsonreference v0.20.2 // indirect github.com/go-openapi/jsonreference v0.20.2 // indirect
github.com/go-openapi/swag v0.22.4 // indirect github.com/go-openapi/swag v0.22.4 // indirect
@ -34,24 +58,50 @@ require (
github.com/josharian/intern v1.0.0 // indirect github.com/josharian/intern v1.0.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect github.com/json-iterator/go v1.1.12 // indirect
github.com/klauspost/compress v1.17.11 // indirect github.com/klauspost/compress v1.17.11 // indirect
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect
github.com/magiconair/properties v1.8.7 // indirect
github.com/mailru/easyjson v0.7.7 // indirect github.com/mailru/easyjson v0.7.7 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect github.com/mattn/go-isatty v0.0.20 // indirect
github.com/moby/docker-image-spec v1.3.1 // indirect
github.com/moby/patternmatcher v0.6.0 // indirect
github.com/moby/sys/sequential v0.5.0 // indirect
github.com/moby/sys/user v0.1.0 // indirect
github.com/moby/term v0.5.0 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/montanaflynn/stats v0.7.1 // indirect
github.com/morikuni/aec v1.0.0 // indirect
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
github.com/opencontainers/go-digest v1.0.0 // indirect
github.com/opencontainers/image-spec v1.1.0 // indirect
github.com/openzipkin/zipkin-go v0.4.3 // indirect github.com/openzipkin/zipkin-go v0.4.3 // indirect
github.com/pelletier/go-toml/v2 v2.2.2 // indirect github.com/pelletier/go-toml/v2 v2.2.2 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect
github.com/prometheus/client_golang v1.21.0 // indirect github.com/prometheus/client_golang v1.21.0 // indirect
github.com/prometheus/client_model v0.6.1 // indirect github.com/prometheus/client_model v0.6.1 // indirect
github.com/prometheus/common v0.62.0 // indirect github.com/prometheus/common v0.62.0 // indirect
github.com/prometheus/procfs v0.15.1 // indirect github.com/prometheus/procfs v0.15.1 // indirect
github.com/redis/go-redis/v9 v9.7.1 // indirect github.com/redis/go-redis/v9 v9.7.1 // indirect
github.com/shirou/gopsutil/v3 v3.23.12 // indirect
github.com/shoenig/go-m1cpu v0.1.6 // indirect
github.com/sirupsen/logrus v1.9.3 // indirect
github.com/spaolacci/murmur3 v1.1.0 // indirect github.com/spaolacci/murmur3 v1.1.0 // indirect
github.com/tklauser/go-sysconf v0.3.12 // indirect
github.com/tklauser/numcpus v0.6.1 // indirect
github.com/xdg-go/pbkdf2 v1.0.0 // indirect
github.com/xdg-go/scram v1.1.2 // indirect
github.com/xdg-go/stringprep v1.0.4 // indirect
github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 // indirect
github.com/yuin/gopher-lua v1.1.1 // indirect
github.com/yusufpapurcu/wmi v1.2.3 // indirect
go.etcd.io/etcd/api/v3 v3.5.15 // indirect go.etcd.io/etcd/api/v3 v3.5.15 // indirect
go.etcd.io/etcd/client/pkg/v3 v3.5.15 // indirect go.etcd.io/etcd/client/pkg/v3 v3.5.15 // indirect
go.etcd.io/etcd/client/v3 v3.5.15 // indirect go.etcd.io/etcd/client/v3 v3.5.15 // indirect
go.opentelemetry.io/auto/sdk v1.1.0 // indirect go.opentelemetry.io/auto/sdk v1.1.0 // indirect
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 // indirect
go.opentelemetry.io/otel v1.34.0 // indirect go.opentelemetry.io/otel v1.34.0 // indirect
go.opentelemetry.io/otel/exporters/jaeger v1.17.0 // indirect go.opentelemetry.io/otel/exporters/jaeger v1.17.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.24.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.24.0 // indirect
@ -67,8 +117,10 @@ require (
go.uber.org/automaxprocs v1.6.0 // indirect go.uber.org/automaxprocs v1.6.0 // indirect
go.uber.org/multierr v1.9.0 // indirect go.uber.org/multierr v1.9.0 // indirect
go.uber.org/zap v1.24.0 // indirect go.uber.org/zap v1.24.0 // indirect
golang.org/x/crypto v0.33.0 // indirect
golang.org/x/net v0.35.0 // indirect golang.org/x/net v0.35.0 // indirect
golang.org/x/oauth2 v0.25.0 // indirect golang.org/x/oauth2 v0.25.0 // indirect
golang.org/x/sync v0.11.0 // indirect
golang.org/x/sys v0.30.0 // indirect golang.org/x/sys v0.30.0 // indirect
golang.org/x/term v0.29.0 // indirect golang.org/x/term v0.29.0 // indirect
golang.org/x/text v0.22.0 // indirect golang.org/x/text v0.22.0 // indirect

124
go.sum
View File

@ -1,3 +1,15 @@
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/go.mod h1:KBVKz/Ci5IheI77BgZxPUeKkaGvDy8fV8EDHSCOLIO4=
dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk=
dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk=
github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24 h1:bvDV9vkmnHYOMsOr4WLk+Vo07yKIzd94sVoIqshQ4bU=
github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24/go.mod h1:8o94RPi1/7XTJvwPpRSzSUedZrtlirdB3r9Z20bi2f8=
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOElx5B5HZ4hJQsoJ/PvUvKRhJHDQXO8P8=
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E=
github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
github.com/alicebob/gopher-json v0.0.0-20230218143504-906a9b012302 h1:uvdUDbHQHO85qeSydJtItA4T55Pw6BtAejd0APRJOCE= github.com/alicebob/gopher-json v0.0.0-20230218143504-906a9b012302 h1:uvdUDbHQHO85qeSydJtItA4T55Pw6BtAejd0APRJOCE=
github.com/alicebob/gopher-json v0.0.0-20230218143504-906a9b012302/go.mod h1:SGnFV6hVsYE877CKEZ6tDNTjaSXYUk6QqoIK6PrAtcc= github.com/alicebob/gopher-json v0.0.0-20230218143504-906a9b012302/go.mod h1:SGnFV6hVsYE877CKEZ6tDNTjaSXYUk6QqoIK6PrAtcc=
github.com/alicebob/miniredis/v2 v2.34.0 h1:mBFWMaJSNL9RwdGRyEDoAAv8OQc5UlEhLDQggTglU/0= github.com/alicebob/miniredis/v2 v2.34.0 h1:mBFWMaJSNL9RwdGRyEDoAAv8OQc5UlEhLDQggTglU/0=
@ -14,26 +26,48 @@ github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK3
github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/containerd/containerd v1.7.18 h1:jqjZTQNfXGoEaZdW1WwPU0RqSn1Bm2Ay/KJPUuO8nao=
github.com/containerd/containerd v1.7.18/go.mod h1:IYEk9/IO6wAPUz2bCMVUbsfXjzw5UNP5fLz4PsUygQ4=
github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I=
github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo=
github.com/containerd/platforms v0.2.1 h1:zvwtM3rz2YHPQsF2CHYM8+KtB5dvhISiXh5ZpSBQv6A=
github.com/containerd/platforms v0.2.1/go.mod h1:XHCb+2/hzowdiut9rkudds9bE5yJ7npe7dG/wG+uFPw=
github.com/coreos/go-semver v0.3.1 h1:yi21YpKnrx1gt5R+la8n5WgS0kCrsPp33dmEyHReZr4= github.com/coreos/go-semver v0.3.1 h1:yi21YpKnrx1gt5R+la8n5WgS0kCrsPp33dmEyHReZr4=
github.com/coreos/go-semver v0.3.1/go.mod h1:irMmmIw/7yzSRPWryHsK7EYSg09caPQL03VsM8rvUec= github.com/coreos/go-semver v0.3.1/go.mod h1:irMmmIw/7yzSRPWryHsK7EYSg09caPQL03VsM8rvUec=
github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs= github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs=
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
github.com/cpuguy83/dockercfg v0.3.2 h1:DlJTyZGBDlXqUZ2Dk2Q3xHs/FtnooJJVaad2S9GKorA=
github.com/cpuguy83/dockercfg v0.3.2/go.mod h1:sugsbF4//dDlL/i+S+rtpIWp+5h0BHJHfjj5/jFyUJc=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY=
github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk=
github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E=
github.com/docker/docker v27.1.1+incompatible h1:hO/M4MtV36kzKldqnA37IWhebRA+LnqqcqDja6kVaKY=
github.com/docker/docker v27.1.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c=
github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc=
github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=
github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
github.com/emicklei/go-restful/v3 v3.11.0 h1:rAQeMHw1c7zTmncogyy8VvRZwtkmkZ4FxERmMY4rD+g= github.com/emicklei/go-restful/v3 v3.11.0 h1:rAQeMHw1c7zTmncogyy8VvRZwtkmkZ4FxERmMY4rD+g=
github.com/emicklei/go-restful/v3 v3.11.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= github.com/emicklei/go-restful/v3 v3.11.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc=
github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM= github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM=
github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU= github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU=
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/logr v1.3.0/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/logr v1.3.0/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY=
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
github.com/go-openapi/jsonpointer v0.19.6 h1:eCs3fxoIi3Wh6vtgmLTOjdhSpiqphQ+DaPn38N2ZdrE= github.com/go-openapi/jsonpointer v0.19.6 h1:eCs3fxoIi3Wh6vtgmLTOjdhSpiqphQ+DaPn38N2ZdrE=
github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs= github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs=
github.com/go-openapi/jsonreference v0.20.2 h1:3sVjiK66+uXK/6oQ8xgcRKcFgQ5KXa2KvnJRumpMGbE= github.com/go-openapi/jsonreference v0.20.2 h1:3sVjiK66+uXK/6oQ8xgcRKcFgQ5KXa2KvnJRumpMGbE=
@ -50,8 +84,11 @@ github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc=
github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs= github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs=
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM=
github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/google/gnostic-models v0.6.8 h1:yo/ABAfM5IMRsS1VnXjTBvUb61tFIHozhlYvRgGre9I= github.com/google/gnostic-models v0.6.8 h1:yo/ABAfM5IMRsS1VnXjTBvUb61tFIHozhlYvRgGre9I=
github.com/google/gnostic-models v0.6.8/go.mod h1:5n7qKqH0f5wFt+aWF8CW6pZLLNOfYuF5OpfBSENuI8U= github.com/google/gnostic-models v0.6.8/go.mod h1:5n7qKqH0f5wFt+aWF8CW6pZLLNOfYuF5OpfBSENuI8U=
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
@ -83,6 +120,10 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ81pIr0yLvtUWk2if982qA3F3QD6H4=
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I=
github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY=
github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
@ -90,17 +131,35 @@ github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovk
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0=
github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo=
github.com/moby/patternmatcher v0.6.0 h1:GmP9lR19aU5GqSSFko+5pRqHi+Ohk1O69aFiKkVGiPk=
github.com/moby/patternmatcher v0.6.0/go.mod h1:hDPoyOpDY7OrrMDLaYoY3hf52gNCR/YOUYxkhApJIxc=
github.com/moby/sys/sequential v0.5.0 h1:OPvI35Lzn9K04PBbCLW0g4LcFAJgHsvXsRyewg5lXtc=
github.com/moby/sys/sequential v0.5.0/go.mod h1:tH2cOOs5V9MlPiXcQzRC+eEyab644PWKGRYaaV5ZZlo=
github.com/moby/sys/user v0.1.0 h1:WmZ93f5Ux6het5iituh9x2zAG7NFY9Aqi49jjE1PaQg=
github.com/moby/sys/user v0.1.0/go.mod h1:fKJhFOnsCN6xZ5gSfbM6zaHGgDJMrqt9/reuj4T7MmU=
github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0=
github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/montanaflynn/stats v0.7.1 h1:etflOAAHORrCC44V+aR6Ftzort912ZU+YLiSTuV8eaE=
github.com/montanaflynn/stats v0.7.1/go.mod h1:etXPPgVO6n31NxCd9KQUMvCM+ve0ruNzt6R8Bnaayow=
github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A=
github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc=
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
github.com/onsi/ginkgo/v2 v2.13.0 h1:0jY9lJquiL8fcf3M4LAXN5aMlS/b2BV86HFFPCPMgE4= github.com/onsi/ginkgo/v2 v2.13.0 h1:0jY9lJquiL8fcf3M4LAXN5aMlS/b2BV86HFFPCPMgE4=
github.com/onsi/ginkgo/v2 v2.13.0/go.mod h1:TE309ZR8s5FsKKpuB1YAQYBzCaAfUgatB/xlT/ETL/o= github.com/onsi/ginkgo/v2 v2.13.0/go.mod h1:TE309ZR8s5FsKKpuB1YAQYBzCaAfUgatB/xlT/ETL/o=
github.com/onsi/gomega v1.29.0 h1:KIA/t2t5UBzoirT4H9tsML45GEbo3ouUnBHsCfD2tVg= github.com/onsi/gomega v1.29.0 h1:KIA/t2t5UBzoirT4H9tsML45GEbo3ouUnBHsCfD2tVg=
github.com/onsi/gomega v1.29.0/go.mod h1:9sxs+SwGrKI0+PWe4Fxa9tFQQBG5xSsSbMXOI8PPpoQ= github.com/onsi/gomega v1.29.0/go.mod h1:9sxs+SwGrKI0+PWe4Fxa9tFQQBG5xSsSbMXOI8PPpoQ=
github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
github.com/opencontainers/image-spec v1.1.0 h1:8SG7/vwALn54lVB/0yZ/MMwhFrPYtpEHQb2IpWsCzug=
github.com/opencontainers/image-spec v1.1.0/go.mod h1:W4s4sFTMaBeK1BQLXbG4AdM2szdn85PY75RI83NrTrM=
github.com/openzipkin/zipkin-go v0.4.3 h1:9EGwpqkgnwdEIJ+Od7QVSEIH+ocmm5nPat0G7sjsSdg= github.com/openzipkin/zipkin-go v0.4.3 h1:9EGwpqkgnwdEIJ+Od7QVSEIH+ocmm5nPat0G7sjsSdg=
github.com/openzipkin/zipkin-go v0.4.3/go.mod h1:M9wCJZFWCo2RiY+o1eBCEMe0Dp2S5LDHcMZmk3RmK7c= github.com/openzipkin/zipkin-go v0.4.3/go.mod h1:M9wCJZFWCo2RiY+o1eBCEMe0Dp2S5LDHcMZmk3RmK7c=
github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM= github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM=
@ -109,6 +168,8 @@ github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF4JjgDlrVEn3C11VoGHZN7m8qihwgMEtzYw=
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
github.com/prashantv/gostub v1.1.0 h1:BTyx3RfQjRHnUWaGF9oQos79AlQ5k8WNktv7VGvVH4g= github.com/prashantv/gostub v1.1.0 h1:BTyx3RfQjRHnUWaGF9oQos79AlQ5k8WNktv7VGvVH4g=
github.com/prashantv/gostub v1.1.0/go.mod h1:A5zLQHz7ieHGG7is6LLXLz7I8+3LZzsrV0P1IAHhP5U= github.com/prashantv/gostub v1.1.0/go.mod h1:A5zLQHz7ieHGG7is6LLXLz7I8+3LZzsrV0P1IAHhP5U=
github.com/prometheus/client_golang v1.21.0 h1:DIsaGmiaBkSangBgMtWdNfxbMNdku5IK6iNhrEqWvdA= github.com/prometheus/client_golang v1.21.0 h1:DIsaGmiaBkSangBgMtWdNfxbMNdku5IK6iNhrEqWvdA=
@ -123,6 +184,16 @@ github.com/redis/go-redis/v9 v9.7.1 h1:4LhKRCIduqXqtvCUlaq9c8bdHOkICjDMrr1+Zb3os
github.com/redis/go-redis/v9 v9.7.1/go.mod h1:f6zhXITC7JUJIlPEiBOTXxJgPLdZcA93GewI7inzyWw= github.com/redis/go-redis/v9 v9.7.1/go.mod h1:f6zhXITC7JUJIlPEiBOTXxJgPLdZcA93GewI7inzyWw=
github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=
github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=
github.com/shirou/gopsutil/v3 v3.23.12 h1:z90NtUkp3bMtmICZKpC4+WaknU1eXtp5vtbQ11DgpE4=
github.com/shirou/gopsutil/v3 v3.23.12/go.mod h1:1FrWgea594Jp7qmjHUUPlJDTPgcsb9mGnXDxavtikzM=
github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM=
github.com/shoenig/go-m1cpu v0.1.6/go.mod h1:1JJMcUBvfNwpq05QDQVAnx3gUHr9IYF7GNg9SUEw2VQ=
github.com/shoenig/test v0.6.4 h1:kVTaSd7WLz5WZ2IaoM0RSzRsUD+m8wRR+5qvntpn4LU=
github.com/shoenig/test v0.6.4/go.mod h1:byHiCGXqrVaflBLAMq/srcZIHynQPQgeyvkvXnjqq0k=
github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k=
github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME=
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI= github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI=
github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
@ -133,6 +204,7 @@ github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpE
github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
@ -140,11 +212,28 @@ github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXl
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/testcontainers/testcontainers-go v0.34.0 h1:5fbgF0vIN5u+nD3IWabQwRybuB4GY8G2HHgCkbMzMHo=
github.com/testcontainers/testcontainers-go v0.34.0/go.mod h1:6P/kMkQe8yqPHfPWNulFGdFHTD8HB2vLq/231xY2iPQ=
github.com/tklauser/go-sysconf v0.3.12 h1:0QaGUFOdQaIVdPgfITYzaTegZvdCjmYO52cSFAEVmqU=
github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI=
github.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+Fk=
github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9fESEdAacY=
github.com/xdg-go/pbkdf2 v1.0.0 h1:Su7DPu48wXMwC3bs7MCNG+z4FhcyEuz5dlvchbq0B0c=
github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI=
github.com/xdg-go/scram v1.1.2 h1:FHX5I5B4i4hKRVRBCFRxq1iQRej7WO3hhBuJf+UUySY=
github.com/xdg-go/scram v1.1.2/go.mod h1:RT/sEzTbU5y00aCK8UOx6R7YryM0iF1N2MOmC3kKLN4=
github.com/xdg-go/stringprep v1.0.4 h1:XLI/Ng3O1Atzq0oBs3TWm+5ZVgkq2aqdlvP9JtoZ6c8=
github.com/xdg-go/stringprep v1.0.4/go.mod h1:mPGuuIYwz7CmR2bT9j4GbQqutWS1zV24gijq1dTyGkM=
github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 h1:ilQV1hzziu+LLM3zUTJ0trRztfwgjqKnBWNtSRkbmwM=
github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78/go.mod h1:aL8wCCfTfSfmXjznFBSZNN13rSJjlIOI1fUNAtF7rmI=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
github.com/yuin/gopher-lua v1.1.1 h1:kYKnWBjvbNP4XLT3+bPEwAXJx262OhaHDWDVOPjL46M= github.com/yuin/gopher-lua v1.1.1 h1:kYKnWBjvbNP4XLT3+bPEwAXJx262OhaHDWDVOPjL46M=
github.com/yuin/gopher-lua v1.1.1/go.mod h1:GBR0iDaNXjAgGg9zfCvksxSRnQx76gclCIb7kdAd1Pw= github.com/yuin/gopher-lua v1.1.1/go.mod h1:GBR0iDaNXjAgGg9zfCvksxSRnQx76gclCIb7kdAd1Pw=
github.com/yusufpapurcu/wmi v1.2.3 h1:E1ctvB7uKFMOJw3fdOW32DwGE9I7t++CRUEMKvFoFiw=
github.com/yusufpapurcu/wmi v1.2.3/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
github.com/zeromicro/go-zero v1.8.1 h1:iUYQEMQzS9Pb8ebzJtV3FGtv/YTjZxAh/NvLW/316wo= github.com/zeromicro/go-zero v1.8.1 h1:iUYQEMQzS9Pb8ebzJtV3FGtv/YTjZxAh/NvLW/316wo=
github.com/zeromicro/go-zero v1.8.1/go.mod h1:gc54Ad4qt7OJ0PbKajnYsSKsZBYN4JLRIXKlqDX2A2I= github.com/zeromicro/go-zero v1.8.1/go.mod h1:gc54Ad4qt7OJ0PbKajnYsSKsZBYN4JLRIXKlqDX2A2I=
go.etcd.io/etcd/api/v3 v3.5.15 h1:3KpLJir1ZEBrYuV2v+Twaa/e2MdDCEZ/70H+lzEiwsk= go.etcd.io/etcd/api/v3 v3.5.15 h1:3KpLJir1ZEBrYuV2v+Twaa/e2MdDCEZ/70H+lzEiwsk=
@ -153,8 +242,12 @@ go.etcd.io/etcd/client/pkg/v3 v3.5.15 h1:fo0HpWz/KlHGMCC+YejpiCmyWDEuIpnTDzpJLB5
go.etcd.io/etcd/client/pkg/v3 v3.5.15/go.mod h1:mXDI4NAOwEiszrHCb0aqfAYNCrZP4e9hRca3d1YK8EU= go.etcd.io/etcd/client/pkg/v3 v3.5.15/go.mod h1:mXDI4NAOwEiszrHCb0aqfAYNCrZP4e9hRca3d1YK8EU=
go.etcd.io/etcd/client/v3 v3.5.15 h1:23M0eY4Fd/inNv1ZfU3AxrbbOdW79r9V9Rl62Nm6ip4= go.etcd.io/etcd/client/v3 v3.5.15 h1:23M0eY4Fd/inNv1ZfU3AxrbbOdW79r9V9Rl62Nm6ip4=
go.etcd.io/etcd/client/v3 v3.5.15/go.mod h1:CLSJxrYjvLtHsrPKsy7LmZEE+DK2ktfd2bN4RhBMwlU= go.etcd.io/etcd/client/v3 v3.5.15/go.mod h1:CLSJxrYjvLtHsrPKsy7LmZEE+DK2ktfd2bN4RhBMwlU=
go.mongodb.org/mongo-driver v1.17.3 h1:TQyXhnsWfWtgAhMtOgtYHMTkZIfBTpMTsMnd9ZBeHxQ=
go.mongodb.org/mongo-driver v1.17.3/go.mod h1:Hy04i7O2kC4RS06ZrhPRqj/u4DTYkFDAAccj+rVKqgQ=
go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 h1:jq9TW8u3so/bN+JPT166wjOI6/vQPF6Xe7nMNIltagk=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0/go.mod h1:p8pYQP+m5XfbZm9fxtSKAbM6oIllS7s2AfxrChvc7iw=
go.opentelemetry.io/otel v1.34.0 h1:zRLXxLCgL1WyKsPVrgbSdMN4c0FMkDAskSTQP+0hdUY= go.opentelemetry.io/otel v1.34.0 h1:zRLXxLCgL1WyKsPVrgbSdMN4c0FMkDAskSTQP+0hdUY=
go.opentelemetry.io/otel v1.34.0/go.mod h1:OWFPOQ+h4G8xpyjgqo4SxJYdDQ/qmRH+wivy7zzx9oI= go.opentelemetry.io/otel v1.34.0/go.mod h1:OWFPOQ+h4G8xpyjgqo4SxJYdDQ/qmRH+wivy7zzx9oI=
go.opentelemetry.io/otel/exporters/jaeger v1.17.0 h1:D7UpUy2Xc2wsi1Ras6V40q806WM07rqoCWzXu7Sqy+4= go.opentelemetry.io/otel/exporters/jaeger v1.17.0 h1:D7UpUy2Xc2wsi1Ras6V40q806WM07rqoCWzXu7Sqy+4=
@ -185,6 +278,8 @@ go.uber.org/automaxprocs v1.6.0 h1:O3y2/QNTOdbF+e/dpXNNW7Rx2hZ4sTIPyybbxyNqTUs=
go.uber.org/automaxprocs v1.6.0/go.mod h1:ifeIMSnPZuznNm6jmdzmU3/bfk01Fe2fotchwEFJ8r8= go.uber.org/automaxprocs v1.6.0/go.mod h1:ifeIMSnPZuznNm6jmdzmU3/bfk01Fe2fotchwEFJ8r8=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
go.uber.org/mock v0.5.0 h1:KAMbZvZPyBPWgD14IrIQ38QCyjwpvVVV6K/bHl1IwQU=
go.uber.org/mock v0.5.0/go.mod h1:ge71pBPLYDk7QIi1LupWxdAykm7KIEFchiOqd6z7qMM=
go.uber.org/multierr v1.9.0 h1:7fIwc/ZtS0q++VgcfqFDxSBZVv/Xo49/SYnDFupUwlI= go.uber.org/multierr v1.9.0 h1:7fIwc/ZtS0q++VgcfqFDxSBZVv/Xo49/SYnDFupUwlI=
go.uber.org/multierr v1.9.0/go.mod h1:X2jQV1h+kxSjClGpnseKVIxpmcjrj7MNnI0bnlfKTVQ= go.uber.org/multierr v1.9.0/go.mod h1:X2jQV1h+kxSjClGpnseKVIxpmcjrj7MNnI0bnlfKTVQ=
go.uber.org/zap v1.24.0 h1:FiJd5l1UOLj0wCgbSE0rwwXHzEdAZS6hiiSnxJN/D60= go.uber.org/zap v1.24.0 h1:FiJd5l1UOLj0wCgbSE0rwwXHzEdAZS6hiiSnxJN/D60=
@ -192,14 +287,20 @@ go.uber.org/zap v1.24.0/go.mod h1:2kMP+WWQ8aoFoedH3T2sq6iJ2yDWpHbP0f6MQbS9Gkg=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.33.0 h1:IOBPskki6Lysi0lo9qQvbxiQ+FvsCC/YWOecCHAixus=
golang.org/x/crypto v0.33.0/go.mod h1:bVdXmD7IV/4GdElGPozy6U7lWdRXA4qyRVGJV57uQ5M=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.35.0 h1:T5GQRQb2y08kTAByq9L4/bz8cipCdA8FbRTXewonqY8= golang.org/x/net v0.35.0 h1:T5GQRQb2y08kTAByq9L4/bz8cipCdA8FbRTXewonqY8=
golang.org/x/net v0.35.0/go.mod h1:EglIi67kWsHKlRzzVMUD93VMSWGFOMSZgxFjparz1Qk= golang.org/x/net v0.35.0/go.mod h1:EglIi67kWsHKlRzzVMUD93VMSWGFOMSZgxFjparz1Qk=
golang.org/x/oauth2 v0.25.0 h1:CY4y7XT9v0cRI9oupztF8AgiIu99L/ksR/Xp/6jrZ70= golang.org/x/oauth2 v0.25.0 h1:CY4y7XT9v0cRI9oupztF8AgiIu99L/ksR/Xp/6jrZ70=
@ -208,21 +309,37 @@ golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJ
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.11.0 h1:GGz8+XQP4FvTTrjZPzNKTMFtSXH80RAzG+5ghFPgK9w=
golang.org/x/sync v0.11.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc= golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc=
golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.29.0 h1:L6pJp37ocefwRRtYPKSWOWzOtWSxVajvz2ldH/xi3iU= golang.org/x/term v0.29.0 h1:L6pJp37ocefwRRtYPKSWOWzOtWSxVajvz2ldH/xi3iU=
golang.org/x/term v0.29.0/go.mod h1:6bl4lRlvVuDgSf3179VpIxBF0o10JUpXWOnI7nErv7s= golang.org/x/term v0.29.0/go.mod h1:6bl4lRlvVuDgSf3179VpIxBF0o10JUpXWOnI7nErv7s=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM= golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM=
golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY= golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY=
golang.org/x/time v0.10.0 h1:3usCWA8tQn0L8+hFJQNgzpWbd89begxN66o1Ojdn5L4= golang.org/x/time v0.10.0 h1:3usCWA8tQn0L8+hFJQNgzpWbd89begxN66o1Ojdn5L4=
@ -232,8 +349,9 @@ golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtn
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d h1:vU5i/LfpvrRCpgM/VPfJLg5KjxD3E+hfT1SH+d9zLwg= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= golang.org/x/tools v0.22.0 h1:gqSGLZqv+AI9lIQzniJ0nZDRG5GBPsSi+DRNHWNz6yA=
golang.org/x/tools v0.22.0/go.mod h1:aCwcsjqvq7Yqt6TNyX7QMU2enbQ/Gt0bo6krSeEri+c=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
@ -259,6 +377,8 @@ gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gotest.tools/v3 v3.5.1 h1:EENdUnS3pdur5nybKYIh2Vfgc8IUNBjxDPSjtiJcOzU=
gotest.tools/v3 v3.5.1/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU=
k8s.io/api v0.29.3 h1:2ORfZ7+bGC3YJqGpV0KSDDEVf8hdGQ6A03/50vj8pmw= k8s.io/api v0.29.3 h1:2ORfZ7+bGC3YJqGpV0KSDDEVf8hdGQ6A03/50vj8pmw=
k8s.io/api v0.29.3/go.mod h1:y2yg2NTyHUUkIoTC+phinTnEa3KFM6RZ3szxt014a80= k8s.io/api v0.29.3/go.mod h1:y2yg2NTyHUUkIoTC+phinTnEa3KFM6RZ3szxt014a80=
k8s.io/apimachinery v0.29.4 h1:RaFdJiDmuKs/8cm1M6Dh1Kvyh59YQFDcFuFTSmXes6Q= k8s.io/apimachinery v0.29.4 h1:RaFdJiDmuKs/8cm1M6Dh1Kvyh59YQFDcFuFTSmXes6Q=

View File

@ -1,7 +1,33 @@
package config package config
import "github.com/zeromicro/go-zero/zrpc" import (
"time"
"github.com/zeromicro/go-zero/core/stores/cache"
"github.com/zeromicro/go-zero/zrpc"
)
type Config struct { type Config struct {
zrpc.RpcServerConf zrpc.RpcServerConf
// Redis Cluster
Cache cache.CacheConf
CacheExpireTime time.Duration
CacheWithNotFoundExpiry time.Duration
Mongo struct {
Schema string
User string
Password string
Host string
Port string
Database string
ReplicaName string
MaxStaleness time.Duration
MaxPoolSize uint64
MinPoolSize uint64
MaxConnIdleTime time.Duration
Compressors []string
EnableStandardReadWriteSplitMode bool
ConnectTimeoutMs int64
}
} }

View File

@ -0,0 +1,38 @@
package categoryservicelogic
import (
"context"
"code.30cm.net/digimon/app-cloudep-product-service/pkg/domain/entity"
"code.30cm.net/digimon/app-cloudep-product-service/gen_result/pb/product"
"code.30cm.net/digimon/app-cloudep-product-service/internal/svc"
"github.com/zeromicro/go-zero/core/logx"
)
type CreateLogic struct {
ctx context.Context
svcCtx *svc.ServiceContext
logx.Logger
}
func NewCreateLogic(ctx context.Context, svcCtx *svc.ServiceContext) *CreateLogic {
return &CreateLogic{
ctx: ctx,
svcCtx: svcCtx,
Logger: logx.WithContext(ctx),
}
}
// Create 建立 product 分類
func (l *CreateLogic) Create(in *product.CreateCategoryReq) (*product.OKResp, error) {
err := l.svcCtx.CategoryUseCase.Insert(l.ctx, &entity.Category{
Name: in.GetName(),
})
if err != nil {
return nil, err
}
return &product.OKResp{}, nil
}

View File

@ -0,0 +1,34 @@
package categoryservicelogic
import (
"context"
"code.30cm.net/digimon/app-cloudep-product-service/gen_result/pb/product"
"code.30cm.net/digimon/app-cloudep-product-service/internal/svc"
"github.com/zeromicro/go-zero/core/logx"
)
type DeleteLogic struct {
ctx context.Context
svcCtx *svc.ServiceContext
logx.Logger
}
func NewDeleteLogic(ctx context.Context, svcCtx *svc.ServiceContext) *DeleteLogic {
return &DeleteLogic{
ctx: ctx,
svcCtx: svcCtx,
Logger: logx.WithContext(ctx),
}
}
// Delete 刪除 product 分類
func (l *DeleteLogic) Delete(in *product.CategoryReq) (*product.OKResp, error) {
err := l.svcCtx.CategoryUseCase.Delete(l.ctx, in.GetId())
if err != nil {
return nil, err
}
return &product.OKResp{}, nil
}

View File

@ -0,0 +1,39 @@
package categoryservicelogic
import (
"context"
"code.30cm.net/digimon/app-cloudep-product-service/gen_result/pb/product"
"code.30cm.net/digimon/app-cloudep-product-service/internal/svc"
"github.com/zeromicro/go-zero/core/logx"
)
type GetLogic struct {
ctx context.Context
svcCtx *svc.ServiceContext
logx.Logger
}
func NewGetLogic(ctx context.Context, svcCtx *svc.ServiceContext) *GetLogic {
return &GetLogic{
ctx: ctx,
svcCtx: svcCtx,
Logger: logx.WithContext(ctx),
}
}
// Get 取得 product 分類
func (l *GetLogic) Get(in *product.CategoryReq) (*product.Category, error) {
category, err := l.svcCtx.CategoryUseCase.FindOneByID(l.ctx, in.GetId())
if err != nil {
return nil, err
}
return &product.Category{
Id: category.ID.Hex(),
Name: category.Name,
CreateTime: category.CreatedAt,
UpdateTime: category.UpdatedAt,
}, nil
}

View File

@ -0,0 +1,58 @@
package categoryservicelogic
import (
"context"
"code.30cm.net/digimon/app-cloudep-product-service/pkg/domain/usecase"
"code.30cm.net/digimon/app-cloudep-product-service/gen_result/pb/product"
"code.30cm.net/digimon/app-cloudep-product-service/internal/svc"
"github.com/zeromicro/go-zero/core/logx"
)
type ListLogic struct {
ctx context.Context
svcCtx *svc.ServiceContext
logx.Logger
}
func NewListLogic(ctx context.Context, svcCtx *svc.ServiceContext) *ListLogic {
return &ListLogic{
ctx: ctx,
svcCtx: svcCtx,
Logger: logx.WithContext(ctx),
}
}
// List 建立 product 分類
func (l *ListLogic) List(in *product.ListCategoryReq) (*product.ListCategoryResp, error) {
q := usecase.CategoryQueryParams{
PageSize: in.GetPageSize(),
PageIndex: in.GetPageIndex(),
}
if len(in.GetIds()) > 0 {
q.ID = in.GetIds()
}
category, total, err := l.svcCtx.CategoryUseCase.ListCategory(l.ctx, q)
if err != nil {
return nil, err
}
res := make([]*product.Category, 0, len(category))
for _, c := range category {
res = append(res, &product.Category{
Id: c.ID.Hex(),
Name: c.Name,
CreateTime: c.CreatedAt,
UpdateTime: c.UpdatedAt,
})
}
return &product.ListCategoryResp{
Total: total,
Data: res,
}, nil
}

View File

@ -0,0 +1,36 @@
package categoryservicelogic
import (
"context"
"code.30cm.net/digimon/app-cloudep-product-service/pkg/domain/entity"
"code.30cm.net/digimon/app-cloudep-product-service/gen_result/pb/product"
"code.30cm.net/digimon/app-cloudep-product-service/internal/svc"
"github.com/zeromicro/go-zero/core/logx"
)
type ModifyLogic struct {
ctx context.Context
svcCtx *svc.ServiceContext
logx.Logger
}
func NewModifyLogic(ctx context.Context, svcCtx *svc.ServiceContext) *ModifyLogic {
return &ModifyLogic{
ctx: ctx,
svcCtx: svcCtx,
Logger: logx.WithContext(ctx),
}
}
// Modify 修改 product 分類名稱
func (l *ModifyLogic) Modify(in *product.ModifyCategoryReq) (*product.OKResp, error) {
err := l.svcCtx.CategoryUseCase.Update(l.ctx, in.GetId(), &entity.Category{Name: in.GetName()})
if err != nil {
return nil, err
}
return &product.OKResp{}, nil
}

View File

@ -0,0 +1,55 @@
package kycservicelogic
import (
"context"
"code.30cm.net/digimon/app-cloudep-product-service/pkg/domain/entity"
"code.30cm.net/digimon/app-cloudep-product-service/pkg/domain/kyc"
"code.30cm.net/digimon/app-cloudep-product-service/gen_result/pb/product"
"code.30cm.net/digimon/app-cloudep-product-service/internal/svc"
"github.com/zeromicro/go-zero/core/logx"
)
type CreateLogic struct {
ctx context.Context
svcCtx *svc.ServiceContext
logx.Logger
}
func NewCreateLogic(ctx context.Context, svcCtx *svc.ServiceContext) *CreateLogic {
return &CreateLogic{
ctx: ctx,
svcCtx: svcCtx,
Logger: logx.WithContext(ctx),
}
}
// Create 建立 KYC 資料
func (l *CreateLogic) Create(in *product.CreateKycReq) (*product.OKResp, error) {
err := l.svcCtx.KYCUseCase.Create(l.ctx, &entity.KYC{
UID: in.GetUid(),
CountryRegion: in.GetCountryRegion(),
Name: in.GetName(),
Identification: in.GetIdentification(),
IdentificationType: in.GetIdentificationType(),
Address: in.GetAddress(),
PostalCode: in.GetPostalCode(),
IDFrontImage: in.GetIdFrontImage(),
IDBackImage: in.GetIdBackImage(),
BankStatementImg: in.GetBankStatementImg(),
BankCode: in.GetBankCode(),
BankName: in.GetName(),
BranchCode: in.GetBranchCode(),
BranchName: in.GetBranchName(),
BankAccount: in.GetBankAccount(),
Status: kyc.StatusPending,
RejectReason: "",
})
if err != nil {
return nil, err
}
return &product.OKResp{}, nil
}

View File

@ -0,0 +1,56 @@
package kycservicelogic
import (
"context"
"code.30cm.net/digimon/app-cloudep-product-service/gen_result/pb/product"
"code.30cm.net/digimon/app-cloudep-product-service/internal/svc"
"github.com/zeromicro/go-zero/core/logx"
)
type FindByIdLogic struct {
ctx context.Context
svcCtx *svc.ServiceContext
logx.Logger
}
func NewFindByIdLogic(ctx context.Context, svcCtx *svc.ServiceContext) *FindByIdLogic {
return &FindByIdLogic{
ctx: ctx,
svcCtx: svcCtx,
Logger: logx.WithContext(ctx),
}
}
// FindById 根據 KYC ID 查詢
func (l *FindByIdLogic) FindById(in *product.FindKycByIDReq) (*product.Kyc, error) {
kycInfo, err := l.svcCtx.KYCUseCase.FindByID(l.ctx, in.GetId())
if err != nil {
return nil, err
}
return &product.Kyc{
Id: kycInfo.ID.Hex(),
Uid: kycInfo.UID,
CountryRegion: kycInfo.CountryRegion,
Name: kycInfo.Name,
Identification: kycInfo.Identification,
IdentificationType: kycInfo.IdentificationType,
Address: kycInfo.Address,
PostalCode: kycInfo.PostalCode,
// 上傳文件網址(可為 object storage 的 URL
IdFrontImage: kycInfo.IDFrontImage,
IdBackImage: kycInfo.IDBackImage,
BankStatementImg: kycInfo.BankStatementImg,
BankCode: kycInfo.BankCode,
BankName: kycInfo.BankName,
BranchCode: kycInfo.BranchCode,
BranchName: kycInfo.BranchName,
BankAccount: kycInfo.BankAccount,
Status: kycInfo.Status.ToString(),
RejectReason: kycInfo.RejectReason,
UpdatedAt: kycInfo.UpdatedAt,
CreatedAt: kycInfo.CreatedAt,
}, nil
}

View File

@ -0,0 +1,56 @@
package kycservicelogic
import (
"context"
"code.30cm.net/digimon/app-cloudep-product-service/gen_result/pb/product"
"code.30cm.net/digimon/app-cloudep-product-service/internal/svc"
"github.com/zeromicro/go-zero/core/logx"
)
type FindLatestByUidLogic struct {
ctx context.Context
svcCtx *svc.ServiceContext
logx.Logger
}
func NewFindLatestByUidLogic(ctx context.Context, svcCtx *svc.ServiceContext) *FindLatestByUidLogic {
return &FindLatestByUidLogic{
ctx: ctx,
svcCtx: svcCtx,
Logger: logx.WithContext(ctx),
}
}
// FindLatestByUid 根據使用者 UID 查詢最新 KYC 紀錄
func (l *FindLatestByUidLogic) FindLatestByUid(in *product.FindLatestKycByUIDReq) (*product.Kyc, error) {
kycInfo, err := l.svcCtx.KYCUseCase.FindLatestByUID(l.ctx, in.GetUid())
if err != nil {
return nil, err
}
return &product.Kyc{
Id: kycInfo.ID.Hex(),
Uid: kycInfo.UID,
CountryRegion: kycInfo.CountryRegion,
Name: kycInfo.Name,
Identification: kycInfo.Identification,
IdentificationType: kycInfo.IdentificationType,
Address: kycInfo.Address,
PostalCode: kycInfo.PostalCode,
// 上傳文件網址(可為 object storage 的 URL
IdFrontImage: kycInfo.IDFrontImage,
IdBackImage: kycInfo.IDBackImage,
BankStatementImg: kycInfo.BankStatementImg,
BankCode: kycInfo.BankCode,
BankName: kycInfo.BankName,
BranchCode: kycInfo.BranchCode,
BranchName: kycInfo.BranchName,
BankAccount: kycInfo.BankAccount,
Status: kycInfo.Status.ToString(),
RejectReason: kycInfo.RejectReason,
UpdatedAt: kycInfo.UpdatedAt,
CreatedAt: kycInfo.CreatedAt,
}, nil
}

View File

@ -0,0 +1,73 @@
package kycservicelogic
import (
"context"
"code.30cm.net/digimon/app-cloudep-product-service/pkg/domain/usecase"
"code.30cm.net/digimon/app-cloudep-product-service/gen_result/pb/product"
"code.30cm.net/digimon/app-cloudep-product-service/internal/svc"
"github.com/zeromicro/go-zero/core/logx"
)
type ListLogic struct {
ctx context.Context
svcCtx *svc.ServiceContext
logx.Logger
}
func NewListLogic(ctx context.Context, svcCtx *svc.ServiceContext) *ListLogic {
return &ListLogic{
ctx: ctx,
svcCtx: svcCtx,
Logger: logx.WithContext(ctx),
}
}
// List 分頁查詢 Kyc 清單(後台審核用)
func (l *ListLogic) List(in *product.ListKycReq) (*product.ListKycResp, error) {
filter := usecase.KYCQueryParams{
UID: in.Uid,
Country: in.Country,
Status: in.Status,
PageSize: in.GetPageSize(),
PageIndex: in.GetPageIndex(),
}
list, total, err := l.svcCtx.KYCUseCase.List(l.ctx, filter)
if err != nil {
return nil, err
}
result := make([]*product.Kyc, 0, len(list))
for _, kycInfo := range list {
result = append(result, &product.Kyc{
Id: kycInfo.ID.Hex(),
Uid: kycInfo.UID,
CountryRegion: kycInfo.CountryRegion,
Name: kycInfo.Name,
Identification: kycInfo.Identification,
IdentificationType: kycInfo.IdentificationType,
Address: kycInfo.Address,
PostalCode: kycInfo.PostalCode,
IdFrontImage: kycInfo.IDFrontImage,
IdBackImage: kycInfo.IDBackImage,
BankStatementImg: kycInfo.BankStatementImg,
BankCode: kycInfo.BankCode,
BankName: kycInfo.BankName,
BranchCode: kycInfo.BranchCode,
BranchName: kycInfo.BranchName,
BankAccount: kycInfo.BankAccount,
Status: kycInfo.Status.ToString(),
RejectReason: kycInfo.RejectReason,
UpdatedAt: kycInfo.UpdatedAt,
CreatedAt: kycInfo.CreatedAt,
})
}
return &product.ListKycResp{
Total: total,
List: result,
}, nil
}

View File

@ -0,0 +1,52 @@
package kycservicelogic
import (
"context"
"code.30cm.net/digimon/app-cloudep-product-service/pkg/domain/usecase"
"google.golang.org/protobuf/proto"
"code.30cm.net/digimon/app-cloudep-product-service/gen_result/pb/product"
"code.30cm.net/digimon/app-cloudep-product-service/internal/svc"
"github.com/zeromicro/go-zero/core/logx"
)
type UpdateLogic struct {
ctx context.Context
svcCtx *svc.ServiceContext
logx.Logger
}
func NewUpdateLogic(ctx context.Context, svcCtx *svc.ServiceContext) *UpdateLogic {
return &UpdateLogic{
ctx: ctx,
svcCtx: svcCtx,
Logger: logx.WithContext(ctx),
}
}
// Update 更新使用者的 Kyc尚未審核
func (l *UpdateLogic) Update(in *product.UpdateKycInfoReq) (*product.OKResp, error) {
// 如果已完成應該不能
err := l.svcCtx.KYCUseCase.UpdateKYCInfo(l.ctx, in.GetId(), &usecase.KYCUpdateParams{
Name: proto.String(in.GetName()),
Identification: proto.String(in.GetIdentification()),
IdentificationType: proto.String(in.GetIdentificationType()),
Address: proto.String(in.GetAddress()),
PostalCode: proto.String(in.GetPostalCode()),
IDFrontImage: proto.String(in.GetIdFrontImage()),
IDBackImage: proto.String(in.GetIdBackImage()),
BankStatementImg: proto.String(in.GetBankStatementImg()),
BankCode: proto.String(in.GetBankCode()),
BankName: proto.String(in.GetBankName()),
BranchCode: proto.String(in.GetBranchCode()),
BranchName: proto.String(in.GetBranchName()),
BankAccount: proto.String(in.GetBankAccount()),
})
if err != nil {
return nil, err
}
return &product.OKResp{}, nil
}

View File

@ -0,0 +1,34 @@
package kycservicelogic
import (
"context"
"code.30cm.net/digimon/app-cloudep-product-service/gen_result/pb/product"
"code.30cm.net/digimon/app-cloudep-product-service/internal/svc"
"github.com/zeromicro/go-zero/core/logx"
)
type UpdateStatusLogic struct {
ctx context.Context
svcCtx *svc.ServiceContext
logx.Logger
}
func NewUpdateStatusLogic(ctx context.Context, svcCtx *svc.ServiceContext) *UpdateStatusLogic {
return &UpdateStatusLogic{
ctx: ctx,
svcCtx: svcCtx,
Logger: logx.WithContext(ctx),
}
}
// UpdateStatus 更新 Kyc 審核狀態與原因
func (l *UpdateStatusLogic) UpdateStatus(in *product.UpdateKycStatusReq) (*product.OKResp, error) {
err := l.svcCtx.KYCUseCase.UpdateStatus(l.ctx, in.GetId(), in.GetStatus(), in.GetReason())
if err != nil {
return nil, err
}
return &product.OKResp{}, nil
}

View File

@ -0,0 +1,99 @@
package productitemservicelogic
import (
"context"
"code.30cm.net/digimon/app-cloudep-product-service/internal/utils"
"code.30cm.net/digimon/app-cloudep-product-service/pkg/domain/product"
"code.30cm.net/digimon/app-cloudep-product-service/pkg/domain/usecase"
"code.30cm.net/digimon/library-go/errs"
PB "code.30cm.net/digimon/app-cloudep-product-service/gen_result/pb/product"
"code.30cm.net/digimon/app-cloudep-product-service/internal/svc"
"github.com/zeromicro/go-zero/core/logx"
)
type CreateLogic struct {
ctx context.Context
svcCtx *svc.ServiceContext
logx.Logger
}
func NewCreateLogic(ctx context.Context, svcCtx *svc.ServiceContext) *CreateLogic {
return &CreateLogic{
ctx: ctx,
svcCtx: svcCtx,
Logger: logx.WithContext(ctx),
}
}
// Create 建立 ProductItem
func (l *CreateLogic) Create(in *PB.CreateProductItemRequest) (*PB.OKResp, error) {
insert := &usecase.ProductItems{
ReferenceID: in.GetItem().ReferenceId, // 對應的專案 ID
Name: in.GetItem().Name, // 名稱
Description: utils.EncodeToBase64Snappy(in.GetItem().Description), // 描述
ShortDescription: utils.EncodeToBase64Snappy(in.GetItem().ShortDescription), // 封面簡短描述
IsUnLimit: in.GetItem().IsUnLimit, // 是否沒有數量上限
IsFree: in.GetItem().IsFree, // 是否為免費品項(贈品) -> 開啟就是自訂金額
Stock: in.GetItem().Stock, // 庫存總數
Price: in.GetItem().Price, // 價格
SKU: in.GetItem().Sku, // 型號:對應顯示 Item 的 FK
SalesCount: 0, // 已賣出數量(相反,減到零就不能在賣)
}
if len(in.GetItem().Media) > 0 {
// 專案動態內容(圖片或者影片)
m := make([]usecase.Media, 0, len(in.GetItem().Media))
for i, item := range in.GetItem().Media {
m = append(m, usecase.Media{
Sort: uint64(i),
URL: item.Url,
Type: item.Type,
})
}
insert.Media = m
}
if len(in.GetItem().CustomFields) > 0 {
// 自定義屬性
c := make([]usecase.CustomFields, 0, len(in.GetItem().CustomFields))
for _, cItem := range in.GetItem().CustomFields {
c = append(c, usecase.CustomFields{
Key: cItem.Key,
Value: cItem.Value,
})
}
insert.CustomFields = c
}
if len(in.GetItem().Freight) > 0 {
// 運費
f := make([]usecase.CustomFields, 0, len(in.GetItem().Freight))
for _, fItem := range in.GetItem().Freight {
f = append(f, usecase.CustomFields{
Key: fItem.Key,
Value: fItem.Value,
})
}
insert.Freight = f
}
status, e := product.StringToItemStatus(in.GetItem().Status)
if !e {
return nil, errs.InvalidFormat("failed to convert string to item status")
}
insert.Status = status
timeSeries, te := product.StringToTimeSeries(in.GetItem().TimeSeries)
if !te {
return nil, errs.InvalidFormat("failed to convert string to time series")
}
insert.TimeSeries = timeSeries
err := l.svcCtx.ProductItemUseCase.Create(l.ctx, insert)
if err != nil {
return nil, err
}
return &PB.OKResp{}, nil
}

View File

@ -0,0 +1,35 @@
package productitemservicelogic
import (
"context"
"code.30cm.net/digimon/app-cloudep-product-service/gen_result/pb/product"
"code.30cm.net/digimon/app-cloudep-product-service/internal/svc"
"github.com/zeromicro/go-zero/core/logx"
)
type DecSalesCountLogic struct {
ctx context.Context
svcCtx *svc.ServiceContext
logx.Logger
}
func NewDecSalesCountLogic(ctx context.Context, svcCtx *svc.ServiceContext) *DecSalesCountLogic {
return &DecSalesCountLogic{
ctx: ctx,
svcCtx: svcCtx,
Logger: logx.WithContext(ctx),
}
}
// DecSalesCount 減少賣出數量
func (l *DecSalesCountLogic) DecSalesCount(in *product.IncDecSalesCountRequest) (*product.OKResp, error) {
// TODO 有問題可以在這邊加瑣
err := l.svcCtx.ProductItemUseCase.DecSalesCount(l.ctx, in.GetId(), in.GetCount())
if err != nil {
return nil, err
}
return &product.OKResp{}, nil
}

View File

@ -0,0 +1,34 @@
package productitemservicelogic
import (
"context"
"code.30cm.net/digimon/app-cloudep-product-service/gen_result/pb/product"
"code.30cm.net/digimon/app-cloudep-product-service/internal/svc"
"github.com/zeromicro/go-zero/core/logx"
)
type DeleteByReferenceIdLogic struct {
ctx context.Context
svcCtx *svc.ServiceContext
logx.Logger
}
func NewDeleteByReferenceIdLogic(ctx context.Context, svcCtx *svc.ServiceContext) *DeleteByReferenceIdLogic {
return &DeleteByReferenceIdLogic{
ctx: ctx,
svcCtx: svcCtx,
Logger: logx.WithContext(ctx),
}
}
// DeleteByReferenceId 使用 ProductID 刪除所有 Item
func (l *DeleteByReferenceIdLogic) DeleteByReferenceId(in *product.DeleteProductItemsByReferenceIDReq) (*product.OKResp, error) {
err := l.svcCtx.ProductItemUseCase.DeleteByReferenceID(l.ctx, in.GetId())
if err != nil {
return nil, err
}
return &product.OKResp{}, nil
}

View File

@ -0,0 +1,34 @@
package productitemservicelogic
import (
"context"
"code.30cm.net/digimon/app-cloudep-product-service/gen_result/pb/product"
"code.30cm.net/digimon/app-cloudep-product-service/internal/svc"
"github.com/zeromicro/go-zero/core/logx"
)
type DeleteLogic struct {
ctx context.Context
svcCtx *svc.ServiceContext
logx.Logger
}
func NewDeleteLogic(ctx context.Context, svcCtx *svc.ServiceContext) *DeleteLogic {
return &DeleteLogic{
ctx: ctx,
svcCtx: svcCtx,
Logger: logx.WithContext(ctx),
}
}
// Delete 刪除 Delete Product Item
func (l *DeleteLogic) Delete(in *product.DeleteProductItemRequest) (*product.OKResp, error) {
err := l.svcCtx.ProductItemUseCase.Delete(l.ctx, in.GetId())
if err != nil {
return nil, err
}
return &product.OKResp{}, nil
}

View File

@ -0,0 +1,84 @@
package productitemservicelogic
import (
"context"
"code.30cm.net/digimon/app-cloudep-product-service/internal/utils"
"google.golang.org/protobuf/proto"
"code.30cm.net/digimon/app-cloudep-product-service/gen_result/pb/product"
"code.30cm.net/digimon/app-cloudep-product-service/internal/svc"
"github.com/zeromicro/go-zero/core/logx"
)
type GetLogic struct {
ctx context.Context
svcCtx *svc.ServiceContext
logx.Logger
}
func NewGetLogic(ctx context.Context, svcCtx *svc.ServiceContext) *GetLogic {
return &GetLogic{
ctx: ctx,
svcCtx: svcCtx,
Logger: logx.WithContext(ctx),
}
}
// Get 取得 ProductItem
func (l *GetLogic) Get(in *product.GetProductItemRequest) (*product.ProductItem, error) {
item, err := l.svcCtx.ProductItemUseCase.Get(l.ctx, in.GetId())
if err != nil {
return nil, err
}
result := &product.ProductItem{
Id: proto.String(item.ID),
ReferenceId: item.ReferenceID,
Name: item.Name,
Description: utils.DecodeToBase64Snappy(item.Description),
ShortDescription: utils.DecodeToBase64Snappy(item.ShortDescription),
IsUnLimit: item.IsUnLimit,
IsFree: item.IsFree,
Stock: item.Stock,
Price: item.Price,
Sku: item.SKU,
TimeSeries: item.TimeSeries.ToString(),
SalesCount: item.SalesCount,
Status: item.Status.ToString(),
UpdatedAt: &item.UpdatedAt,
CreatedAt: &item.CreatedAt,
}
media := make([]*product.Media, 0, len(item.Media))
for _, pi := range item.Media {
media = append(media, &product.Media{
Sort: proto.Uint64(pi.Sort),
Url: pi.URL,
Type: pi.Type,
})
}
result.Media = media
// 運費
f := make([]*product.CustomField, 0, len(item.Freight))
for _, fItem := range item.Freight {
f = append(f, &product.CustomField{
Key: fItem.Key,
Value: fItem.Value,
})
}
result.Freight = f
// 運費
c := make([]*product.CustomField, 0, len(item.CustomFields))
for _, fItem := range item.CustomFields {
c = append(c, &product.CustomField{
Key: fItem.Key,
Value: fItem.Value,
})
}
result.CustomFields = c
return result, nil
}

View File

@ -0,0 +1,35 @@
package productitemservicelogic
import (
"context"
"code.30cm.net/digimon/app-cloudep-product-service/gen_result/pb/product"
"code.30cm.net/digimon/app-cloudep-product-service/internal/svc"
"github.com/zeromicro/go-zero/core/logx"
)
type IncSalesCountLogic struct {
ctx context.Context
svcCtx *svc.ServiceContext
logx.Logger
}
func NewIncSalesCountLogic(ctx context.Context, svcCtx *svc.ServiceContext) *IncSalesCountLogic {
return &IncSalesCountLogic{
ctx: ctx,
svcCtx: svcCtx,
Logger: logx.WithContext(ctx),
}
}
// IncSalesCount 增加賣出數量
func (l *IncSalesCountLogic) IncSalesCount(in *product.IncDecSalesCountRequest) (*product.OKResp, error) {
// TODO 有問題可以在這邊加瑣
err := l.svcCtx.ProductItemUseCase.IncSalesCount(l.ctx, in.GetId(), in.GetCount())
if err != nil {
return nil, err
}
return &product.OKResp{}, nil
}

View File

@ -0,0 +1,113 @@
package productitemservicelogic
import (
"context"
"code.30cm.net/digimon/app-cloudep-product-service/internal/utils"
"code.30cm.net/digimon/app-cloudep-product-service/pkg/domain/product"
"code.30cm.net/digimon/app-cloudep-product-service/pkg/domain/usecase"
"code.30cm.net/digimon/library-go/errs"
"google.golang.org/protobuf/proto"
PB "code.30cm.net/digimon/app-cloudep-product-service/gen_result/pb/product"
"code.30cm.net/digimon/app-cloudep-product-service/internal/svc"
"github.com/zeromicro/go-zero/core/logx"
)
type ListByProductIdLogic struct {
ctx context.Context
svcCtx *svc.ServiceContext
logx.Logger
}
func NewListByProductIdLogic(ctx context.Context, svcCtx *svc.ServiceContext) *ListByProductIdLogic {
return &ListByProductIdLogic{
ctx: ctx,
svcCtx: svcCtx,
Logger: logx.WithContext(ctx),
}
}
// ListByProductId 使用 ProductID 取得 ProductItems
func (l *ListByProductIdLogic) ListByProductId(in *PB.ListProductItemRequest) (*PB.ListProductItemResponse, error) {
filter := usecase.QueryProductItemParam{
PageSize: in.GetPageSize(),
PageIndex: in.GetPageIndex(),
ReferenceID: proto.String(in.GetReferenceId()),
}
if in.Status != nil {
status, e := product.StringToItemStatus(in.GetStatus())
if !e {
return nil, errs.InvalidFormat("failed to convert string to item status")
}
filter.Status = &status
}
if in.IsFree != nil {
filter.IsFree = in.IsFree
}
if in.IsUnLimit != nil {
filter.IsUnLimit = in.IsUnLimit
}
list, total, err := l.svcCtx.ProductItemUseCase.List(l.ctx, filter)
if err != nil {
return nil, err
}
result := make([]*PB.ProductItem, 0)
for _, item := range list {
pi := &PB.ProductItem{
Id: proto.String(item.ID),
ReferenceId: item.ReferenceID,
Name: item.Name,
Description: utils.DecodeToBase64Snappy(item.Description),
ShortDescription: utils.DecodeToBase64Snappy(item.ShortDescription),
IsUnLimit: item.IsUnLimit,
IsFree: item.IsFree,
Stock: item.Stock,
Price: item.Price,
Sku: item.SKU,
TimeSeries: item.TimeSeries.ToString(),
SalesCount: item.SalesCount,
Status: item.Status.ToString(),
UpdatedAt: &item.UpdatedAt,
CreatedAt: &item.CreatedAt,
}
media := make([]*PB.Media, 0, len(item.Media))
for _, pi := range item.Media {
media = append(media, &PB.Media{
Sort: proto.Uint64(pi.Sort),
Url: pi.URL,
Type: pi.Type,
})
}
pi.Media = media
// 運費
f := make([]*PB.CustomField, 0, len(item.Freight))
for _, fItem := range item.Freight {
f = append(f, &PB.CustomField{
Key: fItem.Key,
Value: fItem.Value,
})
}
pi.Freight = f
// 運費
c := make([]*PB.CustomField, 0, len(item.CustomFields))
for _, fItem := range item.CustomFields {
c = append(c, &PB.CustomField{
Key: fItem.Key,
Value: fItem.Value,
})
}
pi.CustomFields = c
result = append(result, pi)
}
return &PB.ListProductItemResponse{
Total: total,
Data: result,
}, nil
}

View File

@ -0,0 +1,89 @@
package productitemservicelogic
import (
"context"
"code.30cm.net/digimon/app-cloudep-product-service/gen_result/pb/product"
"code.30cm.net/digimon/app-cloudep-product-service/internal/svc"
domain "code.30cm.net/digimon/app-cloudep-product-service/pkg/domain/product"
"code.30cm.net/digimon/app-cloudep-product-service/pkg/domain/usecase"
"code.30cm.net/digimon/library-go/errs"
"github.com/zeromicro/go-zero/core/logx"
)
type UpdateLogic struct {
ctx context.Context
svcCtx *svc.ServiceContext
logx.Logger
}
func NewUpdateLogic(ctx context.Context, svcCtx *svc.ServiceContext) *UpdateLogic {
return &UpdateLogic{
ctx: ctx,
svcCtx: svcCtx,
Logger: logx.WithContext(ctx),
}
}
// Update 更新 Item
func (l *UpdateLogic) Update(in *product.UpdateProductItemRequest) (*product.OKResp, error) {
update := &usecase.UpdateProductItems{
Name: in.Name,
Description: in.Description,
ShortDescription: in.ShortDescription,
IsUnLimit: in.IsUnLimit,
IsFree: in.IsFree,
Stock: in.Stock,
Price: in.Price,
SKU: in.Sku,
}
if in.TimeSeries != nil {
ts, status := domain.StringToTimeSeries(in.GetTimeSeries())
if !status {
return nil, errs.InvalidFormat("timeSeries")
}
update.TimeSeries = &ts
}
if len(in.GetMedia()) > 0 {
// 專案動態內容(圖片或者影片)
m := make([]usecase.Media, 0, len(in.GetMedia()))
for _, item := range in.GetMedia() {
m = append(m, usecase.Media{
Sort: *item.Sort,
URL: item.Url,
Type: item.Type,
})
}
update.Media = m
}
if len(in.GetCustomFields()) > 0 {
// 自定義屬性
c := make([]usecase.CustomFields, 0, len(in.GetCustomFields()))
for _, cItem := range in.GetCustomFields() {
c = append(c, usecase.CustomFields{
Key: cItem.Key,
Value: cItem.Value,
})
}
update.CustomFields = c
}
if len(in.GetFreight()) > 0 {
// 運費
f := make([]usecase.CustomFields, 0, len(in.GetFreight()))
for _, fItem := range in.GetFreight() {
f = append(f, usecase.CustomFields{
Key: fItem.Key,
Value: fItem.Value,
})
}
update.Freight = f
}
err := l.svcCtx.ProductItemUseCase.Update(l.ctx, in.GetId(), update)
if err != nil {
return nil, err
}
return &product.OKResp{}, nil
}

View File

@ -0,0 +1,42 @@
package productitemservicelogic
import (
"context"
"code.30cm.net/digimon/app-cloudep-product-service/pkg/domain/product"
"code.30cm.net/digimon/library-go/errs"
PB "code.30cm.net/digimon/app-cloudep-product-service/gen_result/pb/product"
"code.30cm.net/digimon/app-cloudep-product-service/internal/svc"
"github.com/zeromicro/go-zero/core/logx"
)
type UpdateStatusLogic struct {
ctx context.Context
svcCtx *svc.ServiceContext
logx.Logger
}
func NewUpdateStatusLogic(ctx context.Context, svcCtx *svc.ServiceContext) *UpdateStatusLogic {
return &UpdateStatusLogic{
ctx: ctx,
svcCtx: svcCtx,
Logger: logx.WithContext(ctx),
}
}
// UpdateStatus 更新 Item status
func (l *UpdateStatusLogic) UpdateStatus(in *PB.UpdateStatusRequest) (*PB.OKResp, error) {
status, e := product.StringToItemStatus(in.GetStatus())
if !e {
return nil, errs.InvalidFormat("failed to convert string to item status")
}
err := l.svcCtx.ProductItemUseCase.UpdateStatus(l.ctx, in.GetId(), status)
if err != nil {
return nil, err
}
return &PB.OKResp{}, nil
}

View File

@ -0,0 +1,56 @@
package tagservicelogic
import (
"context"
"code.30cm.net/digimon/app-cloudep-product-service/gen_result/pb/product"
"code.30cm.net/digimon/app-cloudep-product-service/internal/svc"
"code.30cm.net/digimon/app-cloudep-product-service/pkg/domain/entity"
domainProduct "code.30cm.net/digimon/app-cloudep-product-service/pkg/domain/product"
"code.30cm.net/digimon/library-go/errs"
"github.com/zeromicro/go-zero/core/logx"
)
type CreateLogic struct {
ctx context.Context
svcCtx *svc.ServiceContext
logx.Logger
}
func NewCreateLogic(ctx context.Context, svcCtx *svc.ServiceContext) *CreateLogic {
return &CreateLogic{
ctx: ctx,
svcCtx: svcCtx,
Logger: logx.WithContext(ctx),
}
}
// Create 建立 tags
func (l *CreateLogic) Create(in *product.CreateTagsReq) (*product.OKResp, error) {
showType, status := domainProduct.StringToShowType(in.GetShowType())
if !status {
return nil, errs.InvalidFormat("failed to convert show types")
}
itemTypes, itemStatus := domainProduct.StringToItemType(in.GetTypes())
if !itemStatus {
return nil, errs.InvalidFormat("failed to convert types")
}
insert := &entity.Tags{
Types: itemTypes,
Name: in.GetName(),
ShowType: showType,
}
if in.Cover != nil {
insert.Cover = in.Cover
}
err := l.svcCtx.TagsUseCase.Create(l.ctx, insert)
if err != nil {
return nil, err
}
return &product.OKResp{}, nil
}

View File

@ -0,0 +1,34 @@
package tagservicelogic
import (
"context"
"code.30cm.net/digimon/app-cloudep-product-service/gen_result/pb/product"
"code.30cm.net/digimon/app-cloudep-product-service/internal/svc"
"github.com/zeromicro/go-zero/core/logx"
)
type DeleteLogic struct {
ctx context.Context
svcCtx *svc.ServiceContext
logx.Logger
}
func NewDeleteLogic(ctx context.Context, svcCtx *svc.ServiceContext) *DeleteLogic {
return &DeleteLogic{
ctx: ctx,
svcCtx: svcCtx,
Logger: logx.WithContext(ctx),
}
}
// Delete 刪除tags
func (l *DeleteLogic) Delete(in *product.TagsReq) (*product.OKResp, error) {
err := l.svcCtx.TagsUseCase.Delete(l.ctx, in.GetId())
if err != nil {
return nil, err
}
return &product.OKResp{}, nil
}

View File

@ -0,0 +1,45 @@
package tagservicelogic
import (
"context"
"code.30cm.net/digimon/app-cloudep-product-service/gen_result/pb/product"
"code.30cm.net/digimon/app-cloudep-product-service/internal/svc"
"github.com/zeromicro/go-zero/core/logx"
)
type GetLogic struct {
ctx context.Context
svcCtx *svc.ServiceContext
logx.Logger
}
func NewGetLogic(ctx context.Context, svcCtx *svc.ServiceContext) *GetLogic {
return &GetLogic{
ctx: ctx,
svcCtx: svcCtx,
Logger: logx.WithContext(ctx),
}
}
// Get 取得 tags
func (l *GetLogic) Get(in *product.TagsReq) (*product.Tags, error) {
tag, err := l.svcCtx.TagsUseCase.GetByID(l.ctx, in.GetId())
if err != nil {
return nil, err
}
result := &product.Tags{Id: tag.ID.Hex(),
Types: tag.Types.ToString(),
Name: tag.Name,
ShowType: tag.ShowType.ToString(),
UpdateAt: tag.UpdatedAt,
CreatedAt: tag.CreatedAt,
}
if tag.Cover != nil {
result.Cover = *tag.Cover
}
return result, nil
}

View File

@ -0,0 +1,92 @@
package tagservicelogic
import (
"context"
"code.30cm.net/digimon/app-cloudep-product-service/gen_result/pb/product"
"code.30cm.net/digimon/app-cloudep-product-service/internal/svc"
"code.30cm.net/digimon/app-cloudep-product-service/pkg/domain/entity"
domain "code.30cm.net/digimon/app-cloudep-product-service/pkg/domain/product"
"code.30cm.net/digimon/app-cloudep-product-service/pkg/domain/usecase"
"code.30cm.net/digimon/library-go/errs"
"github.com/zeromicro/go-zero/core/logx"
)
type ListLogic struct {
ctx context.Context
svcCtx *svc.ServiceContext
logx.Logger
}
func NewListLogic(ctx context.Context, svcCtx *svc.ServiceContext) *ListLogic {
return &ListLogic{
ctx: ctx,
svcCtx: svcCtx,
Logger: logx.WithContext(ctx),
}
}
func (l *ListLogic) List(in *product.ListTagsReq) (*product.ListTagsResp, error) {
if len(in.GetIds()) > 0 {
tags, err := l.svcCtx.TagsUseCase.GetByIDs(l.ctx, in.GetIds())
if err != nil {
return nil, err
}
return buildTagResp(tags, int64(len(tags))), nil
}
// 查詢條件轉換
q := usecase.TagQueryParams{
PageSize: in.GetPageSize(),
PageIndex: in.GetPageIndex(),
}
if in.Name != nil {
q.Name = in.Name
}
if in.Types != nil {
itemTypes, ok := domain.StringToItemType(in.GetTypes())
if !ok {
return nil, errs.InvalidFormat("failed to convert types")
}
q.Types = &itemTypes
}
if in.ShowType != nil {
showType, ok := domain.StringToShowType(in.GetShowType())
if !ok {
return nil, errs.InvalidFormat("failed to convert show types")
}
q.ShowType = &showType
}
tags, total, err := l.svcCtx.TagsUseCase.List(l.ctx, q)
if err != nil {
return nil, err
}
return buildTagResp(tags, total), nil
}
func buildTagResp(tags []*entity.Tags, total int64) *product.ListTagsResp {
result := make([]*product.Tags, 0, len(tags))
for _, tag := range tags {
r := &product.Tags{
Id: tag.ID.Hex(),
Types: tag.Types.ToString(),
Name: tag.Name,
ShowType: tag.ShowType.ToString(),
UpdateAt: tag.UpdatedAt,
CreatedAt: tag.CreatedAt,
}
if tag.Cover != nil {
r.Cover = *tag.Cover
}
result = append(result, r)
}
return &product.ListTagsResp{
Total: total,
Data: result,
}
}

View File

@ -0,0 +1,64 @@
package tagservicelogic
import (
"context"
domain "code.30cm.net/digimon/app-cloudep-product-service/pkg/domain/product"
"code.30cm.net/digimon/app-cloudep-product-service/pkg/domain/usecase"
"code.30cm.net/digimon/library-go/errs"
"code.30cm.net/digimon/app-cloudep-product-service/gen_result/pb/product"
"code.30cm.net/digimon/app-cloudep-product-service/internal/svc"
"github.com/zeromicro/go-zero/core/logx"
)
type ModifyLogic struct {
ctx context.Context
svcCtx *svc.ServiceContext
logx.Logger
}
func NewModifyLogic(ctx context.Context, svcCtx *svc.ServiceContext) *ModifyLogic {
return &ModifyLogic{
ctx: ctx,
svcCtx: svcCtx,
Logger: logx.WithContext(ctx),
}
}
// Modify 修改 tags
func (l *ModifyLogic) Modify(in *product.ModifyTagsReq) (*product.OKResp, error) {
var params usecase.TagModifyParams
if in.Types != nil {
itemTypes, ok := domain.StringToItemType(in.GetTypes())
if !ok {
return nil, errs.InvalidFormat("failed to convert types")
}
params.Types = &itemTypes
}
if in.ShowType != nil {
showType, ok := domain.StringToShowType(in.GetShowType())
if !ok {
return nil, errs.InvalidFormat("failed to convert show types")
}
params.ShowType = &showType
}
// 處理 name
if in.Name != nil {
params.Name = in.Name
}
// 處理 cover
if in.Cover != nil {
params.Cover = in.Cover
}
// 執行更新
err := l.svcCtx.TagsUseCase.Update(l.ctx, in.GetId(), params)
if err != nil {
return nil, err
}
return &product.OKResp{}, nil
}

View File

@ -0,0 +1,54 @@
// Code generated by goctl. DO NOT EDIT.
// goctl 1.8.1
// Source: product.proto
package server
import (
"context"
"code.30cm.net/digimon/app-cloudep-product-service/gen_result/pb/product"
categoryservicelogic "code.30cm.net/digimon/app-cloudep-product-service/internal/logic/category_service"
"code.30cm.net/digimon/app-cloudep-product-service/internal/svc"
)
type CategoryServiceServer struct {
svcCtx *svc.ServiceContext
product.UnimplementedCategory_ServiceServer
}
func NewCategoryServiceServer(svcCtx *svc.ServiceContext) *CategoryServiceServer {
return &CategoryServiceServer{
svcCtx: svcCtx,
}
}
// Create 建立 product 分類
func (s *CategoryServiceServer) Create(ctx context.Context, in *product.CreateCategoryReq) (*product.OKResp, error) {
l := categoryservicelogic.NewCreateLogic(ctx, s.svcCtx)
return l.Create(in)
}
// Modify 修改 product 分類名稱
func (s *CategoryServiceServer) Modify(ctx context.Context, in *product.ModifyCategoryReq) (*product.OKResp, error) {
l := categoryservicelogic.NewModifyLogic(ctx, s.svcCtx)
return l.Modify(in)
}
// Delete 刪除 product 分類
func (s *CategoryServiceServer) Delete(ctx context.Context, in *product.CategoryReq) (*product.OKResp, error) {
l := categoryservicelogic.NewDeleteLogic(ctx, s.svcCtx)
return l.Delete(in)
}
// Get 取得 product 分類
func (s *CategoryServiceServer) Get(ctx context.Context, in *product.CategoryReq) (*product.Category, error) {
l := categoryservicelogic.NewGetLogic(ctx, s.svcCtx)
return l.Get(in)
}
// List 建立 product 分類
func (s *CategoryServiceServer) List(ctx context.Context, in *product.ListCategoryReq) (*product.ListCategoryResp, error) {
l := categoryservicelogic.NewListLogic(ctx, s.svcCtx)
return l.List(in)
}

View File

@ -0,0 +1,60 @@
// Code generated by goctl. DO NOT EDIT.
// goctl 1.8.1
// Source: product.proto
package server
import (
"context"
"code.30cm.net/digimon/app-cloudep-product-service/gen_result/pb/product"
kycservicelogic "code.30cm.net/digimon/app-cloudep-product-service/internal/logic/kyc_service"
"code.30cm.net/digimon/app-cloudep-product-service/internal/svc"
)
type KycServiceServer struct {
svcCtx *svc.ServiceContext
product.UnimplementedKyc_ServiceServer
}
func NewKycServiceServer(svcCtx *svc.ServiceContext) *KycServiceServer {
return &KycServiceServer{
svcCtx: svcCtx,
}
}
// Create 建立 KYC 資料
func (s *KycServiceServer) Create(ctx context.Context, in *product.CreateKycReq) (*product.OKResp, error) {
l := kycservicelogic.NewCreateLogic(ctx, s.svcCtx)
return l.Create(in)
}
// FindLatestByUID 根據使用者 UID 查詢最新 KYC 紀錄
func (s *KycServiceServer) FindLatestByUid(ctx context.Context, in *product.FindLatestKycByUIDReq) (*product.Kyc, error) {
l := kycservicelogic.NewFindLatestByUidLogic(ctx, s.svcCtx)
return l.FindLatestByUid(in)
}
// FindByID 根據 KYC ID 查詢
func (s *KycServiceServer) FindById(ctx context.Context, in *product.FindKycByIDReq) (*product.Kyc, error) {
l := kycservicelogic.NewFindByIdLogic(ctx, s.svcCtx)
return l.FindById(in)
}
// List 分頁查詢 Kyc 清單(後台審核用)
func (s *KycServiceServer) List(ctx context.Context, in *product.ListKycReq) (*product.ListKycResp, error) {
l := kycservicelogic.NewListLogic(ctx, s.svcCtx)
return l.List(in)
}
// UpdateStatus 更新 Kyc 審核狀態與原因
func (s *KycServiceServer) UpdateStatus(ctx context.Context, in *product.UpdateKycStatusReq) (*product.OKResp, error) {
l := kycservicelogic.NewUpdateStatusLogic(ctx, s.svcCtx)
return l.UpdateStatus(in)
}
// Update 更新使用者的 Kyc尚未審核
func (s *KycServiceServer) Update(ctx context.Context, in *product.UpdateKycInfoReq) (*product.OKResp, error) {
l := kycservicelogic.NewUpdateLogic(ctx, s.svcCtx)
return l.Update(in)
}

View File

@ -1,21 +0,0 @@
// Code generated by goctl. DO NOT EDIT.
// goctl 1.8.1
// Source: product.proto
package server
import (
"code.30cm.net/digimon/app-cloudep-product-service/gen_result/pb/product"
"code.30cm.net/digimon/app-cloudep-product-service/internal/svc"
)
type ProductServer struct {
svcCtx *svc.ServiceContext
product.UnimplementedProductServer
}
func NewProductServer(svcCtx *svc.ServiceContext) *ProductServer {
return &ProductServer{
svcCtx: svcCtx,
}
}

View File

@ -0,0 +1,78 @@
// Code generated by goctl. DO NOT EDIT.
// goctl 1.8.1
// Source: product.proto
package server
import (
"context"
"code.30cm.net/digimon/app-cloudep-product-service/gen_result/pb/product"
productitemservicelogic "code.30cm.net/digimon/app-cloudep-product-service/internal/logic/product_item_service"
"code.30cm.net/digimon/app-cloudep-product-service/internal/svc"
)
type ProductItemServiceServer struct {
svcCtx *svc.ServiceContext
product.UnimplementedProduct_Item_ServiceServer
}
func NewProductItemServiceServer(svcCtx *svc.ServiceContext) *ProductItemServiceServer {
return &ProductItemServiceServer{
svcCtx: svcCtx,
}
}
// Create 建立 ProductItem
func (s *ProductItemServiceServer) Create(ctx context.Context, in *product.CreateProductItemRequest) (*product.OKResp, error) {
l := productitemservicelogic.NewCreateLogic(ctx, s.svcCtx)
return l.Create(in)
}
// GetProductItem 取得 ProductItem
func (s *ProductItemServiceServer) Get(ctx context.Context, in *product.GetProductItemRequest) (*product.ProductItem, error) {
l := productitemservicelogic.NewGetLogic(ctx, s.svcCtx)
return l.Get(in)
}
// ListByProductId 使用 ProductID 取得 ProductItems
func (s *ProductItemServiceServer) ListByProductId(ctx context.Context, in *product.ListProductItemRequest) (*product.ListProductItemResponse, error) {
l := productitemservicelogic.NewListByProductIdLogic(ctx, s.svcCtx)
return l.ListByProductId(in)
}
// Delete 刪除 Delete Product Item
func (s *ProductItemServiceServer) Delete(ctx context.Context, in *product.DeleteProductItemRequest) (*product.OKResp, error) {
l := productitemservicelogic.NewDeleteLogic(ctx, s.svcCtx)
return l.Delete(in)
}
// DeleteByReferenceId 使用 ProductID 刪除所有 Item
func (s *ProductItemServiceServer) DeleteByReferenceId(ctx context.Context, in *product.DeleteProductItemsByReferenceIDReq) (*product.OKResp, error) {
l := productitemservicelogic.NewDeleteByReferenceIdLogic(ctx, s.svcCtx)
return l.DeleteByReferenceId(in)
}
// IncSalesCount 增加賣出數量
func (s *ProductItemServiceServer) IncSalesCount(ctx context.Context, in *product.IncDecSalesCountRequest) (*product.OKResp, error) {
l := productitemservicelogic.NewIncSalesCountLogic(ctx, s.svcCtx)
return l.IncSalesCount(in)
}
// DecSalesCount 減少賣出數量
func (s *ProductItemServiceServer) DecSalesCount(ctx context.Context, in *product.IncDecSalesCountRequest) (*product.OKResp, error) {
l := productitemservicelogic.NewDecSalesCountLogic(ctx, s.svcCtx)
return l.DecSalesCount(in)
}
// Update 更新 Item
func (s *ProductItemServiceServer) Update(ctx context.Context, in *product.UpdateProductItemRequest) (*product.OKResp, error) {
l := productitemservicelogic.NewUpdateLogic(ctx, s.svcCtx)
return l.Update(in)
}
// UpdateStatus 更新 Item status
func (s *ProductItemServiceServer) UpdateStatus(ctx context.Context, in *product.UpdateStatusRequest) (*product.OKResp, error) {
l := productitemservicelogic.NewUpdateStatusLogic(ctx, s.svcCtx)
return l.UpdateStatus(in)
}

View File

@ -0,0 +1,54 @@
// Code generated by goctl. DO NOT EDIT.
// goctl 1.8.1
// Source: product.proto
package server
import (
"context"
"code.30cm.net/digimon/app-cloudep-product-service/gen_result/pb/product"
tagservicelogic "code.30cm.net/digimon/app-cloudep-product-service/internal/logic/tag_service"
"code.30cm.net/digimon/app-cloudep-product-service/internal/svc"
)
type TagServiceServer struct {
svcCtx *svc.ServiceContext
product.UnimplementedTag_ServiceServer
}
func NewTagServiceServer(svcCtx *svc.ServiceContext) *TagServiceServer {
return &TagServiceServer{
svcCtx: svcCtx,
}
}
// CreateTags 建立 tags
func (s *TagServiceServer) Create(ctx context.Context, in *product.CreateTagsReq) (*product.OKResp, error) {
l := tagservicelogic.NewCreateLogic(ctx, s.svcCtx)
return l.Create(in)
}
// ModifyTags 修改 tags
func (s *TagServiceServer) Modify(ctx context.Context, in *product.ModifyTagsReq) (*product.OKResp, error) {
l := tagservicelogic.NewModifyLogic(ctx, s.svcCtx)
return l.Modify(in)
}
// DeleteTags 刪除tags
func (s *TagServiceServer) Delete(ctx context.Context, in *product.TagsReq) (*product.OKResp, error) {
l := tagservicelogic.NewDeleteLogic(ctx, s.svcCtx)
return l.Delete(in)
}
// GetTags 取得 tags
func (s *TagServiceServer) Get(ctx context.Context, in *product.TagsReq) (*product.Tags, error) {
l := tagservicelogic.NewGetLogic(ctx, s.svcCtx)
return l.Get(in)
}
// ListTags 建立 tags
func (s *TagServiceServer) List(ctx context.Context, in *product.ListTagsReq) (*product.ListTagsResp, error) {
l := tagservicelogic.NewListLogic(ctx, s.svcCtx)
return l.List(in)
}

View File

@ -1,13 +1,189 @@
package svc package svc
import "code.30cm.net/digimon/app-cloudep-product-service/internal/config" import (
"code.30cm.net/digimon/app-cloudep-product-service/internal/config"
"code.30cm.net/digimon/app-cloudep-product-service/pkg/domain/usecase"
repo "code.30cm.net/digimon/app-cloudep-product-service/pkg/repository"
uc "code.30cm.net/digimon/app-cloudep-product-service/pkg/usecase"
mgo "code.30cm.net/digimon/library-go/mongo"
"github.com/zeromicro/go-zero/core/stores/cache"
"github.com/zeromicro/go-zero/core/stores/mon"
)
type ServiceContext struct { type ServiceContext struct {
Config config.Config Config config.Config
CategoryUseCase usecase.CategoryUseCase
TagsUseCase usecase.ProductBaseTags
KYCUseCase usecase.KYCUseCase
ProductItemUseCase usecase.ProductItemUseCase
} }
func NewServiceContext(c config.Config) *ServiceContext { func NewServiceContext(c config.Config) *ServiceContext {
return &ServiceContext{ return &ServiceContext{
Config: c, CategoryUseCase: MustCategory(c),
TagsUseCase: MustTags(c),
KYCUseCase: MustKYC(c),
ProductItemUseCase: MustProductItem(c),
Config: c,
} }
} }
func MustCategory(c config.Config) usecase.CategoryUseCase {
// 準備Mongo Config
conf := &mgo.Conf{
Schema: c.Mongo.Schema,
Host: c.Mongo.Host,
Database: c.Mongo.Database,
MaxStaleness: c.Mongo.MaxStaleness,
MaxPoolSize: c.Mongo.MaxPoolSize,
MinPoolSize: c.Mongo.MinPoolSize,
MaxConnIdleTime: c.Mongo.MaxConnIdleTime,
Compressors: c.Mongo.Compressors,
EnableStandardReadWriteSplitMode: c.Mongo.EnableStandardReadWriteSplitMode,
ConnectTimeoutMs: c.Mongo.ConnectTimeoutMs,
}
if c.Mongo.User != "" {
conf.User = c.Mongo.User
conf.Password = c.Mongo.Password
}
// 快取選項
cacheOpts := []cache.Option{
cache.WithExpiry(c.CacheExpireTime),
cache.WithNotFoundExpiry(c.CacheWithNotFoundExpiry),
}
dbOpts := []mon.Option{
mgo.SetCustomDecimalType(),
mgo.InitMongoOptions(*conf),
}
categoryRepo := repo.MustCategoryRepository(repo.CategoryRepositoryParam{
Conf: conf,
CacheConf: c.Cache,
CacheOpts: cacheOpts,
DBOpts: dbOpts,
})
return uc.MustCategoryUseCase(uc.CategoryUseCaseParam{
CategoryRepo: categoryRepo})
}
func MustTags(c config.Config) usecase.ProductBaseTags {
// 準備Mongo Config
conf := &mgo.Conf{
Schema: c.Mongo.Schema,
Host: c.Mongo.Host,
Database: c.Mongo.Database,
MaxStaleness: c.Mongo.MaxStaleness,
MaxPoolSize: c.Mongo.MaxPoolSize,
MinPoolSize: c.Mongo.MinPoolSize,
MaxConnIdleTime: c.Mongo.MaxConnIdleTime,
Compressors: c.Mongo.Compressors,
EnableStandardReadWriteSplitMode: c.Mongo.EnableStandardReadWriteSplitMode,
ConnectTimeoutMs: c.Mongo.ConnectTimeoutMs,
}
if c.Mongo.User != "" {
conf.User = c.Mongo.User
conf.Password = c.Mongo.Password
}
// 快取選項
cacheOpts := []cache.Option{
cache.WithExpiry(c.CacheExpireTime),
cache.WithNotFoundExpiry(c.CacheWithNotFoundExpiry),
}
dbOpts := []mon.Option{
mgo.SetCustomDecimalType(),
mgo.InitMongoOptions(*conf),
}
tagsRepo := repo.NewTagsRepository(repo.TagsRepositoryParam{
Conf: conf,
CacheConf: c.Cache,
CacheOpts: cacheOpts,
DBOpts: dbOpts,
})
return uc.MustTagsUseCase(uc.TagsUseCaseParam{
TagsRepo: tagsRepo})
}
func MustKYC(c config.Config) usecase.KYCUseCase {
// 準備Mongo Config
conf := &mgo.Conf{
Schema: c.Mongo.Schema,
Host: c.Mongo.Host,
Database: c.Mongo.Database,
MaxStaleness: c.Mongo.MaxStaleness,
MaxPoolSize: c.Mongo.MaxPoolSize,
MinPoolSize: c.Mongo.MinPoolSize,
MaxConnIdleTime: c.Mongo.MaxConnIdleTime,
Compressors: c.Mongo.Compressors,
EnableStandardReadWriteSplitMode: c.Mongo.EnableStandardReadWriteSplitMode,
ConnectTimeoutMs: c.Mongo.ConnectTimeoutMs,
}
if c.Mongo.User != "" {
conf.User = c.Mongo.User
conf.Password = c.Mongo.Password
}
// 快取選項
cacheOpts := []cache.Option{
cache.WithExpiry(c.CacheExpireTime),
cache.WithNotFoundExpiry(c.CacheWithNotFoundExpiry),
}
dbOpts := []mon.Option{
mgo.SetCustomDecimalType(),
mgo.InitMongoOptions(*conf),
}
kycRepo := repo.NewKYCRepository(repo.KYCRepositoryParam{
Conf: conf,
CacheConf: c.Cache,
CacheOpts: cacheOpts,
DBOpts: dbOpts,
})
return uc.MustKYCUseCase(uc.KYCUseCaseParam{
KYCRepo: kycRepo})
}
func MustProductItem(c config.Config) usecase.ProductItemUseCase {
// 準備Mongo Config
conf := &mgo.Conf{
Schema: c.Mongo.Schema,
Host: c.Mongo.Host,
Database: c.Mongo.Database,
MaxStaleness: c.Mongo.MaxStaleness,
MaxPoolSize: c.Mongo.MaxPoolSize,
MinPoolSize: c.Mongo.MinPoolSize,
MaxConnIdleTime: c.Mongo.MaxConnIdleTime,
Compressors: c.Mongo.Compressors,
EnableStandardReadWriteSplitMode: c.Mongo.EnableStandardReadWriteSplitMode,
ConnectTimeoutMs: c.Mongo.ConnectTimeoutMs,
}
if c.Mongo.User != "" {
conf.User = c.Mongo.User
conf.Password = c.Mongo.Password
}
// 快取選項
cacheOpts := []cache.Option{
cache.WithExpiry(c.CacheExpireTime),
cache.WithNotFoundExpiry(c.CacheWithNotFoundExpiry),
}
dbOpts := []mon.Option{
mgo.SetCustomDecimalType(),
mgo.InitMongoOptions(*conf),
}
productItemRepo := repo.NewProductItemRepository(repo.ProductItemRepositoryParam{
Conf: conf,
CacheConf: c.Cache,
CacheOpts: cacheOpts,
DBOpts: dbOpts,
})
return uc.MustProductItemUseCase(uc.ProductItemUseCaseParam{
ProductItems: productItemRepo})
}

23
internal/utils/encode.go Executable file
View File

@ -0,0 +1,23 @@
package utils
import (
"encoding/base64"
"github.com/golang/snappy"
)
// EncodeToBase64Snappy 將字串進行 Base64 和 Snappy 編碼
func EncodeToBase64Snappy(data string) string {
snappyData := snappy.Encode(nil, []byte(data))
encoded := base64.StdEncoding.EncodeToString(snappyData)
return encoded
}
// DecodeToBase64Snappy 將字串進行 Base64 和 Snappy 解碼
func DecodeToBase64Snappy(data string) string {
decodeB64Content, _ := base64.StdEncoding.DecodeString(data)
snappyContent, _ := snappy.Decode(nil, decodeB64Content)
return string(snappyContent)
}

8
pkg/domain/const.go Normal file
View File

@ -0,0 +1,8 @@
package domain
import "time"
const (
DefaultSingleFlyCacheTimeout = 60 * time.Second
DefaultFindDataNotFoundTimeout = 5 * time.Second
)

View File

@ -0,0 +1,17 @@
package entity
import (
"go.mongodb.org/mongo-driver/bson/primitive"
)
// Category 類別以後獨立服務
type Category struct {
ID primitive.ObjectID `bson:"_id,omitempty"`
Name string `bson:"name"` // 服務類別
UpdatedAt int64 `bson:"updated_at"` // 更新時間
CreatedAt int64 `bson:"created_at"` // 創建時間
}
func (c *Category) CollectionName() string {
return "category"
}

34
pkg/domain/entity/kyc.go Normal file
View File

@ -0,0 +1,34 @@
package entity
import (
"code.30cm.net/digimon/app-cloudep-product-service/pkg/domain/kyc"
"go.mongodb.org/mongo-driver/bson/primitive"
)
type KYC struct {
ID primitive.ObjectID `bson:"_id,omitempty"`
UID string `bson:"uid"` // 驗證人 UID
CountryRegion string `bson:"country_region"` // 地區(例如 "TW", "JP", "US"...
Name string `bson:"name"` // 真實姓名
Identification string `bson:"identification"` // 身分證字號 or 護照號碼
IdentificationType string `bson:"identification_type"` // ID 類型ID_CARD, PASSPORT, RESIDENT_CERT
Address string `bson:"address"` // 戶籍地址(或居住地址)
PostalCode string `bson:"postal_code"` // 郵遞區號(海外使用)
// 上傳文件網址(可為 object storage 的 URL
IDFrontImage string `bson:"id_front_image"` // 身分證/護照 正面
IDBackImage string `bson:"id_back_image"` // 身分證/居留證 反面
BankStatementImg string `bson:"bank_statement_img"` // 銀行存摺封面照
BankCode string `bson:"bank_code"` // 銀行代碼(可為 SWIFT
BankName string `bson:"bank_name"` // 銀行名稱(顯示用)
BranchCode string `bson:"branch_code"` // 分行代碼
BranchName string `bson:"branch_name"` // 分行名稱(顯示用)
BankAccount string `bson:"bank_account"` // 銀行帳號
Status kyc.Status `bson:"status"` // 審核狀態PENDING, APPROVED, REJECTED
RejectReason string `bson:"reject_reason"` // 若被駁回,原因描述
UpdatedAt int64 `bson:"updated_at,omitempty"`
CreatedAt int64 `bson:"created_at,omitempty"`
}
func (p *KYC) CollectionName() string {
return "kyc"
}

44
pkg/domain/entity/product.go Executable file
View File

@ -0,0 +1,44 @@
package entity
import "go.mongodb.org/mongo-driver/bson/primitive"
type Stage struct {
Stage uint64 `bson:"stage"` // 第幾階段
Target uint64 `bson:"target"` // 階段目標金額
Description string `bson:"description"` // 階段描述
}
// Product 專案,頻道,等共用的東西
type Product struct {
ID primitive.ObjectID `bson:"_id,omitempty"` // 專案 ID
UID string `bson:"uid"` // 專案擁有者 UID
Title string `bson:"title"` // 專案名稱
ShortTitle *string `bson:"short_title,omitempty"` // 計畫簡短標題 -> 不一定要有
Details *string `bson:"details"` // 詳細內容
ShortDescription string `bson:"short_description,omitempty"` // 簡短描述
Media []Media `bson:"media,omitempty"` // 專案動態內容(圖片或者影片)
Slug *string `bson:"slug,omitempty"` // URL 後綴(查詢用)
IsPublished bool `bson:"is_published" ` // 是否已上架
Amount uint64 `bson:"amount,omitempty"` // 目標金額
StartTime *int64 `bson:"start_time,omitempty"` // 專案開始時間
EndTime *int64 `bson:"end_time,omitempty"` // 專案結束時間
Category string `bson:"category"` // 類別
CustomFields []CustomFields `bson:"custom_fields,omitempty"` // 自定義屬性
UpdatedAt int64 `bson:"updated_at,omitempty"` // 更新時間
CreatedAt int64 `bson:"created_at,omitempty"` // 建立時間
}
type Media struct {
Sort uint64 `bson:"sort"`
Type string `bson:"type"`
URL string `bson:"url"`
}
type CustomFields struct {
Key string `bson:"key"`
Value string `bson:"value"`
}
func (p *Product) CollectionName() string {
return "product"
}

View File

@ -0,0 +1,32 @@
package entity
import (
"code.30cm.net/digimon/app-cloudep-product-service/pkg/domain/product"
"github.com/shopspring/decimal"
"go.mongodb.org/mongo-driver/bson/primitive"
)
type ProductItems struct {
ID primitive.ObjectID `bson:"_id,omitempty"` // 專案 ID
ReferenceID string `bson:"reference_id"` // 對應的專案 ID
Name string `bson:"name"` // 名稱
Description string `bson:"description"` // 描述
ShortDescription string `bson:"short_description"` // 封面簡短描述
IsUnLimit bool `bson:"is_un_limit"` // 是否沒有數量上限
IsFree bool `bson:"is_free"` // 是否為免費品項(贈品) -> 開啟就是自訂金額
Stock uint64 `bson:"stock"` // 庫存總數
Price decimal.Decimal `bson:"price"` // 價格
SKU string `bson:"sku"` // 型號:對應顯示 Item 的 FK
TimeSeries product.TimeSeries `bson:"time_series"` // 時段種類
Media []Media `bson:"media,omitempty"` // 專案動態內容(圖片或者影片)
Status product.ItemStatus `bson:"status"` // 商品狀態
Freight []CustomFields `bson:"freight,omitempty"` // 運費
CustomFields []CustomFields `bson:"custom_fields,omitempty"` // 自定義屬性
SalesCount uint64 `bson:"sales_count" ` // 已賣出數量(相反,減到零就不能在賣)
UpdatedAt int64 `bson:"updated_at"` // 更新時間
CreatedAt int64 `bson:"created_at"` // 創建時間
}
func (p *ProductItems) CollectionName() string {
return "product_items"
}

View File

@ -0,0 +1,21 @@
package entity
import "go.mongodb.org/mongo-driver/bson/primitive"
// ProductStatistics 統計的資訊表
type ProductStatistics struct {
ID primitive.ObjectID `bson:"_id,omitempty"` // 專案 ID
ProductID string `bson:"product_id"` // 對應的專案 ID
Orders uint64 `bson:"total_orders"` // 總接單數
OrdersUpdateTime int64 `bson:"total_orders_update_time"` // 更新總接單數的時間
AverageRating float64 `bson:"average_rating"` // 綜合評價4.5 顆星)
AverageRatingUpdateTime int64 `bson:"average_rating_time"` // 更新評價的時間
FansCount uint64 `bson:"fans_count"` // 追蹤數量
FansCountUpdateTime int64 `bson:"fans_count_update_time"` // 更新追蹤的時間
UpdatedAt int64 `bson:"updated_at"` // 更新時間
CreatedAt int64 `bson:"created_at"` // 創建時間
}
func (p *ProductStatistics) CollectionName() string {
return "product_statistics"
}

View File

@ -0,0 +1,20 @@
package entity
import (
"code.30cm.net/digimon/app-cloudep-product-service/pkg/domain/product"
"go.mongodb.org/mongo-driver/bson/primitive"
)
type Tags struct {
ID primitive.ObjectID `bson:"_id,omitempty"` // 專案 ID
Types product.ItemType `bson:"types"` // Tag 類型
Name string `bson:"name"` // tag 名稱
ShowType product.ShowType `bson:"show_type"` // 顯示筐
Cover *string `bson:"cover,omitempty"` // 封面圖片
UpdatedAt int64 `bson:"updated_at" json:"updated_at"` // 更新時間
CreatedAt int64 `bson:"created_at" json:"created_at"` // 創建時間
}
func (p *Tags) CollectionName() string {
return "tags"
}

View File

@ -0,0 +1,15 @@
package entity
import "go.mongodb.org/mongo-driver/bson/primitive"
type TagsBindingTable struct {
ID primitive.ObjectID `bson:"_id,omitempty"`
ReferenceID string `bson:"reference_id"` // 參照 id (可能為專案或其他的東西)
TagID string `bson:"tag_id"` // tag id
UpdatedAt int64 `bson:"updated_at"` // 更新時間
CreatedAt int64 `bson:"created_at"` // 創建時間
}
func (tbt *TagsBindingTable) CollectionName() string {
return "tag_binding_table"
}

1
pkg/domain/error.go Normal file
View File

@ -0,0 +1 @@
package domain

13
pkg/domain/kyc/status.go Normal file
View File

@ -0,0 +1,13 @@
package kyc
type Status string
func (s *Status) ToString() string {
return string(*s)
}
const (
StatusPending Status = "PENDING"
StatusAPPROVED Status = "APPROVED"
StatusREJECTED Status = "REJECTED"
)

View File

@ -0,0 +1,50 @@
package product
type ItemStatus int8
func (p *ItemStatus) ToString() string {
s, _ := StatusToString(*p)
return s
}
const (
StatusActive ItemStatus = 1 + iota // 上架
StatusInactive // 下架
StatusOutOfStock // 缺貨
StatusUnderReview // 商品審核中
)
const (
StatusActiveStr = "active"
StatusInactiveStr = "inactive"
StatusOutOfStockStr = "out_of_stock"
StatusUnderReviewStr = "under_review"
)
var statusToStringMap = map[ItemStatus]string{
StatusActive: StatusActiveStr,
StatusInactive: StatusInactiveStr,
StatusOutOfStock: StatusOutOfStockStr,
}
var stringToStatusMap = map[string]ItemStatus{
StatusActiveStr: StatusActive,
StatusInactiveStr: StatusInactive,
StatusOutOfStockStr: StatusOutOfStock,
StatusUnderReviewStr: StatusUnderReview,
}
// StatusToString 將 ProductItemStatus 轉換為字串
func StatusToString(status ItemStatus) (string, bool) {
str, ok := statusToStringMap[status]
return str, ok
}
// StringToItemStatus 將字串轉換為 ProductItemStatus
func StringToItemStatus(statusStr string) (ItemStatus, bool) {
status, ok := stringToStatusMap[statusStr]
return status, ok
}

View File

@ -0,0 +1,51 @@
package product
type ItemType int8
const (
ItemTypeUnknown ItemType = iota // 未知
ItemTypeNormal // 普通 tag
ItemTypeSkill // 技能專用 tag
ItemTypeProduct // 專案用
)
func (t *ItemType) ToString() string {
s, _ := ItemTypeToString(*t)
return s
}
const (
ItemTypeUnknownStr = "unknown"
ItemTypeNormalStr = "normal"
ItemTypeSkillStr = "skill"
ItemTypeProductStr = "product"
)
var itemTypeToStringMap = map[ItemType]string{
ItemTypeUnknown: ItemTypeUnknownStr,
ItemTypeNormal: ItemTypeNormalStr,
ItemTypeSkill: ItemTypeSkillStr,
ItemTypeProduct: ItemTypeProductStr,
}
var stringToItemTypeMap = map[string]ItemType{
ItemTypeUnknownStr: ItemTypeUnknown,
ItemTypeNormalStr: ItemTypeNormal,
ItemTypeSkillStr: ItemTypeSkill,
ItemTypeProductStr: ItemTypeProduct,
}
// ItemTypeToString 將 ItemType 轉換為字串
func ItemTypeToString(it ItemType) (string, bool) {
str, ok := itemTypeToStringMap[it]
return str, ok
}
// StringToItemType 將字串轉換為 ItemType
func StringToItemType(str string) (ItemType, bool) {
it, ok := stringToItemTypeMap[str]
return it, ok
}

View File

@ -0,0 +1,39 @@
package product
type ShowType int64
const (
ShowTypeNormal ShowType = iota // 沒有任何外匡
)
func (s *ShowType) ToString() string {
str, _ := ShowTypeToString(*s)
return str
}
const (
ShowTypeNormalStr = "normal"
)
var showTypeToStringMap = map[ShowType]string{
ShowTypeNormal: ShowTypeNormalStr,
}
var stringToShowTypeMap = map[string]ShowType{
ShowTypeNormalStr: ShowTypeNormal,
}
// ShowTypeToString 將 ShowType 轉換為字串
func ShowTypeToString(st ShowType) (string, bool) {
str, ok := showTypeToStringMap[st]
return str, ok
}
// StringToShowType 將字串轉換為 ShowType
func StringToShowType(str string) (ShowType, bool) {
st, ok := stringToShowTypeMap[str]
return st, ok
}

View File

@ -0,0 +1,48 @@
package product
type TimeSeries int8
const (
TimeSeriesUnknown TimeSeries = iota // 未知
TimeSeriesTenMinutes // 每 10 分鐘
TimeSeriesHalfHour // 每半小時
TimeSeriesOneHour // 每小時
)
func (t *TimeSeries) ToString() string {
s, _ := TimeSeriesToString(*t)
return s
}
const (
TimeSeriesUnknownStr = "unknown"
TimeSeriesTenMinutesStr = "ten_minutes"
TimeSeriesHalfHourStr = "half_hour"
TimeSeriesOneHourStr = "one_hour"
)
var timeSeriesToStringMap = map[TimeSeries]string{
TimeSeriesUnknown: TimeSeriesUnknownStr,
TimeSeriesTenMinutes: TimeSeriesTenMinutesStr,
TimeSeriesHalfHour: TimeSeriesHalfHourStr,
TimeSeriesOneHour: TimeSeriesOneHourStr,
}
var stringToTimeSeriesMap = map[string]TimeSeries{
TimeSeriesUnknownStr: TimeSeriesUnknown,
TimeSeriesTenMinutesStr: TimeSeriesTenMinutes,
TimeSeriesHalfHourStr: TimeSeriesHalfHour,
TimeSeriesOneHourStr: TimeSeriesOneHour,
}
// TimeSeriesToString 將 TimeSeries 轉換為字串
func TimeSeriesToString(ts TimeSeries) (string, bool) {
str, ok := timeSeriesToStringMap[ts]
return str, ok
}
// StringToTimeSeries 將字串轉換為 TimeSeries
func StringToTimeSeries(str string) (TimeSeries, bool) {
ts, ok := stringToTimeSeriesMap[str]
return ts, ok
}

43
pkg/domain/redis.go Normal file
View File

@ -0,0 +1,43 @@
package domain
import "strings"
type RedisKey string
func (key RedisKey) ToString() string {
return "product:" + string(key)
}
func (key RedisKey) With(s ...string) RedisKey {
parts := append([]string{string(key)}, s...)
return RedisKey(strings.Join(parts, ":"))
}
const (
GetProductRedisKey RedisKey = "get"
GetProductItemRedisKey RedisKey = "get_item"
GetProductStatisticsRedisKey RedisKey = "statistics"
GetTagsRedisKey RedisKey = "tags"
CategoryRedisKey RedisKey = "category"
)
func GetProductRK(id string) string {
return GetProductRedisKey.With(id).ToString()
}
func GetProductItemRK(id string) string {
return GetProductItemRedisKey.With(id).ToString()
}
func GetProductStatisticsRK(id string) string {
return GetProductStatisticsRedisKey.With(id).ToString()
}
func GetTagsRK(id string) string {
return GetTagsRedisKey.With(id).ToString()
}
func GetCategoryRedisKey(id string) string {
return CategoryRedisKey.With(id).ToString()
}

View File

@ -0,0 +1,26 @@
package repository
import (
"context"
"code.30cm.net/digimon/app-cloudep-product-service/pkg/domain/entity"
"go.mongodb.org/mongo-driver/mongo"
)
// CategoryRepository 操作更多信息
type CategoryRepository interface {
Insert(ctx context.Context, data *entity.Category) error
FindOneByID(ctx context.Context, id string) (*entity.Category, error)
Update(ctx context.Context, id string, data *entity.Category) (*mongo.UpdateResult, error)
Delete(ctx context.Context, id string) (int64, error)
ListCategory(ctx context.Context, params *CategoryQueryParams) ([]*entity.Category, int64, error)
// IsCategoryExists 丟進去存在的再拿出來
IsCategoryExists(ctx context.Context, ids []string) []string
}
type CategoryQueryParams struct {
PageSize int64 // 每頁顯示的專案數量
PageIndex int64 // 要查詢的頁數
ID []string
}

View File

@ -0,0 +1,49 @@
package repository
import (
"context"
"code.30cm.net/digimon/app-cloudep-product-service/pkg/domain/entity"
)
// KYCKnow Your Customer
type KYCRepository interface {
// Create 建立 KYC 資料
Create(ctx context.Context, kyc *entity.KYC) error
// FindLatestByUID 根據使用者 UID 取得最新 KYC 紀錄
FindLatestByUID(ctx context.Context, uid string) (*entity.KYC, error)
// FindByID 根據 KYC ID 查詢
FindByID(ctx context.Context, id string) (*entity.KYC, error)
// List 分頁查詢(後台審核列表用)
List(ctx context.Context, params KYCQueryParams) ([]*entity.KYC, int64, error)
// UpdateStatus 更新 KYC 狀態與審核原因(審核用)
UpdateStatus(ctx context.Context, id string, status string, reason string) error
// UpdateKYCInfo 更新使用者的 KYC限於尚未審核的
UpdateKYCInfo(ctx context.Context, id string, update *KYCUpdateParams) error
}
type KYCQueryParams struct {
UID *string
Country *string
Status *string // PENDING, APPROVED, REJECTED
PageSize int64
PageIndex int64
SortByDate bool // 是否依申請時間倒序
}
type KYCUpdateParams struct {
Name *string
Identification *string
IdentificationType *string
Address *string
PostalCode *string
IDFrontImage *string
IDBackImage *string
BankStatementImg *string
BankCode *string
BankName *string
BranchCode *string
BranchName *string
BankAccount *string
}

View File

@ -0,0 +1,56 @@
package repository
import (
"context"
"code.30cm.net/digimon/app-cloudep-product-service/pkg/domain/entity"
"go.mongodb.org/mongo-driver/mongo"
"go.mongodb.org/mongo-driver/mongo/options"
)
// ProductRepository 存取 Product 的地方
type ProductRepository interface {
Insert(ctx context.Context, data *entity.Product) error
FindOneByID(ctx context.Context, id string) (*entity.Product, error)
FindOneBySlug(ctx context.Context, slug string) (*entity.Product, error)
Update(ctx context.Context, productID string, data *ProductUpdateParams) (*mongo.UpdateResult, error)
Delete(ctx context.Context, id string) error
ListProduct(ctx context.Context, params *ProductQueryParams) ([]*entity.Product, int64, error)
Transaction(ctx context.Context,
fn func(sessCtx mongo.SessionContext) (any, error),
opts ...*options.TransactionOptions) error
ProductIndex
}
type ProductIndex interface {
Index20250317001UP(ctx context.Context) (*mongo.Cursor, error)
}
// ProductQueryParams 用於查詢專案的參數
type ProductQueryParams struct {
UID *string // 專案擁有者 UID
IsPublished *bool // 是否已上架
Category *string // 類別
StartTime *int64 // 起始時間Unix 時間戳)
EndTime *int64 // 結束時間Unix 時間戳)
Slug *string // URL 後綴
PageIndex int64 // 頁碼
PageSize int64 // 每頁數量
}
// ProductUpdateParams 用於更新專案的參數
type ProductUpdateParams struct {
Title *string
ShortTitle *string
Details *string
ShortDescription string
Media []entity.Media
Category *string
Slug *string
IsPublished *bool
Amount *uint64
StartTime *int64
EndTime *int64
CustomFields []entity.CustomFields
}

View File

@ -0,0 +1,77 @@
package repository
import (
"context"
"code.30cm.net/digimon/app-cloudep-product-service/pkg/domain/entity"
"code.30cm.net/digimon/app-cloudep-product-service/pkg/domain/product"
"github.com/shopspring/decimal"
"go.mongodb.org/mongo-driver/mongo"
)
// ProductItemRepository 定義商品相關操作的接口
type ProductItemRepository interface {
ProductItemIndex
ProductItemBasic
ProductItemStatistics
}
type ProductItemIndex interface {
Index20250317001UP(ctx context.Context) (*mongo.Cursor, error)
}
// ProductItemBasic 基礎操作
type ProductItemBasic interface {
// Insert 一次新增多筆
Insert(ctx context.Context, item []entity.ProductItems) error
// Delete 刪除商品
Delete(ctx context.Context, ids []string) error
// Update 更新商品
Update(ctx context.Context, id string, param *ProductUpdateItem) error
// DeleteByReferenceID 刪除某Project 下所有
DeleteByReferenceID(ctx context.Context, id string) error
// FindByID 根據 ID 查找商品
FindByID(ctx context.Context, id string) (*entity.ProductItems, error)
// ListProductItem 查找商品列表
ListProductItem(ctx context.Context, param ProductItemQueryParams) ([]entity.ProductItems, int64, error)
// UpdateStatus 更新品相狀態
UpdateStatus(ctx context.Context, id string, status product.ItemStatus) error
}
type ProductItemQueryParams struct {
PageSize int64 // 每頁顯示的專案數量
PageIndex int64 // 要查詢的頁數
ItemID []string // Item 的 ID
ReferenceID *string // 對應參照的ID
IsFree *bool // 是否為免費品項(贈品)
Status *product.ItemStatus // 商品狀態
IsUnlimited *bool
}
type ProductUpdateItem struct {
Name *string // 名稱
Description *string // 描述
Price *decimal.Decimal // 價格
Stock *int64 // 庫存總數
ShortDescription *string // 封面簡短描述
IsUnLimit *bool // 是否沒有數量上限
IsFree *bool // 是否為免費品項(贈品) -> 開啟就是自訂金額
SKU *string // 型號:對應顯示 Item 的 FK
TimeSeries *product.TimeSeries // 時段種類
Media []entity.Media // 專案動態內容(圖片或者影片)
Freight []entity.CustomFields // 運費
CustomFields []entity.CustomFields // 自定義屬性
}
type ProductItemStatistics interface {
// IncSalesCount 更新接單總數 -> 新增
IncSalesCount(ctx context.Context, id string, count int64) error
// DecSalesCount 減少接單總數 -> 取消
DecSalesCount(ctx context.Context, id string, count int64) error
// GetSalesCount 取得這item 的接單數
GetSalesCount(ctx context.Context, ids []string) ([]ProductItemSalesCount, error)
}
type ProductItemSalesCount struct {
ID string
Count uint64
}

View File

@ -0,0 +1,34 @@
package repository
import (
"context"
"code.30cm.net/digimon/app-cloudep-product-service/pkg/domain/entity"
"go.mongodb.org/mongo-driver/mongo"
)
type ProductStatisticsRepo interface {
// Create 新增一筆產品統計資料
Create(ctx context.Context, stats *entity.ProductStatistics) error
// GetByID 根據內部 ID 取得統計資料
GetByID(ctx context.Context, id string) (*entity.ProductStatistics, error)
// GetByProductID 根據產品 ID 取得統計資料
GetByProductID(ctx context.Context, productID string) (*entity.ProductStatistics, error)
// IncOrders 新增訂單數
IncOrders(ctx context.Context, productID string, count int64) error
// DecOrders 減少訂單數。-> 退貨時專用
DecOrders(ctx context.Context, productID string, count int64) error
// UpdateAverageRating 只更新綜合評價及其更新時間
UpdateAverageRating(ctx context.Context, productID string, averageRating float64) error
// IncFansCount 新增粉絲數
IncFansCount(ctx context.Context, productID string, fansCount uint64) error
// DecFansCount 減少粉絲數。-> 退貨時專用
DecFansCount(ctx context.Context, productID string, fansCount uint64) error
// Delete 刪除統計資料
Delete(ctx context.Context, id string) error
ProductStatisticsIndex
}
type ProductStatisticsIndex interface {
Index20250317001UP(ctx context.Context) (*mongo.Cursor, error)
}

View File

@ -0,0 +1,78 @@
package repository
import (
"context"
"code.30cm.net/digimon/app-cloudep-product-service/pkg/domain/entity"
"code.30cm.net/digimon/app-cloudep-product-service/pkg/domain/product"
"go.mongodb.org/mongo-driver/mongo"
)
// TagRepo 定義與 tag (Tags) 資料表相關的 CRUD 與查詢操作
type TagRepo interface {
Base
TagBindingRepo
Index
}
type Base interface {
// Create 新增一筆 Tag 資料
Create(ctx context.Context, tag *entity.Tags) error
// GetByID 根據 tag 的內部 ID 取得資料
GetByID(ctx context.Context, id string) (*entity.Tags, error)
// Update 更新現有的 Tag 資料
Update(ctx context.Context, id string, tag TagModifyParams) error
// Delete 刪除指定 ID 的 Tag 資料
Delete(ctx context.Context, id string) error
// 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 時的參數結構
type TagQueryParams struct {
Types *product.ItemType // 過濾 Tag 類型
Name *string // 過濾名稱(部分比對)
ShowType *product.ShowType // 過濾顯示筐
// 可根據需求增加其他查詢條件,如分頁、排序等
PageSize int64
PageIndex int64
}
type TagModifyParams struct {
Types *product.ItemType // 過濾 Tag 類型
Name *string // 過濾名稱(部分比對)
ShowType *product.ShowType // 過濾顯示筐
Cover *string `bson:"cover,omitempty"` // 封面圖片
}
// TagBindingRepo 定義與 tag binding (TagsBindingTable) 資料表相關的操作
type TagBindingRepo interface {
// BindTags 建立一筆 tag 與其他資料(例如專案)的綁定關係
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 的資料列表
// 回傳值分別為資料列表與符合條件的總筆數
ListTagBinding(ctx context.Context, params TagBindingQueryParams) ([]*entity.TagsBindingTable, int64, error)
}
// TagBindingQueryParams 為查詢 TagBindingTable 時的參數結構
type TagBindingQueryParams struct {
ReferenceID *string // 過濾參照 ID
TagID *string // 過濾 Tag ID
// 可根據需求增加其他查詢條件,如分頁、排序等
PageSize int64
PageIndex int64
}
type Index interface {
IndexTags20250317001UP(ctx context.Context) (*mongo.Cursor, error)
IndexTagsBinding20250317001UP(ctx context.Context) (*mongo.Cursor, error)
}

26
pkg/domain/usecase/category.go Executable file
View File

@ -0,0 +1,26 @@
package usecase
import (
"context"
"code.30cm.net/digimon/app-cloudep-product-service/pkg/domain/entity"
)
type CategoryUseCase interface {
// Insert 創建新的類別
Insert(ctx context.Context, data *entity.Category) error
// FindOneByID 根據 ID 查詢單個類別
FindOneByID(ctx context.Context, id string) (*entity.Category, error)
// Update 更新類別信息
Update(ctx context.Context, id string, data *entity.Category) error
// Delete 刪除指定 ID 的類別
Delete(ctx context.Context, id string) error
// ListCategory 根據查詢參數獲取類別列表
ListCategory(ctx context.Context, params CategoryQueryParams) ([]*entity.Category, int64, error)
}
type CategoryQueryParams struct {
PageSize int64 // 每頁顯示的專案數量
PageIndex int64 // 要查詢的頁數
ID []string
}

47
pkg/domain/usecase/kyc.go Normal file
View File

@ -0,0 +1,47 @@
package usecase
import (
"context"
"code.30cm.net/digimon/app-cloudep-product-service/pkg/domain/entity"
)
type KYCUseCase interface {
// Create 建立 KYC 資料
Create(ctx context.Context, kyc *entity.KYC) error
// FindLatestByUID 根據使用者 UID 取得最新 KYC 紀錄
FindLatestByUID(ctx context.Context, uid string) (*entity.KYC, error)
// FindByID 根據 KYC ID 查詢
FindByID(ctx context.Context, id string) (*entity.KYC, error)
// List 分頁查詢(後台審核列表用)
List(ctx context.Context, params KYCQueryParams) ([]*entity.KYC, int64, error)
// UpdateStatus 更新 KYC 狀態與審核原因(審核用)
UpdateStatus(ctx context.Context, id string, status string, reason string) error
// UpdateKYCInfo 更新使用者的 KYC限於尚未審核的
UpdateKYCInfo(ctx context.Context, id string, update *KYCUpdateParams) error
}
type KYCQueryParams struct {
UID *string
Country *string
Status *string // PENDING, APPROVED, REJECTED
PageSize int64
PageIndex int64
SortByDate bool // 是否依申請時間倒序
}
type KYCUpdateParams struct {
Name *string
Identification *string
IdentificationType *string
Address *string
PostalCode *string
IDFrontImage *string
IDBackImage *string
BankStatementImg *string
BankCode *string
BankName *string
BranchCode *string
BranchName *string
BankAccount *string
}

View File

@ -0,0 +1,133 @@
package usecase
import (
"context"
"code.30cm.net/digimon/app-cloudep-product-service/pkg/domain/product"
)
type ProductUseCase interface {
Base
// IncOrders 新增訂單數
IncOrders(ctx context.Context, productID string, count int64) error
// DecOrders 減少訂單數。-> 退貨時專用
DecOrders(ctx context.Context, productID string, count int64) error
// UpdateAverageRating 只更新綜合評價及其更新時間
UpdateAverageRating(ctx context.Context, productID string, averageRating float64) error
// IncFansCount 新增粉絲數
IncFansCount(ctx context.Context, productID string, fansCount uint64) error
// DecFansCount 減少粉絲數。
DecFansCount(ctx context.Context, productID string, fansCount uint64) error
// BindTag 建立一筆 tag 與其他資料(例如專案)的綁定關係
BindTag(ctx context.Context, binding TagsBindingTable) error
// UnbindTag 刪除一筆綁定資料
UnbindTag(ctx context.Context, binding TagsBindingTable) error
// GetBindingsByReference 根據參照 ID 取得所有綁定資料
GetBindingsByReference(ctx context.Context, referenceID string) ([]TagsBindingTableResp, error)
// ListTagBinding 根據查詢條件取得 tag binding 的資料列表
ListTagBinding(ctx context.Context, params TagBindingQueryParams) ([]TagsBindingTableResp, int64, error)
}
type Base interface {
Create(ctx context.Context, product *Product) error
Update(ctx context.Context, id string, product *Product) error
Delete(ctx context.Context, id string) error
Get(ctx context.Context, id string) (*ProductResp, error)
List(ctx context.Context, data ProductQueryParams) ([]*ProductResp, int64, error)
}
type Product struct {
UID *string // 專案擁有者 UID
Title *string // 專案名稱
ShortTitle *string // 計畫簡短標題 -> 不一定要有
Details *string // 詳細內容
ShortDescription *string // 簡短描述
Media []Media // 專案動態內容(圖片或者影片)
Slug *string // URL 後綴(查詢用)
IsPublished *bool // 是否已上架
Amount uint64 // 目標金額
StartTime *string // 專案開始時間
EndTime *string // 專案結束時間
Category *string // 類別
CustomFields []CustomFields // 自定義屬性
Tags []string // 這個專案專屬的 tagID
}
type ProductResp struct {
ID string
UID string // 專案擁有者 UID
Title string // 專案名稱
ShortTitle string // 計畫簡短標題 -> 不一定要有
Details string // 詳細內容
ShortDescription string // 簡短描述
Media []Media // 專案動態內容(圖片或者影片)
Slug string // URL 後綴(查詢用)
IsPublished bool // 是否已上架
Amount uint64 // 目標金額
StartTime string // 專案開始時間
EndTime string // 專案結束時間
Category string // 類別
CustomFields []CustomFields // 自定義屬性
Tags []Tags // 這個專案專屬的 tagID
Orders uint64 // 總接單數
OrdersUpdateTime string // 更新總接單數的時間
AverageRating float64 // 綜合評價4.5 顆星)
AverageRatingUpdateTime string // 更新評價的時間
FansCount uint64 // 追蹤數量
FansCountUpdateTime string // 更新追蹤的時間
UpdatedAt string // 更新時間
CreatedAt string // 建立時間
}
type Media struct {
Sort uint64
Type string
URL string
}
type CustomFields struct {
Key string `bson:"key"`
Value string `bson:"value"`
}
type ProductQueryParams struct {
UID *string // 專案擁有者 UID
IsPublished *bool // 是否已上架
Category *string // 類別
StartTime *int64 // 起始時間Unix 時間戳)
EndTime *int64 // 結束時間Unix 時間戳)
Slug *string // URL 後綴
PageIndex int64 // 頁碼
PageSize int64 // 每頁數量
}
type Tags struct {
ID string // 專案 ID
Types product.ItemType // Tag 類型
Name string // tag 名稱
ShowType product.ShowType // 顯示筐
Cover string // 封面圖片
UpdatedAt string // 更新時間
CreatedAt string // 創建時間
}
type TagsBindingTable struct {
ReferenceID string // 參照 id (可能為專案或其他的東西)
TagID string // tag id
}
type TagsBindingTableResp struct {
ID string
ReferenceID string // 參照 id (可能為專案或其他的東西)
TagID string // tag id
UpdatedAt string // 更新時間
CreatedAt string // 創建時間
}
// TagBindingQueryParams 為查詢 TagBindingTable 時的參數結構
type TagBindingQueryParams struct {
ReferenceID *string // 過濾參照 ID
TagID *string // 過濾 Tag ID
PageSize int64
PageIndex int64
}

View File

@ -0,0 +1,73 @@
package usecase
import (
"context"
"code.30cm.net/digimon/app-cloudep-product-service/pkg/domain/product"
)
type ProductItemUseCase interface {
// Create 新增單筆商品
Create(ctx context.Context, productItem *ProductItems) error
// Get 根據 ID 查詢單筆商品
Get(ctx context.Context, id string) (*ProductItems, error)
// Update 更新商品資訊
Update(ctx context.Context, id string, productItem *UpdateProductItems) error
// UpdateStatus 更新商品資訊
UpdateStatus(ctx context.Context, id string, status product.ItemStatus) error
// IncSalesCount 更新商品資訊
IncSalesCount(ctx context.Context, id string, saleCount uint64) error
// DecSalesCount 更新商品資訊
DecSalesCount(ctx context.Context, id string, saleCount uint64) error
// Delete ID 刪除商品
Delete(ctx context.Context, ids []string) error
// List 列出符合條件的商品,可根據需求加入分頁或其他條件參數
List(ctx context.Context, filter QueryProductItemParam) ([]*ProductItems, int64, error)
// DeleteByReferenceID 刪除某Project 下所有
DeleteByReferenceID(ctx context.Context, id string) error
}
type ProductItems struct {
ID string `json:"_id,omitempty"` // 專案 ID
ReferenceID string `json:"reference_id"` // 對應的專案 ID
Name string `json:"name"` // 名稱
Description string `json:"description"` // 描述
ShortDescription string `json:"short_description"` // 封面簡短描述
IsUnLimit bool `json:"is_un_limit"` // 是否沒有數量上限
IsFree bool `json:"is_free"` // 是否為免費品項(贈品) -> 開啟就是自訂金額
Stock uint64 `json:"stock"` // 庫存總數
Price string `json:"price"` // 價格
SKU string `json:"sku"` // 型號:對應顯示 Item 的 FK
TimeSeries product.TimeSeries `json:"time_series"` // 時段種類
Media []Media `json:"media,omitempty"` // 專案動態內容(圖片或者影片)
Status product.ItemStatus `json:"status"` // 商品狀態
Freight []CustomFields `json:"freight,omitempty"` // 運費
CustomFields []CustomFields `json:"custom_fields,omitempty"` // 自定義屬性
SalesCount uint64 `json:"sales_count" ` // 已賣出數量(相反,減到零就不能在賣)
UpdatedAt int64 `json:"updated_at"` // 更新時間
CreatedAt int64 `json:"created_at"` // 創建時間
}
type UpdateProductItems struct {
Name *string // 名稱
Description *string // 描述
ShortDescription *string // 封面簡短描述
IsUnLimit *bool // 是否沒有數量上限
IsFree *bool // 是否為免費品項(贈品) -> 開啟就是自訂金額
Stock *uint64 // 庫存總數
Price *string // 價格
SKU *string // 型號:對應顯示 Item 的 FK
TimeSeries *product.TimeSeries // 時段種類
Media []Media // 專案動態內容(圖片或者影片)
Freight []CustomFields // 運費
CustomFields []CustomFields // 自定義屬性
}
type QueryProductItemParam struct {
PageSize int64
PageIndex int64
ReferenceID *string // 對應的專案 ID
IsUnLimit *bool // 是否沒有數量上限
IsFree *bool // 是否為免費品項(贈品) -> 開啟就是自訂金額
Status *product.ItemStatus // 商品狀態
}

View File

@ -0,0 +1,41 @@
package usecase
import (
"context"
"code.30cm.net/digimon/app-cloudep-product-service/pkg/domain/entity"
"code.30cm.net/digimon/app-cloudep-product-service/pkg/domain/product"
)
type ProductBaseTags interface {
// Create 新增一筆 Tag 資料
Create(ctx context.Context, tag *entity.Tags) error
// GetByID 根據 tag 的內部 ID 取得資料
GetByID(ctx context.Context, id string) (*entity.Tags, error)
// Update 更新現有的 Tag 資料
Update(ctx context.Context, id string, tag TagModifyParams) error
// Delete 刪除指定 ID 的 Tag 資料
Delete(ctx context.Context, id string) error
// 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 時的參數結構
type TagQueryParams struct {
Types *product.ItemType // 過濾 Tag 類型
Name *string // 過濾名稱(部分比對)
ShowType *product.ShowType // 過濾顯示筐
// 可根據需求增加其他查詢條件,如分頁、排序等
PageSize int64
PageIndex int64
}
type TagModifyParams struct {
Types *product.ItemType // 過濾 Tag 類型
Name *string // 過濾名稱(部分比對)
ShowType *product.ShowType // 過濾顯示筐
Cover *string `bson:"cover,omitempty"` // 封面圖片
}

View File

@ -0,0 +1,133 @@
// Code generated by MockGen. DO NOT EDIT.
// Source: ./pkg/domain/repository/category.go
//
// Generated by this command:
//
// mockgen -source=./pkg/domain/repository/category.go -destination=./pkg/mock/repository/category.go -package=mock
//
// Package mock is a generated GoMock package.
package mock
import (
context "context"
reflect "reflect"
entity "code.30cm.net/digimon/app-cloudep-product-service/pkg/domain/entity"
repository "code.30cm.net/digimon/app-cloudep-product-service/pkg/domain/repository"
mongo "go.mongodb.org/mongo-driver/mongo"
gomock "go.uber.org/mock/gomock"
)
// MockCategoryRepository is a mock of CategoryRepository interface.
type MockCategoryRepository struct {
ctrl *gomock.Controller
recorder *MockCategoryRepositoryMockRecorder
isgomock struct{}
}
// MockCategoryRepositoryMockRecorder is the mock recorder for MockCategoryRepository.
type MockCategoryRepositoryMockRecorder struct {
mock *MockCategoryRepository
}
// NewMockCategoryRepository creates a new mock instance.
func NewMockCategoryRepository(ctrl *gomock.Controller) *MockCategoryRepository {
mock := &MockCategoryRepository{ctrl: ctrl}
mock.recorder = &MockCategoryRepositoryMockRecorder{mock}
return mock
}
// EXPECT returns an object that allows the caller to indicate expected use.
func (m *MockCategoryRepository) EXPECT() *MockCategoryRepositoryMockRecorder {
return m.recorder
}
// Delete mocks base method.
func (m *MockCategoryRepository) Delete(ctx context.Context, id string) (int64, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Delete", ctx, id)
ret0, _ := ret[0].(int64)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// Delete indicates an expected call of Delete.
func (mr *MockCategoryRepositoryMockRecorder) Delete(ctx, id any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Delete", reflect.TypeOf((*MockCategoryRepository)(nil).Delete), ctx, id)
}
// FindOneByID mocks base method.
func (m *MockCategoryRepository) FindOneByID(ctx context.Context, id string) (*entity.Category, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "FindOneByID", ctx, id)
ret0, _ := ret[0].(*entity.Category)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// FindOneByID indicates an expected call of FindOneByID.
func (mr *MockCategoryRepositoryMockRecorder) FindOneByID(ctx, id any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "FindOneByID", reflect.TypeOf((*MockCategoryRepository)(nil).FindOneByID), ctx, id)
}
// Insert mocks base method.
func (m *MockCategoryRepository) Insert(ctx context.Context, data *entity.Category) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Insert", ctx, data)
ret0, _ := ret[0].(error)
return ret0
}
// Insert indicates an expected call of Insert.
func (mr *MockCategoryRepositoryMockRecorder) Insert(ctx, data any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Insert", reflect.TypeOf((*MockCategoryRepository)(nil).Insert), ctx, data)
}
// IsCategoryExists mocks base method.
func (m *MockCategoryRepository) IsCategoryExists(ctx context.Context, ids []string) []string {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "IsCategoryExists", ctx, ids)
ret0, _ := ret[0].([]string)
return ret0
}
// IsCategoryExists indicates an expected call of IsCategoryExists.
func (mr *MockCategoryRepositoryMockRecorder) IsCategoryExists(ctx, ids any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "IsCategoryExists", reflect.TypeOf((*MockCategoryRepository)(nil).IsCategoryExists), ctx, ids)
}
// ListCategory mocks base method.
func (m *MockCategoryRepository) ListCategory(ctx context.Context, params *repository.CategoryQueryParams) ([]*entity.Category, int64, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "ListCategory", ctx, params)
ret0, _ := ret[0].([]*entity.Category)
ret1, _ := ret[1].(int64)
ret2, _ := ret[2].(error)
return ret0, ret1, ret2
}
// ListCategory indicates an expected call of ListCategory.
func (mr *MockCategoryRepositoryMockRecorder) ListCategory(ctx, params any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListCategory", reflect.TypeOf((*MockCategoryRepository)(nil).ListCategory), ctx, params)
}
// Update mocks base method.
func (m *MockCategoryRepository) Update(ctx context.Context, id string, data *entity.Category) (*mongo.UpdateResult, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Update", ctx, id, data)
ret0, _ := ret[0].(*mongo.UpdateResult)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// Update indicates an expected call of Update.
func (mr *MockCategoryRepositoryMockRecorder) Update(ctx, id, data any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Update", reflect.TypeOf((*MockCategoryRepository)(nil).Update), ctx, id, data)
}

131
pkg/mock/repository/kyc.go Normal file
View File

@ -0,0 +1,131 @@
// Code generated by MockGen. DO NOT EDIT.
// Source: ./pkg/domain/repository/kyc.go
//
// Generated by this command:
//
// mockgen -source=./pkg/domain/repository/kyc.go -destination=./pkg/mock/repository/kyc.go -package=mock
//
// Package mock is a generated GoMock package.
package mock
import (
context "context"
reflect "reflect"
entity "code.30cm.net/digimon/app-cloudep-product-service/pkg/domain/entity"
repository "code.30cm.net/digimon/app-cloudep-product-service/pkg/domain/repository"
gomock "go.uber.org/mock/gomock"
)
// MockKYCRepository is a mock of KYCRepository interface.
type MockKYCRepository struct {
ctrl *gomock.Controller
recorder *MockKYCRepositoryMockRecorder
isgomock struct{}
}
// MockKYCRepositoryMockRecorder is the mock recorder for MockKYCRepository.
type MockKYCRepositoryMockRecorder struct {
mock *MockKYCRepository
}
// NewMockKYCRepository creates a new mock instance.
func NewMockKYCRepository(ctrl *gomock.Controller) *MockKYCRepository {
mock := &MockKYCRepository{ctrl: ctrl}
mock.recorder = &MockKYCRepositoryMockRecorder{mock}
return mock
}
// EXPECT returns an object that allows the caller to indicate expected use.
func (m *MockKYCRepository) EXPECT() *MockKYCRepositoryMockRecorder {
return m.recorder
}
// Create mocks base method.
func (m *MockKYCRepository) Create(ctx context.Context, kyc *entity.KYC) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Create", ctx, kyc)
ret0, _ := ret[0].(error)
return ret0
}
// Create indicates an expected call of Create.
func (mr *MockKYCRepositoryMockRecorder) Create(ctx, kyc any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Create", reflect.TypeOf((*MockKYCRepository)(nil).Create), ctx, kyc)
}
// FindByID mocks base method.
func (m *MockKYCRepository) FindByID(ctx context.Context, id string) (*entity.KYC, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "FindByID", ctx, id)
ret0, _ := ret[0].(*entity.KYC)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// FindByID indicates an expected call of FindByID.
func (mr *MockKYCRepositoryMockRecorder) FindByID(ctx, id any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "FindByID", reflect.TypeOf((*MockKYCRepository)(nil).FindByID), ctx, id)
}
// FindLatestByUID mocks base method.
func (m *MockKYCRepository) FindLatestByUID(ctx context.Context, uid string) (*entity.KYC, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "FindLatestByUID", ctx, uid)
ret0, _ := ret[0].(*entity.KYC)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// FindLatestByUID indicates an expected call of FindLatestByUID.
func (mr *MockKYCRepositoryMockRecorder) FindLatestByUID(ctx, uid any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "FindLatestByUID", reflect.TypeOf((*MockKYCRepository)(nil).FindLatestByUID), ctx, uid)
}
// List mocks base method.
func (m *MockKYCRepository) List(ctx context.Context, params repository.KYCQueryParams) ([]*entity.KYC, int64, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "List", ctx, params)
ret0, _ := ret[0].([]*entity.KYC)
ret1, _ := ret[1].(int64)
ret2, _ := ret[2].(error)
return ret0, ret1, ret2
}
// List indicates an expected call of List.
func (mr *MockKYCRepositoryMockRecorder) List(ctx, params any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "List", reflect.TypeOf((*MockKYCRepository)(nil).List), ctx, params)
}
// UpdateKYCInfo mocks base method.
func (m *MockKYCRepository) UpdateKYCInfo(ctx context.Context, id string, update *repository.KYCUpdateParams) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "UpdateKYCInfo", ctx, id, update)
ret0, _ := ret[0].(error)
return ret0
}
// UpdateKYCInfo indicates an expected call of UpdateKYCInfo.
func (mr *MockKYCRepositoryMockRecorder) UpdateKYCInfo(ctx, id, update any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateKYCInfo", reflect.TypeOf((*MockKYCRepository)(nil).UpdateKYCInfo), ctx, id, update)
}
// UpdateStatus mocks base method.
func (m *MockKYCRepository) UpdateStatus(ctx context.Context, id, status, reason string) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "UpdateStatus", ctx, id, status, reason)
ret0, _ := ret[0].(error)
return ret0
}
// UpdateStatus indicates an expected call of UpdateStatus.
func (mr *MockKYCRepositoryMockRecorder) UpdateStatus(ctx, id, status, reason any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateStatus", reflect.TypeOf((*MockKYCRepository)(nil).UpdateStatus), ctx, id, status, reason)
}

View File

@ -0,0 +1,207 @@
// Code generated by MockGen. DO NOT EDIT.
// Source: ./pkg/domain/repository/product.go
//
// Generated by this command:
//
// mockgen -source=./pkg/domain/repository/product.go -destination=./pkg/mock/repository/product.go -package=mock
//
// Package mock is a generated GoMock package.
package mock
import (
context "context"
reflect "reflect"
entity "code.30cm.net/digimon/app-cloudep-product-service/pkg/domain/entity"
repository "code.30cm.net/digimon/app-cloudep-product-service/pkg/domain/repository"
mongo "go.mongodb.org/mongo-driver/mongo"
options "go.mongodb.org/mongo-driver/mongo/options"
gomock "go.uber.org/mock/gomock"
)
// MockProductRepository is a mock of ProductRepository interface.
type MockProductRepository struct {
ctrl *gomock.Controller
recorder *MockProductRepositoryMockRecorder
isgomock struct{}
}
// MockProductRepositoryMockRecorder is the mock recorder for MockProductRepository.
type MockProductRepositoryMockRecorder struct {
mock *MockProductRepository
}
// NewMockProductRepository creates a new mock instance.
func NewMockProductRepository(ctrl *gomock.Controller) *MockProductRepository {
mock := &MockProductRepository{ctrl: ctrl}
mock.recorder = &MockProductRepositoryMockRecorder{mock}
return mock
}
// EXPECT returns an object that allows the caller to indicate expected use.
func (m *MockProductRepository) EXPECT() *MockProductRepositoryMockRecorder {
return m.recorder
}
// Delete mocks base method.
func (m *MockProductRepository) Delete(ctx context.Context, id string) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Delete", ctx, id)
ret0, _ := ret[0].(error)
return ret0
}
// Delete indicates an expected call of Delete.
func (mr *MockProductRepositoryMockRecorder) Delete(ctx, id any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Delete", reflect.TypeOf((*MockProductRepository)(nil).Delete), ctx, id)
}
// FindOneByID mocks base method.
func (m *MockProductRepository) FindOneByID(ctx context.Context, id string) (*entity.Product, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "FindOneByID", ctx, id)
ret0, _ := ret[0].(*entity.Product)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// FindOneByID indicates an expected call of FindOneByID.
func (mr *MockProductRepositoryMockRecorder) FindOneByID(ctx, id any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "FindOneByID", reflect.TypeOf((*MockProductRepository)(nil).FindOneByID), ctx, id)
}
// FindOneBySlug mocks base method.
func (m *MockProductRepository) FindOneBySlug(ctx context.Context, slug string) (*entity.Product, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "FindOneBySlug", ctx, slug)
ret0, _ := ret[0].(*entity.Product)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// FindOneBySlug indicates an expected call of FindOneBySlug.
func (mr *MockProductRepositoryMockRecorder) FindOneBySlug(ctx, slug any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "FindOneBySlug", reflect.TypeOf((*MockProductRepository)(nil).FindOneBySlug), ctx, slug)
}
// Index20250317001UP mocks base method.
func (m *MockProductRepository) Index20250317001UP(ctx context.Context) (*mongo.Cursor, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Index20250317001UP", ctx)
ret0, _ := ret[0].(*mongo.Cursor)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// Index20250317001UP indicates an expected call of Index20250317001UP.
func (mr *MockProductRepositoryMockRecorder) Index20250317001UP(ctx any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Index20250317001UP", reflect.TypeOf((*MockProductRepository)(nil).Index20250317001UP), ctx)
}
// Insert mocks base method.
func (m *MockProductRepository) Insert(ctx context.Context, data *entity.Product) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Insert", ctx, data)
ret0, _ := ret[0].(error)
return ret0
}
// Insert indicates an expected call of Insert.
func (mr *MockProductRepositoryMockRecorder) Insert(ctx, data any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Insert", reflect.TypeOf((*MockProductRepository)(nil).Insert), ctx, data)
}
// ListProduct mocks base method.
func (m *MockProductRepository) ListProduct(ctx context.Context, params *repository.ProductQueryParams) ([]*entity.Product, int64, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "ListProduct", ctx, params)
ret0, _ := ret[0].([]*entity.Product)
ret1, _ := ret[1].(int64)
ret2, _ := ret[2].(error)
return ret0, ret1, ret2
}
// ListProduct indicates an expected call of ListProduct.
func (mr *MockProductRepositoryMockRecorder) ListProduct(ctx, params any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListProduct", reflect.TypeOf((*MockProductRepository)(nil).ListProduct), ctx, params)
}
// Transaction mocks base method.
func (m *MockProductRepository) Transaction(ctx context.Context, fn func(mongo.SessionContext) (any, error), opts ...*options.TransactionOptions) error {
m.ctrl.T.Helper()
varargs := []any{ctx, fn}
for _, a := range opts {
varargs = append(varargs, a)
}
ret := m.ctrl.Call(m, "Transaction", varargs...)
ret0, _ := ret[0].(error)
return ret0
}
// Transaction indicates an expected call of Transaction.
func (mr *MockProductRepositoryMockRecorder) Transaction(ctx, fn any, opts ...any) *gomock.Call {
mr.mock.ctrl.T.Helper()
varargs := append([]any{ctx, fn}, opts...)
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Transaction", reflect.TypeOf((*MockProductRepository)(nil).Transaction), varargs...)
}
// Update mocks base method.
func (m *MockProductRepository) Update(ctx context.Context, productID string, data *repository.ProductUpdateParams) (*mongo.UpdateResult, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Update", ctx, productID, data)
ret0, _ := ret[0].(*mongo.UpdateResult)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// Update indicates an expected call of Update.
func (mr *MockProductRepositoryMockRecorder) Update(ctx, productID, data any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Update", reflect.TypeOf((*MockProductRepository)(nil).Update), ctx, productID, data)
}
// MockProductIndex is a mock of ProductIndex interface.
type MockProductIndex struct {
ctrl *gomock.Controller
recorder *MockProductIndexMockRecorder
isgomock struct{}
}
// MockProductIndexMockRecorder is the mock recorder for MockProductIndex.
type MockProductIndexMockRecorder struct {
mock *MockProductIndex
}
// NewMockProductIndex creates a new mock instance.
func NewMockProductIndex(ctrl *gomock.Controller) *MockProductIndex {
mock := &MockProductIndex{ctrl: ctrl}
mock.recorder = &MockProductIndexMockRecorder{mock}
return mock
}
// EXPECT returns an object that allows the caller to indicate expected use.
func (m *MockProductIndex) EXPECT() *MockProductIndexMockRecorder {
return m.recorder
}
// Index20250317001UP mocks base method.
func (m *MockProductIndex) Index20250317001UP(ctx context.Context) (*mongo.Cursor, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Index20250317001UP", ctx)
ret0, _ := ret[0].(*mongo.Cursor)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// Index20250317001UP indicates an expected call of Index20250317001UP.
func (mr *MockProductIndexMockRecorder) Index20250317001UP(ctx any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Index20250317001UP", reflect.TypeOf((*MockProductIndex)(nil).Index20250317001UP), ctx)
}

View File

@ -0,0 +1,435 @@
// Code generated by MockGen. DO NOT EDIT.
// Source: ./pkg/domain/repository/product_item.go
//
// Generated by this command:
//
// mockgen -source=./pkg/domain/repository/product_item.go -destination=./pkg/mock/repository/product_item.go -package=mock
//
// Package mock is a generated GoMock package.
package mock
import (
context "context"
reflect "reflect"
entity "code.30cm.net/digimon/app-cloudep-product-service/pkg/domain/entity"
product "code.30cm.net/digimon/app-cloudep-product-service/pkg/domain/product"
repository "code.30cm.net/digimon/app-cloudep-product-service/pkg/domain/repository"
mongo "go.mongodb.org/mongo-driver/mongo"
gomock "go.uber.org/mock/gomock"
)
// MockProductItemRepository is a mock of ProductItemRepository interface.
type MockProductItemRepository struct {
ctrl *gomock.Controller
recorder *MockProductItemRepositoryMockRecorder
isgomock struct{}
}
// MockProductItemRepositoryMockRecorder is the mock recorder for MockProductItemRepository.
type MockProductItemRepositoryMockRecorder struct {
mock *MockProductItemRepository
}
// NewMockProductItemRepository creates a new mock instance.
func NewMockProductItemRepository(ctrl *gomock.Controller) *MockProductItemRepository {
mock := &MockProductItemRepository{ctrl: ctrl}
mock.recorder = &MockProductItemRepositoryMockRecorder{mock}
return mock
}
// EXPECT returns an object that allows the caller to indicate expected use.
func (m *MockProductItemRepository) EXPECT() *MockProductItemRepositoryMockRecorder {
return m.recorder
}
// DecSalesCount mocks base method.
func (m *MockProductItemRepository) DecSalesCount(ctx context.Context, id string, count int64) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "DecSalesCount", ctx, id, count)
ret0, _ := ret[0].(error)
return ret0
}
// DecSalesCount indicates an expected call of DecSalesCount.
func (mr *MockProductItemRepositoryMockRecorder) DecSalesCount(ctx, id, count any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DecSalesCount", reflect.TypeOf((*MockProductItemRepository)(nil).DecSalesCount), ctx, id, count)
}
// Delete mocks base method.
func (m *MockProductItemRepository) Delete(ctx context.Context, ids []string) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Delete", ctx, ids)
ret0, _ := ret[0].(error)
return ret0
}
// Delete indicates an expected call of Delete.
func (mr *MockProductItemRepositoryMockRecorder) Delete(ctx, ids any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Delete", reflect.TypeOf((*MockProductItemRepository)(nil).Delete), ctx, ids)
}
// DeleteByReferenceID mocks base method.
func (m *MockProductItemRepository) DeleteByReferenceID(ctx context.Context, id string) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "DeleteByReferenceID", ctx, id)
ret0, _ := ret[0].(error)
return ret0
}
// DeleteByReferenceID indicates an expected call of DeleteByReferenceID.
func (mr *MockProductItemRepositoryMockRecorder) DeleteByReferenceID(ctx, id any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteByReferenceID", reflect.TypeOf((*MockProductItemRepository)(nil).DeleteByReferenceID), ctx, id)
}
// FindByID mocks base method.
func (m *MockProductItemRepository) FindByID(ctx context.Context, id string) (*entity.ProductItems, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "FindByID", ctx, id)
ret0, _ := ret[0].(*entity.ProductItems)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// FindByID indicates an expected call of FindByID.
func (mr *MockProductItemRepositoryMockRecorder) FindByID(ctx, id any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "FindByID", reflect.TypeOf((*MockProductItemRepository)(nil).FindByID), ctx, id)
}
// GetSalesCount mocks base method.
func (m *MockProductItemRepository) GetSalesCount(ctx context.Context, ids []string) ([]repository.ProductItemSalesCount, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetSalesCount", ctx, ids)
ret0, _ := ret[0].([]repository.ProductItemSalesCount)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// GetSalesCount indicates an expected call of GetSalesCount.
func (mr *MockProductItemRepositoryMockRecorder) GetSalesCount(ctx, ids any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetSalesCount", reflect.TypeOf((*MockProductItemRepository)(nil).GetSalesCount), ctx, ids)
}
// IncSalesCount mocks base method.
func (m *MockProductItemRepository) IncSalesCount(ctx context.Context, id string, count int64) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "IncSalesCount", ctx, id, count)
ret0, _ := ret[0].(error)
return ret0
}
// IncSalesCount indicates an expected call of IncSalesCount.
func (mr *MockProductItemRepositoryMockRecorder) IncSalesCount(ctx, id, count any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "IncSalesCount", reflect.TypeOf((*MockProductItemRepository)(nil).IncSalesCount), ctx, id, count)
}
// Index20250317001UP mocks base method.
func (m *MockProductItemRepository) Index20250317001UP(ctx context.Context) (*mongo.Cursor, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Index20250317001UP", ctx)
ret0, _ := ret[0].(*mongo.Cursor)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// Index20250317001UP indicates an expected call of Index20250317001UP.
func (mr *MockProductItemRepositoryMockRecorder) Index20250317001UP(ctx any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Index20250317001UP", reflect.TypeOf((*MockProductItemRepository)(nil).Index20250317001UP), ctx)
}
// Insert mocks base method.
func (m *MockProductItemRepository) Insert(ctx context.Context, item []entity.ProductItems) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Insert", ctx, item)
ret0, _ := ret[0].(error)
return ret0
}
// Insert indicates an expected call of Insert.
func (mr *MockProductItemRepositoryMockRecorder) Insert(ctx, item any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Insert", reflect.TypeOf((*MockProductItemRepository)(nil).Insert), ctx, item)
}
// ListProductItem mocks base method.
func (m *MockProductItemRepository) ListProductItem(ctx context.Context, param repository.ProductItemQueryParams) ([]entity.ProductItems, int64, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "ListProductItem", ctx, param)
ret0, _ := ret[0].([]entity.ProductItems)
ret1, _ := ret[1].(int64)
ret2, _ := ret[2].(error)
return ret0, ret1, ret2
}
// ListProductItem indicates an expected call of ListProductItem.
func (mr *MockProductItemRepositoryMockRecorder) ListProductItem(ctx, param any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListProductItem", reflect.TypeOf((*MockProductItemRepository)(nil).ListProductItem), ctx, param)
}
// Update mocks base method.
func (m *MockProductItemRepository) Update(ctx context.Context, id string, param *repository.ProductUpdateItem) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Update", ctx, id, param)
ret0, _ := ret[0].(error)
return ret0
}
// Update indicates an expected call of Update.
func (mr *MockProductItemRepositoryMockRecorder) Update(ctx, id, param any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Update", reflect.TypeOf((*MockProductItemRepository)(nil).Update), ctx, id, param)
}
// UpdateStatus mocks base method.
func (m *MockProductItemRepository) UpdateStatus(ctx context.Context, id string, status product.ItemStatus) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "UpdateStatus", ctx, id, status)
ret0, _ := ret[0].(error)
return ret0
}
// UpdateStatus indicates an expected call of UpdateStatus.
func (mr *MockProductItemRepositoryMockRecorder) UpdateStatus(ctx, id, status any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateStatus", reflect.TypeOf((*MockProductItemRepository)(nil).UpdateStatus), ctx, id, status)
}
// MockProductItemIndex is a mock of ProductItemIndex interface.
type MockProductItemIndex struct {
ctrl *gomock.Controller
recorder *MockProductItemIndexMockRecorder
isgomock struct{}
}
// MockProductItemIndexMockRecorder is the mock recorder for MockProductItemIndex.
type MockProductItemIndexMockRecorder struct {
mock *MockProductItemIndex
}
// NewMockProductItemIndex creates a new mock instance.
func NewMockProductItemIndex(ctrl *gomock.Controller) *MockProductItemIndex {
mock := &MockProductItemIndex{ctrl: ctrl}
mock.recorder = &MockProductItemIndexMockRecorder{mock}
return mock
}
// EXPECT returns an object that allows the caller to indicate expected use.
func (m *MockProductItemIndex) EXPECT() *MockProductItemIndexMockRecorder {
return m.recorder
}
// Index20250317001UP mocks base method.
func (m *MockProductItemIndex) Index20250317001UP(ctx context.Context) (*mongo.Cursor, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Index20250317001UP", ctx)
ret0, _ := ret[0].(*mongo.Cursor)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// Index20250317001UP indicates an expected call of Index20250317001UP.
func (mr *MockProductItemIndexMockRecorder) Index20250317001UP(ctx any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Index20250317001UP", reflect.TypeOf((*MockProductItemIndex)(nil).Index20250317001UP), ctx)
}
// MockProductItemBasic is a mock of ProductItemBasic interface.
type MockProductItemBasic struct {
ctrl *gomock.Controller
recorder *MockProductItemBasicMockRecorder
isgomock struct{}
}
// MockProductItemBasicMockRecorder is the mock recorder for MockProductItemBasic.
type MockProductItemBasicMockRecorder struct {
mock *MockProductItemBasic
}
// NewMockProductItemBasic creates a new mock instance.
func NewMockProductItemBasic(ctrl *gomock.Controller) *MockProductItemBasic {
mock := &MockProductItemBasic{ctrl: ctrl}
mock.recorder = &MockProductItemBasicMockRecorder{mock}
return mock
}
// EXPECT returns an object that allows the caller to indicate expected use.
func (m *MockProductItemBasic) EXPECT() *MockProductItemBasicMockRecorder {
return m.recorder
}
// Delete mocks base method.
func (m *MockProductItemBasic) Delete(ctx context.Context, ids []string) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Delete", ctx, ids)
ret0, _ := ret[0].(error)
return ret0
}
// Delete indicates an expected call of Delete.
func (mr *MockProductItemBasicMockRecorder) Delete(ctx, ids any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Delete", reflect.TypeOf((*MockProductItemBasic)(nil).Delete), ctx, ids)
}
// DeleteByReferenceID mocks base method.
func (m *MockProductItemBasic) DeleteByReferenceID(ctx context.Context, id string) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "DeleteByReferenceID", ctx, id)
ret0, _ := ret[0].(error)
return ret0
}
// DeleteByReferenceID indicates an expected call of DeleteByReferenceID.
func (mr *MockProductItemBasicMockRecorder) DeleteByReferenceID(ctx, id any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteByReferenceID", reflect.TypeOf((*MockProductItemBasic)(nil).DeleteByReferenceID), ctx, id)
}
// FindByID mocks base method.
func (m *MockProductItemBasic) FindByID(ctx context.Context, id string) (*entity.ProductItems, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "FindByID", ctx, id)
ret0, _ := ret[0].(*entity.ProductItems)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// FindByID indicates an expected call of FindByID.
func (mr *MockProductItemBasicMockRecorder) FindByID(ctx, id any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "FindByID", reflect.TypeOf((*MockProductItemBasic)(nil).FindByID), ctx, id)
}
// Insert mocks base method.
func (m *MockProductItemBasic) Insert(ctx context.Context, item []entity.ProductItems) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Insert", ctx, item)
ret0, _ := ret[0].(error)
return ret0
}
// Insert indicates an expected call of Insert.
func (mr *MockProductItemBasicMockRecorder) Insert(ctx, item any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Insert", reflect.TypeOf((*MockProductItemBasic)(nil).Insert), ctx, item)
}
// ListProductItem mocks base method.
func (m *MockProductItemBasic) ListProductItem(ctx context.Context, param repository.ProductItemQueryParams) ([]entity.ProductItems, int64, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "ListProductItem", ctx, param)
ret0, _ := ret[0].([]entity.ProductItems)
ret1, _ := ret[1].(int64)
ret2, _ := ret[2].(error)
return ret0, ret1, ret2
}
// ListProductItem indicates an expected call of ListProductItem.
func (mr *MockProductItemBasicMockRecorder) ListProductItem(ctx, param any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListProductItem", reflect.TypeOf((*MockProductItemBasic)(nil).ListProductItem), ctx, param)
}
// Update mocks base method.
func (m *MockProductItemBasic) Update(ctx context.Context, id string, param *repository.ProductUpdateItem) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Update", ctx, id, param)
ret0, _ := ret[0].(error)
return ret0
}
// Update indicates an expected call of Update.
func (mr *MockProductItemBasicMockRecorder) Update(ctx, id, param any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Update", reflect.TypeOf((*MockProductItemBasic)(nil).Update), ctx, id, param)
}
// UpdateStatus mocks base method.
func (m *MockProductItemBasic) UpdateStatus(ctx context.Context, id string, status product.ItemStatus) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "UpdateStatus", ctx, id, status)
ret0, _ := ret[0].(error)
return ret0
}
// UpdateStatus indicates an expected call of UpdateStatus.
func (mr *MockProductItemBasicMockRecorder) UpdateStatus(ctx, id, status any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateStatus", reflect.TypeOf((*MockProductItemBasic)(nil).UpdateStatus), ctx, id, status)
}
// MockProductItemStatistics is a mock of ProductItemStatistics interface.
type MockProductItemStatistics struct {
ctrl *gomock.Controller
recorder *MockProductItemStatisticsMockRecorder
isgomock struct{}
}
// MockProductItemStatisticsMockRecorder is the mock recorder for MockProductItemStatistics.
type MockProductItemStatisticsMockRecorder struct {
mock *MockProductItemStatistics
}
// NewMockProductItemStatistics creates a new mock instance.
func NewMockProductItemStatistics(ctrl *gomock.Controller) *MockProductItemStatistics {
mock := &MockProductItemStatistics{ctrl: ctrl}
mock.recorder = &MockProductItemStatisticsMockRecorder{mock}
return mock
}
// EXPECT returns an object that allows the caller to indicate expected use.
func (m *MockProductItemStatistics) EXPECT() *MockProductItemStatisticsMockRecorder {
return m.recorder
}
// DecSalesCount mocks base method.
func (m *MockProductItemStatistics) DecSalesCount(ctx context.Context, id string, count int64) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "DecSalesCount", ctx, id, count)
ret0, _ := ret[0].(error)
return ret0
}
// DecSalesCount indicates an expected call of DecSalesCount.
func (mr *MockProductItemStatisticsMockRecorder) DecSalesCount(ctx, id, count any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DecSalesCount", reflect.TypeOf((*MockProductItemStatistics)(nil).DecSalesCount), ctx, id, count)
}
// GetSalesCount mocks base method.
func (m *MockProductItemStatistics) GetSalesCount(ctx context.Context, ids []string) ([]repository.ProductItemSalesCount, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetSalesCount", ctx, ids)
ret0, _ := ret[0].([]repository.ProductItemSalesCount)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// GetSalesCount indicates an expected call of GetSalesCount.
func (mr *MockProductItemStatisticsMockRecorder) GetSalesCount(ctx, ids any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetSalesCount", reflect.TypeOf((*MockProductItemStatistics)(nil).GetSalesCount), ctx, ids)
}
// IncSalesCount mocks base method.
func (m *MockProductItemStatistics) IncSalesCount(ctx context.Context, id string, count int64) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "IncSalesCount", ctx, id, count)
ret0, _ := ret[0].(error)
return ret0
}
// IncSalesCount indicates an expected call of IncSalesCount.
func (mr *MockProductItemStatisticsMockRecorder) IncSalesCount(ctx, id, count any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "IncSalesCount", reflect.TypeOf((*MockProductItemStatistics)(nil).IncSalesCount), ctx, id, count)
}

View File

@ -0,0 +1,225 @@
// Code generated by MockGen. DO NOT EDIT.
// Source: ./pkg/domain/repository/product_statistics.go
//
// Generated by this command:
//
// mockgen -source=./pkg/domain/repository/product_statistics.go -destination=./pkg/mock/repository/product_statistics.go -package=mock
//
// Package mock is a generated GoMock package.
package mock
import (
context "context"
reflect "reflect"
entity "code.30cm.net/digimon/app-cloudep-product-service/pkg/domain/entity"
mongo "go.mongodb.org/mongo-driver/mongo"
gomock "go.uber.org/mock/gomock"
)
// MockProductStatisticsRepo is a mock of ProductStatisticsRepo interface.
type MockProductStatisticsRepo struct {
ctrl *gomock.Controller
recorder *MockProductStatisticsRepoMockRecorder
isgomock struct{}
}
// MockProductStatisticsRepoMockRecorder is the mock recorder for MockProductStatisticsRepo.
type MockProductStatisticsRepoMockRecorder struct {
mock *MockProductStatisticsRepo
}
// NewMockProductStatisticsRepo creates a new mock instance.
func NewMockProductStatisticsRepo(ctrl *gomock.Controller) *MockProductStatisticsRepo {
mock := &MockProductStatisticsRepo{ctrl: ctrl}
mock.recorder = &MockProductStatisticsRepoMockRecorder{mock}
return mock
}
// EXPECT returns an object that allows the caller to indicate expected use.
func (m *MockProductStatisticsRepo) EXPECT() *MockProductStatisticsRepoMockRecorder {
return m.recorder
}
// Create mocks base method.
func (m *MockProductStatisticsRepo) Create(ctx context.Context, stats *entity.ProductStatistics) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Create", ctx, stats)
ret0, _ := ret[0].(error)
return ret0
}
// Create indicates an expected call of Create.
func (mr *MockProductStatisticsRepoMockRecorder) Create(ctx, stats any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Create", reflect.TypeOf((*MockProductStatisticsRepo)(nil).Create), ctx, stats)
}
// DecFansCount mocks base method.
func (m *MockProductStatisticsRepo) DecFansCount(ctx context.Context, productID string, fansCount uint64) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "DecFansCount", ctx, productID, fansCount)
ret0, _ := ret[0].(error)
return ret0
}
// DecFansCount indicates an expected call of DecFansCount.
func (mr *MockProductStatisticsRepoMockRecorder) DecFansCount(ctx, productID, fansCount any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DecFansCount", reflect.TypeOf((*MockProductStatisticsRepo)(nil).DecFansCount), ctx, productID, fansCount)
}
// DecOrders mocks base method.
func (m *MockProductStatisticsRepo) DecOrders(ctx context.Context, productID string, count int64) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "DecOrders", ctx, productID, count)
ret0, _ := ret[0].(error)
return ret0
}
// DecOrders indicates an expected call of DecOrders.
func (mr *MockProductStatisticsRepoMockRecorder) DecOrders(ctx, productID, count any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DecOrders", reflect.TypeOf((*MockProductStatisticsRepo)(nil).DecOrders), ctx, productID, count)
}
// Delete mocks base method.
func (m *MockProductStatisticsRepo) Delete(ctx context.Context, id string) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Delete", ctx, id)
ret0, _ := ret[0].(error)
return ret0
}
// Delete indicates an expected call of Delete.
func (mr *MockProductStatisticsRepoMockRecorder) Delete(ctx, id any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Delete", reflect.TypeOf((*MockProductStatisticsRepo)(nil).Delete), ctx, id)
}
// GetByID mocks base method.
func (m *MockProductStatisticsRepo) GetByID(ctx context.Context, id string) (*entity.ProductStatistics, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetByID", ctx, id)
ret0, _ := ret[0].(*entity.ProductStatistics)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// GetByID indicates an expected call of GetByID.
func (mr *MockProductStatisticsRepoMockRecorder) GetByID(ctx, id any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetByID", reflect.TypeOf((*MockProductStatisticsRepo)(nil).GetByID), ctx, id)
}
// GetByProductID mocks base method.
func (m *MockProductStatisticsRepo) GetByProductID(ctx context.Context, productID string) (*entity.ProductStatistics, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetByProductID", ctx, productID)
ret0, _ := ret[0].(*entity.ProductStatistics)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// GetByProductID indicates an expected call of GetByProductID.
func (mr *MockProductStatisticsRepoMockRecorder) GetByProductID(ctx, productID any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetByProductID", reflect.TypeOf((*MockProductStatisticsRepo)(nil).GetByProductID), ctx, productID)
}
// IncFansCount mocks base method.
func (m *MockProductStatisticsRepo) IncFansCount(ctx context.Context, productID string, fansCount uint64) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "IncFansCount", ctx, productID, fansCount)
ret0, _ := ret[0].(error)
return ret0
}
// IncFansCount indicates an expected call of IncFansCount.
func (mr *MockProductStatisticsRepoMockRecorder) IncFansCount(ctx, productID, fansCount any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "IncFansCount", reflect.TypeOf((*MockProductStatisticsRepo)(nil).IncFansCount), ctx, productID, fansCount)
}
// IncOrders mocks base method.
func (m *MockProductStatisticsRepo) IncOrders(ctx context.Context, productID string, count int64) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "IncOrders", ctx, productID, count)
ret0, _ := ret[0].(error)
return ret0
}
// IncOrders indicates an expected call of IncOrders.
func (mr *MockProductStatisticsRepoMockRecorder) IncOrders(ctx, productID, count any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "IncOrders", reflect.TypeOf((*MockProductStatisticsRepo)(nil).IncOrders), ctx, productID, count)
}
// Index20250317001UP mocks base method.
func (m *MockProductStatisticsRepo) Index20250317001UP(ctx context.Context) (*mongo.Cursor, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Index20250317001UP", ctx)
ret0, _ := ret[0].(*mongo.Cursor)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// Index20250317001UP indicates an expected call of Index20250317001UP.
func (mr *MockProductStatisticsRepoMockRecorder) Index20250317001UP(ctx any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Index20250317001UP", reflect.TypeOf((*MockProductStatisticsRepo)(nil).Index20250317001UP), ctx)
}
// UpdateAverageRating mocks base method.
func (m *MockProductStatisticsRepo) UpdateAverageRating(ctx context.Context, productID string, averageRating float64) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "UpdateAverageRating", ctx, productID, averageRating)
ret0, _ := ret[0].(error)
return ret0
}
// UpdateAverageRating indicates an expected call of UpdateAverageRating.
func (mr *MockProductStatisticsRepoMockRecorder) UpdateAverageRating(ctx, productID, averageRating any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateAverageRating", reflect.TypeOf((*MockProductStatisticsRepo)(nil).UpdateAverageRating), ctx, productID, averageRating)
}
// MockProductStatisticsIndex is a mock of ProductStatisticsIndex interface.
type MockProductStatisticsIndex struct {
ctrl *gomock.Controller
recorder *MockProductStatisticsIndexMockRecorder
isgomock struct{}
}
// MockProductStatisticsIndexMockRecorder is the mock recorder for MockProductStatisticsIndex.
type MockProductStatisticsIndexMockRecorder struct {
mock *MockProductStatisticsIndex
}
// NewMockProductStatisticsIndex creates a new mock instance.
func NewMockProductStatisticsIndex(ctrl *gomock.Controller) *MockProductStatisticsIndex {
mock := &MockProductStatisticsIndex{ctrl: ctrl}
mock.recorder = &MockProductStatisticsIndexMockRecorder{mock}
return mock
}
// EXPECT returns an object that allows the caller to indicate expected use.
func (m *MockProductStatisticsIndex) EXPECT() *MockProductStatisticsIndexMockRecorder {
return m.recorder
}
// Index20250317001UP mocks base method.
func (m *MockProductStatisticsIndex) Index20250317001UP(ctx context.Context) (*mongo.Cursor, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Index20250317001UP", ctx)
ret0, _ := ret[0].(*mongo.Cursor)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// Index20250317001UP indicates an expected call of Index20250317001UP.
func (mr *MockProductStatisticsIndexMockRecorder) Index20250317001UP(ctx any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Index20250317001UP", reflect.TypeOf((*MockProductStatisticsIndex)(nil).Index20250317001UP), ctx)
}

498
pkg/mock/repository/tags.go Normal file
View File

@ -0,0 +1,498 @@
// Code generated by MockGen. DO NOT EDIT.
// Source: ./pkg/domain/repository/tags.go
//
// Generated by this command:
//
// mockgen -source=./pkg/domain/repository/tags.go -destination=./pkg/mock/repository/tags.go -package=mock
//
// Package mock is a generated GoMock package.
package mock
import (
context "context"
reflect "reflect"
entity "code.30cm.net/digimon/app-cloudep-product-service/pkg/domain/entity"
repository "code.30cm.net/digimon/app-cloudep-product-service/pkg/domain/repository"
mongo "go.mongodb.org/mongo-driver/mongo"
gomock "go.uber.org/mock/gomock"
)
// MockTagRepo is a mock of TagRepo interface.
type MockTagRepo struct {
ctrl *gomock.Controller
recorder *MockTagRepoMockRecorder
isgomock struct{}
}
// MockTagRepoMockRecorder is the mock recorder for MockTagRepo.
type MockTagRepoMockRecorder struct {
mock *MockTagRepo
}
// NewMockTagRepo creates a new mock instance.
func NewMockTagRepo(ctrl *gomock.Controller) *MockTagRepo {
mock := &MockTagRepo{ctrl: ctrl}
mock.recorder = &MockTagRepoMockRecorder{mock}
return mock
}
// EXPECT returns an object that allows the caller to indicate expected use.
func (m *MockTagRepo) EXPECT() *MockTagRepoMockRecorder {
return m.recorder
}
// BindTags mocks base method.
func (m *MockTagRepo) BindTags(ctx context.Context, binding []*entity.TagsBindingTable) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "BindTags", ctx, binding)
ret0, _ := ret[0].(error)
return ret0
}
// BindTags indicates an expected call of BindTags.
func (mr *MockTagRepoMockRecorder) BindTags(ctx, binding any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "BindTags", reflect.TypeOf((*MockTagRepo)(nil).BindTags), ctx, binding)
}
// Create mocks base method.
func (m *MockTagRepo) Create(ctx context.Context, tag *entity.Tags) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Create", ctx, tag)
ret0, _ := ret[0].(error)
return ret0
}
// Create indicates an expected call of Create.
func (mr *MockTagRepoMockRecorder) Create(ctx, tag any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Create", reflect.TypeOf((*MockTagRepo)(nil).Create), ctx, tag)
}
// Delete mocks base method.
func (m *MockTagRepo) Delete(ctx context.Context, id string) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Delete", ctx, id)
ret0, _ := ret[0].(error)
return ret0
}
// Delete indicates an expected call of Delete.
func (mr *MockTagRepoMockRecorder) Delete(ctx, id any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Delete", reflect.TypeOf((*MockTagRepo)(nil).Delete), ctx, id)
}
// GetBindingsByReference mocks base method.
func (m *MockTagRepo) GetBindingsByReference(ctx context.Context, referenceID string) ([]*entity.TagsBindingTable, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetBindingsByReference", ctx, referenceID)
ret0, _ := ret[0].([]*entity.TagsBindingTable)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// GetBindingsByReference indicates an expected call of GetBindingsByReference.
func (mr *MockTagRepoMockRecorder) GetBindingsByReference(ctx, referenceID any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetBindingsByReference", reflect.TypeOf((*MockTagRepo)(nil).GetBindingsByReference), ctx, referenceID)
}
// GetByID mocks base method.
func (m *MockTagRepo) GetByID(ctx context.Context, id string) (*entity.Tags, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetByID", ctx, id)
ret0, _ := ret[0].(*entity.Tags)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// GetByID indicates an expected call of GetByID.
func (mr *MockTagRepoMockRecorder) GetByID(ctx, id any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetByID", reflect.TypeOf((*MockTagRepo)(nil).GetByID), ctx, id)
}
// GetByIDs mocks base method.
func (m *MockTagRepo) GetByIDs(ctx context.Context, ids []string) ([]*entity.Tags, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetByIDs", ctx, ids)
ret0, _ := ret[0].([]*entity.Tags)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// GetByIDs indicates an expected call of GetByIDs.
func (mr *MockTagRepoMockRecorder) GetByIDs(ctx, ids any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetByIDs", reflect.TypeOf((*MockTagRepo)(nil).GetByIDs), ctx, ids)
}
// IndexTags20250317001UP mocks base method.
func (m *MockTagRepo) IndexTags20250317001UP(ctx context.Context) (*mongo.Cursor, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "IndexTags20250317001UP", ctx)
ret0, _ := ret[0].(*mongo.Cursor)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// IndexTags20250317001UP indicates an expected call of IndexTags20250317001UP.
func (mr *MockTagRepoMockRecorder) IndexTags20250317001UP(ctx any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "IndexTags20250317001UP", reflect.TypeOf((*MockTagRepo)(nil).IndexTags20250317001UP), ctx)
}
// IndexTagsBinding20250317001UP mocks base method.
func (m *MockTagRepo) IndexTagsBinding20250317001UP(ctx context.Context) (*mongo.Cursor, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "IndexTagsBinding20250317001UP", ctx)
ret0, _ := ret[0].(*mongo.Cursor)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// IndexTagsBinding20250317001UP indicates an expected call of IndexTagsBinding20250317001UP.
func (mr *MockTagRepoMockRecorder) IndexTagsBinding20250317001UP(ctx any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "IndexTagsBinding20250317001UP", reflect.TypeOf((*MockTagRepo)(nil).IndexTagsBinding20250317001UP), ctx)
}
// List mocks base method.
func (m *MockTagRepo) List(ctx context.Context, params repository.TagQueryParams) ([]*entity.Tags, int64, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "List", ctx, params)
ret0, _ := ret[0].([]*entity.Tags)
ret1, _ := ret[1].(int64)
ret2, _ := ret[2].(error)
return ret0, ret1, ret2
}
// List indicates an expected call of List.
func (mr *MockTagRepoMockRecorder) List(ctx, params any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "List", reflect.TypeOf((*MockTagRepo)(nil).List), ctx, params)
}
// ListTagBinding mocks base method.
func (m *MockTagRepo) ListTagBinding(ctx context.Context, params repository.TagBindingQueryParams) ([]*entity.TagsBindingTable, int64, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "ListTagBinding", ctx, params)
ret0, _ := ret[0].([]*entity.TagsBindingTable)
ret1, _ := ret[1].(int64)
ret2, _ := ret[2].(error)
return ret0, ret1, ret2
}
// ListTagBinding indicates an expected call of ListTagBinding.
func (mr *MockTagRepoMockRecorder) ListTagBinding(ctx, params any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListTagBinding", reflect.TypeOf((*MockTagRepo)(nil).ListTagBinding), ctx, params)
}
// UnbindTag mocks base method.
func (m *MockTagRepo) UnbindTag(ctx context.Context, tagID, referenceID string) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "UnbindTag", ctx, tagID, referenceID)
ret0, _ := ret[0].(error)
return ret0
}
// UnbindTag indicates an expected call of UnbindTag.
func (mr *MockTagRepoMockRecorder) UnbindTag(ctx, tagID, referenceID any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UnbindTag", reflect.TypeOf((*MockTagRepo)(nil).UnbindTag), ctx, tagID, referenceID)
}
// UnbindTagByReferenceID mocks base method.
func (m *MockTagRepo) UnbindTagByReferenceID(ctx context.Context, referenceID string) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "UnbindTagByReferenceID", ctx, referenceID)
ret0, _ := ret[0].(error)
return ret0
}
// UnbindTagByReferenceID indicates an expected call of UnbindTagByReferenceID.
func (mr *MockTagRepoMockRecorder) UnbindTagByReferenceID(ctx, referenceID any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UnbindTagByReferenceID", reflect.TypeOf((*MockTagRepo)(nil).UnbindTagByReferenceID), ctx, referenceID)
}
// Update mocks base method.
func (m *MockTagRepo) Update(ctx context.Context, id string, tag repository.TagModifyParams) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Update", ctx, id, tag)
ret0, _ := ret[0].(error)
return ret0
}
// Update indicates an expected call of Update.
func (mr *MockTagRepoMockRecorder) Update(ctx, id, tag any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Update", reflect.TypeOf((*MockTagRepo)(nil).Update), ctx, id, tag)
}
// MockBase is a mock of Base interface.
type MockBase struct {
ctrl *gomock.Controller
recorder *MockBaseMockRecorder
isgomock struct{}
}
// MockBaseMockRecorder is the mock recorder for MockBase.
type MockBaseMockRecorder struct {
mock *MockBase
}
// NewMockBase creates a new mock instance.
func NewMockBase(ctrl *gomock.Controller) *MockBase {
mock := &MockBase{ctrl: ctrl}
mock.recorder = &MockBaseMockRecorder{mock}
return mock
}
// EXPECT returns an object that allows the caller to indicate expected use.
func (m *MockBase) EXPECT() *MockBaseMockRecorder {
return m.recorder
}
// Create mocks base method.
func (m *MockBase) Create(ctx context.Context, tag *entity.Tags) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Create", ctx, tag)
ret0, _ := ret[0].(error)
return ret0
}
// Create indicates an expected call of Create.
func (mr *MockBaseMockRecorder) Create(ctx, tag any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Create", reflect.TypeOf((*MockBase)(nil).Create), ctx, tag)
}
// Delete mocks base method.
func (m *MockBase) Delete(ctx context.Context, id string) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Delete", ctx, id)
ret0, _ := ret[0].(error)
return ret0
}
// Delete indicates an expected call of Delete.
func (mr *MockBaseMockRecorder) Delete(ctx, id any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Delete", reflect.TypeOf((*MockBase)(nil).Delete), ctx, id)
}
// GetByID mocks base method.
func (m *MockBase) GetByID(ctx context.Context, id string) (*entity.Tags, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetByID", ctx, id)
ret0, _ := ret[0].(*entity.Tags)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// GetByID indicates an expected call of GetByID.
func (mr *MockBaseMockRecorder) GetByID(ctx, id any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetByID", reflect.TypeOf((*MockBase)(nil).GetByID), ctx, id)
}
// GetByIDs mocks base method.
func (m *MockBase) GetByIDs(ctx context.Context, ids []string) ([]*entity.Tags, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetByIDs", ctx, ids)
ret0, _ := ret[0].([]*entity.Tags)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// GetByIDs indicates an expected call of GetByIDs.
func (mr *MockBaseMockRecorder) GetByIDs(ctx, ids any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetByIDs", reflect.TypeOf((*MockBase)(nil).GetByIDs), ctx, ids)
}
// List mocks base method.
func (m *MockBase) List(ctx context.Context, params repository.TagQueryParams) ([]*entity.Tags, int64, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "List", ctx, params)
ret0, _ := ret[0].([]*entity.Tags)
ret1, _ := ret[1].(int64)
ret2, _ := ret[2].(error)
return ret0, ret1, ret2
}
// List indicates an expected call of List.
func (mr *MockBaseMockRecorder) List(ctx, params any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "List", reflect.TypeOf((*MockBase)(nil).List), ctx, params)
}
// Update mocks base method.
func (m *MockBase) Update(ctx context.Context, id string, tag repository.TagModifyParams) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Update", ctx, id, tag)
ret0, _ := ret[0].(error)
return ret0
}
// Update indicates an expected call of Update.
func (mr *MockBaseMockRecorder) Update(ctx, id, tag any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Update", reflect.TypeOf((*MockBase)(nil).Update), ctx, id, tag)
}
// MockTagBindingRepo is a mock of TagBindingRepo interface.
type MockTagBindingRepo struct {
ctrl *gomock.Controller
recorder *MockTagBindingRepoMockRecorder
isgomock struct{}
}
// MockTagBindingRepoMockRecorder is the mock recorder for MockTagBindingRepo.
type MockTagBindingRepoMockRecorder struct {
mock *MockTagBindingRepo
}
// NewMockTagBindingRepo creates a new mock instance.
func NewMockTagBindingRepo(ctrl *gomock.Controller) *MockTagBindingRepo {
mock := &MockTagBindingRepo{ctrl: ctrl}
mock.recorder = &MockTagBindingRepoMockRecorder{mock}
return mock
}
// EXPECT returns an object that allows the caller to indicate expected use.
func (m *MockTagBindingRepo) EXPECT() *MockTagBindingRepoMockRecorder {
return m.recorder
}
// BindTags mocks base method.
func (m *MockTagBindingRepo) BindTags(ctx context.Context, binding []*entity.TagsBindingTable) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "BindTags", ctx, binding)
ret0, _ := ret[0].(error)
return ret0
}
// BindTags indicates an expected call of BindTags.
func (mr *MockTagBindingRepoMockRecorder) BindTags(ctx, binding any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "BindTags", reflect.TypeOf((*MockTagBindingRepo)(nil).BindTags), ctx, binding)
}
// GetBindingsByReference mocks base method.
func (m *MockTagBindingRepo) GetBindingsByReference(ctx context.Context, referenceID string) ([]*entity.TagsBindingTable, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetBindingsByReference", ctx, referenceID)
ret0, _ := ret[0].([]*entity.TagsBindingTable)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// GetBindingsByReference indicates an expected call of GetBindingsByReference.
func (mr *MockTagBindingRepoMockRecorder) GetBindingsByReference(ctx, referenceID any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetBindingsByReference", reflect.TypeOf((*MockTagBindingRepo)(nil).GetBindingsByReference), ctx, referenceID)
}
// ListTagBinding mocks base method.
func (m *MockTagBindingRepo) ListTagBinding(ctx context.Context, params repository.TagBindingQueryParams) ([]*entity.TagsBindingTable, int64, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "ListTagBinding", ctx, params)
ret0, _ := ret[0].([]*entity.TagsBindingTable)
ret1, _ := ret[1].(int64)
ret2, _ := ret[2].(error)
return ret0, ret1, ret2
}
// ListTagBinding indicates an expected call of ListTagBinding.
func (mr *MockTagBindingRepoMockRecorder) ListTagBinding(ctx, params any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListTagBinding", reflect.TypeOf((*MockTagBindingRepo)(nil).ListTagBinding), ctx, params)
}
// UnbindTag mocks base method.
func (m *MockTagBindingRepo) UnbindTag(ctx context.Context, tagID, referenceID string) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "UnbindTag", ctx, tagID, referenceID)
ret0, _ := ret[0].(error)
return ret0
}
// UnbindTag indicates an expected call of UnbindTag.
func (mr *MockTagBindingRepoMockRecorder) UnbindTag(ctx, tagID, referenceID any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UnbindTag", reflect.TypeOf((*MockTagBindingRepo)(nil).UnbindTag), ctx, tagID, referenceID)
}
// UnbindTagByReferenceID mocks base method.
func (m *MockTagBindingRepo) UnbindTagByReferenceID(ctx context.Context, referenceID string) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "UnbindTagByReferenceID", ctx, referenceID)
ret0, _ := ret[0].(error)
return ret0
}
// UnbindTagByReferenceID indicates an expected call of UnbindTagByReferenceID.
func (mr *MockTagBindingRepoMockRecorder) UnbindTagByReferenceID(ctx, referenceID any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UnbindTagByReferenceID", reflect.TypeOf((*MockTagBindingRepo)(nil).UnbindTagByReferenceID), ctx, referenceID)
}
// MockIndex is a mock of Index interface.
type MockIndex struct {
ctrl *gomock.Controller
recorder *MockIndexMockRecorder
isgomock struct{}
}
// MockIndexMockRecorder is the mock recorder for MockIndex.
type MockIndexMockRecorder struct {
mock *MockIndex
}
// NewMockIndex creates a new mock instance.
func NewMockIndex(ctrl *gomock.Controller) *MockIndex {
mock := &MockIndex{ctrl: ctrl}
mock.recorder = &MockIndexMockRecorder{mock}
return mock
}
// EXPECT returns an object that allows the caller to indicate expected use.
func (m *MockIndex) EXPECT() *MockIndexMockRecorder {
return m.recorder
}
// IndexTags20250317001UP mocks base method.
func (m *MockIndex) IndexTags20250317001UP(ctx context.Context) (*mongo.Cursor, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "IndexTags20250317001UP", ctx)
ret0, _ := ret[0].(*mongo.Cursor)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// IndexTags20250317001UP indicates an expected call of IndexTags20250317001UP.
func (mr *MockIndexMockRecorder) IndexTags20250317001UP(ctx any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "IndexTags20250317001UP", reflect.TypeOf((*MockIndex)(nil).IndexTags20250317001UP), ctx)
}
// IndexTagsBinding20250317001UP mocks base method.
func (m *MockIndex) IndexTagsBinding20250317001UP(ctx context.Context) (*mongo.Cursor, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "IndexTagsBinding20250317001UP", ctx)
ret0, _ := ret[0].(*mongo.Cursor)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// IndexTagsBinding20250317001UP indicates an expected call of IndexTagsBinding20250317001UP.
func (mr *MockIndexMockRecorder) IndexTagsBinding20250317001UP(ctx any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "IndexTagsBinding20250317001UP", reflect.TypeOf((*MockIndex)(nil).IndexTagsBinding20250317001UP), ctx)
}

192
pkg/repository/category.go Normal file
View File

@ -0,0 +1,192 @@
package repository
import (
"context"
"errors"
"code.30cm.net/digimon/app-cloudep-product-service/pkg/domain"
"code.30cm.net/digimon/app-cloudep-product-service/pkg/domain/entity"
"code.30cm.net/digimon/app-cloudep-product-service/pkg/domain/repository"
mgo "code.30cm.net/digimon/library-go/mongo"
"time"
"github.com/zeromicro/go-zero/core/stores/cache"
"github.com/zeromicro/go-zero/core/stores/mon"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/bson/primitive"
"go.mongodb.org/mongo-driver/mongo"
"go.mongodb.org/mongo-driver/mongo/options"
)
type CategoryRepositoryParam struct {
Conf *mgo.Conf
CacheConf cache.CacheConf
DBOpts []mon.Option
CacheOpts []cache.Option
}
type CategoryRepository struct {
DB mgo.DocumentDBWithCacheUseCase
}
func MustCategoryRepository(param CategoryRepositoryParam) repository.CategoryRepository {
e := entity.Category{}
documentDB, err := mgo.MustDocumentDBWithCache(
param.Conf,
e.CollectionName(),
param.CacheConf,
param.DBOpts,
param.CacheOpts,
)
if err != nil {
panic(err)
}
return &CategoryRepository{
DB: documentDB,
}
}
func (repo *CategoryRepository) IsCategoryExists(ctx context.Context, ids []string) []string {
// 將傳入的 string id 轉換為 ObjectID
objectIDs := make([]primitive.ObjectID, 0, len(ids))
for _, id := range ids {
objID, err := primitive.ObjectIDFromHex(id)
if err == nil { // 如果轉換失敗,忽略該 id
objectIDs = append(objectIDs, objID)
}
}
// 查詢存在的標籤
filter := bson.M{"_id": bson.M{"$in": objectIDs}}
// find 並沒有快取要快取要去其他地方做
opts := options.Find().SetProjection(bson.M{"_id": 1}) // 只返回 _id 欄位
// 執行查詢並獲取結果
var category []*entity.Category
err := repo.DB.GetClient().Find(ctx, &category, filter, opts)
if err != nil {
return []string{}
}
// 將存在的標籤ID轉換回 string並加入結果列表
existingIDs := make([]string, 0, len(category))
for _, item := range category {
existingIDs = append(existingIDs, item.ID.Hex())
}
return existingIDs
}
func (repo *CategoryRepository) Insert(ctx context.Context, data *entity.Category) error {
now := time.Now().UTC().UnixNano()
if data.ID.IsZero() {
data.ID = primitive.NewObjectID()
data.CreatedAt = now
data.UpdatedAt = now
}
_, err := repo.DB.GetClient().InsertOne(ctx, data)
return err
}
func (repo *CategoryRepository) FindOneByID(ctx context.Context, id string) (*entity.Category, error) {
oid, err := primitive.ObjectIDFromHex(id)
if err != nil {
return nil, ErrInvalidObjectID
}
var data entity.Category
rk := domain.GetCategoryRedisKey(id)
err = repo.DB.FindOne(ctx, rk, &data, bson.M{"_id": oid})
switch {
case err == nil:
return &data, nil
case errors.Is(err, mon.ErrNotFound):
return nil, ErrNotFound
default:
return nil, err
}
}
func (repo *CategoryRepository) Update(ctx context.Context, id string, data *entity.Category) (*mongo.UpdateResult, error) {
oid, err := primitive.ObjectIDFromHex(id)
if err != nil {
return nil, ErrInvalidObjectID
}
updateFields := bson.M{}
if data.Name != "" {
updateFields["name"] = data.Name
}
updateFields["updated_at"] = time.Now().UTC().UnixNano()
// 構建查找條件
filter := bson.M{"_id": oid}
update := bson.M{"$set": updateFields}
opt := options.Update().SetUpsert(false)
rk := domain.GetCategoryRedisKey(id)
res, err := repo.DB.UpdateOne(ctx, rk, filter, update, opt)
if err != nil {
return nil, err
}
// 檢查更新結果,若沒有匹配的文檔,則返回錯誤
if res.MatchedCount == 0 {
return nil, ErrNotFound // 自定義的錯誤表示未找到記錄
}
return res, err
}
func (repo *CategoryRepository) Delete(ctx context.Context, id string) (int64, error) {
oid, err := primitive.ObjectIDFromHex(id)
if err != nil {
return 0, ErrInvalidObjectID
}
rk := domain.GetCategoryRedisKey(id)
res, err := repo.DB.DeleteOne(ctx, rk, bson.M{"_id": oid})
return res, err
}
func (repo *CategoryRepository) ListCategory(ctx context.Context, params *repository.CategoryQueryParams) ([]*entity.Category, int64, error) {
// TODO 有需要列表快取實在取列表快取
// 構建查詢過濾器
filter := bson.M{}
if len(params.ID) > 0 {
objectIDs := make([]primitive.ObjectID, 0, len(params.ID))
for _, id := range params.ID {
objID, err := primitive.ObjectIDFromHex(id)
if err != nil {
continue
}
objectIDs = append(objectIDs, objID)
}
filter["_id"] = bson.M{"$in": objectIDs}
}
// 設置排序選項
opts := options.Find().SetSkip((params.PageIndex - 1) * params.PageSize).SetLimit(params.PageSize)
opts.SetSort(bson.D{{Key: "created_at", Value: -1}})
// 查詢符合條件的總數
count, err := repo.DB.GetClient().CountDocuments(ctx, filter)
if err != nil {
return nil, 0, err
}
// 執行查詢並獲取結果
var category []*entity.Category
err = repo.DB.GetClient().Find(ctx, &category, filter, opts)
if err != nil {
return nil, 0, err
}
return category, count, nil
}

View File

@ -0,0 +1,283 @@
package repository
import (
"context"
"fmt"
"testing"
"time"
"code.30cm.net/digimon/app-cloudep-product-service/pkg/domain/entity"
"code.30cm.net/digimon/app-cloudep-product-service/pkg/domain/repository"
mgo "code.30cm.net/digimon/library-go/mongo"
"github.com/alicebob/miniredis/v2"
"github.com/stretchr/testify/assert"
"github.com/zeromicro/go-zero/core/stores/cache"
"github.com/zeromicro/go-zero/core/stores/mon"
"github.com/zeromicro/go-zero/core/stores/redis"
"go.mongodb.org/mongo-driver/bson/primitive"
)
func SetupTestCategoryRepository(db string) (repository.CategoryRepository, func(), error) {
h, p, tearDown, err := startMongoContainer()
if err != nil {
return nil, nil, err
}
s, _ := miniredis.Run()
conf := &mgo.Conf{
Schema: Schema,
Host: fmt.Sprintf("%s:%s", h, p),
Database: db,
MaxStaleness: 300,
MaxPoolSize: 100,
MinPoolSize: 100,
MaxConnIdleTime: 300,
Compressors: []string{},
EnableStandardReadWriteSplitMode: false,
ConnectTimeoutMs: 3000,
}
cacheConf := cache.CacheConf{
cache.NodeConf{
RedisConf: redis.RedisConf{
Host: s.Addr(),
Type: redis.NodeType,
},
Weight: 100,
},
}
cacheOpts := []cache.Option{
cache.WithExpiry(1000 * time.Microsecond),
cache.WithNotFoundExpiry(1000 * time.Microsecond),
}
param := CategoryRepositoryParam{
Conf: conf,
CacheConf: cacheConf,
CacheOpts: cacheOpts,
DBOpts: []mon.Option{
mgo.SetCustomDecimalType(),
mgo.InitMongoOptions(*conf),
},
}
repo := MustCategoryRepository(param)
//_, _ = repo.Index20250317001UP(context.Background())
return repo, tearDown, nil
}
func TestCategoryRepository_Insert(t *testing.T) {
repo, tearDown, err := SetupTestCategoryRepository("testDB")
defer tearDown()
assert.NoError(t, err)
ctx := context.Background()
testData := &entity.Category{
Name: "Test Category",
}
err = repo.Insert(ctx, testData)
assert.NoError(t, err, "插入操作應該成功")
assert.NotZero(t, testData.ID, "ID 應該被生成")
assert.NotNil(t, testData.CreatedAt, "CreateAt 應該被設置")
assert.NotNil(t, testData.UpdatedAt, "UpdateAt 應該被設置")
}
func TestCategoryRepository_FindOneByID(t *testing.T) {
repo, tearDown, err := SetupTestCategoryRepository("testDB")
defer tearDown()
assert.NoError(t, err)
ctx := context.Background()
testData := &entity.Category{
Name: "Test Category Find",
}
_ = repo.Insert(ctx, testData)
tests := []struct {
name string
id string
expectError bool
}{
{
name: "Valid ID - Find Record",
id: testData.ID.Hex(),
expectError: false,
},
{
name: "Invalid ID Format",
id: "invalid_id",
expectError: true,
},
{
name: "Nonexistent ID",
id: primitive.NewObjectID().Hex(),
expectError: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result, err := repo.FindOneByID(ctx, tt.id)
if tt.expectError {
assert.Error(t, err)
assert.Nil(t, result)
} else {
assert.NoError(t, err)
assert.Equal(t, testData.Name, result.Name)
}
})
}
}
func TestCategoryRepository_Update(t *testing.T) {
repo, tearDown, err := SetupTestCategoryRepository("testDB")
defer tearDown()
assert.NoError(t, err)
ctx := context.Background()
testData := &entity.Category{
Name: "Test Category Update",
}
_ = repo.Insert(ctx, testData)
updatedData := &entity.Category{
ID: testData.ID,
Name: "Updated Category Name",
}
result, err := repo.Update(ctx, testData.ID.Hex(), updatedData)
assert.NoError(t, err, "更新操作應該成功")
assert.Equal(t, int64(1), result.ModifiedCount, "更新的文件數量應為 1")
// 檢查更新後的數據
updatedRecord, err := repo.FindOneByID(ctx, testData.ID.Hex())
assert.NoError(t, err, "應該可以找到更新的記錄")
assert.Equal(t, "Updated Category Name", updatedRecord.Name, "Category 名稱應該被更新")
}
func TestCategoryRepository_Delete(t *testing.T) {
repo, tearDown, err := SetupTestCategoryRepository("testDB")
defer tearDown()
assert.NoError(t, err)
ctx := context.Background()
testData := &entity.Category{
Name: "Test Category Delete",
}
_ = repo.Insert(ctx, testData)
tests := []struct {
name string
id string
expectError bool
expectCount int64
}{
{
name: "Valid ID - Successful Deletion",
id: testData.ID.Hex(),
expectError: false,
expectCount: 1,
},
{
name: "Invalid ID Format",
id: "invalid_id",
expectError: true,
expectCount: 0,
},
{
name: "Nonexistent ID",
id: primitive.NewObjectID().Hex(),
expectError: false,
expectCount: 0,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
count, err := repo.Delete(ctx, tt.id)
if tt.expectError {
assert.Error(t, err)
assert.Equal(t, int64(0), count)
} else {
assert.NoError(t, err)
assert.Equal(t, tt.expectCount, count)
}
})
}
}
func TestCategoryRepository_ListProduct(t *testing.T) {
repo, tearDown, err := SetupTestCategoryRepository("testDB")
defer tearDown()
assert.NoError(t, err)
ctx := context.Background()
// 插入測試數據
for i := 0; i < 5; i++ {
_ = repo.Insert(ctx, &entity.Category{
Name: "Test Category List",
})
}
params := &repository.CategoryQueryParams{
PageSize: 2,
PageIndex: 1,
}
categories, count, err := repo.ListCategory(ctx, params)
assert.NoError(t, err, "查詢應該成功")
assert.Equal(t, int64(5), count, "總數應該為 5")
assert.Len(t, categories, int(params.PageSize), "返回的數據應符合頁面大小")
}
func TestCategoryRepository_IsCategoryExists(t *testing.T) {
repo, tearDown, err := SetupTestCategoryRepository("testDB")
defer tearDown()
assert.NoError(t, err)
ctx := context.Background()
// 插入一些測試分類數據
category1 := &entity.Category{ID: primitive.NewObjectID()}
category2 := &entity.Category{ID: primitive.NewObjectID()}
category3 := &entity.Category{ID: primitive.NewObjectID()}
_ = repo.Insert(ctx, category1)
_ = repo.Insert(ctx, category2)
// 定義測試案例
tests := []struct {
name string
ids []string
expectedIDs []string
}{
{
name: "部分分類存在",
ids: []string{category1.ID.Hex(), category2.ID.Hex(), category3.ID.Hex()}, // category3 不存在
expectedIDs: []string{category1.ID.Hex(), category2.ID.Hex()},
},
{
name: "所有分類存在",
ids: []string{category1.ID.Hex(), category2.ID.Hex()},
expectedIDs: []string{category1.ID.Hex(), category2.ID.Hex()},
},
{
name: "無分類存在",
ids: []string{category3.ID.Hex()}, // category3 不存在
expectedIDs: []string{},
},
{
name: "包含無效 ID",
ids: []string{category1.ID.Hex(), "invalid_id"}, // "invalid_id" 是無效 ID 格式
expectedIDs: []string{category1.ID.Hex()},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := repo.IsCategoryExists(ctx, tt.ids)
assert.ElementsMatch(t, tt.expectedIDs, result, "返回的存在ID應該與期望值匹配")
})
}
}

12
pkg/repository/error.go Executable file
View File

@ -0,0 +1,12 @@
package repository
import (
"errors"
"github.com/zeromicro/go-zero/core/stores/mon"
)
var (
ErrNotFound = mon.ErrNotFound
ErrInvalidObjectID = errors.New("invalid objectId")
)

197
pkg/repository/kyc.go Normal file
View File

@ -0,0 +1,197 @@
package repository
import (
"context"
"errors"
"time"
"code.30cm.net/digimon/app-cloudep-product-service/pkg/domain/entity"
"code.30cm.net/digimon/app-cloudep-product-service/pkg/domain/repository"
mgo "code.30cm.net/digimon/library-go/mongo"
"github.com/zeromicro/go-zero/core/stores/cache"
"github.com/zeromicro/go-zero/core/stores/mon"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/bson/primitive"
"go.mongodb.org/mongo-driver/mongo"
"go.mongodb.org/mongo-driver/mongo/options"
)
type KYCRepositoryParam struct {
Conf *mgo.Conf
CacheConf cache.CacheConf
DBOpts []mon.Option
CacheOpts []cache.Option
}
type KYCRepository struct {
DB mgo.DocumentDBWithCacheUseCase
}
func NewKYCRepository(param KYCRepositoryParam) repository.KYCRepository {
e := entity.KYC{}
documentDB, err := mgo.MustDocumentDBWithCache(
param.Conf,
e.CollectionName(),
param.CacheConf,
param.DBOpts,
param.CacheOpts,
)
if err != nil {
panic(err)
}
return &KYCRepository{
DB: documentDB,
}
}
func (repo *KYCRepository) Create(ctx context.Context, data *entity.KYC) error {
if data.ID.IsZero() {
now := time.Now().UTC().UnixNano()
data.ID = primitive.NewObjectID()
data.CreatedAt = now
data.UpdatedAt = now
}
_, err := repo.DB.GetClient().InsertOne(ctx, data)
return err
}
func (repo *KYCRepository) FindLatestByUID(ctx context.Context, uid string) (*entity.KYC, error) {
filter := bson.M{"uid": uid}
var result entity.KYC
opts := options.FindOne().SetSort(bson.D{{Key: "created_at", Value: -1}}) // 用 SetSort 加入排序
err := repo.DB.GetClient().FindOne(ctx, &result, filter, opts)
if err != nil {
if errors.Is(err, mongo.ErrNoDocuments) {
return nil, ErrNotFound
}
return nil, err
}
return &result, nil
}
func (repo *KYCRepository) FindByID(ctx context.Context, id string) (*entity.KYC, error) {
oid, err := primitive.ObjectIDFromHex(id)
if err != nil {
return nil, err
}
filter := bson.M{"_id": oid}
var result entity.KYC
err = repo.DB.GetClient().FindOne(ctx, &result, filter)
if err != nil {
return nil, err
}
return &result, nil
}
func (repo *KYCRepository) List(ctx context.Context, params repository.KYCQueryParams) ([]*entity.KYC, int64, error) {
filter := bson.M{}
if params.UID != nil {
filter["uid"] = *params.UID
}
if params.Country != nil {
filter["country_region"] = *params.Country
}
if params.Status != nil {
filter["status"] = *params.Status
}
// 設置排序選項
opts := options.Find().SetSkip((params.PageIndex - 1) * params.PageSize).SetLimit(params.PageSize)
// if params.SortByDate {
// opts.SetSort(bson.E{Key: "created_at", Value: -1})
// } else {
// opts.SetSort(bson.D{{Key: "updated_at", Value: -1}})
// }
// 查詢符合條件的總數
total, err := repo.DB.GetClient().CountDocuments(ctx, filter)
if err != nil {
return nil, 0, err
}
// 執行查詢並獲取結果
var results []*entity.KYC
err = repo.DB.GetClient().Find(ctx, &results, filter, opts)
if err != nil {
return nil, 0, err
}
return results, total, nil
}
func (repo *KYCRepository) UpdateStatus(ctx context.Context, id string, status string, reason string) error {
oid, err := primitive.ObjectIDFromHex(id)
if err != nil {
return err
}
filter := bson.M{"_id": oid}
update := bson.M{
"$set": bson.M{
"status": status,
"reject_reason": reason,
"updated_at": time.Now().UTC().UnixNano(),
},
}
_, err = repo.DB.GetClient().UpdateOne(ctx, filter, update)
return err
}
func (repo *KYCRepository) UpdateKYCInfo(ctx context.Context, id string, update *repository.KYCUpdateParams) error {
oid, err := primitive.ObjectIDFromHex(id)
if err != nil {
return err
}
setFields := bson.M{"updated_at": time.Now().UTC().UnixNano()}
if update.Name != nil {
setFields["name"] = *update.Name
}
if update.Identification != nil {
setFields["identification"] = *update.Identification
}
if update.IdentificationType != nil {
setFields["identification_type"] = *update.IdentificationType
}
if update.Address != nil {
setFields["address"] = *update.Address
}
if update.PostalCode != nil {
setFields["postal_code"] = *update.PostalCode
}
if update.IDFrontImage != nil {
setFields["id_front_image"] = *update.IDFrontImage
}
if update.IDBackImage != nil {
setFields["id_back_image"] = *update.IDBackImage
}
if update.BankStatementImg != nil {
setFields["bank_statement_img"] = *update.BankStatementImg
}
if update.BankCode != nil {
setFields["bank_code"] = *update.BankCode
}
if update.BankName != nil {
setFields["bank_name"] = *update.BankName
}
if update.BranchCode != nil {
setFields["branch_code"] = *update.BranchCode
}
if update.BranchName != nil {
setFields["branch_name"] = *update.BranchName
}
if update.BankAccount != nil {
setFields["bank_account"] = *update.BankAccount
}
filter := bson.M{"_id": oid, "status": "PENDING"} // 僅允許更新尚未審核的
updateDoc := bson.M{"$set": setFields}
_, err = repo.DB.GetClient().UpdateOne(ctx, filter, updateDoc)
return err
}

479
pkg/repository/kyc_test.go Normal file
View File

@ -0,0 +1,479 @@
package repository
import (
"context"
"fmt"
"testing"
"time"
"code.30cm.net/digimon/app-cloudep-product-service/pkg/domain/entity"
"code.30cm.net/digimon/app-cloudep-product-service/pkg/domain/kyc"
"code.30cm.net/digimon/app-cloudep-product-service/pkg/domain/repository"
mgo "code.30cm.net/digimon/library-go/mongo"
"github.com/alicebob/miniredis/v2"
"github.com/stretchr/testify/assert"
"github.com/zeromicro/go-zero/core/stores/cache"
"github.com/zeromicro/go-zero/core/stores/mon"
"github.com/zeromicro/go-zero/core/stores/redis"
"go.mongodb.org/mongo-driver/bson/primitive"
)
func SetupTestKYCRepository(db string) (repository.KYCRepository, func(), error) {
h, p, tearDown, err := startMongoContainer()
if err != nil {
return nil, nil, err
}
s, _ := miniredis.Run()
conf := &mgo.Conf{
Schema: Schema,
Host: fmt.Sprintf("%s:%s", h, p),
Database: db,
MaxStaleness: 300,
MaxPoolSize: 100,
MinPoolSize: 100,
MaxConnIdleTime: 300,
Compressors: []string{},
EnableStandardReadWriteSplitMode: false,
ConnectTimeoutMs: 3000,
}
cacheConf := cache.CacheConf{
cache.NodeConf{
RedisConf: redis.RedisConf{
Host: s.Addr(),
Type: redis.NodeType,
},
Weight: 100,
},
}
cacheOpts := []cache.Option{
cache.WithExpiry(1000 * time.Microsecond),
cache.WithNotFoundExpiry(1000 * time.Microsecond),
}
param := KYCRepositoryParam{
Conf: conf,
CacheConf: cacheConf,
CacheOpts: cacheOpts,
DBOpts: []mon.Option{
mgo.SetCustomDecimalType(),
mgo.InitMongoOptions(*conf),
},
}
repo := NewKYCRepository(param)
//_, _ = repo.Index20250317001UP(context.Background())
return repo, tearDown, nil
}
func TestInsertKYC(t *testing.T) {
model, tearDown, err := SetupTestKYCRepository("testDB")
assert.NoError(t, err)
defer tearDown()
ctx := context.Background()
// 建立多筆 KYC 資料
items := []entity.KYC{
{
UID: "user-a",
CountryRegion: "TW",
Name: "王小明",
Identification: "A123456789",
IdentificationType: "ID_CARD",
Address: "台北市信義區",
PostalCode: "110",
Status: "PENDING",
},
{
UID: "user-b",
CountryRegion: "US",
Name: "John Smith",
Identification: "P987654321",
IdentificationType: "PASSPORT",
Address: "123 Main St, NY",
PostalCode: "10001",
Status: "PENDING",
},
}
// 插入資料
for i := range items {
err := model.Create(ctx, &items[i])
assert.NoError(t, err)
assert.False(t, items[i].ID.IsZero(), "ID 應自動產生")
assert.NotZero(t, items[i].CreatedAt)
assert.NotZero(t, items[i].UpdatedAt)
}
// 驗證插入資料是否正確
for _, item := range items {
kyc, err := model.FindByID(ctx, item.ID.Hex())
assert.NoError(t, err)
assert.Equal(t, item.UID, kyc.UID)
assert.Equal(t, item.Name, kyc.Name)
assert.Equal(t, item.CountryRegion, kyc.CountryRegion)
}
}
func TestFindLatestByUID(t *testing.T) {
model, tearDown, err := SetupTestKYCRepository("testDB")
assert.NoError(t, err)
defer tearDown()
ctx := context.Background()
now := time.Now().UnixNano()
// 測試資料準備
testData := []entity.KYC{
{
UID: "user-multi",
Name: "早期資料",
Status: "PENDING",
CreatedAt: now - 1000,
UpdatedAt: now - 1000,
},
{
UID: "user-multi",
Name: "最新資料",
Status: "APPROVED",
CreatedAt: now + 1000,
UpdatedAt: now + 1000,
},
{
UID: "user-single",
Name: "唯一資料",
Status: "PENDING",
CreatedAt: now,
UpdatedAt: now,
},
}
for i := range testData {
err := model.Create(ctx, &testData[i])
assert.NoError(t, err)
}
// table-driven 測試表
tests := []struct {
name string
uid string
expectNil bool
expect string // 預期的 Name
}{
{
name: "多筆資料,取最新",
uid: "user-multi",
expectNil: false,
expect: "最新資料",
},
{
name: "單筆資料",
uid: "user-single",
expectNil: false,
expect: "唯一資料",
},
{
name: "查無資料",
uid: "user-none",
expectNil: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result, err := model.FindLatestByUID(ctx, tt.uid)
if tt.expectNil {
assert.Error(t, err)
assert.Nil(t, result)
} else {
assert.NoError(t, err)
assert.NotNil(t, result)
assert.Equal(t, tt.expect, result.Name)
}
})
}
}
func TestKYCRepository_List(t *testing.T) {
model, tearDown, err := SetupTestKYCRepository("testDB")
assert.NoError(t, err)
defer tearDown()
ctx := context.Background()
now := time.Now().UnixNano()
seedData := []entity.KYC{
{
UID: "user-1",
CountryRegion: "TW",
Status: "PENDING",
Name: "Alice",
CreatedAt: now,
UpdatedAt: now,
},
{
UID: "user-1",
CountryRegion: "JP",
Status: "APPROVED",
Name: "Bob",
CreatedAt: now + 1,
UpdatedAt: now + 1,
},
{
UID: "user-2",
CountryRegion: "US",
Status: "REJECTED",
Name: "Charlie",
CreatedAt: now + 2,
UpdatedAt: now + 2,
},
}
for i := range seedData {
err := model.Create(ctx, &seedData[i])
assert.NoError(t, err)
}
tests := []struct {
name string
params repository.KYCQueryParams
expectedCount int
expectedTotal int64
expectedFirst string
}{
{
name: "Query by UID",
params: repository.KYCQueryParams{
UID: ptr("user-1"),
PageSize: 10,
PageIndex: 1,
},
expectedCount: 2,
expectedTotal: 2,
},
{
name: "Query by CountryRegion",
params: repository.KYCQueryParams{
Country: ptr("JP"),
PageSize: 10,
PageIndex: 1,
},
expectedCount: 1,
expectedTotal: 1,
expectedFirst: "Bob",
},
{
name: "Query by Status",
params: repository.KYCQueryParams{
Status: ptr("REJECTED"),
PageSize: 10,
PageIndex: 1,
},
expectedCount: 1,
expectedTotal: 1,
expectedFirst: "Charlie",
},
{
name: "Pagination test",
params: repository.KYCQueryParams{
PageSize: 1,
PageIndex: 1,
},
expectedCount: 1,
expectedTotal: 3,
},
{
name: "No match",
params: repository.KYCQueryParams{
UID: ptr("non-existent"),
PageSize: 10,
PageIndex: 1,
},
expectedCount: 0,
expectedTotal: 0,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
list, total, err := model.List(ctx, tt.params)
assert.NoError(t, err)
assert.Equal(t, tt.expectedCount, len(list))
assert.Equal(t, tt.expectedTotal, total)
if tt.expectedFirst != "" && len(list) > 0 {
assert.Equal(t, tt.expectedFirst, list[0].Name)
}
})
}
}
func TestKYCRepository_UpdateStatus(t *testing.T) {
model, tearDown, err := SetupTestKYCRepository("testDB")
assert.NoError(t, err)
defer tearDown()
ctx := context.Background()
// 建立一筆 KYC 資料
k := &entity.KYC{
UID: "user-status",
Name: "測試用戶",
Status: kyc.StatusPending,
CreatedAt: time.Now().UnixNano(),
UpdatedAt: time.Now().UnixNano(),
}
err = model.Create(ctx, k)
assert.NoError(t, err)
tests := []struct {
name string
id string
newStatus kyc.Status
reason string
expectErr bool
expectStatus kyc.Status
}{
{
name: "成功更新為 REJECTED",
id: k.ID.Hex(),
newStatus: kyc.StatusREJECTED,
reason: "文件不符",
expectErr: false,
expectStatus: kyc.StatusREJECTED,
},
{
name: "失敗 - 無效 ID 格式",
id: "not-a-valid-id",
newStatus: kyc.StatusAPPROVED,
reason: "",
expectErr: true,
},
{
name: "失敗 - 找不到資料",
id: primitive.NewObjectID().Hex(), // 合法但不存在
newStatus: kyc.StatusAPPROVED,
reason: "",
expectErr: false, // Mongo 不會報錯,但更新筆數是 0後面可以補強這邊驗證
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err := model.UpdateStatus(ctx, tt.id, tt.newStatus.ToString(), tt.reason)
if tt.expectErr {
assert.Error(t, err)
} else {
assert.NoError(t, err)
// 如果是第一個 case檢查資料是否真的被更新
if tt.expectStatus != "" {
result, err := model.FindByID(ctx, tt.id)
assert.NoError(t, err)
assert.Equal(t, tt.expectStatus, result.Status)
assert.Equal(t, tt.reason, result.RejectReason)
assert.NotZero(t, result.UpdatedAt)
}
}
})
}
}
func TestKYCRepository_UpdateKYCInfo(t *testing.T) {
model, tearDown, err := SetupTestKYCRepository("testDB")
assert.NoError(t, err)
defer tearDown()
ctx := context.Background()
now := time.Now().UnixNano()
// 建立兩筆資料:一筆 PENDING、一筆 APPROVED
pendingKYC := &entity.KYC{
UID: "user-pending",
Name: "原名",
Address: "原地址",
Status: "PENDING",
CreatedAt: now,
UpdatedAt: now,
}
err = model.Create(ctx, pendingKYC)
assert.NoError(t, err)
approvedKYC := &entity.KYC{
UID: "user-approved",
Name: "原名",
Address: "原地址",
Status: "APPROVED",
CreatedAt: now,
UpdatedAt: now,
}
err = model.Create(ctx, approvedKYC)
assert.NoError(t, err)
name := "新名字"
address := "新地址"
tests := []struct {
name string
id string
update *repository.KYCUpdateParams
expectErr bool
expectApply bool // 預期更新是否應成功套用
}{
{
name: "成功更新 PENDING 狀態資料",
id: pendingKYC.ID.Hex(),
update: &repository.KYCUpdateParams{
Name: &name,
Address: &address,
},
expectErr: false,
expectApply: true,
},
{
name: "更新失敗 - 非 PENDING 狀態",
id: approvedKYC.ID.Hex(),
update: &repository.KYCUpdateParams{
Name: &name,
Address: &address,
},
expectErr: false,
expectApply: false,
},
{
name: "更新失敗 - 非法 ID 格式",
id: "not-an-id",
update: &repository.KYCUpdateParams{
Name: &name,
},
expectErr: true,
expectApply: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err := model.UpdateKYCInfo(ctx, tt.id, tt.update)
if tt.expectErr {
assert.Error(t, err)
return
}
assert.NoError(t, err)
// 若預期資料已被套用,驗證欄位是否更新
if tt.expectApply {
got, err := model.FindByID(ctx, tt.id)
assert.NoError(t, err)
assert.Equal(t, name, got.Name)
assert.Equal(t, address, got.Address)
} else {
got, err := model.FindByID(ctx, tt.id)
assert.NoError(t, err)
assert.NotEqual(t, name, got.Name)
}
})
}
}

View File

@ -0,0 +1,52 @@
package repository
import (
"context"
"fmt"
"github.com/testcontainers/testcontainers-go"
"github.com/testcontainers/testcontainers-go/wait"
)
const (
Host = "127.0.0.1"
Port = "27017"
Schema = "mongodb"
)
func startMongoContainer() (string, string, func(), error) {
ctx := context.Background()
req := testcontainers.ContainerRequest{
Image: "mongo:latest",
ExposedPorts: []string{"27017/tcp"},
WaitingFor: wait.ForListeningPort("27017/tcp"),
}
mongoC, err := testcontainers.GenericContainer(ctx, testcontainers.GenericContainerRequest{
ContainerRequest: req,
Started: true,
})
if err != nil {
return "", "", nil, err
}
port, err := mongoC.MappedPort(ctx, Port)
if err != nil {
return "", "", nil, err
}
host, err := mongoC.Host(ctx)
if err != nil {
return "", "", nil, err
}
uri := fmt.Sprintf("mongodb://%s:%s", host, port.Port())
tearDown := func() {
mongoC.Terminate(ctx)
}
fmt.Printf("Connecting to %s\n", uri)
return host, port.Port(), tearDown, nil
}

273
pkg/repository/product.go Normal file
View File

@ -0,0 +1,273 @@
package repository
import (
"context"
"errors"
"time"
"code.30cm.net/digimon/app-cloudep-product-service/pkg/domain"
"code.30cm.net/digimon/app-cloudep-product-service/pkg/domain/entity"
"code.30cm.net/digimon/app-cloudep-product-service/pkg/domain/repository"
mgo "code.30cm.net/digimon/library-go/mongo"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/bson/primitive"
"go.mongodb.org/mongo-driver/mongo"
"go.mongodb.org/mongo-driver/mongo/options"
"github.com/zeromicro/go-zero/core/stores/cache"
"github.com/zeromicro/go-zero/core/stores/mon"
)
type ProductRepositoryParam struct {
Conf *mgo.Conf
CacheConf cache.CacheConf
DBOpts []mon.Option
CacheOpts []cache.Option
}
type ProductRepository struct {
DB mgo.DocumentDBWithCacheUseCase
}
func NewProductRepository(param ProductRepositoryParam) repository.ProductRepository {
e := entity.Product{}
documentDB, err := mgo.MustDocumentDBWithCache(
param.Conf,
e.CollectionName(),
param.CacheConf,
param.DBOpts,
param.CacheOpts,
)
if err != nil {
panic(err)
}
return &ProductRepository{
DB: documentDB,
}
}
func (repo *ProductRepository) Insert(ctx context.Context, data *entity.Product) error {
if data.ID.IsZero() {
now := time.Now().UTC().UnixNano()
data.ID = primitive.NewObjectID()
data.CreatedAt = now
data.UpdatedAt = now
}
rk := domain.GetProductRK(data.ID.Hex())
_, err := repo.DB.InsertOne(ctx, rk, data)
return err
}
func (repo *ProductRepository) FindOneByID(ctx context.Context, id string) (*entity.Product, error) {
oid, err := primitive.ObjectIDFromHex(id)
if err != nil {
return nil, err
}
var result *entity.Product
err = repo.DB.FindOne(ctx, domain.GetProductRK(id), &result, bson.M{"_id": oid})
switch {
case err == nil:
return result, nil
case errors.Is(err, mon.ErrNotFound):
return nil, ErrNotFound
default:
return nil, err
}
}
func (repo *ProductRepository) FindOneBySlug(ctx context.Context, slug string) (*entity.Product, error) {
var result *entity.Product
err := repo.DB.FindOne(ctx, domain.GetProductRK(slug), &result, bson.M{"slug": slug})
switch {
case err == nil:
return result, nil
case errors.Is(err, mon.ErrNotFound):
return nil, ErrNotFound
default:
return nil, err
}
}
func (repo *ProductRepository) Update(ctx context.Context, productID string, data *repository.ProductUpdateParams) (*mongo.UpdateResult, error) {
item, err := repo.FindOneByID(ctx, productID)
if err != nil {
return nil, err
}
now := time.Now().UTC().UnixNano()
// 動態構建更新內容
updateFields := bson.M{
"updated_at": now, // 確保 `updateAt` 總是更新
}
if data.Title != nil {
updateFields["title"] = *data.Title
}
if data.IsPublished != nil {
updateFields["is_published"] = *data.IsPublished
}
if data.StartTime != nil {
updateFields["start_time"] = *data.StartTime
}
if data.EndTime != nil {
updateFields["end_time"] = *data.EndTime
}
if data.ShortDescription != "" {
updateFields["short_description"] = data.ShortDescription
}
if data.Category != nil {
updateFields["category"] = *data.Category
}
if data.Details != nil {
updateFields["details"] = *data.Details
}
if data.ShortTitle != nil {
updateFields["short_title"] = *data.ShortTitle
}
if data.Slug != nil {
updateFields["slug"] = *data.Slug
}
if data.Amount != nil {
updateFields["amount"] = *data.Amount
}
if len(data.Media) > 0 {
updateFields["media"] = data.Media
}
if len(data.Media) > 0 {
updateFields["media"] = data.Media
}
if len(data.CustomFields) > 0 {
updateFields["custom_fields"] = data.CustomFields
}
oid, err := primitive.ObjectIDFromHex(productID)
if err != nil {
return nil, ErrInvalidObjectID
}
// 執行更新
rk := domain.GetProductRK(productID)
res, err := repo.DB.UpdateOne(ctx, rk, bson.M{"_id": oid}, bson.M{"$set": updateFields})
if err != nil {
return nil, err
}
_ = repo.DB.DelCache(ctx, domain.GetProductRK(*item.Slug))
return res, err
}
func (repo *ProductRepository) Delete(ctx context.Context, id string) error {
item, err := repo.FindOneByID(ctx, id)
if err != nil {
return err
}
oid, err := primitive.ObjectIDFromHex(id)
if err != nil {
return ErrInvalidObjectID
}
filter := bson.M{"_id": oid}
_, err = repo.DB.DeleteOne(ctx, domain.GetProductRK(id), filter)
if err != nil {
return err
}
_ = repo.DB.DelCache(ctx, domain.GetProductRK(*item.Slug))
return nil
}
func (repo *ProductRepository) ListProduct(ctx context.Context, params *repository.ProductQueryParams) ([]*entity.Product, int64, error) {
// 構建查詢過濾器
filter := bson.M{}
if params.UID != nil && *params.UID != "" {
filter["uid"] = *params.UID
}
if params.IsPublished != nil {
filter["is_published"] = *params.IsPublished
}
if params.Category != nil {
filter["category"] = *params.Category
}
if params.StartTime != nil {
filter["start_time"] = bson.M{"$gte": *params.StartTime} // 篩選開始時間在指定時間之後的專案
}
if params.EndTime != nil {
filter["end_time"] = bson.M{"$lte": *params.EndTime} // 篩選結束時間在指定時間之前的專案
}
if params.Slug != nil && *params.Slug != "" {
filter["slug"] = *params.Slug
}
// 設置排序選項
opts := options.Find().SetSkip((params.PageIndex - 1) * params.PageSize).SetLimit(params.PageSize)
opts.SetSort(bson.D{{Key: "updated_at", Value: -1}})
// 查詢符合條件的總數
count, err := repo.DB.GetClient().CountDocuments(ctx, filter)
if err != nil {
return nil, 0, err
}
// 執行查詢並獲取結果
var products []*entity.Product
err = repo.DB.GetClient().Find(ctx, &products, filter, opts)
if err != nil {
return nil, 0, err
}
return products, count, nil
}
func (repo *ProductRepository) Transaction(
ctx context.Context,
fn func(sessCtx mongo.SessionContext) (any, error),
opts ...*options.TransactionOptions) error {
// 獲取資料庫連接
db := repo.DB.GetClient().Database()
// 使用 MongoDB 的會話進行操作
return db.Client().UseSession(ctx, func(sessionCtx mongo.SessionContext) error {
// 開始交易,支持可選的 TransactionOptions
if err := sessionCtx.StartTransaction(opts...); err != nil {
return err
}
// 執行用戶提供的交易函數
result, err := fn(sessionCtx)
if err != nil {
// 若發生錯誤則中止交易
if abortErr := sessionCtx.AbortTransaction(ctx); abortErr != nil {
return abortErr
}
return err
}
// 提交交易
if err := sessionCtx.CommitTransaction(ctx); err != nil {
// 若提交失敗則中止交易
if abortErr := sessionCtx.AbortTransaction(ctx); abortErr != nil {
return abortErr
}
return err
}
// 返回交易執行結果
_ = result // 結果未使用,保留擴展可能
return nil
})
}
func (repo *ProductRepository) Index20250317001UP(ctx context.Context) (*mongo.Cursor, error) {
// 等價於 db.account.createIndex({"create_at": 1})
repo.DB.PopulateIndex(ctx, "uid", 1, false)
repo.DB.PopulateIndex(ctx, "slug", 1, true)
repo.DB.PopulateIndex(ctx, "category", 1, false)
return repo.DB.GetClient().Indexes().List(ctx)
}

View File

@ -0,0 +1,354 @@
package repository
import (
"context"
"errors"
"fmt"
"time"
"code.30cm.net/digimon/app-cloudep-product-service/pkg/domain"
"code.30cm.net/digimon/app-cloudep-product-service/pkg/domain/entity"
"code.30cm.net/digimon/app-cloudep-product-service/pkg/domain/product"
"code.30cm.net/digimon/app-cloudep-product-service/pkg/domain/repository"
mgo "code.30cm.net/digimon/library-go/mongo"
"github.com/zeromicro/go-zero/core/stores/cache"
"github.com/zeromicro/go-zero/core/stores/mon"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/bson/primitive"
"go.mongodb.org/mongo-driver/mongo"
"go.mongodb.org/mongo-driver/mongo/options"
)
type ProductItemRepositoryParam struct {
Conf *mgo.Conf
CacheConf cache.CacheConf
DBOpts []mon.Option
CacheOpts []cache.Option
}
type ProductItemRepository struct {
DB mgo.DocumentDBWithCacheUseCase
}
func NewProductItemRepository(param ProductItemRepositoryParam) repository.ProductItemRepository {
e := entity.ProductItems{}
documentDB, err := mgo.MustDocumentDBWithCache(
param.Conf,
e.CollectionName(),
param.CacheConf,
param.DBOpts,
param.CacheOpts,
)
if err != nil {
panic(err)
}
return &ProductItemRepository{
DB: documentDB,
}
}
func (repo *ProductItemRepository) Insert(ctx context.Context, items []entity.ProductItems) error {
now := time.Now().UTC().UnixNano()
docs := make([]any, 0, len(items))
for i := range items {
if items[i].ID.IsZero() {
items[i].ID = primitive.NewObjectID()
items[i].CreatedAt = now
items[i].UpdatedAt = now
}
docs = append(docs, items[i])
}
if len(docs) == 0 {
return nil
}
_, err := repo.DB.GetClient().InsertMany(ctx, docs)
return err
}
func (repo *ProductItemRepository) FindByID(ctx context.Context, id string) (*entity.ProductItems, error) {
oid, err := primitive.ObjectIDFromHex(id)
if err != nil {
return nil, ErrInvalidObjectID
}
var data entity.ProductItems
rk := domain.GetProductItemRK(id)
err = repo.DB.FindOne(ctx, rk, &data, bson.M{"_id": oid})
switch {
case err == nil:
return &data, nil
case errors.Is(err, mon.ErrNotFound):
return nil, ErrNotFound
default:
return nil, err
}
}
func (repo *ProductItemRepository) Delete(ctx context.Context, ids []string) error {
if len(ids) == 0 {
return nil
}
objectIDs := make([]primitive.ObjectID, 0, len(ids))
cacheKeys := make([]string, 0, len(ids))
for _, id := range ids {
oid, err := primitive.ObjectIDFromHex(id)
if err != nil {
return ErrInvalidObjectID
}
objectIDs = append(objectIDs, oid)
cacheKeys = append(cacheKeys, domain.GetProductItemRK(id))
}
// 刪除多筆 Mongo 資料
filter := bson.M{"_id": bson.M{"$in": objectIDs}}
_, err := repo.DB.GetClient().DeleteMany(ctx, filter)
// 可選:刪除 Redis 快取(每個 ID 都清除)
_ = repo.DB.DelCache(ctx, cacheKeys...)
return err
}
func (repo *ProductItemRepository) Update(ctx context.Context, id string, param *repository.ProductUpdateItem) error {
// 將 `id` 轉換為 MongoDB 的 ObjectID
objectID, err := primitive.ObjectIDFromHex(id)
if err != nil {
return ErrInvalidObjectID
}
// 構建更新文檔
setFields := bson.M{}
// 檢查並添加需要更新的欄位
if param.Name != nil {
setFields["name"] = *param.Name
}
if param.Description != nil {
setFields["description"] = *param.Description
}
if param.ShortDescription != nil {
setFields["short_description"] = *param.ShortDescription
}
if param.Price != nil {
setFields["price"] = *param.Price
}
if param.Stock != nil {
setFields["stock"] = *param.Stock
}
if param.IsUnLimit != nil {
setFields["is_un_limit"] = *param.IsUnLimit
}
if param.IsFree != nil {
setFields["is_free"] = *param.IsFree
}
if param.SKU != nil {
setFields["sku"] = *param.SKU
}
if param.TimeSeries != nil {
setFields["time_series"] = *param.TimeSeries
}
if len(param.Media) > 0 {
setFields["media"] = param.Media
}
if param.CustomFields != nil {
setFields["custom_fields"] = param.CustomFields
}
if param.Freight != nil {
setFields["freight"] = param.Freight
}
// 如果沒有任何需要更新的內容,直接返回
if len(setFields) == 0 {
return fmt.Errorf("no fields to update")
}
// 執行更新操作
filter := bson.M{"_id": objectID}
rk := domain.GetProductItemRK(id)
_, err = repo.DB.UpdateOne(ctx, rk, filter, bson.M{
"$set": setFields,
})
if err != nil {
return fmt.Errorf("failed to update product item: %w", err)
}
return nil
}
func (repo *ProductItemRepository) DeleteByReferenceID(ctx context.Context, id string) error {
// 1. 查詢所有符合 reference_id 的項目,只取 _id 欄位
filter := bson.M{"reference_id": id}
projection := bson.M{"_id": 1}
opts := options.Find().SetProjection(projection)
var items []entity.ProductItems
err := repo.DB.GetClient().Find(ctx, &items, filter, opts)
if err != nil {
return err
}
// 2. 刪除這些 ID 對應的 Redis 快取
cacheKeys := make([]string, 0, len(items))
for _, item := range items {
cacheKeys = append(cacheKeys, domain.GetProductItemRK(item.ID.Hex()))
}
if len(cacheKeys) > 0 {
_ = repo.DB.DelCache(ctx, cacheKeys...)
}
// 3. 刪除 DB 中的資料
delFilter := bson.M{"reference_id": id}
_, err = repo.DB.GetClient().DeleteMany(ctx, delFilter)
if err != nil {
return err
}
return nil
}
func (repo *ProductItemRepository) ListProductItem(ctx context.Context, param repository.ProductItemQueryParams) ([]entity.ProductItems, int64, error) {
// 構建查詢過濾器
filter := bson.M{}
ids := make([]primitive.ObjectID, 0, len(param.ItemID))
for _, item := range param.ItemID {
oid, err := primitive.ObjectIDFromHex(item)
if err != nil {
continue
}
ids = append(ids, oid)
}
// 添加篩選條件
if len(param.ItemID) > 0 {
filter["_id"] = bson.M{"$in": ids}
}
if param.ReferenceID != nil {
filter["reference_id"] = *param.ReferenceID
}
if param.IsFree != nil {
filter["is_free"] = *param.IsFree
}
if param.Status != nil {
filter["status"] = *param.Status
}
if param.IsUnlimited != nil {
filter["is_un_limit"] = *param.IsUnlimited
}
// 設置排序選項
opts := options.Find().SetSkip((param.PageIndex - 1) * param.PageSize).SetLimit(param.PageSize)
opts.SetSort(bson.D{{Key: "created_at", Value: -1}})
// 查詢符合條件的總數
count, err := repo.DB.GetClient().CountDocuments(ctx, filter)
if err != nil {
return nil, 0, err
}
// 執行查詢並獲取結果
var products []entity.ProductItems
err = repo.DB.GetClient().Find(ctx, &products, filter, opts)
if err != nil {
return nil, 0, err
}
return products, count, nil
}
func (repo *ProductItemRepository) UpdateStatus(ctx context.Context, id string, status product.ItemStatus) error {
objectID, err := primitive.ObjectIDFromHex(id)
if err != nil {
return ErrInvalidObjectID
}
filter := bson.M{"_id": objectID}
update := bson.M{"$set": bson.M{"status": status}}
rk := domain.GetProductItemRK(id)
_, err = repo.DB.UpdateOne(ctx, rk, filter, update)
if err != nil {
return fmt.Errorf("failed to update status: %w", err)
}
return nil
}
func (repo *ProductItemRepository) IncSalesCount(ctx context.Context, id string, count int64) error {
objectID, err := primitive.ObjectIDFromHex(id)
if err != nil {
return ErrInvalidObjectID
}
filter := bson.M{"_id": objectID}
update := bson.M{"$inc": bson.M{"sales_count": count}}
rk := domain.GetProductItemRK(id)
_, err = repo.DB.UpdateOne(ctx, rk, filter, update)
if err != nil {
return fmt.Errorf("failed to decrease stock: %w", err)
}
return nil
}
func (repo *ProductItemRepository) DecSalesCount(ctx context.Context, id string, count int64) error {
objectID, err := primitive.ObjectIDFromHex(id)
if err != nil {
return ErrInvalidObjectID
}
filter := bson.M{"_id": objectID, "sales_count": bson.M{"$gte": count}}
update := bson.M{"$inc": bson.M{"sales_count": -count}}
rk := domain.GetProductItemRK(id)
_, err = repo.DB.UpdateOne(ctx, rk, filter, update)
if err != nil {
return fmt.Errorf("failed to decrease stock: %w", err)
}
return nil
}
func (repo *ProductItemRepository) GetSalesCount(ctx context.Context, ids []string) ([]repository.ProductItemSalesCount, error) {
objectIDs := make([]primitive.ObjectID, 0, len(ids))
for _, id := range ids {
objectID, err := primitive.ObjectIDFromHex(id)
if err != nil {
continue
}
objectIDs = append(objectIDs, objectID)
}
if len(objectIDs) == 0 {
return nil, ErrInvalidObjectID
}
result := make([]entity.ProductItems, 0, len(ids))
filter := bson.M{"_id": bson.M{"$in": objectIDs}}
err := repo.DB.GetClient().Find(ctx, &result, filter)
if err != nil {
return nil, err
}
stockMap := make([]repository.ProductItemSalesCount, 0, len(result))
for _, item := range result {
stockMap = append(stockMap, repository.ProductItemSalesCount{
ID: item.ID.Hex(),
Count: item.SalesCount,
})
}
return stockMap, nil
}
func (repo *ProductItemRepository) Index20250317001UP(ctx context.Context) (*mongo.Cursor, error) {
repo.DB.PopulateIndex(ctx, "reference_id", 1, false)
repo.DB.PopulateIndex(ctx, "status", 1, false)
return repo.DB.GetClient().Indexes().List(ctx)
}

View File

@ -0,0 +1,800 @@
package repository
import (
"context"
"fmt"
"testing"
"time"
"code.30cm.net/digimon/app-cloudep-product-service/pkg/domain/entity"
"code.30cm.net/digimon/app-cloudep-product-service/pkg/domain/product"
"code.30cm.net/digimon/app-cloudep-product-service/pkg/domain/repository"
mgo "code.30cm.net/digimon/library-go/mongo"
"github.com/alicebob/miniredis/v2"
"github.com/shopspring/decimal"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/zeromicro/go-zero/core/stores/cache"
"github.com/zeromicro/go-zero/core/stores/mon"
"github.com/zeromicro/go-zero/core/stores/redis"
"go.mongodb.org/mongo-driver/bson/primitive"
"google.golang.org/protobuf/proto"
)
func SetupTestProductItemRepository(db string) (repository.ProductItemRepository, func(), error) {
h, p, tearDown, err := startMongoContainer()
if err != nil {
return nil, nil, err
}
s, _ := miniredis.Run()
conf := &mgo.Conf{
Schema: Schema,
Host: fmt.Sprintf("%s:%s", h, p),
Database: db,
MaxStaleness: 300,
MaxPoolSize: 100,
MinPoolSize: 100,
MaxConnIdleTime: 300,
Compressors: []string{},
EnableStandardReadWriteSplitMode: false,
ConnectTimeoutMs: 3000,
}
cacheConf := cache.CacheConf{
cache.NodeConf{
RedisConf: redis.RedisConf{
Host: s.Addr(),
Type: redis.NodeType,
},
Weight: 100,
},
}
cacheOpts := []cache.Option{
cache.WithExpiry(1000 * time.Microsecond),
cache.WithNotFoundExpiry(1000 * time.Microsecond),
}
param := ProductItemRepositoryParam{
Conf: conf,
CacheConf: cacheConf,
CacheOpts: cacheOpts,
DBOpts: []mon.Option{
mgo.SetCustomDecimalType(),
mgo.InitMongoOptions(*conf),
},
}
repo := NewProductItemRepository(param)
_, _ = repo.Index20250317001UP(context.Background())
return repo, tearDown, nil
}
func TestInsertProductItems(t *testing.T) {
model, tearDown, err := SetupTestProductItemRepository("testDB")
defer tearDown()
assert.NoError(t, err)
// 建立多筆 items
items := []entity.ProductItems{
{
ReferenceID: primitive.NewObjectID().Hex(),
Name: "Item A",
},
{
ReferenceID: primitive.NewObjectID().Hex(),
Name: "Item B",
},
}
// 呼叫插入
err = model.Insert(context.Background(), items)
assert.NoError(t, err)
// 驗證插入是否成功(逐筆查詢)
for _, item := range items {
// 檢查 ID 是否自動填入
assert.False(t, item.ID.IsZero(), "ID should be generated")
assert.NotZero(t, item.CreatedAt)
assert.NotZero(t, item.UpdatedAt)
// 查詢 DB 確認存在
var result *entity.ProductItems
result, err = model.FindByID(context.Background(), item.ID.Hex())
assert.NoError(t, err)
assert.Equal(t, item.Name, result.Name)
}
// 🧪 空陣列插入測試(不應報錯)
t.Run("Insert empty slice", func(t *testing.T) {
err := model.Insert(context.Background(), []entity.ProductItems{})
assert.NoError(t, err)
})
}
func TestDeleteProductItem(t *testing.T) {
model, tearDown, err := SetupTestProductItemRepository("testDB")
defer tearDown()
assert.NoError(t, err)
// 建立多筆 items
items := []entity.ProductItems{
{
ReferenceID: primitive.NewObjectID().Hex(),
Name: "Item A",
},
{
ReferenceID: primitive.NewObjectID().Hex(),
Name: "Item B",
},
}
// 呼叫插入
err = model.Insert(context.Background(), items)
assert.NoError(t, err)
tests := []struct {
name string
inputID string
expectErr error
checkAfter bool // 是否需要確認資料已刪除
}{
{
name: "Delete existing product",
inputID: items[0].ID.Hex(),
expectErr: nil,
checkAfter: true,
},
{
name: "Delete non-existing product",
inputID: primitive.NewObjectID().Hex(),
expectErr: nil,
checkAfter: false,
},
{
name: "Invalid ObjectID format",
inputID: "not-an-object-id",
expectErr: ErrInvalidObjectID,
checkAfter: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err := model.Delete(context.Background(), []string{tt.inputID})
if tt.expectErr != nil {
assert.ErrorIs(t, err, tt.expectErr)
} else {
assert.NoError(t, err)
}
// 驗證資料是否真的刪除了(僅當需要)
if tt.checkAfter {
_, err := model.FindByID(context.Background(), tt.inputID)
assert.ErrorIs(t, err, ErrNotFound)
}
})
}
}
func TestListProductItem(t *testing.T) {
model, tearDown, err := SetupTestProductItemRepository("testDB")
defer tearDown()
assert.NoError(t, err)
now := time.Now().UnixNano()
rfcID := primitive.NewObjectID().Hex()
// 插入測試資料
item1 := entity.ProductItems{
ID: primitive.NewObjectID(),
ReferenceID: rfcID,
Name: "Item A",
IsFree: true,
Status: product.StatusActive,
CreatedAt: now,
UpdatedAt: now,
}
item2 := entity.ProductItems{
ID: primitive.NewObjectID(),
ReferenceID: rfcID,
Name: "Item B",
IsFree: false,
Status: product.StatusInactive,
CreatedAt: now,
UpdatedAt: now,
}
item3 := entity.ProductItems{
ID: primitive.NewObjectID(),
ReferenceID: rfcID,
Name: "Item C",
IsFree: true,
Status: product.StatusActive,
CreatedAt: now,
UpdatedAt: now,
}
err = model.Insert(context.Background(), []entity.ProductItems{item1, item2, item3})
assert.NoError(t, err)
tests := []struct {
name string
params repository.ProductItemQueryParams
expectCount int64
expectIDs []primitive.ObjectID
}{
{
name: "Filter by ReferenceID",
params: repository.ProductItemQueryParams{
ReferenceID: ptr(rfcID),
PageSize: 10,
PageIndex: 1,
},
expectCount: 3,
expectIDs: []primitive.ObjectID{item3.ID, item2.ID, item1.ID},
},
{
name: "Filter by IsFree = true",
params: repository.ProductItemQueryParams{
IsFree: ptr(true),
PageSize: 10,
PageIndex: 1,
},
expectCount: 2,
expectIDs: []primitive.ObjectID{item3.ID, item1.ID},
},
{
name: "Filter by Status = 2",
params: repository.ProductItemQueryParams{
Status: ptr(product.StatusInactive),
PageSize: 10,
PageIndex: 1,
},
expectCount: 1,
expectIDs: []primitive.ObjectID{item2.ID},
},
{
name: "Filter by ItemIDs",
params: repository.ProductItemQueryParams{
ItemID: []string{item1.ID.Hex(), item2.ID.Hex()},
PageSize: 10,
PageIndex: 1,
},
expectCount: 2,
expectIDs: []primitive.ObjectID{item2.ID, item1.ID},
},
{
name: "Pagination works",
params: repository.ProductItemQueryParams{
PageSize: 1,
PageIndex: 2,
},
expectCount: 3,
expectIDs: []primitive.ObjectID{item2.ID},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
results, count, err := model.ListProductItem(context.Background(), tt.params)
assert.NoError(t, err)
assert.Equal(t, tt.expectCount, count)
var gotIDs []primitive.ObjectID
for _, r := range results {
gotIDs = append(gotIDs, r.ID)
}
assert.ElementsMatch(t, tt.expectIDs, gotIDs)
})
}
}
func TestDeleteByReferenceID(t *testing.T) {
model, tearDown, err := SetupTestProductItemRepository("testDB")
defer tearDown()
assert.NoError(t, err)
ctx := context.Background()
now := time.Now().UnixNano()
// 插入測試資料
refID := primitive.NewObjectID().Hex()
item1 := entity.ProductItems{
ID: primitive.NewObjectID(),
ReferenceID: refID,
Name: "Item A",
CreatedAt: now,
UpdatedAt: now,
}
item2 := entity.ProductItems{
ID: primitive.NewObjectID(),
ReferenceID: refID,
Name: "Item B",
CreatedAt: now,
UpdatedAt: now,
}
itemOther := entity.ProductItems{
ID: primitive.NewObjectID(),
ReferenceID: primitive.NewObjectID().Hex(),
Name: "Should not be deleted",
CreatedAt: now,
UpdatedAt: now,
}
err = model.Insert(ctx, []entity.ProductItems{item1, item2, itemOther})
assert.NoError(t, err)
tests := []struct {
name string
referenceID string
expectDeleted []primitive.ObjectID
expectRemained []primitive.ObjectID
}{
{
name: "Delete existing reference_id items",
referenceID: refID,
expectDeleted: []primitive.ObjectID{item1.ID, item2.ID},
expectRemained: []primitive.ObjectID{itemOther.ID},
},
{
name: "Delete non-existent reference_id",
referenceID: "no-match-ref",
expectDeleted: nil,
expectRemained: []primitive.ObjectID{itemOther.ID},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err := model.DeleteByReferenceID(ctx, tt.referenceID)
assert.NoError(t, err)
// 檢查指定應被刪除的項目是否真的被刪除
for _, id := range tt.expectDeleted {
_, err = model.FindByID(ctx, id.Hex())
assert.Error(t, err, "Expected item to be deleted: "+id.Hex())
}
// 檢查不應刪除的項目仍存在
for _, id := range tt.expectRemained {
_, err = model.FindByID(ctx, id.Hex())
assert.NoError(t, err, "Expected item to remain: "+id.Hex())
}
})
}
}
func TestUpdateProductItem(t *testing.T) {
model, tearDown, err := SetupTestProductItemRepository("testDB")
defer tearDown()
assert.NoError(t, err)
ctx := context.Background()
// 建立一筆測試資料
item := entity.ProductItems{
ID: primitive.NewObjectID(),
ReferenceID: primitive.NewObjectID().Hex(),
Name: "Original Name",
Stock: 10,
Price: decimal.NewFromInt(500),
IsUnLimit: false,
IsFree: false,
CreatedAt: time.Now().UnixNano(),
UpdatedAt: time.Now().UnixNano(),
}
err = model.Insert(ctx, []entity.ProductItems{item})
assert.NoError(t, err)
tests := []struct {
name string
id string
update *repository.ProductUpdateItem
expectErr error
validate func(t *testing.T, updated *entity.ProductItems)
}{
{
name: "Update is_un_limit and is_free",
id: item.ID.Hex(),
update: &repository.ProductUpdateItem{
IsUnLimit: ptr(true),
IsFree: ptr(true),
},
expectErr: nil,
validate: func(t *testing.T, updated *entity.ProductItems) {
assert.True(t, updated.IsUnLimit)
assert.True(t, updated.IsFree)
},
},
{
name: "Update SKU and TimeSeries",
id: item.ID.Hex(),
update: &repository.ProductUpdateItem{
SKU: ptr("SKU-XYZ-001"),
TimeSeries: ptr(product.TimeSeriesTenMinutes),
},
expectErr: nil,
validate: func(t *testing.T, updated *entity.ProductItems) {
assert.Equal(t, "SKU-XYZ-001", updated.SKU)
assert.Equal(t, product.TimeSeriesTenMinutes, updated.TimeSeries)
},
},
{
name: "Update Media field",
id: item.ID.Hex(),
update: &repository.ProductUpdateItem{
Media: []entity.Media{
{Sort: 1, Type: "image", URL: "https://example.com/img.jpg"},
},
},
expectErr: nil,
validate: func(t *testing.T, updated *entity.ProductItems) {
assert.Len(t, updated.Media, 1)
assert.Equal(t, "image", updated.Media[0].Type)
},
},
{
name: "Update CustomFields",
id: item.ID.Hex(),
update: &repository.ProductUpdateItem{
CustomFields: []entity.CustomFields{
{Key: "color", Value: "red"},
},
},
expectErr: nil,
validate: func(t *testing.T, updated *entity.ProductItems) {
assert.Len(t, updated.CustomFields, 1)
assert.Equal(t, "color", updated.CustomFields[0].Key)
},
},
{
name: "Update Freight",
id: item.ID.Hex(),
update: &repository.ProductUpdateItem{
Freight: []entity.CustomFields{
{
Key: "color",
Value: "red",
},
},
},
expectErr: nil,
validate: func(t *testing.T, updated *entity.ProductItems) {
assert.Equal(t, "color", updated.Freight[0].Key)
assert.Equal(t, "red", updated.Freight[0].Value)
},
},
{
name: "Update name field",
id: item.ID.Hex(),
update: &repository.ProductUpdateItem{
Name: ptr("Updated Name"),
},
expectErr: nil,
validate: func(t *testing.T, updated *entity.ProductItems) {
assert.Equal(t, "Updated Name", updated.Name)
},
},
{
name: "Update stock and price",
id: item.ID.Hex(),
update: &repository.ProductUpdateItem{
Stock: proto.Int64(99),
Price: ptr(decimal.NewFromInt(999)),
},
expectErr: nil,
validate: func(t *testing.T, updated *entity.ProductItems) {
assert.Equal(t, uint64(99), updated.Stock)
assert.Equal(t, "999", updated.Price.String())
},
},
{
name: "Invalid ObjectID",
id: "not-an-id",
update: &repository.ProductUpdateItem{Name: ptr("Invalid")},
expectErr: ErrInvalidObjectID,
},
{
name: "Empty update struct",
id: item.ID.Hex(),
update: &repository.ProductUpdateItem{}, // no fields
expectErr: fmt.Errorf("no fields to update"),
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err := model.Update(ctx, tt.id, tt.update)
if tt.expectErr != nil {
assert.Error(t, err)
assert.Contains(t, err.Error(), tt.expectErr.Error())
return
}
assert.NoError(t, err)
updated, err := model.FindByID(ctx, item.ID.Hex())
assert.NoError(t, err)
if tt.validate != nil {
tt.validate(t, updated)
}
})
}
}
func TestUpdateStatus_TableDriven(t *testing.T) {
repo, tearDown, err := SetupTestProductItemRepository("testDB")
require.NoError(t, err)
defer tearDown()
ctx := context.Background()
// Insert a sample product item.
now := time.Now().UnixNano()
item := entity.ProductItems{
ID: primitive.NewObjectID(),
ReferenceID: primitive.NewObjectID().Hex(),
Name: "Test Item",
Status: product.StatusInactive, // initial status
CreatedAt: now,
UpdatedAt: now,
}
err = repo.Insert(ctx, []entity.ProductItems{item})
require.NoError(t, err)
tests := []struct {
name string
id string
newStatus product.ItemStatus
expectErr error
check bool // whether to verify the update in DB
}{
{
name: "Valid update",
id: item.ID.Hex(),
newStatus: product.StatusActive,
expectErr: nil,
check: true,
},
{
name: "Invalid ObjectID",
id: "invalid-id",
newStatus: product.StatusActive,
expectErr: ErrInvalidObjectID,
check: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err := repo.UpdateStatus(ctx, tt.id, tt.newStatus)
if tt.expectErr != nil {
assert.ErrorIs(t, err, tt.expectErr)
} else {
assert.NoError(t, err)
}
// If expected to check update, verify that the product's status is updated.
if tt.check {
updated, err := repo.FindByID(ctx, tt.id)
assert.NoError(t, err)
assert.Equal(t, tt.newStatus, updated.Status)
}
})
}
}
func TestIncSalesCount(t *testing.T) {
repo, tearDown, err := SetupTestProductItemRepository("testDB")
require.NoError(t, err)
defer tearDown()
ctx := context.Background()
// Insert a sample product item with initial SalesCount = 0.
now := time.Now().UnixNano()
item := entity.ProductItems{
ID: primitive.NewObjectID(),
ReferenceID: primitive.NewObjectID().Hex(),
Name: "Sales Count Test Item",
SalesCount: 0,
CreatedAt: now,
UpdatedAt: now,
}
err = repo.Insert(ctx, []entity.ProductItems{item})
require.NoError(t, err)
tests := []struct {
name string
id string
count int64
expectErr error
check bool // whether to verify the updated sales count in the DB.
}{
{
name: "Increment sales count by 5",
id: item.ID.Hex(),
count: 5,
expectErr: nil,
check: true,
},
{
name: "Invalid ObjectID",
id: "invalid-id",
count: 3,
expectErr: ErrInvalidObjectID,
check: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err := repo.IncSalesCount(ctx, tt.id, tt.count)
if tt.expectErr != nil {
assert.ErrorIs(t, err, tt.expectErr)
} else {
assert.NoError(t, err)
}
// If check is true, verify that the sales_count is updated correctly.
if tt.check {
updated, err := repo.FindByID(ctx, tt.id)
assert.NoError(t, err)
// Since initial SalesCount was 0, after increment it should equal tt.count.
assert.Equal(t, uint64(tt.count), updated.SalesCount)
}
})
}
}
func TestDecSalesCount(t *testing.T) {
repo, tearDown, err := SetupTestProductItemRepository("testDB")
require.NoError(t, err)
defer tearDown()
ctx := context.Background()
now := time.Now().UnixNano()
// Insert an item with an initial SalesCount of 10.
item := entity.ProductItems{
ID: primitive.NewObjectID(),
ReferenceID: primitive.NewObjectID().Hex(),
Name: "Dec Sales Count Test Item",
SalesCount: 10,
CreatedAt: now,
UpdatedAt: now,
}
// Insert an item with SalesCount equal to 0 (to test underflow behavior).
zeroItem := entity.ProductItems{
ID: primitive.NewObjectID(),
ReferenceID: primitive.NewObjectID().Hex(),
Name: "Zero Sales Count Item",
SalesCount: 0,
CreatedAt: now,
UpdatedAt: now,
}
err = repo.Insert(ctx, []entity.ProductItems{item, zeroItem})
require.NoError(t, err)
tests := []struct {
name string
id string
decCount int64
expectErr error
check bool // whether to verify the updated sales count in the DB
expected uint64 // expected SalesCount if check is true
}{
{
name: "Valid decrement from 10",
id: item.ID.Hex(),
decCount: 3,
expectErr: nil,
check: true,
expected: 7,
},
{
name: "Invalid ObjectID",
id: "invalid-id",
decCount: 3,
expectErr: ErrInvalidObjectID,
check: false,
},
{
name: "Decrement from zero (should not allow underflow)",
id: zeroItem.ID.Hex(),
decCount: 5,
expectErr: nil,
check: true,
expected: 0,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err := repo.DecSalesCount(ctx, tt.id, tt.decCount)
if tt.expectErr != nil {
assert.Error(t, err)
// Check that the error message contains "failed to decrease stock" (for the underflow case)
if tt.expectErr.Error() != ErrInvalidObjectID.Error() {
assert.Contains(t, err.Error(), "failed to decrease stock")
} else {
assert.ErrorIs(t, err, tt.expectErr)
}
} else {
assert.NoError(t, err)
}
// If expected to check, verify the updated SalesCount.
if tt.check {
updated, err := repo.FindByID(ctx, tt.id)
assert.NoError(t, err)
assert.Equal(t, tt.expected, updated.SalesCount)
}
})
}
}
func TestGetSalesCount(t *testing.T) {
// 取得測試 repository
repo, tearDown, err := SetupTestProductItemRepository("testDB")
require.NoError(t, err)
defer tearDown()
ctx := context.Background()
// 預先建立測試資料,設定各項目的 SalesCount 值
testItems := []entity.ProductItems{
{
ReferenceID: primitive.NewObjectID().Hex(),
Name: "Test Item 1",
SalesCount: 5,
},
{
ReferenceID: primitive.NewObjectID().Hex(),
Name: "Test Item 2",
SalesCount: 15,
},
}
// 插入測試資料
err = repo.Insert(ctx, testItems)
require.NoError(t, err)
// 建立一組包含有效與無效 ID 的字串陣列
validIDs := []string{
testItems[0].ID.Hex(),
testItems[1].ID.Hex(),
}
// 在陣列前面加上一個無法轉換的 ID
mixedIDs := append([]string{"invalidID"}, validIDs...)
t.Run("with valid and invalid IDs", func(t *testing.T) {
salesCounts, err := repo.GetSalesCount(ctx, mixedIDs)
require.NoError(t, err)
// 預期只會回傳有效的兩筆資料
require.Len(t, salesCounts, len(validIDs))
// 驗證每筆資料的 SalesCount 是否正確
for _, sc := range salesCounts {
switch sc.ID {
case testItems[0].ID.Hex():
assert.Equal(t, uint64(5), sc.Count)
case testItems[1].ID.Hex():
assert.Equal(t, uint64(15), sc.Count)
default:
t.Errorf("unexpected product item ID: %s", sc.ID)
}
}
})
t.Run("with all invalid IDs", func(t *testing.T) {
salesCounts, err := repo.GetSalesCount(ctx, []string{"badid1", "badid2"})
// 因無法轉換成 ObjectID預期會回傳 ErrInvalidObjectID
assert.Error(t, err)
assert.Equal(t, ErrInvalidObjectID, err)
assert.Nil(t, salesCounts)
})
}

View File

@ -0,0 +1,308 @@
package repository
import (
"context"
"errors"
"fmt"
"math"
"time"
"code.30cm.net/digimon/app-cloudep-product-service/pkg/domain"
"code.30cm.net/digimon/app-cloudep-product-service/pkg/domain/entity"
"code.30cm.net/digimon/app-cloudep-product-service/pkg/domain/repository"
mgo "code.30cm.net/digimon/library-go/mongo"
"github.com/zeromicro/go-zero/core/stores/cache"
"github.com/zeromicro/go-zero/core/stores/mon"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/bson/primitive"
"go.mongodb.org/mongo-driver/mongo"
"go.mongodb.org/mongo-driver/mongo/options"
)
type ProductStatisticsRepositoryParam struct {
Conf *mgo.Conf
CacheConf cache.CacheConf
DBOpts []mon.Option
CacheOpts []cache.Option
}
type ProductStatisticsRepository struct {
DB mgo.DocumentDBWithCacheUseCase
}
func NewProductStatisticsRepository(param ProductStatisticsRepositoryParam) repository.ProductStatisticsRepo {
e := entity.ProductStatistics{}
documentDB, err := mgo.MustDocumentDBWithCache(
param.Conf,
e.CollectionName(),
param.CacheConf,
param.DBOpts,
param.CacheOpts,
)
if err != nil {
panic(err)
}
return &ProductStatisticsRepository{
DB: documentDB,
}
}
func (repo *ProductStatisticsRepository) Create(ctx context.Context, data *entity.ProductStatistics) error {
if data.ID.IsZero() {
now := time.Now().UTC().UnixNano()
data.ID = primitive.NewObjectID()
data.CreatedAt = now
data.UpdatedAt = now
}
rk := domain.GetProductStatisticsRK(data.ID.Hex())
_, err := repo.DB.InsertOne(ctx, rk, data)
productKey := domain.GetProductStatisticsRK(data.ProductID)
_ = repo.DB.SetCache(productKey, data)
return err
}
func (repo *ProductStatisticsRepository) GetByID(ctx context.Context, id string) (*entity.ProductStatistics, error) {
oid, err := primitive.ObjectIDFromHex(id)
if err != nil {
return nil, err
}
var result *entity.ProductStatistics
err = repo.DB.FindOne(ctx, domain.GetProductStatisticsRK(id), &result, bson.M{"_id": oid})
switch {
case err == nil:
return result, nil
case errors.Is(err, mon.ErrNotFound):
return nil, ErrNotFound
default:
return nil, err
}
}
func (repo *ProductStatisticsRepository) GetByProductID(ctx context.Context, productID string) (*entity.ProductStatistics, error) {
var result *entity.ProductStatistics
err := repo.DB.FindOne(ctx, domain.GetProductStatisticsRK(productID), &result, bson.M{"product_id": productID})
switch {
case err == nil:
return result, nil
case errors.Is(err, mon.ErrNotFound):
return nil, ErrNotFound
default:
return nil, err
}
}
func (repo *ProductStatisticsRepository) IncOrders(ctx context.Context, productID string, count int64) error {
filter := bson.M{"product_id": productID}
now := time.Now().UTC().UnixNano()
update := bson.M{
"$inc": bson.M{"total_orders": count},
"$set": bson.M{
"total_orders_update_time": now,
"updated_at": now,
},
}
rk := domain.GetProductStatisticsRK(productID)
_, err := repo.DB.UpdateOne(ctx, rk, filter, update)
if err != nil {
return fmt.Errorf("failed to decrease stock: %w", err)
}
id, err := repo.getIDByProductID(ctx, productID)
if err != nil {
return fmt.Errorf("failed to get product_id by id: %w", err)
}
repo.clearCache(ctx, id, productID)
return nil
}
func (repo *ProductStatisticsRepository) DecOrders(ctx context.Context, productID string, count int64) error {
filter := bson.M{"product_id": productID, "total_orders": bson.M{"$gte": count}}
now := time.Now().UTC().UnixNano()
update := bson.M{
"$inc": bson.M{"total_orders": -count},
"$set": bson.M{
"total_orders_update_time": now,
"updated_at": now,
},
}
rk := domain.GetProductStatisticsRK(productID)
_, err := repo.DB.UpdateOne(ctx, rk, filter, update)
if err != nil {
return fmt.Errorf("failed to decrease stock: %w", err)
}
id, err := repo.getIDByProductID(ctx, productID)
if err != nil {
return fmt.Errorf("failed to get product_id by id: %w", err)
}
repo.clearCache(ctx, id, productID)
return nil
}
func (repo *ProductStatisticsRepository) UpdateAverageRating(ctx context.Context, productID string, averageRating float64) error {
filter := bson.M{"product_id": productID}
now := time.Now().UTC().UnixNano()
update := bson.M{
"$set": bson.M{
"average_rating": averageRating,
"average_rating_time": now,
"updated_at": now,
},
}
_, err := repo.DB.UpdateOne(ctx, domain.GetProductStatisticsRK(productID), filter, update)
if err != nil {
return err
}
id, err := repo.getIDByProductID(ctx, productID)
if err != nil {
return fmt.Errorf("failed to get product_id by id: %w", err)
}
repo.clearCache(ctx, id, productID)
return nil
}
func (repo *ProductStatisticsRepository) IncFansCount(ctx context.Context, productID string, fansCount uint64) error {
filter := bson.M{"product_id": productID}
now := time.Now().UTC().UnixNano()
update := bson.M{
"$inc": bson.M{"fans_count": fansCount},
"$set": bson.M{
"fans_count_update_time": now,
"updated_at": now,
},
}
rk := domain.GetProductStatisticsRK(productID)
_, err := repo.DB.UpdateOne(ctx, rk, filter, update)
if err != nil {
return fmt.Errorf("failed to increment fans count: %w", err)
}
id, err := repo.getIDByProductID(ctx, productID)
if err != nil {
return fmt.Errorf("failed to get product_id by id: %w", err)
}
repo.clearCache(ctx, id, productID)
return nil
}
func (repo *ProductStatisticsRepository) DecFansCount(ctx context.Context, productID string, fansCount uint64) error {
// 只允許在 fans_count 大於或等於欲扣減值時進行扣減
filter := bson.M{"product_id": productID, "fans_count": bson.M{"$gte": fansCount}}
now := time.Now().UTC().UnixNano()
// 在更新之前先檢查 fansCount 是否過大
if fansCount > uint64(math.MaxInt64) {
return fmt.Errorf("fansCount value too large: %d", fansCount)
}
delta := -int64(fansCount)
update := bson.M{
"$inc": bson.M{"fans_count": delta},
"$set": bson.M{
"fans_count_update_time": now,
"updated_at": now,
},
}
rk := domain.GetProductStatisticsRK(productID)
_, err := repo.DB.UpdateOne(ctx, rk, filter, update)
if err != nil {
return fmt.Errorf("failed to decrement fans count: %w", err)
}
id, err := repo.getIDByProductID(ctx, productID)
if err != nil {
return fmt.Errorf("failed to get product_id by id: %w", err)
}
repo.clearCache(ctx, id, productID)
return nil
}
func (repo *ProductStatisticsRepository) Delete(ctx context.Context, id string) error {
oid, err := primitive.ObjectIDFromHex(id)
if err != nil {
return ErrInvalidObjectID
}
productID, err := repo.getProductIDByID(ctx, id)
if err != nil {
return fmt.Errorf("failed to get product_id by id: %w", err)
}
filter := bson.M{"_id": oid}
_, err = repo.DB.DeleteOne(ctx, domain.GetProductStatisticsRK(id), filter)
if err != nil {
return err
}
repo.clearCache(ctx, id, productID)
return nil
}
func (repo *ProductStatisticsRepository) Index20250317001UP(ctx context.Context) (*mongo.Cursor, error) {
// 等價於 db.account.createIndex({"product_id": 1})
repo.DB.PopulateIndex(ctx, "product_id", 1, true)
return repo.DB.GetClient().Indexes().List(ctx)
}
// 快取輔助函數
// clearCache 同時刪除 product_id 與 _id 兩個 cache key
func (repo *ProductStatisticsRepository) clearCache(ctx context.Context, id, productID string) {
keys := []string{
domain.GetProductStatisticsRK(productID),
domain.GetProductStatisticsRK(id),
}
for _, key := range keys {
_ = repo.DB.DelCache(ctx, key)
}
}
func (repo *ProductStatisticsRepository) getIDByProductID(ctx context.Context, productID string) (string, error) {
filter := bson.M{"product_id": productID}
var e entity.ProductStatistics
projection := bson.M{"_id": 1}
opts := options.FindOne().SetProjection(projection)
err := repo.DB.GetClient().FindOne(ctx, &e, filter, opts)
if err != nil {
return "", fmt.Errorf("failed to set projection: %w", err)
}
return e.ID.Hex(), nil
}
func (repo *ProductStatisticsRepository) getProductIDByID(ctx context.Context, id string) (string, error) {
oid, err := primitive.ObjectIDFromHex(id)
if err != nil {
return "", err
}
filter := bson.M{"_id": oid}
var e entity.ProductStatistics
projection := bson.M{"product_id": 1}
opts := options.FindOne().SetProjection(projection)
err = repo.DB.GetClient().FindOne(ctx, &e, filter, opts)
if err != nil {
return "", fmt.Errorf("failed to set projection: %w", err)
}
return e.ProductID, nil
}

View File

@ -0,0 +1,563 @@
package repository
import (
"context"
"fmt"
"testing"
"time"
"code.30cm.net/digimon/app-cloudep-product-service/pkg/domain/entity"
"code.30cm.net/digimon/app-cloudep-product-service/pkg/domain/repository"
mgo "code.30cm.net/digimon/library-go/mongo"
"github.com/alicebob/miniredis/v2"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/zeromicro/go-zero/core/stores/cache"
"github.com/zeromicro/go-zero/core/stores/mon"
"github.com/zeromicro/go-zero/core/stores/redis"
)
func SetupTestProductStatisticsRepo(db string) (repository.ProductStatisticsRepo, func(), error) {
h, p, tearDown, err := startMongoContainer()
if err != nil {
return nil, nil, err
}
s, _ := miniredis.Run()
conf := &mgo.Conf{
Schema: Schema,
Host: fmt.Sprintf("%s:%s", h, p),
Database: db,
MaxStaleness: 300,
MaxPoolSize: 100,
MinPoolSize: 100,
MaxConnIdleTime: 300,
Compressors: []string{},
EnableStandardReadWriteSplitMode: false,
ConnectTimeoutMs: 3000,
}
cacheConf := cache.CacheConf{
cache.NodeConf{
RedisConf: redis.RedisConf{
Host: s.Addr(),
Type: redis.NodeType,
},
Weight: 100,
},
}
cacheOpts := []cache.Option{
cache.WithExpiry(1000 * time.Microsecond),
cache.WithNotFoundExpiry(1000 * time.Microsecond),
}
param := ProductStatisticsRepositoryParam{
Conf: conf,
CacheConf: cacheConf,
CacheOpts: cacheOpts,
DBOpts: []mon.Option{
mgo.SetCustomDecimalType(),
mgo.InitMongoOptions(*conf),
},
}
repo := NewProductStatisticsRepository(param)
_, _ = repo.Index20250317001UP(context.Background())
return repo, tearDown, nil
}
func TestCreateProductStatistics(t *testing.T) {
// 假設有 SetupTestProductStatisticsRepository 可用來建立測試用的 ProductStatisticsRepository
repo, tearDown, err := SetupTestProductStatisticsRepo("testDB")
require.NoError(t, err)
defer tearDown()
ctx := context.Background()
// 定義多筆測試資料(不包含 ID、CreatedAt、UpdatedAt由 Create 自動填入)
statsList := []*entity.ProductStatistics{
{
ProductID: "prod-001",
Orders: 100,
AverageRating: 4.5,
FansCount: 200,
},
{
ProductID: "prod-002",
Orders: 50,
AverageRating: 3.8,
FansCount: 150,
},
}
// 逐筆呼叫 Create 新增資料
for _, ps := range statsList {
err := repo.Create(ctx, ps)
assert.NoError(t, err, "Create should not return error")
}
// 驗證每筆資料的自動欄位與內容
for _, ps := range statsList {
// 檢查 ID 與時間欄位是否有自動填入
assert.False(t, ps.ID.IsZero(), "ID should be generated")
assert.NotZero(t, ps.CreatedAt, "CreatedAt should be set")
assert.NotZero(t, ps.UpdatedAt, "UpdatedAt should be set")
// 查詢 DB 確認資料是否存在且欄位值正確
result, err := repo.GetByID(ctx, ps.ID.Hex())
assert.NoError(t, err, "GetByID should not return error")
assert.Equal(t, ps.ProductID, result.ProductID, "ProductID should match")
assert.Equal(t, ps.Orders, result.Orders, "Orders should match")
assert.Equal(t, ps.AverageRating, result.AverageRating, "AverageRating should match")
assert.Equal(t, ps.FansCount, result.FansCount, "FansCount should match")
}
}
func TestGetProductStatisticsByProductID(t *testing.T) {
repo, tearDown, err := SetupTestProductStatisticsRepo("testDB")
require.NoError(t, err)
defer tearDown()
ctx := context.Background()
stats1 := &entity.ProductStatistics{
ProductID: "prod-001",
Orders: 100,
AverageRating: 4.5,
FansCount: 200,
}
stats2 := &entity.ProductStatistics{
ProductID: "prod-002",
Orders: 50,
AverageRating: 3.8,
FansCount: 150,
}
err = repo.Create(ctx, stats1)
require.NoError(t, err)
err = repo.Create(ctx, stats2)
require.NoError(t, err)
// 定義 table-driven 測試案例
tests := []struct {
name string
inputProductID string
expectedErr error
expectedStatistics *entity.ProductStatistics
}{
{
name: "record exists - prod-001",
inputProductID: "prod-001",
expectedErr: nil,
expectedStatistics: stats1,
},
{
name: "record exists - prod-002",
inputProductID: "prod-002",
expectedErr: nil,
expectedStatistics: stats2,
},
{
name: "record not found",
inputProductID: "non-existent",
expectedErr: ErrNotFound,
},
}
for _, tt := range tests {
tt := tt // capture range variable
t.Run(tt.name, func(t *testing.T) {
result, err := repo.GetByProductID(ctx, tt.inputProductID)
if tt.expectedErr != nil {
require.Error(t, err)
assert.Equal(t, tt.expectedErr, err)
assert.Nil(t, result)
} else {
require.NoError(t, err)
// 驗證回傳的資料是否符合預期
assert.Equal(t, tt.expectedStatistics.ProductID, result.ProductID)
assert.Equal(t, tt.expectedStatistics.Orders, result.Orders)
assert.Equal(t, tt.expectedStatistics.AverageRating, result.AverageRating)
assert.Equal(t, tt.expectedStatistics.FansCount, result.FansCount)
// 其他自動產生欄位如 ID、CreatedAt、UpdatedAt 也可檢查非零
assert.False(t, result.ID.IsZero(), "ID should be generated")
assert.NotZero(t, result.CreatedAt, "CreatedAt should be set")
assert.NotZero(t, result.UpdatedAt, "UpdatedAt should be set")
}
})
}
}
func TestIncOrders(t *testing.T) {
repo, tearDown, err := SetupTestProductStatisticsRepo("testDB")
require.NoError(t, err)
defer tearDown()
ctx := context.Background()
// 插入一筆測試資料:初始 Orders 為 100
stats := &entity.ProductStatistics{
ProductID: "prod-001",
Orders: 100,
AverageRating: 4.5,
FansCount: 200,
}
err = repo.Create(ctx, stats)
require.NoError(t, err)
tests := []struct {
name string
productID string
increment int64
expectedOrders uint64 // 預期的 Orders 值
expectErr bool
}{
{
name: "Valid increment",
productID: "prod-001",
increment: 10,
expectedOrders: 110,
expectErr: false,
},
{
name: "Non-existent product",
productID: "prod-not-exist",
increment: 5,
// 當產品不存在時,應回傳錯誤,不檢查 Orders
expectErr: true,
},
}
for _, tt := range tests {
tt := tt // capture range variable
t.Run(tt.name, func(t *testing.T) {
err := repo.IncOrders(ctx, tt.productID, tt.increment)
if tt.expectErr {
require.Error(t, err)
// 可進一步檢查錯誤訊息中是否包含特定關鍵字
assert.Contains(t, err.Error(), "failed to get product_id by id")
} else {
require.NoError(t, err)
// 若成功,利用 GetByProductID 取得最新資料,檢查 Orders 是否正確更新
ps, err := repo.GetByProductID(ctx, tt.productID)
require.NoError(t, err)
assert.Equal(t, tt.expectedOrders, ps.Orders)
}
})
}
}
func TestDecOrders(t *testing.T) {
repo, tearDown, err := SetupTestProductStatisticsRepo("testDB")
require.NoError(t, err)
defer tearDown()
ctx := context.Background()
// 插入一筆測試資料,初始 Orders 為 100
stats := &entity.ProductStatistics{
ProductID: "prod-001",
Orders: 100,
AverageRating: 4.5,
FansCount: 200,
}
err = repo.Create(ctx, stats)
require.NoError(t, err)
tests := []struct {
name string
productID string
decrement int64
expectedOrders uint64 // 減少成功後預期的 Orders 值
expectErr bool
}{
{
name: "Valid decrease",
productID: "prod-001",
decrement: 30,
expectedOrders: 70, // 100 - 30
expectErr: false,
},
{
name: "Insufficient orders",
productID: "prod-001",
decrement: 150, // 超過現有數量
expectedOrders: 70, // 100 - 30
expectErr: false,
},
{
name: "Non-existent product",
productID: "prod-not-exist",
decrement: 10,
expectErr: true,
},
}
for _, tt := range tests {
tt := tt // capture range variable
t.Run(tt.name, func(t *testing.T) {
err := repo.DecOrders(ctx, tt.productID, tt.decrement)
if tt.expectErr {
require.Error(t, err)
} else {
require.NoError(t, err)
// 成功減少後,利用 GetByProductID 取得最新資料
updated, err := repo.GetByProductID(ctx, tt.productID)
require.NoError(t, err)
assert.Equal(t, tt.expectedOrders, updated.Orders)
}
})
}
}
func TestUpdateAverageRating(t *testing.T) {
repo, tearDown, err := SetupTestProductStatisticsRepo("testDB")
require.NoError(t, err)
defer tearDown()
ctx := context.Background()
// 插入一筆測試資料,初始 AverageRating 為 4.0
stats := &entity.ProductStatistics{
ProductID: "prod-001",
Orders: 100,
AverageRating: 4.0,
FansCount: 50,
}
err = repo.Create(ctx, stats)
require.NoError(t, err)
tests := []struct {
name string
productID string
newRating float64
expectErr bool
expectedValue float64 // 預期更新後的 AverageRating
}{
{
name: "Update existing product",
productID: "prod-001",
newRating: 4.8,
expectErr: false,
expectedValue: 4.8,
},
{
name: "Non-existent product",
productID: "prod-nonexist",
newRating: 3.5,
expectErr: true,
},
}
for _, tt := range tests {
tt := tt // capture range variable
t.Run(tt.name, func(t *testing.T) {
err := repo.UpdateAverageRating(ctx, tt.productID, tt.newRating)
if tt.expectErr {
require.Error(t, err)
} else {
require.NoError(t, err)
// 取得更新後的資料
updated, err := repo.GetByProductID(ctx, tt.productID)
require.NoError(t, err)
assert.Equal(t, tt.expectedValue, updated.AverageRating, "AverageRating should be updated")
// 驗證更新時間不為 0
assert.NotZero(t, updated.AverageRatingUpdateTime, "AverageRatingUpdateTime should be set")
assert.NotZero(t, updated.UpdatedAt, "UpdatedAt should be set")
}
})
}
}
func TestIncFansCount(t *testing.T) {
repo, tearDown, err := SetupTestProductStatisticsRepo("testDB")
require.NoError(t, err)
defer tearDown()
ctx := context.Background()
// 插入一筆測試資料,初始 FansCount 為 200
stats := &entity.ProductStatistics{
ProductID: "prod-001",
Orders: 100,
AverageRating: 4.0,
FansCount: 200,
}
err = repo.Create(ctx, stats)
require.NoError(t, err)
tests := []struct {
name string
productID string
incCount uint64
expectedCount uint64 // 預期更新後的 FansCount
expectErr bool
}{
{
name: "Valid increment",
productID: "prod-001",
incCount: 50,
expectedCount: 250, // 200 + 50
expectErr: false,
},
{
name: "Non-existent product",
productID: "non-existent",
incCount: 10,
expectErr: true,
},
}
for _, tt := range tests {
tt := tt // capture range variable
t.Run(tt.name, func(t *testing.T) {
err := repo.IncFansCount(ctx, tt.productID, tt.incCount)
if tt.expectErr {
require.Error(t, err)
} else {
require.NoError(t, err)
// 取得更新後的資料,驗證 FansCount 是否正確更新
updated, err := repo.GetByProductID(ctx, tt.productID)
require.NoError(t, err)
assert.Equal(t, tt.expectedCount, updated.FansCount, "FansCount should be incremented correctly")
assert.NotZero(t, updated.FansCountUpdateTime, "FansCountUpdateTime should be set")
assert.NotZero(t, updated.UpdatedAt, "UpdatedAt should be set")
}
})
}
}
func TestDecFansCount(t *testing.T) {
repo, tearDown, err := SetupTestProductStatisticsRepo("testDB")
require.NoError(t, err)
defer tearDown()
ctx := context.Background()
// 先建立兩筆測試資料
// 測試 valid case初始 FansCount 為 200
statsValid := &entity.ProductStatistics{
ProductID: "prod-valid",
Orders: 100,
AverageRating: 4.5,
FansCount: 200,
}
err = repo.Create(ctx, statsValid)
require.NoError(t, err)
// 測試 insufficient case初始 FansCount 為 30
statsInsufficient := &entity.ProductStatistics{
ProductID: "prod-insufficient",
Orders: 50,
AverageRating: 3.8,
FansCount: 30,
}
err = repo.Create(ctx, statsInsufficient)
require.NoError(t, err)
tests := []struct {
name string
productID string
decrement uint64
expectedFans uint64 // 預期更新後的 FansCount (僅 valid case)
expectErr bool
}{
{
name: "Valid decrement",
productID: "prod-valid",
decrement: 50,
expectedFans: 150, // 200 - 50
expectErr: false,
},
{
name: "Insufficient fans",
productID: "prod-insufficient",
decrement: 50,
expectedFans: 30, // 扣超過就跟原本一樣不會變
expectErr: false,
},
{
name: "Non-existent product",
productID: "prod-nonexistent",
decrement: 10,
expectErr: true,
},
}
for _, tt := range tests {
tt := tt // capture range variable
t.Run(tt.name, func(t *testing.T) {
err := repo.DecFansCount(ctx, tt.productID, tt.decrement)
if tt.expectErr {
require.Error(t, err)
} else {
require.NoError(t, err)
// 取得更新後的資料,驗證 FansCount 是否正確更新
updated, err := repo.GetByProductID(ctx, tt.productID)
require.NoError(t, err)
assert.Equal(t, tt.expectedFans, updated.FansCount, "FansCount should be decremented correctly")
}
})
}
}
func TestDeleteProductStatistics(t *testing.T) {
repo, tearDown, err := SetupTestProductStatisticsRepo("testDB")
require.NoError(t, err)
defer tearDown()
ctx := context.Background()
// 插入一筆測試資料
stats := &entity.ProductStatistics{
ProductID: "prod-001",
Orders: 100,
AverageRating: 4.5,
FansCount: 200,
}
err = repo.Create(ctx, stats)
require.NoError(t, err)
tests := []struct {
name string
id string
expectErr error // 預期錯誤
checkDelete bool // 若為 true刪除成功後進行資料查詢驗證
}{
{
name: "Delete existing record",
id: stats.ID.Hex(),
expectErr: nil,
checkDelete: true,
},
{
name: "Invalid ObjectID format",
id: "invalid-id",
expectErr: ErrInvalidObjectID,
checkDelete: false,
},
}
for _, tc := range tests {
tc := tc // capture range variable
t.Run(tc.name, func(t *testing.T) {
err := repo.Delete(ctx, tc.id)
if tc.expectErr != nil {
require.Error(t, err)
assert.Equal(t, tc.expectErr, err)
} else {
require.NoError(t, err)
if tc.checkDelete {
// 刪除成功後,透過 GetByID 應查無資料
_, err := repo.GetByID(ctx, tc.id)
require.Error(t, err)
assert.Equal(t, ErrNotFound, err)
}
}
})
}
}

View File

@ -0,0 +1,209 @@
package repository
import (
"context"
"errors"
"time"
"code.30cm.net/digimon/app-cloudep-product-service/pkg/domain"
"code.30cm.net/digimon/app-cloudep-product-service/pkg/domain/entity"
"code.30cm.net/digimon/app-cloudep-product-service/pkg/domain/repository"
mgo "code.30cm.net/digimon/library-go/mongo"
"github.com/zeromicro/go-zero/core/stores/cache"
"github.com/zeromicro/go-zero/core/stores/mon"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/bson/primitive"
"go.mongodb.org/mongo-driver/mongo"
"go.mongodb.org/mongo-driver/mongo/options"
)
type TagsRepositoryParam struct {
Conf *mgo.Conf
CacheConf cache.CacheConf
DBOpts []mon.Option
CacheOpts []cache.Option
}
type TagsRepository struct {
Tags mgo.DocumentDBWithCacheUseCase
TageBinding mgo.DocumentDBWithCacheUseCase
}
func NewTagsRepository(param TagsRepositoryParam) repository.TagRepo {
tags := entity.Tags{}
tagsDB, err := mgo.MustDocumentDBWithCache(
param.Conf,
tags.CollectionName(),
param.CacheConf,
param.DBOpts,
param.CacheOpts,
)
if err != nil {
panic(err)
}
tagBinding := entity.TagsBindingTable{}
bindingsDB, err := mgo.MustDocumentDBWithCache(
param.Conf,
tagBinding.CollectionName(),
param.CacheConf,
param.DBOpts,
param.CacheOpts,
)
if err != nil {
panic(err)
}
return &TagsRepository{
Tags: tagsDB,
TageBinding: bindingsDB,
}
}
func (repo *TagsRepository) Create(ctx context.Context, data *entity.Tags) error {
if data.ID.IsZero() {
now := time.Now().UTC().UnixNano()
data.ID = primitive.NewObjectID()
data.CreatedAt = now
data.UpdatedAt = now
}
rk := domain.GetTagsRK(data.ID.Hex())
_, err := repo.Tags.InsertOne(ctx, rk, data)
return err
}
func (repo *TagsRepository) GetByID(ctx context.Context, id string) (*entity.Tags, error) {
oid, err := primitive.ObjectIDFromHex(id)
if err != nil {
return nil, err
}
var result *entity.Tags
err = repo.Tags.FindOne(ctx, domain.GetTagsRK(id), &result, bson.M{"_id": oid})
switch {
case err == nil:
return result, nil
case errors.Is(err, mon.ErrNotFound):
return nil, ErrNotFound
default:
return nil, err
}
}
func (repo *TagsRepository) Update(ctx context.Context, id string, data repository.TagModifyParams) error {
now := time.Now().UTC().UnixNano()
// 動態構建更新內容
updateFields := bson.M{
"updated_at": now, // 確保 `updateAt` 總是更新
}
if data.Name != nil {
updateFields["name"] = *data.Name
}
if data.Types != nil {
updateFields["types"] = *data.Types
}
if data.ShowType != nil {
updateFields["show_type"] = *data.ShowType
}
if data.Cover != nil {
updateFields["cover"] = *data.Cover
}
oid, err := primitive.ObjectIDFromHex(id)
if err != nil {
return ErrInvalidObjectID
}
// 執行更新
rk := domain.GetTagsRK(id)
_, err = repo.Tags.UpdateOne(ctx, rk, bson.M{"_id": oid}, bson.M{"$set": updateFields})
if err != nil {
return err
}
return err
}
func (repo *TagsRepository) Delete(ctx context.Context, id string) error {
oid, err := primitive.ObjectIDFromHex(id)
if err != nil {
return ErrInvalidObjectID
}
filter := bson.M{"_id": oid}
_, err = repo.Tags.DeleteOne(ctx, domain.GetTagsRK(id), filter)
if err != nil {
return err
}
return nil
}
func (repo *TagsRepository) List(ctx context.Context, params repository.TagQueryParams) ([]*entity.Tags, int64, error) {
// 構建查詢過濾器
filter := bson.M{}
if params.Name != nil {
filter["name"] = *params.Name
}
if params.Types != nil {
filter["types"] = *params.Types
}
if params.ShowType != nil {
filter["show_type"] = *params.ShowType
}
// 設置排序選項
opts := options.Find().SetSkip((params.PageIndex - 1) * params.PageSize).SetLimit(params.PageSize)
opts.SetSort(bson.D{{Key: "updated_at", Value: -1}})
// 查詢符合條件的總數
count, err := repo.Tags.GetClient().CountDocuments(ctx, filter)
if err != nil {
return nil, 0, err
}
// 執行查詢並獲取結果
var tags []*entity.Tags
err = repo.Tags.GetClient().Find(ctx, &tags, filter, opts)
if err != nil {
return nil, 0, err
}
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)
repo.Tags.PopulateIndex(ctx, "types", 1, false)
repo.Tags.PopulateIndex(ctx, "name", 1, false)
return repo.Tags.GetClient().Indexes().List(ctx)
}

View File

@ -0,0 +1,103 @@
package repository
import (
"context"
"time"
"code.30cm.net/digimon/app-cloudep-product-service/pkg/domain/entity"
"code.30cm.net/digimon/app-cloudep-product-service/pkg/domain/repository"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/bson/primitive"
"go.mongodb.org/mongo-driver/mongo"
"go.mongodb.org/mongo-driver/mongo/options"
)
func (repo *TagsRepository) BindTags(ctx context.Context, data []*entity.TagsBindingTable) error {
now := time.Now().UTC().UnixNano()
docs := make([]any, 0, len(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
}
func (repo *TagsRepository) UnbindTag(ctx context.Context, tagID, referenceID string) error {
filter := bson.M{"tag_id": tagID, "reference_id": referenceID}
_, err := repo.TageBinding.GetClient().DeleteOne(ctx, filter)
if err != nil {
return err
}
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}
err := repo.TageBinding.GetClient().Find(ctx, &result, filter)
if err != nil {
return nil, err
}
return result, nil
}
func (repo *TagsRepository) ListTagBinding(ctx context.Context, params repository.TagBindingQueryParams) ([]*entity.TagsBindingTable, int64, error) {
// 構建查詢過濾器
filter := bson.M{}
if params.TagID != nil {
filter["tag_id"] = *params.TagID
}
if params.ReferenceID != nil {
filter["reference_id"] = *params.ReferenceID
}
// 設置排序選項
opts := options.Find().SetSkip((params.PageIndex - 1) * params.PageSize).SetLimit(params.PageSize)
opts.SetSort(bson.D{{Key: "updated_at", Value: -1}})
// 查詢符合條件的總數
count, err := repo.TageBinding.GetClient().CountDocuments(ctx, filter)
if err != nil {
return nil, 0, err
}
// 執行查詢並獲取結果
var tags []*entity.TagsBindingTable
err = repo.TageBinding.GetClient().Find(ctx, &tags, filter, opts)
if err != nil {
return nil, 0, err
}
return tags, count, nil
}
func (repo *TagsRepository) IndexTagsBinding20250317001UP(ctx context.Context) (*mongo.Cursor, error) {
repo.TageBinding.PopulateMultiIndex(ctx, []string{
"tag_id",
"reference_id",
}, []int32{1, 1}, true)
return repo.Tags.GetClient().Indexes().List(ctx)
}

View File

@ -0,0 +1,916 @@
package repository
import (
"context"
"errors"
"fmt"
"testing"
"time"
"code.30cm.net/digimon/app-cloudep-product-service/pkg/domain/entity"
"code.30cm.net/digimon/app-cloudep-product-service/pkg/domain/product"
"code.30cm.net/digimon/app-cloudep-product-service/pkg/domain/repository"
mgo "code.30cm.net/digimon/library-go/mongo"
"github.com/alicebob/miniredis/v2"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/zeromicro/go-zero/core/stores/cache"
"github.com/zeromicro/go-zero/core/stores/mon"
"github.com/zeromicro/go-zero/core/stores/redis"
"go.mongodb.org/mongo-driver/bson/primitive"
)
func SetupTestProductTagsRepo(db string) (repository.TagRepo, func(), error) {
h, p, tearDown, err := startMongoContainer()
if err != nil {
return nil, nil, err
}
s, _ := miniredis.Run()
conf := &mgo.Conf{
Schema: Schema,
Host: fmt.Sprintf("%s:%s", h, p),
Database: db,
MaxStaleness: 300,
MaxPoolSize: 100,
MinPoolSize: 100,
MaxConnIdleTime: 300,
Compressors: []string{},
EnableStandardReadWriteSplitMode: false,
ConnectTimeoutMs: 3000,
}
cacheConf := cache.CacheConf{
cache.NodeConf{
RedisConf: redis.RedisConf{
Host: s.Addr(),
Type: redis.NodeType,
},
Weight: 100,
},
}
cacheOpts := []cache.Option{
cache.WithExpiry(1000 * time.Microsecond),
cache.WithNotFoundExpiry(1000 * time.Microsecond),
}
param := TagsRepositoryParam{
Conf: conf,
CacheConf: cacheConf,
CacheOpts: cacheOpts,
DBOpts: []mon.Option{
mgo.SetCustomDecimalType(),
mgo.InitMongoOptions(*conf),
},
}
repo := NewTagsRepository(param)
_, _ = repo.IndexTags20250317001UP(context.Background())
_, _ = repo.IndexTagsBinding20250317001UP(context.Background())
return repo, tearDown, nil
}
func TestCreateTags(t *testing.T) {
repo, tearDown, err := SetupTestProductTagsRepo("testDB")
require.NoError(t, err)
defer tearDown()
ctx := context.Background()
tests := []struct {
name string
inputTag *entity.Tags
check func(t *testing.T, tag *entity.Tags)
expectErr bool
}{
{
name: "Insert tag with zero ID",
inputTag: &entity.Tags{
// ID 為零值Create 會自動生成
Types: product.ItemTypeSkill,
Name: "Tag A",
ShowType: product.ShowTypeNormal,
Cover: nil,
},
check: func(t *testing.T, tag *entity.Tags) {
require.False(t, tag.ID.IsZero(), "ID should be generated")
assert.NotZero(t, tag.CreatedAt, "CreatedAt should be set")
assert.NotZero(t, tag.UpdatedAt, "UpdatedAt should be set")
assert.Equal(t, "Tag A", tag.Name)
},
expectErr: false,
},
{
name: "Insert tag with preset ID",
inputTag: &entity.Tags{
ID: primitive.NewObjectID(), // 預先設定 ID不會被覆蓋
Types: product.ItemTypeSkill,
Name: "Tag B",
ShowType: product.ShowTypeNormal,
Cover: nil,
// 預設時間欄位
CreatedAt: 123456789,
UpdatedAt: 123456789,
},
check: func(t *testing.T, tag *entity.Tags) {
assert.False(t, tag.ID.IsZero())
assert.Equal(t, "Tag B", tag.Name)
// 保持原有的 CreatedAt 與 UpdatedAt 值
assert.Equal(t, int64(123456789), tag.CreatedAt)
assert.Equal(t, int64(123456789), tag.UpdatedAt)
},
expectErr: false,
},
}
for _, tc := range tests {
tc := tc // capture range variable
t.Run(tc.name, func(t *testing.T) {
err := repo.Create(ctx, tc.inputTag)
if tc.expectErr {
require.Error(t, err)
} else {
require.NoError(t, err)
// 驗證 Create 方法對輸入資料的影響
tc.check(t, tc.inputTag)
// 可選:利用 GetByID 再從資料庫中讀取進一步驗證
tagFromDB, err := repo.GetByID(ctx, tc.inputTag.ID.Hex())
require.NoError(t, err)
assert.Equal(t, tc.inputTag.Name, tagFromDB.Name)
assert.Equal(t, tc.inputTag.Types, tagFromDB.Types)
assert.Equal(t, tc.inputTag.ShowType, tagFromDB.ShowType)
}
})
}
}
func TestGetTagByID(t *testing.T) {
repo, tearDown, err := SetupTestProductTagsRepo("testDB")
require.NoError(t, err)
defer tearDown()
ctx := context.Background()
// 先建立一筆測試資料
tag := &entity.Tags{
Types: product.ItemTypeSkill,
Name: "Sample Tag",
ShowType: product.ShowTypeNormal,
Cover: nil,
}
err = repo.Create(ctx, tag)
require.NoError(t, err)
tests := []struct {
name string
inputID string
expectedName string
expectedErr error
}{
{
name: "Valid tag ID",
inputID: tag.ID.Hex(),
expectedName: "Sample Tag",
expectedErr: nil,
},
{
name: "Invalid ObjectID format",
inputID: "invalid-hex",
expectedName: "",
// 這裡預期錯誤內容會包含 "hex" 字樣,故使用 nil 來做後續判斷
expectedErr: errors.New("invalid"),
},
{
name: "Tag not found",
inputID: primitive.NewObjectID().Hex(),
expectedName: "",
expectedErr: ErrNotFound,
},
}
for _, tt := range tests {
tt := tt // capture range variable
t.Run(tt.name, func(t *testing.T) {
res, err := repo.GetByID(ctx, tt.inputID)
if tt.expectedErr != nil {
require.Error(t, err)
if tt.name == "Invalid ObjectID format" {
assert.Contains(t, err.Error(), "hex")
} else {
assert.Equal(t, tt.expectedErr, err)
}
assert.Nil(t, res)
} else {
require.NoError(t, err)
require.NotNil(t, res)
assert.Equal(t, tt.expectedName, res.Name)
}
})
}
}
func TestUpdateTag(t *testing.T) {
repo, tearDown, err := SetupTestProductTagsRepo("testDB")
require.NoError(t, err)
defer tearDown()
ctx := context.Background()
// 先建立一筆初始的 Tag 測試資料
origTag := &entity.Tags{
Types: product.ItemTypeSkill,
ShowType: product.ShowTypeNormal,
Name: "Original Name",
}
err = repo.Create(ctx, origTag)
require.NoError(t, err)
tests := []struct {
name string
id string
params repository.TagModifyParams
expectedName string
expectedTypes product.ItemType
expectedShowType product.ShowType
expectedCover *string
expectErr bool
}{
{
name: "Update all fields",
id: origTag.ID.Hex(),
params: repository.TagModifyParams{
Name: ptr("New Name"),
Types: ptr(product.ItemTypeSkill),
ShowType: ptr(product.ShowTypeNormal),
Cover: ptr("new-cover.jpg"),
},
expectedName: "New Name",
expectedTypes: product.ItemTypeSkill,
expectedShowType: product.ShowTypeNormal,
expectedCover: ptr("new-cover.jpg"),
expectErr: false,
},
{
name: "Update only name",
id: origTag.ID.Hex(),
params: repository.TagModifyParams{
Name: ptr("Another Name"),
},
// 其他欄位保持原值
expectedName: "Another Name",
expectedTypes: origTag.Types,
expectedShowType: origTag.ShowType,
expectedCover: ptr("new-cover.jpg"),
expectErr: false,
},
{
name: "Invalid ObjectID",
id: "invalid-id",
params: repository.TagModifyParams{
Name: ptr("Should Not Update"),
},
expectErr: true,
},
}
for _, tt := range tests {
tt := tt // capture range variable
t.Run(tt.name, func(t *testing.T) {
err := repo.Update(ctx, tt.id, tt.params)
if tt.expectErr {
require.Error(t, err)
} else {
require.NoError(t, err)
// 讀取更新後的資料
updated, err := repo.GetByID(ctx, tt.id)
require.NoError(t, err)
assert.Equal(t, tt.expectedName, updated.Name)
assert.Equal(t, tt.expectedTypes, updated.Types)
assert.Equal(t, tt.expectedShowType, updated.ShowType)
if tt.expectedCover == nil {
assert.Nil(t, updated.Cover)
} else {
assert.Equal(t, *tt.expectedCover, *updated.Cover)
}
// 驗證 updated_at 一定被更新(大於 0
assert.NotZero(t, updated.UpdatedAt)
}
})
}
}
func TestDeleteTag(t *testing.T) {
repo, tearDown, err := SetupTestProductTagsRepo("testDB")
require.NoError(t, err)
defer tearDown()
ctx := context.Background()
// 插入一筆測試資料
tag := &entity.Tags{
Types: product.ItemTypeSkill,
ShowType: product.ShowTypeNormal,
Name: "Test Tag",
// Cover 為 nil
}
err = repo.Create(ctx, tag)
require.NoError(t, err)
tests := []struct {
name string
id string
expectErr error // 預期的錯誤,若為 nil 表示預期成功
checkDelete bool // 若為 true表示刪除後需驗證資料不存在
}{
{
name: "Delete existing tag",
id: tag.ID.Hex(),
expectErr: nil,
checkDelete: true,
},
{
name: "Invalid ObjectID format",
id: "invalid-id",
expectErr: ErrInvalidObjectID,
checkDelete: false,
},
{
name: "Delete non-existent tag",
id: primitive.NewObjectID().Hex(),
expectErr: nil,
checkDelete: false,
},
}
for _, tt := range tests {
tt := tt // capture range variable
t.Run(tt.name, func(t *testing.T) {
err := repo.Delete(ctx, tt.id)
if tt.expectErr != nil {
require.Error(t, err)
assert.Equal(t, tt.expectErr, err)
} else {
require.NoError(t, err)
if tt.checkDelete {
// 刪除成功後,透過 GetByID 應查無資料
_, err := repo.GetByID(ctx, tt.id)
require.Error(t, err)
assert.Equal(t, ErrNotFound, err)
}
}
})
}
}
func TestListTags(t *testing.T) {
repo, tearDown, err := SetupTestProductTagsRepo("testDB")
require.NoError(t, err)
defer tearDown()
ctx := context.Background()
now := time.Now().UnixNano()
// 建立測試資料,手動指定 ID 與時間(不讓 Create 自動覆蓋)
tag1 := &entity.Tags{
ID: primitive.NewObjectID(),
Name: "Tag A",
Types: product.ItemTypeSkill,
ShowType: product.ShowTypeNormal,
CreatedAt: now + 300,
UpdatedAt: now + 300,
}
tag2 := &entity.Tags{
ID: primitive.NewObjectID(),
Name: "Tag B",
Types: product.ItemTypeProduct,
ShowType: product.ShowTypeNormal,
CreatedAt: now + 200,
UpdatedAt: now + 200,
}
tag3 := &entity.Tags{
ID: primitive.NewObjectID(),
Types: product.ItemTypeProduct,
Name: "Tag C",
ShowType: product.ShowTypeNormal,
CreatedAt: now + 100,
UpdatedAt: now + 100,
}
// 插入測試資料
err = repo.Create(ctx, tag1)
require.NoError(t, err)
err = repo.Create(ctx, tag2)
require.NoError(t, err)
err = repo.Create(ctx, tag3)
require.NoError(t, err)
tests := []struct {
name string
params repository.TagQueryParams
expectCount int64
expectIDsOrder []primitive.ObjectID
}{
{
name: "Filter by Name",
params: repository.TagQueryParams{
Name: ptr("Tag A"),
PageSize: 10,
PageIndex: 1,
},
expectCount: 1,
expectIDsOrder: []primitive.ObjectID{tag1.ID},
},
{
name: "Filter by Types = type1",
params: repository.TagQueryParams{
Types: ptr(product.ItemTypeProduct),
PageSize: 10,
PageIndex: 1,
},
// tag1與 tag2 符合條件
expectCount: 2,
// 排序依 updated_at 由大到小,故 tag1 (now+300) 在前,接著 tag2 (now+200)
expectIDsOrder: []primitive.ObjectID{tag2.ID, tag3.ID},
},
{
name: "Filter by ShowType = show1",
params: repository.TagQueryParams{
ShowType: ptr(product.ShowTypeNormal),
PageSize: 10,
PageIndex: 1,
},
// tag1與 tag3 符合條件
expectCount: 3,
// 排序tag1 (now+300) 在前tag3 (now+100) 在後
expectIDsOrder: []primitive.ObjectID{tag1.ID, tag2.ID, tag3.ID},
},
{
name: "Filter by Types=type1 and ShowType=show1",
params: repository.TagQueryParams{
Types: ptr(product.ItemTypeProduct),
ShowType: ptr(product.ShowTypeNormal),
PageSize: 10,
PageIndex: 1,
},
// 只有 tag1 同時符合兩個條件
expectCount: 2,
expectIDsOrder: []primitive.ObjectID{tag2.ID, tag3.ID},
},
{
name: "Pagination works: PageIndex 1",
params: repository.TagQueryParams{
PageSize: 2,
PageIndex: 1,
},
// 全部 3 筆資料符合查詢條件,但分頁僅返回前 2 筆,排序依 updated_at 由大到小tag1, tag2, tag3
expectCount: 3,
expectIDsOrder: []primitive.ObjectID{tag1.ID, tag2.ID},
},
{
name: "Pagination works: PageIndex 2",
params: repository.TagQueryParams{
PageSize: 2,
PageIndex: 2,
},
// 第二頁應返回剩下的 1 筆tag3
expectCount: 3,
expectIDsOrder: []primitive.ObjectID{tag3.ID},
},
}
for _, tt := range tests {
tt := tt // capture range variable
t.Run(tt.name, func(t *testing.T) {
result, count, err := repo.List(ctx, tt.params)
require.NoError(t, err)
assert.Equal(t, tt.expectCount, count)
var gotIDs []primitive.ObjectID
for _, tag := range result {
gotIDs = append(gotIDs, tag.ID)
}
assert.Equal(t, tt.expectIDsOrder, gotIDs)
})
}
}
func TestBindTag(t *testing.T) {
repo, tearDown, err := SetupTestProductTagsRepo("testDB")
require.NoError(t, err)
defer tearDown()
ctx := context.Background()
tests := []struct {
name string
inputBinding *entity.TagsBindingTable
expectError bool
verify func(t *testing.T, binding *entity.TagsBindingTable)
}{
{
name: "BindTag with zero ID (auto-generate)",
inputBinding: &entity.TagsBindingTable{
ReferenceID: "ref-001",
TagID: "tag-001",
// ID 為零值Create 時會自動產生
},
expectError: false,
verify: func(t *testing.T, binding *entity.TagsBindingTable) {
require.False(t, binding.ID.IsZero(), "ID should be generated")
assert.NotZero(t, binding.CreatedAt, "CreatedAt should be set")
assert.NotZero(t, binding.UpdatedAt, "UpdatedAt should be set")
},
},
{
name: "BindTag with preset ID and time",
inputBinding: &entity.TagsBindingTable{
ID: primitive.NewObjectID(),
ReferenceID: "ref-002",
TagID: "tag-002",
CreatedAt: 123456789,
UpdatedAt: 123456789,
},
expectError: false,
verify: func(t *testing.T, binding *entity.TagsBindingTable) {
// 預先設定的值應保持不變
assert.False(t, binding.ID.IsZero(), "ID should not be zero")
assert.Equal(t, int64(123456789), binding.CreatedAt)
assert.Equal(t, int64(123456789), binding.UpdatedAt)
},
},
}
for _, tc := range tests {
tc := tc // capture range variable
t.Run(tc.name, func(t *testing.T) {
err := repo.BindTags(ctx, []*entity.TagsBindingTable{tc.inputBinding})
if tc.expectError {
require.Error(t, err)
} else {
require.NoError(t, err)
// 驗證修改後的資料內容
tc.verify(t, tc.inputBinding)
}
})
}
}
func TestGetBindingsByReference(t *testing.T) {
repo, tearDown, err := SetupTestProductTagsRepo("testDB")
require.NoError(t, err)
defer tearDown()
ctx := context.Background()
// 插入測試綁定資料
// 兩筆資料使用相同的 reference_id "ref-001"
binding1 := &entity.TagsBindingTable{
ReferenceID: "ref-001",
TagID: "tag-001",
}
binding2 := &entity.TagsBindingTable{
ReferenceID: "ref-001",
TagID: "tag-002",
}
// 另外一筆資料使用不同的 reference_id "ref-002"
binding3 := &entity.TagsBindingTable{
ReferenceID: "ref-002",
TagID: "tag-003",
}
// 插入資料
err = repo.BindTags(ctx, []*entity.TagsBindingTable{binding1, binding2, binding3})
require.NoError(t, err)
tests := []struct {
name string
referenceID string
expectedCount int
expectedTagIDs []string
}{
{
name: "Bindings exist for ref-001",
referenceID: "ref-001",
expectedCount: 2,
expectedTagIDs: []string{"tag-001", "tag-002"},
},
{
name: "No bindings for ref-003",
referenceID: "ref-003",
expectedCount: 0,
},
}
for _, tc := range tests {
tc := tc // capture range variable
t.Run(tc.name, func(t *testing.T) {
results, err := repo.GetBindingsByReference(ctx, tc.referenceID)
require.NoError(t, err)
assert.Equal(t, tc.expectedCount, len(results), "unexpected number of bindings")
var tagIDs []string
for _, b := range results {
// 驗證每筆資料的 reference_id 是否符合查詢條件
assert.Equal(t, tc.referenceID, b.ReferenceID)
tagIDs = append(tagIDs, b.TagID)
}
for _, expectedTagID := range tc.expectedTagIDs {
assert.Contains(t, tagIDs, expectedTagID)
}
})
}
}
func TestListTagBinding(t *testing.T) {
repo, tearDown, err := SetupTestProductTagsRepo("testDB")
require.NoError(t, err)
defer tearDown()
ctx := context.Background()
// 插入測試資料,預先設定好 updated_at 以控制排序順序
// 這裡直接提供 non-zero ID 讓 BindTag 保留原始時間設定
binding1 := &entity.TagsBindingTable{
ID: primitive.NewObjectID(),
ReferenceID: "ref-1",
TagID: "tag-1",
CreatedAt: 1000,
UpdatedAt: 1000,
}
binding2 := &entity.TagsBindingTable{
ID: primitive.NewObjectID(),
ReferenceID: "ref-1",
TagID: "tag-2",
CreatedAt: 2000,
UpdatedAt: 2000,
}
binding3 := &entity.TagsBindingTable{
ID: primitive.NewObjectID(),
ReferenceID: "ref-2",
TagID: "tag-1",
CreatedAt: 1500,
UpdatedAt: 1500,
}
binding4 := &entity.TagsBindingTable{
ID: primitive.NewObjectID(),
ReferenceID: "ref-2",
TagID: "tag-3",
CreatedAt: 2500,
UpdatedAt: 2500,
}
// 插入資料
err = repo.BindTags(ctx, []*entity.TagsBindingTable{binding1, binding2, binding3, binding4})
require.NoError(t, err)
// 測試案例
tests := []struct {
name string
params repository.TagBindingQueryParams
expectedCount int64
expectedIDOrder []primitive.ObjectID
}{
{
name: "Filter by TagID=tag-1",
params: repository.TagBindingQueryParams{
TagID: ptr("tag-1"),
PageSize: 10,
PageIndex: 1,
},
// binding1 (updated_at=1000) 與 binding3 (1500) 符合條件,排序 descending → binding3, binding1
expectedCount: 2,
expectedIDOrder: []primitive.ObjectID{binding3.ID, binding1.ID},
},
{
name: "Filter by ReferenceID=ref-2",
params: repository.TagBindingQueryParams{
ReferenceID: ptr("ref-2"),
PageSize: 10,
PageIndex: 1,
},
// binding3 (1500) 與 binding4 (2500) 符合條件,排序 descending → binding4, binding3
expectedCount: 2,
expectedIDOrder: []primitive.ObjectID{binding4.ID, binding3.ID},
},
{
name: "Filter by TagID=tag-1 and ReferenceID=ref-1",
params: repository.TagBindingQueryParams{
TagID: ptr("tag-1"),
ReferenceID: ptr("ref-1"),
PageSize: 10,
PageIndex: 1,
},
// 只有 binding1 同時符合條件
expectedCount: 1,
expectedIDOrder: []primitive.ObjectID{binding1.ID},
},
{
name: "Pagination: PageIndex 1",
params: repository.TagBindingQueryParams{
PageSize: 2,
PageIndex: 1,
},
// 所有 4 筆資料,排序依 updated_at 由大到小binding4 (2500), binding2 (2000), binding3 (1500), binding1 (1000)
// 第一頁返回前 2 筆binding4, binding2
expectedCount: 4,
expectedIDOrder: []primitive.ObjectID{binding4.ID, binding2.ID},
},
{
name: "Pagination: PageIndex 2",
params: repository.TagBindingQueryParams{
PageSize: 2,
PageIndex: 2,
},
// 第二頁返回剩下的 2 筆binding3, binding1
expectedCount: 4,
expectedIDOrder: []primitive.ObjectID{binding3.ID, binding1.ID},
},
}
for _, tc := range tests {
tc := tc // capture range variable
t.Run(tc.name, func(t *testing.T) {
results, count, err := repo.ListTagBinding(ctx, tc.params)
require.NoError(t, err)
assert.Equal(t, tc.expectedCount, count, "unexpected total count")
var gotIDs []primitive.ObjectID
for _, rec := range results {
gotIDs = append(gotIDs, rec.ID)
}
assert.Equal(t, tc.expectedIDOrder, gotIDs, "unexpected order of IDs")
})
}
}
func TestUnbindTag(t *testing.T) {
repo, tearDown, err := SetupTestProductTagsRepo("testDB")
require.NoError(t, err)
defer tearDown()
ctx := context.Background()
// 先插入一筆綁定資料,用來測試刪除
binding := &entity.TagsBindingTable{
ReferenceID: "ref-001",
TagID: "tag-001",
}
err = repo.BindTags(ctx, []*entity.TagsBindingTable{binding})
require.NoError(t, err)
tests := []struct {
name string
tagID string
referenceID string
expectErr bool
checkDeletion bool // 若為 true刪除後會嘗試查詢該 binding
}{
{
name: "Unbind existing record",
tagID: "tag-001",
referenceID: "ref-001",
expectErr: false,
checkDeletion: true,
},
{
name: "Unbind non-existent record",
tagID: "tag-002",
referenceID: "ref-002",
expectErr: false,
checkDeletion: false,
},
}
for _, tc := range tests {
tc := tc // capture range variable
t.Run(tc.name, func(t *testing.T) {
err := repo.UnbindTag(ctx, tc.tagID, tc.referenceID)
if tc.expectErr {
require.Error(t, err)
} else {
require.NoError(t, err)
if tc.checkDeletion {
// 透過底層查詢確認該 binding 已不存在
res, err := repo.GetBindingsByReference(ctx, tc.referenceID)
assert.NoError(t, err)
assert.Equal(t, 0, len(res))
}
}
})
}
}
func TestGetByIDs(t *testing.T) {
repo, tearDown, err := SetupTestProductTagsRepo("testDB_get_by_ids")
require.NoError(t, err)
defer tearDown()
ctx := context.Background()
now := time.Now().Unix()
tag1 := &entity.Tags{
ID: primitive.NewObjectID(),
Name: "Tag1",
Types: product.ItemTypeProduct,
ShowType: product.ShowTypeNormal,
CreatedAt: now,
UpdatedAt: now,
}
tag2 := &entity.Tags{
ID: primitive.NewObjectID(),
Name: "Tag2",
Types: product.ItemTypeSkill,
ShowType: product.ShowTypeNormal,
CreatedAt: now,
UpdatedAt: now,
}
// 寫入測試資料
require.NoError(t, repo.Create(ctx, tag1))
require.NoError(t, repo.Create(ctx, tag2))
t.Run("return matching tags", func(t *testing.T) {
results, err := repo.GetByIDs(ctx, []string{tag1.ID.Hex(), tag2.ID.Hex()})
require.NoError(t, err)
require.Len(t, results, 2)
gotIDs := map[primitive.ObjectID]bool{}
for _, tag := range results {
gotIDs[tag.ID] = true
}
assert.True(t, gotIDs[tag1.ID])
assert.True(t, gotIDs[tag2.ID])
})
t.Run("skip invalid object ids", func(t *testing.T) {
results, err := repo.GetByIDs(ctx, []string{"invalid_id", tag1.ID.Hex()})
require.NoError(t, err)
require.Len(t, results, 1)
assert.Equal(t, tag1.ID, results[0].ID)
})
t.Run("return empty if all ids invalid", func(t *testing.T) {
results, err := repo.GetByIDs(ctx, []string{"invalid1", "wrong!"})
require.NoError(t, err)
assert.Empty(t, results)
})
}
func TestUnbindTagByReferenceID(t *testing.T) {
repo, tearDown, err := SetupTestProductTagsRepo("testDB_get_by_ids")
require.NoError(t, err)
defer tearDown()
ctx := context.Background()
now := time.Now().Unix()
referenceID := "ref123"
// 建立測試資料
binding1 := &entity.TagsBindingTable{
ID: primitive.NewObjectID(),
ReferenceID: referenceID,
TagID: "tag1",
CreatedAt: now,
UpdatedAt: now,
}
binding2 := &entity.TagsBindingTable{
ID: primitive.NewObjectID(),
ReferenceID: referenceID,
TagID: "tag2",
CreatedAt: now,
UpdatedAt: now,
}
bindingOther := &entity.TagsBindingTable{
ID: primitive.NewObjectID(),
ReferenceID: "ref999",
TagID: "tag999",
CreatedAt: now,
UpdatedAt: now,
}
require.NoError(t, repo.BindTags(ctx, []*entity.TagsBindingTable{binding1, binding2, bindingOther}))
t.Run("should delete bindings by referenceID", func(t *testing.T) {
err := repo.UnbindTagByReferenceID(ctx, referenceID)
require.NoError(t, err)
// 確認 ref123 的都被刪了ref999 還在
result, err := repo.GetBindingsByReference(ctx, referenceID)
require.NoError(t, err)
assert.Len(t, result, 0)
// 檢查其他 reference 還在
resultOthers, err := repo.GetBindingsByReference(ctx, "ref999")
require.NoError(t, err)
assert.Len(t, resultOthers, 1)
assert.Equal(t, "tag999", resultOthers[0].TagID)
})
t.Run("should not fail if no matching data", func(t *testing.T) {
err := repo.UnbindTagByReferenceID(ctx, "nonexistent-ref")
require.NoError(t, err)
})
}

View File

@ -0,0 +1,499 @@
package repository
import (
"context"
"errors"
"fmt"
"testing"
"time"
"code.30cm.net/digimon/app-cloudep-product-service/pkg/domain/entity"
"code.30cm.net/digimon/app-cloudep-product-service/pkg/domain/repository"
mgo "code.30cm.net/digimon/library-go/mongo"
"github.com/alicebob/miniredis/v2"
"github.com/stretchr/testify/assert"
"github.com/zeromicro/go-zero/core/stores/cache"
"github.com/zeromicro/go-zero/core/stores/redis"
"go.mongodb.org/mongo-driver/bson/primitive"
"go.mongodb.org/mongo-driver/mongo"
)
func SetupTestProductRepository(db string) (repository.ProductRepository, func(), error) {
h, p, tearDown, err := startMongoContainer()
if err != nil {
return nil, nil, err
}
s, _ := miniredis.Run()
conf := &mgo.Conf{
Schema: Schema,
Host: fmt.Sprintf("%s:%s", h, p),
Database: db,
MaxStaleness: 300,
MaxPoolSize: 100,
MinPoolSize: 100,
MaxConnIdleTime: 300,
Compressors: []string{},
EnableStandardReadWriteSplitMode: false,
ConnectTimeoutMs: 3000,
}
cacheConf := cache.CacheConf{
cache.NodeConf{
RedisConf: redis.RedisConf{
Host: s.Addr(),
Type: redis.NodeType,
},
Weight: 100,
},
}
cacheOpts := []cache.Option{
cache.WithExpiry(1000 * time.Microsecond),
cache.WithNotFoundExpiry(1000 * time.Microsecond),
}
param := ProductRepositoryParam{
Conf: conf,
CacheConf: cacheConf,
CacheOpts: cacheOpts,
}
repo := NewProductRepository(param)
_, _ = repo.Index20250317001UP(context.Background())
return repo, tearDown, nil
}
func TestListProduct(t *testing.T) {
model, tearDown, err := SetupTestProductRepository("testDB")
defer tearDown()
assert.NoError(t, err)
now := time.Now()
products := []*entity.Product{
{
UID: "user1",
Title: "Product 1",
IsPublished: true,
Amount: 100,
StartTime: ptr(now.Add(-24 * time.Hour).Unix()),
EndTime: ptr(now.Add(24 * time.Hour).Unix()),
Category: "tech",
CreatedAt: now.Unix(),
UpdatedAt: now.Unix(),
Slug: ptr("product-1"),
Details: ptr("details..."),
},
{
UID: "user2",
Title: "Product 2",
IsPublished: false,
Amount: 200,
StartTime: ptr(now.Add(-48 * time.Hour).Unix()),
EndTime: ptr(now.Add(48 * time.Hour).Unix()),
Category: "health",
CreatedAt: now.Unix(),
UpdatedAt: now.Unix(),
Slug: ptr("product-2"),
Details: ptr("details..."),
},
{
UID: "user1",
Title: "Product 3",
IsPublished: true,
Amount: 300,
StartTime: ptr(now.Add(-72 * time.Hour).Unix()),
EndTime: ptr(now.Add(72 * time.Hour).Unix()),
Category: "tech",
CreatedAt: now.Unix(),
UpdatedAt: now.Unix(),
Slug: ptr("product-3"),
Details: ptr("details..."),
},
}
for _, p := range products {
err = model.Insert(context.Background(), p)
assert.NoError(t, err)
}
tests := []struct {
name string
params *repository.ProductQueryParams
expectCount int64
expectIDs []string
}{
{
name: "Filter by UID",
params: &repository.ProductQueryParams{
UID: ptr("user1"),
PageSize: 10,
PageIndex: 1,
},
expectCount: 2,
expectIDs: []string{products[2].ID.Hex(), products[0].ID.Hex()},
},
{
name: "Filter by Published",
params: &repository.ProductQueryParams{
IsPublished: ptr(true),
PageSize: 10,
PageIndex: 1,
},
expectCount: 2,
expectIDs: []string{products[2].ID.Hex(), products[0].ID.Hex()},
},
{
name: "Filter by Category",
params: &repository.ProductQueryParams{
Category: ptr("tech"),
PageSize: 10,
PageIndex: 1,
},
expectCount: 2,
expectIDs: []string{products[2].ID.Hex(), products[0].ID.Hex()},
},
{
name: "Filter by StartTime >= now - 36h (should get P1 & P2)",
params: &repository.ProductQueryParams{
StartTime: ptr(now.Add(-36 * time.Hour).Unix()),
PageSize: 10,
PageIndex: 1,
},
expectCount: 1,
expectIDs: []string{products[0].ID.Hex()},
},
{
name: "Filter by EndTime <= now + 30h (should get P1)",
params: &repository.ProductQueryParams{
EndTime: ptr(now.Add(30 * time.Hour).Unix()),
PageSize: 10,
PageIndex: 1,
},
expectCount: 1,
expectIDs: []string{products[0].ID.Hex()},
},
{
name: "Filter by Slug = product-2",
params: &repository.ProductQueryParams{
Slug: ptr("product-2"),
PageSize: 10,
PageIndex: 1,
},
expectCount: 1,
expectIDs: []string{products[1].ID.Hex()},
},
{
name: "Filter by UID + Category + Published",
params: &repository.ProductQueryParams{
UID: ptr("user1"),
Category: ptr("tech"),
IsPublished: ptr(true),
PageSize: 10,
PageIndex: 1,
},
expectCount: 2,
expectIDs: []string{products[2].ID.Hex(), products[0].ID.Hex()},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result, count, err := model.ListProduct(context.Background(), tt.params)
assert.NoError(t, err)
assert.Equal(t, tt.expectCount, count)
var resultIDs []string
for _, r := range result {
resultIDs = append(resultIDs, r.ID.Hex())
}
assert.Equal(t, tt.expectIDs, resultIDs)
})
}
}
func TestFindOneBySlug(t *testing.T) {
model, tearDown, err := SetupTestProductRepository("testDB")
defer tearDown()
assert.NoError(t, err)
now := time.Now()
products := []*entity.Product{
{
UID: "user1",
Title: "Product 1",
IsPublished: true,
Slug: ptr("product-1"),
CreatedAt: now.Unix(),
UpdatedAt: now.Unix(),
},
{
UID: "user2",
Title: "Product 2",
IsPublished: true,
Slug: ptr("product-2"),
CreatedAt: now.Unix(),
UpdatedAt: now.Unix(),
},
}
// 插入測試資料
for _, p := range products {
err = model.Insert(context.Background(), p)
assert.NoError(t, err)
}
tests := []struct {
name string
slug string
expectFound bool
expectID string
}{
{
name: "Found: product-1",
slug: "product-1",
expectFound: true,
expectID: products[0].ID.Hex(),
},
{
name: "Not Found: unknown-slug",
slug: "unknown-slug",
expectFound: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
res, err := model.FindOneBySlug(context.Background(), tt.slug)
if tt.expectFound {
assert.NoError(t, err)
assert.NotNil(t, res)
assert.Equal(t, tt.expectID, res.ID.Hex())
} else {
assert.Nil(t, res)
assert.ErrorIs(t, err, ErrNotFound)
}
})
}
}
func TestDeleteProduct(t *testing.T) {
model, tearDown, err := SetupTestProductRepository("testDB")
defer tearDown()
assert.NoError(t, err)
// 建立測試資料
now := time.Now()
product := &entity.Product{
UID: "user1",
Title: "Deletable Product",
IsPublished: true,
Slug: ptr("delete-slug"),
CreatedAt: now.Unix(),
UpdatedAt: now.Unix(),
}
err = model.Insert(context.Background(), product)
assert.NoError(t, err)
t.Run("Delete existing product", func(t *testing.T) {
err := model.Delete(context.Background(), product.ID.Hex())
assert.NoError(t, err)
// 再查詢應該會是找不到
_, err = model.FindOneByID(context.Background(), product.ID.Hex())
assert.ErrorIs(t, err, ErrNotFound)
})
t.Run("Delete non-existing product", func(t *testing.T) {
invalidID := primitive.NewObjectID().Hex()
err := model.Delete(context.Background(), invalidID)
assert.ErrorIs(t, err, ErrNotFound)
})
}
func TestUpdateProduct(t *testing.T) {
model, tearDown, err := SetupTestProductRepository("testDB")
defer tearDown()
assert.NoError(t, err)
now := time.Now()
product := &entity.Product{
UID: "user1",
Title: "Original Title",
IsPublished: false,
Slug: ptr("original-slug"),
CreatedAt: now.UnixNano(),
UpdatedAt: now.UnixNano(),
}
err = model.Insert(context.Background(), product)
assert.NoError(t, err)
tests := []struct {
name string
update *repository.ProductUpdateParams
validate func(t *testing.T, updated *entity.Product)
}{
{
name: "Update title",
update: &repository.ProductUpdateParams{
Title: ptr("New Title"),
},
validate: func(t *testing.T, updated *entity.Product) {
assert.Equal(t, "New Title", updated.Title)
},
},
{
name: "Update is_published",
update: &repository.ProductUpdateParams{
IsPublished: ptr(true),
},
validate: func(t *testing.T, updated *entity.Product) {
assert.True(t, updated.IsPublished)
},
},
{
name: "Update start and end time",
update: &repository.ProductUpdateParams{
StartTime: ptr(now.Add(1 * time.Hour).UnixNano()),
EndTime: ptr(now.Add(2 * time.Hour).UnixNano()),
},
validate: func(t *testing.T, updated *entity.Product) {
assert.Equal(t, now.Add(1*time.Hour).UnixNano(), *updated.StartTime)
assert.Equal(t, now.Add(2*time.Hour).UnixNano(), *updated.EndTime)
},
},
{
name: "Update short description",
update: &repository.ProductUpdateParams{
ShortDescription: "Short desc here",
},
validate: func(t *testing.T, updated *entity.Product) {
assert.Equal(t, "Short desc here", updated.ShortDescription)
},
},
{
name: "Update details, short_title, slug",
update: &repository.ProductUpdateParams{
Details: ptr("Updated details"),
ShortTitle: ptr("ShortT"),
Slug: ptr("new-slug"),
},
validate: func(t *testing.T, updated *entity.Product) {
assert.Equal(t, "Updated details", *updated.Details)
assert.Equal(t, "ShortT", *updated.ShortTitle)
assert.Equal(t, "new-slug", *updated.Slug)
},
},
{
name: "Update amount",
update: &repository.ProductUpdateParams{
Amount: ptr(uint64(500)),
},
validate: func(t *testing.T, updated *entity.Product) {
assert.Equal(t, uint64(500), updated.Amount)
},
},
{
name: "Update media",
update: &repository.ProductUpdateParams{
Media: []entity.Media{
{Sort: 1, Type: "image", URL: "https://example.com/1.jpg"},
},
},
validate: func(t *testing.T, updated *entity.Product) {
assert.Len(t, updated.Media, 1)
assert.Equal(t, "image", updated.Media[0].Type)
},
},
{
name: "Update custom fields",
update: &repository.ProductUpdateParams{
CustomFields: []entity.CustomFields{
{Key: "color", Value: "red"},
},
},
validate: func(t *testing.T, updated *entity.Product) {
assert.Len(t, updated.CustomFields, 1)
assert.Equal(t, "color", updated.CustomFields[0].Key)
assert.Equal(t, "red", updated.CustomFields[0].Value)
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
_, err := model.Update(context.Background(), product.ID.Hex(), tt.update)
assert.NoError(t, err)
// 重新查詢確認資料已更新
updated, err := model.FindOneByID(context.Background(), product.ID.Hex())
assert.NoError(t, err)
tt.validate(t, updated)
})
}
}
func TestProductRepository_Transaction(t *testing.T) {
model, tearDown, err := SetupTestProductRepository("testDB")
defer tearDown()
assert.NoError(t, err)
ctx := context.Background()
tests := []struct {
name string
transactionFunc func(sessCtx mongo.SessionContext) (any, error)
expectError bool
}{
{
name: "Successful Transaction",
transactionFunc: func(sessCtx mongo.SessionContext) (any, error) {
product := &entity.Product{
UID: "user123",
Title: "Test Transaction Product",
Amount: 1000,
IsPublished: true,
}
err := model.Insert(ctx, product)
return product, err
},
expectError: false,
},
{
name: "Transaction Rollback on Error",
transactionFunc: func(sessCtx mongo.SessionContext) (any, error) {
product := &entity.Product{
UID: "user123",
Title: "Rollback Test Product",
Amount: 5000,
IsPublished: true,
}
_ = model.Insert(ctx, product)
// 模擬交易內錯誤
return nil, errors.New("forced error")
},
expectError: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err := model.Transaction(ctx, tt.transactionFunc)
if tt.expectError {
assert.Error(t, err, "交易應該返回錯誤")
} else {
assert.NoError(t, err, "交易應該成功完成")
product, _, err := model.ListProduct(ctx,
&repository.ProductQueryParams{
PageSize: 20, PageIndex: 1,
UID: ptr("user123")})
assert.NoError(t, err)
assert.Equal(t, product[0].UID, "user123")
}
})
}
}
func ptr[T any](v T) *T {
return &v
}

139
pkg/usecase/category.go Normal file
View File

@ -0,0 +1,139 @@
package usecase
import (
"context"
"errors"
"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/usecase"
"code.30cm.net/digimon/library-go/errs"
"github.com/zeromicro/go-zero/core/logx"
"github.com/zeromicro/go-zero/core/stores/mon"
)
type CategoryUseCaseParam struct {
CategoryRepo repository.CategoryRepository
}
type CategoryUseCase struct {
CategoryUseCaseParam
}
func MustCategoryUseCase(param CategoryUseCaseParam) usecase.CategoryUseCase {
return &CategoryUseCase{
param,
}
}
func (use *CategoryUseCase) Insert(ctx context.Context, data *entity.Category) error {
err := use.CategoryRepo.Insert(ctx, &entity.Category{
Name: data.Name,
})
if err != nil {
return errs.DBErrorL(
logx.WithContext(ctx),
[]logx.LogField{
{Key: "req", Value: data},
{Key: "func", Value: "CategoryRepo.Insert"},
{Key: "err", Value: err.Error()},
},
"failed to create category").Wrap(err)
}
return nil
}
func (use *CategoryUseCase) FindOneByID(ctx context.Context, id string) (*entity.Category, error) {
res, err := use.CategoryRepo.FindOneByID(ctx, id)
if err != nil {
if errors.Is(err, mon.ErrNotFound) {
e := errs.ResourceNotFound(
"failed to get category",
)
return nil, e
}
e := errs.DBErrorL(
logx.WithContext(ctx),
[]logx.LogField{
{Key: "req", Value: id},
{Key: "func", Value: "CategoryRepo.FindOneByID"},
{Key: "err", Value: err.Error()},
},
"failed to get category").Wrap(err)
return nil, e
}
return res, nil
}
func (use *CategoryUseCase) Update(ctx context.Context, id string, data *entity.Category) error {
_, err := use.CategoryRepo.Update(ctx, id, &entity.Category{
Name: data.Name,
})
if err != nil {
if errors.Is(err, mon.ErrNotFound) {
e := errs.ResourceNotFound(
"failed to update category",
)
return e
}
e := errs.DBErrorL(
logx.WithContext(ctx),
[]logx.LogField{
{Key: "req", Value: id},
{Key: "func", Value: "CategoryRepo.Update"},
{Key: "err", Value: err.Error()},
},
"failed to update category").Wrap(err)
return e
}
return nil
}
func (use *CategoryUseCase) Delete(ctx context.Context, id string) error {
_, err := use.CategoryRepo.Delete(ctx, id)
if err != nil {
e := errs.DBErrorL(
logx.WithContext(ctx),
[]logx.LogField{
{Key: "req", Value: id},
{Key: "func", Value: "CategoryRepo.Delete"},
{Key: "err", Value: err.Error()},
},
"failed to delete category").Wrap(err)
return e
}
return nil
}
func (use *CategoryUseCase) ListCategory(ctx context.Context, params usecase.CategoryQueryParams) ([]*entity.Category, int64, error) {
category, i, err := use.CategoryRepo.ListCategory(ctx, &repository.CategoryQueryParams{
ID: params.ID,
PageIndex: params.PageIndex,
PageSize: params.PageSize,
})
if err != nil {
e := errs.DBErrorL(
logx.WithContext(ctx),
[]logx.LogField{
{Key: "req", Value: params},
{Key: "func", Value: "CategoryRepo.ListCategory"},
{Key: "err", Value: err.Error()},
},
"failed to list category").Wrap(err)
return nil, 0, e
}
return category, i, nil
}

View File

@ -0,0 +1,415 @@
package usecase
import (
"context"
"errors"
"testing"
"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/usecase"
"code.30cm.net/digimon/library-go/errs"
"github.com/stretchr/testify/assert"
"github.com/zeromicro/go-zero/core/logx"
"github.com/zeromicro/go-zero/core/stores/mon"
"go.mongodb.org/mongo-driver/bson/primitive"
"go.uber.org/mock/gomock"
mockRepository "code.30cm.net/digimon/app-cloudep-product-service/pkg/mock/repository"
)
func TestCategoryUseCase_FindOneByID(t *testing.T) {
mockCtrl := gomock.NewController(t)
defer mockCtrl.Finish()
mockCategoryRepo := mockRepository.NewMockCategoryRepository(mockCtrl)
useCase := MustCategoryUseCase(CategoryUseCaseParam{
CategoryRepo: mockCategoryRepo,
})
ctx := context.Background()
id := primitive.NewObjectID()
tests := []struct {
name string
id primitive.ObjectID
mockSetup func()
expectedError error
expectedResult *entity.Category
}{
{
name: "成功找到 Category",
id: id,
mockSetup: func() {
mockCategoryRepo.EXPECT().FindOneByID(ctx, id.Hex()).Return(&entity.Category{
ID: id,
Name: "Test Category",
}, nil)
},
expectedError: nil,
expectedResult: &entity.Category{
ID: id,
Name: "Test Category",
},
},
{
name: "Category 不存在",
id: primitive.NewObjectID(),
mockSetup: func() {
mockCategoryRepo.EXPECT().FindOneByID(ctx, gomock.Any()).Return(nil, mon.ErrNotFound)
},
expectedError: errs.ResourceNotFound("failed to get category"),
expectedResult: nil,
},
{
name: "資料庫錯誤",
id: primitive.NewObjectID(),
mockSetup: func() {
mockCategoryRepo.EXPECT().FindOneByID(ctx, gomock.Any()).Return(nil, errors.New("database error"))
},
expectedError: errs.DBErrorL(
logx.WithContext(ctx),
[]logx.LogField{
{Key: "req", Value: "db_error_id"},
{Key: "func", Value: "CategoryRepo.FindOneByID"},
{Key: "err", Value: "database error"},
},
"failed to get category").Wrap(errors.New("database error")),
expectedResult: nil,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
tt.mockSetup()
result, err := useCase.FindOneByID(ctx, tt.id.Hex())
if tt.expectedError == nil {
assert.NoError(t, err)
assert.Equal(t, tt.expectedResult, result)
} else {
assert.Error(t, err)
assert.Equal(t, tt.expectedError.Error(), err.Error())
assert.Nil(t, result)
}
})
}
}
func TestCategoryUseCase_Update(t *testing.T) {
mockCtrl := gomock.NewController(t)
defer mockCtrl.Finish()
mockCategoryRepo := mockRepository.NewMockCategoryRepository(mockCtrl)
useCase := MustCategoryUseCase(CategoryUseCaseParam{
CategoryRepo: mockCategoryRepo,
})
ctx := context.Background()
tests := []struct {
name string
id string
data entity.Category
mockSetup func()
expectedError error
}{
{
name: "成功更新 Category",
id: "valid_id",
data: entity.Category{
Name: "Updated Category",
},
mockSetup: func() {
mockCategoryRepo.EXPECT().Update(ctx, "valid_id", &entity.Category{
Name: "Updated Category",
}).Return(nil, nil)
},
expectedError: nil,
},
{
name: "Category 不存在",
id: "non_existing_id",
data: entity.Category{
Name: "Non-existing Category",
},
mockSetup: func() {
mockCategoryRepo.EXPECT().Update(ctx, "non_existing_id", &entity.Category{
Name: "Non-existing Category",
}).Return(nil, mon.ErrNotFound)
},
expectedError: errs.ResourceNotFound(
"failed to update category",
),
},
{
name: "資料庫錯誤",
id: "db_error_id",
data: entity.Category{
Name: "DB Error Category",
},
mockSetup: func() {
mockCategoryRepo.EXPECT().Update(ctx, "db_error_id", &entity.Category{
Name: "DB Error Category",
}).Return(nil, errors.New("database error"))
},
expectedError: errs.DBErrorL(
logx.WithContext(ctx),
[]logx.LogField{
{Key: "req", Value: "db_error_id"},
{Key: "func", Value: "CategoryRepo.Update"},
{Key: "err", Value: "database error"},
},
"failed to update category").Wrap(errors.New("database error")),
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
tt.mockSetup()
// 執行測試方法
err := useCase.Update(ctx, tt.id, &tt.data)
// 驗證結果
if tt.expectedError == nil {
assert.NoError(t, err)
} else {
assert.Error(t, err)
assert.Equal(t, tt.expectedError.Error(), err.Error())
}
})
}
}
func TestCategoryUseCase_Insert(t *testing.T) {
mockCtrl := gomock.NewController(t)
defer mockCtrl.Finish()
mockCategoryRepo := mockRepository.NewMockCategoryRepository(mockCtrl)
useCase := MustCategoryUseCase(CategoryUseCaseParam{
CategoryRepo: mockCategoryRepo,
})
ctx := context.Background()
tests := []struct {
name string
input entity.Category
mockSetup func()
expectedError error
}{
{
name: "成功插入 Category",
input: entity.Category{
Name: "Test Category",
},
mockSetup: func() {
mockCategoryRepo.EXPECT().Insert(ctx, &entity.Category{
Name: "Test Category",
}).Return(nil)
},
expectedError: nil,
},
{
name: "插入失敗 - Database Error",
input: entity.Category{
Name: "Invalid Category",
},
mockSetup: func() {
mockCategoryRepo.EXPECT().Insert(ctx, &entity.Category{
Name: "Invalid Category",
}).Return(errors.New("database connection error"))
},
expectedError: errs.DBErrorL(
logx.WithContext(ctx),
[]logx.LogField{
{Key: "req", Value: entity.Category{Name: "Invalid Category"}},
{Key: "func", Value: "CategoryRepo.Insert"},
{Key: "err", Value: "database connection error"},
},
"failed to create category",
).Wrap(errors.New("database connection error")),
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
tt.mockSetup()
// 執行測試方法
err := useCase.Insert(ctx, &tt.input)
// 驗證結果
if tt.expectedError == nil {
assert.NoError(t, err)
} else {
assert.Error(t, err)
assert.Equal(t, tt.expectedError.Error(), err.Error())
}
})
}
}
func TestCategoryUseCase_Delete(t *testing.T) {
mockCtrl := gomock.NewController(t)
defer mockCtrl.Finish()
mockCategoryRepo := mockRepository.NewMockCategoryRepository(mockCtrl)
useCase := MustCategoryUseCase(CategoryUseCaseParam{
CategoryRepo: mockCategoryRepo,
})
ctx := context.Background()
tests := []struct {
name string
id string
mockSetup func()
expectedError error
}{
{
name: "成功刪除 Category",
id: "valid_id",
mockSetup: func() {
mockCategoryRepo.EXPECT().Delete(ctx, gomock.Any()).Return(int64(1), nil)
},
expectedError: nil,
},
{
name: "資料庫錯誤",
id: "db_error_id",
mockSetup: func() {
mockCategoryRepo.EXPECT().Delete(ctx, gomock.Any()).Return(int64(0), errors.New("database error"))
},
expectedError: errs.DBErrorL(
logx.WithContext(ctx),
[]logx.LogField{
{Key: "req", Value: "db_error_id"},
{Key: "func", Value: "CategoryRepo.Delete"},
{Key: "err", Value: "database error"},
},
"failed to delete category").Wrap(errors.New("database error")),
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
tt.mockSetup()
// 執行測試方法
err := useCase.Delete(ctx, tt.id)
// 驗證結果
if tt.expectedError == nil {
assert.NoError(t, err)
} else {
assert.Error(t, err)
assert.Equal(t, tt.expectedError.Error(), err.Error())
}
})
}
}
func TestCategoryUseCase_ListCategory(t *testing.T) {
mockCtrl := gomock.NewController(t)
defer mockCtrl.Finish()
mockCategoryRepo := mockRepository.NewMockCategoryRepository(mockCtrl)
useCase := MustCategoryUseCase(CategoryUseCaseParam{
CategoryRepo: mockCategoryRepo,
})
ctx := context.Background()
id := primitive.NewObjectID()
tests := []struct {
name string
params usecase.CategoryQueryParams
mockSetup func()
expectedResult []*entity.Category
expectedCount int64
expectedError error
}{
{
name: "成功列出 Category",
params: usecase.CategoryQueryParams{
ID: []string{id.Hex()},
PageIndex: 1,
PageSize: 10,
},
mockSetup: func() {
mockCategoryRepo.EXPECT().ListCategory(ctx, &repository.CategoryQueryParams{
ID: []string{id.Hex()},
PageIndex: 1,
PageSize: 10,
}).Return([]*entity.Category{
{
ID: id,
Name: "Test Category",
},
}, int64(1), nil)
},
expectedResult: []*entity.Category{
{
ID: id,
Name: "Test Category",
},
},
expectedCount: int64(1),
expectedError: nil,
},
{
name: "資料庫錯誤",
params: usecase.CategoryQueryParams{
ID: []string{"db_error_id"},
PageIndex: 1,
PageSize: 10,
},
mockSetup: func() {
mockCategoryRepo.EXPECT().ListCategory(ctx, &repository.CategoryQueryParams{
ID: []string{"db_error_id"},
PageIndex: 1,
PageSize: 10,
}).Return(nil, int64(0), errors.New("database error"))
},
expectedResult: nil,
expectedCount: int64(0),
expectedError: errs.DBErrorL(
logx.WithContext(ctx),
[]logx.LogField{
{Key: "req", Value: usecase.CategoryQueryParams{
ID: []string{"db_error_id"},
PageIndex: 1,
PageSize: 10,
}},
{Key: "func", Value: "CategoryRepo.ListCategory"},
{Key: "err", Value: "database error"},
},
"failed to list category").Wrap(errors.New("database error")),
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
tt.mockSetup()
// 執行測試方法
result, count, err := useCase.ListCategory(ctx, tt.params)
// 驗證結果
assert.Equal(t, tt.expectedResult, result)
assert.Equal(t, tt.expectedCount, count)
if tt.expectedError == nil {
assert.NoError(t, err)
} else {
assert.Error(t, err)
assert.Equal(t, tt.expectedError.Error(), err.Error())
}
})
}
}

171
pkg/usecase/kyc.go Normal file
View File

@ -0,0 +1,171 @@
package usecase
import (
"context"
"errors"
"fmt"
"code.30cm.net/digimon/app-cloudep-product-service/pkg/domain/entity"
"code.30cm.net/digimon/app-cloudep-product-service/pkg/domain/kyc"
"code.30cm.net/digimon/app-cloudep-product-service/pkg/domain/repository"
"code.30cm.net/digimon/app-cloudep-product-service/pkg/domain/usecase"
repo "code.30cm.net/digimon/app-cloudep-product-service/pkg/repository"
"code.30cm.net/digimon/library-go/errs"
"github.com/zeromicro/go-zero/core/logx"
)
type KYCUseCaseParam struct {
KYCRepo repository.KYCRepository
}
type KYCUseCase struct {
KYCUseCaseParam
}
func MustKYCUseCase(param KYCUseCaseParam) usecase.KYCUseCase {
return &KYCUseCase{
param,
}
}
func (use *KYCUseCase) Create(ctx context.Context, data *entity.KYC) error {
latest, err := use.KYCRepo.FindLatestByUID(ctx, data.UID)
// 發生真正的錯誤(非找不到)
if err != nil && !errors.Is(err, repo.ErrNotFound) {
return errs.DBErrorL(logx.WithContext(ctx),
[]logx.LogField{
{Key: "param", Value: data},
{Key: "func", Value: "KYCRepo.FindLatestByUID"},
{Key: "err", Value: err.Error()},
}, "failed to get latest kyc")
}
// 若查到資料,且不是被駁回的,表示已存在審核中/已通過資料 → 禁止再次建立
if err == nil && latest.Status != kyc.StatusREJECTED {
return errs.ForbiddenL(logx.WithContext(ctx),
[]logx.LogField{
{Key: "param", Value: data},
{Key: "func", Value: "KYCRepo.FindLatestByUID"},
{Key: "reason", Value: "KYC already in progress or approved"},
}, "KYC already in progress or approved")
}
// ✅ 若查不到資料ErrNotFound或前一筆是 REJECTED允許建立
data.Status = kyc.StatusPending
err = use.KYCRepo.Create(ctx, data)
if err != nil {
return errs.DBErrorL(logx.WithContext(ctx),
[]logx.LogField{
{Key: "param", Value: data},
{Key: "func", Value: "KYCRepo.Create"},
{Key: "err", Value: err.Error()},
}, "failed to create kyc review")
}
return nil
}
func (use *KYCUseCase) FindLatestByUID(ctx context.Context, uid string) (*entity.KYC, error) {
latest, err := use.KYCRepo.FindLatestByUID(ctx, uid)
if errors.Is(err, repo.ErrNotFound) {
return nil, errs.ResourceNotFound("failed to find latest kyc")
}
if err != nil {
return nil, errs.DBErrorL(logx.WithContext(ctx),
[]logx.LogField{
{Key: "uid", Value: uid},
{Key: "func", Value: "KYCRepo.FindLatestByUID"},
{Key: "err", Value: err.Error()},
}, "failed to get latest kyc")
}
return latest, nil
}
func (use *KYCUseCase) FindByID(ctx context.Context, id string) (*entity.KYC, error) {
byID, err := use.KYCRepo.FindByID(ctx, id)
if err != nil {
return nil, errs.DBErrorL(logx.WithContext(ctx),
[]logx.LogField{
{Key: "id", Value: id},
{Key: "func", Value: "KYCRepo.FindByID"},
{Key: "err", Value: err.Error()},
}, "failed to get kyc")
}
return byID, nil
}
func (use *KYCUseCase) List(ctx context.Context, params usecase.KYCQueryParams) ([]*entity.KYC, int64, error) {
q := repository.KYCQueryParams{
PageIndex: params.PageIndex,
PageSize: params.PageSize,
SortByDate: true,
}
if params.UID != nil {
q.UID = params.UID
}
if params.Country != nil {
q.Country = params.Country
}
if params.Status != nil {
q.Status = params.Status
}
list, i, err := use.KYCRepo.List(ctx, q)
if err != nil {
return nil, 0, errs.DBErrorL(logx.WithContext(ctx),
[]logx.LogField{
{Key: "params", Value: params},
{Key: "func", Value: "KYCRepo.List"},
{Key: "err", Value: err.Error()},
}, "failed to list kyc")
}
return list, i, nil
}
func (use *KYCUseCase) UpdateStatus(ctx context.Context, id string, status string, reason string) error {
err := use.KYCRepo.UpdateStatus(ctx, id, status, reason)
if err != nil {
return errs.DBErrorL(logx.WithContext(ctx),
[]logx.LogField{
{Key: "params", Value: fmt.Sprintf("id:%s, status:%s, reason: %s", id, status, reason)},
{Key: "func", Value: "KYCRepo.UpdateStatus"},
{Key: "err", Value: err.Error()},
}, "failed to update kyc status")
}
return nil
}
func (use *KYCUseCase) UpdateKYCInfo(ctx context.Context, id string, update *usecase.KYCUpdateParams) error {
err := use.KYCRepo.UpdateKYCInfo(ctx, id, &repository.KYCUpdateParams{
Name: update.Name,
Identification: update.Identification,
IdentificationType: update.IdentificationType,
Address: update.Address,
PostalCode: update.PostalCode,
IDFrontImage: update.IDFrontImage,
IDBackImage: update.IDBackImage,
BankStatementImg: update.BankStatementImg,
BankCode: update.BankCode,
BankName: update.BankName,
BranchCode: update.BranchCode,
BranchName: update.BranchName,
BankAccount: update.BankAccount,
})
if err != nil {
return errs.DBErrorL(logx.WithContext(ctx),
[]logx.LogField{
{Key: "id", Value: id},
{Key: "params", Value: update},
{Key: "func", Value: "KYCRepo.UpdateKYCInfo"},
{Key: "err", Value: err.Error()},
}, "failed to update kyc")
}
return nil
}

549
pkg/usecase/kyc_test.go Normal file
View File

@ -0,0 +1,549 @@
package usecase
import (
"context"
"errors"
"fmt"
"testing"
"code.30cm.net/digimon/app-cloudep-product-service/pkg/domain/entity"
"code.30cm.net/digimon/app-cloudep-product-service/pkg/domain/kyc"
"code.30cm.net/digimon/app-cloudep-product-service/pkg/domain/repository"
"code.30cm.net/digimon/app-cloudep-product-service/pkg/domain/usecase"
mockRepository "code.30cm.net/digimon/app-cloudep-product-service/pkg/mock/repository"
repo "code.30cm.net/digimon/app-cloudep-product-service/pkg/repository"
"code.30cm.net/digimon/library-go/errs"
"github.com/stretchr/testify/assert"
"github.com/zeromicro/go-zero/core/logx"
"go.mongodb.org/mongo-driver/bson/primitive"
"go.uber.org/mock/gomock"
"google.golang.org/protobuf/proto"
)
func TestKYCUseCase_Create(t *testing.T) {
mockCtrl := gomock.NewController(t)
defer mockCtrl.Finish()
mockKYCRepo := mockRepository.NewMockKYCRepository(mockCtrl)
useCase := MustKYCUseCase(KYCUseCaseParam{
KYCRepo: mockKYCRepo,
})
ctx := context.Background()
uid := "test-user"
tests := []struct {
name string
input *entity.KYC
mockSetup func()
expectedError error
}{
{
name: "查不到資料可以建立",
input: &entity.KYC{
UID: uid,
},
mockSetup: func() {
mockKYCRepo.EXPECT().FindLatestByUID(ctx, uid).Return(nil, repo.ErrNotFound)
mockKYCRepo.EXPECT().Create(ctx, gomock.Any()).Return(nil)
},
expectedError: nil,
},
{
name: "前一筆為 REJECTED 可建立",
input: &entity.KYC{
UID: uid,
},
mockSetup: func() {
mockKYCRepo.EXPECT().FindLatestByUID(ctx, uid).Return(&entity.KYC{
UID: uid,
Status: kyc.StatusREJECTED,
}, nil)
mockKYCRepo.EXPECT().Create(ctx, gomock.Any()).Return(nil)
},
expectedError: nil,
},
{
name: "已存在未駁回資料,禁止建立",
input: &entity.KYC{
UID: uid,
},
mockSetup: func() {
mockKYCRepo.EXPECT().FindLatestByUID(ctx, uid).Return(&entity.KYC{
UID: uid,
Status: kyc.StatusPending,
}, nil)
},
expectedError: errs.ForbiddenL(
logx.WithContext(ctx),
[]logx.LogField{
{Key: "param", Value: &entity.KYC{UID: uid}},
{Key: "func", Value: "KYCRepo.FindLatestByUID"},
{Key: "reason", Value: "KYC already in progress or approved"},
},
"KYC already in progress or approved",
),
},
{
name: "查詢資料庫錯誤",
input: &entity.KYC{
UID: uid,
},
mockSetup: func() {
mockKYCRepo.EXPECT().FindLatestByUID(ctx, uid).Return(nil, errors.New("database error"))
},
expectedError: errs.DBErrorL(
logx.WithContext(ctx),
[]logx.LogField{
{Key: "param", Value: &entity.KYC{UID: uid}},
{Key: "func", Value: "KYCRepo.FindLatestByUID"},
{Key: "err", Value: "database error"},
},
"failed to get latest kyc",
),
},
{
name: "建立時資料庫錯誤",
input: &entity.KYC{
UID: uid,
},
mockSetup: func() {
mockKYCRepo.EXPECT().FindLatestByUID(ctx, uid).Return(nil, repo.ErrNotFound)
mockKYCRepo.EXPECT().Create(ctx, gomock.Any()).Return(errors.New("insert failed"))
},
expectedError: errs.DBErrorL(
logx.WithContext(ctx),
[]logx.LogField{
{Key: "param", Value: &entity.KYC{UID: uid, Status: kyc.StatusPending}},
{Key: "func", Value: "KYCRepo.Create"},
{Key: "err", Value: "insert failed"},
},
"failed to create kyc review",
),
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
tt.mockSetup()
err := useCase.Create(ctx, tt.input)
if tt.expectedError == nil {
assert.NoError(t, err)
} else {
assert.Error(t, err)
assert.Equal(t, tt.expectedError.Error(), err.Error())
}
})
}
}
func TestKYCUseCase_FindLatestByUID(t *testing.T) {
mockCtrl := gomock.NewController(t)
defer mockCtrl.Finish()
mockKYCRepo := mockRepository.NewMockKYCRepository(mockCtrl)
useCase := MustKYCUseCase(KYCUseCaseParam{
KYCRepo: mockKYCRepo,
})
ctx := context.Background()
uid := "test-user"
tests := []struct {
name string
inputUID string
mockSetup func()
expectedResult *entity.KYC
expectedError error
}{
{
name: "查詢成功",
inputUID: uid,
mockSetup: func() {
mockKYCRepo.EXPECT().FindLatestByUID(ctx, uid).Return(&entity.KYC{
UID: uid,
Status: kyc.StatusPending,
}, nil)
},
expectedResult: &entity.KYC{
UID: uid,
Status: kyc.StatusPending,
},
expectedError: nil,
},
{
name: "資料庫錯誤",
inputUID: uid,
mockSetup: func() {
mockKYCRepo.EXPECT().FindLatestByUID(ctx, uid).Return(nil, errors.New("database error"))
},
expectedResult: nil,
expectedError: errs.DBErrorL(
logx.WithContext(ctx),
[]logx.LogField{
{Key: "uid", Value: uid},
{Key: "func", Value: "KYCRepo.FindLatestByUID"},
{Key: "err", Value: "database error"},
},
"failed to get latest kyc",
),
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
tt.mockSetup()
result, err := useCase.FindLatestByUID(ctx, tt.inputUID)
if tt.expectedError == nil {
assert.NoError(t, err)
assert.Equal(t, tt.expectedResult, result)
} else {
assert.Error(t, err)
assert.Equal(t, tt.expectedError.Error(), err.Error())
assert.Nil(t, result)
}
})
}
}
func TestKYCUseCase_FindByID(t *testing.T) {
mockCtrl := gomock.NewController(t)
defer mockCtrl.Finish()
mockKYCRepo := mockRepository.NewMockKYCRepository(mockCtrl)
useCase := MustKYCUseCase(KYCUseCaseParam{
KYCRepo: mockKYCRepo,
})
ctx := context.Background()
objID := primitive.NewObjectID()
idStr := objID.Hex()
tests := []struct {
name string
inputID string
mockSetup func()
expectedResult *entity.KYC
expectedError error
}{
{
name: "查詢成功",
inputID: idStr,
mockSetup: func() {
mockKYCRepo.EXPECT().FindByID(ctx, idStr).Return(&entity.KYC{
ID: objID,
UID: "user-1",
Status: kyc.StatusAPPROVED,
}, nil)
},
expectedResult: &entity.KYC{
ID: objID,
UID: "user-1",
Status: kyc.StatusAPPROVED,
},
expectedError: nil,
},
{
name: "資料庫錯誤",
inputID: idStr,
mockSetup: func() {
mockKYCRepo.EXPECT().FindByID(ctx, idStr).Return(nil, errors.New("database error"))
},
expectedResult: nil,
expectedError: errs.DBErrorL(
logx.WithContext(ctx),
[]logx.LogField{
{Key: "id", Value: idStr},
{Key: "func", Value: "KYCRepo.FindByID"},
{Key: "err", Value: "database error"},
},
"failed to get kyc",
),
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
tt.mockSetup()
result, err := useCase.FindByID(ctx, tt.inputID)
if tt.expectedError == nil {
assert.NoError(t, err)
assert.Equal(t, tt.expectedResult, result)
} else {
assert.Error(t, err)
assert.Equal(t, tt.expectedError.Error(), err.Error())
assert.Nil(t, result)
}
})
}
}
func TestKYCUseCase_List(t *testing.T) {
mockCtrl := gomock.NewController(t)
defer mockCtrl.Finish()
mockKYCRepo := mockRepository.NewMockKYCRepository(mockCtrl)
useCase := MustKYCUseCase(KYCUseCaseParam{
KYCRepo: mockKYCRepo,
})
ctx := context.Background()
uid := "user-1"
country := "TW"
status := kyc.StatusAPPROVED
ss := status.ToString()
tests := []struct {
name string
inputParams usecase.KYCQueryParams
mockSetup func()
expectedList []*entity.KYC
expectedCount int64
expectedError error
}{
{
name: "查詢成功",
inputParams: usecase.KYCQueryParams{
PageIndex: 1,
PageSize: 10,
UID: &uid,
Country: &country,
Status: &ss,
},
mockSetup: func() {
mockKYCRepo.EXPECT().List(ctx, repository.KYCQueryParams{
PageIndex: 1,
PageSize: 10,
UID: &uid,
Country: &country,
Status: &ss,
SortByDate: true,
}).Return([]*entity.KYC{
{UID: uid, CountryRegion: country, Status: status},
}, int64(1), nil)
},
expectedList: []*entity.KYC{
{UID: uid, CountryRegion: country, Status: status},
},
expectedCount: 1,
expectedError: nil,
},
{
name: "查詢失敗 - 資料庫錯誤",
inputParams: usecase.KYCQueryParams{
PageIndex: 2,
PageSize: 20,
},
mockSetup: func() {
mockKYCRepo.EXPECT().List(ctx, repository.KYCQueryParams{
PageIndex: 2,
PageSize: 20,
SortByDate: true,
}).Return(nil, int64(0), errors.New("database error"))
},
expectedList: nil,
expectedCount: 0,
expectedError: errs.DBErrorL(logx.WithContext(ctx),
[]logx.LogField{
{Key: "params", Value: usecase.KYCQueryParams{
PageIndex: 2,
PageSize: 20,
}},
{Key: "func", Value: "KYCRepo.List"},
{Key: "err", Value: "database error"},
}, "failed to list kyc"),
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
tt.mockSetup()
list, count, err := useCase.List(ctx, tt.inputParams)
assert.Equal(t, tt.expectedList, list)
assert.Equal(t, tt.expectedCount, count)
if tt.expectedError == nil {
assert.NoError(t, err)
} else {
assert.Error(t, err)
assert.Equal(t, tt.expectedError.Error(), err.Error())
}
})
}
}
func TestKYCUseCase_UpdateStatus(t *testing.T) {
mockCtrl := gomock.NewController(t)
defer mockCtrl.Finish()
mockKYCRepo := mockRepository.NewMockKYCRepository(mockCtrl)
useCase := MustKYCUseCase(KYCUseCaseParam{
KYCRepo: mockKYCRepo,
})
ctx := context.Background()
id := primitive.NewObjectID().Hex()
status := kyc.StatusAPPROVED
reason := "all good"
tests := []struct {
name string
id string
status kyc.Status
reason string
mockSetup func()
expectedError error
}{
{
name: "更新成功",
id: id,
status: status,
reason: reason,
mockSetup: func() {
mockKYCRepo.EXPECT().UpdateStatus(ctx, id, status.ToString(), reason).Return(nil)
},
expectedError: nil,
},
{
name: "更新失敗 - DB 錯誤",
id: id,
status: status,
reason: reason,
mockSetup: func() {
mockKYCRepo.EXPECT().UpdateStatus(ctx, id, status.ToString(), reason).Return(errors.New("db error"))
},
expectedError: errs.DBErrorL(
logx.WithContext(ctx),
[]logx.LogField{
{Key: "params", Value: fmt.Sprintf("id:%s, status:%s, reason: %s", id, status, reason)},
{Key: "func", Value: "KYCRepo.UpdateStatus"},
{Key: "err", Value: "db error"},
},
"failed to update kyc status",
),
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
tt.mockSetup()
err := useCase.UpdateStatus(ctx, tt.id, tt.status.ToString(), tt.reason)
if tt.expectedError == nil {
assert.NoError(t, err)
} else {
assert.Error(t, err)
assert.Equal(t, tt.expectedError.Error(), err.Error())
}
})
}
}
func TestKYCUseCase_UpdateKYCInfo(t *testing.T) {
mockCtrl := gomock.NewController(t)
defer mockCtrl.Finish()
mockKYCRepo := mockRepository.NewMockKYCRepository(mockCtrl)
useCase := MustKYCUseCase(KYCUseCaseParam{
KYCRepo: mockKYCRepo,
})
ctx := context.Background()
id := primitive.NewObjectID().Hex()
updateParams := &usecase.KYCUpdateParams{
Name: proto.String("Daniel Wang"),
Identification: proto.String("A123456789"),
IdentificationType: proto.String("passport"),
Address: proto.String("Taipei City"),
PostalCode: proto.String("100"),
IDFrontImage: proto.String("https://example.com/front.jpg"),
IDBackImage: proto.String("https://example.com/back.jpg"),
BankStatementImg: proto.String("https://example.com/bank.jpg"),
BankCode: proto.String("123"),
BankName: proto.String("ABC Bank"),
BranchCode: proto.String("001"),
BranchName: proto.String("Taipei Branch"),
BankAccount: proto.String("1234567890"),
}
tests := []struct {
name string
id string
input *usecase.KYCUpdateParams
mockSetup func()
expectedError error
}{
{
name: "更新成功",
id: id,
input: updateParams,
mockSetup: func() {
mockKYCRepo.EXPECT().UpdateKYCInfo(ctx, id, &repository.KYCUpdateParams{
Name: updateParams.Name,
Identification: updateParams.Identification,
IdentificationType: updateParams.IdentificationType,
Address: updateParams.Address,
PostalCode: updateParams.PostalCode,
IDFrontImage: updateParams.IDFrontImage,
IDBackImage: updateParams.IDBackImage,
BankStatementImg: updateParams.BankStatementImg,
BankCode: updateParams.BankCode,
BankName: updateParams.BankName,
BranchCode: updateParams.BranchCode,
BranchName: updateParams.BranchName,
BankAccount: updateParams.BankAccount,
}).Return(nil)
},
expectedError: nil,
},
{
name: "更新失敗 - DB 錯誤",
id: id,
input: updateParams,
mockSetup: func() {
mockKYCRepo.EXPECT().UpdateKYCInfo(ctx, id, gomock.Any()).Return(errors.New("db error"))
},
expectedError: errs.DBErrorL(logx.WithContext(ctx),
[]logx.LogField{
{Key: "id", Value: id},
{Key: "params", Value: updateParams},
{Key: "func", Value: "KYCRepo.UpdateKYCInfo"},
{Key: "err", Value: "db error"},
},
"failed to update kyc",
),
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
tt.mockSetup()
err := useCase.UpdateKYCInfo(ctx, tt.id, tt.input)
if tt.expectedError == nil {
assert.NoError(t, err)
} else {
assert.Error(t, err)
assert.Equal(t, tt.expectedError.Error(), err.Error())
}
})
}
}

637
pkg/usecase/product.go Normal file
View File

@ -0,0 +1,637 @@
package usecase
import (
"context"
"errors"
"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/usecase"
repo "code.30cm.net/digimon/app-cloudep-product-service/pkg/repository"
"code.30cm.net/digimon/app-cloudep-product-service/pkg/utils"
"code.30cm.net/digimon/library-go/errs"
"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 {
ProductRepo repository.ProductRepository
TagRepo repository.TagRepo
TagBinding repository.TagBindingRepo
ProductStatisticsRepo repository.ProductStatisticsRepo
}
type ProductUseCase struct {
ProductUseCaseParam
}
func MustProductUseCase(param ProductUseCaseParam) usecase.ProductUseCase {
return &ProductUseCase{
param,
}
}
func (use *ProductUseCase) Create(ctx context.Context, product *usecase.Product) error {
insert := convertUseCaseToEntity(product)
// Transaction 設定:只做必要寫入
opts := options.Transaction().SetReadConcern(readconcern.Local())
//nolint:contextcheck
err := use.ProductRepo.Transaction(ctx, func(sessCtx mongo.SessionContext) (any, error) {
if err := use.ProductRepo.Insert(sessCtx, insert); err != nil {
return nil, logDBError(ctx, "ProductRepo.Insert", []logx.LogField{{Key: "req", Value: product}}, err, "failed to create 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 {
return nil, logDBError(ctx, "ProductStatisticsRepo.Create", []logx.LogField{{Key: "ProductID", Value: insert.ID.Hex()}}, err, "failed to create product statistics")
}
tagsBinding := buildTagsBinding(insert.ID.Hex(), product.Tags)
if err := use.TagBinding.BindTags(sessCtx, tagsBinding); err != nil {
return nil, logDBError(ctx, "TagBinding.BindTags", []logx.LogField{{Key: "ReferenceID", Value: insert.ID.Hex()}, {Key: "tags", Value: product.Tags}}, err, "failed to bind product tags")
}
return struct{}{}, nil
}, opts)
if err != nil {
return err
}
return nil
}
func (use *ProductUseCase) Update(ctx context.Context, id string, product *usecase.Product) error {
update := convertUseCaseToUpdateParams(product)
tagsBinding := buildTagsBinding(id, product.Tags)
opts := options.Transaction().SetReadConcern(readconcern.Local())
err := use.ProductRepo.Transaction(ctx, func(sessCtx mongo.SessionContext) (any, error) {
//nolint:contextcheck
_, err := use.ProductRepo.Update(sessCtx, id, update)
if err != nil {
return nil, logDBError(ctx, "ProductRepo.Update", []logx.LogField{{Key: "req", Value: product}}, err, "failed to update product")
}
//nolint:contextcheck
if err := use.TagBinding.UnbindTagByReferenceID(sessCtx, id); err != nil {
return nil, logDBError(ctx, "TagBinding.UnbindTagByReferenceID", []logx.LogField{{Key: "ReferenceID", Value: id}}, err, "failed to unbind tags")
}
//nolint:contextcheck
if err := use.TagBinding.BindTags(sessCtx, tagsBinding); err != nil {
return nil, logDBError(ctx, "TagBinding.BindTags", []logx.LogField{{Key: "ReferenceID", Value: id}, {Key: "tags", Value: product.Tags}}, err, "failed to bind tags")
}
return struct{}{}, nil
}, opts)
return err
}
func (use *ProductUseCase) Delete(ctx context.Context, id string) error {
// Transaction 設定:只做必要寫入
opts := options.Transaction().SetReadConcern(readconcern.Local())
err := use.ProductRepo.Transaction(ctx, func(sessCtx mongo.SessionContext) (any, error) {
//nolint:contextcheck
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
}
//nolint:contextcheck
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 struct{}{}, nil
}, opts)
if err != nil {
return err
}
return nil
}
func (use *ProductUseCase) Get(ctx context.Context, id string) (*usecase.ProductResp, error) {
product, err := use.ProductRepo.FindOneByID(ctx, id)
if err != nil {
return nil, logDBError(ctx, "ProductRepo.FindOneByID", []logx.LogField{{Key: "product_id", Value: id}}, err, "failed to find product")
}
stats, err := use.ProductStatisticsRepo.GetByID(ctx, id)
if err != nil {
return nil, logDBError(ctx, "ProductStatisticsRepo.GetByID", []logx.LogField{{Key: "product_id", Value: id}}, err, "failed to get product statistics")
}
bindings, err := use.TagRepo.GetBindingsByReference(ctx, id)
if err != nil {
return nil, logDBError(ctx, "TagRepo.GetBindingsByReference", []logx.LogField{{Key: "product_id", Value: id}}, err, "failed to get tag bindings")
}
tagIDs := make([]string, 0, len(bindings))
for _, item := range bindings {
tagIDs = append(tagIDs, item.TagID)
}
tags, err := use.TagRepo.GetByIDs(ctx, tagIDs)
if err != nil {
return nil, logDBError(ctx, "TagRepo.GetByIDs", []logx.LogField{{Key: "product_id", Value: id}}, err, "failed to get tags")
}
return convertEntityToResp(product, stats, tags), nil
}
// TODO 效能有問題這邊優先改List 會有N + 1 問題
func (use *ProductUseCase) List(ctx context.Context, data usecase.ProductQueryParams) ([]*usecase.ProductResp, int64, error) {
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
}
if data.EndTime != nil {
query.EndTime = data.EndTime
}
products, total, err := use.ProductRepo.ListProduct(ctx, query)
if err != nil {
return nil, 0, err
}
result := make([]*usecase.ProductResp, 0, len(products))
for _, p := range products {
// 查詢統計資料
stats, _ := use.ProductStatisticsRepo.GetByID(ctx, p.ID.Hex())
// 查詢 Tag 資訊
tags, _ := use.TagRepo.GetBindingsByReference(ctx, p.ID.Hex())
tagIDs := make([]string, 0, len(tags))
for _, t := range tags {
tagIDs = append(tagIDs, t.TagID)
}
tagsInfo, _ := use.TagRepo.GetByIDs(ctx, tagIDs)
// 組合 Tags 回應
respTags := make([]usecase.Tags, 0, len(tagsInfo))
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
}
respTags = append(respTags, item)
}
// Media & CustomFields
media := make([]usecase.Media, 0, len(p.Media))
for _, m := range p.Media {
media = append(media, usecase.Media{
Sort: m.Sort,
URL: m.URL,
Type: m.Type,
})
}
customFields := make([]usecase.CustomFields, 0, len(p.CustomFields))
for _, f := range p.CustomFields {
customFields = append(customFields, usecase.CustomFields{
Key: f.Key,
Value: f.Value,
})
}
resp := &usecase.ProductResp{
ID: p.ID.Hex(),
UID: p.UID,
Title: p.Title,
ShortTitle: utils.ToValue(p.ShortTitle),
Details: utils.ToValue(p.Details),
ShortDescription: p.ShortDescription,
Media: media,
Slug: utils.ToValue(p.Slug),
IsPublished: p.IsPublished,
Amount: p.Amount,
StartTime: utils.UnixToRfc3339(utils.ToValue(p.StartTime)),
EndTime: utils.UnixToRfc3339(utils.ToValue(p.EndTime)),
Category: p.Category,
CustomFields: customFields,
Tags: respTags,
Orders: stats.Orders,
OrdersUpdateTime: utils.UnixToRfc3339(stats.OrdersUpdateTime),
AverageRating: stats.AverageRating,
AverageRatingUpdateTime: utils.UnixToRfc3339(stats.AverageRatingUpdateTime),
FansCount: stats.FansCount,
FansCountUpdateTime: utils.UnixToRfc3339(stats.FansCountUpdateTime),
UpdatedAt: utils.UnixToRfc3339(p.UpdatedAt),
CreatedAt: utils.UnixToRfc3339(p.CreatedAt),
}
result = append(result, resp)
}
return result, total, nil
}
func (use *ProductUseCase) IncOrders(ctx context.Context, productID string, count int64) error {
err := use.ProductStatisticsRepo.IncOrders(ctx, productID, count)
if err != nil {
return errs.DBErrorL(logx.WithContext(ctx),
[]logx.LogField{
{Key: "product_id", Value: productID},
{Key: "func", Value: "ProductStatisticsRepo.IncOrders"},
{Key: "err", Value: err.Error()},
}, "failed to inc order")
}
return nil
}
func (use *ProductUseCase) DecOrders(ctx context.Context, productID string, count int64) error {
err := use.ProductStatisticsRepo.DecOrders(ctx, productID, count)
if err != nil {
return errs.DBErrorL(logx.WithContext(ctx),
[]logx.LogField{
{Key: "product_id", Value: productID},
{Key: "func", Value: "ProductStatisticsRepo.DecOrders"},
{Key: "err", Value: err.Error()},
}, "failed to dec order")
}
return nil
}
func (use *ProductUseCase) UpdateAverageRating(ctx context.Context, productID string, averageRating float64) error {
err := use.ProductStatisticsRepo.UpdateAverageRating(ctx, productID, averageRating)
if err != nil {
return errs.DBErrorL(logx.WithContext(ctx),
[]logx.LogField{
{Key: "product_id", Value: productID},
{Key: "func", Value: "ProductStatisticsRepo.UpdateAverageRating"},
{Key: "err", Value: err.Error()},
}, "failed to update average rating")
}
return nil
}
func (use *ProductUseCase) IncFansCount(ctx context.Context, productID string, fansCount uint64) error {
err := use.ProductStatisticsRepo.IncFansCount(ctx, productID, fansCount)
if err != nil {
return errs.DBErrorL(logx.WithContext(ctx),
[]logx.LogField{
{Key: "product_id", Value: productID},
{Key: "func", Value: "ProductStatisticsRepo.IncFansCount"},
{Key: "err", Value: err.Error()},
}, "failed to inc fans count")
}
return nil
}
func (use *ProductUseCase) DecFansCount(ctx context.Context, productID string, fansCount uint64) error {
err := use.ProductStatisticsRepo.DecFansCount(ctx, productID, fansCount)
if err != nil {
return errs.DBErrorL(logx.WithContext(ctx),
[]logx.LogField{
{Key: "product_id", Value: productID},
{Key: "func", Value: "ProductStatisticsRepo.DecFansCount"},
{Key: "err", Value: err.Error()},
}, "failed to dec fans count")
}
return nil
}
func (use *ProductUseCase) BindTag(ctx context.Context, binding usecase.TagsBindingTable) error {
_, err := use.TagRepo.GetByID(ctx, binding.TagID)
if err != nil {
if errors.Is(err, repo.ErrNotFound) {
return errs.ResourceNotFound("failed to get tags")
}
return err
}
_, err = use.ProductRepo.FindOneByID(ctx, binding.ReferenceID)
if err != nil {
if errors.Is(err, repo.ErrNotFound) {
return errs.ResourceNotFound("failed to get tags")
}
return err
}
err = use.TagBinding.BindTags(ctx, []*entity.TagsBindingTable{{ReferenceID: binding.ReferenceID, TagID: binding.TagID}})
if err != nil {
return errs.DBErrorL(logx.WithContext(ctx),
[]logx.LogField{
{Key: "binding", Value: binding},
{Key: "func", Value: "TagBinding.BindTags"},
{Key: "err", Value: err.Error()},
}, "failed to bind tags")
}
return nil
}
func (use *ProductUseCase) UnbindTag(ctx context.Context, binding usecase.TagsBindingTable) error {
_, err := use.TagRepo.GetByID(ctx, binding.TagID)
if err != nil {
if errors.Is(err, repo.ErrNotFound) {
return errs.ResourceNotFound("failed to get tags")
}
return err
}
_, err = use.ProductRepo.FindOneByID(ctx, binding.ReferenceID)
if err != nil {
if errors.Is(err, repo.ErrNotFound) {
return errs.ResourceNotFound("failed to get tags")
}
return err
}
err = use.TagBinding.UnbindTag(ctx, binding.TagID, binding.ReferenceID)
if err != nil {
return errs.DBErrorL(logx.WithContext(ctx),
[]logx.LogField{
{Key: "binding", Value: binding},
{Key: "func", Value: "TagBinding.UnbindTag"},
{Key: "err", Value: err.Error()},
}, "failed to unbind tags")
}
return nil
}
func (use *ProductUseCase) GetBindingsByReference(ctx context.Context, referenceID string) ([]usecase.TagsBindingTableResp, error) {
ref, err := use.TagBinding.GetBindingsByReference(ctx, referenceID)
if err != nil {
return nil, errs.DBErrorL(logx.WithContext(ctx),
[]logx.LogField{
{Key: "referenceID", Value: referenceID},
{Key: "func", Value: "TagBinding.GetBindingsByReference"},
{Key: "err", Value: err.Error()},
}, "failed to get bindings by reference")
}
result := make([]usecase.TagsBindingTableResp, 0, len(ref))
for _, bind := range ref {
result = append(result, usecase.TagsBindingTableResp{
ID: bind.ID.Hex(),
ReferenceID: bind.ReferenceID,
TagID: bind.TagID,
CreatedAt: utils.UnixToRfc3339(bind.CreatedAt),
UpdatedAt: utils.UnixToRfc3339(bind.UpdatedAt),
})
}
return result, nil
}
func (use *ProductUseCase) ListTagBinding(ctx context.Context, params usecase.TagBindingQueryParams) ([]usecase.TagsBindingTableResp, int64, error) {
ref, total, err := use.TagBinding.ListTagBinding(ctx, repository.TagBindingQueryParams{
ReferenceID: params.ReferenceID,
TagID: params.TagID,
PageIndex: params.PageIndex,
PageSize: params.PageSize,
})
if err != nil {
return nil, 0, errs.DBErrorL(logx.WithContext(ctx),
[]logx.LogField{
{Key: "params", Value: params},
{Key: "func", Value: "TagBinding.ListTagBinding"},
{Key: "err", Value: err.Error()},
}, "failed to list tags")
}
result := make([]usecase.TagsBindingTableResp, 0, len(ref))
for _, bind := range ref {
result = append(result, usecase.TagsBindingTableResp{
ID: bind.ID.Hex(),
ReferenceID: bind.ReferenceID,
TagID: bind.TagID,
CreatedAt: utils.UnixToRfc3339(bind.CreatedAt),
UpdatedAt: utils.UnixToRfc3339(bind.UpdatedAt),
})
}
return result, total, nil
}
func logDBError(ctx context.Context, funcName string, fields []logx.LogField, err error, msg string) error {
return errs.DBErrorL(logx.WithContext(ctx), append(fields, logx.Field("func", funcName), logx.Field("err", err.Error())), msg)
}
func buildTagsBinding(referenceID string, tags []string) []*entity.TagsBindingTable {
binding := make([]*entity.TagsBindingTable, 0, len(tags))
for _, tag := range tags {
binding = append(binding, &entity.TagsBindingTable{
ReferenceID: referenceID,
TagID: tag,
})
}
return binding
}
func convertUseCaseToEntity(product *usecase.Product) *entity.Product {
insert := &entity.Product{}
if product.UID != nil {
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
}
return insert
}
func convertUseCaseToUpdateParams(product *usecase.Product) *repository.ProductUpdateParams {
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
}
return update
}
func convertEntityToResp(p *entity.Product, stats *entity.ProductStatistics, tags []*entity.Tags) *usecase.ProductResp {
tagsResp := make([]usecase.Tags, 0, len(tags))
for _, tag := range tags {
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
}
tagsResp = append(tagsResp, item)
}
media := make([]usecase.Media, 0, len(p.Media))
for _, m := range p.Media {
media = append(media, usecase.Media{Sort: m.Sort, URL: m.URL, Type: m.Type})
}
cf := make([]usecase.CustomFields, 0, len(p.CustomFields))
for _, field := range p.CustomFields {
cf = append(cf, usecase.CustomFields{Key: field.Key, Value: field.Value})
}
return &usecase.ProductResp{
ID: p.ID.Hex(),
UID: p.UID,
Title: p.Title,
ShortTitle: utils.ToValue(p.ShortTitle),
Details: utils.ToValue(p.Details),
ShortDescription: p.ShortDescription,
Slug: utils.ToValue(p.Slug),
IsPublished: p.IsPublished,
Amount: p.Amount,
StartTime: utils.UnixToRfc3339(utils.ToValue(p.StartTime)),
EndTime: utils.UnixToRfc3339(utils.ToValue(p.EndTime)),
Category: p.Category,
Media: media,
CustomFields: cf,
Tags: tagsResp,
Orders: stats.Orders,
OrdersUpdateTime: utils.UnixToRfc3339(stats.OrdersUpdateTime),
AverageRating: stats.AverageRating,
AverageRatingUpdateTime: utils.UnixToRfc3339(stats.AverageRatingUpdateTime),
FansCount: stats.FansCount,
FansCountUpdateTime: utils.UnixToRfc3339(stats.FansCountUpdateTime),
UpdatedAt: utils.UnixToRfc3339(p.UpdatedAt),
CreatedAt: utils.UnixToRfc3339(p.CreatedAt),
}
}

Some files were not shown because too many files have changed in this diff Show More