Compare commits
35 Commits
main
...
feat/produ
Author | SHA1 | Date |
---|---|---|
|
a14747bbd7 | |
|
8b0aa7a0db | |
|
61870ec7a2 | |
|
4806eea021 | |
|
748d0c0725 | |
|
1a32f58515 | |
|
5d4f15fe8c | |
|
b5a12ceaa2 | |
|
a2729b95ca | |
|
ae920e4f77 | |
|
8d042000f5 | |
|
e98ac37f1c | |
|
44152bf93a | |
|
7951f4b98a | |
|
c4617956e5 | |
|
52905207c6 | |
|
39841cc168 | |
|
cb7e9fb5bb | |
|
6f790d65b3 | |
|
14678b986b | |
|
37b8bf8a74 | |
|
bf8d532c6e | |
|
eafd691dab | |
|
84e608f4bb | |
|
fa9b188811 | |
|
8e1293f4b7 | |
|
dba8d1deeb | |
|
f84f58a460 | |
|
9e3fa8ecd0 | |
|
6e47895174 | |
|
f6e936a760 | |
|
2451bc4257 | |
|
fdc0799fcc | |
|
2e2bdecc48 | |
|
6f27ff3bbc |
|
@ -1,3 +1,3 @@
|
||||||
.idea/
|
.idea/
|
||||||
etc/gateway.yaml
|
etc/product.yaml
|
||||||
.DS_Store
|
.DS_Store
|
|
@ -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
|
||||||
|
|
9
Makefile
9
Makefile
|
@ -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"
|
||||||
|
|
|
@ -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"]
|
|
@ -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...)
|
||||||
|
}
|
|
@ -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...)
|
||||||
|
}
|
|
@ -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,
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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...)
|
||||||
|
}
|
|
@ -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...)
|
||||||
|
}
|
|
@ -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
|
@ -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; // 可改 enum:PENDING, 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
54
go.mod
|
@ -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
124
go.sum
|
@ -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=
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
|
@ -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
|
||||||
|
}
|
|
@ -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
|
||||||
|
}
|
|
@ -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
|
||||||
|
}
|
|
@ -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
|
||||||
|
}
|
|
@ -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
|
||||||
|
}
|
|
@ -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
|
||||||
|
}
|
|
@ -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
|
||||||
|
}
|
|
@ -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
|
||||||
|
}
|
|
@ -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
|
||||||
|
}
|
|
@ -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
|
||||||
|
}
|
|
@ -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
|
||||||
|
}
|
|
@ -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
|
||||||
|
}
|
|
@ -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
|
||||||
|
}
|
|
@ -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
|
||||||
|
}
|
|
@ -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
|
||||||
|
}
|
|
@ -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
|
||||||
|
}
|
|
@ -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
|
||||||
|
}
|
|
@ -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
|
||||||
|
}
|
|
@ -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
|
||||||
|
}
|
|
@ -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
|
||||||
|
}
|
|
@ -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
|
||||||
|
}
|
|
@ -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
|
||||||
|
}
|
|
@ -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,
|
||||||
|
}
|
||||||
|
}
|
|
@ -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
|
||||||
|
}
|
|
@ -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)
|
||||||
|
}
|
|
@ -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)
|
||||||
|
}
|
|
@ -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,
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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)
|
||||||
|
}
|
|
@ -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)
|
||||||
|
}
|
|
@ -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})
|
||||||
|
}
|
||||||
|
|
|
@ -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)
|
||||||
|
}
|
|
@ -0,0 +1,8 @@
|
||||||
|
package domain
|
||||||
|
|
||||||
|
import "time"
|
||||||
|
|
||||||
|
const (
|
||||||
|
DefaultSingleFlyCacheTimeout = 60 * time.Second
|
||||||
|
DefaultFindDataNotFoundTimeout = 5 * time.Second
|
||||||
|
)
|
|
@ -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"
|
||||||
|
}
|
|
@ -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"
|
||||||
|
}
|
|
@ -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"
|
||||||
|
}
|
|
@ -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"
|
||||||
|
}
|
|
@ -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"
|
||||||
|
}
|
|
@ -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"
|
||||||
|
}
|
|
@ -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"
|
||||||
|
}
|
|
@ -0,0 +1 @@
|
||||||
|
package domain
|
|
@ -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"
|
||||||
|
)
|
|
@ -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
|
||||||
|
}
|
|
@ -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
|
||||||
|
}
|
|
@ -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
|
||||||
|
}
|
|
@ -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
|
||||||
|
}
|
|
@ -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()
|
||||||
|
}
|
|
@ -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
|
||||||
|
}
|
|
@ -0,0 +1,49 @@
|
||||||
|
package repository
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"code.30cm.net/digimon/app-cloudep-product-service/pkg/domain/entity"
|
||||||
|
)
|
||||||
|
|
||||||
|
// KYC(Know 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
|
||||||
|
}
|
|
@ -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
|
||||||
|
}
|
|
@ -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
|
||||||
|
}
|
|
@ -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)
|
||||||
|
}
|
|
@ -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)
|
||||||
|
}
|
|
@ -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
|
||||||
|
}
|
|
@ -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
|
||||||
|
}
|
|
@ -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
|
||||||
|
}
|
|
@ -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 // 商品狀態
|
||||||
|
}
|
|
@ -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"` // 封面圖片
|
||||||
|
}
|
|
@ -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)
|
||||||
|
}
|
|
@ -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)
|
||||||
|
}
|
|
@ -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)
|
||||||
|
}
|
|
@ -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)
|
||||||
|
}
|
|
@ -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)
|
||||||
|
}
|
|
@ -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)
|
||||||
|
}
|
|
@ -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
|
||||||
|
}
|
|
@ -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應該與期望值匹配")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
|
@ -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")
|
||||||
|
)
|
|
@ -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
|
||||||
|
}
|
|
@ -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)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
|
@ -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
|
||||||
|
}
|
|
@ -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)
|
||||||
|
}
|
|
@ -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)
|
||||||
|
}
|
|
@ -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)
|
||||||
|
})
|
||||||
|
}
|
|
@ -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
|
||||||
|
}
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
|
@ -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)
|
||||||
|
}
|
|
@ -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)
|
||||||
|
}
|
|
@ -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)
|
||||||
|
})
|
||||||
|
}
|
|
@ -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
|
||||||
|
}
|
|
@ -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
|
||||||
|
}
|
|
@ -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())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
|
@ -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
|
||||||
|
}
|
|
@ -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())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
|
@ -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
Loading…
Reference in New Issue