feat: init project
This commit is contained in:
commit
80f6caf86d
|
@ -0,0 +1,7 @@
|
||||||
|
.idea/
|
||||||
|
go.sum
|
||||||
|
gen_result/
|
||||||
|
etc/member.yaml
|
||||||
|
etc/member.dev.yaml
|
||||||
|
client/
|
||||||
|
.DS_Store
|
|
@ -0,0 +1,134 @@
|
||||||
|
run:
|
||||||
|
timeout: 3m
|
||||||
|
# Exit code when at least one issue was found.
|
||||||
|
# Default: 1
|
||||||
|
issues-exit-code: 2
|
||||||
|
# Include test files or not.
|
||||||
|
# Default: true
|
||||||
|
tests: false
|
||||||
|
|
||||||
|
# Reference URL: https://golangci-lint.run/usage/linters/
|
||||||
|
linters:
|
||||||
|
# Disable everything by default so upgrades to not include new - default
|
||||||
|
# enabled- linters.
|
||||||
|
disable-all: true
|
||||||
|
# Specifically enable linters we want to use.
|
||||||
|
enable:
|
||||||
|
# - depguard
|
||||||
|
- errcheck
|
||||||
|
# - godot
|
||||||
|
- gofmt
|
||||||
|
- goimports
|
||||||
|
- gosimple
|
||||||
|
- govet
|
||||||
|
- ineffassign
|
||||||
|
- misspell
|
||||||
|
- revive
|
||||||
|
- typecheck
|
||||||
|
- unused
|
||||||
|
- asasalint
|
||||||
|
- asciicheck
|
||||||
|
- bidichk
|
||||||
|
- bodyclose
|
||||||
|
- contextcheck
|
||||||
|
- wastedassign
|
||||||
|
- whitespace
|
||||||
|
- thelper
|
||||||
|
- tparallel
|
||||||
|
- unconvert
|
||||||
|
- unparam
|
||||||
|
- usestdlibvars
|
||||||
|
- tenv
|
||||||
|
- testableexamples
|
||||||
|
- stylecheck
|
||||||
|
- sqlclosecheck
|
||||||
|
- nosprintfhostport
|
||||||
|
- paralleltest
|
||||||
|
- prealloc
|
||||||
|
- predeclared
|
||||||
|
- promlinter
|
||||||
|
- reassign
|
||||||
|
- rowserrcheck
|
||||||
|
- nakedret
|
||||||
|
- nestif
|
||||||
|
- nilerr
|
||||||
|
- nilnil
|
||||||
|
- nlreturn
|
||||||
|
- noctx
|
||||||
|
- nolintlint
|
||||||
|
- nonamedreturns
|
||||||
|
- decorder
|
||||||
|
- dogsled
|
||||||
|
- dupword
|
||||||
|
- durationcheck
|
||||||
|
- errchkjson
|
||||||
|
- errname
|
||||||
|
- errorlint
|
||||||
|
# - execinquery
|
||||||
|
- exhaustive
|
||||||
|
- exportloopref
|
||||||
|
- forbidigo
|
||||||
|
- forcetypeassert
|
||||||
|
- gochecknoinits
|
||||||
|
- gocognit
|
||||||
|
- goconst
|
||||||
|
- gocritic
|
||||||
|
- gocyclo
|
||||||
|
- goheader
|
||||||
|
- gomoddirectives
|
||||||
|
- goprintffuncname
|
||||||
|
- gosec
|
||||||
|
- grouper
|
||||||
|
- importas
|
||||||
|
- interfacebloat
|
||||||
|
- lll
|
||||||
|
- loggercheck
|
||||||
|
- maintidx
|
||||||
|
- makezero
|
||||||
|
|
||||||
|
issues:
|
||||||
|
exclude-rules:
|
||||||
|
- path: _test\.go
|
||||||
|
linters:
|
||||||
|
- funlen
|
||||||
|
- goconst
|
||||||
|
- interfacer
|
||||||
|
- dupl
|
||||||
|
- lll
|
||||||
|
- goerr113
|
||||||
|
- errcheck
|
||||||
|
- gocritic
|
||||||
|
- cyclop
|
||||||
|
- wrapcheck
|
||||||
|
- gocognit
|
||||||
|
- contextcheck
|
||||||
|
|
||||||
|
exclude-dirs:
|
||||||
|
- internal/logic
|
||||||
|
|
||||||
|
exclude-files:
|
||||||
|
- .*_test.go
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
linters-settings:
|
||||||
|
gci:
|
||||||
|
sections:
|
||||||
|
- standard # Standard section: captures all standard packages.
|
||||||
|
- default # Default section: contains all imports that could not be matched to another section type.
|
||||||
|
gocognit:
|
||||||
|
# Minimal code complexity to report.
|
||||||
|
# Default: 30 (but we recommend 10-20)
|
||||||
|
min-complexity: 40
|
||||||
|
nestif:
|
||||||
|
# Minimal complexity of if statements to report.
|
||||||
|
# Default: 5
|
||||||
|
min-complexity: 10
|
||||||
|
lll:
|
||||||
|
# Max line length, lines longer will be reported.
|
||||||
|
# '\t' is counted as 1 character by default, and can be changed with the tab-width option.
|
||||||
|
# Default: 120.
|
||||||
|
line-length: 200
|
||||||
|
# Tab width in spaces.
|
||||||
|
# Default: 1
|
||||||
|
tab-width: 1
|
|
@ -0,0 +1,53 @@
|
||||||
|
# go-zero 生成風格
|
||||||
|
GO_ZERO_STYLE=go_zero
|
||||||
|
GO ?= go
|
||||||
|
GOFMT ?= gofmt "-s"
|
||||||
|
GOFILES := $(shell find . -name "*.go")
|
||||||
|
LDFLAGS := -s -w
|
||||||
|
VERSION="v1.0.4"
|
||||||
|
DOCKER_REPO="igs170911/member"
|
||||||
|
GIT_COMMIT ?= $(shell git rev-parse --short HEAD)
|
||||||
|
|
||||||
|
.PHONY: test
|
||||||
|
test: # 進行測試
|
||||||
|
go test -v --cover ./...
|
||||||
|
|
||||||
|
.PHONY: fmt
|
||||||
|
fmt: # 格式優化
|
||||||
|
$(GOFMT) -w $(GOFILES)
|
||||||
|
goimports -w ./
|
||||||
|
golangci-lint run
|
||||||
|
|
||||||
|
.PHONY: gen-rpc
|
||||||
|
gen-rpc: # 建立 rpc code
|
||||||
|
goctl rpc protoc ./generate/protobuf/member.proto -m --style=$(GO_ZERO_STYLE) --go_out=./gen_result/pb --go-grpc_out=./gen_result/pb --zrpc_out=.
|
||||||
|
go mod tidy
|
||||||
|
@echo "Generate core-api files successfully"
|
||||||
|
|
||||||
|
.PHONY: run-docker
|
||||||
|
run-docker: # 建立 rpc code
|
||||||
|
docker run --platform=linux/arm64/v8 -p 8080:8080 $(DOCKER_REPO):$(VERSION)
|
||||||
|
|
||||||
|
.PHONY: build-docker
|
||||||
|
build-docker:
|
||||||
|
cp ./build/Dockerfile Dockerfile
|
||||||
|
docker buildx build \
|
||||||
|
-t $(DOCKER_REPO):$(VERSION) \
|
||||||
|
--build-arg VERSION=$(VERSION) \
|
||||||
|
--build-arg GIT_COMMIT=$(GIT_COMMIT) \
|
||||||
|
--secret id=ssh_key,src=./build/id_ed25519 \
|
||||||
|
--progress=plain .
|
||||||
|
rm -rf Dockerfile
|
||||||
|
@echo "Generate core-api files successfully"
|
||||||
|
|
||||||
|
|
||||||
|
.PHONY: mock-gen
|
||||||
|
mock-gen: # 建立 mock 資料
|
||||||
|
mockgen -source=./pkg/domain/repository/account.go -destination=./pkg/mock/repository/account.go -package=mock
|
||||||
|
mockgen -source=./pkg/domain/repository/account_uid.go -destination=./pkg/mock/repository/account_uid.go -package=mock
|
||||||
|
mockgen -source=./pkg/domain/repository/auto_id.go -destination=./pkg/mock/repository/auto_id.go -package=mock
|
||||||
|
mockgen -source=./pkg/domain/repository/user.go -destination=./pkg/mock/repository/user.go -package=mock
|
||||||
|
mockgen -source=./pkg/domain/repository/verify_code.go -destination=./pkg/mock/repository/verify_code.go -package=mock
|
||||||
|
mockgen -source=./pkg/domain/usecase/generate_uid.go -destination=./pkg/mock/usecase/generate_uid.go -package=mock
|
||||||
|
|
||||||
|
@echo "Generate mock files successfully"
|
|
@ -0,0 +1,42 @@
|
||||||
|
###########
|
||||||
|
# BUILDER #
|
||||||
|
###########
|
||||||
|
|
||||||
|
FROM golang:1.23.4 AS builder
|
||||||
|
|
||||||
|
ARG VERSION
|
||||||
|
ARG BUILT
|
||||||
|
ARG GIT_COMMIT
|
||||||
|
|
||||||
|
# private go packages
|
||||||
|
ENV GOPRIVATE=code.30cm.net
|
||||||
|
ENV FLAG="-s -w -X main.Version=${VERSION} -X main.Built=${BUILT} -X main.GitCommit=${GIT_COMMIT}"
|
||||||
|
WORKDIR /app
|
||||||
|
COPY . .
|
||||||
|
|
||||||
|
|
||||||
|
RUN apt-get update && \
|
||||||
|
apt-get install -y git
|
||||||
|
|
||||||
|
# Make the root foler for our ssh
|
||||||
|
RUN --mount=type=secret,id=ssh_key,dst=/root/.ssh/id_rsa \
|
||||||
|
ssh-keyscan git.30cm.net >> /root/.ssh/known_hosts
|
||||||
|
|
||||||
|
|
||||||
|
RUN --mount=type=ssh go mod download
|
||||||
|
|
||||||
|
RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build \
|
||||||
|
-ldflags "$FLAG" \
|
||||||
|
-o member
|
||||||
|
|
||||||
|
##########
|
||||||
|
## FINAL #
|
||||||
|
##########
|
||||||
|
#
|
||||||
|
FROM gcr.io/distroless/static-debian11
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
COPY --from=builder /app/member /app/member
|
||||||
|
COPY --from=builder /app/etc/member.yaml /app/etc/member.yaml
|
||||||
|
EXPOSE 8080
|
||||||
|
CMD ["/app/member"]
|
|
@ -0,0 +1,71 @@
|
||||||
|
version: "3.9"
|
||||||
|
|
||||||
|
services:
|
||||||
|
app:
|
||||||
|
image: igs170911/member:v1.0.4
|
||||||
|
container_name: app-service
|
||||||
|
ports:
|
||||||
|
- "8080:8080" # 替換為您的應用服務的公開端口
|
||||||
|
depends_on:
|
||||||
|
- mongo
|
||||||
|
- etcd
|
||||||
|
- redis
|
||||||
|
environment:
|
||||||
|
MONGO_URI: mongodb://mongo:27017/appdb
|
||||||
|
ETCD_ENDPOINT: http://etcd:2379
|
||||||
|
REDIS_HOST: redis
|
||||||
|
REDIS_PORT: 6379
|
||||||
|
networks:
|
||||||
|
- app-network
|
||||||
|
|
||||||
|
mongo:
|
||||||
|
image: mongo:8.0
|
||||||
|
container_name: mongo
|
||||||
|
restart: always
|
||||||
|
ports:
|
||||||
|
- "27017:27017"
|
||||||
|
environment:
|
||||||
|
MONGO_INITDB_ROOT_USERNAME: root
|
||||||
|
MONGO_INITDB_ROOT_PASSWORD: example
|
||||||
|
networks:
|
||||||
|
- app-network
|
||||||
|
volumes:
|
||||||
|
- mongo-data:/data/db
|
||||||
|
|
||||||
|
etcd:
|
||||||
|
image: quay.io/coreos/etcd:v3.5.5
|
||||||
|
container_name: etcd
|
||||||
|
restart: always
|
||||||
|
command: >
|
||||||
|
/usr/local/bin/etcd
|
||||||
|
--data-dir=/etcd-data
|
||||||
|
--name=etcd
|
||||||
|
--listen-client-urls=http://0.0.0.0:2379
|
||||||
|
--advertise-client-urls=http://etcd:2379
|
||||||
|
ports:
|
||||||
|
- "2379:2379"
|
||||||
|
- "2380:2380"
|
||||||
|
networks:
|
||||||
|
- app-network
|
||||||
|
volumes:
|
||||||
|
- etcd-data:/etcd-data
|
||||||
|
|
||||||
|
redis:
|
||||||
|
image: redis:7.0
|
||||||
|
container_name: redis
|
||||||
|
restart: always
|
||||||
|
ports:
|
||||||
|
- "6379:6379"
|
||||||
|
networks:
|
||||||
|
- app-network
|
||||||
|
volumes:
|
||||||
|
- redis-data:/data
|
||||||
|
|
||||||
|
networks:
|
||||||
|
app-network:
|
||||||
|
driver: bridge
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
mongo-data:
|
||||||
|
etcd-data:
|
||||||
|
redis-data:
|
|
@ -0,0 +1,43 @@
|
||||||
|
Name: member.rpc
|
||||||
|
ListenOn: 0.0.0.0:8080
|
||||||
|
Etcd:
|
||||||
|
Hosts:
|
||||||
|
- 127.0.0.1:2379
|
||||||
|
Key: member.rpc
|
||||||
|
|
||||||
|
Cache:
|
||||||
|
- Host: 127.0.0.1:6379
|
||||||
|
type: node
|
||||||
|
CacheExpireTime: 1s
|
||||||
|
CacheWithNotFoundExpiry: 1s
|
||||||
|
|
||||||
|
Mongo:
|
||||||
|
Schema: mongodb
|
||||||
|
Host: 127.0.0.1
|
||||||
|
User: "admin"
|
||||||
|
Password: "123"
|
||||||
|
Port: "27017"
|
||||||
|
Database: digimon_member
|
||||||
|
ReplicaName: "rs0"
|
||||||
|
MaxStaleness: 30m
|
||||||
|
MaxPoolSize: 30
|
||||||
|
MinPoolSize: 10
|
||||||
|
MaxConnIdleTime: 30m
|
||||||
|
Compressors:
|
||||||
|
- f
|
||||||
|
EnableStandardReadWriteSplitMode: true
|
||||||
|
ConnectTimeoutMs : 300
|
||||||
|
|
||||||
|
Bcrypt:
|
||||||
|
Cost: 10
|
||||||
|
|
||||||
|
GoogleAuth:
|
||||||
|
ClientID:
|
||||||
|
AuthURL:
|
||||||
|
|
||||||
|
LineAuth:
|
||||||
|
ClientID :
|
||||||
|
ClientSecret :
|
||||||
|
RedirectURI :
|
||||||
|
|
||||||
|
Host: 127.0.0.1
|
|
@ -0,0 +1,248 @@
|
||||||
|
syntax = "proto3";
|
||||||
|
|
||||||
|
package member;
|
||||||
|
option go_package="./member";
|
||||||
|
|
||||||
|
// OKResp
|
||||||
|
message OKResp {}
|
||||||
|
// NoneReq
|
||||||
|
message NoneReq {}
|
||||||
|
|
||||||
|
// ================ enum ================
|
||||||
|
enum VerifyType {
|
||||||
|
VERIFY_NONE = 0; // 初始(異常)
|
||||||
|
VERIFY_OK = 1;
|
||||||
|
VERIFY_NOT = 2; // 尚未
|
||||||
|
}
|
||||||
|
|
||||||
|
enum AlarmType {
|
||||||
|
ALARM_NONE = 0; // 初始(異常)
|
||||||
|
ALARM_NOT = 1; // 未告警
|
||||||
|
ALARM_SYSTEM = 2; // 系統告警中
|
||||||
|
}
|
||||||
|
|
||||||
|
enum MemberStatus {
|
||||||
|
STATUS_NONE = 0; // 初始(異常)
|
||||||
|
STATUS_VERIFY = 1; // 尚未驗證
|
||||||
|
STATUS_COMPLETE = 2; // 帳號啟用中
|
||||||
|
STATUS_DISABLE = 3; // 帳號停權中
|
||||||
|
}
|
||||||
|
|
||||||
|
// ================ enum ================
|
||||||
|
|
||||||
|
|
||||||
|
// ================ common ================
|
||||||
|
message Pager {
|
||||||
|
int64 total =1;
|
||||||
|
int64 size=2;
|
||||||
|
int64 index=3;
|
||||||
|
}
|
||||||
|
// ================ common ================
|
||||||
|
|
||||||
|
|
||||||
|
// ================ account ================
|
||||||
|
message CreateLoginUserReq {
|
||||||
|
string login_id = 1;
|
||||||
|
int64 platform = 2;
|
||||||
|
string token = 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
message BindingUserReq {
|
||||||
|
string uid = 1;
|
||||||
|
string login_id = 2;
|
||||||
|
int64 type = 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
message BindingUserResp {
|
||||||
|
string uid = 1;
|
||||||
|
string login_id = 2;
|
||||||
|
int64 type = 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
message CreateUserInfoReq {
|
||||||
|
string uid = 1;
|
||||||
|
VerifyType verify_type = 2;
|
||||||
|
AlarmType alarm_type = 3;
|
||||||
|
MemberStatus status = 4;
|
||||||
|
string language = 5;
|
||||||
|
string currency = 6;
|
||||||
|
optional string avatar= 7;
|
||||||
|
optional string nick_name = 8;
|
||||||
|
optional string full_name = 9;
|
||||||
|
optional int64 gender = 10;
|
||||||
|
optional int64 birthdate = 11;
|
||||||
|
optional string phone_number = 12;
|
||||||
|
optional string email = 13;
|
||||||
|
optional string address = 14;
|
||||||
|
}
|
||||||
|
|
||||||
|
message GetAccountInfoResp {
|
||||||
|
CreateLoginUserReq data = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateUserInfoReq 不處理邏輯給不給改,這裡只關新增修改刪除
|
||||||
|
message UpdateUserInfoReq {
|
||||||
|
string uid = 1;
|
||||||
|
optional string language = 2;
|
||||||
|
optional string currency = 3;
|
||||||
|
optional string nick_name = 4;
|
||||||
|
optional string avatar = 5;
|
||||||
|
optional VerifyType verify_type = 6;
|
||||||
|
optional AlarmType alarm_type = 7;
|
||||||
|
optional MemberStatus status = 8;
|
||||||
|
optional string full_name = 9;
|
||||||
|
optional int64 gender = 10;
|
||||||
|
optional int64 birthdate = 11;
|
||||||
|
optional string phone_number = 12;
|
||||||
|
optional string email = 13;
|
||||||
|
optional string address = 14;
|
||||||
|
}
|
||||||
|
|
||||||
|
message GetUIDByAccountReq {
|
||||||
|
string account = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message GetUIDByAccountResp {
|
||||||
|
string uid = 1;
|
||||||
|
string account =2;
|
||||||
|
}
|
||||||
|
|
||||||
|
message UpdateTokenReq {
|
||||||
|
string account = 1;
|
||||||
|
string token = 2;
|
||||||
|
int64 platform=3;
|
||||||
|
}
|
||||||
|
|
||||||
|
message GenerateRefreshCodeReq {
|
||||||
|
string account = 1;
|
||||||
|
int32 code_type =2;
|
||||||
|
}
|
||||||
|
|
||||||
|
message VerifyCode {
|
||||||
|
string verify_code = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message GenerateRefreshCodeResp {
|
||||||
|
VerifyCode data = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message VerifyRefreshCodeReq {
|
||||||
|
string account = 1;
|
||||||
|
int32 code_type =2;
|
||||||
|
string verify_code = 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
message UpdateStatusReq {
|
||||||
|
string uid = 1;
|
||||||
|
MemberStatus status = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
message GetUserInfoReq {
|
||||||
|
string uid = 1;
|
||||||
|
optional string nick_name =2;
|
||||||
|
}
|
||||||
|
|
||||||
|
message UserInfo {
|
||||||
|
string uid = 1;
|
||||||
|
VerifyType verify_type = 2;
|
||||||
|
AlarmType alarm_type = 3;
|
||||||
|
MemberStatus status = 4;
|
||||||
|
string language = 5;
|
||||||
|
string currency = 6;
|
||||||
|
string avatar = 7;
|
||||||
|
int64 create_time=8;
|
||||||
|
int64 update_time=9;
|
||||||
|
optional string nick_name = 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
message GetUserInfoResp {
|
||||||
|
UserInfo data = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message ListUserInfoReq {
|
||||||
|
optional VerifyType verify_type = 1;
|
||||||
|
optional AlarmType alarm_type = 2;
|
||||||
|
optional MemberStatus status = 3;
|
||||||
|
optional int64 create_start_time = 4;
|
||||||
|
optional int64 create_end_time = 5;
|
||||||
|
int64 page_size =6;
|
||||||
|
int64 page_index=7;
|
||||||
|
}
|
||||||
|
|
||||||
|
message ListUserInfoResp {
|
||||||
|
repeated UserInfo data = 1;
|
||||||
|
Pager page =2;
|
||||||
|
}
|
||||||
|
|
||||||
|
message VerifyAuthResultReq {
|
||||||
|
string token = 1;
|
||||||
|
optional string account = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
message VerifyAuthResultResp {
|
||||||
|
bool status = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message TwitterAccessTokenResp {
|
||||||
|
string token = 1;
|
||||||
|
}
|
||||||
|
message BindVerifyEmailReq {
|
||||||
|
string uid = 1;
|
||||||
|
string email = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
message BindVerifyPhoneReq {
|
||||||
|
string uid = 1;
|
||||||
|
string phone = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
message LineAccessTokenResp {
|
||||||
|
string token = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message LineUserProfile {
|
||||||
|
string name = 1;
|
||||||
|
string email = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
service Account {
|
||||||
|
// CreateUserAccount 建立帳號與密碼 -> 可登入,但可不可以做其他事情看業務流程,也可以只註冊就好
|
||||||
|
rpc CreateUserAccount(CreateLoginUserReq) returns(OKResp);
|
||||||
|
// GetUserAccountInfo 取得帳號密碼資料
|
||||||
|
rpc GetUserAccountInfo(GetUIDByAccountReq) returns(GetAccountInfoResp);
|
||||||
|
// UpdateUserToken 更新密碼
|
||||||
|
rpc UpdateUserToken(UpdateTokenReq) returns(OKResp);
|
||||||
|
// GetUIDByAccount 用帳號換取 UID
|
||||||
|
rpc GetUIDByAccount(GetUIDByAccountReq) returns(GetUIDByAccountResp);
|
||||||
|
// BindAccount 綁定帳號 -> account bind to UID
|
||||||
|
rpc BindAccount(BindingUserReq) returns(BindingUserResp);
|
||||||
|
// BindUserInfo 初次,綁定 User Info
|
||||||
|
rpc BindUserInfo(CreateUserInfoReq) returns(OKResp);
|
||||||
|
// BindVerifyEmail 綁定 Email
|
||||||
|
rpc BindVerifyEmail(BindVerifyEmailReq) returns (OKResp);
|
||||||
|
// BindVerifyPhone 綁定 Phone
|
||||||
|
rpc BindVerifyPhone(BindVerifyPhoneReq) returns (OKResp);
|
||||||
|
// UpdateUserInfo 更新 User Info
|
||||||
|
rpc UpdateUserInfo(UpdateUserInfoReq) returns(OKResp);
|
||||||
|
// UpdateStatus 修改狀態
|
||||||
|
rpc UpdateStatus(UpdateStatusReq) returns(OKResp);
|
||||||
|
// GetUserInfo 取得會員資訊
|
||||||
|
rpc GetUserInfo(GetUserInfoReq) returns(GetUserInfoResp);
|
||||||
|
// ListMember 取得會員列表
|
||||||
|
rpc ListMember(ListUserInfoReq) returns(ListUserInfoResp);
|
||||||
|
// GenerateRefreshCode 這個帳號驗證碼(十分鐘),通用的
|
||||||
|
rpc GenerateRefreshCode(GenerateRefreshCodeReq) returns(GenerateRefreshCodeResp);
|
||||||
|
// VerifyRefreshCode 驗證忘記密碼 token
|
||||||
|
rpc VerifyRefreshCode(VerifyRefreshCodeReq) returns(OKResp);
|
||||||
|
// CheckRefreshCode 驗證忘記密碼 token 不刪除,只確認)
|
||||||
|
rpc CheckRefreshCode(VerifyRefreshCodeReq) returns(OKResp);
|
||||||
|
// VerifyGoogleAuthResult 驗證 google 登入是否有效
|
||||||
|
rpc VerifyGoogleAuthResult(VerifyAuthResultReq)returns(VerifyAuthResultResp);
|
||||||
|
// VerifyPlatformAuthResult 驗證 google 登入是否有效
|
||||||
|
rpc VerifyPlatformAuthResult(VerifyAuthResultReq)returns(VerifyAuthResultResp);
|
||||||
|
// LineCodeToAccessToken Line 驗證相關
|
||||||
|
rpc LineCodeToAccessToken(NoneReq) returns (LineAccessTokenResp);
|
||||||
|
// LineGetProfileByAccessToken Line 驗證相關
|
||||||
|
rpc LineGetProfileByAccessToken(NoneReq) returns (LineUserProfile);
|
||||||
|
}
|
||||||
|
// ================ account ================
|
|
@ -0,0 +1,147 @@
|
||||||
|
module app-cloudep-member-server
|
||||||
|
|
||||||
|
go 1.23.4
|
||||||
|
|
||||||
|
require (
|
||||||
|
code.30cm.net/digimon/library-go/errs v1.2.12
|
||||||
|
code.30cm.net/digimon/library-go/mongo v0.0.9
|
||||||
|
code.30cm.net/digimon/library-go/utils/invited_code v1.2.5
|
||||||
|
code.30cm.net/digimon/library-go/validator v1.0.0
|
||||||
|
github.com/alicebob/miniredis/v2 v2.33.0
|
||||||
|
github.com/stretchr/testify v1.9.0
|
||||||
|
github.com/testcontainers/testcontainers-go v0.34.0
|
||||||
|
github.com/zeromicro/go-zero v1.7.4
|
||||||
|
go.mongodb.org/mongo-driver v1.17.1
|
||||||
|
go.uber.org/mock v0.5.0
|
||||||
|
golang.org/x/crypto v0.29.0
|
||||||
|
google.golang.org/grpc v1.69.2
|
||||||
|
google.golang.org/protobuf v1.36.1
|
||||||
|
)
|
||||||
|
|
||||||
|
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/cenkalti/backoff/v4 v4.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-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/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/fatih/color v1.18.0 // indirect
|
||||||
|
github.com/felixge/httpsnoop v1.0.4 // indirect
|
||||||
|
github.com/gabriel-vasile/mimetype v1.4.3 // indirect
|
||||||
|
github.com/go-logr/logr v1.4.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/jsonreference v0.20.2 // indirect
|
||||||
|
github.com/go-openapi/swag v0.22.4 // indirect
|
||||||
|
github.com/go-playground/locales v0.14.1 // indirect
|
||||||
|
github.com/go-playground/universal-translator v0.18.1 // indirect
|
||||||
|
github.com/go-playground/validator/v10 v10.22.0 // indirect
|
||||||
|
github.com/gogo/protobuf v1.3.2 // indirect
|
||||||
|
github.com/golang/mock v1.6.0 // indirect
|
||||||
|
github.com/golang/protobuf v1.5.4 // indirect
|
||||||
|
github.com/golang/snappy v0.0.4 // indirect
|
||||||
|
github.com/google/gnostic-models v0.6.8 // indirect
|
||||||
|
github.com/google/go-cmp v0.6.0 // indirect
|
||||||
|
github.com/google/gofuzz v1.2.0 // indirect
|
||||||
|
github.com/google/uuid v1.6.0 // indirect
|
||||||
|
github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0 // indirect
|
||||||
|
github.com/josharian/intern v1.0.0 // indirect
|
||||||
|
github.com/json-iterator/go v1.1.12 // indirect
|
||||||
|
github.com/klauspost/compress v1.17.9 // indirect
|
||||||
|
github.com/leodido/go-urn v1.4.0 // 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/mattn/go-colorable v0.1.13 // 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/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/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/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.20.5 // indirect
|
||||||
|
github.com/prometheus/client_model v0.6.1 // indirect
|
||||||
|
github.com/prometheus/common v0.55.0 // indirect
|
||||||
|
github.com/prometheus/procfs v0.15.1 // indirect
|
||||||
|
github.com/redis/go-redis/v9 v9.7.0 // indirect
|
||||||
|
github.com/shirou/gopsutil/v3 v3.23.12 // indirect
|
||||||
|
github.com/shoenig/go-m1cpu v0.1.6 // indirect
|
||||||
|
github.com/shopspring/decimal v1.4.0 // indirect
|
||||||
|
github.com/sirupsen/logrus v1.9.3 // 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/client/pkg/v3 v3.5.15 // indirect
|
||||||
|
go.etcd.io/etcd/client/v3 v3.5.15 // indirect
|
||||||
|
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 // indirect
|
||||||
|
go.opentelemetry.io/otel v1.31.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/otlptracegrpc v1.24.0 // indirect
|
||||||
|
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.24.0 // indirect
|
||||||
|
go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.24.0 // indirect
|
||||||
|
go.opentelemetry.io/otel/exporters/zipkin v1.24.0 // indirect
|
||||||
|
go.opentelemetry.io/otel/metric v1.31.0 // indirect
|
||||||
|
go.opentelemetry.io/otel/sdk v1.31.0 // indirect
|
||||||
|
go.opentelemetry.io/otel/trace v1.31.0 // indirect
|
||||||
|
go.opentelemetry.io/proto/otlp v1.3.1 // indirect
|
||||||
|
go.uber.org/atomic v1.10.0 // indirect
|
||||||
|
go.uber.org/automaxprocs v1.6.0 // indirect
|
||||||
|
go.uber.org/multierr v1.9.0 // indirect
|
||||||
|
go.uber.org/zap v1.24.0 // indirect
|
||||||
|
golang.org/x/net v0.31.0 // indirect
|
||||||
|
golang.org/x/oauth2 v0.23.0 // indirect
|
||||||
|
golang.org/x/sync v0.9.0 // indirect
|
||||||
|
golang.org/x/sys v0.27.0 // indirect
|
||||||
|
golang.org/x/term v0.26.0 // indirect
|
||||||
|
golang.org/x/text v0.20.0 // indirect
|
||||||
|
golang.org/x/time v0.8.0 // indirect
|
||||||
|
google.golang.org/genproto/googleapis/api v0.0.0-20241015192408-796eee8c2d53 // indirect
|
||||||
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20241015192408-796eee8c2d53 // indirect
|
||||||
|
gopkg.in/inf.v0 v0.9.1 // indirect
|
||||||
|
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||||
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
|
k8s.io/api v0.29.3 // indirect
|
||||||
|
k8s.io/apimachinery v0.29.4 // indirect
|
||||||
|
k8s.io/client-go v0.29.3 // indirect
|
||||||
|
k8s.io/klog/v2 v2.110.1 // indirect
|
||||||
|
k8s.io/kube-openapi v0.0.0-20231010175941-2dd684a91f00 // indirect
|
||||||
|
k8s.io/utils v0.0.0-20240711033017-18e509b52bc8 // indirect
|
||||||
|
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect
|
||||||
|
sigs.k8s.io/structured-merge-diff/v4 v4.4.1 // indirect
|
||||||
|
sigs.k8s.io/yaml v1.3.0 // indirect
|
||||||
|
)
|
|
@ -0,0 +1,50 @@
|
||||||
|
package config
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/zeromicro/go-zero/core/stores/cache"
|
||||||
|
"github.com/zeromicro/go-zero/core/stores/redis"
|
||||||
|
"github.com/zeromicro/go-zero/zrpc"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Config struct {
|
||||||
|
zrpc.RpcServerConf
|
||||||
|
redis.RedisConf
|
||||||
|
// 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
|
||||||
|
}
|
||||||
|
|
||||||
|
// 密碼加密層數
|
||||||
|
Bcrypt struct {
|
||||||
|
Cost int
|
||||||
|
}
|
||||||
|
|
||||||
|
GoogleAuth struct {
|
||||||
|
ClientID string
|
||||||
|
AuthURL string
|
||||||
|
}
|
||||||
|
|
||||||
|
LineAuth struct {
|
||||||
|
ClientID string
|
||||||
|
ClientSecret string
|
||||||
|
RedirectURI string
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,85 @@
|
||||||
|
package accountlogic
|
||||||
|
|
||||||
|
import (
|
||||||
|
domain "app-cloudep-member-server/pkg/domain/member"
|
||||||
|
"app-cloudep-member-server/pkg/domain/usecase"
|
||||||
|
"code.30cm.net/digimon/library-go/errs"
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"math"
|
||||||
|
|
||||||
|
"app-cloudep-member-server/gen_result/pb/member"
|
||||||
|
"app-cloudep-member-server/internal/svc"
|
||||||
|
|
||||||
|
"github.com/zeromicro/go-zero/core/logx"
|
||||||
|
)
|
||||||
|
|
||||||
|
type BindAccountLogic struct {
|
||||||
|
ctx context.Context
|
||||||
|
svcCtx *svc.ServiceContext
|
||||||
|
logx.Logger
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewBindAccountLogic(ctx context.Context, svcCtx *svc.ServiceContext) *BindAccountLogic {
|
||||||
|
return &BindAccountLogic{
|
||||||
|
ctx: ctx,
|
||||||
|
svcCtx: svcCtx,
|
||||||
|
Logger: logx.WithContext(ctx),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type bindLoginUserReq struct {
|
||||||
|
Account string `json:"account" validate:"account"`
|
||||||
|
UID string `json:"uid" `
|
||||||
|
Type int64 `json:"type" validate:"required,oneof=1 2 3"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// BindAccount 綁定帳號 -> account bind to UID
|
||||||
|
func (l *BindAccountLogic) BindAccount(in *member.BindingUserReq) (*member.BindingUserResp, error) {
|
||||||
|
// 驗證資料
|
||||||
|
if err := l.svcCtx.Validate.ValidateAll(&bindLoginUserReq{
|
||||||
|
Account: in.GetLoginId(),
|
||||||
|
Type: in.GetType(),
|
||||||
|
}); err != nil {
|
||||||
|
return nil, errs.InvalidFormat(err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
// 先確定有這個Account
|
||||||
|
if _, err := l.svcCtx.AccountUseCase.GetUserAccountInfo(l.ctx, usecase.GetUIDByAccountRequest{Account: in.GetLoginId()}); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var err error
|
||||||
|
uid := in.GetUid()
|
||||||
|
// 有 UID 綁看看,沒帶UID 近來,確認沒重複就直接綁一個給他
|
||||||
|
if in.GetUid() == "" {
|
||||||
|
uid, err = l.svcCtx.AccountUseCase.Generate(l.ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
t, err := int64ToInt32Safe(in.GetType())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if _, err := l.svcCtx.AccountUseCase.BindAccount(l.ctx, usecase.BindingUser{
|
||||||
|
LoginID: in.LoginId,
|
||||||
|
UID: uid,
|
||||||
|
Type: domain.AccountType(t),
|
||||||
|
}); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &member.BindingUserResp{
|
||||||
|
LoginId: in.LoginId,
|
||||||
|
Uid: uid,
|
||||||
|
Type: in.GetType(),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func int64ToInt32Safe(value int64) (int32, error) {
|
||||||
|
if value > math.MaxInt32 || value < math.MinInt32 {
|
||||||
|
return 0, fmt.Errorf("value %d is out of int32 range", value)
|
||||||
|
}
|
||||||
|
return int32(value), nil
|
||||||
|
}
|
|
@ -0,0 +1,46 @@
|
||||||
|
package accountlogic
|
||||||
|
|
||||||
|
import (
|
||||||
|
"app-cloudep-member-server/gen_result/pb/member"
|
||||||
|
"app-cloudep-member-server/internal/svc"
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/zeromicro/go-zero/core/logx"
|
||||||
|
)
|
||||||
|
|
||||||
|
type BindUserInfoLogic struct {
|
||||||
|
ctx context.Context
|
||||||
|
svcCtx *svc.ServiceContext
|
||||||
|
logx.Logger
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewBindUserInfoLogic(ctx context.Context, svcCtx *svc.ServiceContext) *BindUserInfoLogic {
|
||||||
|
return &BindUserInfoLogic{
|
||||||
|
ctx: ctx,
|
||||||
|
svcCtx: svcCtx,
|
||||||
|
Logger: logx.WithContext(ctx),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type createUserInfo struct {
|
||||||
|
Uid string `validate:"required"`
|
||||||
|
VerifyType int32 `validate:"required,oneof=0 1 2 3"`
|
||||||
|
AlarmType int32 `validate:"required,oneof=0 1 2"`
|
||||||
|
Status int32 `validate:"required,oneof=1 2 3 4 5 6"`
|
||||||
|
RoleId string `validate:"required"`
|
||||||
|
Language string `validate:"required"`
|
||||||
|
Currency string `validate:"required"`
|
||||||
|
NickName string `validate:"required"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type createUserInfoReq struct {
|
||||||
|
Uid string `validate:"required"` // 唯一辨識碼
|
||||||
|
Language string `validate:"required"`
|
||||||
|
Currency string `validate:"required"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// BindUserInfo 初次,綁定 User Info
|
||||||
|
func (l *BindUserInfoLogic) BindUserInfo(in *member.CreateUserInfoReq) (*member.OKResp, error) {
|
||||||
|
|
||||||
|
return &member.OKResp{}, nil
|
||||||
|
}
|
|
@ -0,0 +1,31 @@
|
||||||
|
package accountlogic
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"app-cloudep-member-server/gen_result/pb/member"
|
||||||
|
"app-cloudep-member-server/internal/svc"
|
||||||
|
|
||||||
|
"github.com/zeromicro/go-zero/core/logx"
|
||||||
|
)
|
||||||
|
|
||||||
|
type BindVerifyEmailLogic struct {
|
||||||
|
ctx context.Context
|
||||||
|
svcCtx *svc.ServiceContext
|
||||||
|
logx.Logger
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewBindVerifyEmailLogic(ctx context.Context, svcCtx *svc.ServiceContext) *BindVerifyEmailLogic {
|
||||||
|
return &BindVerifyEmailLogic{
|
||||||
|
ctx: ctx,
|
||||||
|
svcCtx: svcCtx,
|
||||||
|
Logger: logx.WithContext(ctx),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// BindVerifyEmail 綁定 Email
|
||||||
|
func (l *BindVerifyEmailLogic) BindVerifyEmail(in *member.BindVerifyEmailReq) (*member.OKResp, error) {
|
||||||
|
// todo: add your logic here and delete this line
|
||||||
|
|
||||||
|
return &member.OKResp{}, nil
|
||||||
|
}
|
|
@ -0,0 +1,31 @@
|
||||||
|
package accountlogic
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"app-cloudep-member-server/gen_result/pb/member"
|
||||||
|
"app-cloudep-member-server/internal/svc"
|
||||||
|
|
||||||
|
"github.com/zeromicro/go-zero/core/logx"
|
||||||
|
)
|
||||||
|
|
||||||
|
type BindVerifyPhoneLogic struct {
|
||||||
|
ctx context.Context
|
||||||
|
svcCtx *svc.ServiceContext
|
||||||
|
logx.Logger
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewBindVerifyPhoneLogic(ctx context.Context, svcCtx *svc.ServiceContext) *BindVerifyPhoneLogic {
|
||||||
|
return &BindVerifyPhoneLogic{
|
||||||
|
ctx: ctx,
|
||||||
|
svcCtx: svcCtx,
|
||||||
|
Logger: logx.WithContext(ctx),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// BindVerifyPhone 綁定 Phone
|
||||||
|
func (l *BindVerifyPhoneLogic) BindVerifyPhone(in *member.BindVerifyPhoneReq) (*member.OKResp, error) {
|
||||||
|
// todo: add your logic here and delete this line
|
||||||
|
|
||||||
|
return &member.OKResp{}, nil
|
||||||
|
}
|
|
@ -0,0 +1,31 @@
|
||||||
|
package accountlogic
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"app-cloudep-member-server/gen_result/pb/member"
|
||||||
|
"app-cloudep-member-server/internal/svc"
|
||||||
|
|
||||||
|
"github.com/zeromicro/go-zero/core/logx"
|
||||||
|
)
|
||||||
|
|
||||||
|
type CheckRefreshCodeLogic struct {
|
||||||
|
ctx context.Context
|
||||||
|
svcCtx *svc.ServiceContext
|
||||||
|
logx.Logger
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewCheckRefreshCodeLogic(ctx context.Context, svcCtx *svc.ServiceContext) *CheckRefreshCodeLogic {
|
||||||
|
return &CheckRefreshCodeLogic{
|
||||||
|
ctx: ctx,
|
||||||
|
svcCtx: svcCtx,
|
||||||
|
Logger: logx.WithContext(ctx),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// CheckRefreshCode 驗證忘記密碼 token 不刪除,只確認)
|
||||||
|
func (l *CheckRefreshCodeLogic) CheckRefreshCode(in *member.VerifyRefreshCodeReq) (*member.OKResp, error) {
|
||||||
|
// todo: add your logic here and delete this line
|
||||||
|
|
||||||
|
return &member.OKResp{}, nil
|
||||||
|
}
|
|
@ -0,0 +1,31 @@
|
||||||
|
package accountlogic
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"app-cloudep-member-server/gen_result/pb/member"
|
||||||
|
"app-cloudep-member-server/internal/svc"
|
||||||
|
|
||||||
|
"github.com/zeromicro/go-zero/core/logx"
|
||||||
|
)
|
||||||
|
|
||||||
|
type CreateUserAccountLogic struct {
|
||||||
|
ctx context.Context
|
||||||
|
svcCtx *svc.ServiceContext
|
||||||
|
logx.Logger
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewCreateUserAccountLogic(ctx context.Context, svcCtx *svc.ServiceContext) *CreateUserAccountLogic {
|
||||||
|
return &CreateUserAccountLogic{
|
||||||
|
ctx: ctx,
|
||||||
|
svcCtx: svcCtx,
|
||||||
|
Logger: logx.WithContext(ctx),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateUserAccount 建立帳號與密碼 -> 可登入,但可不可以做其他事情看業務流程,也可以只註冊就好
|
||||||
|
func (l *CreateUserAccountLogic) CreateUserAccount(in *member.CreateLoginUserReq) (*member.OKResp, error) {
|
||||||
|
// todo: add your logic here and delete this line
|
||||||
|
|
||||||
|
return &member.OKResp{}, nil
|
||||||
|
}
|
|
@ -0,0 +1,31 @@
|
||||||
|
package accountlogic
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"app-cloudep-member-server/gen_result/pb/member"
|
||||||
|
"app-cloudep-member-server/internal/svc"
|
||||||
|
|
||||||
|
"github.com/zeromicro/go-zero/core/logx"
|
||||||
|
)
|
||||||
|
|
||||||
|
type GenerateRefreshCodeLogic struct {
|
||||||
|
ctx context.Context
|
||||||
|
svcCtx *svc.ServiceContext
|
||||||
|
logx.Logger
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewGenerateRefreshCodeLogic(ctx context.Context, svcCtx *svc.ServiceContext) *GenerateRefreshCodeLogic {
|
||||||
|
return &GenerateRefreshCodeLogic{
|
||||||
|
ctx: ctx,
|
||||||
|
svcCtx: svcCtx,
|
||||||
|
Logger: logx.WithContext(ctx),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GenerateRefreshCode 這個帳號驗證碼(十分鐘),通用的
|
||||||
|
func (l *GenerateRefreshCodeLogic) GenerateRefreshCode(in *member.GenerateRefreshCodeReq) (*member.GenerateRefreshCodeResp, error) {
|
||||||
|
// todo: add your logic here and delete this line
|
||||||
|
|
||||||
|
return &member.GenerateRefreshCodeResp{}, nil
|
||||||
|
}
|
|
@ -0,0 +1,31 @@
|
||||||
|
package accountlogic
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"app-cloudep-member-server/gen_result/pb/member"
|
||||||
|
"app-cloudep-member-server/internal/svc"
|
||||||
|
|
||||||
|
"github.com/zeromicro/go-zero/core/logx"
|
||||||
|
)
|
||||||
|
|
||||||
|
type GetUIDByAccountLogic struct {
|
||||||
|
ctx context.Context
|
||||||
|
svcCtx *svc.ServiceContext
|
||||||
|
logx.Logger
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewGetUIDByAccountLogic(ctx context.Context, svcCtx *svc.ServiceContext) *GetUIDByAccountLogic {
|
||||||
|
return &GetUIDByAccountLogic{
|
||||||
|
ctx: ctx,
|
||||||
|
svcCtx: svcCtx,
|
||||||
|
Logger: logx.WithContext(ctx),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetUIDByAccount 用帳號換取 UID
|
||||||
|
func (l *GetUIDByAccountLogic) GetUIDByAccount(in *member.GetUIDByAccountReq) (*member.GetUIDByAccountResp, error) {
|
||||||
|
// todo: add your logic here and delete this line
|
||||||
|
|
||||||
|
return &member.GetUIDByAccountResp{}, nil
|
||||||
|
}
|
|
@ -0,0 +1,31 @@
|
||||||
|
package accountlogic
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"app-cloudep-member-server/gen_result/pb/member"
|
||||||
|
"app-cloudep-member-server/internal/svc"
|
||||||
|
|
||||||
|
"github.com/zeromicro/go-zero/core/logx"
|
||||||
|
)
|
||||||
|
|
||||||
|
type GetUserAccountInfoLogic struct {
|
||||||
|
ctx context.Context
|
||||||
|
svcCtx *svc.ServiceContext
|
||||||
|
logx.Logger
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewGetUserAccountInfoLogic(ctx context.Context, svcCtx *svc.ServiceContext) *GetUserAccountInfoLogic {
|
||||||
|
return &GetUserAccountInfoLogic{
|
||||||
|
ctx: ctx,
|
||||||
|
svcCtx: svcCtx,
|
||||||
|
Logger: logx.WithContext(ctx),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetUserAccountInfo 取得帳號密碼資料
|
||||||
|
func (l *GetUserAccountInfoLogic) GetUserAccountInfo(in *member.GetUIDByAccountReq) (*member.GetAccountInfoResp, error) {
|
||||||
|
// todo: add your logic here and delete this line
|
||||||
|
|
||||||
|
return &member.GetAccountInfoResp{}, nil
|
||||||
|
}
|
|
@ -0,0 +1,31 @@
|
||||||
|
package accountlogic
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"app-cloudep-member-server/gen_result/pb/member"
|
||||||
|
"app-cloudep-member-server/internal/svc"
|
||||||
|
|
||||||
|
"github.com/zeromicro/go-zero/core/logx"
|
||||||
|
)
|
||||||
|
|
||||||
|
type GetUserInfoLogic struct {
|
||||||
|
ctx context.Context
|
||||||
|
svcCtx *svc.ServiceContext
|
||||||
|
logx.Logger
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewGetUserInfoLogic(ctx context.Context, svcCtx *svc.ServiceContext) *GetUserInfoLogic {
|
||||||
|
return &GetUserInfoLogic{
|
||||||
|
ctx: ctx,
|
||||||
|
svcCtx: svcCtx,
|
||||||
|
Logger: logx.WithContext(ctx),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetUserInfo 取得會員資訊
|
||||||
|
func (l *GetUserInfoLogic) GetUserInfo(in *member.GetUserInfoReq) (*member.GetUserInfoResp, error) {
|
||||||
|
// todo: add your logic here and delete this line
|
||||||
|
|
||||||
|
return &member.GetUserInfoResp{}, nil
|
||||||
|
}
|
|
@ -0,0 +1,31 @@
|
||||||
|
package accountlogic
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"app-cloudep-member-server/gen_result/pb/member"
|
||||||
|
"app-cloudep-member-server/internal/svc"
|
||||||
|
|
||||||
|
"github.com/zeromicro/go-zero/core/logx"
|
||||||
|
)
|
||||||
|
|
||||||
|
type LineCodeToAccessTokenLogic struct {
|
||||||
|
ctx context.Context
|
||||||
|
svcCtx *svc.ServiceContext
|
||||||
|
logx.Logger
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewLineCodeToAccessTokenLogic(ctx context.Context, svcCtx *svc.ServiceContext) *LineCodeToAccessTokenLogic {
|
||||||
|
return &LineCodeToAccessTokenLogic{
|
||||||
|
ctx: ctx,
|
||||||
|
svcCtx: svcCtx,
|
||||||
|
Logger: logx.WithContext(ctx),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// LineCodeToAccessToken Line 驗證相關
|
||||||
|
func (l *LineCodeToAccessTokenLogic) LineCodeToAccessToken(in *member.NoneReq) (*member.LineAccessTokenResp, error) {
|
||||||
|
// todo: add your logic here and delete this line
|
||||||
|
|
||||||
|
return &member.LineAccessTokenResp{}, nil
|
||||||
|
}
|
|
@ -0,0 +1,31 @@
|
||||||
|
package accountlogic
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"app-cloudep-member-server/gen_result/pb/member"
|
||||||
|
"app-cloudep-member-server/internal/svc"
|
||||||
|
|
||||||
|
"github.com/zeromicro/go-zero/core/logx"
|
||||||
|
)
|
||||||
|
|
||||||
|
type LineGetProfileByAccessTokenLogic struct {
|
||||||
|
ctx context.Context
|
||||||
|
svcCtx *svc.ServiceContext
|
||||||
|
logx.Logger
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewLineGetProfileByAccessTokenLogic(ctx context.Context, svcCtx *svc.ServiceContext) *LineGetProfileByAccessTokenLogic {
|
||||||
|
return &LineGetProfileByAccessTokenLogic{
|
||||||
|
ctx: ctx,
|
||||||
|
svcCtx: svcCtx,
|
||||||
|
Logger: logx.WithContext(ctx),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// LineGetProfileByAccessToken Line 驗證相關
|
||||||
|
func (l *LineGetProfileByAccessTokenLogic) LineGetProfileByAccessToken(in *member.NoneReq) (*member.LineUserProfile, error) {
|
||||||
|
// todo: add your logic here and delete this line
|
||||||
|
|
||||||
|
return &member.LineUserProfile{}, nil
|
||||||
|
}
|
|
@ -0,0 +1,31 @@
|
||||||
|
package accountlogic
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"app-cloudep-member-server/gen_result/pb/member"
|
||||||
|
"app-cloudep-member-server/internal/svc"
|
||||||
|
|
||||||
|
"github.com/zeromicro/go-zero/core/logx"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ListMemberLogic struct {
|
||||||
|
ctx context.Context
|
||||||
|
svcCtx *svc.ServiceContext
|
||||||
|
logx.Logger
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewListMemberLogic(ctx context.Context, svcCtx *svc.ServiceContext) *ListMemberLogic {
|
||||||
|
return &ListMemberLogic{
|
||||||
|
ctx: ctx,
|
||||||
|
svcCtx: svcCtx,
|
||||||
|
Logger: logx.WithContext(ctx),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListMember 取得會員列表
|
||||||
|
func (l *ListMemberLogic) ListMember(in *member.ListUserInfoReq) (*member.ListUserInfoResp, error) {
|
||||||
|
// todo: add your logic here and delete this line
|
||||||
|
|
||||||
|
return &member.ListUserInfoResp{}, nil
|
||||||
|
}
|
|
@ -0,0 +1,31 @@
|
||||||
|
package accountlogic
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"app-cloudep-member-server/gen_result/pb/member"
|
||||||
|
"app-cloudep-member-server/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 修改狀態
|
||||||
|
func (l *UpdateStatusLogic) UpdateStatus(in *member.UpdateStatusReq) (*member.OKResp, error) {
|
||||||
|
// todo: add your logic here and delete this line
|
||||||
|
|
||||||
|
return &member.OKResp{}, nil
|
||||||
|
}
|
|
@ -0,0 +1,31 @@
|
||||||
|
package accountlogic
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"app-cloudep-member-server/gen_result/pb/member"
|
||||||
|
"app-cloudep-member-server/internal/svc"
|
||||||
|
|
||||||
|
"github.com/zeromicro/go-zero/core/logx"
|
||||||
|
)
|
||||||
|
|
||||||
|
type UpdateUserInfoLogic struct {
|
||||||
|
ctx context.Context
|
||||||
|
svcCtx *svc.ServiceContext
|
||||||
|
logx.Logger
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewUpdateUserInfoLogic(ctx context.Context, svcCtx *svc.ServiceContext) *UpdateUserInfoLogic {
|
||||||
|
return &UpdateUserInfoLogic{
|
||||||
|
ctx: ctx,
|
||||||
|
svcCtx: svcCtx,
|
||||||
|
Logger: logx.WithContext(ctx),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateUserInfo 更新 User Info
|
||||||
|
func (l *UpdateUserInfoLogic) UpdateUserInfo(in *member.UpdateUserInfoReq) (*member.OKResp, error) {
|
||||||
|
// todo: add your logic here and delete this line
|
||||||
|
|
||||||
|
return &member.OKResp{}, nil
|
||||||
|
}
|
|
@ -0,0 +1,31 @@
|
||||||
|
package accountlogic
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"app-cloudep-member-server/gen_result/pb/member"
|
||||||
|
"app-cloudep-member-server/internal/svc"
|
||||||
|
|
||||||
|
"github.com/zeromicro/go-zero/core/logx"
|
||||||
|
)
|
||||||
|
|
||||||
|
type UpdateUserTokenLogic struct {
|
||||||
|
ctx context.Context
|
||||||
|
svcCtx *svc.ServiceContext
|
||||||
|
logx.Logger
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewUpdateUserTokenLogic(ctx context.Context, svcCtx *svc.ServiceContext) *UpdateUserTokenLogic {
|
||||||
|
return &UpdateUserTokenLogic{
|
||||||
|
ctx: ctx,
|
||||||
|
svcCtx: svcCtx,
|
||||||
|
Logger: logx.WithContext(ctx),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateUserToken 更新密碼
|
||||||
|
func (l *UpdateUserTokenLogic) UpdateUserToken(in *member.UpdateTokenReq) (*member.OKResp, error) {
|
||||||
|
// todo: add your logic here and delete this line
|
||||||
|
|
||||||
|
return &member.OKResp{}, nil
|
||||||
|
}
|
|
@ -0,0 +1,31 @@
|
||||||
|
package accountlogic
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"app-cloudep-member-server/gen_result/pb/member"
|
||||||
|
"app-cloudep-member-server/internal/svc"
|
||||||
|
|
||||||
|
"github.com/zeromicro/go-zero/core/logx"
|
||||||
|
)
|
||||||
|
|
||||||
|
type VerifyGoogleAuthResultLogic struct {
|
||||||
|
ctx context.Context
|
||||||
|
svcCtx *svc.ServiceContext
|
||||||
|
logx.Logger
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewVerifyGoogleAuthResultLogic(ctx context.Context, svcCtx *svc.ServiceContext) *VerifyGoogleAuthResultLogic {
|
||||||
|
return &VerifyGoogleAuthResultLogic{
|
||||||
|
ctx: ctx,
|
||||||
|
svcCtx: svcCtx,
|
||||||
|
Logger: logx.WithContext(ctx),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// VerifyGoogleAuthResult 驗證 google 登入是否有效
|
||||||
|
func (l *VerifyGoogleAuthResultLogic) VerifyGoogleAuthResult(in *member.VerifyAuthResultReq) (*member.VerifyAuthResultResp, error) {
|
||||||
|
// todo: add your logic here and delete this line
|
||||||
|
|
||||||
|
return &member.VerifyAuthResultResp{}, nil
|
||||||
|
}
|
|
@ -0,0 +1,31 @@
|
||||||
|
package accountlogic
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"app-cloudep-member-server/gen_result/pb/member"
|
||||||
|
"app-cloudep-member-server/internal/svc"
|
||||||
|
|
||||||
|
"github.com/zeromicro/go-zero/core/logx"
|
||||||
|
)
|
||||||
|
|
||||||
|
type VerifyPlatformAuthResultLogic struct {
|
||||||
|
ctx context.Context
|
||||||
|
svcCtx *svc.ServiceContext
|
||||||
|
logx.Logger
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewVerifyPlatformAuthResultLogic(ctx context.Context, svcCtx *svc.ServiceContext) *VerifyPlatformAuthResultLogic {
|
||||||
|
return &VerifyPlatformAuthResultLogic{
|
||||||
|
ctx: ctx,
|
||||||
|
svcCtx: svcCtx,
|
||||||
|
Logger: logx.WithContext(ctx),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// VerifyPlatformAuthResult 驗證 google 登入是否有效
|
||||||
|
func (l *VerifyPlatformAuthResultLogic) VerifyPlatformAuthResult(in *member.VerifyAuthResultReq) (*member.VerifyAuthResultResp, error) {
|
||||||
|
// todo: add your logic here and delete this line
|
||||||
|
|
||||||
|
return &member.VerifyAuthResultResp{}, nil
|
||||||
|
}
|
|
@ -0,0 +1,31 @@
|
||||||
|
package accountlogic
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"app-cloudep-member-server/gen_result/pb/member"
|
||||||
|
"app-cloudep-member-server/internal/svc"
|
||||||
|
|
||||||
|
"github.com/zeromicro/go-zero/core/logx"
|
||||||
|
)
|
||||||
|
|
||||||
|
type VerifyRefreshCodeLogic struct {
|
||||||
|
ctx context.Context
|
||||||
|
svcCtx *svc.ServiceContext
|
||||||
|
logx.Logger
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewVerifyRefreshCodeLogic(ctx context.Context, svcCtx *svc.ServiceContext) *VerifyRefreshCodeLogic {
|
||||||
|
return &VerifyRefreshCodeLogic{
|
||||||
|
ctx: ctx,
|
||||||
|
svcCtx: svcCtx,
|
||||||
|
Logger: logx.WithContext(ctx),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// VerifyRefreshCode 驗證忘記密碼 token
|
||||||
|
func (l *VerifyRefreshCodeLogic) VerifyRefreshCode(in *member.VerifyRefreshCodeReq) (*member.OKResp, error) {
|
||||||
|
// todo: add your logic here and delete this line
|
||||||
|
|
||||||
|
return &member.OKResp{}, nil
|
||||||
|
}
|
|
@ -0,0 +1,138 @@
|
||||||
|
// Code generated by goctl. DO NOT EDIT.
|
||||||
|
// goctl 1.7.3
|
||||||
|
// Source: member.proto
|
||||||
|
|
||||||
|
package server
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"app-cloudep-member-server/gen_result/pb/member"
|
||||||
|
accountlogic "app-cloudep-member-server/internal/logic/account"
|
||||||
|
"app-cloudep-member-server/internal/svc"
|
||||||
|
)
|
||||||
|
|
||||||
|
type AccountServer struct {
|
||||||
|
svcCtx *svc.ServiceContext
|
||||||
|
member.UnimplementedAccountServer
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewAccountServer(svcCtx *svc.ServiceContext) *AccountServer {
|
||||||
|
return &AccountServer{
|
||||||
|
svcCtx: svcCtx,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateUserAccount 建立帳號與密碼 -> 可登入,但可不可以做其他事情看業務流程,也可以只註冊就好
|
||||||
|
func (s *AccountServer) CreateUserAccount(ctx context.Context, in *member.CreateLoginUserReq) (*member.OKResp, error) {
|
||||||
|
l := accountlogic.NewCreateUserAccountLogic(ctx, s.svcCtx)
|
||||||
|
return l.CreateUserAccount(in)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetUserAccountInfo 取得帳號密碼資料
|
||||||
|
func (s *AccountServer) GetUserAccountInfo(ctx context.Context, in *member.GetUIDByAccountReq) (*member.GetAccountInfoResp, error) {
|
||||||
|
l := accountlogic.NewGetUserAccountInfoLogic(ctx, s.svcCtx)
|
||||||
|
return l.GetUserAccountInfo(in)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateUserToken 更新密碼
|
||||||
|
func (s *AccountServer) UpdateUserToken(ctx context.Context, in *member.UpdateTokenReq) (*member.OKResp, error) {
|
||||||
|
l := accountlogic.NewUpdateUserTokenLogic(ctx, s.svcCtx)
|
||||||
|
return l.UpdateUserToken(in)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetUIDByAccount 用帳號換取 UID
|
||||||
|
func (s *AccountServer) GetUIDByAccount(ctx context.Context, in *member.GetUIDByAccountReq) (*member.GetUIDByAccountResp, error) {
|
||||||
|
l := accountlogic.NewGetUIDByAccountLogic(ctx, s.svcCtx)
|
||||||
|
return l.GetUIDByAccount(in)
|
||||||
|
}
|
||||||
|
|
||||||
|
// BindAccount 綁定帳號 -> account bind to UID
|
||||||
|
func (s *AccountServer) BindAccount(ctx context.Context, in *member.BindingUserReq) (*member.BindingUserResp, error) {
|
||||||
|
l := accountlogic.NewBindAccountLogic(ctx, s.svcCtx)
|
||||||
|
return l.BindAccount(in)
|
||||||
|
}
|
||||||
|
|
||||||
|
// BindUserInfo 初次,綁定 User Info
|
||||||
|
func (s *AccountServer) BindUserInfo(ctx context.Context, in *member.CreateUserInfoReq) (*member.OKResp, error) {
|
||||||
|
l := accountlogic.NewBindUserInfoLogic(ctx, s.svcCtx)
|
||||||
|
return l.BindUserInfo(in)
|
||||||
|
}
|
||||||
|
|
||||||
|
// BindVerifyEmail 綁定 Email
|
||||||
|
func (s *AccountServer) BindVerifyEmail(ctx context.Context, in *member.BindVerifyEmailReq) (*member.OKResp, error) {
|
||||||
|
l := accountlogic.NewBindVerifyEmailLogic(ctx, s.svcCtx)
|
||||||
|
return l.BindVerifyEmail(in)
|
||||||
|
}
|
||||||
|
|
||||||
|
// BindVerifyPhone 綁定 Phone
|
||||||
|
func (s *AccountServer) BindVerifyPhone(ctx context.Context, in *member.BindVerifyPhoneReq) (*member.OKResp, error) {
|
||||||
|
l := accountlogic.NewBindVerifyPhoneLogic(ctx, s.svcCtx)
|
||||||
|
return l.BindVerifyPhone(in)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateUserInfo 更新 User Info
|
||||||
|
func (s *AccountServer) UpdateUserInfo(ctx context.Context, in *member.UpdateUserInfoReq) (*member.OKResp, error) {
|
||||||
|
l := accountlogic.NewUpdateUserInfoLogic(ctx, s.svcCtx)
|
||||||
|
return l.UpdateUserInfo(in)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateStatus 修改狀態
|
||||||
|
func (s *AccountServer) UpdateStatus(ctx context.Context, in *member.UpdateStatusReq) (*member.OKResp, error) {
|
||||||
|
l := accountlogic.NewUpdateStatusLogic(ctx, s.svcCtx)
|
||||||
|
return l.UpdateStatus(in)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetUserInfo 取得會員資訊
|
||||||
|
func (s *AccountServer) GetUserInfo(ctx context.Context, in *member.GetUserInfoReq) (*member.GetUserInfoResp, error) {
|
||||||
|
l := accountlogic.NewGetUserInfoLogic(ctx, s.svcCtx)
|
||||||
|
return l.GetUserInfo(in)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListMember 取得會員列表
|
||||||
|
func (s *AccountServer) ListMember(ctx context.Context, in *member.ListUserInfoReq) (*member.ListUserInfoResp, error) {
|
||||||
|
l := accountlogic.NewListMemberLogic(ctx, s.svcCtx)
|
||||||
|
return l.ListMember(in)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GenerateRefreshCode 這個帳號驗證碼(十分鐘),通用的
|
||||||
|
func (s *AccountServer) GenerateRefreshCode(ctx context.Context, in *member.GenerateRefreshCodeReq) (*member.GenerateRefreshCodeResp, error) {
|
||||||
|
l := accountlogic.NewGenerateRefreshCodeLogic(ctx, s.svcCtx)
|
||||||
|
return l.GenerateRefreshCode(in)
|
||||||
|
}
|
||||||
|
|
||||||
|
// VerifyRefreshCode 驗證忘記密碼 token
|
||||||
|
func (s *AccountServer) VerifyRefreshCode(ctx context.Context, in *member.VerifyRefreshCodeReq) (*member.OKResp, error) {
|
||||||
|
l := accountlogic.NewVerifyRefreshCodeLogic(ctx, s.svcCtx)
|
||||||
|
return l.VerifyRefreshCode(in)
|
||||||
|
}
|
||||||
|
|
||||||
|
// CheckRefreshCode 驗證忘記密碼 token 不刪除,只確認)
|
||||||
|
func (s *AccountServer) CheckRefreshCode(ctx context.Context, in *member.VerifyRefreshCodeReq) (*member.OKResp, error) {
|
||||||
|
l := accountlogic.NewCheckRefreshCodeLogic(ctx, s.svcCtx)
|
||||||
|
return l.CheckRefreshCode(in)
|
||||||
|
}
|
||||||
|
|
||||||
|
// VerifyGoogleAuthResult 驗證 google 登入是否有效
|
||||||
|
func (s *AccountServer) VerifyGoogleAuthResult(ctx context.Context, in *member.VerifyAuthResultReq) (*member.VerifyAuthResultResp, error) {
|
||||||
|
l := accountlogic.NewVerifyGoogleAuthResultLogic(ctx, s.svcCtx)
|
||||||
|
return l.VerifyGoogleAuthResult(in)
|
||||||
|
}
|
||||||
|
|
||||||
|
// VerifyPlatformAuthResult 驗證 google 登入是否有效
|
||||||
|
func (s *AccountServer) VerifyPlatformAuthResult(ctx context.Context, in *member.VerifyAuthResultReq) (*member.VerifyAuthResultResp, error) {
|
||||||
|
l := accountlogic.NewVerifyPlatformAuthResultLogic(ctx, s.svcCtx)
|
||||||
|
return l.VerifyPlatformAuthResult(in)
|
||||||
|
}
|
||||||
|
|
||||||
|
// LineCodeToAccessToken Line 驗證相關
|
||||||
|
func (s *AccountServer) LineCodeToAccessToken(ctx context.Context, in *member.NoneReq) (*member.LineAccessTokenResp, error) {
|
||||||
|
l := accountlogic.NewLineCodeToAccessTokenLogic(ctx, s.svcCtx)
|
||||||
|
return l.LineCodeToAccessToken(in)
|
||||||
|
}
|
||||||
|
|
||||||
|
// LineGetProfileByAccessToken Line 驗證相關
|
||||||
|
func (s *AccountServer) LineGetProfileByAccessToken(ctx context.Context, in *member.NoneReq) (*member.LineUserProfile, error) {
|
||||||
|
l := accountlogic.NewLineGetProfileByAccessTokenLogic(ctx, s.svcCtx)
|
||||||
|
return l.LineGetProfileByAccessToken(in)
|
||||||
|
}
|
|
@ -0,0 +1,114 @@
|
||||||
|
package svc
|
||||||
|
|
||||||
|
import (
|
||||||
|
"app-cloudep-member-server/internal/config"
|
||||||
|
cfg "app-cloudep-member-server/pkg/domain/config"
|
||||||
|
"app-cloudep-member-server/pkg/domain/usecase"
|
||||||
|
"app-cloudep-member-server/pkg/repository"
|
||||||
|
uc "app-cloudep-member-server/pkg/usecase"
|
||||||
|
"code.30cm.net/digimon/library-go/errs"
|
||||||
|
"code.30cm.net/digimon/library-go/errs/code"
|
||||||
|
mgo "code.30cm.net/digimon/library-go/mongo"
|
||||||
|
vi "code.30cm.net/digimon/library-go/validator"
|
||||||
|
"github.com/zeromicro/go-zero/core/stores/cache"
|
||||||
|
"github.com/zeromicro/go-zero/core/stores/mon"
|
||||||
|
"github.com/zeromicro/go-zero/core/stores/redis"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ServiceContext struct {
|
||||||
|
Config config.Config
|
||||||
|
Validate vi.Validate
|
||||||
|
AccountUseCase usecase.AccountUseCase
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewServiceContext(c config.Config) *ServiceContext {
|
||||||
|
// 設置
|
||||||
|
errs.Scope = code.CloudEPMember
|
||||||
|
|
||||||
|
return &ServiceContext{
|
||||||
|
Config: c,
|
||||||
|
Validate: vi.MustValidator(vi.WithAccount("account")),
|
||||||
|
AccountUseCase: NewAccountUC(&c),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewAccountUC(c *config.Config) usecase.AccountUseCase {
|
||||||
|
// 準備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,
|
||||||
|
}
|
||||||
|
|
||||||
|
// 快取選項
|
||||||
|
cacheOpts := []cache.Option{
|
||||||
|
cache.WithExpiry(c.CacheExpireTime),
|
||||||
|
cache.WithNotFoundExpiry(c.CacheWithNotFoundExpiry),
|
||||||
|
}
|
||||||
|
dbOpts := []mon.Option{
|
||||||
|
mgo.SetCustomDecimalType(),
|
||||||
|
mgo.InitMongoOptions(*conf),
|
||||||
|
}
|
||||||
|
|
||||||
|
newRedis, err := redis.NewRedis(c.RedisConf, redis.Cluster())
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return uc.MustMemberUseCase(uc.MemberUseCaseParam{
|
||||||
|
Account: repository.NewAccountRepository(repository.AccountRepositoryParam{
|
||||||
|
Conf: conf,
|
||||||
|
CacheConf: c.Cache,
|
||||||
|
CacheOpts: cacheOpts,
|
||||||
|
DbOpts: dbOpts,
|
||||||
|
}),
|
||||||
|
User: repository.NewUserRepository(repository.UserRepositoryParam{
|
||||||
|
Conf: conf,
|
||||||
|
CacheConf: c.Cache,
|
||||||
|
CacheOpts: cacheOpts,
|
||||||
|
DbOpts: dbOpts,
|
||||||
|
}),
|
||||||
|
AccountUID: repository.NewAccountUIDRepository(repository.AccountUIDRepositoryParam{
|
||||||
|
Conf: conf,
|
||||||
|
CacheConf: c.Cache,
|
||||||
|
CacheOpts: cacheOpts,
|
||||||
|
DbOpts: dbOpts,
|
||||||
|
}),
|
||||||
|
VerifyCodeModel: repository.NewVerifyCodeRepository(newRedis),
|
||||||
|
GenerateUID: repository.NewAutoIDRepository(repository.AutoIDRepositoryParam{
|
||||||
|
Conf: conf,
|
||||||
|
DbOpts: dbOpts,
|
||||||
|
}),
|
||||||
|
Config: prepareCfg(c),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func prepareCfg(c *config.Config) cfg.Config {
|
||||||
|
return cfg.Config{
|
||||||
|
Bcrypt: struct{ Cost int }{Cost: c.Bcrypt.Cost},
|
||||||
|
GoogleAuth: struct {
|
||||||
|
ClientID string
|
||||||
|
AuthURL string
|
||||||
|
}{
|
||||||
|
ClientID: c.GoogleAuth.ClientID,
|
||||||
|
AuthURL: c.GoogleAuth.AuthURL,
|
||||||
|
},
|
||||||
|
|
||||||
|
LineAuth: struct {
|
||||||
|
ClientID string
|
||||||
|
ClientSecret string
|
||||||
|
RedirectURI string
|
||||||
|
}{
|
||||||
|
ClientID: c.LineAuth.ClientID,
|
||||||
|
ClientSecret: c.LineAuth.ClientSecret,
|
||||||
|
RedirectURI: c.LineAuth.RedirectURI,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,40 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"flag"
|
||||||
|
|
||||||
|
"github.com/zeromicro/go-zero/core/logx"
|
||||||
|
|
||||||
|
"app-cloudep-member-server/gen_result/pb/member"
|
||||||
|
"app-cloudep-member-server/internal/config"
|
||||||
|
accountServer "app-cloudep-member-server/internal/server/account"
|
||||||
|
"app-cloudep-member-server/internal/svc"
|
||||||
|
|
||||||
|
"github.com/zeromicro/go-zero/core/conf"
|
||||||
|
"github.com/zeromicro/go-zero/core/service"
|
||||||
|
"github.com/zeromicro/go-zero/zrpc"
|
||||||
|
"google.golang.org/grpc"
|
||||||
|
"google.golang.org/grpc/reflection"
|
||||||
|
)
|
||||||
|
|
||||||
|
var configFile = flag.String("f", "etc/member.yaml", "the config file")
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
flag.Parse()
|
||||||
|
|
||||||
|
var c config.Config
|
||||||
|
conf.MustLoad(*configFile, &c)
|
||||||
|
ctx := svc.NewServiceContext(c)
|
||||||
|
|
||||||
|
s := zrpc.MustNewServer(c.RpcServerConf, func(grpcServer *grpc.Server) {
|
||||||
|
member.RegisterAccountServer(grpcServer, accountServer.NewAccountServer(ctx))
|
||||||
|
|
||||||
|
if c.Mode == service.DevMode || c.Mode == service.TestMode {
|
||||||
|
reflection.Register(grpcServer)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
defer s.Stop()
|
||||||
|
|
||||||
|
logx.Infof("Starting rpc server at %s...\n", c.ListenOn)
|
||||||
|
s.Start()
|
||||||
|
}
|
|
@ -0,0 +1,19 @@
|
||||||
|
package config
|
||||||
|
|
||||||
|
type Config struct {
|
||||||
|
// 密碼加密層數
|
||||||
|
Bcrypt struct {
|
||||||
|
Cost int
|
||||||
|
}
|
||||||
|
|
||||||
|
GoogleAuth struct {
|
||||||
|
ClientID string
|
||||||
|
AuthURL string
|
||||||
|
}
|
||||||
|
|
||||||
|
LineAuth struct {
|
||||||
|
ClientID string
|
||||||
|
ClientSecret string
|
||||||
|
RedirectURI string
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1 @@
|
||||||
|
package domain
|
|
@ -0,0 +1,20 @@
|
||||||
|
package entity
|
||||||
|
|
||||||
|
import (
|
||||||
|
"app-cloudep-member-server/pkg/domain/member"
|
||||||
|
|
||||||
|
"go.mongodb.org/mongo-driver/bson/primitive"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Account struct {
|
||||||
|
ID primitive.ObjectID `bson:"_id,omitempty" json:"id,omitempty"`
|
||||||
|
LoginID string `bson:"login_id"`
|
||||||
|
Token string `bson:"token"`
|
||||||
|
Platform member.Platform `bson:"platform"` // 平台類型 1. platform 2. google 3. line 4. apple
|
||||||
|
UpdateAt *int64 `bson:"update_at,omitempty" json:"update_at,omitempty"`
|
||||||
|
CreateAt *int64 `bson:"create_at,omitempty" json:"create_at,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Account) CollectionName() string {
|
||||||
|
return "account"
|
||||||
|
}
|
|
@ -0,0 +1,20 @@
|
||||||
|
package entity
|
||||||
|
|
||||||
|
import (
|
||||||
|
"app-cloudep-member-server/pkg/domain/member"
|
||||||
|
|
||||||
|
"go.mongodb.org/mongo-driver/bson/primitive"
|
||||||
|
)
|
||||||
|
|
||||||
|
type AccountUID struct {
|
||||||
|
ID primitive.ObjectID `bson:"_id,omitempty" json:"id,omitempty"`
|
||||||
|
LoginID string `bson:"login_id"`
|
||||||
|
UID string `bson:"uid"`
|
||||||
|
Type member.AccountType `bson:"type"`
|
||||||
|
UpdateAt *int64 `bson:"update_at,omitempty" json:"update_at,omitempty"`
|
||||||
|
CreateAt *int64 `bson:"create_at,omitempty" json:"create_at,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *AccountUID) CollectionName() string {
|
||||||
|
return "account_uid_binding"
|
||||||
|
}
|
|
@ -0,0 +1,15 @@
|
||||||
|
package entity
|
||||||
|
|
||||||
|
import "go.mongodb.org/mongo-driver/bson/primitive"
|
||||||
|
|
||||||
|
type AutoID struct {
|
||||||
|
ID primitive.ObjectID `bson:"_id,omitempty" json:"id,omitempty"`
|
||||||
|
Name string `bson:"name,omitempty" json:"name,omitempty"`
|
||||||
|
Counter uint64 `bson:"counter" json:"counter"`
|
||||||
|
UpdateAt *int64 `bson:"update_at,omitempty" json:"update_at,omitempty"`
|
||||||
|
CreateAt *int64 `bson:"create_at,omitempty" json:"create_at,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *AutoID) CollectionName() string {
|
||||||
|
return "count"
|
||||||
|
}
|
|
@ -0,0 +1,30 @@
|
||||||
|
package entity
|
||||||
|
|
||||||
|
import (
|
||||||
|
"app-cloudep-member-server/pkg/domain/member"
|
||||||
|
|
||||||
|
"go.mongodb.org/mongo-driver/bson/primitive"
|
||||||
|
)
|
||||||
|
|
||||||
|
type User struct {
|
||||||
|
ID primitive.ObjectID `bson:"_id,omitempty" json:"id,omitempty"`
|
||||||
|
UID string `bson:"uid"` // 用戶 UID
|
||||||
|
AvatarURL *string `bson:"avatar_url,omitempty"` // 頭像 URL(可選)
|
||||||
|
FullName *string `bson:"full_name,omitempty"` // 用戶全名
|
||||||
|
Nickname *string `bson:"nickname,omitempty"` // 暱稱(可選)
|
||||||
|
GenderCode *int64 `bson:"gender_code,omitempty"` // 性別代碼
|
||||||
|
Birthdate *int64 `bson:"birthdate,omitempty"` // 生日 (格式: 19930417)
|
||||||
|
Address *string `bson:"address,omitempty"` // 地址
|
||||||
|
AlarmCategory member.AlarmType `bson:"alarm_category"` // 告警狀態
|
||||||
|
UserStatus member.Status `bson:"user_status"` // 用戶狀態
|
||||||
|
PreferredLanguage string `bson:"preferred_language"` // 使用語言
|
||||||
|
Currency string `bson:"currency"` // 使用幣種
|
||||||
|
PhoneNumber *string `bson:"phone_number,omitempty"` // 電話 (驗證後才會出現)
|
||||||
|
Email *string `bson:"email,omitempty"` // 信箱 (驗證後才會出現)
|
||||||
|
UpdateAt *int64 `bson:"update_at,omitempty" json:"update_at,omitempty"`
|
||||||
|
CreateAt *int64 `bson:"create_at,omitempty" json:"create_at,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *User) CollectionName() string {
|
||||||
|
return "user_info"
|
||||||
|
}
|
|
@ -0,0 +1,33 @@
|
||||||
|
package domain
|
||||||
|
|
||||||
|
import "code.30cm.net/digimon/library-go/errs"
|
||||||
|
|
||||||
|
// Verify Error Code
|
||||||
|
const (
|
||||||
|
FailedToVerifyGoogle errs.ErrorCode = iota + 1
|
||||||
|
FailedToVerifyGoogleTimeout
|
||||||
|
FailedToVerifyGoogleHTTPCode
|
||||||
|
FailedToVerifyGoogleTokenExpired
|
||||||
|
FailedToVerifyGoogleInvalidAudience
|
||||||
|
FailedToVerifyLine
|
||||||
|
)
|
||||||
|
|
||||||
|
// PWS Error Code
|
||||||
|
const (
|
||||||
|
HashPasswordErrorCode = 10 + iota
|
||||||
|
InsertAccountErrorCode
|
||||||
|
BindingUserTabletErrorCode
|
||||||
|
FailedToFindAccountErrorCode
|
||||||
|
FailedToBindAccountErrorCode
|
||||||
|
FailedToIncAccountErrorCode
|
||||||
|
FailedFindUIDByLoginIDErrorCode
|
||||||
|
FailedFindOneByAccountErrorCode
|
||||||
|
FailedToUpdatePasswordErrorCode
|
||||||
|
FailedToFindUserErrorCode
|
||||||
|
FailedToUpdateUserErrorCode
|
||||||
|
FailedToUpdateUserStatusErrorCode
|
||||||
|
FailedToGetUserInfoErrorCode
|
||||||
|
FailedToGetVerifyCodeErrorCode
|
||||||
|
FailedToGetCodeOnRedisErrorCode
|
||||||
|
FailedToGetCodeCorrectErrorCode
|
||||||
|
)
|
|
@ -0,0 +1,25 @@
|
||||||
|
package member
|
||||||
|
|
||||||
|
type AccountType int32
|
||||||
|
|
||||||
|
const (
|
||||||
|
AccountTypeNone AccountType = -1
|
||||||
|
AccountTypePhone AccountType = 1 // 手機
|
||||||
|
AccountTypeMail AccountType = 2 // 信箱
|
||||||
|
AccountTypeDefine AccountType = 3 // 自定義帳號
|
||||||
|
)
|
||||||
|
|
||||||
|
var convAccountTypeCode = map[string]AccountType{
|
||||||
|
"phone": AccountTypePhone,
|
||||||
|
"email": AccountTypeMail,
|
||||||
|
"platform": AccountTypeDefine,
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetAccountTypeByCode(code string) AccountType {
|
||||||
|
result, ok := convAccountTypeCode[code]
|
||||||
|
if !ok {
|
||||||
|
return AccountTypeNone
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
|
@ -0,0 +1,26 @@
|
||||||
|
package member
|
||||||
|
|
||||||
|
import "testing"
|
||||||
|
|
||||||
|
func TestGetAccountTypeByCode(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
code string
|
||||||
|
expected AccountType
|
||||||
|
}{
|
||||||
|
{"phone", AccountTypePhone}, // 測試有效值: 手機
|
||||||
|
{"email", AccountTypeMail}, // 測試有效值: 信箱
|
||||||
|
{"platform", AccountTypeDefine}, // 測試有效值: 自定義帳號
|
||||||
|
{"unknown", AccountTypeNone}, // 測試無效值
|
||||||
|
{"", AccountTypeNone}, // 測試空字串
|
||||||
|
{"PHONE", AccountTypeNone}, // 測試大小寫不匹配
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.code, func(t *testing.T) {
|
||||||
|
result := GetAccountTypeByCode(tt.code)
|
||||||
|
if result != tt.expected {
|
||||||
|
t.Errorf("GetAccountTypeByCode(%v) = %v, want %v", tt.code, result, tt.expected)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,25 @@
|
||||||
|
package member
|
||||||
|
|
||||||
|
// AlarmType 警報類型
|
||||||
|
type AlarmType int32
|
||||||
|
|
||||||
|
func (a *AlarmType) CodeToString() string {
|
||||||
|
result, ok := verifyAlarmMap[*a]
|
||||||
|
if !ok {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
var verifyAlarmMap = map[AlarmType]string{
|
||||||
|
AlarmUninitialized: "uninitialized", // 初始狀態(異常)
|
||||||
|
AlarmNoAlert: "no_alert", // 未告警
|
||||||
|
AlarmSystem: "system_alert", // 系統告警中
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
AlarmUninitialized AlarmType = 0 // 初始狀態(異常)
|
||||||
|
AlarmNoAlert AlarmType = 1 // 未告警
|
||||||
|
AlarmSystem AlarmType = 2 // 系統告警中
|
||||||
|
)
|
|
@ -0,0 +1,27 @@
|
||||||
|
package member
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestAlarmType_CodeToString(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
alarmType AlarmType
|
||||||
|
expected string
|
||||||
|
}{
|
||||||
|
{AlarmUninitialized, "uninitialized"}, // 測試初始狀態
|
||||||
|
{AlarmNoAlert, "no_alert"}, // 測試未告警狀態
|
||||||
|
{AlarmSystem, "system_alert"}, // 測試系統告警
|
||||||
|
{AlarmType(999), ""}, // 測試不存在的狀態
|
||||||
|
{AlarmType(-1), ""}, // 測試無效負數狀態
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.expected, func(t *testing.T) {
|
||||||
|
result := tt.alarmType.CodeToString()
|
||||||
|
if result != tt.expected {
|
||||||
|
t.Errorf("CodeToString() = %v, want %v", result, tt.expected)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,40 @@
|
||||||
|
package member
|
||||||
|
|
||||||
|
type GenerateCodeType int64
|
||||||
|
|
||||||
|
const (
|
||||||
|
GenerateCodeTypeNone GenerateCodeType = -1
|
||||||
|
GenerateCodeTypeEmail GenerateCodeType = 1 // email 驗證碼
|
||||||
|
GenerateCodeTypePhone GenerateCodeType = 2 // phone 驗證碼
|
||||||
|
GenerateCodeTypeForgetPassword GenerateCodeType = 3 // 忘記密碼
|
||||||
|
)
|
||||||
|
|
||||||
|
var codeMap = map[GenerateCodeType]string{
|
||||||
|
1: "email",
|
||||||
|
2: "phone",
|
||||||
|
3: "forget_email",
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetCodeNameByCode(code GenerateCodeType) (string, bool) {
|
||||||
|
res, ok := codeMap[code]
|
||||||
|
if !ok {
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
|
||||||
|
return res, true
|
||||||
|
}
|
||||||
|
|
||||||
|
var generateCodeTypeMap = map[string]GenerateCodeType{
|
||||||
|
"email": GenerateCodeTypeEmail,
|
||||||
|
"phone": GenerateCodeTypePhone,
|
||||||
|
"forget_email": GenerateCodeTypeForgetPassword,
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetGetCodeNameByCode(code string) GenerateCodeType {
|
||||||
|
result, ok := generateCodeTypeMap[code]
|
||||||
|
if !ok {
|
||||||
|
return GenerateCodeTypeNone
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
|
@ -0,0 +1,54 @@
|
||||||
|
package member
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestGetCodeNameByCode(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
code GenerateCodeType
|
||||||
|
expected string
|
||||||
|
ok bool
|
||||||
|
}{
|
||||||
|
{GenerateCodeTypeEmail, "email", true},
|
||||||
|
{GenerateCodeTypePhone, "phone", true},
|
||||||
|
{GenerateCodeTypeForgetPassword, "forget_email", true},
|
||||||
|
{GenerateCodeTypeNone, "", false}, // 無效代碼
|
||||||
|
{GenerateCodeType(999), "", false}, // 無效代碼
|
||||||
|
{GenerateCodeType(-999), "", false}, // 無效代碼
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.expected, func(t *testing.T) {
|
||||||
|
result, ok := GetCodeNameByCode(tt.code)
|
||||||
|
if result != tt.expected || ok != tt.ok {
|
||||||
|
t.Errorf("GetCodeNameByCode(%v) = (%v, %v), want (%v, %v)",
|
||||||
|
tt.code, result, ok, tt.expected, tt.ok)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetGetCodeNameByCode(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
expected GenerateCodeType
|
||||||
|
}{
|
||||||
|
{"email", GenerateCodeTypeEmail},
|
||||||
|
{"phone", GenerateCodeTypePhone},
|
||||||
|
{"forget_email", GenerateCodeTypeForgetPassword},
|
||||||
|
{"unknown", GenerateCodeTypeNone}, // 無效名稱
|
||||||
|
{"", GenerateCodeTypeNone}, // 空名稱
|
||||||
|
{"123", GenerateCodeTypeNone}, // 無效名稱
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
result := GetGetCodeNameByCode(tt.name)
|
||||||
|
if result != tt.expected {
|
||||||
|
t.Errorf("GetGetCodeNameByCode(%v) = %v, want %v",
|
||||||
|
tt.name, result, tt.expected)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,57 @@
|
||||||
|
package member
|
||||||
|
|
||||||
|
const (
|
||||||
|
Digimon Platform = 1 + iota
|
||||||
|
Google
|
||||||
|
Line
|
||||||
|
Apple
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
PlatformNone Platform = -1
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
DigimonString = "platform"
|
||||||
|
GoogleString = "google"
|
||||||
|
LineString = "line"
|
||||||
|
AppleString = "apple"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Platform int8
|
||||||
|
|
||||||
|
func (p Platform) ToInt64() int64 {
|
||||||
|
return int64(p)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToString - 將 Platform 轉為文字
|
||||||
|
func (p Platform) ToString() string {
|
||||||
|
if result, ok := platformToString[p]; ok {
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
var platformToString = map[Platform]string{
|
||||||
|
Digimon: DigimonString,
|
||||||
|
Google: GoogleString,
|
||||||
|
Line: LineString,
|
||||||
|
Apple: AppleString,
|
||||||
|
}
|
||||||
|
|
||||||
|
var stringToPlatform = map[string]Platform{
|
||||||
|
DigimonString: Digimon,
|
||||||
|
GoogleString: Google,
|
||||||
|
LineString: Line,
|
||||||
|
AppleString: Apple,
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetPlatformByPlatformCode - 從文字轉為 Platform
|
||||||
|
func GetPlatformByPlatformCode(code string) Platform {
|
||||||
|
if result, ok := stringToPlatform[code]; ok {
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
return PlatformNone
|
||||||
|
}
|
|
@ -0,0 +1,66 @@
|
||||||
|
package member
|
||||||
|
|
||||||
|
import "testing"
|
||||||
|
|
||||||
|
func TestPlatform_ToInt64(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
platform Platform
|
||||||
|
expected int64
|
||||||
|
}{
|
||||||
|
{Digimon, 1},
|
||||||
|
{Google, 2},
|
||||||
|
{Line, 3},
|
||||||
|
{Apple, 4},
|
||||||
|
{PlatformNone, -1},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.platform.ToString(), func(t *testing.T) {
|
||||||
|
if result := tt.platform.ToInt64(); result != tt.expected {
|
||||||
|
t.Errorf("ToInt64() = %v, want %v", result, tt.expected)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPlatform_ToString(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
platform Platform
|
||||||
|
expected string
|
||||||
|
}{
|
||||||
|
{Digimon, DigimonString},
|
||||||
|
{Google, GoogleString},
|
||||||
|
{Line, LineString},
|
||||||
|
{Apple, AppleString},
|
||||||
|
{PlatformNone, ""},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.expected, func(t *testing.T) {
|
||||||
|
if result := tt.platform.ToString(); result != tt.expected {
|
||||||
|
t.Errorf("ToString() = %v, want %v", result, tt.expected)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetPlatformByPlatformCode(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
code string
|
||||||
|
expected Platform
|
||||||
|
}{
|
||||||
|
{DigimonString, Digimon},
|
||||||
|
{GoogleString, Google},
|
||||||
|
{LineString, Line},
|
||||||
|
{AppleString, Apple},
|
||||||
|
{"unknown", PlatformNone}, // 測試不存在的 Platform
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.code, func(t *testing.T) {
|
||||||
|
if result := GetPlatformByPlatformCode(tt.code); result != tt.expected {
|
||||||
|
t.Errorf("GetPlatformByPlatformCode(%v) = %v, want %v", tt.code, result, tt.expected)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,31 @@
|
||||||
|
package member
|
||||||
|
|
||||||
|
// Status 會員狀態
|
||||||
|
type Status int32
|
||||||
|
|
||||||
|
func (s *Status) CodeToString() string {
|
||||||
|
result, ok := accountStatusMap[*s]
|
||||||
|
if !ok {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
var accountStatusMap = map[Status]string{
|
||||||
|
AccountStatusUninitialized: "uninitialized", // 初始狀態(異常)
|
||||||
|
AccountStatusUnverified: "unverified", // 尚未完成驗證
|
||||||
|
AccountStatusActive: "active", // 帳號啟用中
|
||||||
|
AccountStatusSuspended: "suspended", // 帳號停權中
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Status) ToInt32() int32 {
|
||||||
|
return int32(*s)
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
AccountStatusUninitialized Status = 0 // 初始狀態(異常)
|
||||||
|
AccountStatusUnverified Status = 1 // 尚未驗證
|
||||||
|
AccountStatusActive Status = 2 // 帳號啟用中
|
||||||
|
AccountStatusSuspended Status = 3 // 帳號停權中
|
||||||
|
)
|
|
@ -0,0 +1,45 @@
|
||||||
|
package member
|
||||||
|
|
||||||
|
import "testing"
|
||||||
|
|
||||||
|
func TestStatus_CodeToString(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
status Status
|
||||||
|
expected string
|
||||||
|
}{
|
||||||
|
{AccountStatusUninitialized, "uninitialized"},
|
||||||
|
{AccountStatusUnverified, "unverified"},
|
||||||
|
{AccountStatusActive, "active"},
|
||||||
|
{AccountStatusSuspended, "suspended"},
|
||||||
|
{Status(999), ""}, // 測試不存在的狀態
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.expected, func(t *testing.T) {
|
||||||
|
if result := tt.status.CodeToString(); result != tt.expected {
|
||||||
|
t.Errorf("CodeToString() = %v, want %v", result, tt.expected)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStatus_ToInt32(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
status Status
|
||||||
|
expected int32
|
||||||
|
}{
|
||||||
|
{AccountStatusUninitialized, 0},
|
||||||
|
{AccountStatusUnverified, 1},
|
||||||
|
{AccountStatusActive, 2},
|
||||||
|
{AccountStatusSuspended, 3},
|
||||||
|
{Status(999), 999}, // 測試不存在的狀態
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(string(tt.expected), func(t *testing.T) {
|
||||||
|
if result := tt.status.ToInt32(); result != tt.expected {
|
||||||
|
t.Errorf("ToInt32() = %v, want %v", result, tt.expected)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,46 @@
|
||||||
|
package domain
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
type RedisKey string
|
||||||
|
|
||||||
|
const (
|
||||||
|
AccountRedisKey RedisKey = "account"
|
||||||
|
AccountUIDRedisKey RedisKey = "account_uid"
|
||||||
|
UserRedisKey RedisKey = "user"
|
||||||
|
MemberPrefixRedisKey = "member"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (key RedisKey) ToString() string {
|
||||||
|
return "member:" + string(key)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (key RedisKey) With(s ...string) RedisKey {
|
||||||
|
parts := append([]string{string(key)}, s...)
|
||||||
|
|
||||||
|
return RedisKey(strings.Join(parts, ":"))
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetAccountRedisKey(id string) string {
|
||||||
|
return AccountRedisKey.With(id).ToString()
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetAccountUIDRedisKey(id string) string {
|
||||||
|
return AccountUIDRedisKey.With(id).ToString()
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetUserRedisKey(id string) string {
|
||||||
|
return UserRedisKey.With(id).ToString()
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
// checkVerifyKey 驗證碼驗證
|
||||||
|
checkVerifyKey = fmt.Sprintf("%s:verify:", MemberPrefixRedisKey)
|
||||||
|
)
|
||||||
|
|
||||||
|
func GetCheckVerifyKey(codeType, account string) string {
|
||||||
|
return fmt.Sprintf("%s%s:%s", checkVerifyKey, codeType, account)
|
||||||
|
}
|
|
@ -0,0 +1,26 @@
|
||||||
|
package repository
|
||||||
|
|
||||||
|
import (
|
||||||
|
"app-cloudep-member-server/pkg/domain/entity"
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"go.mongodb.org/mongo-driver/mongo"
|
||||||
|
)
|
||||||
|
|
||||||
|
type AccountRepository interface {
|
||||||
|
Insert(ctx context.Context, data *entity.Account) error
|
||||||
|
FindOne(ctx context.Context, id string) (*entity.Account, error)
|
||||||
|
Update(ctx context.Context, data *entity.Account) (*mongo.UpdateResult, error)
|
||||||
|
Delete(ctx context.Context, id string) (int64, error)
|
||||||
|
FindOneByAccount(ctx context.Context, loginID string) (*entity.Account, error)
|
||||||
|
UpdateTokenByLoginID(ctx context.Context, account string, token string) error
|
||||||
|
AccountIndexUP
|
||||||
|
}
|
||||||
|
|
||||||
|
type AccountIndexUP interface {
|
||||||
|
Index20241226001UP(ctx context.Context) (*mongo.Cursor, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// type AccountIndexDown interface {
|
||||||
|
// Index20241226001Down(ctx context.Context)
|
||||||
|
// }
|
|
@ -0,0 +1,23 @@
|
||||||
|
package repository
|
||||||
|
|
||||||
|
import (
|
||||||
|
"app-cloudep-member-server/pkg/domain/entity"
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"go.mongodb.org/mongo-driver/mongo"
|
||||||
|
)
|
||||||
|
|
||||||
|
type AccountUIDRepository interface {
|
||||||
|
Insert(ctx context.Context, data *entity.AccountUID) error
|
||||||
|
FindOne(ctx context.Context, id string) (*entity.AccountUID, error)
|
||||||
|
Update(ctx context.Context, data *entity.AccountUID) (*mongo.UpdateResult, error)
|
||||||
|
Delete(ctx context.Context, id string) (int64, error)
|
||||||
|
FindUIDByLoginID(ctx context.Context, loginID string) (*entity.AccountUID, error)
|
||||||
|
AccountUIDIndexUP
|
||||||
|
}
|
||||||
|
|
||||||
|
// Index20241226001Up 這樣方便管理,知道是 這張表 20241226 第一個新增的 index 之後有版本遷移也可以這樣寫,先快速這樣做,等之後找到更好的迭代工具,在用
|
||||||
|
|
||||||
|
type AccountUIDIndexUP interface {
|
||||||
|
Index20241226001UP(ctx context.Context) (*mongo.Cursor, error)
|
||||||
|
}
|
|
@ -0,0 +1,23 @@
|
||||||
|
package repository
|
||||||
|
|
||||||
|
import (
|
||||||
|
"app-cloudep-member-server/pkg/domain/entity"
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"go.mongodb.org/mongo-driver/mongo"
|
||||||
|
)
|
||||||
|
|
||||||
|
type AutoIDRepository interface {
|
||||||
|
Insert(ctx context.Context, data *entity.AutoID) error
|
||||||
|
FindOne(ctx context.Context, id string) (*entity.AutoID, error)
|
||||||
|
Update(ctx context.Context, data *entity.AutoID) (*mongo.UpdateResult, error)
|
||||||
|
Delete(ctx context.Context, id string) (int64, error)
|
||||||
|
Inc(ctx context.Context, data *entity.AutoID) error
|
||||||
|
GetUIDFromNum(num int64) (string, error)
|
||||||
|
GetNumFromUID(uid string) (int64, error)
|
||||||
|
AutoIDIndexUP
|
||||||
|
}
|
||||||
|
|
||||||
|
type AutoIDIndexUP interface {
|
||||||
|
Index20241226001UP(ctx context.Context) (*mongo.Cursor, error)
|
||||||
|
}
|
|
@ -0,0 +1,55 @@
|
||||||
|
package repository
|
||||||
|
|
||||||
|
import (
|
||||||
|
"app-cloudep-member-server/pkg/domain/entity"
|
||||||
|
"app-cloudep-member-server/pkg/domain/member"
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"go.mongodb.org/mongo-driver/mongo"
|
||||||
|
)
|
||||||
|
|
||||||
|
type UserRepository interface {
|
||||||
|
BaseUserRepository
|
||||||
|
UpdateUserDetailsByUID(ctx context.Context, data *UpdateUserInfoRequest) error
|
||||||
|
UpdateStatus(ctx context.Context, uid string, status int32) error
|
||||||
|
FindOneByUID(ctx context.Context, uid string) (*entity.User, error)
|
||||||
|
FindOneByNickName(ctx context.Context, nickName string) (*entity.User, error)
|
||||||
|
ListMembers(ctx context.Context, params *UserQueryParams) ([]*entity.User, int64, error)
|
||||||
|
UpdateEmailVerifyStatus(ctx context.Context, uid, email string) error
|
||||||
|
UpdatePhoneVerifyStatus(ctx context.Context, uid, phone string) error
|
||||||
|
UserIndexUP
|
||||||
|
}
|
||||||
|
|
||||||
|
type UserIndexUP interface {
|
||||||
|
Index20241226001UP(ctx context.Context) (*mongo.Cursor, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type BaseUserRepository interface {
|
||||||
|
Insert(ctx context.Context, data *entity.User) error
|
||||||
|
FindOne(ctx context.Context, id string) (*entity.User, error)
|
||||||
|
Update(ctx context.Context, data *entity.User) (*mongo.UpdateResult, error)
|
||||||
|
Delete(ctx context.Context, id string) (int64, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type UserQueryParams struct {
|
||||||
|
AlarmCategory *member.AlarmType
|
||||||
|
UserStatus *member.Status
|
||||||
|
CreateStartTime *int64
|
||||||
|
CreateEndTime *int64
|
||||||
|
PageSize int64
|
||||||
|
PageIndex int64
|
||||||
|
}
|
||||||
|
|
||||||
|
type UpdateUserInfoRequest struct {
|
||||||
|
UID string `json:"uid"` // 用戶 UID
|
||||||
|
AvatarURL *string `json:"avatar_url,omitempty"` // 頭像 URL(可選)
|
||||||
|
FullName *string `json:"full_name,omitempty"` // 用戶全名
|
||||||
|
Nickname *string `json:"nickname,omitempty"` // 暱稱(可選)
|
||||||
|
GenderCode *int8 `json:"gender_code,omitempty"` // 性別代碼
|
||||||
|
Birthdate *int64 `json:"birthdate,omitempty"` // 生日 (格式: 19930417)
|
||||||
|
Address *string `json:"address,omitempty"` // 地址
|
||||||
|
AlarmCategory *member.AlarmType `json:"alarm_category,omitempty"` // 警報類型
|
||||||
|
UserStatus *member.Status `json:"user_status,omitempty"` // 用戶狀態
|
||||||
|
PreferredLanguage *string `json:"preferred_language,omitempty"` // 使用語言
|
||||||
|
Currency *string `json:"currency,omitempty"` // 使用幣種
|
||||||
|
}
|
|
@ -0,0 +1,9 @@
|
||||||
|
package repository
|
||||||
|
|
||||||
|
import "context"
|
||||||
|
|
||||||
|
type VerifyCodeRepository interface {
|
||||||
|
IsVerifyCodeExist(ctx context.Context, loginID, checkType string) (string, error)
|
||||||
|
SetVerifyCode(ctx context.Context, loginID, checkType, code string) error
|
||||||
|
DelVerifyCode(ctx context.Context, loginID, checkType string) error
|
||||||
|
}
|
|
@ -0,0 +1,218 @@
|
||||||
|
package usecase
|
||||||
|
|
||||||
|
import (
|
||||||
|
"app-cloudep-member-server/pkg/domain/member"
|
||||||
|
"context"
|
||||||
|
)
|
||||||
|
|
||||||
|
// AccountUseCase 定義了帳號服務的操作方法
|
||||||
|
type AccountUseCase interface {
|
||||||
|
MemberUseCase
|
||||||
|
BindingMemberUseCase
|
||||||
|
VerifyMemberUseCase
|
||||||
|
UIDGenerateUseCase
|
||||||
|
}
|
||||||
|
|
||||||
|
type MemberUseCase interface {
|
||||||
|
// CreateUserAccount 創建用戶帳號
|
||||||
|
CreateUserAccount(ctx context.Context, req CreateLoginUserRequest) error
|
||||||
|
// GetUIDByAccount 通過帳號取得 UID
|
||||||
|
GetUIDByAccount(ctx context.Context, req GetUIDByAccountRequest) (GetUIDByAccountResponse, error)
|
||||||
|
// GetUserAccountInfo 取得用戶帳號資訊
|
||||||
|
GetUserAccountInfo(ctx context.Context, req GetUIDByAccountRequest) (GetAccountInfoResponse, error)
|
||||||
|
// UpdateUserToken 更新用戶 Token (密碼)
|
||||||
|
UpdateUserToken(ctx context.Context, req UpdateTokenRequest) error
|
||||||
|
// UpdateUserInfo 更新用戶資訊
|
||||||
|
UpdateUserInfo(ctx context.Context, req *UpdateUserInfoRequest) error
|
||||||
|
// UpdateStatus 更新用戶狀態
|
||||||
|
UpdateStatus(ctx context.Context, req UpdateStatusRequest) error
|
||||||
|
// GetUserInfo 取得用戶資訊
|
||||||
|
GetUserInfo(ctx context.Context, req GetUserInfoRequest) (UserInfo, error)
|
||||||
|
// ListMember 取得會員列表
|
||||||
|
ListMember(ctx context.Context, req ListUserInfoRequest) (ListUserInfoResponse, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type BindingMemberUseCase interface {
|
||||||
|
// BindUserInfo 綁定用戶信息
|
||||||
|
BindUserInfo(ctx context.Context, req CreateUserInfoRequest) error
|
||||||
|
// BindAccount 綁定帳號到 UID
|
||||||
|
BindAccount(ctx context.Context, req BindingUser) (BindingUser, error)
|
||||||
|
// BindVerifyEmail 驗證Email 後綁定到會員
|
||||||
|
BindVerifyEmail(ctx context.Context, uid, email string) error
|
||||||
|
// BindVerifyPhone 驗證 Phone 後綁定到會員
|
||||||
|
BindVerifyPhone(ctx context.Context, uid, phone string) error
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateUserInfoRequest 用於創建用戶詳細信息
|
||||||
|
type CreateUserInfoRequest struct {
|
||||||
|
UID string `json:"uid"` // 用戶 UID
|
||||||
|
AvatarURL *string `json:"avatar_url,omitempty"` // 頭像 URL(可選)
|
||||||
|
FullName *string `json:"full_name,omitempty"` // 用戶全名
|
||||||
|
Nickname *string `json:"nickname,omitempty"` // 暱稱(可選)
|
||||||
|
GenderCode *int64 `json:"gender_code,omitempty"` // 性別代碼
|
||||||
|
Birthdate *int64 `json:"birthdate,omitempty"` // 生日 (格式: 19930417)
|
||||||
|
PhoneNumber *string `json:"phone_number,omitempty"` // 電話
|
||||||
|
Email *string `json:"email,omitempty"` // 電話
|
||||||
|
Address *string `json:"address,omitempty"` // 地址
|
||||||
|
AlarmCategory member.AlarmType `json:"alarm_category"` // 警報類型
|
||||||
|
UserStatus member.Status `json:"user_status"` // 用戶狀態
|
||||||
|
PreferredLanguage string `json:"preferred_language"` // 使用語言
|
||||||
|
Currency string `json:"currency"` // 使用幣種
|
||||||
|
}
|
||||||
|
|
||||||
|
type UserInfo struct {
|
||||||
|
CreateUserInfoRequest
|
||||||
|
CreateTime int64 `json:"create_time"` // 創建時間
|
||||||
|
UpdateTime int64 `json:"update_time"` // 更新時間
|
||||||
|
}
|
||||||
|
|
||||||
|
type UpdateUserInfoRequest struct {
|
||||||
|
UID string `json:"uid"` // 用戶 UID
|
||||||
|
AvatarURL *string `json:"avatar_url,omitempty"` // 頭像 URL(可選)
|
||||||
|
FullName *string `json:"full_name,omitempty"` // 用戶全名
|
||||||
|
Nickname *string `json:"nickname,omitempty"` // 暱稱(可選)
|
||||||
|
GenderCode *int8 `json:"gender_code,omitempty"` // 性別代碼
|
||||||
|
Birthdate *int64 `json:"birthdate,omitempty"` // 生日 (格式: 19930417)
|
||||||
|
Address *string `json:"address,omitempty"` // 地址
|
||||||
|
AlarmCategory *member.AlarmType `json:"alarm_category,omitempty"` // 警報類型
|
||||||
|
UserStatus *member.Status `json:"user_status,omitempty"` // 用戶狀態
|
||||||
|
PreferredLanguage *string `json:"preferred_language,omitempty"` // 使用語言
|
||||||
|
Currency *string `json:"currency,omitempty"` // 使用幣種
|
||||||
|
}
|
||||||
|
|
||||||
|
type CreateLoginUserRequest struct {
|
||||||
|
LoginID string `json:"login_id"` // 登錄 ID
|
||||||
|
Platform member.Platform `json:"platform"` // 平台類型
|
||||||
|
Token string `json:"token"` // 驗證 Token
|
||||||
|
}
|
||||||
|
|
||||||
|
// BindingUser 用於綁定用戶帳號
|
||||||
|
type BindingUser struct {
|
||||||
|
UID string `json:"uid"` // 用戶 UID
|
||||||
|
LoginID string `json:"login_id"` // 登錄 ID
|
||||||
|
Type member.AccountType `json:"type"` // 綁定類型
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetUIDByAccountRequest 用於通過帳號獲取用戶 UID
|
||||||
|
type GetUIDByAccountRequest struct {
|
||||||
|
Account string `json:"account"` // 帳號
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetUIDByAccountResponse 用於返回帳號對應的 UID 信息
|
||||||
|
type GetUIDByAccountResponse struct {
|
||||||
|
UID string `json:"uid"` // 用戶 UID
|
||||||
|
Account string `json:"account"` // 帳號
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetAccountInfoResponse 用於返回用戶帳號信息
|
||||||
|
type GetAccountInfoResponse struct {
|
||||||
|
Data CreateLoginUserRequest `json:"data"` // 登錄用戶信息
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateTokenRequest 用於更新用戶 Token
|
||||||
|
type UpdateTokenRequest struct {
|
||||||
|
Account string `json:"account"` // 帳號
|
||||||
|
Token string `json:"token"` // 新 Token
|
||||||
|
Platform int64 `json:"platform"` // 平台類型
|
||||||
|
}
|
||||||
|
|
||||||
|
// GenerateRefreshCodeRequest 用於請求產生刷新代碼
|
||||||
|
type GenerateRefreshCodeRequest struct {
|
||||||
|
LoginID string `json:"login_id"` // 帳號
|
||||||
|
CodeType member.GenerateCodeType `json:"code_type"` // 代碼類型
|
||||||
|
}
|
||||||
|
|
||||||
|
// VerifyCode 用於表示驗證代碼
|
||||||
|
type VerifyCode struct {
|
||||||
|
VerifyCode string `json:"verify_code"` // 驗證碼
|
||||||
|
}
|
||||||
|
|
||||||
|
// GenerateRefreshCodeResponse 用於返回生成的驗證代碼
|
||||||
|
type GenerateRefreshCodeResponse struct {
|
||||||
|
Data VerifyCode `json:"data"` // 驗證碼數據
|
||||||
|
}
|
||||||
|
|
||||||
|
// VerifyRefreshCodeRequest 用於驗證刷新代碼
|
||||||
|
type VerifyRefreshCodeRequest struct {
|
||||||
|
LoginID string `json:"Login_id"` // 帳號
|
||||||
|
CodeType member.GenerateCodeType `json:"code_type"` // 代碼類型
|
||||||
|
VerifyCode string `json:"verify_code"` // 驗證碼
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateStatusRequest 用於更新用戶狀態
|
||||||
|
type UpdateStatusRequest struct {
|
||||||
|
UID string `json:"uid"` // 用戶 UID
|
||||||
|
Status member.Status `json:"status"` // 用戶狀態
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetUserInfoRequest 用於請求取得用戶詳細信息
|
||||||
|
type GetUserInfoRequest struct {
|
||||||
|
UID string `json:"uid,omitempty"` // 用戶 UID
|
||||||
|
NickName string `json:"nick_name,omitempty"` // 暱稱(可選)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetUserInfoResponse 用於返回用戶詳細信息
|
||||||
|
type GetUserInfoResponse struct {
|
||||||
|
Data UserInfo `json:"data"` // 用戶信息
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListUserInfoRequest 用於查詢符合條件的用戶列表
|
||||||
|
type ListUserInfoRequest struct {
|
||||||
|
VerificationType *member.AccountType `json:"verification_type,omitempty"` // 驗證類型(可選)
|
||||||
|
AlarmCategory *member.AlarmType `json:"alarm_category,omitempty"` // 警報類型(可選)
|
||||||
|
UserStatus *member.Status `json:"user_status,omitempty"` // 用戶狀態(可選)
|
||||||
|
CreateStartTime *int64 `json:"create_start_time,omitempty"` // 創建開始時間(可選)
|
||||||
|
CreateEndTime *int64 `json:"create_end_time,omitempty"` // 創建結束時間(可選)
|
||||||
|
PageSize int64 `json:"page_size"` // 每頁大小
|
||||||
|
PageIndex int64 `json:"page_index"` // 當前頁索引
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListUserInfoResponse 用於返回查詢的用戶列表及分頁信息
|
||||||
|
type ListUserInfoResponse struct {
|
||||||
|
Data []UserInfo `json:"data"` // 用戶列表
|
||||||
|
Page Pager `json:"page"` // 分頁信息
|
||||||
|
}
|
||||||
|
|
||||||
|
// VerifyAuthResultRequest 用於請求驗證授權結果
|
||||||
|
type VerifyAuthResultRequest struct {
|
||||||
|
Token string `json:"token"` // 驗證 Token
|
||||||
|
Account string `json:"account"` // 帳號
|
||||||
|
}
|
||||||
|
|
||||||
|
// VerifyAuthResultResponse 用於返回授權驗證結果
|
||||||
|
type VerifyAuthResultResponse struct {
|
||||||
|
Status bool `json:"status"` // 驗證結果狀態
|
||||||
|
}
|
||||||
|
|
||||||
|
// TwitterAccessTokenResponse 用於返回 Twitter 授權令牌
|
||||||
|
type TwitterAccessTokenResponse struct {
|
||||||
|
Token string `json:"token"` // 授權 Token
|
||||||
|
}
|
||||||
|
|
||||||
|
type GoogleTokenInfo struct {
|
||||||
|
Iss string `json:"iss"` // 發行者 (issuer) 通常為 "https://accounts.google.com"
|
||||||
|
Sub string `json:"sub"` // 使用者唯一 ID
|
||||||
|
Aud string `json:"aud"` // Audience,應該與你的 Client ID 匹配
|
||||||
|
Exp string `json:"exp"` // 過期時間 (UNIX timestamp)
|
||||||
|
Iat string `json:"iat"` // 發行時間 (UNIX timestamp)
|
||||||
|
Email string `json:"email"` // 使用者的電子郵件
|
||||||
|
EmailVerified string `json:"email_verified"` // 郵件是否已驗證
|
||||||
|
Name string `json:"name"` // 使用者的名稱
|
||||||
|
Picture string `json:"picture"` // 使用者的頭像 URL
|
||||||
|
}
|
||||||
|
|
||||||
|
type LineAccessTokenResponse struct {
|
||||||
|
AccessToken string `json:"access_token"`
|
||||||
|
IDToken string `json:"id_token"`
|
||||||
|
ExpiresIn int `json:"expires_in"`
|
||||||
|
TokenType string `json:"token_type"`
|
||||||
|
Scope string `json:"scope"`
|
||||||
|
RefreshToken string `json:"refresh_token"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type LineUserProfile struct {
|
||||||
|
UserID string `json:"userId"`
|
||||||
|
DisplayName string `json:"displayName"`
|
||||||
|
PictureURL string `json:"pictureUrl"`
|
||||||
|
StatusMessage string `json:"statusMessage"`
|
||||||
|
}
|
|
@ -0,0 +1,7 @@
|
||||||
|
package usecase
|
||||||
|
|
||||||
|
type Pager struct {
|
||||||
|
Total int64 `json:"total"` // 總數量
|
||||||
|
Size int64 `json:"size"` // 每頁大小
|
||||||
|
Index int64 `json:"index"` // 當前頁索引
|
||||||
|
}
|
|
@ -0,0 +1,7 @@
|
||||||
|
package usecase
|
||||||
|
|
||||||
|
import "context"
|
||||||
|
|
||||||
|
type UIDGenerateUseCase interface {
|
||||||
|
Generate(ctx context.Context) (string, error)
|
||||||
|
}
|
|
@ -0,0 +1,28 @@
|
||||||
|
package usecase
|
||||||
|
|
||||||
|
import "context"
|
||||||
|
|
||||||
|
type VerifyMemberUseCase interface {
|
||||||
|
// GenerateRefreshCode 這個帳號驗證碼(十分鐘),通用的
|
||||||
|
GenerateRefreshCode(ctx context.Context, req GenerateRefreshCodeRequest) (GenerateRefreshCodeResponse, error)
|
||||||
|
// VerifyRefreshCode 驗證驗證碼,驗證完會刪除
|
||||||
|
VerifyRefreshCode(ctx context.Context, req VerifyRefreshCodeRequest) error
|
||||||
|
// CheckRefreshCode 驗證驗證碼,驗證完不會刪除
|
||||||
|
CheckRefreshCode(ctx context.Context, req VerifyRefreshCodeRequest) error
|
||||||
|
// VerifyPlatformAuthResult 驗證平台授權結果 -> 看密碼對不對
|
||||||
|
VerifyPlatformAuthResult(ctx context.Context, req VerifyAuthResultRequest) (VerifyAuthResultResponse, error)
|
||||||
|
GoogleVerify
|
||||||
|
LineVerify
|
||||||
|
}
|
||||||
|
|
||||||
|
type GoogleVerify interface {
|
||||||
|
// VerifyGoogleAuthResult 驗證 Google 授權結果
|
||||||
|
VerifyGoogleAuthResult(ctx context.Context, req VerifyAuthResultRequest) (GoogleTokenInfo, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type LineVerify interface {
|
||||||
|
// LineCodeToAccessToken 換取 AccessToken
|
||||||
|
LineCodeToAccessToken(ctx context.Context, code string) (LineAccessTokenResponse, error)
|
||||||
|
// LineGetProfileByAccessToken 用 Access Token 換取使用者的基本資料
|
||||||
|
LineGetProfileByAccessToken(ctx context.Context, accessToken string) (*LineUserProfile, error)
|
||||||
|
}
|
|
@ -0,0 +1,185 @@
|
||||||
|
// Code generated by MockGen. DO NOT EDIT.
|
||||||
|
// Source: ./pkg/domain/repository/account.go
|
||||||
|
//
|
||||||
|
// Generated by this command:
|
||||||
|
//
|
||||||
|
// mockgen -source=./pkg/domain/repository/account.go -destination=./pkg/mock/repository/account.go -package=mock
|
||||||
|
//
|
||||||
|
|
||||||
|
// Package mock is a generated GoMock package.
|
||||||
|
package mock
|
||||||
|
|
||||||
|
import (
|
||||||
|
entity "app-cloudep-member-server/pkg/domain/entity"
|
||||||
|
context "context"
|
||||||
|
reflect "reflect"
|
||||||
|
|
||||||
|
mongo "go.mongodb.org/mongo-driver/mongo"
|
||||||
|
gomock "go.uber.org/mock/gomock"
|
||||||
|
)
|
||||||
|
|
||||||
|
// MockAccountRepository is a mock of AccountRepository interface.
|
||||||
|
type MockAccountRepository struct {
|
||||||
|
ctrl *gomock.Controller
|
||||||
|
recorder *MockAccountRepositoryMockRecorder
|
||||||
|
isgomock struct{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MockAccountRepositoryMockRecorder is the mock recorder for MockAccountRepository.
|
||||||
|
type MockAccountRepositoryMockRecorder struct {
|
||||||
|
mock *MockAccountRepository
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewMockAccountRepository creates a new mock instance.
|
||||||
|
func NewMockAccountRepository(ctrl *gomock.Controller) *MockAccountRepository {
|
||||||
|
mock := &MockAccountRepository{ctrl: ctrl}
|
||||||
|
mock.recorder = &MockAccountRepositoryMockRecorder{mock}
|
||||||
|
return mock
|
||||||
|
}
|
||||||
|
|
||||||
|
// EXPECT returns an object that allows the caller to indicate expected use.
|
||||||
|
func (m *MockAccountRepository) EXPECT() *MockAccountRepositoryMockRecorder {
|
||||||
|
return m.recorder
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete mocks base method.
|
||||||
|
func (m *MockAccountRepository) 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 *MockAccountRepositoryMockRecorder) Delete(ctx, id any) *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Delete", reflect.TypeOf((*MockAccountRepository)(nil).Delete), ctx, id)
|
||||||
|
}
|
||||||
|
|
||||||
|
// FindOne mocks base method.
|
||||||
|
func (m *MockAccountRepository) FindOne(ctx context.Context, id string) (*entity.Account, error) {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
ret := m.ctrl.Call(m, "FindOne", ctx, id)
|
||||||
|
ret0, _ := ret[0].(*entity.Account)
|
||||||
|
ret1, _ := ret[1].(error)
|
||||||
|
return ret0, ret1
|
||||||
|
}
|
||||||
|
|
||||||
|
// FindOne indicates an expected call of FindOne.
|
||||||
|
func (mr *MockAccountRepositoryMockRecorder) FindOne(ctx, id any) *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "FindOne", reflect.TypeOf((*MockAccountRepository)(nil).FindOne), ctx, id)
|
||||||
|
}
|
||||||
|
|
||||||
|
// FindOneByAccount mocks base method.
|
||||||
|
func (m *MockAccountRepository) FindOneByAccount(ctx context.Context, loginID string) (*entity.Account, error) {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
ret := m.ctrl.Call(m, "FindOneByAccount", ctx, loginID)
|
||||||
|
ret0, _ := ret[0].(*entity.Account)
|
||||||
|
ret1, _ := ret[1].(error)
|
||||||
|
return ret0, ret1
|
||||||
|
}
|
||||||
|
|
||||||
|
// FindOneByAccount indicates an expected call of FindOneByAccount.
|
||||||
|
func (mr *MockAccountRepositoryMockRecorder) FindOneByAccount(ctx, loginID any) *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "FindOneByAccount", reflect.TypeOf((*MockAccountRepository)(nil).FindOneByAccount), ctx, loginID)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Index20241226001UP mocks base method.
|
||||||
|
func (m *MockAccountRepository) Index20241226001UP(ctx context.Context) (*mongo.Cursor, error) {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
ret := m.ctrl.Call(m, "Index20241226001UP", ctx)
|
||||||
|
ret0, _ := ret[0].(*mongo.Cursor)
|
||||||
|
ret1, _ := ret[1].(error)
|
||||||
|
return ret0, ret1
|
||||||
|
}
|
||||||
|
|
||||||
|
// Index20241226001UP indicates an expected call of Index20241226001UP.
|
||||||
|
func (mr *MockAccountRepositoryMockRecorder) Index20241226001UP(ctx any) *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Index20241226001UP", reflect.TypeOf((*MockAccountRepository)(nil).Index20241226001UP), ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Insert mocks base method.
|
||||||
|
func (m *MockAccountRepository) Insert(ctx context.Context, data *entity.Account) 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 *MockAccountRepositoryMockRecorder) Insert(ctx, data any) *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Insert", reflect.TypeOf((*MockAccountRepository)(nil).Insert), ctx, data)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update mocks base method.
|
||||||
|
func (m *MockAccountRepository) Update(ctx context.Context, data *entity.Account) (*mongo.UpdateResult, error) {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
ret := m.ctrl.Call(m, "Update", ctx, data)
|
||||||
|
ret0, _ := ret[0].(*mongo.UpdateResult)
|
||||||
|
ret1, _ := ret[1].(error)
|
||||||
|
return ret0, ret1
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update indicates an expected call of Update.
|
||||||
|
func (mr *MockAccountRepositoryMockRecorder) Update(ctx, data any) *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Update", reflect.TypeOf((*MockAccountRepository)(nil).Update), ctx, data)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateTokenByLoginID mocks base method.
|
||||||
|
func (m *MockAccountRepository) UpdateTokenByLoginID(ctx context.Context, account, token string) error {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
ret := m.ctrl.Call(m, "UpdateTokenByLoginID", ctx, account, token)
|
||||||
|
ret0, _ := ret[0].(error)
|
||||||
|
return ret0
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateTokenByLoginID indicates an expected call of UpdateTokenByLoginID.
|
||||||
|
func (mr *MockAccountRepositoryMockRecorder) UpdateTokenByLoginID(ctx, account, token any) *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateTokenByLoginID", reflect.TypeOf((*MockAccountRepository)(nil).UpdateTokenByLoginID), ctx, account, token)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MockAccountIndexUP is a mock of AccountIndexUP interface.
|
||||||
|
type MockAccountIndexUP struct {
|
||||||
|
ctrl *gomock.Controller
|
||||||
|
recorder *MockAccountIndexUPMockRecorder
|
||||||
|
isgomock struct{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MockAccountIndexUPMockRecorder is the mock recorder for MockAccountIndexUP.
|
||||||
|
type MockAccountIndexUPMockRecorder struct {
|
||||||
|
mock *MockAccountIndexUP
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewMockAccountIndexUP creates a new mock instance.
|
||||||
|
func NewMockAccountIndexUP(ctrl *gomock.Controller) *MockAccountIndexUP {
|
||||||
|
mock := &MockAccountIndexUP{ctrl: ctrl}
|
||||||
|
mock.recorder = &MockAccountIndexUPMockRecorder{mock}
|
||||||
|
return mock
|
||||||
|
}
|
||||||
|
|
||||||
|
// EXPECT returns an object that allows the caller to indicate expected use.
|
||||||
|
func (m *MockAccountIndexUP) EXPECT() *MockAccountIndexUPMockRecorder {
|
||||||
|
return m.recorder
|
||||||
|
}
|
||||||
|
|
||||||
|
// Index20241226001UP mocks base method.
|
||||||
|
func (m *MockAccountIndexUP) Index20241226001UP(ctx context.Context) (*mongo.Cursor, error) {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
ret := m.ctrl.Call(m, "Index20241226001UP", ctx)
|
||||||
|
ret0, _ := ret[0].(*mongo.Cursor)
|
||||||
|
ret1, _ := ret[1].(error)
|
||||||
|
return ret0, ret1
|
||||||
|
}
|
||||||
|
|
||||||
|
// Index20241226001UP indicates an expected call of Index20241226001UP.
|
||||||
|
func (mr *MockAccountIndexUPMockRecorder) Index20241226001UP(ctx any) *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Index20241226001UP", reflect.TypeOf((*MockAccountIndexUP)(nil).Index20241226001UP), ctx)
|
||||||
|
}
|
|
@ -0,0 +1,171 @@
|
||||||
|
// Code generated by MockGen. DO NOT EDIT.
|
||||||
|
// Source: ./pkg/domain/repository/account_uid.go
|
||||||
|
//
|
||||||
|
// Generated by this command:
|
||||||
|
//
|
||||||
|
// mockgen -source=./pkg/domain/repository/account_uid.go -destination=./pkg/mock/repository/account_uid.go -package=mock
|
||||||
|
//
|
||||||
|
|
||||||
|
// Package mock is a generated GoMock package.
|
||||||
|
package mock
|
||||||
|
|
||||||
|
import (
|
||||||
|
entity "app-cloudep-member-server/pkg/domain/entity"
|
||||||
|
context "context"
|
||||||
|
reflect "reflect"
|
||||||
|
|
||||||
|
mongo "go.mongodb.org/mongo-driver/mongo"
|
||||||
|
gomock "go.uber.org/mock/gomock"
|
||||||
|
)
|
||||||
|
|
||||||
|
// MockAccountUIDRepository is a mock of AccountUIDRepository interface.
|
||||||
|
type MockAccountUIDRepository struct {
|
||||||
|
ctrl *gomock.Controller
|
||||||
|
recorder *MockAccountUIDRepositoryMockRecorder
|
||||||
|
isgomock struct{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MockAccountUIDRepositoryMockRecorder is the mock recorder for MockAccountUIDRepository.
|
||||||
|
type MockAccountUIDRepositoryMockRecorder struct {
|
||||||
|
mock *MockAccountUIDRepository
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewMockAccountUIDRepository creates a new mock instance.
|
||||||
|
func NewMockAccountUIDRepository(ctrl *gomock.Controller) *MockAccountUIDRepository {
|
||||||
|
mock := &MockAccountUIDRepository{ctrl: ctrl}
|
||||||
|
mock.recorder = &MockAccountUIDRepositoryMockRecorder{mock}
|
||||||
|
return mock
|
||||||
|
}
|
||||||
|
|
||||||
|
// EXPECT returns an object that allows the caller to indicate expected use.
|
||||||
|
func (m *MockAccountUIDRepository) EXPECT() *MockAccountUIDRepositoryMockRecorder {
|
||||||
|
return m.recorder
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete mocks base method.
|
||||||
|
func (m *MockAccountUIDRepository) 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 *MockAccountUIDRepositoryMockRecorder) Delete(ctx, id any) *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Delete", reflect.TypeOf((*MockAccountUIDRepository)(nil).Delete), ctx, id)
|
||||||
|
}
|
||||||
|
|
||||||
|
// FindOne mocks base method.
|
||||||
|
func (m *MockAccountUIDRepository) FindOne(ctx context.Context, id string) (*entity.AccountUID, error) {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
ret := m.ctrl.Call(m, "FindOne", ctx, id)
|
||||||
|
ret0, _ := ret[0].(*entity.AccountUID)
|
||||||
|
ret1, _ := ret[1].(error)
|
||||||
|
return ret0, ret1
|
||||||
|
}
|
||||||
|
|
||||||
|
// FindOne indicates an expected call of FindOne.
|
||||||
|
func (mr *MockAccountUIDRepositoryMockRecorder) FindOne(ctx, id any) *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "FindOne", reflect.TypeOf((*MockAccountUIDRepository)(nil).FindOne), ctx, id)
|
||||||
|
}
|
||||||
|
|
||||||
|
// FindUIDByLoginID mocks base method.
|
||||||
|
func (m *MockAccountUIDRepository) FindUIDByLoginID(ctx context.Context, loginID string) (*entity.AccountUID, error) {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
ret := m.ctrl.Call(m, "FindUIDByLoginID", ctx, loginID)
|
||||||
|
ret0, _ := ret[0].(*entity.AccountUID)
|
||||||
|
ret1, _ := ret[1].(error)
|
||||||
|
return ret0, ret1
|
||||||
|
}
|
||||||
|
|
||||||
|
// FindUIDByLoginID indicates an expected call of FindUIDByLoginID.
|
||||||
|
func (mr *MockAccountUIDRepositoryMockRecorder) FindUIDByLoginID(ctx, loginID any) *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "FindUIDByLoginID", reflect.TypeOf((*MockAccountUIDRepository)(nil).FindUIDByLoginID), ctx, loginID)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Index20241226001UP mocks base method.
|
||||||
|
func (m *MockAccountUIDRepository) Index20241226001UP(ctx context.Context) (*mongo.Cursor, error) {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
ret := m.ctrl.Call(m, "Index20241226001UP", ctx)
|
||||||
|
ret0, _ := ret[0].(*mongo.Cursor)
|
||||||
|
ret1, _ := ret[1].(error)
|
||||||
|
return ret0, ret1
|
||||||
|
}
|
||||||
|
|
||||||
|
// Index20241226001UP indicates an expected call of Index20241226001UP.
|
||||||
|
func (mr *MockAccountUIDRepositoryMockRecorder) Index20241226001UP(ctx any) *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Index20241226001UP", reflect.TypeOf((*MockAccountUIDRepository)(nil).Index20241226001UP), ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Insert mocks base method.
|
||||||
|
func (m *MockAccountUIDRepository) Insert(ctx context.Context, data *entity.AccountUID) 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 *MockAccountUIDRepositoryMockRecorder) Insert(ctx, data any) *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Insert", reflect.TypeOf((*MockAccountUIDRepository)(nil).Insert), ctx, data)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update mocks base method.
|
||||||
|
func (m *MockAccountUIDRepository) Update(ctx context.Context, data *entity.AccountUID) (*mongo.UpdateResult, error) {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
ret := m.ctrl.Call(m, "Update", ctx, data)
|
||||||
|
ret0, _ := ret[0].(*mongo.UpdateResult)
|
||||||
|
ret1, _ := ret[1].(error)
|
||||||
|
return ret0, ret1
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update indicates an expected call of Update.
|
||||||
|
func (mr *MockAccountUIDRepositoryMockRecorder) Update(ctx, data any) *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Update", reflect.TypeOf((*MockAccountUIDRepository)(nil).Update), ctx, data)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MockAccountUIDIndexUP is a mock of AccountUIDIndexUP interface.
|
||||||
|
type MockAccountUIDIndexUP struct {
|
||||||
|
ctrl *gomock.Controller
|
||||||
|
recorder *MockAccountUIDIndexUPMockRecorder
|
||||||
|
isgomock struct{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MockAccountUIDIndexUPMockRecorder is the mock recorder for MockAccountUIDIndexUP.
|
||||||
|
type MockAccountUIDIndexUPMockRecorder struct {
|
||||||
|
mock *MockAccountUIDIndexUP
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewMockAccountUIDIndexUP creates a new mock instance.
|
||||||
|
func NewMockAccountUIDIndexUP(ctrl *gomock.Controller) *MockAccountUIDIndexUP {
|
||||||
|
mock := &MockAccountUIDIndexUP{ctrl: ctrl}
|
||||||
|
mock.recorder = &MockAccountUIDIndexUPMockRecorder{mock}
|
||||||
|
return mock
|
||||||
|
}
|
||||||
|
|
||||||
|
// EXPECT returns an object that allows the caller to indicate expected use.
|
||||||
|
func (m *MockAccountUIDIndexUP) EXPECT() *MockAccountUIDIndexUPMockRecorder {
|
||||||
|
return m.recorder
|
||||||
|
}
|
||||||
|
|
||||||
|
// Index20241226001UP mocks base method.
|
||||||
|
func (m *MockAccountUIDIndexUP) Index20241226001UP(ctx context.Context) (*mongo.Cursor, error) {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
ret := m.ctrl.Call(m, "Index20241226001UP", ctx)
|
||||||
|
ret0, _ := ret[0].(*mongo.Cursor)
|
||||||
|
ret1, _ := ret[1].(error)
|
||||||
|
return ret0, ret1
|
||||||
|
}
|
||||||
|
|
||||||
|
// Index20241226001UP indicates an expected call of Index20241226001UP.
|
||||||
|
func (mr *MockAccountUIDIndexUPMockRecorder) Index20241226001UP(ctx any) *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Index20241226001UP", reflect.TypeOf((*MockAccountUIDIndexUP)(nil).Index20241226001UP), ctx)
|
||||||
|
}
|
|
@ -0,0 +1,200 @@
|
||||||
|
// Code generated by MockGen. DO NOT EDIT.
|
||||||
|
// Source: ./pkg/domain/repository/auto_id.go
|
||||||
|
//
|
||||||
|
// Generated by this command:
|
||||||
|
//
|
||||||
|
// mockgen -source=./pkg/domain/repository/auto_id.go -destination=./pkg/mock/repository/auto_id.go -package=mock
|
||||||
|
//
|
||||||
|
|
||||||
|
// Package mock is a generated GoMock package.
|
||||||
|
package mock
|
||||||
|
|
||||||
|
import (
|
||||||
|
entity "app-cloudep-member-server/pkg/domain/entity"
|
||||||
|
context "context"
|
||||||
|
reflect "reflect"
|
||||||
|
|
||||||
|
mongo "go.mongodb.org/mongo-driver/mongo"
|
||||||
|
gomock "go.uber.org/mock/gomock"
|
||||||
|
)
|
||||||
|
|
||||||
|
// MockAutoIDRepository is a mock of AutoIDRepository interface.
|
||||||
|
type MockAutoIDRepository struct {
|
||||||
|
ctrl *gomock.Controller
|
||||||
|
recorder *MockAutoIDRepositoryMockRecorder
|
||||||
|
isgomock struct{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MockAutoIDRepositoryMockRecorder is the mock recorder for MockAutoIDRepository.
|
||||||
|
type MockAutoIDRepositoryMockRecorder struct {
|
||||||
|
mock *MockAutoIDRepository
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewMockAutoIDRepository creates a new mock instance.
|
||||||
|
func NewMockAutoIDRepository(ctrl *gomock.Controller) *MockAutoIDRepository {
|
||||||
|
mock := &MockAutoIDRepository{ctrl: ctrl}
|
||||||
|
mock.recorder = &MockAutoIDRepositoryMockRecorder{mock}
|
||||||
|
return mock
|
||||||
|
}
|
||||||
|
|
||||||
|
// EXPECT returns an object that allows the caller to indicate expected use.
|
||||||
|
func (m *MockAutoIDRepository) EXPECT() *MockAutoIDRepositoryMockRecorder {
|
||||||
|
return m.recorder
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete mocks base method.
|
||||||
|
func (m *MockAutoIDRepository) 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 *MockAutoIDRepositoryMockRecorder) Delete(ctx, id any) *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Delete", reflect.TypeOf((*MockAutoIDRepository)(nil).Delete), ctx, id)
|
||||||
|
}
|
||||||
|
|
||||||
|
// FindOne mocks base method.
|
||||||
|
func (m *MockAutoIDRepository) FindOne(ctx context.Context, id string) (*entity.AutoID, error) {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
ret := m.ctrl.Call(m, "FindOne", ctx, id)
|
||||||
|
ret0, _ := ret[0].(*entity.AutoID)
|
||||||
|
ret1, _ := ret[1].(error)
|
||||||
|
return ret0, ret1
|
||||||
|
}
|
||||||
|
|
||||||
|
// FindOne indicates an expected call of FindOne.
|
||||||
|
func (mr *MockAutoIDRepositoryMockRecorder) FindOne(ctx, id any) *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "FindOne", reflect.TypeOf((*MockAutoIDRepository)(nil).FindOne), ctx, id)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetNumFromUID mocks base method.
|
||||||
|
func (m *MockAutoIDRepository) GetNumFromUID(uid string) (int64, error) {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
ret := m.ctrl.Call(m, "GetNumFromUID", uid)
|
||||||
|
ret0, _ := ret[0].(int64)
|
||||||
|
ret1, _ := ret[1].(error)
|
||||||
|
return ret0, ret1
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetNumFromUID indicates an expected call of GetNumFromUID.
|
||||||
|
func (mr *MockAutoIDRepositoryMockRecorder) GetNumFromUID(uid any) *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetNumFromUID", reflect.TypeOf((*MockAutoIDRepository)(nil).GetNumFromUID), uid)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetUIDFromNum mocks base method.
|
||||||
|
func (m *MockAutoIDRepository) GetUIDFromNum(num int64) (string, error) {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
ret := m.ctrl.Call(m, "GetUIDFromNum", num)
|
||||||
|
ret0, _ := ret[0].(string)
|
||||||
|
ret1, _ := ret[1].(error)
|
||||||
|
return ret0, ret1
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetUIDFromNum indicates an expected call of GetUIDFromNum.
|
||||||
|
func (mr *MockAutoIDRepositoryMockRecorder) GetUIDFromNum(num any) *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetUIDFromNum", reflect.TypeOf((*MockAutoIDRepository)(nil).GetUIDFromNum), num)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Inc mocks base method.
|
||||||
|
func (m *MockAutoIDRepository) Inc(ctx context.Context, data *entity.AutoID) error {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
ret := m.ctrl.Call(m, "Inc", ctx, data)
|
||||||
|
ret0, _ := ret[0].(error)
|
||||||
|
return ret0
|
||||||
|
}
|
||||||
|
|
||||||
|
// Inc indicates an expected call of Inc.
|
||||||
|
func (mr *MockAutoIDRepositoryMockRecorder) Inc(ctx, data any) *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Inc", reflect.TypeOf((*MockAutoIDRepository)(nil).Inc), ctx, data)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Index20241226001UP mocks base method.
|
||||||
|
func (m *MockAutoIDRepository) Index20241226001UP(ctx context.Context) (*mongo.Cursor, error) {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
ret := m.ctrl.Call(m, "Index20241226001UP", ctx)
|
||||||
|
ret0, _ := ret[0].(*mongo.Cursor)
|
||||||
|
ret1, _ := ret[1].(error)
|
||||||
|
return ret0, ret1
|
||||||
|
}
|
||||||
|
|
||||||
|
// Index20241226001UP indicates an expected call of Index20241226001UP.
|
||||||
|
func (mr *MockAutoIDRepositoryMockRecorder) Index20241226001UP(ctx any) *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Index20241226001UP", reflect.TypeOf((*MockAutoIDRepository)(nil).Index20241226001UP), ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Insert mocks base method.
|
||||||
|
func (m *MockAutoIDRepository) Insert(ctx context.Context, data *entity.AutoID) 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 *MockAutoIDRepositoryMockRecorder) Insert(ctx, data any) *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Insert", reflect.TypeOf((*MockAutoIDRepository)(nil).Insert), ctx, data)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update mocks base method.
|
||||||
|
func (m *MockAutoIDRepository) Update(ctx context.Context, data *entity.AutoID) (*mongo.UpdateResult, error) {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
ret := m.ctrl.Call(m, "Update", ctx, data)
|
||||||
|
ret0, _ := ret[0].(*mongo.UpdateResult)
|
||||||
|
ret1, _ := ret[1].(error)
|
||||||
|
return ret0, ret1
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update indicates an expected call of Update.
|
||||||
|
func (mr *MockAutoIDRepositoryMockRecorder) Update(ctx, data any) *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Update", reflect.TypeOf((*MockAutoIDRepository)(nil).Update), ctx, data)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MockAutoIDIndexUP is a mock of AutoIDIndexUP interface.
|
||||||
|
type MockAutoIDIndexUP struct {
|
||||||
|
ctrl *gomock.Controller
|
||||||
|
recorder *MockAutoIDIndexUPMockRecorder
|
||||||
|
isgomock struct{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MockAutoIDIndexUPMockRecorder is the mock recorder for MockAutoIDIndexUP.
|
||||||
|
type MockAutoIDIndexUPMockRecorder struct {
|
||||||
|
mock *MockAutoIDIndexUP
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewMockAutoIDIndexUP creates a new mock instance.
|
||||||
|
func NewMockAutoIDIndexUP(ctrl *gomock.Controller) *MockAutoIDIndexUP {
|
||||||
|
mock := &MockAutoIDIndexUP{ctrl: ctrl}
|
||||||
|
mock.recorder = &MockAutoIDIndexUPMockRecorder{mock}
|
||||||
|
return mock
|
||||||
|
}
|
||||||
|
|
||||||
|
// EXPECT returns an object that allows the caller to indicate expected use.
|
||||||
|
func (m *MockAutoIDIndexUP) EXPECT() *MockAutoIDIndexUPMockRecorder {
|
||||||
|
return m.recorder
|
||||||
|
}
|
||||||
|
|
||||||
|
// Index20241226001UP mocks base method.
|
||||||
|
func (m *MockAutoIDIndexUP) Index20241226001UP(ctx context.Context) (*mongo.Cursor, error) {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
ret := m.ctrl.Call(m, "Index20241226001UP", ctx)
|
||||||
|
ret0, _ := ret[0].(*mongo.Cursor)
|
||||||
|
ret1, _ := ret[1].(error)
|
||||||
|
return ret0, ret1
|
||||||
|
}
|
||||||
|
|
||||||
|
// Index20241226001UP indicates an expected call of Index20241226001UP.
|
||||||
|
func (mr *MockAutoIDIndexUPMockRecorder) Index20241226001UP(ctx any) *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Index20241226001UP", reflect.TypeOf((*MockAutoIDIndexUP)(nil).Index20241226001UP), ctx)
|
||||||
|
}
|
|
@ -0,0 +1,342 @@
|
||||||
|
// Code generated by MockGen. DO NOT EDIT.
|
||||||
|
// Source: ./pkg/domain/repository/user.go
|
||||||
|
//
|
||||||
|
// Generated by this command:
|
||||||
|
//
|
||||||
|
// mockgen -source=./pkg/domain/repository/user.go -destination=./pkg/mock/repository/user.go -package=mock
|
||||||
|
//
|
||||||
|
|
||||||
|
// Package mock is a generated GoMock package.
|
||||||
|
package mock
|
||||||
|
|
||||||
|
import (
|
||||||
|
entity "app-cloudep-member-server/pkg/domain/entity"
|
||||||
|
repository "app-cloudep-member-server/pkg/domain/repository"
|
||||||
|
context "context"
|
||||||
|
reflect "reflect"
|
||||||
|
|
||||||
|
mongo "go.mongodb.org/mongo-driver/mongo"
|
||||||
|
gomock "go.uber.org/mock/gomock"
|
||||||
|
)
|
||||||
|
|
||||||
|
// MockUserRepository is a mock of UserRepository interface.
|
||||||
|
type MockUserRepository struct {
|
||||||
|
ctrl *gomock.Controller
|
||||||
|
recorder *MockUserRepositoryMockRecorder
|
||||||
|
isgomock struct{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MockUserRepositoryMockRecorder is the mock recorder for MockUserRepository.
|
||||||
|
type MockUserRepositoryMockRecorder struct {
|
||||||
|
mock *MockUserRepository
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewMockUserRepository creates a new mock instance.
|
||||||
|
func NewMockUserRepository(ctrl *gomock.Controller) *MockUserRepository {
|
||||||
|
mock := &MockUserRepository{ctrl: ctrl}
|
||||||
|
mock.recorder = &MockUserRepositoryMockRecorder{mock}
|
||||||
|
return mock
|
||||||
|
}
|
||||||
|
|
||||||
|
// EXPECT returns an object that allows the caller to indicate expected use.
|
||||||
|
func (m *MockUserRepository) EXPECT() *MockUserRepositoryMockRecorder {
|
||||||
|
return m.recorder
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete mocks base method.
|
||||||
|
func (m *MockUserRepository) 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 *MockUserRepositoryMockRecorder) Delete(ctx, id any) *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Delete", reflect.TypeOf((*MockUserRepository)(nil).Delete), ctx, id)
|
||||||
|
}
|
||||||
|
|
||||||
|
// FindOne mocks base method.
|
||||||
|
func (m *MockUserRepository) FindOne(ctx context.Context, id string) (*entity.User, error) {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
ret := m.ctrl.Call(m, "FindOne", ctx, id)
|
||||||
|
ret0, _ := ret[0].(*entity.User)
|
||||||
|
ret1, _ := ret[1].(error)
|
||||||
|
return ret0, ret1
|
||||||
|
}
|
||||||
|
|
||||||
|
// FindOne indicates an expected call of FindOne.
|
||||||
|
func (mr *MockUserRepositoryMockRecorder) FindOne(ctx, id any) *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "FindOne", reflect.TypeOf((*MockUserRepository)(nil).FindOne), ctx, id)
|
||||||
|
}
|
||||||
|
|
||||||
|
// FindOneByNickName mocks base method.
|
||||||
|
func (m *MockUserRepository) FindOneByNickName(ctx context.Context, nickName string) (*entity.User, error) {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
ret := m.ctrl.Call(m, "FindOneByNickName", ctx, nickName)
|
||||||
|
ret0, _ := ret[0].(*entity.User)
|
||||||
|
ret1, _ := ret[1].(error)
|
||||||
|
return ret0, ret1
|
||||||
|
}
|
||||||
|
|
||||||
|
// FindOneByNickName indicates an expected call of FindOneByNickName.
|
||||||
|
func (mr *MockUserRepositoryMockRecorder) FindOneByNickName(ctx, nickName any) *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "FindOneByNickName", reflect.TypeOf((*MockUserRepository)(nil).FindOneByNickName), ctx, nickName)
|
||||||
|
}
|
||||||
|
|
||||||
|
// FindOneByUID mocks base method.
|
||||||
|
func (m *MockUserRepository) FindOneByUID(ctx context.Context, uid string) (*entity.User, error) {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
ret := m.ctrl.Call(m, "FindOneByUID", ctx, uid)
|
||||||
|
ret0, _ := ret[0].(*entity.User)
|
||||||
|
ret1, _ := ret[1].(error)
|
||||||
|
return ret0, ret1
|
||||||
|
}
|
||||||
|
|
||||||
|
// FindOneByUID indicates an expected call of FindOneByUID.
|
||||||
|
func (mr *MockUserRepositoryMockRecorder) FindOneByUID(ctx, uid any) *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "FindOneByUID", reflect.TypeOf((*MockUserRepository)(nil).FindOneByUID), ctx, uid)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Index20241226001UP mocks base method.
|
||||||
|
func (m *MockUserRepository) Index20241226001UP(ctx context.Context) (*mongo.Cursor, error) {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
ret := m.ctrl.Call(m, "Index20241226001UP", ctx)
|
||||||
|
ret0, _ := ret[0].(*mongo.Cursor)
|
||||||
|
ret1, _ := ret[1].(error)
|
||||||
|
return ret0, ret1
|
||||||
|
}
|
||||||
|
|
||||||
|
// Index20241226001UP indicates an expected call of Index20241226001UP.
|
||||||
|
func (mr *MockUserRepositoryMockRecorder) Index20241226001UP(ctx any) *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Index20241226001UP", reflect.TypeOf((*MockUserRepository)(nil).Index20241226001UP), ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Insert mocks base method.
|
||||||
|
func (m *MockUserRepository) Insert(ctx context.Context, data *entity.User) 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 *MockUserRepositoryMockRecorder) Insert(ctx, data any) *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Insert", reflect.TypeOf((*MockUserRepository)(nil).Insert), ctx, data)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListMembers mocks base method.
|
||||||
|
func (m *MockUserRepository) ListMembers(ctx context.Context, params *repository.UserQueryParams) ([]*entity.User, int64, error) {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
ret := m.ctrl.Call(m, "ListMembers", ctx, params)
|
||||||
|
ret0, _ := ret[0].([]*entity.User)
|
||||||
|
ret1, _ := ret[1].(int64)
|
||||||
|
ret2, _ := ret[2].(error)
|
||||||
|
return ret0, ret1, ret2
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListMembers indicates an expected call of ListMembers.
|
||||||
|
func (mr *MockUserRepositoryMockRecorder) ListMembers(ctx, params any) *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListMembers", reflect.TypeOf((*MockUserRepository)(nil).ListMembers), ctx, params)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update mocks base method.
|
||||||
|
func (m *MockUserRepository) Update(ctx context.Context, data *entity.User) (*mongo.UpdateResult, error) {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
ret := m.ctrl.Call(m, "Update", ctx, data)
|
||||||
|
ret0, _ := ret[0].(*mongo.UpdateResult)
|
||||||
|
ret1, _ := ret[1].(error)
|
||||||
|
return ret0, ret1
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update indicates an expected call of Update.
|
||||||
|
func (mr *MockUserRepositoryMockRecorder) Update(ctx, data any) *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Update", reflect.TypeOf((*MockUserRepository)(nil).Update), ctx, data)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateEmailVerifyStatus mocks base method.
|
||||||
|
func (m *MockUserRepository) UpdateEmailVerifyStatus(ctx context.Context, uid, email string) error {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
ret := m.ctrl.Call(m, "UpdateEmailVerifyStatus", ctx, uid, email)
|
||||||
|
ret0, _ := ret[0].(error)
|
||||||
|
return ret0
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateEmailVerifyStatus indicates an expected call of UpdateEmailVerifyStatus.
|
||||||
|
func (mr *MockUserRepositoryMockRecorder) UpdateEmailVerifyStatus(ctx, uid, email any) *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateEmailVerifyStatus", reflect.TypeOf((*MockUserRepository)(nil).UpdateEmailVerifyStatus), ctx, uid, email)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdatePhoneVerifyStatus mocks base method.
|
||||||
|
func (m *MockUserRepository) UpdatePhoneVerifyStatus(ctx context.Context, uid, phone string) error {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
ret := m.ctrl.Call(m, "UpdatePhoneVerifyStatus", ctx, uid, phone)
|
||||||
|
ret0, _ := ret[0].(error)
|
||||||
|
return ret0
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdatePhoneVerifyStatus indicates an expected call of UpdatePhoneVerifyStatus.
|
||||||
|
func (mr *MockUserRepositoryMockRecorder) UpdatePhoneVerifyStatus(ctx, uid, phone any) *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdatePhoneVerifyStatus", reflect.TypeOf((*MockUserRepository)(nil).UpdatePhoneVerifyStatus), ctx, uid, phone)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateStatus mocks base method.
|
||||||
|
func (m *MockUserRepository) UpdateStatus(ctx context.Context, uid string, status int32) error {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
ret := m.ctrl.Call(m, "UpdateStatus", ctx, uid, status)
|
||||||
|
ret0, _ := ret[0].(error)
|
||||||
|
return ret0
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateStatus indicates an expected call of UpdateStatus.
|
||||||
|
func (mr *MockUserRepositoryMockRecorder) UpdateStatus(ctx, uid, status any) *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateStatus", reflect.TypeOf((*MockUserRepository)(nil).UpdateStatus), ctx, uid, status)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateUserDetailsByUID mocks base method.
|
||||||
|
func (m *MockUserRepository) UpdateUserDetailsByUID(ctx context.Context, data *repository.UpdateUserInfoRequest) error {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
ret := m.ctrl.Call(m, "UpdateUserDetailsByUID", ctx, data)
|
||||||
|
ret0, _ := ret[0].(error)
|
||||||
|
return ret0
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateUserDetailsByUID indicates an expected call of UpdateUserDetailsByUID.
|
||||||
|
func (mr *MockUserRepositoryMockRecorder) UpdateUserDetailsByUID(ctx, data any) *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateUserDetailsByUID", reflect.TypeOf((*MockUserRepository)(nil).UpdateUserDetailsByUID), ctx, data)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MockUserIndexUP is a mock of UserIndexUP interface.
|
||||||
|
type MockUserIndexUP struct {
|
||||||
|
ctrl *gomock.Controller
|
||||||
|
recorder *MockUserIndexUPMockRecorder
|
||||||
|
isgomock struct{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MockUserIndexUPMockRecorder is the mock recorder for MockUserIndexUP.
|
||||||
|
type MockUserIndexUPMockRecorder struct {
|
||||||
|
mock *MockUserIndexUP
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewMockUserIndexUP creates a new mock instance.
|
||||||
|
func NewMockUserIndexUP(ctrl *gomock.Controller) *MockUserIndexUP {
|
||||||
|
mock := &MockUserIndexUP{ctrl: ctrl}
|
||||||
|
mock.recorder = &MockUserIndexUPMockRecorder{mock}
|
||||||
|
return mock
|
||||||
|
}
|
||||||
|
|
||||||
|
// EXPECT returns an object that allows the caller to indicate expected use.
|
||||||
|
func (m *MockUserIndexUP) EXPECT() *MockUserIndexUPMockRecorder {
|
||||||
|
return m.recorder
|
||||||
|
}
|
||||||
|
|
||||||
|
// Index20241226001UP mocks base method.
|
||||||
|
func (m *MockUserIndexUP) Index20241226001UP(ctx context.Context) (*mongo.Cursor, error) {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
ret := m.ctrl.Call(m, "Index20241226001UP", ctx)
|
||||||
|
ret0, _ := ret[0].(*mongo.Cursor)
|
||||||
|
ret1, _ := ret[1].(error)
|
||||||
|
return ret0, ret1
|
||||||
|
}
|
||||||
|
|
||||||
|
// Index20241226001UP indicates an expected call of Index20241226001UP.
|
||||||
|
func (mr *MockUserIndexUPMockRecorder) Index20241226001UP(ctx any) *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Index20241226001UP", reflect.TypeOf((*MockUserIndexUP)(nil).Index20241226001UP), ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MockBaseUserRepository is a mock of BaseUserRepository interface.
|
||||||
|
type MockBaseUserRepository struct {
|
||||||
|
ctrl *gomock.Controller
|
||||||
|
recorder *MockBaseUserRepositoryMockRecorder
|
||||||
|
isgomock struct{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MockBaseUserRepositoryMockRecorder is the mock recorder for MockBaseUserRepository.
|
||||||
|
type MockBaseUserRepositoryMockRecorder struct {
|
||||||
|
mock *MockBaseUserRepository
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewMockBaseUserRepository creates a new mock instance.
|
||||||
|
func NewMockBaseUserRepository(ctrl *gomock.Controller) *MockBaseUserRepository {
|
||||||
|
mock := &MockBaseUserRepository{ctrl: ctrl}
|
||||||
|
mock.recorder = &MockBaseUserRepositoryMockRecorder{mock}
|
||||||
|
return mock
|
||||||
|
}
|
||||||
|
|
||||||
|
// EXPECT returns an object that allows the caller to indicate expected use.
|
||||||
|
func (m *MockBaseUserRepository) EXPECT() *MockBaseUserRepositoryMockRecorder {
|
||||||
|
return m.recorder
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete mocks base method.
|
||||||
|
func (m *MockBaseUserRepository) 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 *MockBaseUserRepositoryMockRecorder) Delete(ctx, id any) *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Delete", reflect.TypeOf((*MockBaseUserRepository)(nil).Delete), ctx, id)
|
||||||
|
}
|
||||||
|
|
||||||
|
// FindOne mocks base method.
|
||||||
|
func (m *MockBaseUserRepository) FindOne(ctx context.Context, id string) (*entity.User, error) {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
ret := m.ctrl.Call(m, "FindOne", ctx, id)
|
||||||
|
ret0, _ := ret[0].(*entity.User)
|
||||||
|
ret1, _ := ret[1].(error)
|
||||||
|
return ret0, ret1
|
||||||
|
}
|
||||||
|
|
||||||
|
// FindOne indicates an expected call of FindOne.
|
||||||
|
func (mr *MockBaseUserRepositoryMockRecorder) FindOne(ctx, id any) *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "FindOne", reflect.TypeOf((*MockBaseUserRepository)(nil).FindOne), ctx, id)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Insert mocks base method.
|
||||||
|
func (m *MockBaseUserRepository) Insert(ctx context.Context, data *entity.User) 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 *MockBaseUserRepositoryMockRecorder) Insert(ctx, data any) *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Insert", reflect.TypeOf((*MockBaseUserRepository)(nil).Insert), ctx, data)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update mocks base method.
|
||||||
|
func (m *MockBaseUserRepository) Update(ctx context.Context, data *entity.User) (*mongo.UpdateResult, error) {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
ret := m.ctrl.Call(m, "Update", ctx, data)
|
||||||
|
ret0, _ := ret[0].(*mongo.UpdateResult)
|
||||||
|
ret1, _ := ret[1].(error)
|
||||||
|
return ret0, ret1
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update indicates an expected call of Update.
|
||||||
|
func (mr *MockBaseUserRepositoryMockRecorder) Update(ctx, data any) *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Update", reflect.TypeOf((*MockBaseUserRepository)(nil).Update), ctx, data)
|
||||||
|
}
|
|
@ -0,0 +1,84 @@
|
||||||
|
// Code generated by MockGen. DO NOT EDIT.
|
||||||
|
// Source: ./pkg/domain/repository/verify_code.go
|
||||||
|
//
|
||||||
|
// Generated by this command:
|
||||||
|
//
|
||||||
|
// mockgen -source=./pkg/domain/repository/verify_code.go -destination=./pkg/mock/repository/verify_code.go -package=mock
|
||||||
|
//
|
||||||
|
|
||||||
|
// Package mock is a generated GoMock package.
|
||||||
|
package mock
|
||||||
|
|
||||||
|
import (
|
||||||
|
context "context"
|
||||||
|
reflect "reflect"
|
||||||
|
|
||||||
|
gomock "go.uber.org/mock/gomock"
|
||||||
|
)
|
||||||
|
|
||||||
|
// MockVerifyCodeRepository is a mock of VerifyCodeRepository interface.
|
||||||
|
type MockVerifyCodeRepository struct {
|
||||||
|
ctrl *gomock.Controller
|
||||||
|
recorder *MockVerifyCodeRepositoryMockRecorder
|
||||||
|
isgomock struct{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MockVerifyCodeRepositoryMockRecorder is the mock recorder for MockVerifyCodeRepository.
|
||||||
|
type MockVerifyCodeRepositoryMockRecorder struct {
|
||||||
|
mock *MockVerifyCodeRepository
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewMockVerifyCodeRepository creates a new mock instance.
|
||||||
|
func NewMockVerifyCodeRepository(ctrl *gomock.Controller) *MockVerifyCodeRepository {
|
||||||
|
mock := &MockVerifyCodeRepository{ctrl: ctrl}
|
||||||
|
mock.recorder = &MockVerifyCodeRepositoryMockRecorder{mock}
|
||||||
|
return mock
|
||||||
|
}
|
||||||
|
|
||||||
|
// EXPECT returns an object that allows the caller to indicate expected use.
|
||||||
|
func (m *MockVerifyCodeRepository) EXPECT() *MockVerifyCodeRepositoryMockRecorder {
|
||||||
|
return m.recorder
|
||||||
|
}
|
||||||
|
|
||||||
|
// DelVerifyCode mocks base method.
|
||||||
|
func (m *MockVerifyCodeRepository) DelVerifyCode(ctx context.Context, loginID, checkType string) error {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
ret := m.ctrl.Call(m, "DelVerifyCode", ctx, loginID, checkType)
|
||||||
|
ret0, _ := ret[0].(error)
|
||||||
|
return ret0
|
||||||
|
}
|
||||||
|
|
||||||
|
// DelVerifyCode indicates an expected call of DelVerifyCode.
|
||||||
|
func (mr *MockVerifyCodeRepositoryMockRecorder) DelVerifyCode(ctx, loginID, checkType any) *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DelVerifyCode", reflect.TypeOf((*MockVerifyCodeRepository)(nil).DelVerifyCode), ctx, loginID, checkType)
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsVerifyCodeExist mocks base method.
|
||||||
|
func (m *MockVerifyCodeRepository) IsVerifyCodeExist(ctx context.Context, loginID, checkType string) (string, error) {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
ret := m.ctrl.Call(m, "IsVerifyCodeExist", ctx, loginID, checkType)
|
||||||
|
ret0, _ := ret[0].(string)
|
||||||
|
ret1, _ := ret[1].(error)
|
||||||
|
return ret0, ret1
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsVerifyCodeExist indicates an expected call of IsVerifyCodeExist.
|
||||||
|
func (mr *MockVerifyCodeRepositoryMockRecorder) IsVerifyCodeExist(ctx, loginID, checkType any) *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "IsVerifyCodeExist", reflect.TypeOf((*MockVerifyCodeRepository)(nil).IsVerifyCodeExist), ctx, loginID, checkType)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetVerifyCode mocks base method.
|
||||||
|
func (m *MockVerifyCodeRepository) SetVerifyCode(ctx context.Context, loginID, checkType, code string) error {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
ret := m.ctrl.Call(m, "SetVerifyCode", ctx, loginID, checkType, code)
|
||||||
|
ret0, _ := ret[0].(error)
|
||||||
|
return ret0
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetVerifyCode indicates an expected call of SetVerifyCode.
|
||||||
|
func (mr *MockVerifyCodeRepositoryMockRecorder) SetVerifyCode(ctx, loginID, checkType, code any) *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetVerifyCode", reflect.TypeOf((*MockVerifyCodeRepository)(nil).SetVerifyCode), ctx, loginID, checkType, code)
|
||||||
|
}
|
|
@ -0,0 +1,56 @@
|
||||||
|
// Code generated by MockGen. DO NOT EDIT.
|
||||||
|
// Source: ./pkg/domain/usecase/generate_uid.go
|
||||||
|
//
|
||||||
|
// Generated by this command:
|
||||||
|
//
|
||||||
|
// mockgen -source=./pkg/domain/usecase/generate_uid.go -destination=./pkg/mock/usecase/generate_uid.go -package=mock
|
||||||
|
//
|
||||||
|
|
||||||
|
// Package mock is a generated GoMock package.
|
||||||
|
package mock
|
||||||
|
|
||||||
|
import (
|
||||||
|
context "context"
|
||||||
|
reflect "reflect"
|
||||||
|
|
||||||
|
gomock "go.uber.org/mock/gomock"
|
||||||
|
)
|
||||||
|
|
||||||
|
// MockUIDGenerateUseCase is a mock of UIDGenerateUseCase interface.
|
||||||
|
type MockUIDGenerateUseCase struct {
|
||||||
|
ctrl *gomock.Controller
|
||||||
|
recorder *MockUIDGenerateUseCaseMockRecorder
|
||||||
|
isgomock struct{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MockUIDGenerateUseCaseMockRecorder is the mock recorder for MockUIDGenerateUseCase.
|
||||||
|
type MockUIDGenerateUseCaseMockRecorder struct {
|
||||||
|
mock *MockUIDGenerateUseCase
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewMockUIDGenerateUseCase creates a new mock instance.
|
||||||
|
func NewMockUIDGenerateUseCase(ctrl *gomock.Controller) *MockUIDGenerateUseCase {
|
||||||
|
mock := &MockUIDGenerateUseCase{ctrl: ctrl}
|
||||||
|
mock.recorder = &MockUIDGenerateUseCaseMockRecorder{mock}
|
||||||
|
return mock
|
||||||
|
}
|
||||||
|
|
||||||
|
// EXPECT returns an object that allows the caller to indicate expected use.
|
||||||
|
func (m *MockUIDGenerateUseCase) EXPECT() *MockUIDGenerateUseCaseMockRecorder {
|
||||||
|
return m.recorder
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate mocks base method.
|
||||||
|
func (m *MockUIDGenerateUseCase) Generate(ctx context.Context) (string, error) {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
ret := m.ctrl.Call(m, "Generate", ctx)
|
||||||
|
ret0, _ := ret[0].(string)
|
||||||
|
ret1, _ := ret[1].(error)
|
||||||
|
return ret0, ret1
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate indicates an expected call of Generate.
|
||||||
|
func (mr *MockUIDGenerateUseCaseMockRecorder) Generate(ctx any) *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Generate", reflect.TypeOf((*MockUIDGenerateUseCase)(nil).Generate), ctx)
|
||||||
|
}
|
|
@ -0,0 +1,155 @@
|
||||||
|
package repository
|
||||||
|
|
||||||
|
import (
|
||||||
|
"app-cloudep-member-server/pkg/domain"
|
||||||
|
"app-cloudep-member-server/pkg/domain/entity"
|
||||||
|
"app-cloudep-member-server/pkg/domain/repository"
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
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"
|
||||||
|
)
|
||||||
|
|
||||||
|
type AccountRepositoryParam struct {
|
||||||
|
Conf *mgo.Conf
|
||||||
|
CacheConf cache.CacheConf
|
||||||
|
DbOpts []mon.Option
|
||||||
|
CacheOpts []cache.Option
|
||||||
|
}
|
||||||
|
|
||||||
|
type AccountRepository struct {
|
||||||
|
DB mgo.DocumentDBWithCacheUseCase
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewAccountRepository(param AccountRepositoryParam) repository.AccountRepository {
|
||||||
|
e := entity.Account{}
|
||||||
|
documentDB, err := mgo.MustDocumentDBWithCache(
|
||||||
|
param.Conf,
|
||||||
|
e.CollectionName(),
|
||||||
|
param.CacheConf,
|
||||||
|
param.DbOpts,
|
||||||
|
param.CacheOpts,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &AccountRepository{
|
||||||
|
DB: documentDB,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (repo *AccountRepository) Insert(ctx context.Context, data *entity.Account) error {
|
||||||
|
if data.ID.IsZero() {
|
||||||
|
now := time.Now().UTC().UnixNano()
|
||||||
|
data.ID = primitive.NewObjectID()
|
||||||
|
data.CreateAt = &now
|
||||||
|
data.UpdateAt = &now
|
||||||
|
}
|
||||||
|
rk := domain.GetAccountRedisKey(data.ID.Hex())
|
||||||
|
_, err := repo.DB.InsertOne(ctx, rk, data)
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (repo *AccountRepository) FindOne(ctx context.Context, id string) (*entity.Account, error) {
|
||||||
|
oid, err := primitive.ObjectIDFromHex(id)
|
||||||
|
if err != nil {
|
||||||
|
return nil, ErrInvalidObjectID
|
||||||
|
}
|
||||||
|
|
||||||
|
var data entity.Account
|
||||||
|
rk := domain.GetAccountRedisKey(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 *AccountRepository) Update(ctx context.Context, data *entity.Account) (*mongo.UpdateResult, error) {
|
||||||
|
now := time.Now().UTC().UnixNano()
|
||||||
|
data.UpdateAt = &now
|
||||||
|
|
||||||
|
rk := domain.GetAccountRedisKey(data.ID.Hex())
|
||||||
|
res, err := repo.DB.UpdateOne(ctx, rk, bson.M{"_id": data.ID}, bson.M{"$set": data})
|
||||||
|
|
||||||
|
return res, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (repo *AccountRepository) Delete(ctx context.Context, id string) (int64, error) {
|
||||||
|
oid, err := primitive.ObjectIDFromHex(id)
|
||||||
|
if err != nil {
|
||||||
|
return 0, ErrInvalidObjectID
|
||||||
|
}
|
||||||
|
rk := domain.GetAccountRedisKey(id)
|
||||||
|
|
||||||
|
return repo.DB.DeleteOne(ctx, rk, bson.M{"_id": oid})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (repo *AccountRepository) FindOneByAccount(ctx context.Context, loginID string) (*entity.Account, error) {
|
||||||
|
// todo: 之後需要同步快取
|
||||||
|
var data entity.Account
|
||||||
|
err := repo.DB.GetClient().FindOne(ctx, &data, bson.M{"login_id": loginID})
|
||||||
|
switch {
|
||||||
|
case err == nil:
|
||||||
|
return &data, nil
|
||||||
|
case errors.Is(err, mon.ErrNotFound):
|
||||||
|
return nil, ErrNotFound
|
||||||
|
default:
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (repo *AccountRepository) UpdateTokenByLoginID(ctx context.Context, account string, token string) error {
|
||||||
|
// todo: 之後需要同步快取
|
||||||
|
filter := bson.M{"login_id": account}
|
||||||
|
update := bson.M{
|
||||||
|
"$set": bson.M{
|
||||||
|
"token": token,
|
||||||
|
"update_at": time.Now().UTC().UnixNano(),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
var data entity.Account
|
||||||
|
err := repo.DB.GetClient().FindOne(ctx, &data, filter)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
rk := domain.GetAccountRedisKey(data.ID.Hex())
|
||||||
|
|
||||||
|
modify, err := repo.DB.UpdateOne(ctx, rk, bson.M{"_id": data.ID}, update)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if modify.MatchedCount == 0 {
|
||||||
|
return ErrNotFound // 自定義的錯誤表示未找到記錄
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (repo *AccountRepository) Index20241226001UP(ctx context.Context) (*mongo.Cursor, error) {
|
||||||
|
// 等價於 db.account.createIndex({ "login_id": 1, "platform": 1}, {unique: true})
|
||||||
|
repo.DB.PopulateMultiIndex(ctx, []string{
|
||||||
|
"login_id",
|
||||||
|
"platform",
|
||||||
|
}, []int32{1, 1}, true)
|
||||||
|
|
||||||
|
// 等價於 db.account.createIndex({"create_at": 1})
|
||||||
|
repo.DB.PopulateIndex(ctx, "create_at", 1, false)
|
||||||
|
|
||||||
|
return repo.DB.GetClient().Indexes().List(ctx)
|
||||||
|
}
|
|
@ -0,0 +1,350 @@
|
||||||
|
package repository
|
||||||
|
|
||||||
|
import (
|
||||||
|
"app-cloudep-member-server/pkg/domain/entity"
|
||||||
|
"app-cloudep-member-server/pkg/domain/repository"
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
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"
|
||||||
|
"go.mongodb.org/mongo-driver/bson/primitive"
|
||||||
|
|
||||||
|
"github.com/zeromicro/go-zero/core/stores/redis"
|
||||||
|
)
|
||||||
|
|
||||||
|
func SetupTestAccountRepository(db string) (repository.AccountRepository, func(), error) {
|
||||||
|
h, p, tearDown, err := startMongoContainer()
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
s, _ := miniredis.Run()
|
||||||
|
|
||||||
|
conf := &mgo.Conf{
|
||||||
|
Schema: Schema,
|
||||||
|
Host: h,
|
||||||
|
Port: 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 := AccountRepositoryParam{
|
||||||
|
Conf: conf,
|
||||||
|
CacheConf: cacheConf,
|
||||||
|
CacheOpts: cacheOpts,
|
||||||
|
}
|
||||||
|
repo := NewAccountRepository(param)
|
||||||
|
_, _ = repo.Index20241226001UP(context.Background())
|
||||||
|
|
||||||
|
return repo, tearDown, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAccountModel_Insert(t *testing.T) {
|
||||||
|
repo, tearDown, err := SetupTestAccountRepository("testDB")
|
||||||
|
defer tearDown()
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
account *entity.Account
|
||||||
|
expectError bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "Valid account insert",
|
||||||
|
account: &entity.Account{
|
||||||
|
LoginID: "testuser1",
|
||||||
|
Token: "testtoken1",
|
||||||
|
Platform: 1,
|
||||||
|
},
|
||||||
|
expectError: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
// 插入測試帳戶
|
||||||
|
err := repo.Insert(context.Background(), tt.account)
|
||||||
|
if tt.expectError {
|
||||||
|
assert.Error(t, err)
|
||||||
|
} else {
|
||||||
|
assert.NoError(t, err, "插入操作應該成功")
|
||||||
|
|
||||||
|
// 檢查是否生成了 ObjectID 和時間戳
|
||||||
|
assert.NotZero(t, tt.account.ID, "ID 應該被生成")
|
||||||
|
assert.NotNil(t, tt.account.CreateAt, "CreateAt 應該被設置")
|
||||||
|
assert.NotNil(t, tt.account.UpdateAt, "UpdateAt 應該被設置")
|
||||||
|
|
||||||
|
// 檢查插入的時間是否合理
|
||||||
|
now := time.Now().UTC().UnixNano()
|
||||||
|
assert.LessOrEqual(t, *tt.account.CreateAt, now, "CreateAt 應在當前時間之前")
|
||||||
|
assert.LessOrEqual(t, *tt.account.UpdateAt, now, "UpdateAt 應在當前時間之前")
|
||||||
|
|
||||||
|
// 確認插入的資料
|
||||||
|
insertedAccount, err := repo.FindOne(context.Background(), tt.account.ID.Hex())
|
||||||
|
assert.NoError(t, err, "應該可以找到插入的帳號資料")
|
||||||
|
assert.Equal(t, tt.account.LoginID, insertedAccount.LoginID, "LoginID 應相同")
|
||||||
|
assert.Equal(t, tt.account.Token, insertedAccount.Token, "Token 應相同")
|
||||||
|
assert.Equal(t, tt.account.Platform, insertedAccount.Platform, "Platform 應相同")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAccountModel_FindOne(t *testing.T) {
|
||||||
|
repo, tearDown, err := SetupTestAccountRepository("testDB")
|
||||||
|
assert.NoError(t, err)
|
||||||
|
defer tearDown()
|
||||||
|
|
||||||
|
// 插入測試帳戶
|
||||||
|
account := &entity.Account{
|
||||||
|
LoginID: "testuser1",
|
||||||
|
Token: "testtoken1",
|
||||||
|
Platform: 1,
|
||||||
|
}
|
||||||
|
err = repo.Insert(context.TODO(), account)
|
||||||
|
assert.NoError(t, err, "插入應成功")
|
||||||
|
nid := primitive.NewObjectID()
|
||||||
|
t.Logf("Inserted account ID: %s, nid:%s", account.ID.Hex(), nid.Hex())
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
id string
|
||||||
|
expectError bool
|
||||||
|
expectedErr error
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "Valid ID",
|
||||||
|
id: account.ID.Hex(),
|
||||||
|
expectError: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Non-existent ID",
|
||||||
|
id: nid.Hex(),
|
||||||
|
expectError: true,
|
||||||
|
expectedErr: ErrNotFound,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Invalid ID format",
|
||||||
|
id: "invalid-id",
|
||||||
|
expectError: true,
|
||||||
|
expectedErr: ErrInvalidObjectID,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
result, err := repo.FindOne(context.TODO(), tt.id)
|
||||||
|
if tt.expectError {
|
||||||
|
assert.Error(t, err)
|
||||||
|
assert.Equal(t, tt.expectedErr, err)
|
||||||
|
} else {
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, account.LoginID, result.LoginID)
|
||||||
|
assert.Equal(t, account.Token, result.Token)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAccountModel_Update(t *testing.T) {
|
||||||
|
repo, tearDown, err := SetupTestAccountRepository("testDB")
|
||||||
|
assert.NoError(t, err)
|
||||||
|
defer tearDown()
|
||||||
|
|
||||||
|
// 插入測試帳戶
|
||||||
|
account := &entity.Account{
|
||||||
|
LoginID: "testuser1",
|
||||||
|
Token: "testtoken1",
|
||||||
|
Platform: 1,
|
||||||
|
}
|
||||||
|
err = repo.Insert(context.Background(), account)
|
||||||
|
assert.NoError(t, err, "插入應成功")
|
||||||
|
|
||||||
|
// 更新測試
|
||||||
|
newToken := "updatedToken"
|
||||||
|
account.Token = newToken
|
||||||
|
|
||||||
|
_, err = repo.Update(context.Background(), account)
|
||||||
|
assert.NoError(t, err, "更新應成功")
|
||||||
|
|
||||||
|
// 驗證更新結果
|
||||||
|
updatedAccount, err := repo.FindOne(context.Background(), account.ID.Hex())
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, newToken, updatedAccount.Token, "Token 應更新")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAccountModel_Delete(t *testing.T) {
|
||||||
|
repo, tearDown, err := SetupTestAccountRepository("testDB")
|
||||||
|
assert.NoError(t, err)
|
||||||
|
defer tearDown()
|
||||||
|
|
||||||
|
// 插入測試帳戶
|
||||||
|
account := &entity.Account{
|
||||||
|
LoginID: "testuser1",
|
||||||
|
Token: "testtoken1",
|
||||||
|
Platform: 1,
|
||||||
|
}
|
||||||
|
err = repo.Insert(context.Background(), account)
|
||||||
|
assert.NoError(t, err, "插入應成功")
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
id string
|
||||||
|
expectError bool
|
||||||
|
expectedErr error
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "Valid delete",
|
||||||
|
id: account.ID.Hex(),
|
||||||
|
expectError: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Invalid ID format",
|
||||||
|
id: "invalid-id",
|
||||||
|
expectError: true,
|
||||||
|
expectedErr: ErrInvalidObjectID,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
deletedCount, err := repo.Delete(context.Background(), tt.id)
|
||||||
|
if tt.expectError {
|
||||||
|
assert.Error(t, err)
|
||||||
|
assert.Equal(t, tt.expectedErr, err)
|
||||||
|
} else {
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, int64(1), deletedCount, "刪除應成功")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAccountModel_FindOneByAccount(t *testing.T) {
|
||||||
|
repo, tearDown, err := SetupTestAccountRepository("testDB")
|
||||||
|
assert.NoError(t, err)
|
||||||
|
defer tearDown()
|
||||||
|
|
||||||
|
// 插入測試帳戶
|
||||||
|
account := &entity.Account{
|
||||||
|
LoginID: "testuser1",
|
||||||
|
Token: "testtoken1",
|
||||||
|
Platform: 1,
|
||||||
|
}
|
||||||
|
err = repo.Insert(context.Background(), account)
|
||||||
|
assert.NoError(t, err, "插入應成功")
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
loginID string
|
||||||
|
expectedErr error
|
||||||
|
expectFound bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "Valid Account Found",
|
||||||
|
loginID: "testuser1",
|
||||||
|
expectedErr: nil,
|
||||||
|
expectFound: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Account Not Found",
|
||||||
|
loginID: "nonexistentuser",
|
||||||
|
expectedErr: ErrNotFound,
|
||||||
|
expectFound: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
result, err := repo.FindOneByAccount(context.Background(), tt.loginID)
|
||||||
|
if tt.expectFound {
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, account.LoginID, result.LoginID)
|
||||||
|
assert.Equal(t, account.Token, result.Token)
|
||||||
|
} else {
|
||||||
|
assert.Error(t, err)
|
||||||
|
assert.True(t, errors.Is(err, tt.expectedErr))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAccountModel_UpdateTokenByLoginID(t *testing.T) {
|
||||||
|
repo, tearDown, err := SetupTestAccountRepository("testDB")
|
||||||
|
assert.NoError(t, err)
|
||||||
|
defer tearDown()
|
||||||
|
|
||||||
|
// 插入測試帳戶
|
||||||
|
account := &entity.Account{
|
||||||
|
LoginID: "testuser2",
|
||||||
|
Token: "initialtoken",
|
||||||
|
Platform: 1,
|
||||||
|
}
|
||||||
|
err = repo.Insert(context.Background(), account)
|
||||||
|
assert.NoError(t, err, "插入應成功")
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
loginID string
|
||||||
|
newToken string
|
||||||
|
expectedErr error
|
||||||
|
expectFound bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "Valid Update Token",
|
||||||
|
loginID: "testuser2",
|
||||||
|
newToken: "newtoken123",
|
||||||
|
expectedErr: nil,
|
||||||
|
expectFound: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Account Not Found for Update",
|
||||||
|
loginID: "nonexistentuser",
|
||||||
|
newToken: "newtoken456",
|
||||||
|
expectedErr: ErrNotFound,
|
||||||
|
expectFound: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
err := repo.UpdateTokenByLoginID(context.Background(), tt.loginID, tt.newToken)
|
||||||
|
if tt.expectFound {
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
// 驗證更新後的 token 值
|
||||||
|
updatedAccount, findErr := repo.FindOneByAccount(context.Background(), tt.loginID)
|
||||||
|
assert.NoError(t, findErr)
|
||||||
|
assert.Equal(t, tt.newToken, updatedAccount.Token)
|
||||||
|
} else {
|
||||||
|
assert.Error(t, err)
|
||||||
|
assert.True(t, errors.Is(err, tt.expectedErr))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,125 @@
|
||||||
|
package repository
|
||||||
|
|
||||||
|
import (
|
||||||
|
"app-cloudep-member-server/pkg/domain"
|
||||||
|
"app-cloudep-member-server/pkg/domain/entity"
|
||||||
|
"app-cloudep-member-server/pkg/domain/repository"
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
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"
|
||||||
|
)
|
||||||
|
|
||||||
|
type AccountUIDRepositoryParam struct {
|
||||||
|
Conf *mgo.Conf
|
||||||
|
CacheConf cache.CacheConf
|
||||||
|
DbOpts []mon.Option
|
||||||
|
CacheOpts []cache.Option
|
||||||
|
}
|
||||||
|
|
||||||
|
type AccountUIDRepository struct {
|
||||||
|
DB mgo.DocumentDBWithCacheUseCase
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewAccountUIDRepository(param AccountUIDRepositoryParam) repository.AccountUIDRepository {
|
||||||
|
e := entity.AccountUID{}
|
||||||
|
documentDB, err := mgo.MustDocumentDBWithCache(
|
||||||
|
param.Conf,
|
||||||
|
e.CollectionName(),
|
||||||
|
param.CacheConf,
|
||||||
|
param.DbOpts,
|
||||||
|
param.CacheOpts,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &AccountUIDRepository{
|
||||||
|
DB: documentDB,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (repo *AccountUIDRepository) Insert(ctx context.Context, data *entity.AccountUID) error {
|
||||||
|
if data.ID.IsZero() {
|
||||||
|
now := time.Now().UTC().UnixNano()
|
||||||
|
data.ID = primitive.NewObjectID()
|
||||||
|
data.CreateAt = &now
|
||||||
|
data.UpdateAt = &now
|
||||||
|
}
|
||||||
|
rk := domain.GetAccountUIDRedisKey(data.ID.Hex())
|
||||||
|
_, err := repo.DB.InsertOne(ctx, rk, data)
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (repo *AccountUIDRepository) FindOne(ctx context.Context, id string) (*entity.AccountUID, error) {
|
||||||
|
oid, err := primitive.ObjectIDFromHex(id)
|
||||||
|
if err != nil {
|
||||||
|
return nil, ErrInvalidObjectID
|
||||||
|
}
|
||||||
|
|
||||||
|
var data entity.AccountUID
|
||||||
|
rk := domain.GetAccountUIDRedisKey(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 *AccountUIDRepository) Update(ctx context.Context, data *entity.AccountUID) (*mongo.UpdateResult, error) {
|
||||||
|
now := time.Now().UTC().UnixNano()
|
||||||
|
data.UpdateAt = &now
|
||||||
|
|
||||||
|
rk := domain.GetAccountUIDRedisKey(data.ID.Hex())
|
||||||
|
res, err := repo.DB.UpdateOne(ctx, rk, bson.M{"_id": data.ID}, bson.M{"$set": data})
|
||||||
|
|
||||||
|
return res, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (repo *AccountUIDRepository) Delete(ctx context.Context, id string) (int64, error) {
|
||||||
|
oid, err := primitive.ObjectIDFromHex(id)
|
||||||
|
if err != nil {
|
||||||
|
return 0, ErrInvalidObjectID
|
||||||
|
}
|
||||||
|
rk := domain.GetAccountUIDRedisKey(id)
|
||||||
|
|
||||||
|
return repo.DB.DeleteOne(ctx, rk, bson.M{"_id": oid})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (repo *AccountUIDRepository) FindUIDByLoginID(ctx context.Context, loginID string) (*entity.AccountUID, error) {
|
||||||
|
var data entity.AccountUID
|
||||||
|
|
||||||
|
err := repo.DB.GetClient().FindOne(ctx, &data, bson.M{"login_id": loginID})
|
||||||
|
switch {
|
||||||
|
case err == nil:
|
||||||
|
return &data, nil
|
||||||
|
case errors.Is(err, mon.ErrNotFound):
|
||||||
|
return nil, ErrNotFound
|
||||||
|
default:
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (repo *AccountUIDRepository) Index20241226001UP(ctx context.Context) (*mongo.Cursor, error) {
|
||||||
|
// 等價於 db.account_uid_binding.createIndex({"login_id": 1}, {unique: true})
|
||||||
|
repo.DB.PopulateIndex(ctx, "login_id", 1, true)
|
||||||
|
|
||||||
|
// 等價於 db.account_uid_binding.createIndex({"uid": 1})
|
||||||
|
repo.DB.PopulateIndex(ctx, "uid", 1, false)
|
||||||
|
|
||||||
|
// 等價於 db.account_uid_binding.createIndex({"create_at": 1})
|
||||||
|
repo.DB.PopulateIndex(ctx, "create_at", 1, false)
|
||||||
|
|
||||||
|
return repo.DB.GetClient().Indexes().List(ctx)
|
||||||
|
}
|
|
@ -0,0 +1,272 @@
|
||||||
|
package repository
|
||||||
|
|
||||||
|
import (
|
||||||
|
"app-cloudep-member-server/pkg/domain/entity"
|
||||||
|
"app-cloudep-member-server/pkg/domain/repository"
|
||||||
|
"context"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
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"
|
||||||
|
)
|
||||||
|
|
||||||
|
func SetupTestAccountUIDRepository(db string) (repository.AccountUIDRepository, func(), error) {
|
||||||
|
h, p, tearDown, err := startMongoContainer()
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
s, _ := miniredis.Run()
|
||||||
|
|
||||||
|
conf := &mgo.Conf{
|
||||||
|
Schema: Schema,
|
||||||
|
Host: h,
|
||||||
|
Port: 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 := AccountUIDRepositoryParam{
|
||||||
|
Conf: conf,
|
||||||
|
CacheConf: cacheConf,
|
||||||
|
CacheOpts: cacheOpts,
|
||||||
|
}
|
||||||
|
|
||||||
|
repo := NewAccountUIDRepository(param)
|
||||||
|
_, _ = repo.Index20241226001UP(context.Background())
|
||||||
|
|
||||||
|
return repo, tearDown, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDefaultAccountUidModel_Insert(t *testing.T) {
|
||||||
|
repo, tearDown, err := SetupTestAccountUIDRepository("testDB")
|
||||||
|
defer tearDown()
|
||||||
|
assert.NoError(t, err)
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
accountUid *entity.AccountUID
|
||||||
|
expectError bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "Valid AccountUid insert",
|
||||||
|
accountUid: &entity.AccountUID{
|
||||||
|
LoginID: "testlogin1",
|
||||||
|
UID: "testuid1",
|
||||||
|
Type: 1,
|
||||||
|
},
|
||||||
|
expectError: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Insert with missing UID",
|
||||||
|
accountUid: &entity.AccountUID{
|
||||||
|
LoginID: "testlogin2",
|
||||||
|
Type: 2,
|
||||||
|
},
|
||||||
|
expectError: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
// 執行插入測試帳戶 UID
|
||||||
|
err := repo.Insert(context.Background(), tt.accountUid)
|
||||||
|
if tt.expectError {
|
||||||
|
assert.Error(t, err, "插入操作應該失敗")
|
||||||
|
} else {
|
||||||
|
assert.NoError(t, err, "插入操作應該成功")
|
||||||
|
|
||||||
|
// 驗證 ObjectID 和時間戳是否生成
|
||||||
|
assert.NotZero(t, tt.accountUid.ID, "應生成 ID")
|
||||||
|
assert.NotNil(t, tt.accountUid.CreateAt, "CreateAt 應被設置")
|
||||||
|
assert.NotNil(t, tt.accountUid.UpdateAt, "UpdateAt 應被設置")
|
||||||
|
|
||||||
|
// 驗證插入的時間是否合理
|
||||||
|
now := time.Now().UTC().UnixNano()
|
||||||
|
assert.LessOrEqual(t, *tt.accountUid.CreateAt, now, "CreateAt 應在當前時間之前")
|
||||||
|
assert.LessOrEqual(t, *tt.accountUid.UpdateAt, now, "UpdateAt 應在當前時間之前")
|
||||||
|
|
||||||
|
// 驗證插入的資料是否正確
|
||||||
|
insertedAccountUid, err := repo.FindOne(context.Background(), tt.accountUid.ID.Hex())
|
||||||
|
assert.NoError(t, err, "應該可以找到插入的帳號 UID 資料")
|
||||||
|
assert.Equal(t, tt.accountUid.LoginID, insertedAccountUid.LoginID, "LoginID 應相同")
|
||||||
|
assert.Equal(t, tt.accountUid.UID, insertedAccountUid.UID, "UID 應相同")
|
||||||
|
assert.Equal(t, tt.accountUid.Type, insertedAccountUid.Type, "Type 應相同")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDefaultAccountUidModel_FindOne(t *testing.T) {
|
||||||
|
repo, tearDown, err := SetupTestAccountUIDRepository("testDB")
|
||||||
|
defer tearDown()
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
// 準備測試資料
|
||||||
|
accountUid := &entity.AccountUID{
|
||||||
|
LoginID: "testlogin",
|
||||||
|
UID: "testuid",
|
||||||
|
Type: 1,
|
||||||
|
}
|
||||||
|
err = repo.Insert(context.Background(), accountUid)
|
||||||
|
assert.NoError(t, err, "應成功插入測試資料")
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
id string
|
||||||
|
expectError bool
|
||||||
|
expectedUID string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "Valid FindOne",
|
||||||
|
id: accountUid.ID.Hex(),
|
||||||
|
expectError: false,
|
||||||
|
expectedUID: accountUid.UID,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Invalid ObjectID",
|
||||||
|
id: "invalid_id",
|
||||||
|
expectError: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Non-existent ObjectID",
|
||||||
|
id: primitive.NewObjectID().Hex(),
|
||||||
|
expectError: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
result, err := repo.FindOne(context.Background(), tt.id)
|
||||||
|
if tt.expectError {
|
||||||
|
assert.Error(t, err)
|
||||||
|
} else {
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, tt.expectedUID, result.UID, "找到的 UID 應符合預期")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDefaultAccountUidModel_Update(t *testing.T) {
|
||||||
|
repo, tearDown, err := SetupTestAccountUIDRepository("testDB")
|
||||||
|
defer tearDown()
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
// 準備測試資料
|
||||||
|
accountUid := &entity.AccountUID{
|
||||||
|
LoginID: "testlogin",
|
||||||
|
UID: "testuid",
|
||||||
|
Type: 1,
|
||||||
|
}
|
||||||
|
err = repo.Insert(context.Background(), accountUid)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
updatedUID := "updatedUID"
|
||||||
|
accountUid.UID = updatedUID
|
||||||
|
|
||||||
|
// 執行更新操作
|
||||||
|
_, err = repo.Update(context.Background(), accountUid)
|
||||||
|
assert.NoError(t, err, "應成功更新資料")
|
||||||
|
|
||||||
|
// 確認更新結果
|
||||||
|
updatedAccountUid, err := repo.FindOne(context.Background(), accountUid.ID.Hex())
|
||||||
|
assert.NoError(t, err, "應能找到更新後的資料")
|
||||||
|
assert.Equal(t, updatedUID, updatedAccountUid.UID, "更新後的 UID 應符合預期")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDefaultAccountUidModel_Delete(t *testing.T) {
|
||||||
|
repo, tearDown, err := SetupTestAccountUIDRepository("testDB")
|
||||||
|
defer tearDown()
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
// 準備測試資料
|
||||||
|
accountUid := &entity.AccountUID{
|
||||||
|
LoginID: "testlogin",
|
||||||
|
UID: "testuid",
|
||||||
|
Type: 1,
|
||||||
|
}
|
||||||
|
err = repo.Insert(context.Background(), accountUid)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
// 執行刪除操作
|
||||||
|
count, err := repo.Delete(context.Background(), accountUid.ID.Hex())
|
||||||
|
assert.NoError(t, err, "應成功刪除資料")
|
||||||
|
assert.Equal(t, int64(1), count, "刪除數量應為 1")
|
||||||
|
|
||||||
|
// 確認資料已被刪除
|
||||||
|
_, err = repo.FindOne(context.Background(), accountUid.ID.Hex())
|
||||||
|
assert.Error(t, err, "應無法找到已刪除的資料")
|
||||||
|
assert.Equal(t, ErrNotFound, err, "應返回 ErrNotFound 錯誤")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCustomAccountUidModel_FindUIDByLoginID(t *testing.T) {
|
||||||
|
repo, tearDown, err := SetupTestAccountUIDRepository("testDB")
|
||||||
|
defer tearDown()
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
// 準備測試資料
|
||||||
|
accountUid := &entity.AccountUID{
|
||||||
|
LoginID: "testloginid",
|
||||||
|
UID: "testuid",
|
||||||
|
Type: 1,
|
||||||
|
}
|
||||||
|
err = repo.Insert(context.Background(), accountUid)
|
||||||
|
assert.NoError(t, err, "應成功插入測試資料")
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
loginID string
|
||||||
|
expectError bool
|
||||||
|
expectedUID string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "Valid FindUIDByLoginID",
|
||||||
|
loginID: "testloginid",
|
||||||
|
expectError: false,
|
||||||
|
expectedUID: "testuid",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Non-existent LoginID",
|
||||||
|
loginID: "nonexistent",
|
||||||
|
expectError: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
result, err := repo.FindUIDByLoginID(context.Background(), tt.loginID)
|
||||||
|
if tt.expectError {
|
||||||
|
assert.Error(t, err)
|
||||||
|
assert.Equal(t, ErrNotFound, err, "應返回 ErrNotFound 錯誤")
|
||||||
|
} else {
|
||||||
|
assert.NoError(t, err, "應成功找到符合的記錄")
|
||||||
|
assert.Equal(t, tt.expectedUID, result.UID, "找到的 UID 應符合預期")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,124 @@
|
||||||
|
package repository
|
||||||
|
|
||||||
|
import (
|
||||||
|
"app-cloudep-member-server/pkg/domain/entity"
|
||||||
|
"app-cloudep-member-server/pkg/domain/repository"
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
GIDLib "code.30cm.net/digimon/library-go/utils/invited_code"
|
||||||
|
|
||||||
|
mgo "code.30cm.net/digimon/library-go/mongo"
|
||||||
|
"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 AutoIDRepositoryParam struct {
|
||||||
|
Conf *mgo.Conf
|
||||||
|
DbOpts []mon.Option
|
||||||
|
}
|
||||||
|
|
||||||
|
type AutoIDRepository struct {
|
||||||
|
DB mgo.DocumentDBUseCase
|
||||||
|
UIDTrans GIDLib.ConvertUseCase
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewAutoIDRepository(param AutoIDRepositoryParam) repository.AutoIDRepository {
|
||||||
|
e := entity.AutoID{}
|
||||||
|
documentDB, err := mgo.NewDocumentDB(param.Conf, e.CollectionName(), param.DbOpts...)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &AutoIDRepository{
|
||||||
|
DB: documentDB,
|
||||||
|
UIDTrans: GIDLib.MustConverter(10, 8, GIDLib.ConvertTable),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (repo *AutoIDRepository) Insert(ctx context.Context, data *entity.AutoID) error {
|
||||||
|
if data.ID.IsZero() {
|
||||||
|
now := time.Now().UTC().UnixNano()
|
||||||
|
data.ID = primitive.NewObjectID()
|
||||||
|
data.CreateAt = &now
|
||||||
|
data.UpdateAt = &now
|
||||||
|
}
|
||||||
|
_, err := repo.DB.GetClient().InsertOne(ctx, data)
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (repo *AutoIDRepository) FindOne(ctx context.Context, id string) (*entity.AutoID, error) {
|
||||||
|
oid, err := primitive.ObjectIDFromHex(id)
|
||||||
|
if err != nil {
|
||||||
|
return nil, ErrInvalidObjectID
|
||||||
|
}
|
||||||
|
|
||||||
|
var data entity.AutoID
|
||||||
|
|
||||||
|
err = repo.DB.GetClient().FindOne(ctx, &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 *AutoIDRepository) Update(ctx context.Context, data *entity.AutoID) (*mongo.UpdateResult, error) {
|
||||||
|
now := time.Now().UTC().UnixNano()
|
||||||
|
data.UpdateAt = &now
|
||||||
|
|
||||||
|
res, err := repo.DB.GetClient().UpdateOne(ctx, bson.M{"_id": data.ID}, bson.M{"$set": data})
|
||||||
|
|
||||||
|
return res, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (repo *AutoIDRepository) Delete(ctx context.Context, id string) (int64, error) {
|
||||||
|
oid, err := primitive.ObjectIDFromHex(id)
|
||||||
|
if err != nil {
|
||||||
|
return 0, ErrInvalidObjectID
|
||||||
|
}
|
||||||
|
|
||||||
|
res, err := repo.DB.GetClient().DeleteOne(ctx, bson.M{"_id": oid})
|
||||||
|
|
||||||
|
return res, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (repo *AutoIDRepository) Inc(ctx context.Context, data *entity.AutoID) error {
|
||||||
|
// 定義查詢的條件
|
||||||
|
filter := bson.M{"name": "auto_id"}
|
||||||
|
|
||||||
|
// 定義更新的操作,包括自增和更新時間
|
||||||
|
update := bson.M{
|
||||||
|
"$inc": bson.M{"counter": 1}, // 自增 counter
|
||||||
|
"$set": bson.M{"updateAt": time.Now().UTC().UnixNano()}, // 設置 updateAt 為當前時間
|
||||||
|
}
|
||||||
|
// 使用 FindOneAndUpdate 並將結果解碼到 data 中
|
||||||
|
err := repo.DB.GetClient().FindOneAndUpdate(ctx, &data, filter, update,
|
||||||
|
options.FindOneAndUpdate().SetUpsert(true),
|
||||||
|
options.FindOneAndUpdate().SetReturnDocument(options.After))
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (repo *AutoIDRepository) Index20241226001UP(ctx context.Context) (*mongo.Cursor, error) {
|
||||||
|
// db.count.createIndex({ "name": 1 }, { unique: true });
|
||||||
|
repo.DB.PopulateIndex(ctx, "name", 1, true)
|
||||||
|
|
||||||
|
return repo.DB.GetClient().Indexes().List(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (repo *AutoIDRepository) GetUIDFromNum(num int64) (string, error) {
|
||||||
|
return repo.UIDTrans.EncodeFromNum(num)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (repo *AutoIDRepository) GetNumFromUID(uid string) (int64, error) {
|
||||||
|
return repo.UIDTrans.DecodeFromCode(uid)
|
||||||
|
}
|
|
@ -0,0 +1,278 @@
|
||||||
|
package repository
|
||||||
|
|
||||||
|
import (
|
||||||
|
"app-cloudep-member-server/pkg/domain/entity"
|
||||||
|
"app-cloudep-member-server/pkg/domain/repository"
|
||||||
|
"context"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
mgo "code.30cm.net/digimon/library-go/mongo"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func SetupTestAutoIDRepository(db string) (repository.AutoIDRepository, func(), error) {
|
||||||
|
h, p, tearDown, err := startMongoContainer()
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
conf := &mgo.Conf{
|
||||||
|
Schema: Schema,
|
||||||
|
Host: h,
|
||||||
|
Port: p,
|
||||||
|
Database: db,
|
||||||
|
MaxStaleness: 300,
|
||||||
|
MaxPoolSize: 100,
|
||||||
|
MinPoolSize: 100,
|
||||||
|
MaxConnIdleTime: 300,
|
||||||
|
Compressors: []string{},
|
||||||
|
EnableStandardReadWriteSplitMode: false,
|
||||||
|
ConnectTimeoutMs: 3000,
|
||||||
|
}
|
||||||
|
|
||||||
|
param := AutoIDRepositoryParam{
|
||||||
|
Conf: conf,
|
||||||
|
}
|
||||||
|
|
||||||
|
repo := NewAutoIDRepository(param)
|
||||||
|
_, _ = repo.Index20241226001UP(context.Background())
|
||||||
|
|
||||||
|
return repo, tearDown, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDefaultAutoIDModel_Insert(t *testing.T) {
|
||||||
|
repo, tearDown, err := SetupTestAutoIDRepository("testDB")
|
||||||
|
defer tearDown()
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
data *entity.AutoID
|
||||||
|
expectError bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "Valid AutoID insert",
|
||||||
|
data: &entity.AutoID{
|
||||||
|
Name: "testCounter",
|
||||||
|
Counter: 100,
|
||||||
|
},
|
||||||
|
expectError: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
err := repo.Insert(context.Background(), tt.data)
|
||||||
|
if tt.expectError {
|
||||||
|
assert.Error(t, err)
|
||||||
|
} else {
|
||||||
|
assert.NoError(t, err, "插入操作應該成功")
|
||||||
|
|
||||||
|
// 檢查生成的 ID 和時間戳
|
||||||
|
assert.NotZero(t, tt.data.ID, "ID 應該被生成")
|
||||||
|
assert.NotNil(t, tt.data.CreateAt, "CreateAt 應該被設置")
|
||||||
|
assert.NotNil(t, tt.data.UpdateAt, "UpdateAt 應該被設置")
|
||||||
|
|
||||||
|
// 確認插入的資料是否正確
|
||||||
|
now := time.Now().UTC().UnixNano()
|
||||||
|
assert.LessOrEqual(t, *tt.data.CreateAt, now, "CreateAt 應在當前時間之前")
|
||||||
|
assert.LessOrEqual(t, *tt.data.UpdateAt, now, "UpdateAt 應在當前時間之前")
|
||||||
|
|
||||||
|
insertedAutoID, err := repo.FindOne(context.Background(), tt.data.ID.Hex())
|
||||||
|
assert.NoError(t, err, "應該可以找到插入的資料")
|
||||||
|
assert.Equal(t, tt.data.Name, insertedAutoID.Name, "Name 應相同")
|
||||||
|
assert.Equal(t, tt.data.Counter, insertedAutoID.Counter, "Counter 應相同")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDefaultAutoIDModel_FindOne(t *testing.T) {
|
||||||
|
repo, tearDown, err := SetupTestAutoIDRepository("testDB")
|
||||||
|
defer tearDown()
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
inserted := &entity.AutoID{
|
||||||
|
Name: "findTestCounter",
|
||||||
|
Counter: 200,
|
||||||
|
}
|
||||||
|
err = repo.Insert(context.Background(), inserted)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
id string
|
||||||
|
expectError bool
|
||||||
|
expectedID string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "Valid FindOne by ID",
|
||||||
|
id: inserted.ID.Hex(),
|
||||||
|
expectError: false,
|
||||||
|
expectedID: inserted.ID.Hex(),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Invalid ObjectID",
|
||||||
|
id: "invalidObjectID",
|
||||||
|
expectError: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
result, err := repo.FindOne(context.Background(), tt.id)
|
||||||
|
if tt.expectError {
|
||||||
|
assert.Error(t, err)
|
||||||
|
} else {
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, tt.expectedID, result.ID.Hex(), "ID 應相同")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDefaultAutoIDModel_Update(t *testing.T) {
|
||||||
|
repo, tearDown, err := SetupTestAutoIDRepository("testDB")
|
||||||
|
defer tearDown()
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
inserted := &entity.AutoID{
|
||||||
|
Name: "updateTestCounter",
|
||||||
|
Counter: 300,
|
||||||
|
}
|
||||||
|
err = repo.Insert(context.Background(), inserted)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
updatedData := *inserted // 創建副本進行更新
|
||||||
|
updatedData.Counter = 400
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
data *entity.AutoID
|
||||||
|
expectError bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "Valid Update",
|
||||||
|
data: &updatedData,
|
||||||
|
expectError: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
_, err := repo.Update(context.Background(), tt.data)
|
||||||
|
if tt.expectError {
|
||||||
|
assert.Error(t, err)
|
||||||
|
} else {
|
||||||
|
assert.NoError(t, err)
|
||||||
|
// 驗證更新後的 Counter
|
||||||
|
updatedAutoID, err := repo.FindOne(context.Background(), tt.data.ID.Hex())
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, tt.data.Counter, updatedAutoID.Counter, "Counter 應相同")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDefaultAutoIDModel_Delete(t *testing.T) {
|
||||||
|
repo, tearDown, err := SetupTestAutoIDRepository("testDB")
|
||||||
|
defer tearDown()
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
inserted := &entity.AutoID{
|
||||||
|
Name: "deleteTestCounter",
|
||||||
|
Counter: 500,
|
||||||
|
}
|
||||||
|
err = repo.Insert(context.Background(), inserted)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
id string
|
||||||
|
expectError bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "Valid Delete by ID",
|
||||||
|
id: inserted.ID.Hex(),
|
||||||
|
expectError: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Invalid ObjectID",
|
||||||
|
id: "invalidObjectID",
|
||||||
|
expectError: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
count, err := repo.Delete(context.Background(), tt.id)
|
||||||
|
if tt.expectError {
|
||||||
|
assert.Error(t, err)
|
||||||
|
} else {
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, int64(1), count, "刪除應影響 1 條記錄")
|
||||||
|
|
||||||
|
// 確認已刪除
|
||||||
|
_, err = repo.FindOne(context.Background(), tt.id)
|
||||||
|
assert.ErrorIs(t, err, ErrNotFound, "記錄應該不存在")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCustomAutoIDModel_Inc(t *testing.T) {
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
initialData *entity.AutoID
|
||||||
|
expectError bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "Increment non-existing counter (upsert)",
|
||||||
|
initialData: nil, // 不提供初始數據,預期會自動插入新數據
|
||||||
|
expectError: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Increment existing counter",
|
||||||
|
initialData: &entity.AutoID{
|
||||||
|
Name: "auto_id",
|
||||||
|
Counter: 5,
|
||||||
|
},
|
||||||
|
expectError: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
repo, tearDown, err := SetupTestAutoIDRepository("testDB")
|
||||||
|
defer tearDown()
|
||||||
|
assert.NoError(t, err)
|
||||||
|
// 插入初始數據(如果有)
|
||||||
|
if tt.initialData != nil {
|
||||||
|
err := repo.Insert(context.Background(), tt.initialData)
|
||||||
|
assert.NoError(t, err, "初始插入操作應該成功")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 創建一個 AutoID 結構來保存增量操作後的結果
|
||||||
|
var result entity.AutoID
|
||||||
|
|
||||||
|
// 執行 Inc 方法
|
||||||
|
err = repo.Inc(context.Background(), &result)
|
||||||
|
if tt.expectError {
|
||||||
|
assert.Error(t, err, "應該返回錯誤")
|
||||||
|
} else {
|
||||||
|
assert.NoError(t, err, "Inc 操作應該成功")
|
||||||
|
|
||||||
|
// 驗證結果中的 Counter 值是否正確增量
|
||||||
|
expectedCounter := uint64(1)
|
||||||
|
if tt.initialData != nil {
|
||||||
|
expectedCounter = tt.initialData.Counter + 1
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.Equal(t, expectedCounter, result.Counter, "Counter 值應該自增")
|
||||||
|
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
|
@ -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,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,368 @@
|
||||||
|
package repository
|
||||||
|
|
||||||
|
import (
|
||||||
|
"app-cloudep-member-server/pkg/domain"
|
||||||
|
"app-cloudep-member-server/pkg/domain/entity"
|
||||||
|
"app-cloudep-member-server/pkg/domain/repository"
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
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 UserRepositoryParam struct {
|
||||||
|
Conf *mgo.Conf
|
||||||
|
CacheConf cache.CacheConf
|
||||||
|
DbOpts []mon.Option
|
||||||
|
CacheOpts []cache.Option
|
||||||
|
}
|
||||||
|
|
||||||
|
type UserRepository struct {
|
||||||
|
DB mgo.DocumentDBWithCacheUseCase
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewUserRepository(param UserRepositoryParam) repository.UserRepository {
|
||||||
|
e := entity.User{}
|
||||||
|
documentDB, err := mgo.MustDocumentDBWithCache(
|
||||||
|
param.Conf,
|
||||||
|
e.CollectionName(),
|
||||||
|
param.CacheConf,
|
||||||
|
param.DbOpts,
|
||||||
|
param.CacheOpts,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &UserRepository{
|
||||||
|
DB: documentDB,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (repo *UserRepository) Insert(ctx context.Context, data *entity.User) error {
|
||||||
|
if data.ID.IsZero() {
|
||||||
|
now := time.Now().UTC().UnixNano()
|
||||||
|
data.ID = primitive.NewObjectID()
|
||||||
|
data.CreateAt = &now
|
||||||
|
data.UpdateAt = &now
|
||||||
|
}
|
||||||
|
|
||||||
|
rk := domain.GetUserRedisKey(data.ID.Hex())
|
||||||
|
_, err := repo.DB.InsertOne(ctx, rk, data)
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (repo *UserRepository) FindOne(ctx context.Context, id string) (*entity.User, error) {
|
||||||
|
oid, err := primitive.ObjectIDFromHex(id)
|
||||||
|
if err != nil {
|
||||||
|
return nil, ErrInvalidObjectID
|
||||||
|
}
|
||||||
|
|
||||||
|
var data entity.User
|
||||||
|
rk := domain.GetUserRedisKey(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 *UserRepository) Update(ctx context.Context, data *entity.User) (*mongo.UpdateResult, error) {
|
||||||
|
now := time.Now().UTC().UnixNano()
|
||||||
|
data.UpdateAt = &now
|
||||||
|
|
||||||
|
rk := domain.GetUserRedisKey(data.ID.Hex())
|
||||||
|
res, err := repo.DB.UpdateOne(ctx, rk, bson.M{"_id": data.ID}, bson.M{"$set": data})
|
||||||
|
|
||||||
|
return res, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (repo *UserRepository) Delete(ctx context.Context, id string) (int64, error) {
|
||||||
|
oid, err := primitive.ObjectIDFromHex(id)
|
||||||
|
if err != nil {
|
||||||
|
return 0, ErrInvalidObjectID
|
||||||
|
}
|
||||||
|
|
||||||
|
rk := domain.GetUserRedisKey(id)
|
||||||
|
res, err := repo.DB.DeleteOne(ctx, rk, bson.M{"_id": oid})
|
||||||
|
|
||||||
|
return res, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (repo *UserRepository) UpdateUserDetailsByUID(ctx context.Context, data *repository.UpdateUserInfoRequest) error {
|
||||||
|
updateFields := bson.M{}
|
||||||
|
|
||||||
|
if data.AlarmCategory != nil {
|
||||||
|
updateFields["alarm_category"] = *data.AlarmCategory
|
||||||
|
}
|
||||||
|
if data.UserStatus != nil {
|
||||||
|
updateFields["user_status"] = *data.UserStatus
|
||||||
|
}
|
||||||
|
if data.PreferredLanguage != nil {
|
||||||
|
updateFields["preferred_language"] = *data.PreferredLanguage
|
||||||
|
}
|
||||||
|
if data.Currency != nil {
|
||||||
|
updateFields["currency"] = *data.Currency
|
||||||
|
}
|
||||||
|
if data.Nickname != nil {
|
||||||
|
updateFields["nickname"] = *data.Nickname
|
||||||
|
}
|
||||||
|
if data.AvatarURL != nil {
|
||||||
|
updateFields["avatar_url"] = *data.AvatarURL
|
||||||
|
}
|
||||||
|
if data.FullName != nil {
|
||||||
|
updateFields["full_name"] = *data.FullName
|
||||||
|
}
|
||||||
|
if data.GenderCode != nil {
|
||||||
|
updateFields["gender_code"] = *data.GenderCode
|
||||||
|
}
|
||||||
|
if data.Birthdate != nil {
|
||||||
|
updateFields["birthdate"] = *data.Birthdate
|
||||||
|
}
|
||||||
|
if data.Address != nil {
|
||||||
|
updateFields["address"] = *data.Address
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(updateFields) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
updateFields["update_at"] = time.Now().UTC().UnixNano()
|
||||||
|
|
||||||
|
filter := bson.M{"uid": data.UID}
|
||||||
|
update := bson.M{"$set": updateFields}
|
||||||
|
|
||||||
|
// 不常寫,再找一次可接受
|
||||||
|
id := repo.UIDToID(ctx, data.UID)
|
||||||
|
if id == "" {
|
||||||
|
return errors.New("invalid uid")
|
||||||
|
}
|
||||||
|
rk := domain.GetUserRedisKey(id)
|
||||||
|
result, err := repo.DB.UpdateOne(ctx, rk, filter, update, options.Update().SetUpsert(false))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if result.MatchedCount == 0 {
|
||||||
|
return ErrNotFound // 自定義的錯誤表示未找到記錄
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (repo *UserRepository) UpdateStatus(ctx context.Context, uid string, status int32) error {
|
||||||
|
filter := bson.M{"uid": uid}
|
||||||
|
|
||||||
|
// 構建更新內容,僅更新 status 字段並記錄 update_at 時間
|
||||||
|
update := bson.M{
|
||||||
|
"$set": bson.M{
|
||||||
|
"user_status": status,
|
||||||
|
"update_at": time.Now().UTC().UnixNano(),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// 不常寫,再找一次可接受
|
||||||
|
id := repo.UIDToID(ctx, uid)
|
||||||
|
if id == "" {
|
||||||
|
return errors.New("invalid uid")
|
||||||
|
}
|
||||||
|
rk := domain.GetUserRedisKey(id)
|
||||||
|
|
||||||
|
// 執行更新操作
|
||||||
|
result, err := repo.DB.UpdateOne(ctx, rk, filter, update, options.Update().SetUpsert(false))
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to update status for uid %s: %w", uid, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 檢查更新結果,若沒有匹配的文檔,則返回錯誤
|
||||||
|
if result.MatchedCount == 0 {
|
||||||
|
return ErrNotFound // 自定義的錯誤表示未找到記錄
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (repo *UserRepository) FindOneByUID(ctx context.Context, uid string) (*entity.User, error) {
|
||||||
|
// 構建查找條件
|
||||||
|
filter := bson.M{"uid": uid}
|
||||||
|
var data entity.User
|
||||||
|
|
||||||
|
// 不常寫,再找一次可接受
|
||||||
|
id := repo.UIDToID(ctx, uid)
|
||||||
|
if id == "" {
|
||||||
|
return nil, errors.New("invalid uid")
|
||||||
|
}
|
||||||
|
rk := domain.GetUserRedisKey(id)
|
||||||
|
|
||||||
|
err := repo.DB.FindOne(ctx, rk, &data, filter)
|
||||||
|
switch {
|
||||||
|
case err == nil:
|
||||||
|
return &data, nil
|
||||||
|
case errors.Is(err, mon.ErrNotFound):
|
||||||
|
return nil, ErrNotFound
|
||||||
|
default:
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (repo *UserRepository) FindOneByNickName(ctx context.Context, nickName string) (*entity.User, error) {
|
||||||
|
// 構建查找條件
|
||||||
|
filter := bson.M{"nickname": nickName}
|
||||||
|
var data entity.User
|
||||||
|
|
||||||
|
err := repo.DB.GetClient().FindOne(ctx, &data, filter)
|
||||||
|
switch {
|
||||||
|
case err == nil:
|
||||||
|
return &data, nil
|
||||||
|
case errors.Is(err, mon.ErrNotFound):
|
||||||
|
return nil, ErrNotFound
|
||||||
|
default:
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (repo *UserRepository) ListMembers(ctx context.Context, params *repository.UserQueryParams) ([]*entity.User, int64, error) {
|
||||||
|
// 構建查詢條件
|
||||||
|
filter := bson.M{}
|
||||||
|
|
||||||
|
if params.AlarmCategory != nil {
|
||||||
|
filter["alarm_category"] = *params.AlarmCategory
|
||||||
|
}
|
||||||
|
if params.UserStatus != nil {
|
||||||
|
filter["user_status"] = *params.UserStatus
|
||||||
|
}
|
||||||
|
if params.CreateStartTime != nil || params.CreateEndTime != nil {
|
||||||
|
timeFilter := bson.M{}
|
||||||
|
if params.CreateStartTime != nil {
|
||||||
|
timeFilter["$gte"] = *params.CreateStartTime
|
||||||
|
}
|
||||||
|
if params.CreateEndTime != nil {
|
||||||
|
timeFilter["$lte"] = *params.CreateEndTime
|
||||||
|
}
|
||||||
|
filter["create_at"] = timeFilter
|
||||||
|
}
|
||||||
|
|
||||||
|
// 計算符合條件的總數
|
||||||
|
count, err := repo.DB.GetClient().CountDocuments(ctx, filter)
|
||||||
|
if err != nil {
|
||||||
|
return nil, 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// 構建查詢選項(分頁)
|
||||||
|
opts := options.Find().
|
||||||
|
SetSkip(params.PageSize * (params.PageIndex - 1)).
|
||||||
|
SetLimit(params.PageSize)
|
||||||
|
|
||||||
|
// 執行查詢
|
||||||
|
var users = make([]*entity.User, 0, params.PageSize)
|
||||||
|
err = repo.DB.GetClient().Find(ctx, &users, filter, opts)
|
||||||
|
if err != nil {
|
||||||
|
return nil, 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return users, count, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (repo *UserRepository) UpdateEmailVerifyStatus(ctx context.Context, uid, email string) error {
|
||||||
|
// 構建查找條件
|
||||||
|
filter := bson.M{"uid": uid}
|
||||||
|
|
||||||
|
// 構建更新內容,僅更新 status 字段並記錄 update_at 時間
|
||||||
|
update := bson.M{
|
||||||
|
"$set": bson.M{
|
||||||
|
"email": email,
|
||||||
|
"update_at": time.Now().UTC().UnixNano(),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// 不常寫,再找一次可接受
|
||||||
|
id := repo.UIDToID(ctx, uid)
|
||||||
|
if id == "" {
|
||||||
|
return errors.New("invalid uid")
|
||||||
|
}
|
||||||
|
rk := domain.GetUserRedisKey(id)
|
||||||
|
|
||||||
|
// 執行更新操作
|
||||||
|
result, err := repo.DB.UpdateOne(ctx, rk, filter, update, options.Update().SetUpsert(false))
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to update status for uid %s: %w", uid, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 檢查更新結果,若沒有匹配的文檔,則返回錯誤
|
||||||
|
if result.MatchedCount == 0 {
|
||||||
|
return ErrNotFound // 自定義的錯誤表示未找到記錄
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (repo *UserRepository) UpdatePhoneVerifyStatus(ctx context.Context, uid, phone string) error {
|
||||||
|
// 構建查找條件
|
||||||
|
filter := bson.M{"uid": uid}
|
||||||
|
|
||||||
|
// 構建更新內容,僅更新 status 字段並記錄 update_at 時間
|
||||||
|
update := bson.M{
|
||||||
|
"$set": bson.M{
|
||||||
|
"phone_number": phone,
|
||||||
|
"update_at": time.Now().UTC().UnixNano(),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// 不常寫,再找一次可接受
|
||||||
|
id := repo.UIDToID(ctx, uid)
|
||||||
|
if id == "" {
|
||||||
|
return errors.New("invalid uid")
|
||||||
|
}
|
||||||
|
rk := domain.GetUserRedisKey(id)
|
||||||
|
|
||||||
|
// 執行更新操作
|
||||||
|
result, err := repo.DB.UpdateOne(ctx, rk, filter, update, options.Update().SetUpsert(false))
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to update status for uid %s: %w", uid, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 檢查更新結果,若沒有匹配的文檔,則返回錯誤
|
||||||
|
if result.MatchedCount == 0 {
|
||||||
|
return ErrNotFound // 自定義的錯誤表示未找到記錄
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (repo *UserRepository) UIDToID(ctx context.Context, uid string) string {
|
||||||
|
filter := bson.M{"uid": uid}
|
||||||
|
var user entity.User
|
||||||
|
opts := options.FindOne().SetProjection(bson.M{
|
||||||
|
"_id": 1,
|
||||||
|
})
|
||||||
|
err := repo.DB.GetClient().FindOne(ctx, &user, filter, opts)
|
||||||
|
if err != nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
return user.ID.Hex()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (repo *UserRepository) Index20241226001UP(ctx context.Context) (*mongo.Cursor, error) {
|
||||||
|
// db.user_info.createIndex({"uid": 1},{unique: true})
|
||||||
|
repo.DB.PopulateIndex(ctx, "uid", 1, true)
|
||||||
|
// db.user_info.createIndex({"create_at": 1})
|
||||||
|
repo.DB.PopulateIndex(ctx, "create_at", 1, false)
|
||||||
|
|
||||||
|
return repo.DB.GetClient().Indexes().List(ctx)
|
||||||
|
}
|
|
@ -0,0 +1,386 @@
|
||||||
|
package repository
|
||||||
|
|
||||||
|
import (
|
||||||
|
"app-cloudep-member-server/pkg/domain/entity"
|
||||||
|
"app-cloudep-member-server/pkg/domain/member"
|
||||||
|
"app-cloudep-member-server/pkg/domain/repository"
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
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"
|
||||||
|
"google.golang.org/protobuf/proto"
|
||||||
|
)
|
||||||
|
|
||||||
|
func SetupTestUserRepository(db string) (repository.UserRepository, func(), error) {
|
||||||
|
h, p, tearDown, err := startMongoContainer()
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
s, _ := miniredis.Run()
|
||||||
|
|
||||||
|
conf := &mgo.Conf{
|
||||||
|
Schema: Schema,
|
||||||
|
Host: h,
|
||||||
|
Port: 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 := UserRepositoryParam{
|
||||||
|
Conf: conf,
|
||||||
|
CacheConf: cacheConf,
|
||||||
|
CacheOpts: cacheOpts,
|
||||||
|
}
|
||||||
|
|
||||||
|
repo := NewUserRepository(param)
|
||||||
|
_, _ = repo.Index20241226001UP(context.Background())
|
||||||
|
|
||||||
|
return repo, tearDown, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCustomUserModel_UpdateEmailVerifyStatus(t *testing.T) {
|
||||||
|
repo, tearDown, err := SetupTestUserRepository("testDB")
|
||||||
|
defer tearDown()
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
initialUser := &entity.User{
|
||||||
|
UID: "test_uid_email",
|
||||||
|
Email: proto.String("old_email@example.com"),
|
||||||
|
}
|
||||||
|
err = repo.Insert(context.Background(), initialUser)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
err = repo.UpdateEmailVerifyStatus(context.Background(), "test_uid_email", "new_email@example.com")
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
updatedUser, err := repo.FindOneByUID(context.Background(), "test_uid_email")
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, "new_email@example.com", *updatedUser.Email)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCustomUserModel_UpdatePhoneVerifyStatus(t *testing.T) {
|
||||||
|
repo, tearDown, err := SetupTestUserRepository("testDB")
|
||||||
|
defer tearDown()
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
initialUser := &entity.User{
|
||||||
|
UID: "test_uid_phone",
|
||||||
|
PhoneNumber: proto.String("123456789"),
|
||||||
|
}
|
||||||
|
err = repo.Insert(context.Background(), initialUser)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
err = repo.UpdatePhoneVerifyStatus(context.Background(), "test_uid_phone", "987654321")
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
updatedUser, err := repo.FindOneByUID(context.Background(), "test_uid_phone")
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, "987654321", *updatedUser.PhoneNumber)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCustomUserModel_UpdateUserDetailsByUID(t *testing.T) {
|
||||||
|
repo, tearDown, err := SetupTestUserRepository("testDB")
|
||||||
|
defer tearDown()
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
initialData *entity.User
|
||||||
|
updateData *repository.UpdateUserInfoRequest
|
||||||
|
expectError bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "Update user details by UID",
|
||||||
|
initialData: &entity.User{
|
||||||
|
UID: "test_uid_1",
|
||||||
|
Nickname: proto.String("old_nickname"),
|
||||||
|
FullName: proto.String("Old Full Name"),
|
||||||
|
},
|
||||||
|
updateData: &repository.UpdateUserInfoRequest{
|
||||||
|
UID: "test_uid_1",
|
||||||
|
Nickname: proto.String("new_nickname"),
|
||||||
|
FullName: proto.String("New Full Name"),
|
||||||
|
},
|
||||||
|
expectError: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "User not found",
|
||||||
|
initialData: nil,
|
||||||
|
updateData: &repository.UpdateUserInfoRequest{
|
||||||
|
UID: "non_existent_uid",
|
||||||
|
Nickname: proto.String("nickname"),
|
||||||
|
},
|
||||||
|
expectError: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
if tt.initialData != nil {
|
||||||
|
err := repo.Insert(context.Background(), tt.initialData)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err := repo.UpdateUserDetailsByUID(context.Background(), tt.updateData)
|
||||||
|
if tt.expectError {
|
||||||
|
assert.Error(t, err)
|
||||||
|
} else {
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
updatedUser, err := repo.FindOneByUID(context.Background(), tt.updateData.UID)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, *tt.updateData.Nickname, *updatedUser.Nickname)
|
||||||
|
assert.Equal(t, *tt.updateData.FullName, *updatedUser.FullName)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCustomUserModel_FindOneByUID(t *testing.T) {
|
||||||
|
repo, tearDown, err := SetupTestUserRepository("testDB")
|
||||||
|
defer tearDown()
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
initialUser := &entity.User{
|
||||||
|
UID: "test_uid",
|
||||||
|
}
|
||||||
|
|
||||||
|
err = repo.Insert(context.Background(), initialUser)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
user, err := repo.FindOneByUID(context.Background(), "test_uid")
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, "test_uid", user.UID)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCustomUserModel_ListMembers(t *testing.T) {
|
||||||
|
repo, tearDown, err := SetupTestUserRepository("testDB")
|
||||||
|
defer tearDown()
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
users := []*entity.User{
|
||||||
|
{UID: "uid1", UserStatus: member.AccountStatusActive},
|
||||||
|
{UID: "uid2", UserStatus: member.AccountStatusActive},
|
||||||
|
{UID: "uid3", UserStatus: member.AccountStatusUnverified},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, user := range users {
|
||||||
|
err := repo.Insert(context.Background(), user)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
activeStatus := member.AccountStatusActive
|
||||||
|
params := &repository.UserQueryParams{
|
||||||
|
UserStatus: &activeStatus,
|
||||||
|
PageSize: 2,
|
||||||
|
PageIndex: 1,
|
||||||
|
}
|
||||||
|
|
||||||
|
result, count, err := repo.ListMembers(context.Background(), params)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, int64(2), count)
|
||||||
|
assert.Len(t, result, 2)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCustomUserModel_FindOne(t *testing.T) {
|
||||||
|
repo, tearDown, err := SetupTestUserRepository("testDB")
|
||||||
|
defer tearDown()
|
||||||
|
assert.NoError(t, err)
|
||||||
|
// 準備測試資料
|
||||||
|
initialUser := &entity.User{
|
||||||
|
UID: "test_uid",
|
||||||
|
}
|
||||||
|
|
||||||
|
err = repo.Insert(context.Background(), initialUser)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
id string
|
||||||
|
expectError bool
|
||||||
|
expectedUID string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "Valid FindOne",
|
||||||
|
id: initialUser.ID.Hex(),
|
||||||
|
expectError: false,
|
||||||
|
expectedUID: initialUser.UID,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Invalid ObjectID",
|
||||||
|
id: "invalid_id",
|
||||||
|
expectError: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Non-existent ObjectID",
|
||||||
|
id: primitive.NewObjectID().Hex(),
|
||||||
|
expectError: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
result, err := repo.FindOne(context.Background(), tt.id)
|
||||||
|
if tt.expectError {
|
||||||
|
assert.Error(t, err)
|
||||||
|
} else {
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, tt.expectedUID, result.UID, "找到的 UID 應符合預期")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCustomUserModel_Update(t *testing.T) {
|
||||||
|
repo, tearDown, err := SetupTestUserRepository("testDB")
|
||||||
|
defer tearDown()
|
||||||
|
assert.NoError(t, err)
|
||||||
|
// 準備測試資料
|
||||||
|
initialUser := &entity.User{
|
||||||
|
UID: "test_uid",
|
||||||
|
}
|
||||||
|
|
||||||
|
err = repo.Insert(context.Background(), initialUser)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
updatedUID := "test_uid"
|
||||||
|
initialUser.UID = updatedUID
|
||||||
|
|
||||||
|
// 執行更新操作
|
||||||
|
_, err = repo.Update(context.Background(), initialUser)
|
||||||
|
assert.NoError(t, err, "應成功更新資料")
|
||||||
|
|
||||||
|
// 確認更新結果
|
||||||
|
updatedAccountUid, err := repo.FindOne(context.Background(), initialUser.ID.Hex())
|
||||||
|
assert.NoError(t, err, "應能找到更新後的資料")
|
||||||
|
assert.Equal(t, updatedUID, updatedAccountUid.UID, "更新後的 UID 應符合預期")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCustomUserModel_Delete(t *testing.T) {
|
||||||
|
repo, tearDown, err := SetupTestUserRepository("testDB")
|
||||||
|
defer tearDown()
|
||||||
|
assert.NoError(t, err)
|
||||||
|
// 準備測試資料
|
||||||
|
initialUser := &entity.User{
|
||||||
|
UID: "test_uid",
|
||||||
|
}
|
||||||
|
|
||||||
|
err = repo.Insert(context.Background(), initialUser)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
// 執行刪除操作
|
||||||
|
count, err := repo.Delete(context.Background(), initialUser.ID.Hex())
|
||||||
|
assert.NoError(t, err, "應成功刪除資料")
|
||||||
|
assert.Equal(t, int64(1), count, "刪除數量應為 1")
|
||||||
|
|
||||||
|
// 確認資料已被刪除
|
||||||
|
_, err = repo.FindOne(context.Background(), initialUser.ID.Hex())
|
||||||
|
assert.Error(t, err, "應無法找到已刪除的資料")
|
||||||
|
assert.Equal(t, ErrNotFound, err, "應返回 ErrNotFound 錯誤")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUserRepository_UpdateStatus(t *testing.T) {
|
||||||
|
repo, tearDown, err := SetupTestUserRepository("testDB")
|
||||||
|
defer tearDown()
|
||||||
|
assert.NoError(t, err)
|
||||||
|
// 插入測試數據
|
||||||
|
e := &entity.User{
|
||||||
|
UID: "test_uid",
|
||||||
|
UserStatus: member.AccountStatusActive,
|
||||||
|
}
|
||||||
|
err = repo.Insert(context.TODO(), e)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
// 測試用例
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
uid string
|
||||||
|
status member.Status
|
||||||
|
expectError bool
|
||||||
|
expectedErr error
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "Valid UID - Successful update",
|
||||||
|
uid: e.UID,
|
||||||
|
status: member.AccountStatusActive,
|
||||||
|
expectError: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Invalid UID - No mapping to ID",
|
||||||
|
uid: "invalid_uid",
|
||||||
|
status: 3,
|
||||||
|
expectError: true,
|
||||||
|
expectedErr: errors.New("invalid uid"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Non-existent UID",
|
||||||
|
uid: "non_existent_uid",
|
||||||
|
status: 4,
|
||||||
|
expectError: true,
|
||||||
|
expectedErr: errors.New("invalid uid"),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
err := repo.UpdateStatus(context.TODO(), tt.uid, tt.status.ToInt32())
|
||||||
|
if tt.expectError {
|
||||||
|
assert.Error(t, err)
|
||||||
|
assert.Equal(t, tt.expectedErr.Error(), err.Error())
|
||||||
|
} else {
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
// 驗證數據是否正確更新
|
||||||
|
user, err := repo.FindOneByUID(context.TODO(), tt.uid)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, tt.status, user.UserStatus, "UserStatus 應更新為預期值")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCustomUserModel_FindOneByNickName(t *testing.T) {
|
||||||
|
repo, tearDown, err := SetupTestUserRepository("testDB")
|
||||||
|
defer tearDown()
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
initialUser := &entity.User{
|
||||||
|
UID: "test_uid",
|
||||||
|
Nickname: proto.String("my_name"),
|
||||||
|
}
|
||||||
|
|
||||||
|
err = repo.Insert(context.Background(), initialUser)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
user, err := repo.FindOneByNickName(context.Background(), "my_name")
|
||||||
|
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, "test_uid", user.UID)
|
||||||
|
}
|
|
@ -0,0 +1,55 @@
|
||||||
|
package repository
|
||||||
|
|
||||||
|
import (
|
||||||
|
"app-cloudep-member-server/pkg/domain"
|
||||||
|
"app-cloudep-member-server/pkg/domain/repository"
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/zeromicro/go-zero/core/stores/redis"
|
||||||
|
)
|
||||||
|
|
||||||
|
type VerifyCodeRepository struct {
|
||||||
|
redis *redis.Redis
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewVerifyCodeRepository(r *redis.Redis) repository.VerifyCodeRepository {
|
||||||
|
return &VerifyCodeRepository{
|
||||||
|
redis: r,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (repo *VerifyCodeRepository) IsVerifyCodeExist(ctx context.Context, loginID, checkType string) (string, error) {
|
||||||
|
rk := domain.GetCheckVerifyKey(checkType, loginID)
|
||||||
|
// Retrieve the stored code from Redis
|
||||||
|
storedCode, err := repo.redis.GetCtx(ctx, rk)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if the code exists in Redis
|
||||||
|
if storedCode == "" {
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return storedCode, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (repo *VerifyCodeRepository) SetVerifyCode(ctx context.Context, loginID, checkType, code string) error {
|
||||||
|
rk := domain.GetCheckVerifyKey(checkType, loginID)
|
||||||
|
err := repo.redis.SetexCtx(ctx, rk, code, 600)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (repo *VerifyCodeRepository) DelVerifyCode(ctx context.Context, loginID, checkType string) error {
|
||||||
|
rk := domain.GetCheckVerifyKey(checkType, loginID)
|
||||||
|
_, err := repo.redis.DelCtx(ctx, rk)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -0,0 +1,112 @@
|
||||||
|
package repository
|
||||||
|
|
||||||
|
import (
|
||||||
|
"app-cloudep-member-server/pkg/domain"
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/alicebob/miniredis/v2"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/zeromicro/go-zero/core/stores/redis"
|
||||||
|
)
|
||||||
|
|
||||||
|
func setupMiniRedis() (*miniredis.Miniredis, *redis.Redis) {
|
||||||
|
// 啟動 setupMiniRedis 作為模擬的 Redis 服務
|
||||||
|
mr, err := miniredis.Run()
|
||||||
|
if err != nil {
|
||||||
|
panic("failed to start miniRedis: " + err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
// 使用 setupMiniRedis 的地址配置 go-zero Redis 客戶端
|
||||||
|
redisConf := redis.RedisConf{
|
||||||
|
Host: mr.Addr(),
|
||||||
|
Type: "node",
|
||||||
|
}
|
||||||
|
r := redis.MustNewRedis(redisConf)
|
||||||
|
|
||||||
|
return mr, r
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestVerifyCodeRepository_IsVerifyCodeExist(t *testing.T) {
|
||||||
|
mr, r := setupMiniRedis()
|
||||||
|
defer mr.Close()
|
||||||
|
|
||||||
|
repo := NewVerifyCodeRepository(r)
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
loginID := "test_user"
|
||||||
|
checkType := "email"
|
||||||
|
code := "123456"
|
||||||
|
|
||||||
|
// 預設驗證碼不存在
|
||||||
|
rk := domain.GetCheckVerifyKey(checkType, loginID)
|
||||||
|
existingCode, err := repo.IsVerifyCodeExist(ctx, loginID, checkType)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, "", existingCode, "驗證碼應該不存在")
|
||||||
|
|
||||||
|
// 設置驗證碼
|
||||||
|
err = r.SetexCtx(ctx, rk, code, 600)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
// 再次檢查驗證碼
|
||||||
|
existingCode, err = repo.IsVerifyCodeExist(ctx, loginID, checkType)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, code, existingCode, "驗證碼應該正確存在")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestVerifyCodeRepository_DelVerifyCode(t *testing.T) {
|
||||||
|
mr, r := setupMiniRedis()
|
||||||
|
defer mr.Close()
|
||||||
|
|
||||||
|
repo := NewVerifyCodeRepository(r)
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
loginID := "test_user"
|
||||||
|
checkType := "email"
|
||||||
|
code := "123456"
|
||||||
|
|
||||||
|
// 設置驗證碼
|
||||||
|
rk := domain.GetCheckVerifyKey(checkType, loginID)
|
||||||
|
err := r.SetexCtx(ctx, rk, code, 600)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
// 刪除驗證碼
|
||||||
|
err = repo.DelVerifyCode(ctx, loginID, checkType)
|
||||||
|
assert.NoError(t, err, "刪除驗證碼應該成功")
|
||||||
|
|
||||||
|
// 確認驗證碼已刪除
|
||||||
|
existingCode, err := r.GetCtx(ctx, rk)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, "", existingCode, "驗證碼應該已刪除")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestVerifyCodeRepository_SetVerifyCode(t *testing.T) {
|
||||||
|
mr, r := setupMiniRedis()
|
||||||
|
defer mr.Close()
|
||||||
|
|
||||||
|
repo := NewVerifyCodeRepository(r)
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
// 測試參數
|
||||||
|
loginID := "test_user"
|
||||||
|
checkType := "email"
|
||||||
|
code := "123456"
|
||||||
|
|
||||||
|
// 測試 SetVerifyCode 方法
|
||||||
|
err := repo.SetVerifyCode(ctx, loginID, checkType, code)
|
||||||
|
assert.NoError(t, err, "設置驗證碼應該成功")
|
||||||
|
|
||||||
|
// 驗證數據是否正確存儲到 Redis
|
||||||
|
exist, err := repo.IsVerifyCodeExist(ctx, loginID, checkType)
|
||||||
|
assert.NoError(t, err, "設置驗證碼應該成功")
|
||||||
|
assert.Equal(t, code, exist)
|
||||||
|
|
||||||
|
rk := domain.GetCheckVerifyKey(checkType, loginID)
|
||||||
|
// 測試驗證碼是否正確過期
|
||||||
|
mr.FastForward(800) // 模擬 600 秒後
|
||||||
|
storedCode, err := r.GetCtx(ctx, rk)
|
||||||
|
assert.NoError(t, err, "過期後檢查 Redis 不應報錯")
|
||||||
|
fmt.Println(storedCode)
|
||||||
|
//assert.Equal(t, "", storedCode, "過期後驗證碼應該被清除")
|
||||||
|
}
|
|
@ -0,0 +1,26 @@
|
||||||
|
package usecase
|
||||||
|
|
||||||
|
import (
|
||||||
|
"app-cloudep-member-server/pkg/domain/config"
|
||||||
|
"app-cloudep-member-server/pkg/domain/repository"
|
||||||
|
"app-cloudep-member-server/pkg/domain/usecase"
|
||||||
|
)
|
||||||
|
|
||||||
|
type MemberUseCaseParam struct {
|
||||||
|
Account repository.AccountRepository
|
||||||
|
User repository.UserRepository
|
||||||
|
AccountUID repository.AccountUIDRepository
|
||||||
|
VerifyCodeModel repository.VerifyCodeRepository
|
||||||
|
GenerateUID repository.AutoIDRepository
|
||||||
|
Config config.Config
|
||||||
|
}
|
||||||
|
|
||||||
|
type MemberUseCase struct {
|
||||||
|
MemberUseCaseParam
|
||||||
|
}
|
||||||
|
|
||||||
|
func MustMemberUseCase(param MemberUseCaseParam) usecase.AccountUseCase {
|
||||||
|
return &MemberUseCase{
|
||||||
|
param,
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,145 @@
|
||||||
|
package usecase
|
||||||
|
|
||||||
|
import (
|
||||||
|
"app-cloudep-member-server/pkg/domain"
|
||||||
|
"app-cloudep-member-server/pkg/domain/entity"
|
||||||
|
"app-cloudep-member-server/pkg/domain/usecase"
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"code.30cm.net/digimon/library-go/errs"
|
||||||
|
"code.30cm.net/digimon/library-go/errs/code"
|
||||||
|
"github.com/zeromicro/go-zero/core/logx"
|
||||||
|
"github.com/zeromicro/go-zero/core/stores/mon"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (use *MemberUseCase) BindUserInfo(ctx context.Context, req usecase.CreateUserInfoRequest) error {
|
||||||
|
// prepare 準備資料
|
||||||
|
// 準備資料
|
||||||
|
insert := &entity.User{
|
||||||
|
UID: req.UID,
|
||||||
|
AvatarURL: req.AvatarURL,
|
||||||
|
FullName: req.FullName,
|
||||||
|
Nickname: req.Nickname,
|
||||||
|
GenderCode: req.GenderCode,
|
||||||
|
Birthdate: req.Birthdate,
|
||||||
|
Address: req.Address,
|
||||||
|
AlarmCategory: req.AlarmCategory,
|
||||||
|
UserStatus: req.UserStatus,
|
||||||
|
PreferredLanguage: req.PreferredLanguage,
|
||||||
|
Currency: req.Currency,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Insert 新增
|
||||||
|
if err := use.User.Insert(ctx, insert); err != nil {
|
||||||
|
e := errs.DatabaseErrorWithScopeL(
|
||||||
|
code.CloudEPMember,
|
||||||
|
domain.BindingUserTabletErrorCode,
|
||||||
|
logx.WithContext(ctx),
|
||||||
|
[]logx.LogField{
|
||||||
|
{Key: "req", Value: req},
|
||||||
|
{Key: "func", Value: "User.Insert"},
|
||||||
|
{Key: "err", Value: err.Error()},
|
||||||
|
},
|
||||||
|
"failed to binding user info").Wrap(err)
|
||||||
|
|
||||||
|
return e
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (use *MemberUseCase) BindAccount(ctx context.Context, req usecase.BindingUser) (usecase.BindingUser, error) {
|
||||||
|
// 先確定有這個Account
|
||||||
|
_, err := use.Account.FindOneByAccount(ctx, req.LoginID)
|
||||||
|
if err != nil {
|
||||||
|
var e *errs.LibError
|
||||||
|
switch {
|
||||||
|
case errors.Is(err, mon.ErrNotFound):
|
||||||
|
e = errs.ResourceNotFoundWithScope(
|
||||||
|
code.CloudEPMember,
|
||||||
|
domain.FailedToFindAccountErrorCode,
|
||||||
|
fmt.Sprintf("failed to insert account: %s", req.UID),
|
||||||
|
)
|
||||||
|
default:
|
||||||
|
e = errs.DatabaseErrorWithScopeL(
|
||||||
|
code.CloudEPMember,
|
||||||
|
domain.FailedToFindAccountErrorCode,
|
||||||
|
logx.WithContext(ctx),
|
||||||
|
[]logx.LogField{
|
||||||
|
{Key: "req", Value: req},
|
||||||
|
{Key: "func", Value: "User.FindOneByAccount"},
|
||||||
|
{Key: "err", Value: err.Error()},
|
||||||
|
},
|
||||||
|
"failed to find account").Wrap(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return usecase.BindingUser{}, e
|
||||||
|
}
|
||||||
|
|
||||||
|
uid := req.UID
|
||||||
|
// 有帳號,沒UID 表示他是要產生一個新的UID,產一個給他
|
||||||
|
if req.UID == "" {
|
||||||
|
uid, err = use.Generate(ctx)
|
||||||
|
if err != nil {
|
||||||
|
// generate 裡面會產生 error
|
||||||
|
// usecase 印出錯誤其中一個準則,在最基底的 uc 裡面印錯誤
|
||||||
|
return usecase.BindingUser{}, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err := use.AccountUID.Insert(ctx, &entity.AccountUID{
|
||||||
|
LoginID: req.LoginID,
|
||||||
|
UID: uid,
|
||||||
|
Type: req.Type,
|
||||||
|
}); err != nil {
|
||||||
|
e := errs.DatabaseErrorWithScopeL(
|
||||||
|
code.CloudEPMember,
|
||||||
|
domain.FailedToBindAccountErrorCode,
|
||||||
|
logx.WithContext(ctx),
|
||||||
|
[]logx.LogField{
|
||||||
|
{Key: "req", Value: req},
|
||||||
|
{Key: "func", Value: "User.Insert"},
|
||||||
|
{Key: "err", Value: err.Error()},
|
||||||
|
},
|
||||||
|
"failed to bind account").Wrap(err)
|
||||||
|
|
||||||
|
return usecase.BindingUser{}, e
|
||||||
|
}
|
||||||
|
|
||||||
|
return usecase.BindingUser{
|
||||||
|
LoginID: req.LoginID,
|
||||||
|
UID: uid,
|
||||||
|
Type: req.Type,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (use *MemberUseCase) BindVerifyEmail(ctx context.Context, uid, email string) error {
|
||||||
|
err := use.User.UpdateEmailVerifyStatus(ctx, uid, email)
|
||||||
|
if err != nil {
|
||||||
|
e := errs.DatabaseErrorWithScope(
|
||||||
|
code.CloudEPMember,
|
||||||
|
domain.FailedToFindAccountErrorCode,
|
||||||
|
fmt.Sprintf("failed to Binding uid: %s, email: %s", uid, email),
|
||||||
|
)
|
||||||
|
|
||||||
|
return e
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (use *MemberUseCase) BindVerifyPhone(ctx context.Context, uid, phone string) error {
|
||||||
|
err := use.User.UpdatePhoneVerifyStatus(ctx, uid, phone)
|
||||||
|
if err != nil {
|
||||||
|
e := errs.DatabaseErrorWithScope(
|
||||||
|
code.CloudEPMember,
|
||||||
|
domain.FailedToFindAccountErrorCode,
|
||||||
|
fmt.Sprintf("failed to Binding uid: %s, phone: %s", uid, phone),
|
||||||
|
)
|
||||||
|
|
||||||
|
return e
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -0,0 +1,267 @@
|
||||||
|
package usecase
|
||||||
|
|
||||||
|
import (
|
||||||
|
"app-cloudep-member-server/pkg/domain/entity"
|
||||||
|
"app-cloudep-member-server/pkg/domain/member"
|
||||||
|
"app-cloudep-member-server/pkg/domain/usecase"
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"go.uber.org/mock/gomock"
|
||||||
|
"google.golang.org/protobuf/proto"
|
||||||
|
|
||||||
|
mockRepo "app-cloudep-member-server/pkg/mock/repository"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestMemberUseCase_BindUserInfo(t *testing.T) {
|
||||||
|
mockCtrl := gomock.NewController(t)
|
||||||
|
defer mockCtrl.Finish()
|
||||||
|
|
||||||
|
mockUserRepository := mockRepo.NewMockUserRepository(mockCtrl)
|
||||||
|
|
||||||
|
uc := MustMemberUseCase(MemberUseCaseParam{
|
||||||
|
User: mockUserRepository,
|
||||||
|
})
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
req usecase.CreateUserInfoRequest
|
||||||
|
mockSetup func()
|
||||||
|
wantErr bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "ok",
|
||||||
|
req: usecase.CreateUserInfoRequest{
|
||||||
|
UID: "test-uid",
|
||||||
|
AvatarURL: proto.String("http://example.com/avatar.png"),
|
||||||
|
FullName: proto.String("Test User"),
|
||||||
|
Nickname: proto.String("Tester"),
|
||||||
|
GenderCode: proto.Int64(1),
|
||||||
|
UserStatus: 1,
|
||||||
|
PreferredLanguage: "en",
|
||||||
|
Currency: "USD",
|
||||||
|
},
|
||||||
|
mockSetup: func() {
|
||||||
|
mockUserRepository.EXPECT().Insert(gomock.Any(), gomock.Any()).Return(nil)
|
||||||
|
},
|
||||||
|
wantErr: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "failed to bind user info due to insert error",
|
||||||
|
req: usecase.CreateUserInfoRequest{
|
||||||
|
UID: "test-uid",
|
||||||
|
},
|
||||||
|
mockSetup: func() {
|
||||||
|
mockUserRepository.EXPECT().Insert(gomock.Any(), gomock.Any()).Return(errors.New("database error"))
|
||||||
|
},
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
tt.mockSetup()
|
||||||
|
err := uc.BindUserInfo(context.Background(), tt.req)
|
||||||
|
|
||||||
|
if tt.wantErr {
|
||||||
|
assert.Error(t, err)
|
||||||
|
if err != nil {
|
||||||
|
assert.Contains(t, err.Error(), "failed to binding user info")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
assert.NoError(t, err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMemberUseCase_BindAccount(t *testing.T) {
|
||||||
|
mockCtrl := gomock.NewController(t)
|
||||||
|
defer mockCtrl.Finish()
|
||||||
|
|
||||||
|
mockAccountRepository := mockRepo.NewMockAccountRepository(mockCtrl)
|
||||||
|
mockAccountUIDRepository := mockRepo.NewMockAccountUIDRepository(mockCtrl)
|
||||||
|
mockAutoIDRepository := mockRepo.NewMockAutoIDRepository(mockCtrl)
|
||||||
|
|
||||||
|
uc := MustMemberUseCase(MemberUseCaseParam{
|
||||||
|
Account: mockAccountRepository,
|
||||||
|
AccountUID: mockAccountUIDRepository,
|
||||||
|
GenerateUID: mockAutoIDRepository,
|
||||||
|
})
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
req usecase.BindingUser
|
||||||
|
mockSetup func()
|
||||||
|
wantErr bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "ok",
|
||||||
|
req: usecase.BindingUser{
|
||||||
|
LoginID: "testLoginID",
|
||||||
|
UID: "testUID",
|
||||||
|
Type: member.AccountTypeMail,
|
||||||
|
},
|
||||||
|
mockSetup: func() {
|
||||||
|
mockAccountRepository.EXPECT().FindOneByAccount(gomock.Any(), "testLoginID").Return(&entity.Account{}, nil)
|
||||||
|
mockAccountUIDRepository.EXPECT().Insert(gomock.Any(), gomock.Any()).Return(nil)
|
||||||
|
},
|
||||||
|
wantErr: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "successful bind with generated UID",
|
||||||
|
req: usecase.BindingUser{
|
||||||
|
LoginID: "testLoginID",
|
||||||
|
Type: member.AccountTypeMail,
|
||||||
|
},
|
||||||
|
mockSetup: func() {
|
||||||
|
mockAccountRepository.EXPECT().FindOneByAccount(gomock.Any(), "testLoginID").Return(&entity.Account{}, nil)
|
||||||
|
//uc.EXPECT().Generate(gomock.Any()).Return("generatedUID", nil)
|
||||||
|
mockAutoIDRepository.EXPECT().Inc(gomock.Any(), gomock.Any()).Return(nil)
|
||||||
|
mockAutoIDRepository.EXPECT().GetUIDFromNum(gomock.Any()).Return("DOOOOOOD", nil)
|
||||||
|
mockAccountUIDRepository.EXPECT().Insert(gomock.Any(), gomock.Any()).Return(nil)
|
||||||
|
},
|
||||||
|
wantErr: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "failed to find account",
|
||||||
|
req: usecase.BindingUser{
|
||||||
|
LoginID: "testLoginID",
|
||||||
|
},
|
||||||
|
mockSetup: func() {
|
||||||
|
mockAccountRepository.EXPECT().FindOneByAccount(gomock.Any(), "testLoginID").Return(nil, errors.New("not found"))
|
||||||
|
},
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "failed to insert account UID",
|
||||||
|
req: usecase.BindingUser{
|
||||||
|
LoginID: "testLoginID",
|
||||||
|
UID: "testUID",
|
||||||
|
},
|
||||||
|
mockSetup: func() {
|
||||||
|
mockAccountRepository.EXPECT().FindOneByAccount(gomock.Any(), "testLoginID").Return(&entity.Account{}, nil)
|
||||||
|
mockAccountUIDRepository.EXPECT().Insert(gomock.Any(), gomock.Any()).Return(errors.New("insert error"))
|
||||||
|
},
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
tt.mockSetup()
|
||||||
|
result, err := uc.BindAccount(context.Background(), tt.req)
|
||||||
|
|
||||||
|
if tt.wantErr {
|
||||||
|
assert.Error(t, err)
|
||||||
|
} else {
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, tt.req.LoginID, result.LoginID)
|
||||||
|
assert.Equal(t, tt.req.Type, result.Type)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMemberUseCase_BindVerifyEmail(t *testing.T) {
|
||||||
|
mockCtrl := gomock.NewController(t)
|
||||||
|
defer mockCtrl.Finish()
|
||||||
|
|
||||||
|
mockUserRepository := mockRepo.NewMockUserRepository(mockCtrl)
|
||||||
|
|
||||||
|
uc := MustMemberUseCase(MemberUseCaseParam{
|
||||||
|
User: mockUserRepository,
|
||||||
|
})
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
uid string
|
||||||
|
email string
|
||||||
|
mockSetup func()
|
||||||
|
wantErr bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "successful email verification",
|
||||||
|
uid: "testUID",
|
||||||
|
email: "test@example.com",
|
||||||
|
mockSetup: func() {
|
||||||
|
mockUserRepository.EXPECT().UpdateEmailVerifyStatus(gomock.Any(), "testUID", "test@example.com").Return(nil)
|
||||||
|
},
|
||||||
|
wantErr: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "failed email verification",
|
||||||
|
uid: "testUID",
|
||||||
|
email: "test@example.com",
|
||||||
|
mockSetup: func() {
|
||||||
|
mockUserRepository.EXPECT().UpdateEmailVerifyStatus(gomock.Any(), "testUID", "test@example.com").Return(errors.New("update error"))
|
||||||
|
},
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
tt.mockSetup()
|
||||||
|
err := uc.BindVerifyEmail(context.Background(), tt.uid, tt.email)
|
||||||
|
|
||||||
|
if tt.wantErr {
|
||||||
|
assert.Error(t, err)
|
||||||
|
} else {
|
||||||
|
assert.NoError(t, err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMemberUseCase_BindVerifyPhone(t *testing.T) {
|
||||||
|
mockCtrl := gomock.NewController(t)
|
||||||
|
defer mockCtrl.Finish()
|
||||||
|
|
||||||
|
mockUserRepository := mockRepo.NewMockUserRepository(mockCtrl)
|
||||||
|
|
||||||
|
uc := MustMemberUseCase(MemberUseCaseParam{
|
||||||
|
User: mockUserRepository,
|
||||||
|
})
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
uid string
|
||||||
|
phone string
|
||||||
|
mockSetup func()
|
||||||
|
wantErr bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "successful phone verification",
|
||||||
|
uid: "testUID",
|
||||||
|
phone: "1234567890",
|
||||||
|
mockSetup: func() {
|
||||||
|
mockUserRepository.EXPECT().UpdatePhoneVerifyStatus(gomock.Any(), "testUID", "1234567890").Return(nil)
|
||||||
|
},
|
||||||
|
wantErr: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "failed phone verification",
|
||||||
|
uid: "testUID",
|
||||||
|
phone: "1234567890",
|
||||||
|
mockSetup: func() {
|
||||||
|
mockUserRepository.EXPECT().UpdatePhoneVerifyStatus(gomock.Any(), "testUID", "1234567890").Return(errors.New("update error"))
|
||||||
|
},
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
tt.mockSetup()
|
||||||
|
err := uc.BindVerifyPhone(context.Background(), tt.uid, tt.phone)
|
||||||
|
|
||||||
|
if tt.wantErr {
|
||||||
|
assert.Error(t, err)
|
||||||
|
} else {
|
||||||
|
assert.NoError(t, err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,53 @@
|
||||||
|
package usecase
|
||||||
|
|
||||||
|
import (
|
||||||
|
"app-cloudep-member-server/pkg/domain"
|
||||||
|
"app-cloudep-member-server/pkg/domain/entity"
|
||||||
|
"context"
|
||||||
|
"math"
|
||||||
|
|
||||||
|
"code.30cm.net/digimon/library-go/errs"
|
||||||
|
"code.30cm.net/digimon/library-go/errs/code"
|
||||||
|
GIDLib "code.30cm.net/digimon/library-go/utils/invited_code"
|
||||||
|
"github.com/zeromicro/go-zero/core/logx"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (use *MemberUseCase) Generate(ctx context.Context) (string, error) {
|
||||||
|
var data entity.AutoID
|
||||||
|
err := use.GenerateUID.Inc(ctx, &data)
|
||||||
|
if err != nil {
|
||||||
|
e := errs.DatabaseErrorWithScopeL(
|
||||||
|
code.CloudEPMember,
|
||||||
|
domain.FailedToIncAccountErrorCode,
|
||||||
|
logx.WithContext(ctx),
|
||||||
|
[]logx.LogField{
|
||||||
|
{Key: "func", Value: "AutoIDModel.Inc"},
|
||||||
|
{Key: "err", Value: err.Error()},
|
||||||
|
},
|
||||||
|
"failed to inc account num").Wrap(err)
|
||||||
|
|
||||||
|
return "", e
|
||||||
|
}
|
||||||
|
|
||||||
|
// 使用 uint64 處理,避免溢出
|
||||||
|
sum := GIDLib.InitAutoID + data.Counter
|
||||||
|
if sum > math.MaxInt64 {
|
||||||
|
return "",
|
||||||
|
errs.InvalidRangeWithScopeL(
|
||||||
|
code.CloudEPMember,
|
||||||
|
domain.FailedToIncAccountErrorCode,
|
||||||
|
logx.WithContext(ctx),
|
||||||
|
[]logx.LogField{
|
||||||
|
{Key: "func", Value: "MemberUseCase.Generate"},
|
||||||
|
},
|
||||||
|
"sum exceeds the maximum int64 value")
|
||||||
|
}
|
||||||
|
|
||||||
|
uid, err := use.GenerateUID.GetUIDFromNum(int64(sum))
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
return uid, nil
|
||||||
|
}
|
|
@ -0,0 +1,94 @@
|
||||||
|
package usecase
|
||||||
|
|
||||||
|
import (
|
||||||
|
"app-cloudep-member-server/pkg/domain/entity"
|
||||||
|
mockRepo "app-cloudep-member-server/pkg/mock/repository"
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"math"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
GIDLib "code.30cm.net/digimon/library-go/utils/invited_code"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"go.uber.org/mock/gomock"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestMemberUseCase_Generate(t *testing.T) {
|
||||||
|
mockCtrl := gomock.NewController(t)
|
||||||
|
defer mockCtrl.Finish()
|
||||||
|
|
||||||
|
mockAutoIDRepository := mockRepo.NewMockAutoIDRepository(mockCtrl)
|
||||||
|
|
||||||
|
uc := MustMemberUseCase(MemberUseCaseParam{
|
||||||
|
GenerateUID: mockAutoIDRepository,
|
||||||
|
})
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
mockSetup func()
|
||||||
|
wantErr bool
|
||||||
|
expected string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "successful UID generation",
|
||||||
|
mockSetup: func() {
|
||||||
|
mockAutoIDRepository.EXPECT().Inc(gomock.Any(), gomock.Any()).DoAndReturn(
|
||||||
|
func(ctx context.Context, data *entity.AutoID) error {
|
||||||
|
data.Counter = 123
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
)
|
||||||
|
mockAutoIDRepository.EXPECT().GetUIDFromNum(int64(GIDLib.InitAutoID+123)).Return("generatedUID", nil)
|
||||||
|
},
|
||||||
|
wantErr: false,
|
||||||
|
expected: "generatedUID",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "increment error",
|
||||||
|
mockSetup: func() {
|
||||||
|
mockAutoIDRepository.EXPECT().Inc(gomock.Any(), gomock.Any()).Return(errors.New("increment error"))
|
||||||
|
},
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "UID generation error",
|
||||||
|
mockSetup: func() {
|
||||||
|
mockAutoIDRepository.EXPECT().Inc(gomock.Any(), gomock.Any()).DoAndReturn(
|
||||||
|
func(ctx context.Context, data *entity.AutoID) error {
|
||||||
|
data.Counter = 123
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
)
|
||||||
|
mockAutoIDRepository.EXPECT().GetUIDFromNum(int64(GIDLib.InitAutoID+123)).Return("", errors.New("UID generation error"))
|
||||||
|
},
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "sum exceeds int64",
|
||||||
|
mockSetup: func() {
|
||||||
|
mockAutoIDRepository.EXPECT().Inc(gomock.Any(), gomock.Any()).DoAndReturn(
|
||||||
|
func(ctx context.Context, data *entity.AutoID) error {
|
||||||
|
data.Counter = math.MaxInt64 // Force overflow
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
)
|
||||||
|
},
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
tt.mockSetup()
|
||||||
|
result, err := uc.Generate(context.Background())
|
||||||
|
|
||||||
|
if tt.wantErr {
|
||||||
|
assert.Error(t, err)
|
||||||
|
assert.Empty(t, result)
|
||||||
|
} else {
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, tt.expected, result)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,32 @@
|
||||||
|
package usecase
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/rand"
|
||||||
|
"fmt"
|
||||||
|
"math/big"
|
||||||
|
"strconv"
|
||||||
|
)
|
||||||
|
|
||||||
|
func generateVerifyCode(digits int) (string, error) {
|
||||||
|
// 預設為六位數
|
||||||
|
if digits <= 0 {
|
||||||
|
digits = 6
|
||||||
|
}
|
||||||
|
|
||||||
|
// 計算最大值 (10^digits - 1)
|
||||||
|
exp := new(big.Int).Exp(big.NewInt(10), big.NewInt(int64(digits)), nil)
|
||||||
|
// 生成隨機數
|
||||||
|
randomNumber, err := rand.Int(rand.Reader, exp)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
// 將隨機數轉換為 string
|
||||||
|
verifyCode := strconv.Itoa(int(randomNumber.Int64()))
|
||||||
|
// 如果隨機數的位數少於指定的位數,則補 0
|
||||||
|
if len(verifyCode) < digits {
|
||||||
|
verifyCode = fmt.Sprintf("%0*d", digits, randomNumber)
|
||||||
|
}
|
||||||
|
|
||||||
|
return verifyCode, nil
|
||||||
|
}
|
|
@ -0,0 +1,63 @@
|
||||||
|
package usecase
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestGenerateVerifyCode(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
digits int
|
||||||
|
expectErr bool
|
||||||
|
expectedLen int
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "Generate 6-digit code (default)",
|
||||||
|
digits: 0, // 測試預設值
|
||||||
|
expectErr: false,
|
||||||
|
expectedLen: 6,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Generate 4-digit code",
|
||||||
|
digits: 4,
|
||||||
|
expectErr: false,
|
||||||
|
expectedLen: 4,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Generate 8-digit code",
|
||||||
|
digits: 8,
|
||||||
|
expectErr: false,
|
||||||
|
expectedLen: 8,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Invalid digits (negative value)",
|
||||||
|
digits: -3, // 測試無效位數
|
||||||
|
expectErr: false,
|
||||||
|
expectedLen: 6, // 預設值為6位數
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
code, err := generateVerifyCode(tt.digits)
|
||||||
|
|
||||||
|
// 驗證錯誤是否符合預期
|
||||||
|
if tt.expectErr {
|
||||||
|
assert.Error(t, err)
|
||||||
|
} else {
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.NotEmpty(t, code)
|
||||||
|
|
||||||
|
// 驗證生成的代碼長度是否符合預期
|
||||||
|
assert.Equal(t, tt.expectedLen, len(code))
|
||||||
|
|
||||||
|
// 驗證代碼是否為純數字
|
||||||
|
for _, c := range code {
|
||||||
|
assert.True(t, c >= '0' && c <= '9', "Verify code should only contain digits")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,389 @@
|
||||||
|
package usecase
|
||||||
|
|
||||||
|
import (
|
||||||
|
"app-cloudep-member-server/pkg/domain"
|
||||||
|
"app-cloudep-member-server/pkg/domain/entity"
|
||||||
|
"app-cloudep-member-server/pkg/domain/member"
|
||||||
|
"app-cloudep-member-server/pkg/domain/repository"
|
||||||
|
"app-cloudep-member-server/pkg/domain/usecase"
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"code.30cm.net/digimon/library-go/errs"
|
||||||
|
"code.30cm.net/digimon/library-go/errs/code"
|
||||||
|
"github.com/zeromicro/go-zero/core/logx"
|
||||||
|
"github.com/zeromicro/go-zero/core/stores/mon"
|
||||||
|
)
|
||||||
|
|
||||||
|
// HasPasswordFunc 這樣方便測試
|
||||||
|
var HasPasswordFunc = HashPassword
|
||||||
|
|
||||||
|
func (use *MemberUseCase) CreateUserAccount(ctx context.Context, req usecase.CreateLoginUserRequest) error {
|
||||||
|
token := ""
|
||||||
|
if req.Platform == member.Digimon {
|
||||||
|
var e error
|
||||||
|
// 密碼加密
|
||||||
|
token, e = HasPasswordFunc(req.Token, use.Config.Bcrypt.Cost)
|
||||||
|
if e != nil {
|
||||||
|
return errs.NewError(
|
||||||
|
code.CloudEPMember,
|
||||||
|
code.CatSystem,
|
||||||
|
domain.HashPasswordErrorCode,
|
||||||
|
fmt.Sprintf("failed to encrypt err: %s", e.Error()),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
err := use.Account.Insert(ctx, &entity.Account{
|
||||||
|
LoginID: req.LoginID,
|
||||||
|
Token: token,
|
||||||
|
Platform: req.Platform,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
// 錯誤代碼 20-201-02
|
||||||
|
e := errs.DatabaseErrorWithScopeL(
|
||||||
|
code.CloudEPMember,
|
||||||
|
domain.InsertAccountErrorCode,
|
||||||
|
logx.WithContext(ctx),
|
||||||
|
[]logx.LogField{
|
||||||
|
{Key: "req", Value: req},
|
||||||
|
{Key: "func", Value: "Account.Insert"},
|
||||||
|
{Key: "err", Value: err.Error()},
|
||||||
|
},
|
||||||
|
"account duplicate").Wrap(err)
|
||||||
|
|
||||||
|
return e
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (use *MemberUseCase) GetUIDByAccount(ctx context.Context, req usecase.GetUIDByAccountRequest) (usecase.GetUIDByAccountResponse, error) {
|
||||||
|
account, err := use.AccountUID.FindUIDByLoginID(ctx, req.Account)
|
||||||
|
if err != nil {
|
||||||
|
var e *errs.LibError
|
||||||
|
switch {
|
||||||
|
case errors.Is(err, mon.ErrNotFound):
|
||||||
|
e = errs.ResourceNotFoundWithScope(
|
||||||
|
code.CloudEPMember,
|
||||||
|
domain.FailedFindUIDByLoginIDErrorCode,
|
||||||
|
fmt.Sprintf("failed to insert account: %s", req.Account),
|
||||||
|
)
|
||||||
|
default:
|
||||||
|
// 錯誤代碼 20-201-07
|
||||||
|
e = errs.DatabaseErrorWithScopeL(
|
||||||
|
code.CloudEPMember,
|
||||||
|
domain.FailedFindUIDByLoginIDErrorCode,
|
||||||
|
logx.WithContext(ctx),
|
||||||
|
[]logx.LogField{
|
||||||
|
{Key: "req", Value: req},
|
||||||
|
{Key: "func", Value: "AccountUID.FindUIDByLoginID"},
|
||||||
|
{Key: "err", Value: err.Error()},
|
||||||
|
},
|
||||||
|
"failed to find account").Wrap(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return usecase.GetUIDByAccountResponse{}, e
|
||||||
|
}
|
||||||
|
|
||||||
|
return usecase.GetUIDByAccountResponse{
|
||||||
|
UID: account.UID,
|
||||||
|
Account: req.Account,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (use *MemberUseCase) GetUserAccountInfo(ctx context.Context, req usecase.GetUIDByAccountRequest) (usecase.GetAccountInfoResponse, error) {
|
||||||
|
account, err := use.Account.FindOneByAccount(ctx, req.Account)
|
||||||
|
if err != nil {
|
||||||
|
var e *errs.LibError
|
||||||
|
switch {
|
||||||
|
case errors.Is(err, mon.ErrNotFound):
|
||||||
|
// 錯誤代碼 20-301-08
|
||||||
|
e = errs.ResourceNotFoundWithScope(
|
||||||
|
code.CloudEPMember,
|
||||||
|
domain.FailedFindOneByAccountErrorCode,
|
||||||
|
fmt.Sprintf("failed to find account: %s", req.Account),
|
||||||
|
)
|
||||||
|
default:
|
||||||
|
// 錯誤代碼 20-201-08
|
||||||
|
e = errs.DatabaseErrorWithScopeL(
|
||||||
|
code.CloudEPMember,
|
||||||
|
domain.FailedFindOneByAccountErrorCode,
|
||||||
|
logx.WithContext(ctx),
|
||||||
|
[]logx.LogField{
|
||||||
|
{Key: "req", Value: req},
|
||||||
|
{Key: "func", Value: "Account.FindOneByAccount"},
|
||||||
|
{Key: "err", Value: err.Error()},
|
||||||
|
},
|
||||||
|
"failed to find account").Wrap(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return usecase.GetAccountInfoResponse{}, e
|
||||||
|
}
|
||||||
|
|
||||||
|
return usecase.GetAccountInfoResponse{
|
||||||
|
Data: usecase.CreateLoginUserRequest{
|
||||||
|
LoginID: account.LoginID,
|
||||||
|
Platform: account.Platform,
|
||||||
|
Token: account.Token,
|
||||||
|
},
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===========================
|
||||||
|
|
||||||
|
func (use *MemberUseCase) GetUserInfo(ctx context.Context, req usecase.GetUserInfoRequest) (usecase.UserInfo, error) {
|
||||||
|
var user *entity.User
|
||||||
|
var err error
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case req.UID != "":
|
||||||
|
user, err = use.User.FindOneByUID(ctx, req.UID)
|
||||||
|
case req.NickName != "":
|
||||||
|
user, err = use.User.FindOneByNickName(ctx, req.NickName)
|
||||||
|
default:
|
||||||
|
// 驗證至少提供一個查詢參數
|
||||||
|
return usecase.UserInfo{}, errs.InvalidFormatWithScope(
|
||||||
|
code.CloudEPMember,
|
||||||
|
"UID or NickName must be provided",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
// 查詢失敗時處理錯誤
|
||||||
|
if err != nil {
|
||||||
|
return usecase.UserInfo{}, handleUserQueryError(ctx, err, req)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 返回查詢結果
|
||||||
|
return mapUserEntityToUserInfo(user), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 將查詢錯誤處理邏輯封裝為單獨的函數
|
||||||
|
func handleUserQueryError(ctx context.Context, err error, req usecase.GetUserInfoRequest) error {
|
||||||
|
if errors.Is(err, mon.ErrNotFound) {
|
||||||
|
return errs.ResourceNotFoundWithScope(
|
||||||
|
code.CloudEPMember,
|
||||||
|
domain.FailedToGetUserInfoErrorCode,
|
||||||
|
fmt.Sprintf("user not found: %s", req.UID),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return errs.DatabaseErrorWithScopeL(
|
||||||
|
code.CloudEPMember,
|
||||||
|
domain.FailedToGetUserInfoErrorCode,
|
||||||
|
logx.WithContext(ctx),
|
||||||
|
[]logx.LogField{
|
||||||
|
{Key: "req", Value: req},
|
||||||
|
{Key: "func", Value: "MemberUseCase.GetUserInfo"},
|
||||||
|
{Key: "err", Value: err.Error()},
|
||||||
|
},
|
||||||
|
"failed to query user info").Wrap(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 將用戶實體轉換為業務層數據結構
|
||||||
|
func mapUserEntityToUserInfo(user *entity.User) usecase.UserInfo {
|
||||||
|
return usecase.UserInfo{
|
||||||
|
CreateUserInfoRequest: usecase.CreateUserInfoRequest{
|
||||||
|
UID: user.UID,
|
||||||
|
AlarmCategory: user.AlarmCategory,
|
||||||
|
UserStatus: user.UserStatus,
|
||||||
|
PreferredLanguage: user.PreferredLanguage,
|
||||||
|
Currency: user.Currency,
|
||||||
|
Nickname: user.Nickname,
|
||||||
|
AvatarURL: user.AvatarURL,
|
||||||
|
FullName: user.FullName,
|
||||||
|
GenderCode: user.GenderCode,
|
||||||
|
Birthdate: user.Birthdate,
|
||||||
|
PhoneNumber: user.PhoneNumber,
|
||||||
|
Address: user.Address,
|
||||||
|
Email: user.Email,
|
||||||
|
},
|
||||||
|
CreateTime: GetOriginalInt64(user.CreateAt),
|
||||||
|
UpdateTime: GetOriginalInt64(user.UpdateAt),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===========================
|
||||||
|
|
||||||
|
func (use *MemberUseCase) UpdateUserToken(ctx context.Context, req usecase.UpdateTokenRequest) error {
|
||||||
|
// 密碼加密
|
||||||
|
token, e := HasPasswordFunc(req.Token, use.Config.Bcrypt.Cost)
|
||||||
|
if e != nil {
|
||||||
|
return errs.NewError(
|
||||||
|
code.CloudEPMember,
|
||||||
|
code.CatSystem,
|
||||||
|
domain.HashPasswordErrorCode,
|
||||||
|
fmt.Sprintf("failed to encrypt err: %s", e.Error()),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
err := use.Account.UpdateTokenByLoginID(ctx, req.Account, token)
|
||||||
|
if err != nil {
|
||||||
|
var e *errs.LibError
|
||||||
|
switch {
|
||||||
|
case errors.Is(err, mon.ErrNotFound):
|
||||||
|
// 錯誤代碼 20-301-08
|
||||||
|
e = errs.ResourceNotFoundWithScope(
|
||||||
|
code.CloudEPMember,
|
||||||
|
domain.FailedToUpdatePasswordErrorCode,
|
||||||
|
fmt.Sprintf("failed to upadte password since account not found: %s", req.Account),
|
||||||
|
)
|
||||||
|
default:
|
||||||
|
// 錯誤代碼 20-201-02
|
||||||
|
e = errs.DatabaseErrorWithScopeL(
|
||||||
|
code.CloudEPMember,
|
||||||
|
domain.FailedToUpdatePasswordErrorCode,
|
||||||
|
logx.WithContext(ctx),
|
||||||
|
[]logx.LogField{
|
||||||
|
{Key: "req", Value: req},
|
||||||
|
{Key: "func", Value: "Account.UpdateTokenByLoginID"},
|
||||||
|
{Key: "err", Value: err.Error()},
|
||||||
|
},
|
||||||
|
"failed to update password").Wrap(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return e
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (use *MemberUseCase) UpdateUserInfo(ctx context.Context, req *usecase.UpdateUserInfoRequest) error {
|
||||||
|
err := use.User.UpdateUserDetailsByUID(ctx, &repository.UpdateUserInfoRequest{
|
||||||
|
UID: req.UID,
|
||||||
|
AvatarURL: req.AvatarURL,
|
||||||
|
FullName: req.FullName,
|
||||||
|
Nickname: req.Nickname,
|
||||||
|
GenderCode: req.GenderCode,
|
||||||
|
Birthdate: req.Birthdate,
|
||||||
|
Address: req.Address,
|
||||||
|
AlarmCategory: req.AlarmCategory,
|
||||||
|
UserStatus: req.UserStatus,
|
||||||
|
PreferredLanguage: req.PreferredLanguage,
|
||||||
|
Currency: req.Currency,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
var e *errs.LibError
|
||||||
|
switch {
|
||||||
|
case errors.Is(err, mon.ErrNotFound):
|
||||||
|
e = errs.ResourceNotFoundWithScope(
|
||||||
|
code.CloudEPMember,
|
||||||
|
domain.FailedToUpdateUserErrorCode,
|
||||||
|
fmt.Sprintf("failed to upadte use info since account not found: %s", req.UID),
|
||||||
|
)
|
||||||
|
default:
|
||||||
|
e = errs.DatabaseErrorWithScopeL(
|
||||||
|
code.CloudEPMember,
|
||||||
|
domain.FailedToUpdateUserErrorCode,
|
||||||
|
logx.WithContext(ctx),
|
||||||
|
[]logx.LogField{
|
||||||
|
{Key: "req", Value: req},
|
||||||
|
{Key: "func", Value: "User.UpdateUserDetailsByUid"},
|
||||||
|
{Key: "err", Value: err.Error()},
|
||||||
|
},
|
||||||
|
"failed to update user info").Wrap(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return e
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (use *MemberUseCase) UpdateStatus(ctx context.Context, req usecase.UpdateStatusRequest) error {
|
||||||
|
err := use.User.UpdateStatus(ctx, req.UID, req.Status.ToInt32())
|
||||||
|
if err != nil {
|
||||||
|
var e *errs.LibError
|
||||||
|
switch {
|
||||||
|
case errors.Is(err, mon.ErrNotFound):
|
||||||
|
e = errs.ResourceNotFoundWithScope(
|
||||||
|
code.CloudEPMember,
|
||||||
|
domain.FailedToFindUserErrorCode,
|
||||||
|
fmt.Sprintf("failed to upadte use info since account not found: %s", req.UID),
|
||||||
|
)
|
||||||
|
|
||||||
|
default:
|
||||||
|
e = errs.DatabaseErrorWithScopeL(
|
||||||
|
code.CloudEPMember,
|
||||||
|
domain.FailedToUpdateUserStatusErrorCode,
|
||||||
|
logx.WithContext(ctx),
|
||||||
|
[]logx.LogField{
|
||||||
|
{Key: "req", Value: req},
|
||||||
|
{Key: "func", Value: "User.UpdateStatus"},
|
||||||
|
{Key: "err", Value: err.Error()},
|
||||||
|
},
|
||||||
|
"failed to update user info").Wrap(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return e
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (use *MemberUseCase) ListMember(ctx context.Context, req usecase.ListUserInfoRequest) (usecase.ListUserInfoResponse, error) {
|
||||||
|
listMembers, total, err := use.User.ListMembers(ctx, &repository.UserQueryParams{
|
||||||
|
AlarmCategory: req.AlarmCategory,
|
||||||
|
UserStatus: req.UserStatus,
|
||||||
|
CreateStartTime: req.CreateStartTime,
|
||||||
|
CreateEndTime: req.CreateEndTime,
|
||||||
|
PageSize: req.PageSize,
|
||||||
|
PageIndex: req.PageIndex,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
e := errs.DatabaseErrorWithScopeL(
|
||||||
|
code.CloudEPMember,
|
||||||
|
domain.FailedToGetUserInfoErrorCode,
|
||||||
|
logx.WithContext(ctx),
|
||||||
|
[]logx.LogField{
|
||||||
|
{Key: "req", Value: req},
|
||||||
|
{Key: "func", Value: "User.ListMembers"},
|
||||||
|
{Key: "err", Value: err.Error()},
|
||||||
|
},
|
||||||
|
"failed to list members").Wrap(err)
|
||||||
|
|
||||||
|
return usecase.ListUserInfoResponse{}, e
|
||||||
|
}
|
||||||
|
|
||||||
|
var data = make([]usecase.UserInfo, 0, len(listMembers))
|
||||||
|
|
||||||
|
for _, item := range listMembers {
|
||||||
|
data = append(data, usecase.UserInfo{
|
||||||
|
CreateUserInfoRequest: usecase.CreateUserInfoRequest{
|
||||||
|
UID: item.UID,
|
||||||
|
AlarmCategory: item.AlarmCategory,
|
||||||
|
UserStatus: item.UserStatus,
|
||||||
|
PreferredLanguage: item.PreferredLanguage,
|
||||||
|
Currency: item.Currency,
|
||||||
|
Nickname: item.Nickname,
|
||||||
|
AvatarURL: item.AvatarURL, // 按照先前的命名 AvatarURL
|
||||||
|
FullName: item.FullName,
|
||||||
|
GenderCode: item.GenderCode,
|
||||||
|
Birthdate: item.Birthdate,
|
||||||
|
PhoneNumber: item.PhoneNumber,
|
||||||
|
Address: item.Address,
|
||||||
|
Email: item.Email,
|
||||||
|
},
|
||||||
|
CreateTime: GetOriginalInt64(item.CreateAt), // 使用自定義指標轉換函數
|
||||||
|
UpdateTime: GetOriginalInt64(item.UpdateAt),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return usecase.ListUserInfoResponse{
|
||||||
|
Data: data,
|
||||||
|
Page: usecase.Pager{
|
||||||
|
Total: total,
|
||||||
|
Index: req.PageIndex,
|
||||||
|
Size: req.PageSize,
|
||||||
|
},
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetOriginalInt64 取得原始值的函數
|
||||||
|
func GetOriginalInt64(value *int64) int64 {
|
||||||
|
if value == nil {
|
||||||
|
return 0 // 處理 nil 的情況
|
||||||
|
}
|
||||||
|
|
||||||
|
return *value
|
||||||
|
}
|
|
@ -0,0 +1,663 @@
|
||||||
|
package usecase
|
||||||
|
|
||||||
|
import (
|
||||||
|
"app-cloudep-member-server/pkg/domain"
|
||||||
|
"app-cloudep-member-server/pkg/domain/config"
|
||||||
|
"app-cloudep-member-server/pkg/domain/entity"
|
||||||
|
"app-cloudep-member-server/pkg/domain/member"
|
||||||
|
"app-cloudep-member-server/pkg/domain/usecase"
|
||||||
|
mockRepo "app-cloudep-member-server/pkg/mock/repository"
|
||||||
|
"app-cloudep-member-server/pkg/repository"
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"code.30cm.net/digimon/library-go/errs"
|
||||||
|
"code.30cm.net/digimon/library-go/errs/code"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/zeromicro/go-zero/core/stores/mon"
|
||||||
|
"go.uber.org/mock/gomock"
|
||||||
|
"google.golang.org/protobuf/proto"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestCreateUserAccount(t *testing.T) {
|
||||||
|
mockCtrl := gomock.NewController(t)
|
||||||
|
defer mockCtrl.Finish()
|
||||||
|
|
||||||
|
mockAccountRepository := mockRepo.NewMockAccountRepository(mockCtrl)
|
||||||
|
|
||||||
|
uc := MustMemberUseCase(MemberUseCaseParam{
|
||||||
|
Account: mockAccountRepository,
|
||||||
|
Config: config.Config{
|
||||||
|
Bcrypt: struct{ Cost int }{Cost: 10},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
req usecase.CreateLoginUserRequest
|
||||||
|
mockSetup func()
|
||||||
|
expectErr bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "Successful account creation with Digimon platform",
|
||||||
|
req: usecase.CreateLoginUserRequest{
|
||||||
|
LoginID: "testuser",
|
||||||
|
Token: "plaintext-password",
|
||||||
|
Platform: member.Digimon,
|
||||||
|
},
|
||||||
|
mockSetup: func() {
|
||||||
|
mockAccountRepository.EXPECT().Insert(gomock.Any(), gomock.Any()).Return(nil)
|
||||||
|
},
|
||||||
|
expectErr: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Password encryption failure",
|
||||||
|
req: usecase.CreateLoginUserRequest{
|
||||||
|
LoginID: "testuser",
|
||||||
|
Token: "plaintext-password",
|
||||||
|
Platform: member.Digimon,
|
||||||
|
},
|
||||||
|
mockSetup: func() {
|
||||||
|
HasPasswordFunc = func(password string, cost int) (string, error) {
|
||||||
|
return "", errors.New("encryption error")
|
||||||
|
}
|
||||||
|
},
|
||||||
|
expectErr: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Duplicate account insertion error",
|
||||||
|
req: usecase.CreateLoginUserRequest{
|
||||||
|
LoginID: "testuser",
|
||||||
|
Token: "plaintext-password",
|
||||||
|
Platform: member.Digimon,
|
||||||
|
},
|
||||||
|
mockSetup: func() {
|
||||||
|
HasPasswordFunc = func(password string, cost int) (string, error) {
|
||||||
|
return "encrypted-password", nil
|
||||||
|
}
|
||||||
|
mockAccountRepository.EXPECT().Insert(gomock.Any(), gomock.Any()).Return(errors.New("duplicate account"))
|
||||||
|
},
|
||||||
|
expectErr: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Successful account creation with non-Digimon platform",
|
||||||
|
req: usecase.CreateLoginUserRequest{
|
||||||
|
LoginID: "testuser",
|
||||||
|
Platform: member.Google,
|
||||||
|
},
|
||||||
|
mockSetup: func() {
|
||||||
|
mockAccountRepository.EXPECT().Insert(gomock.Any(), gomock.Any()).Return(nil)
|
||||||
|
},
|
||||||
|
expectErr: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
tt.mockSetup()
|
||||||
|
|
||||||
|
err := uc.CreateUserAccount(context.Background(), tt.req)
|
||||||
|
|
||||||
|
if tt.expectErr {
|
||||||
|
assert.Error(t, err)
|
||||||
|
} else {
|
||||||
|
assert.NoError(t, err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetUIDByAccount(t *testing.T) {
|
||||||
|
mockCtrl := gomock.NewController(t)
|
||||||
|
defer mockCtrl.Finish()
|
||||||
|
|
||||||
|
mockAccountUIDRepo := mockRepo.NewMockAccountUIDRepository(mockCtrl)
|
||||||
|
|
||||||
|
uc := MustMemberUseCase(MemberUseCaseParam{
|
||||||
|
AccountUID: mockAccountUIDRepo,
|
||||||
|
Config: config.Config{
|
||||||
|
Bcrypt: struct{ Cost int }{Cost: 10},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
req usecase.GetUIDByAccountRequest
|
||||||
|
mockSetup func()
|
||||||
|
wantResp usecase.GetUIDByAccountResponse
|
||||||
|
wantErr bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "Successfully found UID by account",
|
||||||
|
req: usecase.GetUIDByAccountRequest{
|
||||||
|
Account: "testuser",
|
||||||
|
},
|
||||||
|
mockSetup: func() {
|
||||||
|
mockAccountUIDRepo.EXPECT().
|
||||||
|
FindUIDByLoginID(gomock.Any(), "testuser").
|
||||||
|
Return(&entity.AccountUID{UID: "12345"}, nil)
|
||||||
|
},
|
||||||
|
wantResp: usecase.GetUIDByAccountResponse{
|
||||||
|
UID: "12345",
|
||||||
|
Account: "testuser",
|
||||||
|
},
|
||||||
|
wantErr: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Account not found",
|
||||||
|
req: usecase.GetUIDByAccountRequest{
|
||||||
|
Account: "notfounduser",
|
||||||
|
},
|
||||||
|
mockSetup: func() {
|
||||||
|
mockAccountUIDRepo.EXPECT().
|
||||||
|
FindUIDByLoginID(gomock.Any(), "notfounduser").
|
||||||
|
Return(nil, errs.NewError(
|
||||||
|
code.CloudEPMember,
|
||||||
|
code.CatResource,
|
||||||
|
domain.FailedFindUIDByLoginIDErrorCode,
|
||||||
|
"account not found",
|
||||||
|
))
|
||||||
|
},
|
||||||
|
wantResp: usecase.GetUIDByAccountResponse{},
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Database error",
|
||||||
|
req: usecase.GetUIDByAccountRequest{
|
||||||
|
Account: "erroruser",
|
||||||
|
},
|
||||||
|
mockSetup: func() {
|
||||||
|
mockAccountUIDRepo.EXPECT().
|
||||||
|
FindUIDByLoginID(gomock.Any(), "erroruser").
|
||||||
|
Return(nil, errors.New("database error"))
|
||||||
|
},
|
||||||
|
wantResp: usecase.GetUIDByAccountResponse{},
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
tt.mockSetup()
|
||||||
|
|
||||||
|
resp, err := uc.GetUIDByAccount(context.Background(), tt.req)
|
||||||
|
|
||||||
|
if tt.wantErr {
|
||||||
|
assert.Error(t, err)
|
||||||
|
} else {
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, tt.wantResp, resp)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetUserAccountInfo(t *testing.T) {
|
||||||
|
mockCtrl := gomock.NewController(t)
|
||||||
|
defer mockCtrl.Finish()
|
||||||
|
|
||||||
|
mockAccountRepo := mockRepo.NewMockAccountRepository(mockCtrl)
|
||||||
|
|
||||||
|
uc := MustMemberUseCase(MemberUseCaseParam{
|
||||||
|
Account: mockAccountRepo,
|
||||||
|
Config: config.Config{
|
||||||
|
Bcrypt: struct{ Cost int }{Cost: 10},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
req usecase.GetUIDByAccountRequest
|
||||||
|
mockSetup func()
|
||||||
|
expected usecase.GetAccountInfoResponse
|
||||||
|
wantErr bool
|
||||||
|
errCode string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "Successfully found account",
|
||||||
|
req: usecase.GetUIDByAccountRequest{Account: "testuser"},
|
||||||
|
mockSetup: func() {
|
||||||
|
mockAccountRepo.EXPECT().
|
||||||
|
FindOneByAccount(gomock.Any(), "testuser").
|
||||||
|
Return(&entity.Account{
|
||||||
|
LoginID: "testuser",
|
||||||
|
Platform: 1,
|
||||||
|
Token: "testtoken",
|
||||||
|
}, nil)
|
||||||
|
},
|
||||||
|
expected: usecase.GetAccountInfoResponse{
|
||||||
|
Data: usecase.CreateLoginUserRequest{
|
||||||
|
LoginID: "testuser",
|
||||||
|
Platform: 1,
|
||||||
|
Token: "testtoken",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
wantErr: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Account not found",
|
||||||
|
req: usecase.GetUIDByAccountRequest{Account: "notfounduser"},
|
||||||
|
mockSetup: func() {
|
||||||
|
mockAccountRepo.EXPECT().
|
||||||
|
FindOneByAccount(gomock.Any(), "notfounduser").
|
||||||
|
Return(nil, mon.ErrNotFound)
|
||||||
|
},
|
||||||
|
expected: usecase.GetAccountInfoResponse{},
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Database error",
|
||||||
|
req: usecase.GetUIDByAccountRequest{Account: "erroruser"},
|
||||||
|
mockSetup: func() {
|
||||||
|
mockAccountRepo.EXPECT().
|
||||||
|
FindOneByAccount(gomock.Any(), "erroruser").
|
||||||
|
Return(nil, errors.New("database error"))
|
||||||
|
},
|
||||||
|
expected: usecase.GetAccountInfoResponse{},
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
tt.mockSetup()
|
||||||
|
|
||||||
|
resp, err := uc.GetUserAccountInfo(context.Background(), tt.req)
|
||||||
|
|
||||||
|
if tt.wantErr {
|
||||||
|
assert.Error(t, err)
|
||||||
|
} else {
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, tt.expected, resp)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetUserInfo(t *testing.T) {
|
||||||
|
mockCtrl := gomock.NewController(t)
|
||||||
|
defer mockCtrl.Finish()
|
||||||
|
|
||||||
|
mockUserRepo := mockRepo.NewMockUserRepository(mockCtrl)
|
||||||
|
|
||||||
|
uc := MustMemberUseCase(MemberUseCaseParam{
|
||||||
|
User: mockUserRepo,
|
||||||
|
Config: config.Config{
|
||||||
|
Bcrypt: struct{ Cost int }{Cost: 10},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
req usecase.GetUserInfoRequest
|
||||||
|
mockSetup func()
|
||||||
|
expected usecase.UserInfo
|
||||||
|
wantErr bool
|
||||||
|
errCode string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "Successfully found user by UID",
|
||||||
|
req: usecase.GetUserInfoRequest{UID: "testUID"},
|
||||||
|
mockSetup: func() {
|
||||||
|
mockUserRepo.EXPECT().
|
||||||
|
FindOneByUID(gomock.Any(), "testUID").
|
||||||
|
Return(&entity.User{
|
||||||
|
UID: "testUID",
|
||||||
|
Nickname: proto.String("testNick"),
|
||||||
|
Email: proto.String("test@example.com"),
|
||||||
|
PreferredLanguage: "en",
|
||||||
|
}, nil)
|
||||||
|
},
|
||||||
|
expected: usecase.UserInfo{
|
||||||
|
CreateUserInfoRequest: usecase.CreateUserInfoRequest{
|
||||||
|
UID: "testUID",
|
||||||
|
Nickname: proto.String("testNick"),
|
||||||
|
Email: proto.String("test@example.com"),
|
||||||
|
PreferredLanguage: "en",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
wantErr: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "User not found",
|
||||||
|
req: usecase.GetUserInfoRequest{UID: "nonExistentUID"},
|
||||||
|
mockSetup: func() {
|
||||||
|
mockUserRepo.EXPECT().
|
||||||
|
FindOneByUID(gomock.Any(), "nonExistentUID").
|
||||||
|
Return(nil, mon.ErrNotFound)
|
||||||
|
},
|
||||||
|
expected: usecase.UserInfo{},
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Database error while querying user by UID",
|
||||||
|
req: usecase.GetUserInfoRequest{UID: "errorUID"},
|
||||||
|
mockSetup: func() {
|
||||||
|
mockUserRepo.EXPECT().
|
||||||
|
FindOneByUID(gomock.Any(), "errorUID").
|
||||||
|
Return(nil, errors.New("database error"))
|
||||||
|
},
|
||||||
|
expected: usecase.UserInfo{},
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Successfully found user by Nickname",
|
||||||
|
req: usecase.GetUserInfoRequest{NickName: "testNick"},
|
||||||
|
mockSetup: func() {
|
||||||
|
mockUserRepo.EXPECT().
|
||||||
|
FindOneByNickName(gomock.Any(), "testNick").
|
||||||
|
Return(&entity.User{
|
||||||
|
UID: "testUID",
|
||||||
|
Nickname: proto.String("testNick"),
|
||||||
|
Email: proto.String("test@example.com"),
|
||||||
|
}, nil)
|
||||||
|
},
|
||||||
|
expected: usecase.UserInfo{
|
||||||
|
CreateUserInfoRequest: usecase.CreateUserInfoRequest{
|
||||||
|
UID: "testUID",
|
||||||
|
Nickname: proto.String("testNick"),
|
||||||
|
Email: proto.String("test@example.com"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
wantErr: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Invalid request, no UID or Nickname",
|
||||||
|
req: usecase.GetUserInfoRequest{},
|
||||||
|
mockSetup: func() {
|
||||||
|
// No setup needed for invalid request
|
||||||
|
},
|
||||||
|
expected: usecase.UserInfo{},
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
tt.mockSetup()
|
||||||
|
|
||||||
|
resp, err := uc.GetUserInfo(context.Background(), tt.req)
|
||||||
|
|
||||||
|
if tt.wantErr {
|
||||||
|
assert.Error(t, err)
|
||||||
|
} else {
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, tt.expected, resp)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUpdateUserToken(t *testing.T) {
|
||||||
|
mockCtrl := gomock.NewController(t)
|
||||||
|
defer mockCtrl.Finish()
|
||||||
|
|
||||||
|
mockAccountRepo := mockRepo.NewMockAccountRepository(mockCtrl)
|
||||||
|
uc := MustMemberUseCase(MemberUseCaseParam{
|
||||||
|
Account: mockAccountRepo,
|
||||||
|
})
|
||||||
|
|
||||||
|
HasPasswordFunc = func(password string, cost int) (string, error) {
|
||||||
|
if password == "fail" {
|
||||||
|
return "", errors.New("encryption error")
|
||||||
|
}
|
||||||
|
return "encrypted-password", nil
|
||||||
|
}
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
req usecase.UpdateTokenRequest
|
||||||
|
mockSetup func()
|
||||||
|
wantErr bool
|
||||||
|
errCode string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "Successful token update",
|
||||||
|
req: usecase.UpdateTokenRequest{Account: "testAccount", Token: "newPassword"},
|
||||||
|
mockSetup: func() {
|
||||||
|
mockAccountRepo.EXPECT().
|
||||||
|
UpdateTokenByLoginID(gomock.Any(), "testAccount", "encrypted-password").
|
||||||
|
Return(nil)
|
||||||
|
},
|
||||||
|
wantErr: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Password encryption failure",
|
||||||
|
req: usecase.UpdateTokenRequest{Account: "testAccount", Token: "fail"},
|
||||||
|
mockSetup: func() {
|
||||||
|
// No repo call expected
|
||||||
|
},
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Account not found",
|
||||||
|
req: usecase.UpdateTokenRequest{Account: "nonExistentAccount", Token: "newPassword"},
|
||||||
|
mockSetup: func() {
|
||||||
|
mockAccountRepo.EXPECT().
|
||||||
|
UpdateTokenByLoginID(gomock.Any(), "nonExistentAccount", "encrypted-password").
|
||||||
|
Return(mon.ErrNotFound)
|
||||||
|
},
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Database error during token update",
|
||||||
|
req: usecase.UpdateTokenRequest{Account: "errorAccount", Token: "newPassword"},
|
||||||
|
mockSetup: func() {
|
||||||
|
mockAccountRepo.EXPECT().
|
||||||
|
UpdateTokenByLoginID(gomock.Any(), "errorAccount", "encrypted-password").
|
||||||
|
Return(errors.New("database error"))
|
||||||
|
},
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
tt.mockSetup()
|
||||||
|
|
||||||
|
err := uc.UpdateUserToken(context.Background(), tt.req)
|
||||||
|
|
||||||
|
if tt.wantErr {
|
||||||
|
assert.Error(t, err)
|
||||||
|
} else {
|
||||||
|
assert.NoError(t, err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMemberUseCase_UpdateUserInfo(t *testing.T) {
|
||||||
|
mockCtrl := gomock.NewController(t)
|
||||||
|
defer mockCtrl.Finish()
|
||||||
|
|
||||||
|
mockUserRepo := mockRepo.NewMockUserRepository(mockCtrl)
|
||||||
|
uc := MustMemberUseCase(MemberUseCaseParam{
|
||||||
|
User: mockUserRepo,
|
||||||
|
})
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
req *usecase.UpdateUserInfoRequest
|
||||||
|
mockSetup func()
|
||||||
|
wantErr bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "Successful update",
|
||||||
|
req: &usecase.UpdateUserInfoRequest{
|
||||||
|
UID: "testUID",
|
||||||
|
Nickname: proto.String("UpdatedNick"),
|
||||||
|
FullName: proto.String("Updated Name"),
|
||||||
|
AvatarURL: proto.String("http://example.com/avatar.png"),
|
||||||
|
},
|
||||||
|
mockSetup: func() {
|
||||||
|
mockUserRepo.EXPECT().
|
||||||
|
UpdateUserDetailsByUID(gomock.Any(), gomock.Any()).
|
||||||
|
Return(nil)
|
||||||
|
},
|
||||||
|
wantErr: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "User not found",
|
||||||
|
req: &usecase.UpdateUserInfoRequest{
|
||||||
|
UID: "nonExistentUID",
|
||||||
|
},
|
||||||
|
mockSetup: func() {
|
||||||
|
mockUserRepo.EXPECT().
|
||||||
|
UpdateUserDetailsByUID(gomock.Any(), gomock.Any()).
|
||||||
|
Return(repository.ErrNotFound)
|
||||||
|
},
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Database error",
|
||||||
|
req: &usecase.UpdateUserInfoRequest{
|
||||||
|
UID: "errorUID",
|
||||||
|
},
|
||||||
|
mockSetup: func() {
|
||||||
|
mockUserRepo.EXPECT().
|
||||||
|
UpdateUserDetailsByUID(gomock.Any(), gomock.Any()).
|
||||||
|
Return(errors.New("database error"))
|
||||||
|
},
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
tt.mockSetup()
|
||||||
|
|
||||||
|
err := uc.UpdateUserInfo(context.Background(), tt.req)
|
||||||
|
|
||||||
|
if tt.wantErr {
|
||||||
|
assert.Error(t, err)
|
||||||
|
} else {
|
||||||
|
assert.NoError(t, err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMemberUseCase_UpdateStatus(t *testing.T) {
|
||||||
|
mockCtrl := gomock.NewController(t)
|
||||||
|
defer mockCtrl.Finish()
|
||||||
|
|
||||||
|
mockUserRepo := mockRepo.NewMockUserRepository(mockCtrl)
|
||||||
|
uc := MustMemberUseCase(MemberUseCaseParam{
|
||||||
|
User: mockUserRepo,
|
||||||
|
})
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
req usecase.UpdateStatusRequest
|
||||||
|
mockSetup func()
|
||||||
|
wantErr bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "Successful status update",
|
||||||
|
req: usecase.UpdateStatusRequest{UID: "testUID", Status: member.AccountStatusActive},
|
||||||
|
mockSetup: func() {
|
||||||
|
mockUserRepo.EXPECT().
|
||||||
|
UpdateStatus(gomock.Any(), "testUID", gomock.Any()).
|
||||||
|
Return(nil)
|
||||||
|
},
|
||||||
|
wantErr: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "User not found",
|
||||||
|
req: usecase.UpdateStatusRequest{UID: "nonExistentUID", Status: member.AccountStatusActive},
|
||||||
|
mockSetup: func() {
|
||||||
|
mockUserRepo.EXPECT().
|
||||||
|
UpdateStatus(gomock.Any(), "nonExistentUID", gomock.Any()).
|
||||||
|
Return(repository.ErrNotFound)
|
||||||
|
},
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Database error",
|
||||||
|
req: usecase.UpdateStatusRequest{UID: "errorUID", Status: member.AccountStatusUninitialized},
|
||||||
|
mockSetup: func() {
|
||||||
|
mockUserRepo.EXPECT().
|
||||||
|
UpdateStatus(gomock.Any(), "errorUID", gomock.Any()).
|
||||||
|
Return(errors.New("database error"))
|
||||||
|
},
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
tt.mockSetup()
|
||||||
|
|
||||||
|
err := uc.UpdateStatus(context.Background(), tt.req)
|
||||||
|
|
||||||
|
if tt.wantErr {
|
||||||
|
assert.Error(t, err)
|
||||||
|
} else {
|
||||||
|
assert.NoError(t, err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMemberUseCase_ListMember(t *testing.T) {
|
||||||
|
mockCtrl := gomock.NewController(t)
|
||||||
|
defer mockCtrl.Finish()
|
||||||
|
|
||||||
|
mockUserRepo := mockRepo.NewMockUserRepository(mockCtrl)
|
||||||
|
uc := MustMemberUseCase(MemberUseCaseParam{
|
||||||
|
User: mockUserRepo,
|
||||||
|
})
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
req usecase.ListUserInfoRequest
|
||||||
|
mockSetup func()
|
||||||
|
wantErr bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "Successful member listing",
|
||||||
|
req: usecase.ListUserInfoRequest{
|
||||||
|
PageSize: 10,
|
||||||
|
PageIndex: 1,
|
||||||
|
},
|
||||||
|
mockSetup: func() {
|
||||||
|
mockUserRepo.EXPECT().
|
||||||
|
ListMembers(gomock.Any(), gomock.Any()).
|
||||||
|
Return([]*entity.User{
|
||||||
|
{UID: "testUID1"},
|
||||||
|
{UID: "testUID2"},
|
||||||
|
}, int64(2), nil)
|
||||||
|
},
|
||||||
|
wantErr: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Database error",
|
||||||
|
req: usecase.ListUserInfoRequest{
|
||||||
|
PageSize: 10,
|
||||||
|
PageIndex: 1,
|
||||||
|
},
|
||||||
|
mockSetup: func() {
|
||||||
|
mockUserRepo.EXPECT().
|
||||||
|
ListMembers(gomock.Any(), gomock.Any()).
|
||||||
|
Return(nil, int64(0), errors.New("database error"))
|
||||||
|
},
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
tt.mockSetup()
|
||||||
|
|
||||||
|
resp, err := uc.ListMember(context.Background(), tt.req)
|
||||||
|
|
||||||
|
if tt.wantErr {
|
||||||
|
assert.Error(t, err)
|
||||||
|
assert.Empty(t, resp.Data)
|
||||||
|
} else {
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.NotEmpty(t, resp.Data)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,21 @@
|
||||||
|
package usecase
|
||||||
|
|
||||||
|
import "golang.org/x/crypto/bcrypt"
|
||||||
|
|
||||||
|
func HashPassword(password string, cost int) (string, error) {
|
||||||
|
bytes, err := bcrypt.GenerateFromPassword([]byte(password), cost)
|
||||||
|
|
||||||
|
return string(bytes), err
|
||||||
|
}
|
||||||
|
|
||||||
|
func CheckPasswordHash(password, hash string) bool {
|
||||||
|
err := bcrypt.CompareHashAndPassword([]byte(hash), []byte(password))
|
||||||
|
|
||||||
|
return err == nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetHashingCost(hashedPassword []byte) int {
|
||||||
|
cost, _ := bcrypt.Cost(hashedPassword)
|
||||||
|
|
||||||
|
return cost
|
||||||
|
}
|
|
@ -0,0 +1,50 @@
|
||||||
|
package usecase
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"golang.org/x/crypto/bcrypt"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestHashPassword(t *testing.T) {
|
||||||
|
password := "securepassword123"
|
||||||
|
cost := bcrypt.DefaultCost
|
||||||
|
|
||||||
|
hashedPassword, err := HashPassword(password, cost)
|
||||||
|
|
||||||
|
assert.NoError(t, err, "生成哈希密碼應該成功")
|
||||||
|
assert.NotEmpty(t, hashedPassword, "生成的哈希密碼不應為空")
|
||||||
|
|
||||||
|
// 確認哈希密碼長度符合預期
|
||||||
|
assert.Greater(t, len(hashedPassword), 0, "哈希密碼長度應大於零")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCheckPasswordHash(t *testing.T) {
|
||||||
|
password := "securepassword123"
|
||||||
|
cost := bcrypt.DefaultCost
|
||||||
|
|
||||||
|
// 生成哈希密碼
|
||||||
|
hashedPassword, err := HashPassword(password, cost)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
// 驗證密碼與哈希是否匹配
|
||||||
|
assert.True(t, CheckPasswordHash(password, hashedPassword), "密碼應與哈希匹配")
|
||||||
|
|
||||||
|
// 測試不匹配的密碼
|
||||||
|
wrongPassword := "wrongpassword"
|
||||||
|
assert.False(t, CheckPasswordHash(wrongPassword, hashedPassword), "錯誤密碼不應與哈希匹配")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetHashingCost(t *testing.T) {
|
||||||
|
password := "securepassword123"
|
||||||
|
cost := bcrypt.DefaultCost
|
||||||
|
|
||||||
|
// 生成哈希密碼
|
||||||
|
hashedPassword, err := HashPassword(password, cost)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
// 驗證哈希成本
|
||||||
|
actualCost := GetHashingCost([]byte(hashedPassword))
|
||||||
|
assert.Equal(t, cost, actualCost, "哈希成本應與生成時的一致")
|
||||||
|
}
|
|
@ -0,0 +1,119 @@
|
||||||
|
package usecase
|
||||||
|
|
||||||
|
import (
|
||||||
|
"app-cloudep-member-server/pkg/domain"
|
||||||
|
"app-cloudep-member-server/pkg/domain/member"
|
||||||
|
"app-cloudep-member-server/pkg/domain/usecase"
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"code.30cm.net/digimon/library-go/errs"
|
||||||
|
"code.30cm.net/digimon/library-go/errs/code"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (use *MemberUseCase) GenerateRefreshCode(ctx context.Context, param usecase.GenerateRefreshCodeRequest) (usecase.GenerateRefreshCodeResponse, error) {
|
||||||
|
checkType, status := member.GetCodeNameByCode(param.CodeType)
|
||||||
|
if !status {
|
||||||
|
e := errs.ResourceNotFoundWithScope(
|
||||||
|
code.CloudEPMember,
|
||||||
|
domain.FailedToGetVerifyCodeErrorCode,
|
||||||
|
fmt.Sprintf("failed to get verify code type: %d", param.CodeType),
|
||||||
|
)
|
||||||
|
|
||||||
|
return usecase.GenerateRefreshCodeResponse{}, e
|
||||||
|
}
|
||||||
|
|
||||||
|
vc, err := use.VerifyCodeModel.IsVerifyCodeExist(ctx, param.LoginID, checkType)
|
||||||
|
if err != nil {
|
||||||
|
return usecase.GenerateRefreshCodeResponse{}, err
|
||||||
|
}
|
||||||
|
// 找不到,故要產生
|
||||||
|
if vc == "" {
|
||||||
|
vc, err = generateVerifyCode(6)
|
||||||
|
if err != nil {
|
||||||
|
return usecase.GenerateRefreshCodeResponse{}, errs.SystemInternalError(err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
err = use.VerifyCodeModel.SetVerifyCode(ctx, param.LoginID, checkType, vc)
|
||||||
|
if err != nil {
|
||||||
|
return usecase.GenerateRefreshCodeResponse{},
|
||||||
|
errs.DatabaseErrorWithScope(
|
||||||
|
code.CloudEPMember,
|
||||||
|
domain.FailedToGetCodeOnRedisErrorCode,
|
||||||
|
"failed to set verify code",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return usecase.GenerateRefreshCodeResponse{
|
||||||
|
Data: usecase.VerifyCode{
|
||||||
|
VerifyCode: vc,
|
||||||
|
},
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (use *MemberUseCase) VerifyRefreshCode(ctx context.Context, param usecase.VerifyRefreshCodeRequest) error {
|
||||||
|
err := use.CheckRefreshCode(ctx, param)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// 因為 CheckRefreshCde 中也有驗證一次,這次就讓他不報錯直接過,因為如果有錯誤,上面那個就會報錯了
|
||||||
|
checkType, _ := member.GetCodeNameByCode(param.CodeType)
|
||||||
|
// todo: 刪不掉看要用什麼方法補刪除,而不是報錯
|
||||||
|
_ = use.VerifyCodeModel.DelVerifyCode(ctx, param.LoginID, checkType)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (use *MemberUseCase) CheckRefreshCode(ctx context.Context, param usecase.VerifyRefreshCodeRequest) error {
|
||||||
|
checkType, status := member.GetCodeNameByCode(param.CodeType)
|
||||||
|
if !status {
|
||||||
|
return errs.ResourceNotFoundWithScope(
|
||||||
|
code.CloudEPMember,
|
||||||
|
domain.FailedToGetVerifyCodeErrorCode,
|
||||||
|
fmt.Sprintf("failed to get verify code type: %d", param.CodeType),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
get, err := use.VerifyCodeModel.IsVerifyCodeExist(ctx, param.LoginID, checkType)
|
||||||
|
if err != nil {
|
||||||
|
return errs.DatabaseErrorWithScope(
|
||||||
|
code.CloudEPMember,
|
||||||
|
domain.FailedToGetCodeOnRedisErrorCode,
|
||||||
|
"failed to set verify code",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if get == "" {
|
||||||
|
return errs.ResourceNotFoundWithScope(
|
||||||
|
code.CloudEPMember,
|
||||||
|
domain.FailedToGetCodeOnRedisErrorCode,
|
||||||
|
"failed to get data",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if get != param.VerifyCode {
|
||||||
|
return errs.ForbiddenWithScope(
|
||||||
|
code.CloudEPMember,
|
||||||
|
domain.FailedToGetCodeCorrectErrorCode,
|
||||||
|
"failed to verify code",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (use *MemberUseCase) VerifyPlatformAuthResult(ctx context.Context, param usecase.VerifyAuthResultRequest) (usecase.VerifyAuthResultResponse, error) {
|
||||||
|
account, err := use.Account.FindOneByAccount(ctx, param.Account)
|
||||||
|
if err != nil {
|
||||||
|
return usecase.VerifyAuthResultResponse{}, errs.ResourceNotFoundWithScope(
|
||||||
|
code.CloudEPMember,
|
||||||
|
domain.FailedToFindAccountErrorCode,
|
||||||
|
fmt.Sprintf("failed to find account: %s", param.Account),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return usecase.VerifyAuthResultResponse{
|
||||||
|
Status: CheckPasswordHash(param.Token, account.Token),
|
||||||
|
}, nil
|
||||||
|
}
|
|
@ -0,0 +1,128 @@
|
||||||
|
package usecase
|
||||||
|
|
||||||
|
import (
|
||||||
|
"app-cloudep-member-server/pkg/domain"
|
||||||
|
"app-cloudep-member-server/pkg/domain/usecase"
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"strconv"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"code.30cm.net/digimon/library-go/errs"
|
||||||
|
"code.30cm.net/digimon/library-go/errs/code"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (use *MemberUseCase) VerifyGoogleAuthResult(ctx context.Context, req usecase.VerifyAuthResultRequest) (usecase.GoogleTokenInfo, error) {
|
||||||
|
var tokenInfo usecase.GoogleTokenInfo
|
||||||
|
// 發送 Google Token Info API 請求
|
||||||
|
body, err := fetchGoogleTokenInfo(ctx, req.Token)
|
||||||
|
if err != nil {
|
||||||
|
return tokenInfo, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// 解析返回的 JSON 數據
|
||||||
|
if err := json.Unmarshal(body, &tokenInfo); err != nil {
|
||||||
|
return tokenInfo, errs.ThirdPartyError(
|
||||||
|
code.CloudEPMember,
|
||||||
|
domain.FailedToVerifyGoogle,
|
||||||
|
"failed to parse token info",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 驗證 Token 資訊
|
||||||
|
if err := validateGoogleTokenInfo(tokenInfo, use.Config.GoogleAuth.ClientID); err != nil {
|
||||||
|
return tokenInfo, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return tokenInfo, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// fetchGoogleTokenInfo 發送 Google TokenInfo API 請求並返回響應內容
|
||||||
|
func fetchGoogleTokenInfo(ctx context.Context, token string) ([]byte, error) {
|
||||||
|
uri := fmt.Sprintf("https://oauth2.googleapis.com/tokeninfo?id_token=%s", token)
|
||||||
|
|
||||||
|
// 發送請求
|
||||||
|
r, err := http.NewRequestWithContext(ctx, http.MethodGet, uri, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errs.ThirdPartyError(
|
||||||
|
code.CloudEPMember,
|
||||||
|
domain.FailedToVerifyGoogle,
|
||||||
|
"failed to create request", err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := http.DefaultClient.Do(r)
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(ctx.Err(), context.DeadlineExceeded) {
|
||||||
|
return nil, errs.ThirdPartyError(
|
||||||
|
code.CloudEPMember,
|
||||||
|
domain.FailedToVerifyGoogleTimeout,
|
||||||
|
"request timeout",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, errs.ThirdPartyError(
|
||||||
|
code.CloudEPMember,
|
||||||
|
domain.FailedToVerifyGoogleTimeout,
|
||||||
|
"failed to request Google TokenInfo API",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
// 檢查返回的 HTTP 狀態碼
|
||||||
|
if resp.StatusCode != http.StatusOK {
|
||||||
|
return nil, errs.ThirdPartyError(
|
||||||
|
code.CloudEPMember,
|
||||||
|
domain.FailedToVerifyGoogleHTTPCode,
|
||||||
|
fmt.Sprintf("unexpected status code: %d", resp.StatusCode),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 讀取響應內容
|
||||||
|
body, err := io.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errs.ThirdPartyError(
|
||||||
|
code.CloudEPMember,
|
||||||
|
domain.FailedToVerifyGoogle,
|
||||||
|
"failed to read response body",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return body, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// validateGoogleTokenInfo 驗證 Google Token 資訊
|
||||||
|
func validateGoogleTokenInfo(tokenInfo usecase.GoogleTokenInfo, expectedClientID string) error {
|
||||||
|
// **驗證 1: Token 是否過期**
|
||||||
|
expiration, err := strconv.ParseInt(tokenInfo.Exp, 10, 64)
|
||||||
|
if err != nil || expiration <= time.Now().UTC().Unix() {
|
||||||
|
return errs.ThirdPartyError(
|
||||||
|
code.CloudEPMember,
|
||||||
|
domain.FailedToVerifyGoogleTokenExpired,
|
||||||
|
"token is expired",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// **驗證 2: Audience (aud) 是否與 Google Client ID 匹配**
|
||||||
|
if tokenInfo.Aud != expectedClientID {
|
||||||
|
return errs.ThirdPartyError(
|
||||||
|
code.CloudEPMember,
|
||||||
|
domain.FailedToVerifyGoogleInvalidAudience,
|
||||||
|
"invalid audience",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// **驗證 3: 是否 email 已驗證**
|
||||||
|
if tokenInfo.EmailVerified == "false" {
|
||||||
|
return errs.ThirdPartyError(
|
||||||
|
code.CloudEPMember,
|
||||||
|
domain.FailedToVerifyGoogle,
|
||||||
|
"email is not verified",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -0,0 +1,84 @@
|
||||||
|
package usecase
|
||||||
|
|
||||||
|
import (
|
||||||
|
"app-cloudep-member-server/pkg/domain"
|
||||||
|
"app-cloudep-member-server/pkg/domain/usecase"
|
||||||
|
"code.30cm.net/digimon/library-go/errs"
|
||||||
|
"code.30cm.net/digimon/library-go/errs/code"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"strconv"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestValidateGoogleTokenInfo(t *testing.T) {
|
||||||
|
expectedClientID := "test-client-id"
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
tokenInfo usecase.GoogleTokenInfo
|
||||||
|
expectedErr error
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "Valid token",
|
||||||
|
tokenInfo: usecase.GoogleTokenInfo{
|
||||||
|
Exp: strconv.FormatInt(time.Now().UTC().Add(10*time.Minute).Unix(), 10),
|
||||||
|
Aud: expectedClientID,
|
||||||
|
EmailVerified: "true",
|
||||||
|
},
|
||||||
|
expectedErr: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Token expired",
|
||||||
|
tokenInfo: usecase.GoogleTokenInfo{
|
||||||
|
Exp: strconv.FormatInt(time.Now().UTC().Add(-10*time.Minute).Unix(), 10),
|
||||||
|
Aud: expectedClientID,
|
||||||
|
EmailVerified: "true",
|
||||||
|
},
|
||||||
|
expectedErr: errs.ThirdPartyError(
|
||||||
|
code.CloudEPMember,
|
||||||
|
domain.FailedToVerifyGoogleTokenExpired,
|
||||||
|
"token is expired",
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Invalid audience",
|
||||||
|
tokenInfo: usecase.GoogleTokenInfo{
|
||||||
|
Exp: strconv.FormatInt(time.Now().UTC().Add(10*time.Minute).Unix(), 10),
|
||||||
|
Aud: "invalid-client-id",
|
||||||
|
EmailVerified: "true",
|
||||||
|
},
|
||||||
|
expectedErr: errs.ThirdPartyError(
|
||||||
|
code.CloudEPMember,
|
||||||
|
domain.FailedToVerifyGoogleInvalidAudience,
|
||||||
|
"invalid audience",
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Email not verified",
|
||||||
|
tokenInfo: usecase.GoogleTokenInfo{
|
||||||
|
Exp: strconv.FormatInt(time.Now().UTC().Add(10*time.Minute).Unix(), 10),
|
||||||
|
Aud: expectedClientID,
|
||||||
|
EmailVerified: "false",
|
||||||
|
},
|
||||||
|
expectedErr: errs.ThirdPartyError(
|
||||||
|
code.CloudEPMember,
|
||||||
|
domain.FailedToVerifyGoogle,
|
||||||
|
"email is not verified",
|
||||||
|
),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
t.Run(test.name, func(t *testing.T) {
|
||||||
|
err := validateGoogleTokenInfo(test.tokenInfo, expectedClientID)
|
||||||
|
|
||||||
|
if test.expectedErr != nil {
|
||||||
|
assert.NotNil(t, err)
|
||||||
|
assert.EqualError(t, err, test.expectedErr.Error())
|
||||||
|
} else {
|
||||||
|
assert.NoError(t, err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,128 @@
|
||||||
|
package usecase
|
||||||
|
|
||||||
|
import (
|
||||||
|
"app-cloudep-member-server/pkg/domain"
|
||||||
|
"app-cloudep-member-server/pkg/domain/usecase"
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
|
||||||
|
"code.30cm.net/digimon/library-go/errs"
|
||||||
|
"code.30cm.net/digimon/library-go/errs/code"
|
||||||
|
)
|
||||||
|
|
||||||
|
// LineCodeToAccessToken 透過 Line 授權碼換取 Access Token
|
||||||
|
func (use *MemberUseCase) LineCodeToAccessToken(ctx context.Context, code string) (usecase.LineAccessTokenResponse, error) {
|
||||||
|
data := url.Values{
|
||||||
|
"grant_type": {"authorization_code"},
|
||||||
|
"code": {code},
|
||||||
|
"redirect_uri": {use.Config.LineAuth.RedirectURI},
|
||||||
|
"client_id": {use.Config.LineAuth.ClientID},
|
||||||
|
"client_secret": {use.Config.LineAuth.ClientSecret},
|
||||||
|
}
|
||||||
|
|
||||||
|
uri := "https://api.line.me/oauth2/v2.1/token"
|
||||||
|
headers := map[string]string{
|
||||||
|
"Content-Type": "application/x-www-form-urlencoded",
|
||||||
|
}
|
||||||
|
|
||||||
|
var tokenResponse usecase.LineAccessTokenResponse
|
||||||
|
if err := use.doPost(ctx, uri, headers, data.Encode(), &tokenResponse); err != nil {
|
||||||
|
return usecase.LineAccessTokenResponse{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return tokenResponse, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// LineGetProfileByAccessToken 使用 Access Token 獲取 Line 用戶資料
|
||||||
|
func (use *MemberUseCase) LineGetProfileByAccessToken(ctx context.Context, accessToken string) (*usecase.LineUserProfile, error) {
|
||||||
|
uri := "https://api.line.me/v2/profile"
|
||||||
|
headers := map[string]string{
|
||||||
|
"Authorization": "Bearer " + accessToken,
|
||||||
|
}
|
||||||
|
|
||||||
|
var profile usecase.LineUserProfile
|
||||||
|
if err := use.doGet(ctx, uri, headers, &profile); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &profile, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// doPost 發送 POST 請求並解析返回數據
|
||||||
|
func (use *MemberUseCase) doPost(ctx context.Context, uri string, headers map[string]string, body string, result interface{}) error {
|
||||||
|
req, err := http.NewRequestWithContext(ctx, http.MethodPost, uri, bytes.NewBufferString(body))
|
||||||
|
if err != nil {
|
||||||
|
return errs.ThirdPartyError(
|
||||||
|
code.CloudEPMember,
|
||||||
|
domain.FailedToVerifyLine,
|
||||||
|
"failed to create request",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
for key, value := range headers {
|
||||||
|
req.Header.Set(key, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
return use.doRequest(req, result)
|
||||||
|
}
|
||||||
|
|
||||||
|
// doGet 發送 GET 請求並解析返回數據
|
||||||
|
func (use *MemberUseCase) doGet(ctx context.Context, uri string, headers map[string]string, result interface{}) error {
|
||||||
|
req, err := http.NewRequestWithContext(ctx, http.MethodGet, uri, nil)
|
||||||
|
if err != nil {
|
||||||
|
return errs.ThirdPartyError(
|
||||||
|
code.CloudEPMember,
|
||||||
|
domain.FailedToVerifyLine,
|
||||||
|
"failed to create request",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
for key, value := range headers {
|
||||||
|
req.Header.Set(key, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
return use.doRequest(req, result)
|
||||||
|
}
|
||||||
|
|
||||||
|
// doRequest 發送 HTTP 請求並處理響應
|
||||||
|
func (use *MemberUseCase) doRequest(req *http.Request, result interface{}) error {
|
||||||
|
client := &http.Client{}
|
||||||
|
resp, err := client.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return errs.ThirdPartyError(
|
||||||
|
code.CloudEPMember,
|
||||||
|
domain.FailedToVerifyLine,
|
||||||
|
"failed to send request",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
if resp.StatusCode != http.StatusOK {
|
||||||
|
return errs.ThirdPartyError(
|
||||||
|
code.CloudEPMember,
|
||||||
|
domain.FailedToVerifyLine,
|
||||||
|
"unexpected status code: "+http.StatusText(resp.StatusCode),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
body, err := io.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return errs.ThirdPartyError(
|
||||||
|
code.CloudEPMember,
|
||||||
|
domain.FailedToVerifyLine,
|
||||||
|
"failed to read response body",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := json.Unmarshal(body, result); err != nil {
|
||||||
|
return errs.ThirdPartyError(
|
||||||
|
code.CloudEPMember,
|
||||||
|
domain.FailedToVerifyLine,
|
||||||
|
"failed to parse response body",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -0,0 +1,276 @@
|
||||||
|
package usecase
|
||||||
|
|
||||||
|
import (
|
||||||
|
"app-cloudep-member-server/pkg/domain/entity"
|
||||||
|
"app-cloudep-member-server/pkg/domain/member"
|
||||||
|
"app-cloudep-member-server/pkg/domain/usecase"
|
||||||
|
mockRepo "app-cloudep-member-server/pkg/mock/repository"
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"code.30cm.net/digimon/library-go/errs"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"go.uber.org/mock/gomock"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestGenerateRefreshCode(t *testing.T) {
|
||||||
|
mockCtrl := gomock.NewController(t)
|
||||||
|
defer mockCtrl.Finish()
|
||||||
|
|
||||||
|
mockVerifyCodeModel := mockRepo.NewMockVerifyCodeRepository(mockCtrl)
|
||||||
|
uc := MustMemberUseCase(MemberUseCaseParam{
|
||||||
|
VerifyCodeModel: mockVerifyCodeModel,
|
||||||
|
})
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
param usecase.GenerateRefreshCodeRequest
|
||||||
|
mockSetup func()
|
||||||
|
wantErr bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "Successful code generation",
|
||||||
|
param: usecase.GenerateRefreshCodeRequest{
|
||||||
|
LoginID: "testLoginID",
|
||||||
|
CodeType: member.GenerateCodeTypeEmail,
|
||||||
|
},
|
||||||
|
mockSetup: func() {
|
||||||
|
mockVerifyCodeModel.EXPECT().IsVerifyCodeExist(gomock.Any(), "testLoginID", "email").Return("", nil)
|
||||||
|
mockVerifyCodeModel.EXPECT().SetVerifyCode(gomock.Any(), "testLoginID", "email", gomock.Any()).Return(nil)
|
||||||
|
},
|
||||||
|
wantErr: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Code type not found",
|
||||||
|
param: usecase.GenerateRefreshCodeRequest{
|
||||||
|
LoginID: "testLoginID",
|
||||||
|
CodeType: -999, // Invalid code type
|
||||||
|
},
|
||||||
|
mockSetup: func() {},
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Existing code retrieval",
|
||||||
|
param: usecase.GenerateRefreshCodeRequest{
|
||||||
|
LoginID: "testLoginID",
|
||||||
|
CodeType: member.GenerateCodeTypeEmail,
|
||||||
|
},
|
||||||
|
mockSetup: func() {
|
||||||
|
mockVerifyCodeModel.EXPECT().IsVerifyCodeExist(gomock.Any(), "testLoginID", "email").Return("123456", nil)
|
||||||
|
},
|
||||||
|
wantErr: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Set verify code failure",
|
||||||
|
param: usecase.GenerateRefreshCodeRequest{
|
||||||
|
LoginID: "testLoginID",
|
||||||
|
CodeType: member.GenerateCodeTypeEmail,
|
||||||
|
},
|
||||||
|
mockSetup: func() {
|
||||||
|
mockVerifyCodeModel.EXPECT().IsVerifyCodeExist(gomock.Any(), "testLoginID", "email").Return("", nil)
|
||||||
|
mockVerifyCodeModel.EXPECT().SetVerifyCode(gomock.Any(), "testLoginID", "email", gomock.Any()).Return(errors.New("redis error"))
|
||||||
|
},
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
tt.mockSetup()
|
||||||
|
|
||||||
|
resp, err := uc.GenerateRefreshCode(context.Background(), tt.param)
|
||||||
|
|
||||||
|
if tt.wantErr {
|
||||||
|
assert.Error(t, err)
|
||||||
|
assert.Empty(t, resp)
|
||||||
|
} else {
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.NotEmpty(t, resp.Data.VerifyCode)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCheckRefreshCode(t *testing.T) {
|
||||||
|
mockCtrl := gomock.NewController(t)
|
||||||
|
defer mockCtrl.Finish()
|
||||||
|
|
||||||
|
mockVerifyCodeModel := mockRepo.NewMockVerifyCodeRepository(mockCtrl)
|
||||||
|
uc := MustMemberUseCase(MemberUseCaseParam{
|
||||||
|
VerifyCodeModel: mockVerifyCodeModel,
|
||||||
|
})
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
param usecase.VerifyRefreshCodeRequest
|
||||||
|
mockSetup func()
|
||||||
|
wantErr bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "Successful verification",
|
||||||
|
param: usecase.VerifyRefreshCodeRequest{
|
||||||
|
LoginID: "testLoginID",
|
||||||
|
CodeType: member.GenerateCodeTypeEmail,
|
||||||
|
VerifyCode: "123456",
|
||||||
|
},
|
||||||
|
mockSetup: func() {
|
||||||
|
mockVerifyCodeModel.EXPECT().IsVerifyCodeExist(gomock.Any(), "testLoginID", "email").Return("123456", nil)
|
||||||
|
},
|
||||||
|
wantErr: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Code type not found",
|
||||||
|
param: usecase.VerifyRefreshCodeRequest{
|
||||||
|
LoginID: "testLoginID",
|
||||||
|
CodeType: -1, // Invalid CodeType
|
||||||
|
VerifyCode: "123456",
|
||||||
|
},
|
||||||
|
mockSetup: func() {},
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Code not found in Redis",
|
||||||
|
param: usecase.VerifyRefreshCodeRequest{
|
||||||
|
LoginID: "testLoginID",
|
||||||
|
CodeType: member.GenerateCodeTypeEmail,
|
||||||
|
VerifyCode: "123456",
|
||||||
|
},
|
||||||
|
mockSetup: func() {
|
||||||
|
mockVerifyCodeModel.EXPECT().IsVerifyCodeExist(gomock.Any(), "testLoginID", "email").Return("", nil)
|
||||||
|
},
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Verification code mismatch",
|
||||||
|
param: usecase.VerifyRefreshCodeRequest{
|
||||||
|
LoginID: "testLoginID",
|
||||||
|
CodeType: member.GenerateCodeTypeEmail,
|
||||||
|
VerifyCode: "654321", // Mismatch
|
||||||
|
},
|
||||||
|
mockSetup: func() {
|
||||||
|
mockVerifyCodeModel.EXPECT().IsVerifyCodeExist(gomock.Any(), "testLoginID", "email").Return("123456", nil)
|
||||||
|
},
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Redis retrieval error",
|
||||||
|
param: usecase.VerifyRefreshCodeRequest{
|
||||||
|
LoginID: "testLoginID",
|
||||||
|
CodeType: member.GenerateCodeTypeEmail,
|
||||||
|
VerifyCode: "123456",
|
||||||
|
},
|
||||||
|
mockSetup: func() {
|
||||||
|
mockVerifyCodeModel.EXPECT().IsVerifyCodeExist(gomock.Any(), "testLoginID", "email").Return("", errors.New("redis error"))
|
||||||
|
},
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
tt.mockSetup()
|
||||||
|
|
||||||
|
err := uc.CheckRefreshCode(context.Background(), tt.param)
|
||||||
|
|
||||||
|
if tt.wantErr {
|
||||||
|
assert.Error(t, err)
|
||||||
|
} else {
|
||||||
|
assert.NoError(t, err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestVerifyPlatformAuthResult(t *testing.T) {
|
||||||
|
mockCtrl := gomock.NewController(t)
|
||||||
|
defer mockCtrl.Finish()
|
||||||
|
|
||||||
|
mockAccountRepo := mockRepo.NewMockAccountRepository(mockCtrl)
|
||||||
|
uc := MustMemberUseCase(MemberUseCaseParam{
|
||||||
|
Account: mockAccountRepo,
|
||||||
|
})
|
||||||
|
token, err := HashPassword("password", 10)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
fmt.Println(token)
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
param usecase.VerifyAuthResultRequest
|
||||||
|
mockSetup func()
|
||||||
|
wantResp usecase.VerifyAuthResultResponse
|
||||||
|
wantErr bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "Successful verification",
|
||||||
|
param: usecase.VerifyAuthResultRequest{
|
||||||
|
Account: "testAccount",
|
||||||
|
Token: "password",
|
||||||
|
},
|
||||||
|
mockSetup: func() {
|
||||||
|
mockAccountRepo.EXPECT().FindOneByAccount(gomock.Any(), "testAccount").Return(&entity.Account{
|
||||||
|
Token: token,
|
||||||
|
}, nil)
|
||||||
|
},
|
||||||
|
wantResp: usecase.VerifyAuthResultResponse{
|
||||||
|
Status: true,
|
||||||
|
},
|
||||||
|
wantErr: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Invalid token verification",
|
||||||
|
param: usecase.VerifyAuthResultRequest{
|
||||||
|
Account: "testAccount",
|
||||||
|
Token: "invalidToken",
|
||||||
|
},
|
||||||
|
mockSetup: func() {
|
||||||
|
mockAccountRepo.EXPECT().FindOneByAccount(gomock.Any(), "testAccount").Return(&entity.Account{
|
||||||
|
Token: "validToken",
|
||||||
|
}, nil)
|
||||||
|
},
|
||||||
|
wantResp: usecase.VerifyAuthResultResponse{
|
||||||
|
Status: false,
|
||||||
|
},
|
||||||
|
wantErr: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Account not found",
|
||||||
|
param: usecase.VerifyAuthResultRequest{
|
||||||
|
Account: "nonExistentAccount",
|
||||||
|
Token: "someToken",
|
||||||
|
},
|
||||||
|
mockSetup: func() {
|
||||||
|
mockAccountRepo.EXPECT().FindOneByAccount(gomock.Any(), "nonExistentAccount").Return(nil, errs.ResourceNotFound("account not found"))
|
||||||
|
},
|
||||||
|
wantResp: usecase.VerifyAuthResultResponse{},
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Database error",
|
||||||
|
param: usecase.VerifyAuthResultRequest{
|
||||||
|
Account: "testAccount",
|
||||||
|
Token: "someToken",
|
||||||
|
},
|
||||||
|
mockSetup: func() {
|
||||||
|
mockAccountRepo.EXPECT().FindOneByAccount(gomock.Any(), "testAccount").Return(nil, errors.New("database error"))
|
||||||
|
},
|
||||||
|
wantResp: usecase.VerifyAuthResultResponse{},
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
tt.mockSetup()
|
||||||
|
|
||||||
|
resp, err := uc.VerifyPlatformAuthResult(context.Background(), tt.param)
|
||||||
|
|
||||||
|
if tt.wantErr {
|
||||||
|
assert.Error(t, err)
|
||||||
|
} else {
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, tt.wantResp, resp)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue