Compare commits
No commits in common. "7cbee88aecdf11f10e93128d91e4148b8fc617b2" and "68cf992930c368971c4f018fcf6660d167dee264" have entirely different histories.
7cbee88aec
...
68cf992930
|
@ -117,14 +117,6 @@ issues:
|
||||||
- gocognit
|
- gocognit
|
||||||
- contextcheck
|
- contextcheck
|
||||||
|
|
||||||
exclude-dirs:
|
|
||||||
- internal/model
|
|
||||||
|
|
||||||
exclude-files:
|
|
||||||
- .*_test.go
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
linters-settings:
|
linters-settings:
|
||||||
gci:
|
gci:
|
||||||
sections:
|
sections:
|
||||||
|
|
9
Makefile
9
Makefile
|
@ -18,7 +18,6 @@ test: # 進行測試
|
||||||
fmt: # 格式優化
|
fmt: # 格式優化
|
||||||
$(GOFMT) -w $(GOFILES)
|
$(GOFMT) -w $(GOFILES)
|
||||||
goimports -w ./
|
goimports -w ./
|
||||||
golangci-lint run
|
|
||||||
|
|
||||||
.PHONY: gen-rpc
|
.PHONY: gen-rpc
|
||||||
gen-rpc: # 建立 rpc code
|
gen-rpc: # 建立 rpc code
|
||||||
|
@ -52,9 +51,9 @@ gen-mongo-model: # 建立 rpc 資料庫
|
||||||
# 只產生 Model 剩下的要自己撰寫,連欄位名稱也是
|
# 只產生 Model 剩下的要自己撰寫,連欄位名稱也是
|
||||||
goctl model mongo -t post --dir ./internal/model/mongo --style $(GO_ZERO_STYLE)
|
goctl model mongo -t post --dir ./internal/model/mongo --style $(GO_ZERO_STYLE)
|
||||||
goctl model mongo -t comment --dir ./internal/model/mongo --style $(GO_ZERO_STYLE)
|
goctl model mongo -t comment --dir ./internal/model/mongo --style $(GO_ZERO_STYLE)
|
||||||
goctl model mongo -t tags --dir ./internal/model/mongo --style $(GO_ZERO_STYLE)
|
# goctl model mongo -t tags --dir ./internal/model/mongo --style $(GO_ZERO_STYLE)
|
||||||
goctl model mongo -t post_likes --dir ./internal/model/mongo --style $(GO_ZERO_STYLE)
|
# goctl model mongo -t post_likes --dir ./internal/model/mongo --style $(GO_ZERO_STYLE)
|
||||||
goctl model mongo -t comment_likes --dir ./internal/model/mongo --style $(GO_ZERO_STYLE)
|
# goctl model mongo -t comment_likes --dir ./internal/model/mongo --style $(GO_ZERO_STYLE)
|
||||||
@echo "Generate mongo model files successfully"
|
@echo "Generate mongo model files successfully"
|
||||||
|
|
||||||
.PHONY: mock-gen
|
.PHONY: mock-gen
|
||||||
|
@ -63,8 +62,6 @@ mock-gen: # 建立 mock 資料
|
||||||
mockgen -source=./internal/model/mongo/post_model.go -destination=./internal/mock/model/post_model.go -package=mock
|
mockgen -source=./internal/model/mongo/post_model.go -destination=./internal/mock/model/post_model.go -package=mock
|
||||||
mockgen -source=./internal/model/mongo/comment_model_gen.go -destination=./internal/mock/model/comment_model_gen.go -package=mock
|
mockgen -source=./internal/model/mongo/comment_model_gen.go -destination=./internal/mock/model/comment_model_gen.go -package=mock
|
||||||
mockgen -source=./internal/model/mongo/comment_model.go -destination=./internal/mock/model/comment_model.go -package=mock
|
mockgen -source=./internal/model/mongo/comment_model.go -destination=./internal/mock/model/comment_model.go -package=mock
|
||||||
mockgen -source=./internal/domain/repository/social_network.go -destination=./internal/mock/repository/social_network.go -package=mock
|
|
||||||
mockgen -source=./internal/domain/repository/timeline.go -destination=./internal/mock/repository/timeline.go -package=mock
|
|
||||||
@echo "Generate mock files successfully"
|
@echo "Generate mock files successfully"
|
||||||
|
|
||||||
.PHONY: migrate-database
|
.PHONY: migrate-database
|
||||||
|
|
|
@ -11,21 +11,4 @@ Mongo:
|
||||||
User: ""
|
User: ""
|
||||||
Password: ""
|
Password: ""
|
||||||
Port: "27017"
|
Port: "27017"
|
||||||
Database: digimon_tweeting
|
Database: digimon_tweeting
|
||||||
|
|
||||||
TimelineSetting:
|
|
||||||
Expire: 86400
|
|
||||||
MaxLength: 1000
|
|
||||||
|
|
||||||
RedisCluster:
|
|
||||||
Host: 127.0.0.1:7001
|
|
||||||
Type: cluster
|
|
||||||
|
|
||||||
Neo4J:
|
|
||||||
URI: bolt://localhost:7687
|
|
||||||
Username: neo4j
|
|
||||||
Password: yyyytttt
|
|
||||||
MaxConnectionPoolSize: 20
|
|
||||||
MaxConnectionLifetime: 200s
|
|
||||||
ConnectionTimeout : 200s
|
|
||||||
LogLevel : debug
|
|
|
@ -1,2 +1,5 @@
|
||||||
use digimon_tweeting;
|
use digimon_tweeting;
|
||||||
db.comment.createIndex({ "post_id": 1,"createAt":1});
|
db.comment.createIndex({ "post_id": 1,"createAt":1});
|
||||||
|
|
||||||
|
|
||||||
|
// TODO 看是否有要刪除過多的索引,要在測試一下
|
|
@ -1,5 +0,0 @@
|
||||||
// 企業版才能用,社群版只能用預設的
|
|
||||||
CREATE DATABASE relation;
|
|
||||||
|
|
||||||
// 創建 User 節點 UID 是唯一鍵
|
|
||||||
CREATE CONSTRAINT FOR (u:User) REQUIRE u.uid IS UNIQUE
|
|
|
@ -181,118 +181,4 @@ service CommentService
|
||||||
rpc DeleteComment(DeleteCommentReq) returns (OKResp);
|
rpc DeleteComment(DeleteCommentReq) returns (OKResp);
|
||||||
// UpdateComment 更新評論
|
// UpdateComment 更新評論
|
||||||
rpc UpdateComment(UpdateCommentReq) returns (OKResp);
|
rpc UpdateComment(UpdateCommentReq) returns (OKResp);
|
||||||
}
|
|
||||||
|
|
||||||
// ========== TimeLineService (個人動態時報) ==========
|
|
||||||
|
|
||||||
message GetTimelineReq
|
|
||||||
{
|
|
||||||
string uid = 1; // 用户ID
|
|
||||||
int64 pageIndex = 2; // 頁碼
|
|
||||||
int64 pageSize = 3; // 每一頁大小
|
|
||||||
}
|
|
||||||
|
|
||||||
message FetchTimelineResponse
|
|
||||||
{
|
|
||||||
repeated FetchTimelineItem posts = 1; // 貼文列表
|
|
||||||
Pager page = 2; // 分頁訊息
|
|
||||||
}
|
|
||||||
|
|
||||||
message FetchTimelineItem
|
|
||||||
{
|
|
||||||
string post_id = 1;
|
|
||||||
int64 score = 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
message AddPostToTimelineReq
|
|
||||||
{
|
|
||||||
string uid = 1; // key
|
|
||||||
repeated PostTimelineItem posts = 3;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 貼文更新
|
|
||||||
message PostTimelineItem
|
|
||||||
{
|
|
||||||
string post_id = 1; // 貼文ID
|
|
||||||
int64 created_at = 7; // 發佈時間 -> 排序使用
|
|
||||||
}
|
|
||||||
|
|
||||||
message DoNoMoreDataReq
|
|
||||||
{
|
|
||||||
string uid = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
message HasNoMoreDataResp
|
|
||||||
{
|
|
||||||
bool status = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// TimelineService 業務邏輯在外面組合
|
|
||||||
service TimelineService
|
|
||||||
{
|
|
||||||
// AddPost 加入貼文,只管一股腦全塞,這裡會自動判斷
|
|
||||||
// 誰要砍誰不砍,處理排序,上限是 1000 筆(超過1000 請他回資料庫拿)
|
|
||||||
// 只存活躍用戶的,不活躍不浪費快取的空間
|
|
||||||
rpc AddPost(AddPostToTimelineReq) returns (OKResp);
|
|
||||||
// FetchTimeline 取得這個人的動態時報
|
|
||||||
rpc FetchTimeline(GetTimelineReq) returns (FetchTimelineResponse);
|
|
||||||
// SetNoMoreDataFlag 標記時間線已完整,避免繼續查詢資料庫。
|
|
||||||
rpc SetNoMoreDataFlag(DoNoMoreDataReq) returns (OKResp);
|
|
||||||
// HasNoMoreData 檢查時間線是否已完整,決定是否需要查詢資料庫。
|
|
||||||
rpc HasNoMoreData(DoNoMoreDataReq) returns (HasNoMoreDataResp);
|
|
||||||
// ClearNoMoreDataFlag 清除時間線的 "NoMoreData" 標誌。
|
|
||||||
rpc ClearNoMoreDataFlag(DoNoMoreDataReq) returns (OKResp);
|
|
||||||
}
|
|
||||||
|
|
||||||
// ========== Social Network (關係網路) ==========
|
|
||||||
|
|
||||||
message AddUserToNetworkReq
|
|
||||||
{
|
|
||||||
string uid = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
message DoFollowerRelationReq
|
|
||||||
{
|
|
||||||
string follower_uid = 1;
|
|
||||||
string followee_uid = 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
message FollowReq
|
|
||||||
{
|
|
||||||
string uid = 1;
|
|
||||||
int64 page_size = 2;
|
|
||||||
int64 page_index = 3;
|
|
||||||
}
|
|
||||||
|
|
||||||
message FollowResp
|
|
||||||
{
|
|
||||||
repeated string uid = 1;
|
|
||||||
Pager page = 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
message FollowCountReq
|
|
||||||
{
|
|
||||||
string uid = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
message FollowCountResp
|
|
||||||
{
|
|
||||||
string uid = 1;
|
|
||||||
int64 total = 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
service SocialNetworkService
|
|
||||||
{
|
|
||||||
// MarkFollowRelation 關注
|
|
||||||
rpc MarkFollowRelation(DoFollowerRelationReq) returns (OKResp);
|
|
||||||
// RemoveFollowRelation 取消關注
|
|
||||||
rpc RemoveFollowRelation(DoFollowerRelationReq) returns (OKResp);
|
|
||||||
// GetFollower 取得跟隨者名單
|
|
||||||
rpc GetFollower(FollowReq) returns (FollowResp);
|
|
||||||
// GetFollowee 取得我跟隨的名單
|
|
||||||
rpc GetFollowee(FollowReq) returns (FollowResp);
|
|
||||||
// GetFollowerCount 取得跟隨者數量
|
|
||||||
rpc GetFollowerCount(FollowCountReq) returns (FollowCountResp);
|
|
||||||
// GetFolloweeCount 取得我跟隨的數量
|
|
||||||
rpc GetFolloweeCount(FollowCountReq) returns (FollowCountResp);
|
|
||||||
}
|
}
|
37
go.mod
37
go.mod
|
@ -5,10 +5,7 @@ go 1.22.3
|
||||||
require (
|
require (
|
||||||
code.30cm.net/digimon/library-go/errs v1.2.4
|
code.30cm.net/digimon/library-go/errs v1.2.4
|
||||||
code.30cm.net/digimon/library-go/validator v1.0.0
|
code.30cm.net/digimon/library-go/validator v1.0.0
|
||||||
github.com/alicebob/miniredis/v2 v2.33.0
|
|
||||||
github.com/neo4j/neo4j-go-driver/v5 v5.24.0
|
|
||||||
github.com/stretchr/testify v1.9.0
|
github.com/stretchr/testify v1.9.0
|
||||||
github.com/testcontainers/testcontainers-go v0.33.0
|
|
||||||
github.com/zeromicro/go-zero v1.7.0
|
github.com/zeromicro/go-zero v1.7.0
|
||||||
go.mongodb.org/mongo-driver v1.16.0
|
go.mongodb.org/mongo-driver v1.16.0
|
||||||
go.uber.org/mock v0.4.0
|
go.uber.org/mock v0.4.0
|
||||||
|
@ -17,32 +14,18 @@ require (
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
dario.cat/mergo v1.0.0 // indirect
|
|
||||||
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect
|
|
||||||
github.com/Microsoft/go-winio v0.6.2 // indirect
|
|
||||||
github.com/alicebob/gopher-json v0.0.0-20230218143504-906a9b012302 // indirect
|
|
||||||
github.com/beorn7/perks v1.0.1 // indirect
|
github.com/beorn7/perks v1.0.1 // indirect
|
||||||
github.com/cenkalti/backoff/v4 v4.3.0 // indirect
|
github.com/cenkalti/backoff/v4 v4.3.0 // indirect
|
||||||
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||||
github.com/containerd/containerd v1.7.18 // indirect
|
|
||||||
github.com/containerd/log v0.1.0 // indirect
|
|
||||||
github.com/containerd/platforms v0.2.1 // indirect
|
|
||||||
github.com/coreos/go-semver v0.3.1 // indirect
|
github.com/coreos/go-semver v0.3.1 // indirect
|
||||||
github.com/coreos/go-systemd/v22 v22.5.0 // indirect
|
github.com/coreos/go-systemd/v22 v22.5.0 // indirect
|
||||||
github.com/cpuguy83/dockercfg v0.3.1 // indirect
|
|
||||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
|
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
|
||||||
github.com/distribution/reference v0.6.0 // indirect
|
|
||||||
github.com/docker/docker v27.1.1+incompatible // indirect
|
|
||||||
github.com/docker/go-connections v0.5.0 // indirect
|
|
||||||
github.com/docker/go-units v0.5.0 // indirect
|
|
||||||
github.com/emicklei/go-restful/v3 v3.11.0 // indirect
|
github.com/emicklei/go-restful/v3 v3.11.0 // indirect
|
||||||
github.com/fatih/color v1.17.0 // indirect
|
github.com/fatih/color v1.17.0 // indirect
|
||||||
github.com/felixge/httpsnoop v1.0.4 // indirect
|
|
||||||
github.com/gabriel-vasile/mimetype v1.4.3 // indirect
|
github.com/gabriel-vasile/mimetype v1.4.3 // indirect
|
||||||
github.com/go-logr/logr v1.4.2 // indirect
|
github.com/go-logr/logr v1.4.2 // indirect
|
||||||
github.com/go-logr/stdr v1.2.2 // indirect
|
github.com/go-logr/stdr v1.2.2 // indirect
|
||||||
github.com/go-ole/go-ole v1.2.6 // indirect
|
|
||||||
github.com/go-openapi/jsonpointer v0.19.6 // indirect
|
github.com/go-openapi/jsonpointer v0.19.6 // indirect
|
||||||
github.com/go-openapi/jsonreference v0.20.2 // indirect
|
github.com/go-openapi/jsonreference v0.20.2 // indirect
|
||||||
github.com/go-openapi/swag v0.22.4 // indirect
|
github.com/go-openapi/swag v0.22.4 // indirect
|
||||||
|
@ -62,49 +45,29 @@ require (
|
||||||
github.com/json-iterator/go v1.1.12 // indirect
|
github.com/json-iterator/go v1.1.12 // indirect
|
||||||
github.com/klauspost/compress v1.17.8 // indirect
|
github.com/klauspost/compress v1.17.8 // indirect
|
||||||
github.com/leodido/go-urn v1.4.0 // 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/mailru/easyjson v0.7.7 // indirect
|
||||||
github.com/mattn/go-colorable v0.1.13 // indirect
|
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||||
github.com/moby/docker-image-spec v1.3.1 // indirect
|
|
||||||
github.com/moby/patternmatcher v0.6.0 // indirect
|
|
||||||
github.com/moby/sys/sequential v0.5.0 // indirect
|
|
||||||
github.com/moby/sys/user v0.1.0 // indirect
|
|
||||||
github.com/moby/term v0.5.0 // indirect
|
|
||||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||||
github.com/montanaflynn/stats v0.7.1 // indirect
|
github.com/montanaflynn/stats v0.7.1 // indirect
|
||||||
github.com/morikuni/aec v1.0.0 // indirect
|
|
||||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
|
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
|
||||||
github.com/opencontainers/go-digest v1.0.0 // indirect
|
|
||||||
github.com/opencontainers/image-spec v1.1.0 // indirect
|
|
||||||
github.com/openzipkin/zipkin-go v0.4.3 // indirect
|
github.com/openzipkin/zipkin-go v0.4.3 // indirect
|
||||||
github.com/pelletier/go-toml/v2 v2.2.2 // indirect
|
github.com/pelletier/go-toml/v2 v2.2.2 // indirect
|
||||||
github.com/pkg/errors v0.9.1 // indirect
|
|
||||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||||
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect
|
|
||||||
github.com/prometheus/client_golang v1.19.1 // indirect
|
github.com/prometheus/client_golang v1.19.1 // indirect
|
||||||
github.com/prometheus/client_model v0.5.0 // indirect
|
github.com/prometheus/client_model v0.5.0 // indirect
|
||||||
github.com/prometheus/common v0.48.0 // indirect
|
github.com/prometheus/common v0.48.0 // indirect
|
||||||
github.com/prometheus/procfs v0.12.0 // indirect
|
github.com/prometheus/procfs v0.12.0 // indirect
|
||||||
github.com/redis/go-redis/v9 v9.6.1 // indirect
|
github.com/redis/go-redis/v9 v9.6.1 // indirect
|
||||||
github.com/shirou/gopsutil/v3 v3.23.12 // indirect
|
|
||||||
github.com/shoenig/go-m1cpu v0.1.6 // indirect
|
|
||||||
github.com/sirupsen/logrus v1.9.3 // indirect
|
|
||||||
github.com/spaolacci/murmur3 v1.1.0 // indirect
|
github.com/spaolacci/murmur3 v1.1.0 // indirect
|
||||||
github.com/tklauser/go-sysconf v0.3.12 // indirect
|
|
||||||
github.com/tklauser/numcpus v0.6.1 // indirect
|
|
||||||
github.com/xdg-go/pbkdf2 v1.0.0 // indirect
|
github.com/xdg-go/pbkdf2 v1.0.0 // indirect
|
||||||
github.com/xdg-go/scram v1.1.2 // indirect
|
github.com/xdg-go/scram v1.1.2 // indirect
|
||||||
github.com/xdg-go/stringprep v1.0.4 // indirect
|
github.com/xdg-go/stringprep v1.0.4 // indirect
|
||||||
github.com/youmark/pkcs8 v0.0.0-20201027041543-1326539a0a0a // indirect
|
github.com/youmark/pkcs8 v0.0.0-20201027041543-1326539a0a0a // indirect
|
||||||
github.com/yuin/gopher-lua v1.1.1 // indirect
|
|
||||||
github.com/yusufpapurcu/wmi v1.2.3 // indirect
|
|
||||||
go.etcd.io/etcd/api/v3 v3.5.15 // indirect
|
go.etcd.io/etcd/api/v3 v3.5.15 // indirect
|
||||||
go.etcd.io/etcd/client/pkg/v3 v3.5.15 // indirect
|
go.etcd.io/etcd/client/pkg/v3 v3.5.15 // indirect
|
||||||
go.etcd.io/etcd/client/v3 v3.5.15 // indirect
|
go.etcd.io/etcd/client/v3 v3.5.15 // indirect
|
||||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 // indirect
|
|
||||||
go.opentelemetry.io/otel v1.24.0 // indirect
|
go.opentelemetry.io/otel v1.24.0 // indirect
|
||||||
go.opentelemetry.io/otel/exporters/jaeger v1.17.0 // indirect
|
go.opentelemetry.io/otel/exporters/jaeger v1.17.0 // indirect
|
||||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.24.0 // indirect
|
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.24.0 // indirect
|
||||||
|
|
|
@ -1,15 +1,9 @@
|
||||||
package config
|
package config
|
||||||
|
|
||||||
import (
|
import "github.com/zeromicro/go-zero/zrpc"
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/zeromicro/go-zero/core/stores/redis"
|
|
||||||
"github.com/zeromicro/go-zero/zrpc"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Config struct {
|
type Config struct {
|
||||||
zrpc.RpcServerConf
|
zrpc.RpcServerConf
|
||||||
|
|
||||||
Mongo struct {
|
Mongo struct {
|
||||||
Schema string
|
Schema string
|
||||||
User string
|
User string
|
||||||
|
@ -18,23 +12,4 @@ type Config struct {
|
||||||
Port string
|
Port string
|
||||||
Database string
|
Database string
|
||||||
}
|
}
|
||||||
|
|
||||||
TimelineSetting struct {
|
|
||||||
Expire int64 // Second
|
|
||||||
MaxLength int64 // 暫存筆數
|
|
||||||
}
|
|
||||||
|
|
||||||
// Redis Cluster
|
|
||||||
RedisCluster redis.RedisConf
|
|
||||||
|
|
||||||
// 圖形資料庫
|
|
||||||
Neo4J struct {
|
|
||||||
URI string
|
|
||||||
Username string
|
|
||||||
Password string
|
|
||||||
MaxConnectionPoolSize int
|
|
||||||
MaxConnectionLifetime time.Duration
|
|
||||||
ConnectionTimeout time.Duration
|
|
||||||
LogLevel string
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,7 +11,3 @@ const (
|
||||||
AdTypeOnlyAd
|
AdTypeOnlyAd
|
||||||
AdTypeOnlyNotAd
|
AdTypeOnlyNotAd
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
|
||||||
LastOfTimelineFlag = "NoMoreData"
|
|
||||||
)
|
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package domain
|
package domain
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
ers "code.30cm.net/digimon/library-go/errs"
|
ers "code.30cm.net/digimon/library-go/errs"
|
||||||
|
@ -32,25 +33,10 @@ const (
|
||||||
CommentListErrorCode
|
CommentListErrorCode
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
|
||||||
AddTimeLineErrorCode ErrorCode = iota + 20
|
|
||||||
FetchTimeLineErrorCode
|
|
||||||
ClearNoMoreDataErrorCode
|
|
||||||
HasNoMoreDataErrorCode
|
|
||||||
SetNoMoreDataErrorCode
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
MarkRelationErrorCode ErrorCode = iota + 30
|
|
||||||
GetFollowerErrorCode
|
|
||||||
GetFollowerCountErrorCode
|
|
||||||
GetFolloweeErrorCode
|
|
||||||
GetFolloweeCountErrorCode
|
|
||||||
RemoveRelationErrorCode
|
|
||||||
)
|
|
||||||
|
|
||||||
func CommentError(ec ErrorCode, s ...string) *ers.LibError {
|
func CommentError(ec ErrorCode, s ...string) *ers.LibError {
|
||||||
return ers.NewError(code.CloudEPTweeting, code.DBError, ec.ToUint32(), strings.Join(s, " "))
|
return ers.NewError(code.CloudEPTweeting, code.DBError,
|
||||||
|
ec.ToUint32(),
|
||||||
|
fmt.Sprintf("%s", strings.Join(s, " ")))
|
||||||
}
|
}
|
||||||
|
|
||||||
func CommentErrorL(ec ErrorCode,
|
func CommentErrorL(ec ErrorCode,
|
||||||
|
|
|
@ -1,19 +0,0 @@
|
||||||
package domain
|
|
||||||
|
|
||||||
import "strings"
|
|
||||||
|
|
||||||
type RedisKey string
|
|
||||||
|
|
||||||
func (key RedisKey) ToString() string {
|
|
||||||
return string(key)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (key RedisKey) With(s ...string) RedisKey {
|
|
||||||
parts := append([]string{string(key)}, s...)
|
|
||||||
|
|
||||||
return RedisKey(strings.Join(parts, ":"))
|
|
||||||
}
|
|
||||||
|
|
||||||
const (
|
|
||||||
TimelineRedisKey RedisKey = "timeline"
|
|
||||||
)
|
|
|
@ -1,45 +0,0 @@
|
||||||
package domain
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
)
|
|
||||||
|
|
||||||
// TestRedisKeyToString 測試 ToString 方法
|
|
||||||
func TestRedisKeyToString(t *testing.T) {
|
|
||||||
key := RedisKey("user:timeline")
|
|
||||||
expected := "user:timeline"
|
|
||||||
result := key.ToString()
|
|
||||||
|
|
||||||
assert.Equal(t, expected, result, "ToString should return the correct string representation of RedisKey")
|
|
||||||
}
|
|
||||||
|
|
||||||
// TestRedisKeyWith 測試 With 方法
|
|
||||||
func TestRedisKeyWith(t *testing.T) {
|
|
||||||
key := RedisKey("user:timeline")
|
|
||||||
subKey := "12345"
|
|
||||||
expected := "user:timeline:12345"
|
|
||||||
result := key.With(subKey)
|
|
||||||
|
|
||||||
assert.Equal(t, RedisKey(expected), result, "With should correctly concatenate the RedisKey with the provided subKey")
|
|
||||||
}
|
|
||||||
|
|
||||||
// TestRedisKeyWithMultiple 測試 With 方法與多個參數
|
|
||||||
func TestRedisKeyWithMultiple(t *testing.T) {
|
|
||||||
key := RedisKey("user:timeline")
|
|
||||||
subKeys := []string{"12345", "posts"}
|
|
||||||
expected := "user:timeline:12345:posts"
|
|
||||||
result := key.With(subKeys...)
|
|
||||||
|
|
||||||
assert.Equal(t, RedisKey(expected), result, "With should correctly concatenate the RedisKey with multiple provided subKeys")
|
|
||||||
}
|
|
||||||
|
|
||||||
// TestRedisKeyWithEmpty 測試 With 方法與空參數
|
|
||||||
func TestRedisKeyWithEmpty(t *testing.T) {
|
|
||||||
key := RedisKey("user:timeline")
|
|
||||||
expected := "user:timeline"
|
|
||||||
result := key.With()
|
|
||||||
|
|
||||||
assert.Equal(t, RedisKey(expected), result, "With should return the original key when no subKeys are provided")
|
|
||||||
}
|
|
|
@ -1,26 +0,0 @@
|
||||||
package repository
|
|
||||||
|
|
||||||
import "context"
|
|
||||||
|
|
||||||
type SocialNetworkRepository interface {
|
|
||||||
CreateUserNode(ctx context.Context, uid string) error
|
|
||||||
MarkFollowerRelation(ctx context.Context, fromUID, toUID string) error
|
|
||||||
RemoveFollowerRelation(ctx context.Context, fromUID, toUID string) error
|
|
||||||
GetFollower(ctx context.Context, req FollowReq) (FollowResp, error)
|
|
||||||
GetFollowee(ctx context.Context, req FollowReq) (FollowResp, error)
|
|
||||||
GetFollowerCount(ctx context.Context, uid string) (int64, error)
|
|
||||||
GetFolloweeCount(ctx context.Context, uid string) (int64, error)
|
|
||||||
GetDegreeBetweenUsers(ctx context.Context, uid1, uid2 string) (int64, error)
|
|
||||||
GetUIDsWithinNDegrees(ctx context.Context, uid string, degrees, pageSize, pageIndex int64) ([]string, int64, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
type FollowReq struct {
|
|
||||||
UID string
|
|
||||||
PageSize int64
|
|
||||||
PageIndex int64
|
|
||||||
}
|
|
||||||
|
|
||||||
type FollowResp struct {
|
|
||||||
UIDs []string
|
|
||||||
Total int64
|
|
||||||
}
|
|
|
@ -1,57 +0,0 @@
|
||||||
package repository
|
|
||||||
|
|
||||||
import (
|
|
||||||
"app-cloudep-tweeting-service/gen_result/pb/tweeting"
|
|
||||||
"context"
|
|
||||||
)
|
|
||||||
|
|
||||||
/*
|
|
||||||
----------------------------------------
|
|
||||||
| | | | |
|
|
||||||
| data A| data B |NO Data | .... |
|
|
||||||
| | | | |
|
|
||||||
----------------------------------------
|
|
||||||
|
|
||||||
動態時報在發現這個 Queue 有 No Data 的 Flag 時
|
|
||||||
就不再去 Query 資料庫,防止被狂刷。
|
|
||||||
只是要注意在業務上何時要 加入/刪除 這個 Flag
|
|
||||||
*/
|
|
||||||
|
|
||||||
// TimelineRepository 定義時間線的存儲接口,可以根據不同的排序策略實現。
|
|
||||||
type TimelineRepository interface {
|
|
||||||
// AddPost 將貼文添加到動態時報,並根據排序策略進行排序。
|
|
||||||
AddPost(ctx context.Context, req AddPostRequest) error
|
|
||||||
// FetchTimeline 獲取指定用戶的動態時報。
|
|
||||||
FetchTimeline(ctx context.Context, req FetchTimelineRequest) (FetchTimelineResponse, error)
|
|
||||||
// SetNoMoreDataFlag 標記時間線已完整,避免繼續查詢資料庫。
|
|
||||||
SetNoMoreDataFlag(ctx context.Context, uid string) error
|
|
||||||
// HasNoMoreData 檢查時間線是否已完整,決定是否需要查詢資料庫。
|
|
||||||
HasNoMoreData(ctx context.Context, uid string) (bool, error)
|
|
||||||
// ClearNoMoreDataFlag 清除時間線的 "NoMoreData" 標誌。
|
|
||||||
ClearNoMoreDataFlag(ctx context.Context, uid string) error
|
|
||||||
}
|
|
||||||
|
|
||||||
// AddPostRequest 用於將貼文添加到時間線的請求結構體。
|
|
||||||
type AddPostRequest struct {
|
|
||||||
UID string
|
|
||||||
PostItems []TimelineItem
|
|
||||||
}
|
|
||||||
|
|
||||||
// TimelineItem 表示時間線中的一個元素,排序依據取決於 Score。
|
|
||||||
type TimelineItem struct {
|
|
||||||
PostID string // 貼文ID
|
|
||||||
Score int64 // 排序使用的分數,根據具體實現可能代表時間、優先級等
|
|
||||||
}
|
|
||||||
|
|
||||||
// FetchTimelineRequest 用於獲取時間線的請求結構體。
|
|
||||||
type FetchTimelineRequest struct {
|
|
||||||
UID string
|
|
||||||
PageSize int64
|
|
||||||
PageIndex int64
|
|
||||||
}
|
|
||||||
|
|
||||||
// FetchTimelineResponse 表示獲取時間線的回應結構體。
|
|
||||||
type FetchTimelineResponse struct {
|
|
||||||
Items []TimelineItem
|
|
||||||
Page tweeting.Pager
|
|
||||||
}
|
|
|
@ -1,14 +0,0 @@
|
||||||
package neo4j
|
|
||||||
|
|
||||||
import "time"
|
|
||||||
|
|
||||||
// Config holds the configuration for Neo4j connection.
|
|
||||||
type Config struct {
|
|
||||||
URI string
|
|
||||||
Username string
|
|
||||||
Password string
|
|
||||||
MaxConnectionPoolSize int
|
|
||||||
MaxConnectionLifetime time.Duration
|
|
||||||
ConnectionTimeout time.Duration
|
|
||||||
LogLevel string
|
|
||||||
}
|
|
|
@ -1,53 +0,0 @@
|
||||||
package neo4j
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"github.com/neo4j/neo4j-go-driver/v5/neo4j"
|
|
||||||
n4Cfg "github.com/neo4j/neo4j-go-driver/v5/neo4j/config"
|
|
||||||
)
|
|
||||||
|
|
||||||
// NewNeo4J initializes a Neo4jInit using the provided Config and options.
|
|
||||||
// If opts is not provided, it will initialize Neo4jInit with default configuration.
|
|
||||||
func NewNeo4J(conf *Config, opts ...Option) *Client {
|
|
||||||
driverConfig := &n4Cfg.Config{
|
|
||||||
MaxConnectionLifetime: conf.MaxConnectionLifetime,
|
|
||||||
MaxConnectionPoolSize: conf.MaxConnectionPoolSize,
|
|
||||||
ConnectionAcquisitionTimeout: conf.ConnectionTimeout,
|
|
||||||
}
|
|
||||||
|
|
||||||
neo4ji := &Client{
|
|
||||||
neo4jConf: driverConfig,
|
|
||||||
serviceConf: Config{
|
|
||||||
URI: conf.URI,
|
|
||||||
Username: conf.Username,
|
|
||||||
Password: conf.Password,
|
|
||||||
LogLevel: conf.LogLevel,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, opt := range opts {
|
|
||||||
opt(neo4ji)
|
|
||||||
}
|
|
||||||
|
|
||||||
return neo4ji
|
|
||||||
}
|
|
||||||
|
|
||||||
// Conn initiates connection to the database and returns a Neo4j driver instance.
|
|
||||||
func (c *Client) Conn() (neo4j.DriverWithContext, error) {
|
|
||||||
auth := neo4j.BasicAuth(c.serviceConf.Username, c.serviceConf.Password, "")
|
|
||||||
driver, err := neo4j.NewDriverWithContext(c.serviceConf.URI, auth, func(_ *n4Cfg.Config) {})
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("neo4j driver initialization error: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx := context.Background()
|
|
||||||
// Verify the connection to Neo4j.
|
|
||||||
err = driver.VerifyConnectivity(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("neo4j connectivity verification error: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return driver, nil
|
|
||||||
}
|
|
|
@ -1,209 +0,0 @@
|
||||||
package neo4j
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"testing"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
"github.com/testcontainers/testcontainers-go"
|
|
||||||
"github.com/testcontainers/testcontainers-go/wait"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestNewNeo4J(t *testing.T) {
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
conf *Config
|
|
||||||
expected *Config
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "valid configuration",
|
|
||||||
conf: &Config{
|
|
||||||
URI: "neo4j://localhost:7687",
|
|
||||||
Username: "neo4j",
|
|
||||||
Password: "password",
|
|
||||||
LogLevel: "info",
|
|
||||||
MaxConnectionLifetime: time.Minute * 5,
|
|
||||||
MaxConnectionPoolSize: 10,
|
|
||||||
ConnectionTimeout: time.Second * 5,
|
|
||||||
},
|
|
||||||
expected: &Config{
|
|
||||||
URI: "neo4j://localhost:7687",
|
|
||||||
Username: "neo4j",
|
|
||||||
Password: "password",
|
|
||||||
LogLevel: "info",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "empty URI",
|
|
||||||
conf: &Config{
|
|
||||||
URI: "",
|
|
||||||
Username: "neo4j",
|
|
||||||
Password: "password",
|
|
||||||
LogLevel: "info",
|
|
||||||
MaxConnectionLifetime: time.Minute * 5,
|
|
||||||
MaxConnectionPoolSize: 10,
|
|
||||||
ConnectionTimeout: time.Second * 5,
|
|
||||||
},
|
|
||||||
expected: &Config{
|
|
||||||
URI: "",
|
|
||||||
Username: "neo4j",
|
|
||||||
Password: "password",
|
|
||||||
LogLevel: "info",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "empty username and password",
|
|
||||||
conf: &Config{
|
|
||||||
URI: "neo4j://localhost:7687",
|
|
||||||
Username: "",
|
|
||||||
Password: "",
|
|
||||||
LogLevel: "info",
|
|
||||||
MaxConnectionLifetime: time.Minute * 5,
|
|
||||||
MaxConnectionPoolSize: 10,
|
|
||||||
ConnectionTimeout: time.Second * 5,
|
|
||||||
},
|
|
||||||
expected: &Config{
|
|
||||||
URI: "neo4j://localhost:7687",
|
|
||||||
Username: "",
|
|
||||||
Password: "",
|
|
||||||
LogLevel: "info",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "custom log level",
|
|
||||||
conf: &Config{
|
|
||||||
URI: "neo4j://localhost:7687",
|
|
||||||
Username: "neo4j",
|
|
||||||
Password: "password",
|
|
||||||
LogLevel: "debug",
|
|
||||||
MaxConnectionLifetime: time.Minute * 5,
|
|
||||||
MaxConnectionPoolSize: 10,
|
|
||||||
ConnectionTimeout: time.Second * 5,
|
|
||||||
},
|
|
||||||
expected: &Config{
|
|
||||||
URI: "neo4j://localhost:7687",
|
|
||||||
Username: "neo4j",
|
|
||||||
Password: "password",
|
|
||||||
LogLevel: "debug",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, tt := range tests {
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
|
||||||
client := NewNeo4J(tt.conf)
|
|
||||||
assert.NotNil(t, client)
|
|
||||||
assert.Equal(t, tt.expected.URI, client.serviceConf.URI)
|
|
||||||
assert.Equal(t, tt.expected.Username, client.serviceConf.Username)
|
|
||||||
assert.Equal(t, tt.expected.Password, client.serviceConf.Password)
|
|
||||||
assert.Equal(t, tt.expected.LogLevel, client.serviceConf.LogLevel)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestConn(t *testing.T) {
|
|
||||||
ctx := context.Background()
|
|
||||||
|
|
||||||
neo4jContainer, err := testcontainers.GenericContainer(ctx, testcontainers.GenericContainerRequest{
|
|
||||||
ContainerRequest: testcontainers.ContainerRequest{
|
|
||||||
Image: "neo4j:latest",
|
|
||||||
ExposedPorts: []string{"7687/tcp"},
|
|
||||||
Env: map[string]string{
|
|
||||||
"NEO4J_AUTH": "neo4j/yyyytttt",
|
|
||||||
},
|
|
||||||
WaitingFor: wait.ForLog("Started"),
|
|
||||||
},
|
|
||||||
Started: true,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
defer neo4jContainer.Terminate(ctx)
|
|
||||||
|
|
||||||
host, _ := neo4jContainer.Host(ctx)
|
|
||||||
port, _ := neo4jContainer.MappedPort(ctx, "7687")
|
|
||||||
|
|
||||||
uri := fmt.Sprintf("bolt://%s:%s", host, port.Port())
|
|
||||||
t.Log("Neo4j running at:", uri)
|
|
||||||
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
conf *Config
|
|
||||||
shouldFail bool
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "successful connection",
|
|
||||||
conf: &Config{
|
|
||||||
URI: uri,
|
|
||||||
Username: "neo4j",
|
|
||||||
Password: "yyyytttt",
|
|
||||||
LogLevel: "info",
|
|
||||||
MaxConnectionLifetime: time.Minute * 5,
|
|
||||||
MaxConnectionPoolSize: 10,
|
|
||||||
ConnectionTimeout: time.Second * 5,
|
|
||||||
},
|
|
||||||
shouldFail: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "failed connection due to invalid URI",
|
|
||||||
conf: &Config{
|
|
||||||
URI: uri,
|
|
||||||
Username: "neo4j",
|
|
||||||
Password: "wrongpassword",
|
|
||||||
LogLevel: "info",
|
|
||||||
MaxConnectionLifetime: time.Minute * 5,
|
|
||||||
MaxConnectionPoolSize: 10,
|
|
||||||
ConnectionTimeout: time.Second * 5,
|
|
||||||
},
|
|
||||||
shouldFail: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "failed connection due to missing URI",
|
|
||||||
conf: &Config{
|
|
||||||
URI: "",
|
|
||||||
Username: "neo4j",
|
|
||||||
Password: "password",
|
|
||||||
LogLevel: "info",
|
|
||||||
MaxConnectionLifetime: time.Minute * 5,
|
|
||||||
MaxConnectionPoolSize: 10,
|
|
||||||
ConnectionTimeout: time.Second * 5,
|
|
||||||
},
|
|
||||||
shouldFail: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "failed connection due to missing username and password",
|
|
||||||
conf: &Config{
|
|
||||||
URI: uri,
|
|
||||||
Username: "",
|
|
||||||
Password: "",
|
|
||||||
LogLevel: "info",
|
|
||||||
MaxConnectionLifetime: time.Minute * 5,
|
|
||||||
MaxConnectionPoolSize: 10,
|
|
||||||
ConnectionTimeout: time.Second * 5,
|
|
||||||
},
|
|
||||||
shouldFail: true,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, tt := range tests {
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
|
||||||
client := NewNeo4J(tt.conf)
|
|
||||||
driver, err := client.Conn()
|
|
||||||
if tt.shouldFail {
|
|
||||||
assert.Error(t, err)
|
|
||||||
assert.Nil(t, driver)
|
|
||||||
} else {
|
|
||||||
assert.NoError(t, err)
|
|
||||||
assert.NotNil(t, driver)
|
|
||||||
|
|
||||||
// Close the driver after test
|
|
||||||
defer func() {
|
|
||||||
err := driver.Close(context.Background())
|
|
||||||
assert.NoError(t, err)
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,60 +0,0 @@
|
||||||
package neo4j
|
|
||||||
|
|
||||||
import (
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
n4Cfg "github.com/neo4j/neo4j-go-driver/v5/neo4j/config"
|
|
||||||
"github.com/neo4j/neo4j-go-driver/v5/neo4j/log"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
defaultMaxConnectionLifetime = 5 * time.Minute
|
|
||||||
defaultMaxConnectionPoolSize = 25
|
|
||||||
defaultConnectionTimeout = 5 * time.Second
|
|
||||||
)
|
|
||||||
|
|
||||||
// Option configures Neo4jInit behaviour.
|
|
||||||
type Option func(*Client)
|
|
||||||
|
|
||||||
type Client struct {
|
|
||||||
neo4jConf *n4Cfg.Config
|
|
||||||
serviceConf Config
|
|
||||||
}
|
|
||||||
|
|
||||||
// WithLogLevel sets the log level for the Neo4j driver.
|
|
||||||
func WithLogLevel(level string) Option {
|
|
||||||
return func(neo4ji *Client) {
|
|
||||||
var logger log.Logger
|
|
||||||
|
|
||||||
switch strings.ToLower(level) {
|
|
||||||
case "panic", "fatal", "error":
|
|
||||||
logger = log.ToConsole(log.ERROR)
|
|
||||||
case "warn", "warning":
|
|
||||||
logger = log.ToConsole(log.WARNING)
|
|
||||||
case "info", "debug", "trace":
|
|
||||||
logger = log.ToConsole(log.INFO)
|
|
||||||
default:
|
|
||||||
logger = log.ToConsole(log.ERROR)
|
|
||||||
}
|
|
||||||
|
|
||||||
neo4ji.neo4jConf.Log = logger
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// WithPerformance configures the Neo4j driver for performance by setting connection pool size and lifetime.
|
|
||||||
func WithPerformance() Option {
|
|
||||||
return func(neo4ji *Client) {
|
|
||||||
if neo4ji.serviceConf.MaxConnectionPoolSize > 0 {
|
|
||||||
neo4ji.neo4jConf.MaxConnectionPoolSize = neo4ji.serviceConf.MaxConnectionPoolSize
|
|
||||||
} else {
|
|
||||||
neo4ji.neo4jConf.MaxConnectionPoolSize = defaultMaxConnectionPoolSize
|
|
||||||
}
|
|
||||||
|
|
||||||
if neo4ji.serviceConf.MaxConnectionLifetime > 0 {
|
|
||||||
neo4ji.neo4jConf.MaxConnectionLifetime = neo4ji.serviceConf.MaxConnectionLifetime
|
|
||||||
} else {
|
|
||||||
neo4ji.neo4jConf.MaxConnectionLifetime = defaultMaxConnectionLifetime
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -37,7 +37,6 @@ func (l *DeleteCommentLogic) DeleteComment(in *tweeting.DeleteCommentReq) (*twee
|
||||||
{Key: "err", Value: err},
|
{Key: "err", Value: err},
|
||||||
},
|
},
|
||||||
"failed to del comment").Wrap(err)
|
"failed to del comment").Wrap(err)
|
||||||
|
|
||||||
return nil, e
|
return nil, e
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -41,8 +41,8 @@ func convertToCommentDetailItem(item *model.Comment) *tweeting.CommentDetail {
|
||||||
Uid: item.UID,
|
Uid: item.UID,
|
||||||
Content: item.Content,
|
Content: item.Content,
|
||||||
CreatedAt: item.CreateAt,
|
CreatedAt: item.CreateAt,
|
||||||
LikeCount: item.LikeCount,
|
LikeCount: int64(item.LikeCount),
|
||||||
DislikeCount: item.DisLikeCount,
|
DislikeCount: int64(item.DisLikeCount),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -82,7 +82,6 @@ func (l *GetCommentsLogic) GetComments(in *tweeting.GetCommentsReq) (*tweeting.G
|
||||||
{Key: "err", Value: err},
|
{Key: "err", Value: err},
|
||||||
},
|
},
|
||||||
"failed to find comment").Wrap(err)
|
"failed to find comment").Wrap(err)
|
||||||
|
|
||||||
return nil, e
|
return nil, e
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -27,16 +27,16 @@ func NewUpdateCommentLogic(ctx context.Context, svcCtx *svc.ServiceContext) *Upd
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type checkCommentID struct {
|
type checkCommentId struct {
|
||||||
CommentID string `validate:"required"`
|
CommentId string `validate:"required"`
|
||||||
Content string `json:"content,omitempty" validate:"lte=500"`
|
Content string `json:"content,omitempty" validate:"lte=500"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// UpdateComment 更新評論
|
// UpdateComment 更新評論
|
||||||
func (l *UpdateCommentLogic) UpdateComment(in *tweeting.UpdateCommentReq) (*tweeting.OKResp, error) {
|
func (l *UpdateCommentLogic) UpdateComment(in *tweeting.UpdateCommentReq) (*tweeting.OKResp, error) {
|
||||||
// 驗證資料
|
// 驗證資料
|
||||||
if err := l.svcCtx.Validate.ValidateAll(&checkCommentID{
|
if err := l.svcCtx.Validate.ValidateAll(&checkCommentId{
|
||||||
CommentID: in.GetCommentId(),
|
CommentId: in.GetCommentId(),
|
||||||
Content: in.GetContent(),
|
Content: in.GetContent(),
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
// 錯誤代碼 05-011-00
|
// 錯誤代碼 05-011-00
|
||||||
|
@ -75,7 +75,6 @@ func (l *UpdateCommentLogic) UpdateComment(in *tweeting.UpdateCommentReq) (*twee
|
||||||
{Key: "err", Value: err},
|
{Key: "err", Value: err},
|
||||||
},
|
},
|
||||||
"failed to update comment:", in.CommentId).Wrap(err)
|
"failed to update comment:", in.CommentId).Wrap(err)
|
||||||
|
|
||||||
return nil, e
|
return nil, e
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -32,7 +32,7 @@ type newTweetingReq struct {
|
||||||
UID string `json:"uid" validate:"required"`
|
UID string `json:"uid" validate:"required"`
|
||||||
Content string `json:"content" validate:"required,lte=500"` // 貼文限制 500 字內
|
Content string `json:"content" validate:"required,lte=500"` // 貼文限制 500 字內
|
||||||
Tags []string `json:"tags"`
|
Tags []string `json:"tags"`
|
||||||
MediaURL []string `json:"media_url"`
|
MediaUrl []string `json:"media_url"`
|
||||||
IsAd bool `json:"is_ad"` // default false
|
IsAd bool `json:"is_ad"` // default false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -85,7 +85,6 @@ func (l *CreatePostLogic) CreatePost(in *tweeting.NewPostReq) (*tweeting.PostRes
|
||||||
{Key: "err", Value: err},
|
{Key: "err", Value: err},
|
||||||
},
|
},
|
||||||
"failed to add new post").Wrap(err)
|
"failed to add new post").Wrap(err)
|
||||||
|
|
||||||
return nil, e
|
return nil, e
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -37,7 +37,6 @@ func (l *DeletePostLogic) DeletePost(in *tweeting.DeletePostsReq) (*tweeting.OKR
|
||||||
{Key: "err", Value: err},
|
{Key: "err", Value: err},
|
||||||
},
|
},
|
||||||
"failed to del post").Wrap(err)
|
"failed to del post").Wrap(err)
|
||||||
|
|
||||||
return nil, e
|
return nil, e
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -54,8 +54,8 @@ func convertToPostDetailItem(item *model.Post) *tweeting.PostDetailItem {
|
||||||
IsAd: item.IsAd,
|
IsAd: item.IsAd,
|
||||||
CreatedAt: item.CreateAt,
|
CreatedAt: item.CreateAt,
|
||||||
UpdateAt: item.UpdateAt,
|
UpdateAt: item.UpdateAt,
|
||||||
LikeCount: item.Like,
|
LikeCount: int64(item.Like),
|
||||||
DislikeCount: item.DisLike,
|
DislikeCount: int64(item.DisLike),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -102,7 +102,6 @@ func (l *ListPostsLogic) ListPosts(in *tweeting.QueryPostsReq) (*tweeting.ListPo
|
||||||
{Key: "err", Value: err},
|
{Key: "err", Value: err},
|
||||||
},
|
},
|
||||||
"failed to find posts").Wrap(err)
|
"failed to find posts").Wrap(err)
|
||||||
|
|
||||||
return nil, e
|
return nil, e
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -28,7 +28,7 @@ func NewUpdatePostLogic(ctx context.Context, svcCtx *svc.ServiceContext) *Update
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type checkPostID struct {
|
type checkPostId struct {
|
||||||
PostID string `validate:"required"`
|
PostID string `validate:"required"`
|
||||||
Content string `json:"content,omitempty" validate:"lte=500"`
|
Content string `json:"content,omitempty" validate:"lte=500"`
|
||||||
}
|
}
|
||||||
|
@ -36,7 +36,7 @@ type checkPostID struct {
|
||||||
// UpdatePost 更新貼文
|
// UpdatePost 更新貼文
|
||||||
func (l *UpdatePostLogic) UpdatePost(in *tweeting.UpdatePostReq) (*tweeting.OKResp, error) {
|
func (l *UpdatePostLogic) UpdatePost(in *tweeting.UpdatePostReq) (*tweeting.OKResp, error) {
|
||||||
// 驗證資料
|
// 驗證資料
|
||||||
if err := l.svcCtx.Validate.ValidateAll(&checkPostID{
|
if err := l.svcCtx.Validate.ValidateAll(&checkPostId{
|
||||||
PostID: in.GetPostId(),
|
PostID: in.GetPostId(),
|
||||||
Content: in.GetContent(),
|
Content: in.GetContent(),
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
|
@ -53,7 +53,7 @@ func (l *UpdatePostLogic) UpdatePost(in *tweeting.UpdatePostReq) (*tweeting.OKRe
|
||||||
update.ID = oid
|
update.ID = oid
|
||||||
update.Tags = in.GetTags()
|
update.Tags = in.GetTags()
|
||||||
// 將 Media 存入
|
// 將 Media 存入
|
||||||
media := make([]model.Media, 0, len(in.GetMedia()))
|
var media []model.Media
|
||||||
for _, item := range in.GetMedia() {
|
for _, item := range in.GetMedia() {
|
||||||
media = append(media, model.Media{
|
media = append(media, model.Media{
|
||||||
Links: item.Url,
|
Links: item.Url,
|
||||||
|
@ -88,7 +88,6 @@ func (l *UpdatePostLogic) UpdatePost(in *tweeting.UpdatePostReq) (*tweeting.OKRe
|
||||||
{Key: "err", Value: err},
|
{Key: "err", Value: err},
|
||||||
},
|
},
|
||||||
"failed to update post", in.PostId).Wrap(err)
|
"failed to update post", in.PostId).Wrap(err)
|
||||||
|
|
||||||
return nil, e
|
return nil, e
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,59 +0,0 @@
|
||||||
package socialnetworkservicelogic
|
|
||||||
|
|
||||||
import (
|
|
||||||
"app-cloudep-tweeting-service/internal/domain"
|
|
||||||
"context"
|
|
||||||
|
|
||||||
ers "code.30cm.net/digimon/library-go/errs"
|
|
||||||
|
|
||||||
"app-cloudep-tweeting-service/gen_result/pb/tweeting"
|
|
||||||
"app-cloudep-tweeting-service/internal/svc"
|
|
||||||
|
|
||||||
"github.com/zeromicro/go-zero/core/logx"
|
|
||||||
)
|
|
||||||
|
|
||||||
type GetFolloweeCountLogic struct {
|
|
||||||
ctx context.Context
|
|
||||||
svcCtx *svc.ServiceContext
|
|
||||||
logx.Logger
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewGetFolloweeCountLogic(ctx context.Context, svcCtx *svc.ServiceContext) *GetFolloweeCountLogic {
|
|
||||||
return &GetFolloweeCountLogic{
|
|
||||||
ctx: ctx,
|
|
||||||
svcCtx: svcCtx,
|
|
||||||
Logger: logx.WithContext(ctx),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetFolloweeCount 取得我跟隨的數量
|
|
||||||
func (l *GetFolloweeCountLogic) GetFolloweeCount(in *tweeting.FollowCountReq) (*tweeting.FollowCountResp, error) {
|
|
||||||
// 驗證資料
|
|
||||||
if err := l.svcCtx.Validate.ValidateAll(&getFollowCountReq{
|
|
||||||
UID: in.Uid,
|
|
||||||
}); err != nil {
|
|
||||||
// 錯誤代碼 05-011-00
|
|
||||||
return nil, ers.InvalidFormat(err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
followeeCount, err := l.svcCtx.SocialNetworkRepository.GetFolloweeCount(l.ctx, in.GetUid())
|
|
||||||
if err != nil {
|
|
||||||
// 錯誤代碼 05-021-34
|
|
||||||
e := domain.CommentErrorL(
|
|
||||||
domain.GetFolloweeCountErrorCode,
|
|
||||||
logx.WithContext(l.ctx),
|
|
||||||
[]logx.LogField{
|
|
||||||
{Key: "req", Value: in},
|
|
||||||
{Key: "func", Value: "SocialNetworkRepository.GetFolloweeCount"},
|
|
||||||
{Key: "err", Value: err},
|
|
||||||
},
|
|
||||||
"failed to count follower").Wrap(err)
|
|
||||||
|
|
||||||
return nil, e
|
|
||||||
}
|
|
||||||
|
|
||||||
return &tweeting.FollowCountResp{
|
|
||||||
Uid: in.GetUid(),
|
|
||||||
Total: followeeCount,
|
|
||||||
}, nil
|
|
||||||
}
|
|
|
@ -1,88 +0,0 @@
|
||||||
package socialnetworkservicelogic
|
|
||||||
|
|
||||||
import (
|
|
||||||
"app-cloudep-tweeting-service/gen_result/pb/tweeting"
|
|
||||||
mocklib "app-cloudep-tweeting-service/internal/mock/lib"
|
|
||||||
mockRepo "app-cloudep-tweeting-service/internal/mock/repository"
|
|
||||||
"app-cloudep-tweeting-service/internal/svc"
|
|
||||||
"context"
|
|
||||||
"errors"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
"go.uber.org/mock/gomock"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestGetFolloweeCountLogic_GetFolloweeCount(t *testing.T) {
|
|
||||||
ctrl := gomock.NewController(t)
|
|
||||||
defer ctrl.Finish()
|
|
||||||
|
|
||||||
mockSocialNetworkRepository := mockRepo.NewMockSocialNetworkRepository(ctrl)
|
|
||||||
mockValidate := mocklib.NewMockValidate(ctrl)
|
|
||||||
|
|
||||||
svcCtx := &svc.ServiceContext{
|
|
||||||
SocialNetworkRepository: mockSocialNetworkRepository,
|
|
||||||
Validate: mockValidate,
|
|
||||||
}
|
|
||||||
|
|
||||||
// 测试数据集
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
input *tweeting.FollowCountReq
|
|
||||||
prepare func()
|
|
||||||
expectErr bool
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "ok",
|
|
||||||
input: &tweeting.FollowCountReq{
|
|
||||||
Uid: "12345",
|
|
||||||
},
|
|
||||||
prepare: func() {
|
|
||||||
mockValidate.EXPECT().ValidateAll(gomock.Any()).Return(nil).Times(1)
|
|
||||||
mockSocialNetworkRepository.EXPECT().GetFolloweeCount(gomock.Any(), "12345").Return(int64(10), nil).Times(1)
|
|
||||||
},
|
|
||||||
expectErr: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "驗證失敗",
|
|
||||||
input: &tweeting.FollowCountReq{
|
|
||||||
Uid: "",
|
|
||||||
},
|
|
||||||
prepare: func() {
|
|
||||||
mockValidate.EXPECT().ValidateAll(gomock.Any()).Return(errors.New("validation failed")).Times(1)
|
|
||||||
},
|
|
||||||
expectErr: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "取得跟隨數量失敗",
|
|
||||||
input: &tweeting.FollowCountReq{
|
|
||||||
Uid: "12345",
|
|
||||||
},
|
|
||||||
prepare: func() {
|
|
||||||
mockValidate.EXPECT().ValidateAll(gomock.Any()).Return(nil).Times(1)
|
|
||||||
mockSocialNetworkRepository.EXPECT().GetFolloweeCount(gomock.Any(), "12345").Return(int64(0), errors.New("repository error")).Times(1)
|
|
||||||
},
|
|
||||||
expectErr: true,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, tt := range tests {
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
|
||||||
tt.prepare()
|
|
||||||
|
|
||||||
logic := GetFolloweeCountLogic{
|
|
||||||
svcCtx: svcCtx,
|
|
||||||
ctx: context.TODO(),
|
|
||||||
}
|
|
||||||
|
|
||||||
got, err := logic.GetFolloweeCount(tt.input)
|
|
||||||
|
|
||||||
if tt.expectErr {
|
|
||||||
assert.Error(t, err)
|
|
||||||
} else {
|
|
||||||
assert.NoError(t, err)
|
|
||||||
assert.Equal(t, tt.input.Uid, got.Uid)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,70 +0,0 @@
|
||||||
package socialnetworkservicelogic
|
|
||||||
|
|
||||||
import (
|
|
||||||
"app-cloudep-tweeting-service/internal/domain"
|
|
||||||
"app-cloudep-tweeting-service/internal/domain/repository"
|
|
||||||
"context"
|
|
||||||
|
|
||||||
ers "code.30cm.net/digimon/library-go/errs"
|
|
||||||
|
|
||||||
"app-cloudep-tweeting-service/gen_result/pb/tweeting"
|
|
||||||
"app-cloudep-tweeting-service/internal/svc"
|
|
||||||
|
|
||||||
"github.com/zeromicro/go-zero/core/logx"
|
|
||||||
)
|
|
||||||
|
|
||||||
type GetFolloweeLogic struct {
|
|
||||||
ctx context.Context
|
|
||||||
svcCtx *svc.ServiceContext
|
|
||||||
logx.Logger
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewGetFolloweeLogic(ctx context.Context, svcCtx *svc.ServiceContext) *GetFolloweeLogic {
|
|
||||||
return &GetFolloweeLogic{
|
|
||||||
ctx: ctx,
|
|
||||||
svcCtx: svcCtx,
|
|
||||||
Logger: logx.WithContext(ctx),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetFollowee 取得我跟隨的名單
|
|
||||||
func (l *GetFolloweeLogic) GetFollowee(in *tweeting.FollowReq) (*tweeting.FollowResp, error) {
|
|
||||||
// 驗證資料
|
|
||||||
if err := l.svcCtx.Validate.ValidateAll(&getFollowReq{
|
|
||||||
UID: in.Uid,
|
|
||||||
PageSize: in.PageSize,
|
|
||||||
PageIndex: in.PageIndex,
|
|
||||||
}); err != nil {
|
|
||||||
// 錯誤代碼 05-011-00
|
|
||||||
return nil, ers.InvalidFormat(err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
followee, err := l.svcCtx.SocialNetworkRepository.GetFollowee(l.ctx, repository.FollowReq{
|
|
||||||
UID: in.GetUid(),
|
|
||||||
PageIndex: in.GetPageIndex(),
|
|
||||||
PageSize: in.GetPageSize(),
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
// 錯誤代碼 05-021-33
|
|
||||||
e := domain.CommentErrorL(
|
|
||||||
domain.GetFolloweeErrorCode,
|
|
||||||
logx.WithContext(l.ctx),
|
|
||||||
[]logx.LogField{
|
|
||||||
{Key: "req", Value: in},
|
|
||||||
{Key: "func", Value: "SocialNetworkRepository.GetFollowee"},
|
|
||||||
{Key: "err", Value: err},
|
|
||||||
},
|
|
||||||
"failed to get relation: ", in.GetUid()).Wrap(err)
|
|
||||||
|
|
||||||
return nil, e
|
|
||||||
}
|
|
||||||
|
|
||||||
return &tweeting.FollowResp{
|
|
||||||
Uid: followee.UIDs,
|
|
||||||
Page: &tweeting.Pager{
|
|
||||||
Total: followee.Total,
|
|
||||||
Index: in.GetPageIndex(),
|
|
||||||
Size: in.GetPageSize(),
|
|
||||||
},
|
|
||||||
}, nil
|
|
||||||
}
|
|
|
@ -1,130 +0,0 @@
|
||||||
package socialnetworkservicelogic
|
|
||||||
|
|
||||||
import (
|
|
||||||
"app-cloudep-tweeting-service/gen_result/pb/tweeting"
|
|
||||||
"app-cloudep-tweeting-service/internal/domain/repository"
|
|
||||||
mocklib "app-cloudep-tweeting-service/internal/mock/lib"
|
|
||||||
mockRepo "app-cloudep-tweeting-service/internal/mock/repository"
|
|
||||||
"app-cloudep-tweeting-service/internal/svc"
|
|
||||||
"context"
|
|
||||||
"errors"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
"go.uber.org/mock/gomock"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestGetFolloweeLogic_GetFollowee(t *testing.T) {
|
|
||||||
ctrl := gomock.NewController(t)
|
|
||||||
defer ctrl.Finish()
|
|
||||||
|
|
||||||
// 初始化 mock 依賴
|
|
||||||
mockSocialNetworkRepository := mockRepo.NewMockSocialNetworkRepository(ctrl)
|
|
||||||
mockValidate := mocklib.NewMockValidate(ctrl)
|
|
||||||
|
|
||||||
// 初始化服務上下文
|
|
||||||
svcCtx := &svc.ServiceContext{
|
|
||||||
SocialNetworkRepository: mockSocialNetworkRepository,
|
|
||||||
Validate: mockValidate,
|
|
||||||
}
|
|
||||||
|
|
||||||
// 測試數據集
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
input *tweeting.FollowReq
|
|
||||||
prepare func()
|
|
||||||
expectErr bool
|
|
||||||
wantResp *tweeting.FollowResp
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "成功獲取我跟隨的名單",
|
|
||||||
input: &tweeting.FollowReq{
|
|
||||||
Uid: "12345",
|
|
||||||
PageSize: 10,
|
|
||||||
PageIndex: 1,
|
|
||||||
},
|
|
||||||
prepare: func() {
|
|
||||||
// 模擬驗證通過
|
|
||||||
mockValidate.EXPECT().ValidateAll(gomock.Any()).Return(nil).Times(1)
|
|
||||||
// 模擬 GetFollowee 返回正確的結果
|
|
||||||
mockSocialNetworkRepository.EXPECT().GetFollowee(gomock.Any(), repository.FollowReq{
|
|
||||||
UID: "12345",
|
|
||||||
PageSize: 10,
|
|
||||||
PageIndex: 1,
|
|
||||||
}).Return(repository.FollowResp{
|
|
||||||
UIDs: []string{"user1", "user2"},
|
|
||||||
Total: 2,
|
|
||||||
}, nil).Times(1)
|
|
||||||
},
|
|
||||||
expectErr: false,
|
|
||||||
wantResp: &tweeting.FollowResp{
|
|
||||||
Uid: []string{"user1", "user2"},
|
|
||||||
Page: &tweeting.Pager{
|
|
||||||
Total: 2,
|
|
||||||
Index: 1,
|
|
||||||
Size: 10,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "驗證失敗",
|
|
||||||
input: &tweeting.FollowReq{
|
|
||||||
Uid: "",
|
|
||||||
PageSize: 10,
|
|
||||||
PageIndex: 1,
|
|
||||||
},
|
|
||||||
prepare: func() {
|
|
||||||
// 模擬驗證失敗
|
|
||||||
mockValidate.EXPECT().ValidateAll(gomock.Any()).Return(errors.New("validation failed")).Times(1)
|
|
||||||
},
|
|
||||||
expectErr: true,
|
|
||||||
wantResp: nil,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "獲取我跟隨的名單失敗",
|
|
||||||
input: &tweeting.FollowReq{
|
|
||||||
Uid: "12345",
|
|
||||||
PageSize: 10,
|
|
||||||
PageIndex: 1,
|
|
||||||
},
|
|
||||||
prepare: func() {
|
|
||||||
// 模擬驗證通過
|
|
||||||
mockValidate.EXPECT().ValidateAll(gomock.Any()).Return(nil).Times(1)
|
|
||||||
// 模擬 GetFollowee 返回錯誤
|
|
||||||
mockSocialNetworkRepository.EXPECT().GetFollowee(gomock.Any(), repository.FollowReq{
|
|
||||||
UID: "12345",
|
|
||||||
PageSize: 10,
|
|
||||||
PageIndex: 1,
|
|
||||||
}).Return(repository.FollowResp{}, errors.New("repository error")).Times(1)
|
|
||||||
},
|
|
||||||
expectErr: true,
|
|
||||||
wantResp: nil,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
// 執行測試
|
|
||||||
for _, tt := range tests {
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
|
||||||
// 設置測試環境
|
|
||||||
tt.prepare()
|
|
||||||
|
|
||||||
// 初始化 GetFolloweeLogic
|
|
||||||
logic := GetFolloweeLogic{
|
|
||||||
svcCtx: svcCtx,
|
|
||||||
ctx: context.TODO(),
|
|
||||||
}
|
|
||||||
|
|
||||||
// 執行 GetFollowee
|
|
||||||
got, err := logic.GetFollowee(tt.input)
|
|
||||||
|
|
||||||
// 驗證結果
|
|
||||||
if tt.expectErr {
|
|
||||||
assert.Error(t, err)
|
|
||||||
assert.Nil(t, got)
|
|
||||||
} else {
|
|
||||||
assert.NoError(t, err)
|
|
||||||
assert.Equal(t, tt.wantResp, got)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,63 +0,0 @@
|
||||||
package socialnetworkservicelogic
|
|
||||||
|
|
||||||
import (
|
|
||||||
"app-cloudep-tweeting-service/internal/domain"
|
|
||||||
"context"
|
|
||||||
|
|
||||||
ers "code.30cm.net/digimon/library-go/errs"
|
|
||||||
|
|
||||||
"app-cloudep-tweeting-service/gen_result/pb/tweeting"
|
|
||||||
"app-cloudep-tweeting-service/internal/svc"
|
|
||||||
|
|
||||||
"github.com/zeromicro/go-zero/core/logx"
|
|
||||||
)
|
|
||||||
|
|
||||||
type GetFollowerCountLogic struct {
|
|
||||||
ctx context.Context
|
|
||||||
svcCtx *svc.ServiceContext
|
|
||||||
logx.Logger
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewGetFollowerCountLogic(ctx context.Context, svcCtx *svc.ServiceContext) *GetFollowerCountLogic {
|
|
||||||
return &GetFollowerCountLogic{
|
|
||||||
ctx: ctx,
|
|
||||||
svcCtx: svcCtx,
|
|
||||||
Logger: logx.WithContext(ctx),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type getFollowCountReq struct {
|
|
||||||
UID string `validate:"required"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetFollowerCount 取得跟隨者數量
|
|
||||||
func (l *GetFollowerCountLogic) GetFollowerCount(in *tweeting.FollowCountReq) (*tweeting.FollowCountResp, error) {
|
|
||||||
// 驗證資料
|
|
||||||
if err := l.svcCtx.Validate.ValidateAll(&getFollowCountReq{
|
|
||||||
UID: in.Uid,
|
|
||||||
}); err != nil {
|
|
||||||
// 錯誤代碼 05-011-00
|
|
||||||
return nil, ers.InvalidFormat(err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
followerCount, err := l.svcCtx.SocialNetworkRepository.GetFollowerCount(l.ctx, in.GetUid())
|
|
||||||
if err != nil {
|
|
||||||
// 錯誤代碼 05-021-32
|
|
||||||
e := domain.CommentErrorL(
|
|
||||||
domain.GetFollowerCountErrorCode,
|
|
||||||
logx.WithContext(l.ctx),
|
|
||||||
[]logx.LogField{
|
|
||||||
{Key: "req", Value: in},
|
|
||||||
{Key: "func", Value: "SocialNetworkRepository.GetFollowerCount"},
|
|
||||||
{Key: "err", Value: err},
|
|
||||||
},
|
|
||||||
"failed to count follower").Wrap(err)
|
|
||||||
|
|
||||||
return nil, e
|
|
||||||
}
|
|
||||||
|
|
||||||
return &tweeting.FollowCountResp{
|
|
||||||
Uid: in.GetUid(),
|
|
||||||
Total: followerCount,
|
|
||||||
}, nil
|
|
||||||
}
|
|
|
@ -1,108 +0,0 @@
|
||||||
package socialnetworkservicelogic
|
|
||||||
|
|
||||||
import (
|
|
||||||
"app-cloudep-tweeting-service/gen_result/pb/tweeting"
|
|
||||||
mocklib "app-cloudep-tweeting-service/internal/mock/lib"
|
|
||||||
mockRepo "app-cloudep-tweeting-service/internal/mock/repository"
|
|
||||||
"app-cloudep-tweeting-service/internal/svc"
|
|
||||||
"context"
|
|
||||||
"errors"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
"go.uber.org/mock/gomock"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestGetFollowerCountLogic_GetFollowerCount(t *testing.T) {
|
|
||||||
ctrl := gomock.NewController(t)
|
|
||||||
defer ctrl.Finish()
|
|
||||||
|
|
||||||
// 初始化 mock 依賴
|
|
||||||
mockSocialNetworkRepository := mockRepo.NewMockSocialNetworkRepository(ctrl)
|
|
||||||
mockValidate := mocklib.NewMockValidate(ctrl)
|
|
||||||
|
|
||||||
// 初始化服務上下文
|
|
||||||
svcCtx := &svc.ServiceContext{
|
|
||||||
SocialNetworkRepository: mockSocialNetworkRepository,
|
|
||||||
Validate: mockValidate,
|
|
||||||
}
|
|
||||||
|
|
||||||
// 測試數據集
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
input *tweeting.FollowCountReq
|
|
||||||
prepare func()
|
|
||||||
expectErr bool
|
|
||||||
wantResp *tweeting.FollowCountResp
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "成功獲取跟隨者數量",
|
|
||||||
input: &tweeting.FollowCountReq{
|
|
||||||
Uid: "12345",
|
|
||||||
},
|
|
||||||
prepare: func() {
|
|
||||||
// 模擬驗證通過
|
|
||||||
mockValidate.EXPECT().ValidateAll(gomock.Any()).Return(nil).Times(1)
|
|
||||||
// 模擬 GetFollowerCount 返回正確的結果
|
|
||||||
mockSocialNetworkRepository.EXPECT().GetFollowerCount(gomock.Any(), "12345").Return(int64(10), nil).Times(1)
|
|
||||||
},
|
|
||||||
expectErr: false,
|
|
||||||
wantResp: &tweeting.FollowCountResp{
|
|
||||||
Uid: "12345",
|
|
||||||
Total: 10,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "驗證失敗",
|
|
||||||
input: &tweeting.FollowCountReq{
|
|
||||||
Uid: "",
|
|
||||||
},
|
|
||||||
prepare: func() {
|
|
||||||
// 模擬驗證失敗
|
|
||||||
mockValidate.EXPECT().ValidateAll(gomock.Any()).Return(errors.New("validation failed")).Times(1)
|
|
||||||
},
|
|
||||||
expectErr: true,
|
|
||||||
wantResp: nil,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "獲取跟隨者數量失敗",
|
|
||||||
input: &tweeting.FollowCountReq{
|
|
||||||
Uid: "12345",
|
|
||||||
},
|
|
||||||
prepare: func() {
|
|
||||||
// 模擬驗證通過
|
|
||||||
mockValidate.EXPECT().ValidateAll(gomock.Any()).Return(nil).Times(1)
|
|
||||||
// 模擬 GetFollowerCount 返回錯誤
|
|
||||||
mockSocialNetworkRepository.EXPECT().GetFollowerCount(gomock.Any(), "12345").Return(int64(0), errors.New("repository error")).Times(1)
|
|
||||||
},
|
|
||||||
expectErr: true,
|
|
||||||
wantResp: nil,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
// 執行測試
|
|
||||||
for _, tt := range tests {
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
|
||||||
// 設置測試環境
|
|
||||||
tt.prepare()
|
|
||||||
|
|
||||||
// 初始化 GetFollowerCountLogic
|
|
||||||
logic := GetFollowerCountLogic{
|
|
||||||
svcCtx: svcCtx,
|
|
||||||
ctx: context.TODO(),
|
|
||||||
}
|
|
||||||
|
|
||||||
// 執行 GetFollowerCount
|
|
||||||
got, err := logic.GetFollowerCount(tt.input)
|
|
||||||
|
|
||||||
// 驗證結果
|
|
||||||
if tt.expectErr {
|
|
||||||
assert.Error(t, err)
|
|
||||||
assert.Nil(t, got)
|
|
||||||
} else {
|
|
||||||
assert.NoError(t, err)
|
|
||||||
assert.Equal(t, tt.wantResp, got)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,76 +0,0 @@
|
||||||
package socialnetworkservicelogic
|
|
||||||
|
|
||||||
import (
|
|
||||||
"app-cloudep-tweeting-service/internal/domain"
|
|
||||||
"app-cloudep-tweeting-service/internal/domain/repository"
|
|
||||||
"context"
|
|
||||||
|
|
||||||
ers "code.30cm.net/digimon/library-go/errs"
|
|
||||||
|
|
||||||
"app-cloudep-tweeting-service/gen_result/pb/tweeting"
|
|
||||||
"app-cloudep-tweeting-service/internal/svc"
|
|
||||||
|
|
||||||
"github.com/zeromicro/go-zero/core/logx"
|
|
||||||
)
|
|
||||||
|
|
||||||
type GetFollowerLogic struct {
|
|
||||||
ctx context.Context
|
|
||||||
svcCtx *svc.ServiceContext
|
|
||||||
logx.Logger
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewGetFollowerLogic(ctx context.Context, svcCtx *svc.ServiceContext) *GetFollowerLogic {
|
|
||||||
return &GetFollowerLogic{
|
|
||||||
ctx: ctx,
|
|
||||||
svcCtx: svcCtx,
|
|
||||||
Logger: logx.WithContext(ctx),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type getFollowReq struct {
|
|
||||||
UID string `validate:"required"`
|
|
||||||
PageSize int64 `validate:"required"`
|
|
||||||
PageIndex int64 `validate:"required"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetFollower 取得跟隨者名單
|
|
||||||
func (l *GetFollowerLogic) GetFollower(in *tweeting.FollowReq) (*tweeting.FollowResp, error) {
|
|
||||||
// 驗證資料
|
|
||||||
if err := l.svcCtx.Validate.ValidateAll(&getFollowReq{
|
|
||||||
UID: in.Uid,
|
|
||||||
PageSize: in.PageSize,
|
|
||||||
PageIndex: in.PageIndex,
|
|
||||||
}); err != nil {
|
|
||||||
// 錯誤代碼 05-011-00
|
|
||||||
return nil, ers.InvalidFormat(err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
follower, err := l.svcCtx.SocialNetworkRepository.GetFollower(l.ctx, repository.FollowReq{
|
|
||||||
UID: in.GetUid(),
|
|
||||||
PageIndex: in.GetPageIndex(),
|
|
||||||
PageSize: in.GetPageSize(),
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
// 錯誤代碼 05-021-31
|
|
||||||
e := domain.CommentErrorL(
|
|
||||||
domain.GetFollowerErrorCode,
|
|
||||||
logx.WithContext(l.ctx),
|
|
||||||
[]logx.LogField{
|
|
||||||
{Key: "req", Value: in},
|
|
||||||
{Key: "func", Value: "SocialNetworkRepository.GetFollower"},
|
|
||||||
{Key: "err", Value: err},
|
|
||||||
},
|
|
||||||
"failed to get relation: ", in.GetUid()).Wrap(err)
|
|
||||||
|
|
||||||
return nil, e
|
|
||||||
}
|
|
||||||
|
|
||||||
return &tweeting.FollowResp{
|
|
||||||
Uid: follower.UIDs,
|
|
||||||
Page: &tweeting.Pager{
|
|
||||||
Total: follower.Total,
|
|
||||||
Index: in.GetPageIndex(),
|
|
||||||
Size: in.GetPageSize(),
|
|
||||||
},
|
|
||||||
}, nil
|
|
||||||
}
|
|
|
@ -1,130 +0,0 @@
|
||||||
package socialnetworkservicelogic
|
|
||||||
|
|
||||||
import (
|
|
||||||
"app-cloudep-tweeting-service/gen_result/pb/tweeting"
|
|
||||||
"app-cloudep-tweeting-service/internal/domain/repository"
|
|
||||||
mocklib "app-cloudep-tweeting-service/internal/mock/lib"
|
|
||||||
mockRepo "app-cloudep-tweeting-service/internal/mock/repository"
|
|
||||||
"app-cloudep-tweeting-service/internal/svc"
|
|
||||||
"context"
|
|
||||||
"errors"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
"go.uber.org/mock/gomock"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestGetFollowerLogic_GetFollower(t *testing.T) {
|
|
||||||
ctrl := gomock.NewController(t)
|
|
||||||
defer ctrl.Finish()
|
|
||||||
|
|
||||||
// 初始化 mock 依賴
|
|
||||||
mockSocialNetworkRepository := mockRepo.NewMockSocialNetworkRepository(ctrl)
|
|
||||||
mockValidate := mocklib.NewMockValidate(ctrl)
|
|
||||||
|
|
||||||
// 初始化服務上下文
|
|
||||||
svcCtx := &svc.ServiceContext{
|
|
||||||
SocialNetworkRepository: mockSocialNetworkRepository,
|
|
||||||
Validate: mockValidate,
|
|
||||||
}
|
|
||||||
|
|
||||||
// 測試數據集
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
input *tweeting.FollowReq
|
|
||||||
prepare func()
|
|
||||||
expectErr bool
|
|
||||||
wantResp *tweeting.FollowResp
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "成功獲取跟隨者名單",
|
|
||||||
input: &tweeting.FollowReq{
|
|
||||||
Uid: "12345",
|
|
||||||
PageSize: 10,
|
|
||||||
PageIndex: 1,
|
|
||||||
},
|
|
||||||
prepare: func() {
|
|
||||||
// 模擬驗證通過
|
|
||||||
mockValidate.EXPECT().ValidateAll(gomock.Any()).Return(nil).Times(1)
|
|
||||||
// 模擬 GetFollower 返回正確的結果
|
|
||||||
mockSocialNetworkRepository.EXPECT().GetFollower(gomock.Any(), repository.FollowReq{
|
|
||||||
UID: "12345",
|
|
||||||
PageSize: 10,
|
|
||||||
PageIndex: 1,
|
|
||||||
}).Return(repository.FollowResp{
|
|
||||||
UIDs: []string{"user1", "user2"},
|
|
||||||
Total: 2,
|
|
||||||
}, nil).Times(1)
|
|
||||||
},
|
|
||||||
expectErr: false,
|
|
||||||
wantResp: &tweeting.FollowResp{
|
|
||||||
Uid: []string{"user1", "user2"},
|
|
||||||
Page: &tweeting.Pager{
|
|
||||||
Total: 2,
|
|
||||||
Index: 1,
|
|
||||||
Size: 10,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "驗證失敗",
|
|
||||||
input: &tweeting.FollowReq{
|
|
||||||
Uid: "",
|
|
||||||
PageSize: 10,
|
|
||||||
PageIndex: 1,
|
|
||||||
},
|
|
||||||
prepare: func() {
|
|
||||||
// 模擬驗證失敗
|
|
||||||
mockValidate.EXPECT().ValidateAll(gomock.Any()).Return(errors.New("validation failed")).Times(1)
|
|
||||||
},
|
|
||||||
expectErr: true,
|
|
||||||
wantResp: nil,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "獲取跟隨者名單失敗",
|
|
||||||
input: &tweeting.FollowReq{
|
|
||||||
Uid: "12345",
|
|
||||||
PageSize: 10,
|
|
||||||
PageIndex: 1,
|
|
||||||
},
|
|
||||||
prepare: func() {
|
|
||||||
// 模擬驗證通過
|
|
||||||
mockValidate.EXPECT().ValidateAll(gomock.Any()).Return(nil).Times(1)
|
|
||||||
// 模擬 GetFollower 返回錯誤
|
|
||||||
mockSocialNetworkRepository.EXPECT().GetFollower(gomock.Any(), repository.FollowReq{
|
|
||||||
UID: "12345",
|
|
||||||
PageSize: 10,
|
|
||||||
PageIndex: 1,
|
|
||||||
}).Return(repository.FollowResp{}, errors.New("repository error")).Times(1)
|
|
||||||
},
|
|
||||||
expectErr: true,
|
|
||||||
wantResp: nil,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
// 執行測試
|
|
||||||
for _, tt := range tests {
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
|
||||||
// 設置測試環境
|
|
||||||
tt.prepare()
|
|
||||||
|
|
||||||
// 初始化 GetFollowerLogic
|
|
||||||
logic := GetFollowerLogic{
|
|
||||||
svcCtx: svcCtx,
|
|
||||||
ctx: context.TODO(),
|
|
||||||
}
|
|
||||||
|
|
||||||
// 執行 GetFollower
|
|
||||||
got, err := logic.GetFollower(tt.input)
|
|
||||||
|
|
||||||
// 驗證結果
|
|
||||||
if tt.expectErr {
|
|
||||||
assert.Error(t, err)
|
|
||||||
assert.Nil(t, got)
|
|
||||||
} else {
|
|
||||||
assert.NoError(t, err)
|
|
||||||
assert.Equal(t, tt.wantResp, got)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,63 +0,0 @@
|
||||||
package socialnetworkservicelogic
|
|
||||||
|
|
||||||
import (
|
|
||||||
"app-cloudep-tweeting-service/internal/domain"
|
|
||||||
"context"
|
|
||||||
|
|
||||||
ers "code.30cm.net/digimon/library-go/errs"
|
|
||||||
|
|
||||||
"app-cloudep-tweeting-service/gen_result/pb/tweeting"
|
|
||||||
"app-cloudep-tweeting-service/internal/svc"
|
|
||||||
|
|
||||||
"github.com/zeromicro/go-zero/core/logx"
|
|
||||||
)
|
|
||||||
|
|
||||||
type MarkFollowRelationLogic struct {
|
|
||||||
ctx context.Context
|
|
||||||
svcCtx *svc.ServiceContext
|
|
||||||
logx.Logger
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewMarkFollowRelationLogic(ctx context.Context, svcCtx *svc.ServiceContext) *MarkFollowRelationLogic {
|
|
||||||
return &MarkFollowRelationLogic{
|
|
||||||
ctx: ctx,
|
|
||||||
svcCtx: svcCtx,
|
|
||||||
Logger: logx.WithContext(ctx),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type doFollowReq struct {
|
|
||||||
FollowerUID string `json:"follower_uid" validate:"required"` // 追隨者,跟隨你的人(別人關注你)
|
|
||||||
FolloweeUID string `json:"followee_uid" validate:"required"` // 追蹤者,你跟隨的人(你關注別)
|
|
||||||
}
|
|
||||||
|
|
||||||
// MarkFollowRelation 關注
|
|
||||||
func (l *MarkFollowRelationLogic) MarkFollowRelation(in *tweeting.DoFollowerRelationReq) (*tweeting.OKResp, error) {
|
|
||||||
// 驗證資料
|
|
||||||
if err := l.svcCtx.Validate.ValidateAll(&doFollowReq{
|
|
||||||
FollowerUID: in.GetFollowerUid(),
|
|
||||||
FolloweeUID: in.GetFolloweeUid(),
|
|
||||||
}); err != nil {
|
|
||||||
// 錯誤代碼 05-011-00
|
|
||||||
return nil, ers.InvalidFormat(err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
// 這裡要幫建立關係, follower 追蹤 -> followee
|
|
||||||
err := l.svcCtx.SocialNetworkRepository.MarkFollowerRelation(l.ctx, in.GetFollowerUid(), in.GetFolloweeUid())
|
|
||||||
if err != nil {
|
|
||||||
// 錯誤代碼 05-021-30
|
|
||||||
e := domain.CommentErrorL(
|
|
||||||
domain.MarkRelationErrorCode,
|
|
||||||
logx.WithContext(l.ctx),
|
|
||||||
[]logx.LogField{
|
|
||||||
{Key: "req", Value: in},
|
|
||||||
{Key: "func", Value: "SocialNetworkRepository.MarkFollowerRelationBetweenUsers"},
|
|
||||||
{Key: "err", Value: err},
|
|
||||||
},
|
|
||||||
"failed to mark relation form -> to", in.GetFollowerUid(), in.GetFolloweeUid()).Wrap(err)
|
|
||||||
|
|
||||||
return nil, e
|
|
||||||
}
|
|
||||||
|
|
||||||
return &tweeting.OKResp{}, nil
|
|
||||||
}
|
|
|
@ -1,108 +0,0 @@
|
||||||
package socialnetworkservicelogic
|
|
||||||
|
|
||||||
import (
|
|
||||||
"app-cloudep-tweeting-service/gen_result/pb/tweeting"
|
|
||||||
mocklib "app-cloudep-tweeting-service/internal/mock/lib"
|
|
||||||
mockRepo "app-cloudep-tweeting-service/internal/mock/repository"
|
|
||||||
"app-cloudep-tweeting-service/internal/svc"
|
|
||||||
"context"
|
|
||||||
"errors"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
"go.uber.org/mock/gomock"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestMarkFollowRelationLogic_MarkFollowRelation(t *testing.T) {
|
|
||||||
ctrl := gomock.NewController(t)
|
|
||||||
defer ctrl.Finish()
|
|
||||||
|
|
||||||
// 初始化 mock 依賴
|
|
||||||
mockSocialNetworkRepository := mockRepo.NewMockSocialNetworkRepository(ctrl)
|
|
||||||
mockValidate := mocklib.NewMockValidate(ctrl)
|
|
||||||
|
|
||||||
// 初始化服務上下文
|
|
||||||
svcCtx := &svc.ServiceContext{
|
|
||||||
SocialNetworkRepository: mockSocialNetworkRepository,
|
|
||||||
Validate: mockValidate,
|
|
||||||
}
|
|
||||||
|
|
||||||
// 測試數據集
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
input *tweeting.DoFollowerRelationReq
|
|
||||||
prepare func()
|
|
||||||
expectErr bool
|
|
||||||
wantResp *tweeting.OKResp
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "成功建立關注關係",
|
|
||||||
input: &tweeting.DoFollowerRelationReq{
|
|
||||||
FollowerUid: "follower123",
|
|
||||||
FolloweeUid: "followee456",
|
|
||||||
},
|
|
||||||
prepare: func() {
|
|
||||||
// 模擬驗證通過
|
|
||||||
mockValidate.EXPECT().ValidateAll(gomock.Any()).Return(nil).Times(1)
|
|
||||||
// 模擬成功建立關注關係
|
|
||||||
mockSocialNetworkRepository.EXPECT().MarkFollowerRelation(gomock.Any(), "follower123", "followee456").Return(nil).Times(1)
|
|
||||||
},
|
|
||||||
expectErr: false,
|
|
||||||
wantResp: &tweeting.OKResp{},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "驗證失敗",
|
|
||||||
input: &tweeting.DoFollowerRelationReq{
|
|
||||||
FollowerUid: "",
|
|
||||||
FolloweeUid: "",
|
|
||||||
},
|
|
||||||
prepare: func() {
|
|
||||||
// 模擬驗證失敗
|
|
||||||
mockValidate.EXPECT().ValidateAll(gomock.Any()).Return(errors.New("validation failed")).Times(1)
|
|
||||||
},
|
|
||||||
expectErr: true,
|
|
||||||
wantResp: nil,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "建立關注關係失敗",
|
|
||||||
input: &tweeting.DoFollowerRelationReq{
|
|
||||||
FollowerUid: "follower123",
|
|
||||||
FolloweeUid: "followee456",
|
|
||||||
},
|
|
||||||
prepare: func() {
|
|
||||||
// 模擬驗證通過
|
|
||||||
mockValidate.EXPECT().ValidateAll(gomock.Any()).Return(nil).Times(1)
|
|
||||||
// 模擬建立關注關係失敗
|
|
||||||
mockSocialNetworkRepository.EXPECT().MarkFollowerRelation(gomock.Any(), "follower123", "followee456").Return(errors.New("repository error")).Times(1)
|
|
||||||
},
|
|
||||||
expectErr: true,
|
|
||||||
wantResp: nil,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
// 執行測試
|
|
||||||
for _, tt := range tests {
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
|
||||||
// 設置測試環境
|
|
||||||
tt.prepare()
|
|
||||||
|
|
||||||
// 初始化 MarkFollowRelationLogic
|
|
||||||
logic := MarkFollowRelationLogic{
|
|
||||||
svcCtx: svcCtx,
|
|
||||||
ctx: context.TODO(),
|
|
||||||
}
|
|
||||||
|
|
||||||
// 執行 MarkFollowRelation
|
|
||||||
got, err := logic.MarkFollowRelation(tt.input)
|
|
||||||
|
|
||||||
// 驗證結果
|
|
||||||
if tt.expectErr {
|
|
||||||
assert.Error(t, err)
|
|
||||||
assert.Nil(t, got)
|
|
||||||
} else {
|
|
||||||
assert.NoError(t, err)
|
|
||||||
assert.Equal(t, tt.wantResp, got)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,58 +0,0 @@
|
||||||
package socialnetworkservicelogic
|
|
||||||
|
|
||||||
import (
|
|
||||||
"app-cloudep-tweeting-service/internal/domain"
|
|
||||||
"context"
|
|
||||||
|
|
||||||
ers "code.30cm.net/digimon/library-go/errs"
|
|
||||||
|
|
||||||
"app-cloudep-tweeting-service/gen_result/pb/tweeting"
|
|
||||||
"app-cloudep-tweeting-service/internal/svc"
|
|
||||||
|
|
||||||
"github.com/zeromicro/go-zero/core/logx"
|
|
||||||
)
|
|
||||||
|
|
||||||
type RemoveFollowRelationLogic struct {
|
|
||||||
ctx context.Context
|
|
||||||
svcCtx *svc.ServiceContext
|
|
||||||
logx.Logger
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewRemoveFollowRelationLogic(ctx context.Context, svcCtx *svc.ServiceContext) *RemoveFollowRelationLogic {
|
|
||||||
return &RemoveFollowRelationLogic{
|
|
||||||
ctx: ctx,
|
|
||||||
svcCtx: svcCtx,
|
|
||||||
Logger: logx.WithContext(ctx),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// RemoveFollowRelation 取消關注
|
|
||||||
func (l *RemoveFollowRelationLogic) RemoveFollowRelation(in *tweeting.DoFollowerRelationReq) (*tweeting.OKResp, error) {
|
|
||||||
// 驗證資料
|
|
||||||
if err := l.svcCtx.Validate.ValidateAll(&doFollowReq{
|
|
||||||
FollowerUID: in.GetFollowerUid(),
|
|
||||||
FolloweeUID: in.GetFolloweeUid(),
|
|
||||||
}); err != nil {
|
|
||||||
// 錯誤代碼 05-011-00
|
|
||||||
return nil, ers.InvalidFormat(err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
// 這裡要幫刪除關係, follower 追蹤 -> followee
|
|
||||||
err := l.svcCtx.SocialNetworkRepository.RemoveFollowerRelation(l.ctx, in.GetFollowerUid(), in.GetFolloweeUid())
|
|
||||||
if err != nil {
|
|
||||||
// 錯誤代碼 05-021-35
|
|
||||||
e := domain.CommentErrorL(
|
|
||||||
domain.RemoveRelationErrorCode,
|
|
||||||
logx.WithContext(l.ctx),
|
|
||||||
[]logx.LogField{
|
|
||||||
{Key: "req", Value: in},
|
|
||||||
{Key: "func", Value: "SocialNetworkRepository.RemoveFollowerRelation"},
|
|
||||||
{Key: "err", Value: err},
|
|
||||||
},
|
|
||||||
"failed to remove relation form -> to", in.GetFollowerUid(), in.GetFolloweeUid()).Wrap(err)
|
|
||||||
|
|
||||||
return nil, e
|
|
||||||
}
|
|
||||||
|
|
||||||
return &tweeting.OKResp{}, nil
|
|
||||||
}
|
|
|
@ -1,108 +0,0 @@
|
||||||
package socialnetworkservicelogic
|
|
||||||
|
|
||||||
import (
|
|
||||||
"app-cloudep-tweeting-service/gen_result/pb/tweeting"
|
|
||||||
mocklib "app-cloudep-tweeting-service/internal/mock/lib"
|
|
||||||
mockRepo "app-cloudep-tweeting-service/internal/mock/repository"
|
|
||||||
"app-cloudep-tweeting-service/internal/svc"
|
|
||||||
"context"
|
|
||||||
"errors"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
"go.uber.org/mock/gomock"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestRemoveFollowRelationLogic_RemoveFollowRelation(t *testing.T) {
|
|
||||||
ctrl := gomock.NewController(t)
|
|
||||||
defer ctrl.Finish()
|
|
||||||
|
|
||||||
// 初始化 mock 依賴
|
|
||||||
mockSocialNetworkRepository := mockRepo.NewMockSocialNetworkRepository(ctrl)
|
|
||||||
mockValidate := mocklib.NewMockValidate(ctrl)
|
|
||||||
|
|
||||||
// 初始化服務上下文
|
|
||||||
svcCtx := &svc.ServiceContext{
|
|
||||||
SocialNetworkRepository: mockSocialNetworkRepository,
|
|
||||||
Validate: mockValidate,
|
|
||||||
}
|
|
||||||
|
|
||||||
// 測試數據集
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
input *tweeting.DoFollowerRelationReq
|
|
||||||
prepare func()
|
|
||||||
expectErr bool
|
|
||||||
wantResp *tweeting.OKResp
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "成功取消關注",
|
|
||||||
input: &tweeting.DoFollowerRelationReq{
|
|
||||||
FollowerUid: "follower123",
|
|
||||||
FolloweeUid: "followee456",
|
|
||||||
},
|
|
||||||
prepare: func() {
|
|
||||||
// 模擬驗證通過
|
|
||||||
mockValidate.EXPECT().ValidateAll(gomock.Any()).Return(nil).Times(1)
|
|
||||||
// 模擬成功刪除關注關係
|
|
||||||
mockSocialNetworkRepository.EXPECT().RemoveFollowerRelation(gomock.Any(), "follower123", "followee456").Return(nil).Times(1)
|
|
||||||
},
|
|
||||||
expectErr: false,
|
|
||||||
wantResp: &tweeting.OKResp{},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "驗證失敗",
|
|
||||||
input: &tweeting.DoFollowerRelationReq{
|
|
||||||
FollowerUid: "",
|
|
||||||
FolloweeUid: "",
|
|
||||||
},
|
|
||||||
prepare: func() {
|
|
||||||
// 模擬驗證失敗
|
|
||||||
mockValidate.EXPECT().ValidateAll(gomock.Any()).Return(errors.New("validation failed")).Times(1)
|
|
||||||
},
|
|
||||||
expectErr: true,
|
|
||||||
wantResp: nil,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "取消關注失敗",
|
|
||||||
input: &tweeting.DoFollowerRelationReq{
|
|
||||||
FollowerUid: "follower123",
|
|
||||||
FolloweeUid: "followee456",
|
|
||||||
},
|
|
||||||
prepare: func() {
|
|
||||||
// 模擬驗證通過
|
|
||||||
mockValidate.EXPECT().ValidateAll(gomock.Any()).Return(nil).Times(1)
|
|
||||||
// 模擬刪除關注關係失敗
|
|
||||||
mockSocialNetworkRepository.EXPECT().RemoveFollowerRelation(gomock.Any(), "follower123", "followee456").Return(errors.New("repository error")).Times(1)
|
|
||||||
},
|
|
||||||
expectErr: true,
|
|
||||||
wantResp: nil,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
// 執行測試
|
|
||||||
for _, tt := range tests {
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
|
||||||
// 設置測試環境
|
|
||||||
tt.prepare()
|
|
||||||
|
|
||||||
// 初始化 RemoveFollowRelationLogic
|
|
||||||
logic := RemoveFollowRelationLogic{
|
|
||||||
svcCtx: svcCtx,
|
|
||||||
ctx: context.TODO(),
|
|
||||||
}
|
|
||||||
|
|
||||||
// 執行 RemoveFollowRelation
|
|
||||||
got, err := logic.RemoveFollowRelation(tt.input)
|
|
||||||
|
|
||||||
// 驗證結果
|
|
||||||
if tt.expectErr {
|
|
||||||
assert.Error(t, err)
|
|
||||||
assert.Nil(t, got)
|
|
||||||
} else {
|
|
||||||
assert.NoError(t, err)
|
|
||||||
assert.Equal(t, tt.wantResp, got)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,77 +0,0 @@
|
||||||
package timelineservicelogic
|
|
||||||
|
|
||||||
import (
|
|
||||||
"app-cloudep-tweeting-service/internal/domain"
|
|
||||||
"app-cloudep-tweeting-service/internal/domain/repository"
|
|
||||||
"context"
|
|
||||||
|
|
||||||
ers "code.30cm.net/digimon/library-go/errs"
|
|
||||||
|
|
||||||
"app-cloudep-tweeting-service/gen_result/pb/tweeting"
|
|
||||||
"app-cloudep-tweeting-service/internal/svc"
|
|
||||||
|
|
||||||
"github.com/zeromicro/go-zero/core/logx"
|
|
||||||
)
|
|
||||||
|
|
||||||
type AddPostLogic struct {
|
|
||||||
ctx context.Context
|
|
||||||
svcCtx *svc.ServiceContext
|
|
||||||
logx.Logger
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewAddPostLogic(ctx context.Context, svcCtx *svc.ServiceContext) *AddPostLogic {
|
|
||||||
return &AddPostLogic{
|
|
||||||
ctx: ctx,
|
|
||||||
svcCtx: svcCtx,
|
|
||||||
Logger: logx.WithContext(ctx),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type addPostReq struct {
|
|
||||||
UID string `json:"uid" validate:"required"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// AddPost 加入貼文,只管一股腦全塞,這裡會自動判斷
|
|
||||||
func (l *AddPostLogic) AddPost(in *tweeting.AddPostToTimelineReq) (*tweeting.OKResp, error) {
|
|
||||||
// 驗證資料
|
|
||||||
if err := l.svcCtx.Validate.ValidateAll(&addPostReq{
|
|
||||||
UID: in.GetUid(),
|
|
||||||
}); err != nil {
|
|
||||||
// 錯誤代碼 05-011-00
|
|
||||||
return nil, ers.InvalidFormat(err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(in.GetPosts()) == 0 {
|
|
||||||
// 沒資料,直接 OK
|
|
||||||
return &tweeting.OKResp{}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
post := make([]repository.TimelineItem, 0, len(in.GetPosts()))
|
|
||||||
for _, item := range in.GetPosts() {
|
|
||||||
post = append(post, repository.TimelineItem{
|
|
||||||
PostID: item.PostId,
|
|
||||||
Score: item.CreatedAt,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
err := l.svcCtx.TimelineRepo.AddPost(l.ctx, repository.AddPostRequest{
|
|
||||||
UID: in.GetUid(),
|
|
||||||
PostItems: post,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
// 錯誤代碼 05-021-20
|
|
||||||
e := domain.CommentErrorL(
|
|
||||||
domain.AddTimeLineErrorCode,
|
|
||||||
logx.WithContext(l.ctx),
|
|
||||||
[]logx.LogField{
|
|
||||||
{Key: "req", Value: in},
|
|
||||||
{Key: "func", Value: "TimelineRepo.AddPost"},
|
|
||||||
{Key: "err", Value: err},
|
|
||||||
},
|
|
||||||
"failed to insert timeline repo :", in.GetUid()).Wrap(err)
|
|
||||||
|
|
||||||
return nil, e
|
|
||||||
}
|
|
||||||
|
|
||||||
return &tweeting.OKResp{}, nil
|
|
||||||
}
|
|
|
@ -1,137 +0,0 @@
|
||||||
package timelineservicelogic
|
|
||||||
|
|
||||||
import (
|
|
||||||
"app-cloudep-tweeting-service/gen_result/pb/tweeting"
|
|
||||||
"app-cloudep-tweeting-service/internal/domain/repository"
|
|
||||||
mocklib "app-cloudep-tweeting-service/internal/mock/lib"
|
|
||||||
mockRepo "app-cloudep-tweeting-service/internal/mock/repository"
|
|
||||||
"app-cloudep-tweeting-service/internal/svc"
|
|
||||||
"context"
|
|
||||||
"errors"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
"go.uber.org/mock/gomock"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestAddPostLogic_AddPost(t *testing.T) {
|
|
||||||
ctrl := gomock.NewController(t)
|
|
||||||
defer ctrl.Finish()
|
|
||||||
|
|
||||||
// 初始化 mock 依賴
|
|
||||||
mockTimelineRepo := mockRepo.NewMockTimelineRepository(ctrl)
|
|
||||||
mockValidate := mocklib.NewMockValidate(ctrl)
|
|
||||||
|
|
||||||
// 初始化服務上下文
|
|
||||||
svcCtx := &svc.ServiceContext{
|
|
||||||
TimelineRepo: mockTimelineRepo,
|
|
||||||
Validate: mockValidate,
|
|
||||||
}
|
|
||||||
|
|
||||||
// 測試數據集
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
input *tweeting.AddPostToTimelineReq
|
|
||||||
prepare func()
|
|
||||||
expectErr bool
|
|
||||||
wantResp *tweeting.OKResp
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "成功加入貼文",
|
|
||||||
input: &tweeting.AddPostToTimelineReq{
|
|
||||||
Uid: "user123",
|
|
||||||
Posts: []*tweeting.PostTimelineItem{
|
|
||||||
{PostId: "post1", CreatedAt: 1627890123},
|
|
||||||
{PostId: "post2", CreatedAt: 1627890124},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
prepare: func() {
|
|
||||||
// 模擬驗證通過
|
|
||||||
mockValidate.EXPECT().ValidateAll(gomock.Any()).Return(nil).Times(1)
|
|
||||||
// 模擬成功加入貼文
|
|
||||||
mockTimelineRepo.EXPECT().AddPost(gomock.Any(), repository.AddPostRequest{
|
|
||||||
UID: "user123",
|
|
||||||
PostItems: []repository.TimelineItem{
|
|
||||||
{PostID: "post1", Score: 1627890123},
|
|
||||||
{PostID: "post2", Score: 1627890124},
|
|
||||||
},
|
|
||||||
}).Return(nil).Times(1)
|
|
||||||
},
|
|
||||||
expectErr: false,
|
|
||||||
wantResp: &tweeting.OKResp{},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "驗證失敗",
|
|
||||||
input: &tweeting.AddPostToTimelineReq{
|
|
||||||
Uid: "",
|
|
||||||
},
|
|
||||||
prepare: func() {
|
|
||||||
// 模擬驗證失敗
|
|
||||||
mockValidate.EXPECT().ValidateAll(gomock.Any()).Return(errors.New("validation failed")).Times(1)
|
|
||||||
},
|
|
||||||
expectErr: true,
|
|
||||||
wantResp: nil,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "沒有貼文資料",
|
|
||||||
input: &tweeting.AddPostToTimelineReq{
|
|
||||||
Uid: "user123",
|
|
||||||
Posts: []*tweeting.PostTimelineItem{},
|
|
||||||
},
|
|
||||||
prepare: func() {
|
|
||||||
// 模擬驗證通過
|
|
||||||
mockValidate.EXPECT().ValidateAll(gomock.Any()).Return(nil).Times(1)
|
|
||||||
},
|
|
||||||
expectErr: false,
|
|
||||||
wantResp: &tweeting.OKResp{},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "加入貼文失敗",
|
|
||||||
input: &tweeting.AddPostToTimelineReq{
|
|
||||||
Uid: "user123",
|
|
||||||
Posts: []*tweeting.PostTimelineItem{
|
|
||||||
{PostId: "post1", CreatedAt: 1627890123},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
prepare: func() {
|
|
||||||
// 模擬驗證通過
|
|
||||||
mockValidate.EXPECT().ValidateAll(gomock.Any()).Return(nil).Times(1)
|
|
||||||
// 模擬加入貼文失敗
|
|
||||||
mockTimelineRepo.EXPECT().AddPost(gomock.Any(), repository.AddPostRequest{
|
|
||||||
UID: "user123",
|
|
||||||
PostItems: []repository.TimelineItem{
|
|
||||||
{PostID: "post1", Score: 1627890123},
|
|
||||||
},
|
|
||||||
}).Return(errors.New("repository error")).Times(1)
|
|
||||||
},
|
|
||||||
expectErr: true,
|
|
||||||
wantResp: nil,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
// 執行測試
|
|
||||||
for _, tt := range tests {
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
|
||||||
// 設置測試環境
|
|
||||||
tt.prepare()
|
|
||||||
|
|
||||||
// 初始化 AddPostLogic
|
|
||||||
logic := AddPostLogic{
|
|
||||||
svcCtx: svcCtx,
|
|
||||||
ctx: context.TODO(),
|
|
||||||
}
|
|
||||||
|
|
||||||
// 執行 AddPost
|
|
||||||
got, err := logic.AddPost(tt.input)
|
|
||||||
|
|
||||||
// 驗證結果
|
|
||||||
if tt.expectErr {
|
|
||||||
assert.Error(t, err)
|
|
||||||
assert.Nil(t, got)
|
|
||||||
} else {
|
|
||||||
assert.NoError(t, err)
|
|
||||||
assert.Equal(t, tt.wantResp, got)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,60 +0,0 @@
|
||||||
package timelineservicelogic
|
|
||||||
|
|
||||||
import (
|
|
||||||
"app-cloudep-tweeting-service/internal/domain"
|
|
||||||
"context"
|
|
||||||
|
|
||||||
ers "code.30cm.net/digimon/library-go/errs"
|
|
||||||
|
|
||||||
"app-cloudep-tweeting-service/gen_result/pb/tweeting"
|
|
||||||
"app-cloudep-tweeting-service/internal/svc"
|
|
||||||
|
|
||||||
"github.com/zeromicro/go-zero/core/logx"
|
|
||||||
)
|
|
||||||
|
|
||||||
type ClearNoMoreDataFlagLogic struct {
|
|
||||||
ctx context.Context
|
|
||||||
svcCtx *svc.ServiceContext
|
|
||||||
logx.Logger
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewClearNoMoreDataFlagLogic(ctx context.Context, svcCtx *svc.ServiceContext) *ClearNoMoreDataFlagLogic {
|
|
||||||
return &ClearNoMoreDataFlagLogic{
|
|
||||||
ctx: ctx,
|
|
||||||
svcCtx: svcCtx,
|
|
||||||
Logger: logx.WithContext(ctx),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type clearNoMoreDataFlagReq struct {
|
|
||||||
UID string `json:"uid" validate:"required"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// ClearNoMoreDataFlag 清除時間線的 "NoMoreData" 標誌。
|
|
||||||
func (l *ClearNoMoreDataFlagLogic) ClearNoMoreDataFlag(in *tweeting.DoNoMoreDataReq) (*tweeting.OKResp, error) {
|
|
||||||
// 驗證資料
|
|
||||||
if err := l.svcCtx.Validate.ValidateAll(&clearNoMoreDataFlagReq{
|
|
||||||
UID: in.GetUid(),
|
|
||||||
}); err != nil {
|
|
||||||
// 錯誤代碼 05-011-00
|
|
||||||
return nil, ers.InvalidFormat(err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
err := l.svcCtx.TimelineRepo.ClearNoMoreDataFlag(l.ctx, in.GetUid())
|
|
||||||
if err != nil {
|
|
||||||
// 錯誤代碼 05-021-22
|
|
||||||
e := domain.CommentErrorL(
|
|
||||||
domain.ClearNoMoreDataErrorCode,
|
|
||||||
logx.WithContext(l.ctx),
|
|
||||||
[]logx.LogField{
|
|
||||||
{Key: "req", Value: in},
|
|
||||||
{Key: "func", Value: "TimelineRepo.ClearNoMoreDataFlag"},
|
|
||||||
{Key: "err", Value: err},
|
|
||||||
},
|
|
||||||
"failed to clear no more data flag timeline repo :", in.GetUid()).Wrap(err)
|
|
||||||
|
|
||||||
return nil, e
|
|
||||||
}
|
|
||||||
|
|
||||||
return &tweeting.OKResp{}, nil
|
|
||||||
}
|
|
|
@ -1,105 +0,0 @@
|
||||||
package timelineservicelogic
|
|
||||||
|
|
||||||
import (
|
|
||||||
"app-cloudep-tweeting-service/gen_result/pb/tweeting"
|
|
||||||
mocklib "app-cloudep-tweeting-service/internal/mock/lib"
|
|
||||||
mockRepo "app-cloudep-tweeting-service/internal/mock/repository"
|
|
||||||
"app-cloudep-tweeting-service/internal/svc"
|
|
||||||
"context"
|
|
||||||
"errors"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
"go.uber.org/mock/gomock"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestClearNoMoreDataFlagLogic_ClearNoMoreDataFlag(t *testing.T) {
|
|
||||||
ctrl := gomock.NewController(t)
|
|
||||||
defer ctrl.Finish()
|
|
||||||
|
|
||||||
// 初始化 mock 依賴
|
|
||||||
mockTimelineRepo := mockRepo.NewMockTimelineRepository(ctrl)
|
|
||||||
mockValidate := mocklib.NewMockValidate(ctrl)
|
|
||||||
|
|
||||||
// 初始化服務上下文
|
|
||||||
svcCtx := &svc.ServiceContext{
|
|
||||||
TimelineRepo: mockTimelineRepo,
|
|
||||||
Validate: mockValidate,
|
|
||||||
}
|
|
||||||
|
|
||||||
// 測試數據集
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
input *tweeting.DoNoMoreDataReq
|
|
||||||
prepare func()
|
|
||||||
expectErr bool
|
|
||||||
wantResp *tweeting.OKResp
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "成功清除 NoMoreData 標誌",
|
|
||||||
input: &tweeting.DoNoMoreDataReq{
|
|
||||||
Uid: "user123",
|
|
||||||
},
|
|
||||||
prepare: func() {
|
|
||||||
// 模擬驗證通過
|
|
||||||
mockValidate.EXPECT().ValidateAll(gomock.Any()).Return(nil).Times(1)
|
|
||||||
// 模擬成功清除 NoMoreData 標誌
|
|
||||||
mockTimelineRepo.EXPECT().ClearNoMoreDataFlag(gomock.Any(), "user123").Return(nil).Times(1)
|
|
||||||
},
|
|
||||||
expectErr: false,
|
|
||||||
wantResp: &tweeting.OKResp{},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "驗證失敗",
|
|
||||||
input: &tweeting.DoNoMoreDataReq{
|
|
||||||
Uid: "",
|
|
||||||
},
|
|
||||||
prepare: func() {
|
|
||||||
// 模擬驗證失敗
|
|
||||||
mockValidate.EXPECT().ValidateAll(gomock.Any()).Return(errors.New("validation failed")).Times(1)
|
|
||||||
},
|
|
||||||
expectErr: true,
|
|
||||||
wantResp: nil,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "清除 NoMoreData 標誌失敗",
|
|
||||||
input: &tweeting.DoNoMoreDataReq{
|
|
||||||
Uid: "user123",
|
|
||||||
},
|
|
||||||
prepare: func() {
|
|
||||||
// 模擬驗證通過
|
|
||||||
mockValidate.EXPECT().ValidateAll(gomock.Any()).Return(nil).Times(1)
|
|
||||||
// 模擬清除 NoMoreData 標誌失敗
|
|
||||||
mockTimelineRepo.EXPECT().ClearNoMoreDataFlag(gomock.Any(), "user123").Return(errors.New("repository error")).Times(1)
|
|
||||||
},
|
|
||||||
expectErr: true,
|
|
||||||
wantResp: nil,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
// 執行測試
|
|
||||||
for _, tt := range tests {
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
|
||||||
// 設置測試環境
|
|
||||||
tt.prepare()
|
|
||||||
|
|
||||||
// 初始化 ClearNoMoreDataFlagLogic
|
|
||||||
logic := ClearNoMoreDataFlagLogic{
|
|
||||||
svcCtx: svcCtx,
|
|
||||||
ctx: context.TODO(),
|
|
||||||
}
|
|
||||||
|
|
||||||
// 執行 ClearNoMoreDataFlag
|
|
||||||
got, err := logic.ClearNoMoreDataFlag(tt.input)
|
|
||||||
|
|
||||||
// 驗證結果
|
|
||||||
if tt.expectErr {
|
|
||||||
assert.Error(t, err)
|
|
||||||
assert.Nil(t, got)
|
|
||||||
} else {
|
|
||||||
assert.NoError(t, err)
|
|
||||||
assert.Equal(t, tt.wantResp, got)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,84 +0,0 @@
|
||||||
package timelineservicelogic
|
|
||||||
|
|
||||||
import (
|
|
||||||
"app-cloudep-tweeting-service/internal/domain"
|
|
||||||
"app-cloudep-tweeting-service/internal/domain/repository"
|
|
||||||
"context"
|
|
||||||
|
|
||||||
ers "code.30cm.net/digimon/library-go/errs"
|
|
||||||
|
|
||||||
"app-cloudep-tweeting-service/gen_result/pb/tweeting"
|
|
||||||
"app-cloudep-tweeting-service/internal/svc"
|
|
||||||
|
|
||||||
"github.com/zeromicro/go-zero/core/logx"
|
|
||||||
)
|
|
||||||
|
|
||||||
type FetchTimelineLogic struct {
|
|
||||||
ctx context.Context
|
|
||||||
svcCtx *svc.ServiceContext
|
|
||||||
logx.Logger
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewFetchTimelineLogic(ctx context.Context, svcCtx *svc.ServiceContext) *FetchTimelineLogic {
|
|
||||||
return &FetchTimelineLogic{
|
|
||||||
ctx: ctx,
|
|
||||||
svcCtx: svcCtx,
|
|
||||||
Logger: logx.WithContext(ctx),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type fetchTimelineReq struct {
|
|
||||||
UID string `json:"uid" validate:"required"`
|
|
||||||
PageSize int64 `json:"page_size" validate:"required"`
|
|
||||||
PageIndex int64 `json:"page_index" validate:"required"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// FetchTimeline 取得這個人的動態時報
|
|
||||||
func (l *FetchTimelineLogic) FetchTimeline(in *tweeting.GetTimelineReq) (*tweeting.FetchTimelineResponse, error) {
|
|
||||||
// 驗證資料
|
|
||||||
if err := l.svcCtx.Validate.ValidateAll(&fetchTimelineReq{
|
|
||||||
UID: in.GetUid(),
|
|
||||||
PageSize: in.PageSize,
|
|
||||||
PageIndex: in.PageIndex,
|
|
||||||
}); err != nil {
|
|
||||||
// 錯誤代碼 05-011-00
|
|
||||||
return nil, ers.InvalidFormat(err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
resp, err := l.svcCtx.TimelineRepo.FetchTimeline(l.ctx, repository.FetchTimelineRequest{
|
|
||||||
UID: in.GetUid(),
|
|
||||||
PageIndex: in.GetPageIndex(),
|
|
||||||
PageSize: in.GetPageSize(),
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
// 錯誤代碼 05-021-21
|
|
||||||
e := domain.CommentErrorL(
|
|
||||||
domain.FetchTimeLineErrorCode,
|
|
||||||
logx.WithContext(l.ctx),
|
|
||||||
[]logx.LogField{
|
|
||||||
{Key: "req", Value: in},
|
|
||||||
{Key: "func", Value: "TimelineRepo.FetchTimeline"},
|
|
||||||
{Key: "err", Value: err},
|
|
||||||
},
|
|
||||||
"failed to fetch timeline repo :", in.GetUid()).Wrap(err)
|
|
||||||
|
|
||||||
return nil, e
|
|
||||||
}
|
|
||||||
|
|
||||||
result := make([]*tweeting.FetchTimelineItem, 0, resp.Page.Size)
|
|
||||||
for _, item := range resp.Items {
|
|
||||||
result = append(result, &tweeting.FetchTimelineItem{
|
|
||||||
PostId: item.PostID,
|
|
||||||
Score: item.Score,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
return &tweeting.FetchTimelineResponse{
|
|
||||||
Posts: result,
|
|
||||||
Page: &tweeting.Pager{
|
|
||||||
Total: resp.Page.Total,
|
|
||||||
Index: resp.Page.Index,
|
|
||||||
Size: resp.Page.Size,
|
|
||||||
},
|
|
||||||
}, nil
|
|
||||||
}
|
|
|
@ -1,140 +0,0 @@
|
||||||
package timelineservicelogic
|
|
||||||
|
|
||||||
import (
|
|
||||||
"app-cloudep-tweeting-service/gen_result/pb/tweeting"
|
|
||||||
"app-cloudep-tweeting-service/internal/domain/repository"
|
|
||||||
mocklib "app-cloudep-tweeting-service/internal/mock/lib"
|
|
||||||
mockRepo "app-cloudep-tweeting-service/internal/mock/repository"
|
|
||||||
"app-cloudep-tweeting-service/internal/svc"
|
|
||||||
"context"
|
|
||||||
"errors"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
"go.uber.org/mock/gomock"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestFetchTimelineLogic_FetchTimeline(t *testing.T) {
|
|
||||||
ctrl := gomock.NewController(t)
|
|
||||||
defer ctrl.Finish()
|
|
||||||
|
|
||||||
// 初始化 mock 依賴
|
|
||||||
mockTimelineRepo := mockRepo.NewMockTimelineRepository(ctrl)
|
|
||||||
mockValidate := mocklib.NewMockValidate(ctrl)
|
|
||||||
|
|
||||||
// 初始化服務上下文
|
|
||||||
svcCtx := &svc.ServiceContext{
|
|
||||||
TimelineRepo: mockTimelineRepo,
|
|
||||||
Validate: mockValidate,
|
|
||||||
}
|
|
||||||
|
|
||||||
// 測試數據集
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
input *tweeting.GetTimelineReq
|
|
||||||
prepare func()
|
|
||||||
expectErr bool
|
|
||||||
wantResp *tweeting.FetchTimelineResponse
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "成功獲取動態時報",
|
|
||||||
input: &tweeting.GetTimelineReq{
|
|
||||||
Uid: "user123",
|
|
||||||
PageSize: 10,
|
|
||||||
PageIndex: 1,
|
|
||||||
},
|
|
||||||
prepare: func() {
|
|
||||||
// 模擬驗證通過
|
|
||||||
mockValidate.EXPECT().ValidateAll(gomock.Any()).Return(nil).Times(1)
|
|
||||||
// 模擬成功獲取動態時報
|
|
||||||
mockTimelineRepo.EXPECT().FetchTimeline(gomock.Any(), repository.FetchTimelineRequest{
|
|
||||||
UID: "user123",
|
|
||||||
PageSize: 10,
|
|
||||||
PageIndex: 1,
|
|
||||||
}).Return(repository.FetchTimelineResponse{
|
|
||||||
Items: []repository.TimelineItem{
|
|
||||||
{PostID: "post1", Score: 100},
|
|
||||||
{PostID: "post2", Score: 200},
|
|
||||||
},
|
|
||||||
Page: tweeting.Pager{
|
|
||||||
Total: 2,
|
|
||||||
Size: 10,
|
|
||||||
Index: 1,
|
|
||||||
},
|
|
||||||
}, nil).Times(1)
|
|
||||||
},
|
|
||||||
expectErr: false,
|
|
||||||
wantResp: &tweeting.FetchTimelineResponse{
|
|
||||||
Posts: []*tweeting.FetchTimelineItem{
|
|
||||||
{PostId: "post1", Score: 100},
|
|
||||||
{PostId: "post2", Score: 200},
|
|
||||||
},
|
|
||||||
Page: &tweeting.Pager{
|
|
||||||
Total: 2,
|
|
||||||
Index: 1,
|
|
||||||
Size: 10,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "驗證失敗",
|
|
||||||
input: &tweeting.GetTimelineReq{
|
|
||||||
Uid: "",
|
|
||||||
PageSize: 10,
|
|
||||||
PageIndex: 1,
|
|
||||||
},
|
|
||||||
prepare: func() {
|
|
||||||
// 模擬驗證失敗
|
|
||||||
mockValidate.EXPECT().ValidateAll(gomock.Any()).Return(errors.New("validation failed")).Times(1)
|
|
||||||
},
|
|
||||||
expectErr: true,
|
|
||||||
wantResp: nil,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "獲取動態時報失敗",
|
|
||||||
input: &tweeting.GetTimelineReq{
|
|
||||||
Uid: "user123",
|
|
||||||
PageSize: 10,
|
|
||||||
PageIndex: 1,
|
|
||||||
},
|
|
||||||
prepare: func() {
|
|
||||||
// 模擬驗證通過
|
|
||||||
mockValidate.EXPECT().ValidateAll(gomock.Any()).Return(nil).Times(1)
|
|
||||||
// 模擬獲取動態時報失敗
|
|
||||||
mockTimelineRepo.EXPECT().FetchTimeline(gomock.Any(), repository.FetchTimelineRequest{
|
|
||||||
UID: "user123",
|
|
||||||
PageSize: 10,
|
|
||||||
PageIndex: 1,
|
|
||||||
}).Return(repository.FetchTimelineResponse{}, errors.New("repository error")).Times(1)
|
|
||||||
},
|
|
||||||
expectErr: true,
|
|
||||||
wantResp: nil,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
// 執行測試
|
|
||||||
for _, tt := range tests {
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
|
||||||
// 設置測試環境
|
|
||||||
tt.prepare()
|
|
||||||
|
|
||||||
// 初始化 FetchTimelineLogic
|
|
||||||
logic := FetchTimelineLogic{
|
|
||||||
svcCtx: svcCtx,
|
|
||||||
ctx: context.TODO(),
|
|
||||||
}
|
|
||||||
|
|
||||||
// 執行 FetchTimeline
|
|
||||||
got, err := logic.FetchTimeline(tt.input)
|
|
||||||
|
|
||||||
// 驗證結果
|
|
||||||
if tt.expectErr {
|
|
||||||
assert.Error(t, err)
|
|
||||||
assert.Nil(t, got)
|
|
||||||
} else {
|
|
||||||
assert.NoError(t, err)
|
|
||||||
assert.Equal(t, tt.wantResp, got)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,62 +0,0 @@
|
||||||
package timelineservicelogic
|
|
||||||
|
|
||||||
import (
|
|
||||||
"app-cloudep-tweeting-service/internal/domain"
|
|
||||||
"context"
|
|
||||||
|
|
||||||
ers "code.30cm.net/digimon/library-go/errs"
|
|
||||||
|
|
||||||
"app-cloudep-tweeting-service/gen_result/pb/tweeting"
|
|
||||||
"app-cloudep-tweeting-service/internal/svc"
|
|
||||||
|
|
||||||
"github.com/zeromicro/go-zero/core/logx"
|
|
||||||
)
|
|
||||||
|
|
||||||
type HasNoMoreDataLogic struct {
|
|
||||||
ctx context.Context
|
|
||||||
svcCtx *svc.ServiceContext
|
|
||||||
logx.Logger
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewHasNoMoreDataLogic(ctx context.Context, svcCtx *svc.ServiceContext) *HasNoMoreDataLogic {
|
|
||||||
return &HasNoMoreDataLogic{
|
|
||||||
ctx: ctx,
|
|
||||||
svcCtx: svcCtx,
|
|
||||||
Logger: logx.WithContext(ctx),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type hasNoMoreDataReq struct {
|
|
||||||
UID string `json:"uid" validate:"required"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// HasNoMoreData 檢查時間線是否已完整,決定是否需要查詢資料庫。
|
|
||||||
func (l *HasNoMoreDataLogic) HasNoMoreData(in *tweeting.DoNoMoreDataReq) (*tweeting.HasNoMoreDataResp, error) {
|
|
||||||
// 驗證資料
|
|
||||||
if err := l.svcCtx.Validate.ValidateAll(&hasNoMoreDataReq{
|
|
||||||
UID: in.GetUid(),
|
|
||||||
}); err != nil {
|
|
||||||
// 錯誤代碼 05-011-00
|
|
||||||
return nil, ers.InvalidFormat(err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
res, err := l.svcCtx.TimelineRepo.HasNoMoreData(l.ctx, in.GetUid())
|
|
||||||
if err != nil {
|
|
||||||
// 錯誤代碼 05-021-23
|
|
||||||
e := domain.CommentErrorL(
|
|
||||||
domain.HasNoMoreDataErrorCode,
|
|
||||||
logx.WithContext(l.ctx),
|
|
||||||
[]logx.LogField{
|
|
||||||
{Key: "req", Value: in},
|
|
||||||
{Key: "func", Value: "TimelineRepo.HasNoMoreData"},
|
|
||||||
{Key: "err", Value: err},
|
|
||||||
},
|
|
||||||
"failed to get no more data flag:", in.GetUid()).Wrap(err)
|
|
||||||
|
|
||||||
return nil, e
|
|
||||||
}
|
|
||||||
|
|
||||||
return &tweeting.HasNoMoreDataResp{
|
|
||||||
Status: res,
|
|
||||||
}, nil
|
|
||||||
}
|
|
|
@ -1,107 +0,0 @@
|
||||||
package timelineservicelogic
|
|
||||||
|
|
||||||
import (
|
|
||||||
"app-cloudep-tweeting-service/gen_result/pb/tweeting"
|
|
||||||
mocklib "app-cloudep-tweeting-service/internal/mock/lib"
|
|
||||||
mockRepo "app-cloudep-tweeting-service/internal/mock/repository"
|
|
||||||
"app-cloudep-tweeting-service/internal/svc"
|
|
||||||
"context"
|
|
||||||
"errors"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
"go.uber.org/mock/gomock"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestHasNoMoreDataLogic_HasNoMoreData(t *testing.T) {
|
|
||||||
ctrl := gomock.NewController(t)
|
|
||||||
defer ctrl.Finish()
|
|
||||||
|
|
||||||
// 初始化 mock 依賴
|
|
||||||
mockTimelineRepo := mockRepo.NewMockTimelineRepository(ctrl)
|
|
||||||
mockValidate := mocklib.NewMockValidate(ctrl)
|
|
||||||
|
|
||||||
// 初始化服務上下文
|
|
||||||
svcCtx := &svc.ServiceContext{
|
|
||||||
TimelineRepo: mockTimelineRepo,
|
|
||||||
Validate: mockValidate,
|
|
||||||
}
|
|
||||||
|
|
||||||
// 測試數據集
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
input *tweeting.DoNoMoreDataReq
|
|
||||||
prepare func()
|
|
||||||
expectErr bool
|
|
||||||
wantResp *tweeting.HasNoMoreDataResp
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "成功檢查時間線是否完整",
|
|
||||||
input: &tweeting.DoNoMoreDataReq{
|
|
||||||
Uid: "user123",
|
|
||||||
},
|
|
||||||
prepare: func() {
|
|
||||||
// 模擬驗證通過
|
|
||||||
mockValidate.EXPECT().ValidateAll(gomock.Any()).Return(nil).Times(1)
|
|
||||||
// 模擬成功檢查時間線狀態
|
|
||||||
mockTimelineRepo.EXPECT().HasNoMoreData(gomock.Any(), "user123").Return(true, nil).Times(1)
|
|
||||||
},
|
|
||||||
expectErr: false,
|
|
||||||
wantResp: &tweeting.HasNoMoreDataResp{
|
|
||||||
Status: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "驗證失敗",
|
|
||||||
input: &tweeting.DoNoMoreDataReq{
|
|
||||||
Uid: "",
|
|
||||||
},
|
|
||||||
prepare: func() {
|
|
||||||
// 模擬驗證失敗
|
|
||||||
mockValidate.EXPECT().ValidateAll(gomock.Any()).Return(errors.New("validation failed")).Times(1)
|
|
||||||
},
|
|
||||||
expectErr: true,
|
|
||||||
wantResp: nil,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "檢查時間線狀態失敗",
|
|
||||||
input: &tweeting.DoNoMoreDataReq{
|
|
||||||
Uid: "user123",
|
|
||||||
},
|
|
||||||
prepare: func() {
|
|
||||||
// 模擬驗證通過
|
|
||||||
mockValidate.EXPECT().ValidateAll(gomock.Any()).Return(nil).Times(1)
|
|
||||||
// 模擬檢查時間線狀態失敗
|
|
||||||
mockTimelineRepo.EXPECT().HasNoMoreData(gomock.Any(), "user123").Return(false, errors.New("repository error")).Times(1)
|
|
||||||
},
|
|
||||||
expectErr: true,
|
|
||||||
wantResp: nil,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
// 執行測試
|
|
||||||
for _, tt := range tests {
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
|
||||||
// 設置測試環境
|
|
||||||
tt.prepare()
|
|
||||||
|
|
||||||
// 初始化 HasNoMoreDataLogic
|
|
||||||
logic := HasNoMoreDataLogic{
|
|
||||||
svcCtx: svcCtx,
|
|
||||||
ctx: context.TODO(),
|
|
||||||
}
|
|
||||||
|
|
||||||
// 執行 HasNoMoreData
|
|
||||||
got, err := logic.HasNoMoreData(tt.input)
|
|
||||||
|
|
||||||
// 驗證結果
|
|
||||||
if tt.expectErr {
|
|
||||||
assert.Error(t, err)
|
|
||||||
assert.Nil(t, got)
|
|
||||||
} else {
|
|
||||||
assert.NoError(t, err)
|
|
||||||
assert.Equal(t, tt.wantResp, got)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,60 +0,0 @@
|
||||||
package timelineservicelogic
|
|
||||||
|
|
||||||
import (
|
|
||||||
"app-cloudep-tweeting-service/internal/domain"
|
|
||||||
"context"
|
|
||||||
|
|
||||||
ers "code.30cm.net/digimon/library-go/errs"
|
|
||||||
|
|
||||||
"app-cloudep-tweeting-service/gen_result/pb/tweeting"
|
|
||||||
"app-cloudep-tweeting-service/internal/svc"
|
|
||||||
|
|
||||||
"github.com/zeromicro/go-zero/core/logx"
|
|
||||||
)
|
|
||||||
|
|
||||||
type SetNoMoreDataFlagLogic struct {
|
|
||||||
ctx context.Context
|
|
||||||
svcCtx *svc.ServiceContext
|
|
||||||
logx.Logger
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewSetNoMoreDataFlagLogic(ctx context.Context, svcCtx *svc.ServiceContext) *SetNoMoreDataFlagLogic {
|
|
||||||
return &SetNoMoreDataFlagLogic{
|
|
||||||
ctx: ctx,
|
|
||||||
svcCtx: svcCtx,
|
|
||||||
Logger: logx.WithContext(ctx),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type sasNoMoreDataReq struct {
|
|
||||||
UID string `json:"uid" validate:"required"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetNoMoreDataFlag 標記時間線已完整,避免繼續查詢資料庫。
|
|
||||||
func (l *SetNoMoreDataFlagLogic) SetNoMoreDataFlag(in *tweeting.DoNoMoreDataReq) (*tweeting.OKResp, error) {
|
|
||||||
// 驗證資料
|
|
||||||
if err := l.svcCtx.Validate.ValidateAll(&sasNoMoreDataReq{
|
|
||||||
UID: in.GetUid(),
|
|
||||||
}); err != nil {
|
|
||||||
// 錯誤代碼 05-011-00
|
|
||||||
return nil, ers.InvalidFormat(err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
err := l.svcCtx.TimelineRepo.SetNoMoreDataFlag(l.ctx, in.GetUid())
|
|
||||||
if err != nil {
|
|
||||||
// 錯誤代碼 05-021-24
|
|
||||||
e := domain.CommentErrorL(
|
|
||||||
domain.SetNoMoreDataErrorCode,
|
|
||||||
logx.WithContext(l.ctx),
|
|
||||||
[]logx.LogField{
|
|
||||||
{Key: "req", Value: in},
|
|
||||||
{Key: "func", Value: "TimelineRepo.SetNoMoreDataErrorCode"},
|
|
||||||
{Key: "err", Value: err},
|
|
||||||
},
|
|
||||||
"failed to set no more data flag:", in.GetUid()).Wrap(err)
|
|
||||||
|
|
||||||
return nil, e
|
|
||||||
}
|
|
||||||
|
|
||||||
return &tweeting.OKResp{}, nil
|
|
||||||
}
|
|
|
@ -1,105 +0,0 @@
|
||||||
package timelineservicelogic
|
|
||||||
|
|
||||||
import (
|
|
||||||
"app-cloudep-tweeting-service/gen_result/pb/tweeting"
|
|
||||||
mocklib "app-cloudep-tweeting-service/internal/mock/lib"
|
|
||||||
mockRepo "app-cloudep-tweeting-service/internal/mock/repository"
|
|
||||||
"app-cloudep-tweeting-service/internal/svc"
|
|
||||||
"context"
|
|
||||||
"errors"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
"go.uber.org/mock/gomock"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestSetNoMoreDataFlagLogic_SetNoMoreDataFlag(t *testing.T) {
|
|
||||||
ctrl := gomock.NewController(t)
|
|
||||||
defer ctrl.Finish()
|
|
||||||
|
|
||||||
// 初始化 mock 依賴
|
|
||||||
mockTimelineRepo := mockRepo.NewMockTimelineRepository(ctrl)
|
|
||||||
mockValidate := mocklib.NewMockValidate(ctrl)
|
|
||||||
|
|
||||||
// 初始化服務上下文
|
|
||||||
svcCtx := &svc.ServiceContext{
|
|
||||||
TimelineRepo: mockTimelineRepo,
|
|
||||||
Validate: mockValidate,
|
|
||||||
}
|
|
||||||
|
|
||||||
// 測試數據集
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
input *tweeting.DoNoMoreDataReq
|
|
||||||
prepare func()
|
|
||||||
expectErr bool
|
|
||||||
wantResp *tweeting.OKResp
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "成功標記時間線已完整",
|
|
||||||
input: &tweeting.DoNoMoreDataReq{
|
|
||||||
Uid: "user123",
|
|
||||||
},
|
|
||||||
prepare: func() {
|
|
||||||
// 模擬驗證通過
|
|
||||||
mockValidate.EXPECT().ValidateAll(gomock.Any()).Return(nil).Times(1)
|
|
||||||
// 模擬成功標記時間線狀態
|
|
||||||
mockTimelineRepo.EXPECT().SetNoMoreDataFlag(gomock.Any(), "user123").Return(nil).Times(1)
|
|
||||||
},
|
|
||||||
expectErr: false,
|
|
||||||
wantResp: &tweeting.OKResp{},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "驗證失敗",
|
|
||||||
input: &tweeting.DoNoMoreDataReq{
|
|
||||||
Uid: "",
|
|
||||||
},
|
|
||||||
prepare: func() {
|
|
||||||
// 模擬驗證失敗
|
|
||||||
mockValidate.EXPECT().ValidateAll(gomock.Any()).Return(errors.New("validation failed")).Times(1)
|
|
||||||
},
|
|
||||||
expectErr: true,
|
|
||||||
wantResp: nil,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "標記時間線狀態失敗",
|
|
||||||
input: &tweeting.DoNoMoreDataReq{
|
|
||||||
Uid: "user123",
|
|
||||||
},
|
|
||||||
prepare: func() {
|
|
||||||
// 模擬驗證通過
|
|
||||||
mockValidate.EXPECT().ValidateAll(gomock.Any()).Return(nil).Times(1)
|
|
||||||
// 模擬標記時間線狀態失敗
|
|
||||||
mockTimelineRepo.EXPECT().SetNoMoreDataFlag(gomock.Any(), "user123").Return(errors.New("repository error")).Times(1)
|
|
||||||
},
|
|
||||||
expectErr: true,
|
|
||||||
wantResp: nil,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
// 執行測試
|
|
||||||
for _, tt := range tests {
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
|
||||||
// 設置測試環境
|
|
||||||
tt.prepare()
|
|
||||||
|
|
||||||
// 初始化 SetNoMoreDataFlagLogic
|
|
||||||
logic := SetNoMoreDataFlagLogic{
|
|
||||||
svcCtx: svcCtx,
|
|
||||||
ctx: context.TODO(),
|
|
||||||
}
|
|
||||||
|
|
||||||
// 執行 SetNoMoreDataFlag
|
|
||||||
got, err := logic.SetNoMoreDataFlag(tt.input)
|
|
||||||
|
|
||||||
// 驗證結果
|
|
||||||
if tt.expectErr {
|
|
||||||
assert.Error(t, err)
|
|
||||||
assert.Nil(t, got)
|
|
||||||
} else {
|
|
||||||
assert.NoError(t, err)
|
|
||||||
assert.Equal(t, tt.wantResp, got)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,174 +0,0 @@
|
||||||
// Code generated by MockGen. DO NOT EDIT.
|
|
||||||
// Source: ./internal/domain/repository/social_network.go
|
|
||||||
//
|
|
||||||
// Generated by this command:
|
|
||||||
//
|
|
||||||
// mockgen -source=./internal/domain/repository/social_network.go -destination=./internal/mock/repository/social_network.go -package=mock
|
|
||||||
//
|
|
||||||
|
|
||||||
// Package mock is a generated GoMock package.
|
|
||||||
package mock
|
|
||||||
|
|
||||||
import (
|
|
||||||
repository "app-cloudep-tweeting-service/internal/domain/repository"
|
|
||||||
context "context"
|
|
||||||
reflect "reflect"
|
|
||||||
|
|
||||||
gomock "go.uber.org/mock/gomock"
|
|
||||||
)
|
|
||||||
|
|
||||||
// MockSocialNetworkRepository is a mock of SocialNetworkRepository interface.
|
|
||||||
type MockSocialNetworkRepository struct {
|
|
||||||
ctrl *gomock.Controller
|
|
||||||
recorder *MockSocialNetworkRepositoryMockRecorder
|
|
||||||
}
|
|
||||||
|
|
||||||
// MockSocialNetworkRepositoryMockRecorder is the mock recorder for MockSocialNetworkRepository.
|
|
||||||
type MockSocialNetworkRepositoryMockRecorder struct {
|
|
||||||
mock *MockSocialNetworkRepository
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewMockSocialNetworkRepository creates a new mock instance.
|
|
||||||
func NewMockSocialNetworkRepository(ctrl *gomock.Controller) *MockSocialNetworkRepository {
|
|
||||||
mock := &MockSocialNetworkRepository{ctrl: ctrl}
|
|
||||||
mock.recorder = &MockSocialNetworkRepositoryMockRecorder{mock}
|
|
||||||
return mock
|
|
||||||
}
|
|
||||||
|
|
||||||
// EXPECT returns an object that allows the caller to indicate expected use.
|
|
||||||
func (m *MockSocialNetworkRepository) EXPECT() *MockSocialNetworkRepositoryMockRecorder {
|
|
||||||
return m.recorder
|
|
||||||
}
|
|
||||||
|
|
||||||
// CreateUserNode mocks base method.
|
|
||||||
func (m *MockSocialNetworkRepository) CreateUserNode(ctx context.Context, uid string) error {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
ret := m.ctrl.Call(m, "CreateUserNode", ctx, uid)
|
|
||||||
ret0, _ := ret[0].(error)
|
|
||||||
return ret0
|
|
||||||
}
|
|
||||||
|
|
||||||
// CreateUserNode indicates an expected call of CreateUserNode.
|
|
||||||
func (mr *MockSocialNetworkRepositoryMockRecorder) CreateUserNode(ctx, uid any) *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateUserNode", reflect.TypeOf((*MockSocialNetworkRepository)(nil).CreateUserNode), ctx, uid)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetDegreeBetweenUsers mocks base method.
|
|
||||||
func (m *MockSocialNetworkRepository) GetDegreeBetweenUsers(ctx context.Context, uid1, uid2 string) (int64, error) {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
ret := m.ctrl.Call(m, "GetDegreeBetweenUsers", ctx, uid1, uid2)
|
|
||||||
ret0, _ := ret[0].(int64)
|
|
||||||
ret1, _ := ret[1].(error)
|
|
||||||
return ret0, ret1
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetDegreeBetweenUsers indicates an expected call of GetDegreeBetweenUsers.
|
|
||||||
func (mr *MockSocialNetworkRepositoryMockRecorder) GetDegreeBetweenUsers(ctx, uid1, uid2 any) *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetDegreeBetweenUsers", reflect.TypeOf((*MockSocialNetworkRepository)(nil).GetDegreeBetweenUsers), ctx, uid1, uid2)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetFollowee mocks base method.
|
|
||||||
func (m *MockSocialNetworkRepository) GetFollowee(ctx context.Context, req repository.FollowReq) (repository.FollowResp, error) {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
ret := m.ctrl.Call(m, "GetFollowee", ctx, req)
|
|
||||||
ret0, _ := ret[0].(repository.FollowResp)
|
|
||||||
ret1, _ := ret[1].(error)
|
|
||||||
return ret0, ret1
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetFollowee indicates an expected call of GetFollowee.
|
|
||||||
func (mr *MockSocialNetworkRepositoryMockRecorder) GetFollowee(ctx, req any) *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetFollowee", reflect.TypeOf((*MockSocialNetworkRepository)(nil).GetFollowee), ctx, req)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetFolloweeCount mocks base method.
|
|
||||||
func (m *MockSocialNetworkRepository) GetFolloweeCount(ctx context.Context, uid string) (int64, error) {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
ret := m.ctrl.Call(m, "GetFolloweeCount", ctx, uid)
|
|
||||||
ret0, _ := ret[0].(int64)
|
|
||||||
ret1, _ := ret[1].(error)
|
|
||||||
return ret0, ret1
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetFolloweeCount indicates an expected call of GetFolloweeCount.
|
|
||||||
func (mr *MockSocialNetworkRepositoryMockRecorder) GetFolloweeCount(ctx, uid any) *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetFolloweeCount", reflect.TypeOf((*MockSocialNetworkRepository)(nil).GetFolloweeCount), ctx, uid)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetFollower mocks base method.
|
|
||||||
func (m *MockSocialNetworkRepository) GetFollower(ctx context.Context, req repository.FollowReq) (repository.FollowResp, error) {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
ret := m.ctrl.Call(m, "GetFollower", ctx, req)
|
|
||||||
ret0, _ := ret[0].(repository.FollowResp)
|
|
||||||
ret1, _ := ret[1].(error)
|
|
||||||
return ret0, ret1
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetFollower indicates an expected call of GetFollower.
|
|
||||||
func (mr *MockSocialNetworkRepositoryMockRecorder) GetFollower(ctx, req any) *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetFollower", reflect.TypeOf((*MockSocialNetworkRepository)(nil).GetFollower), ctx, req)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetFollowerCount mocks base method.
|
|
||||||
func (m *MockSocialNetworkRepository) GetFollowerCount(ctx context.Context, uid string) (int64, error) {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
ret := m.ctrl.Call(m, "GetFollowerCount", ctx, uid)
|
|
||||||
ret0, _ := ret[0].(int64)
|
|
||||||
ret1, _ := ret[1].(error)
|
|
||||||
return ret0, ret1
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetFollowerCount indicates an expected call of GetFollowerCount.
|
|
||||||
func (mr *MockSocialNetworkRepositoryMockRecorder) GetFollowerCount(ctx, uid any) *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetFollowerCount", reflect.TypeOf((*MockSocialNetworkRepository)(nil).GetFollowerCount), ctx, uid)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetUIDsWithinNDegrees mocks base method.
|
|
||||||
func (m *MockSocialNetworkRepository) GetUIDsWithinNDegrees(ctx context.Context, uid string, degrees, pageSize, pageIndex int64) ([]string, int64, error) {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
ret := m.ctrl.Call(m, "GetUIDsWithinNDegrees", ctx, uid, degrees, pageSize, pageIndex)
|
|
||||||
ret0, _ := ret[0].([]string)
|
|
||||||
ret1, _ := ret[1].(int64)
|
|
||||||
ret2, _ := ret[2].(error)
|
|
||||||
return ret0, ret1, ret2
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetUIDsWithinNDegrees indicates an expected call of GetUIDsWithinNDegrees.
|
|
||||||
func (mr *MockSocialNetworkRepositoryMockRecorder) GetUIDsWithinNDegrees(ctx, uid, degrees, pageSize, pageIndex any) *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetUIDsWithinNDegrees", reflect.TypeOf((*MockSocialNetworkRepository)(nil).GetUIDsWithinNDegrees), ctx, uid, degrees, pageSize, pageIndex)
|
|
||||||
}
|
|
||||||
|
|
||||||
// MarkFollowerRelation mocks base method.
|
|
||||||
func (m *MockSocialNetworkRepository) MarkFollowerRelation(ctx context.Context, fromUID, toUID string) error {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
ret := m.ctrl.Call(m, "MarkFollowerRelation", ctx, fromUID, toUID)
|
|
||||||
ret0, _ := ret[0].(error)
|
|
||||||
return ret0
|
|
||||||
}
|
|
||||||
|
|
||||||
// MarkFollowerRelation indicates an expected call of MarkFollowerRelation.
|
|
||||||
func (mr *MockSocialNetworkRepositoryMockRecorder) MarkFollowerRelation(ctx, fromUID, toUID any) *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "MarkFollowerRelation", reflect.TypeOf((*MockSocialNetworkRepository)(nil).MarkFollowerRelation), ctx, fromUID, toUID)
|
|
||||||
}
|
|
||||||
|
|
||||||
// RemoveFollowerRelation mocks base method.
|
|
||||||
func (m *MockSocialNetworkRepository) RemoveFollowerRelation(ctx context.Context, fromUID, toUID string) error {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
ret := m.ctrl.Call(m, "RemoveFollowerRelation", ctx, fromUID, toUID)
|
|
||||||
ret0, _ := ret[0].(error)
|
|
||||||
return ret0
|
|
||||||
}
|
|
||||||
|
|
||||||
// RemoveFollowerRelation indicates an expected call of RemoveFollowerRelation.
|
|
||||||
func (mr *MockSocialNetworkRepositoryMockRecorder) RemoveFollowerRelation(ctx, fromUID, toUID any) *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RemoveFollowerRelation", reflect.TypeOf((*MockSocialNetworkRepository)(nil).RemoveFollowerRelation), ctx, fromUID, toUID)
|
|
||||||
}
|
|
|
@ -1,113 +0,0 @@
|
||||||
// Code generated by MockGen. DO NOT EDIT.
|
|
||||||
// Source: ./internal/domain/repository/timeline.go
|
|
||||||
//
|
|
||||||
// Generated by this command:
|
|
||||||
//
|
|
||||||
// mockgen -source=./internal/domain/repository/timeline.go -destination=./internal/mock/repository/timeline.go -package=mock
|
|
||||||
//
|
|
||||||
|
|
||||||
// Package mock is a generated GoMock package.
|
|
||||||
package mock
|
|
||||||
|
|
||||||
import (
|
|
||||||
repository "app-cloudep-tweeting-service/internal/domain/repository"
|
|
||||||
context "context"
|
|
||||||
reflect "reflect"
|
|
||||||
|
|
||||||
gomock "go.uber.org/mock/gomock"
|
|
||||||
)
|
|
||||||
|
|
||||||
// MockTimelineRepository is a mock of TimelineRepository interface.
|
|
||||||
type MockTimelineRepository struct {
|
|
||||||
ctrl *gomock.Controller
|
|
||||||
recorder *MockTimelineRepositoryMockRecorder
|
|
||||||
}
|
|
||||||
|
|
||||||
// MockTimelineRepositoryMockRecorder is the mock recorder for MockTimelineRepository.
|
|
||||||
type MockTimelineRepositoryMockRecorder struct {
|
|
||||||
mock *MockTimelineRepository
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewMockTimelineRepository creates a new mock instance.
|
|
||||||
func NewMockTimelineRepository(ctrl *gomock.Controller) *MockTimelineRepository {
|
|
||||||
mock := &MockTimelineRepository{ctrl: ctrl}
|
|
||||||
mock.recorder = &MockTimelineRepositoryMockRecorder{mock}
|
|
||||||
return mock
|
|
||||||
}
|
|
||||||
|
|
||||||
// EXPECT returns an object that allows the caller to indicate expected use.
|
|
||||||
func (m *MockTimelineRepository) EXPECT() *MockTimelineRepositoryMockRecorder {
|
|
||||||
return m.recorder
|
|
||||||
}
|
|
||||||
|
|
||||||
// AddPost mocks base method.
|
|
||||||
func (m *MockTimelineRepository) AddPost(ctx context.Context, req repository.AddPostRequest) error {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
ret := m.ctrl.Call(m, "AddPost", ctx, req)
|
|
||||||
ret0, _ := ret[0].(error)
|
|
||||||
return ret0
|
|
||||||
}
|
|
||||||
|
|
||||||
// AddPost indicates an expected call of AddPost.
|
|
||||||
func (mr *MockTimelineRepositoryMockRecorder) AddPost(ctx, req any) *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddPost", reflect.TypeOf((*MockTimelineRepository)(nil).AddPost), ctx, req)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ClearNoMoreDataFlag mocks base method.
|
|
||||||
func (m *MockTimelineRepository) ClearNoMoreDataFlag(ctx context.Context, uid string) error {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
ret := m.ctrl.Call(m, "ClearNoMoreDataFlag", ctx, uid)
|
|
||||||
ret0, _ := ret[0].(error)
|
|
||||||
return ret0
|
|
||||||
}
|
|
||||||
|
|
||||||
// ClearNoMoreDataFlag indicates an expected call of ClearNoMoreDataFlag.
|
|
||||||
func (mr *MockTimelineRepositoryMockRecorder) ClearNoMoreDataFlag(ctx, uid any) *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ClearNoMoreDataFlag", reflect.TypeOf((*MockTimelineRepository)(nil).ClearNoMoreDataFlag), ctx, uid)
|
|
||||||
}
|
|
||||||
|
|
||||||
// FetchTimeline mocks base method.
|
|
||||||
func (m *MockTimelineRepository) FetchTimeline(ctx context.Context, req repository.FetchTimelineRequest) (repository.FetchTimelineResponse, error) {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
ret := m.ctrl.Call(m, "FetchTimeline", ctx, req)
|
|
||||||
ret0, _ := ret[0].(repository.FetchTimelineResponse)
|
|
||||||
ret1, _ := ret[1].(error)
|
|
||||||
return ret0, ret1
|
|
||||||
}
|
|
||||||
|
|
||||||
// FetchTimeline indicates an expected call of FetchTimeline.
|
|
||||||
func (mr *MockTimelineRepositoryMockRecorder) FetchTimeline(ctx, req any) *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "FetchTimeline", reflect.TypeOf((*MockTimelineRepository)(nil).FetchTimeline), ctx, req)
|
|
||||||
}
|
|
||||||
|
|
||||||
// HasNoMoreData mocks base method.
|
|
||||||
func (m *MockTimelineRepository) HasNoMoreData(ctx context.Context, uid string) (bool, error) {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
ret := m.ctrl.Call(m, "HasNoMoreData", ctx, uid)
|
|
||||||
ret0, _ := ret[0].(bool)
|
|
||||||
ret1, _ := ret[1].(error)
|
|
||||||
return ret0, ret1
|
|
||||||
}
|
|
||||||
|
|
||||||
// HasNoMoreData indicates an expected call of HasNoMoreData.
|
|
||||||
func (mr *MockTimelineRepositoryMockRecorder) HasNoMoreData(ctx, uid any) *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "HasNoMoreData", reflect.TypeOf((*MockTimelineRepository)(nil).HasNoMoreData), ctx, uid)
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetNoMoreDataFlag mocks base method.
|
|
||||||
func (m *MockTimelineRepository) SetNoMoreDataFlag(ctx context.Context, uid string) error {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
ret := m.ctrl.Call(m, "SetNoMoreDataFlag", ctx, uid)
|
|
||||||
ret0, _ := ret[0].(error)
|
|
||||||
return ret0
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetNoMoreDataFlag indicates an expected call of SetNoMoreDataFlag.
|
|
||||||
func (mr *MockTimelineRepositoryMockRecorder) SetNoMoreDataFlag(ctx, uid any) *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetNoMoreDataFlag", reflect.TypeOf((*MockTimelineRepository)(nil).SetNoMoreDataFlag), ctx, uid)
|
|
||||||
}
|
|
|
@ -1,439 +0,0 @@
|
||||||
package repository
|
|
||||||
|
|
||||||
import (
|
|
||||||
"app-cloudep-tweeting-service/internal/config"
|
|
||||||
"app-cloudep-tweeting-service/internal/domain/repository"
|
|
||||||
client4J "app-cloudep-tweeting-service/internal/lib/neo4j"
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"github.com/zeromicro/go-zero/core/logx"
|
|
||||||
|
|
||||||
"github.com/neo4j/neo4j-go-driver/v5/neo4j"
|
|
||||||
)
|
|
||||||
|
|
||||||
type SocialNetworkParam struct {
|
|
||||||
Config config.Config
|
|
||||||
Neo4jClient *client4J.Client
|
|
||||||
}
|
|
||||||
|
|
||||||
type SocialNetworkRepository struct {
|
|
||||||
cfg config.Config
|
|
||||||
neo4jClient *client4J.Client
|
|
||||||
}
|
|
||||||
|
|
||||||
func MustSocialNetworkRepository(param SocialNetworkParam) repository.SocialNetworkRepository {
|
|
||||||
return &SocialNetworkRepository{
|
|
||||||
cfg: param.Config,
|
|
||||||
neo4jClient: param.Neo4jClient,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *SocialNetworkRepository) CreateUserNode(ctx context.Context, uid string) error {
|
|
||||||
//nolint:contextcheck
|
|
||||||
session, err := s.neo4jClient.Conn()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer session.Close(ctx)
|
|
||||||
|
|
||||||
params := map[string]interface{}{
|
|
||||||
"uid": uid,
|
|
||||||
}
|
|
||||||
|
|
||||||
run, err := session.NewSession(ctx, neo4j.SessionConfig{
|
|
||||||
AccessMode: neo4j.AccessModeWrite,
|
|
||||||
}).Run(ctx, "CREATE (n:User {uid: $uid}) RETURN n", params)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// 處理結果
|
|
||||||
if run.Next(ctx) {
|
|
||||||
_ = run.Record().AsMap()
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *SocialNetworkRepository) MarkFollowerRelation(ctx context.Context, fromUID, toUID string) error {
|
|
||||||
//nolint:contextcheck
|
|
||||||
session, err := s.neo4jClient.Conn()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer session.Close(ctx)
|
|
||||||
|
|
||||||
params := map[string]interface{}{
|
|
||||||
"fromUID": fromUID,
|
|
||||||
"toUID": toUID,
|
|
||||||
}
|
|
||||||
|
|
||||||
// 這是有向的關係 form -> to
|
|
||||||
query := `
|
|
||||||
MERGE (from:User {uid: $fromUID})
|
|
||||||
MERGE (to:User {uid: $toUID})
|
|
||||||
MERGE (from)-[:FRIENDS_WITH]->(to)
|
|
||||||
RETURN from, to
|
|
||||||
`
|
|
||||||
|
|
||||||
run, err := session.NewSession(ctx, neo4j.SessionConfig{
|
|
||||||
AccessMode: neo4j.AccessModeWrite,
|
|
||||||
}).Run(ctx, query, params)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// 處理結果
|
|
||||||
if run.Next(ctx) {
|
|
||||||
_ = run.Record().AsMap()
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *SocialNetworkRepository) GetFollower(ctx context.Context, req repository.FollowReq) (repository.FollowResp, error) {
|
|
||||||
//nolint:contextcheck
|
|
||||||
session, err := s.neo4jClient.Conn()
|
|
||||||
if err != nil {
|
|
||||||
return repository.FollowResp{}, err
|
|
||||||
}
|
|
||||||
defer session.Close(ctx)
|
|
||||||
|
|
||||||
params := map[string]interface{}{
|
|
||||||
"uid": req.UID,
|
|
||||||
"skip": (req.PageIndex - 1) * req.PageSize,
|
|
||||||
"limit": req.PageSize,
|
|
||||||
}
|
|
||||||
|
|
||||||
query := `
|
|
||||||
MATCH (follower:User)-[:FRIENDS_WITH]->(user:User {uid: $uid})
|
|
||||||
RETURN follower.uid AS uid
|
|
||||||
SKIP $skip LIMIT $limit
|
|
||||||
`
|
|
||||||
|
|
||||||
run, err := session.NewSession(ctx, neo4j.SessionConfig{
|
|
||||||
AccessMode: neo4j.AccessModeRead,
|
|
||||||
}).Run(ctx, query, params)
|
|
||||||
if err != nil {
|
|
||||||
return repository.FollowResp{}, err
|
|
||||||
}
|
|
||||||
|
|
||||||
var uidList []string
|
|
||||||
for run.Next(ctx) {
|
|
||||||
record := run.Record()
|
|
||||||
uid, ok := record.Get("uid")
|
|
||||||
if ok {
|
|
||||||
if uidStr, ok := uid.(string); ok {
|
|
||||||
uidList = append(uidList, uidStr)
|
|
||||||
} else {
|
|
||||||
// TODO 可以印 log
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
total, err := s.GetFollowerCount(ctx, req.UID)
|
|
||||||
if err != nil {
|
|
||||||
return repository.FollowResp{}, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return repository.FollowResp{
|
|
||||||
UIDs: uidList,
|
|
||||||
Total: total,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *SocialNetworkRepository) GetFollowee(ctx context.Context, req repository.FollowReq) (repository.FollowResp, error) {
|
|
||||||
//nolint:contextcheck
|
|
||||||
session, err := s.neo4jClient.Conn()
|
|
||||||
if err != nil {
|
|
||||||
return repository.FollowResp{}, err
|
|
||||||
}
|
|
||||||
defer session.Close(ctx)
|
|
||||||
|
|
||||||
params := map[string]interface{}{
|
|
||||||
"uid": req.UID,
|
|
||||||
"skip": (req.PageIndex - 1) * req.PageSize,
|
|
||||||
"limit": req.PageSize,
|
|
||||||
}
|
|
||||||
|
|
||||||
query := `
|
|
||||||
MATCH (user:User {uid: $uid})-[:FRIENDS_WITH]->(followee:User)
|
|
||||||
RETURN followee.uid AS uid
|
|
||||||
SKIP $skip LIMIT $limit
|
|
||||||
`
|
|
||||||
|
|
||||||
run, err := session.NewSession(ctx, neo4j.SessionConfig{
|
|
||||||
AccessMode: neo4j.AccessModeRead,
|
|
||||||
}).Run(ctx, query, params)
|
|
||||||
if err != nil {
|
|
||||||
return repository.FollowResp{}, err
|
|
||||||
}
|
|
||||||
|
|
||||||
var uidList []string
|
|
||||||
for run.Next(ctx) {
|
|
||||||
record := run.Record()
|
|
||||||
uid, ok := record.Get("uid")
|
|
||||||
if ok {
|
|
||||||
if uidStr, ok := uid.(string); ok {
|
|
||||||
uidList = append(uidList, uidStr)
|
|
||||||
} else {
|
|
||||||
// 可以印 log
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
total, err := s.GetFolloweeCount(ctx, req.UID)
|
|
||||||
if err != nil {
|
|
||||||
return repository.FollowResp{}, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return repository.FollowResp{
|
|
||||||
UIDs: uidList,
|
|
||||||
Total: total,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *SocialNetworkRepository) GetFollowerCount(ctx context.Context, uid string) (int64, error) {
|
|
||||||
//nolint:contextcheck
|
|
||||||
session, err := s.neo4jClient.Conn()
|
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
defer session.Close(ctx)
|
|
||||||
|
|
||||||
params := map[string]interface{}{
|
|
||||||
"uid": uid,
|
|
||||||
}
|
|
||||||
|
|
||||||
query := `
|
|
||||||
MATCH (:User)-[:FRIENDS_WITH]->(user:User {uid: $uid})
|
|
||||||
RETURN count(*) AS followerCount
|
|
||||||
`
|
|
||||||
|
|
||||||
run, err := session.NewSession(ctx, neo4j.SessionConfig{
|
|
||||||
AccessMode: neo4j.AccessModeRead,
|
|
||||||
}).Run(ctx, query, params)
|
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
|
|
||||||
var count int64
|
|
||||||
if run.Next(ctx) {
|
|
||||||
record := run.Record()
|
|
||||||
if followerCount, ok := record.Get("followerCount"); ok {
|
|
||||||
if dc, ok := followerCount.(int64); ok {
|
|
||||||
count = dc
|
|
||||||
} else {
|
|
||||||
logx.Info("followerCount error")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return count, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *SocialNetworkRepository) GetFolloweeCount(ctx context.Context, uid string) (int64, error) {
|
|
||||||
//nolint:contextcheck
|
|
||||||
session, err := s.neo4jClient.Conn()
|
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
defer session.Close(ctx)
|
|
||||||
|
|
||||||
params := map[string]interface{}{
|
|
||||||
"uid": uid,
|
|
||||||
}
|
|
||||||
|
|
||||||
query := `
|
|
||||||
MATCH (user:User {uid: $uid})-[:FRIENDS_WITH]->(:User)
|
|
||||||
RETURN count(*) AS followeeCount
|
|
||||||
`
|
|
||||||
|
|
||||||
run, err := session.NewSession(ctx, neo4j.SessionConfig{
|
|
||||||
AccessMode: neo4j.AccessModeRead,
|
|
||||||
}).Run(ctx, query, params)
|
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
|
|
||||||
var count int64
|
|
||||||
if run.Next(ctx) {
|
|
||||||
record := run.Record()
|
|
||||||
if followeeCount, ok := record.Get("followeeCount"); ok {
|
|
||||||
if dc, ok := followeeCount.(int64); ok {
|
|
||||||
count = dc
|
|
||||||
} else {
|
|
||||||
logx.Info("followeeCount error")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return count, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *SocialNetworkRepository) RemoveFollowerRelation(ctx context.Context, fromUID, toUID string) error {
|
|
||||||
//nolint:contextcheck
|
|
||||||
session, err := s.neo4jClient.Conn()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer session.Close(ctx)
|
|
||||||
|
|
||||||
params := map[string]interface{}{
|
|
||||||
"fromUID": fromUID,
|
|
||||||
"toUID": toUID,
|
|
||||||
}
|
|
||||||
|
|
||||||
query := `
|
|
||||||
MATCH (from:User {uid: $fromUID})-[r:FRIENDS_WITH]->(to:User {uid: $toUID})
|
|
||||||
DELETE r
|
|
||||||
`
|
|
||||||
|
|
||||||
_, err = session.NewSession(ctx, neo4j.SessionConfig{
|
|
||||||
AccessMode: neo4j.AccessModeWrite,
|
|
||||||
}).Run(ctx, query, params)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to remove follower relation: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetDegreeBetweenUsers 取得這兩個點之間的度數 (最短路徑長度)
|
|
||||||
func (s *SocialNetworkRepository) GetDegreeBetweenUsers(ctx context.Context, uid1, uid2 string) (int64, error) {
|
|
||||||
//nolint:contextcheck
|
|
||||||
session, err := s.neo4jClient.Conn()
|
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
defer session.Close(ctx)
|
|
||||||
|
|
||||||
params := map[string]interface{}{
|
|
||||||
"uid1": uid1,
|
|
||||||
"uid2": uid2,
|
|
||||||
}
|
|
||||||
|
|
||||||
query := `
|
|
||||||
MATCH (user1:User {uid: $uid1}), (user2:User {uid: $uid2})
|
|
||||||
MATCH p = shortestPath((user1)-[*]-(user2))
|
|
||||||
RETURN length(p) AS degree
|
|
||||||
`
|
|
||||||
|
|
||||||
run, err := session.NewSession(ctx, neo4j.SessionConfig{
|
|
||||||
AccessMode: neo4j.AccessModeRead,
|
|
||||||
}).Run(ctx, query, params)
|
|
||||||
if err != nil {
|
|
||||||
return 0, fmt.Errorf("failed to get degree between users: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
var degree int64
|
|
||||||
if run.Next(ctx) {
|
|
||||||
record := run.Record()
|
|
||||||
if deg, ok := record.Get("degree"); ok {
|
|
||||||
if degreeValue, ok := deg.(int64); ok {
|
|
||||||
degree = degreeValue
|
|
||||||
} else {
|
|
||||||
logx.Info("degree error")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return degree, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetUIDsWithinNDegrees 取得某個節點在 n 度內關係所有 UID
|
|
||||||
func (s *SocialNetworkRepository) GetUIDsWithinNDegrees(ctx context.Context, uid string, degrees, pageSize, pageIndex int64) ([]string, int64, error) {
|
|
||||||
//nolint:contextcheck
|
|
||||||
session, err := s.neo4jClient.Conn()
|
|
||||||
if err != nil {
|
|
||||||
return nil, 0, err
|
|
||||||
}
|
|
||||||
defer session.Close(ctx)
|
|
||||||
|
|
||||||
params := map[string]interface{}{
|
|
||||||
"uid": uid,
|
|
||||||
"degrees": degrees,
|
|
||||||
"skip": (pageIndex - 1) * pageSize,
|
|
||||||
"limit": pageSize,
|
|
||||||
}
|
|
||||||
|
|
||||||
// 查詢結果帶分頁
|
|
||||||
query := `
|
|
||||||
MATCH (user:User {uid: $uid})-[:FRIENDS_WITH*1..$degrees]-(related:User)
|
|
||||||
WITH DISTINCT related.uid AS uid
|
|
||||||
SKIP $skip LIMIT $limit
|
|
||||||
RETURN uid
|
|
||||||
`
|
|
||||||
|
|
||||||
run, err := session.NewSession(ctx, neo4j.SessionConfig{
|
|
||||||
AccessMode: neo4j.AccessModeRead,
|
|
||||||
}).Run(ctx, query, params)
|
|
||||||
if err != nil {
|
|
||||||
return nil, 0, fmt.Errorf("failed to get uids within %d degrees of user: %w", degrees, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
var uidList []string
|
|
||||||
for run.Next(ctx) {
|
|
||||||
record := run.Record()
|
|
||||||
uid, ok := record.Get("uid")
|
|
||||||
if ok {
|
|
||||||
if uidStr, ok := uid.(string); ok {
|
|
||||||
uidList = append(uidList, uidStr)
|
|
||||||
} else {
|
|
||||||
// 可以印 log
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 計算總數
|
|
||||||
totalCount, err := s.getTotalUIDsWithinNDegrees(ctx, uid, degrees)
|
|
||||||
if err != nil {
|
|
||||||
return nil, 0, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return uidList, totalCount, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *SocialNetworkRepository) getTotalUIDsWithinNDegrees(ctx context.Context, uid string, degrees int64) (int64, error) {
|
|
||||||
//nolint:contextcheck
|
|
||||||
session, err := s.neo4jClient.Conn()
|
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
defer session.Close(ctx)
|
|
||||||
|
|
||||||
params := map[string]interface{}{
|
|
||||||
"uid": uid,
|
|
||||||
"degrees": degrees,
|
|
||||||
}
|
|
||||||
|
|
||||||
query := `
|
|
||||||
MATCH (user:User {uid: $uid})-[:FRIENDS_WITH*1..$degrees]-(related:User)
|
|
||||||
RETURN count(DISTINCT related.uid) AS totalCount
|
|
||||||
`
|
|
||||||
|
|
||||||
run, err := session.NewSession(ctx, neo4j.SessionConfig{
|
|
||||||
AccessMode: neo4j.AccessModeRead,
|
|
||||||
}).Run(ctx, query, params)
|
|
||||||
if err != nil {
|
|
||||||
return 0, fmt.Errorf("failed to get total uids within %d degrees of user: %w", degrees, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
var totalCount int64
|
|
||||||
if run.Next(ctx) {
|
|
||||||
record := run.Record()
|
|
||||||
if count, ok := record.Get("totalCount"); ok {
|
|
||||||
if countV, ok := count.(int64); ok {
|
|
||||||
totalCount = countV
|
|
||||||
} else {
|
|
||||||
logx.Info("totalCount error")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return totalCount, nil
|
|
||||||
}
|
|
|
@ -1 +0,0 @@
|
||||||
package repository
|
|
|
@ -1,144 +0,0 @@
|
||||||
package repository
|
|
||||||
|
|
||||||
import (
|
|
||||||
"app-cloudep-tweeting-service/gen_result/pb/tweeting"
|
|
||||||
"app-cloudep-tweeting-service/internal/config"
|
|
||||||
"app-cloudep-tweeting-service/internal/domain"
|
|
||||||
"app-cloudep-tweeting-service/internal/domain/repository"
|
|
||||||
"context"
|
|
||||||
"errors"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/zeromicro/go-zero/core/stores/redis"
|
|
||||||
)
|
|
||||||
|
|
||||||
// TODO 第一版本先使用 Redis 來做,後續如果有效能考量,在考慮使用其他方案
|
|
||||||
|
|
||||||
type TimelineRepositoryParam struct {
|
|
||||||
Config config.Config
|
|
||||||
Redis redis.Redis
|
|
||||||
}
|
|
||||||
|
|
||||||
type TimelineRepository struct {
|
|
||||||
cfg config.Config
|
|
||||||
redis redis.Redis
|
|
||||||
}
|
|
||||||
|
|
||||||
func MustGenerateRepository(param TimelineRepositoryParam) repository.TimelineRepository {
|
|
||||||
return &TimelineRepository{
|
|
||||||
cfg: param.Config,
|
|
||||||
redis: param.Redis,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// AddPost 將貼文添加到時間線,並根據 Score 排序
|
|
||||||
func (t *TimelineRepository) AddPost(ctx context.Context, req repository.AddPostRequest) error {
|
|
||||||
key := domain.TimelineRedisKey.With(req.UID).ToString()
|
|
||||||
|
|
||||||
// 準備要插入的元素
|
|
||||||
zItems := make([]redis.Pair, len(req.PostItems))
|
|
||||||
for i, item := range req.PostItems {
|
|
||||||
zItems[i] = redis.Pair{
|
|
||||||
Score: item.Score,
|
|
||||||
Key: item.PostID,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 將 ZSet 元素添加到 Redis
|
|
||||||
_, err := t.redis.ZaddsCtx(ctx, key, zItems...)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// 檢查 ZSet 長度,並在超過 maxLength 時刪除多餘的元素
|
|
||||||
if t.cfg.TimelineSetting.MaxLength > 0 {
|
|
||||||
// 這裡從 0 到 - (maxLength+1) 代表超過限制的元素範圍
|
|
||||||
_, err := t.redis.ZremrangebyrankCtx(ctx, key, 0, -(t.cfg.TimelineSetting.MaxLength + 1))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 設置過期時間
|
|
||||||
return t.redis.ExpireCtx(ctx, key, int(t.cfg.TimelineSetting.Expire))
|
|
||||||
}
|
|
||||||
|
|
||||||
// FetchTimeline 獲取指定用戶的動態時報
|
|
||||||
func (t *TimelineRepository) FetchTimeline(ctx context.Context, req repository.FetchTimelineRequest) (repository.FetchTimelineResponse, error) {
|
|
||||||
key := domain.TimelineRedisKey.With(req.UID).ToString()
|
|
||||||
|
|
||||||
start := (req.PageIndex - 1) * req.PageSize
|
|
||||||
end := start + req.PageSize - 1
|
|
||||||
|
|
||||||
// 從 Redis 中按分數由高到低獲取時間線元素
|
|
||||||
pair, err := t.redis.ZrevrangeWithScoresCtx(ctx, key, start, end)
|
|
||||||
if err != nil {
|
|
||||||
return repository.FetchTimelineResponse{}, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// 構建返回結果
|
|
||||||
items := make([]repository.TimelineItem, len(pair))
|
|
||||||
for i, z := range pair {
|
|
||||||
items[i] = repository.TimelineItem{
|
|
||||||
PostID: z.Key,
|
|
||||||
Score: z.Score,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 計算總數量
|
|
||||||
total, err := t.redis.ZcardCtx(ctx, key)
|
|
||||||
if err != nil {
|
|
||||||
return repository.FetchTimelineResponse{}, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return repository.FetchTimelineResponse{
|
|
||||||
Items: items,
|
|
||||||
Page: tweeting.Pager{
|
|
||||||
Total: int64(total),
|
|
||||||
Index: req.PageIndex,
|
|
||||||
Size: req.PageSize,
|
|
||||||
},
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetNoMoreDataFlag 標記時間線已完整,避免繼續查詢資料庫
|
|
||||||
func (t *TimelineRepository) SetNoMoreDataFlag(ctx context.Context, uid string) error {
|
|
||||||
key := domain.TimelineRedisKey.With(uid).ToString()
|
|
||||||
|
|
||||||
// 添加一個標誌到時間線的 ZSet
|
|
||||||
_, err := t.redis.ZaddsCtx(ctx, key, redis.Pair{
|
|
||||||
Score: time.Now().UTC().Unix(),
|
|
||||||
Key: domain.LastOfTimelineFlag,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// 設置過期時間
|
|
||||||
return t.redis.ExpireCtx(ctx, key, int(t.cfg.TimelineSetting.Expire))
|
|
||||||
}
|
|
||||||
|
|
||||||
// HasNoMoreData 檢查時間線是否已完整,決定是否需要查詢資料庫
|
|
||||||
func (t *TimelineRepository) HasNoMoreData(ctx context.Context, uid string) (bool, error) {
|
|
||||||
key := domain.TimelineRedisKey.With(uid).ToString()
|
|
||||||
|
|
||||||
// 檢查 "NoMoreData" 標誌是否存在
|
|
||||||
score, err := t.redis.ZscoreCtx(ctx, key, domain.LastOfTimelineFlag)
|
|
||||||
if errors.Is(err, redis.Nil) {
|
|
||||||
return false, nil // 標誌不存在
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
return false, err // 其他錯誤
|
|
||||||
}
|
|
||||||
|
|
||||||
return score != 0, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// ClearNoMoreDataFlag 清除時間線的 "NoMoreData" 標誌
|
|
||||||
func (t *TimelineRepository) ClearNoMoreDataFlag(ctx context.Context, uid string) error {
|
|
||||||
key := domain.TimelineRedisKey.With(uid).ToString()
|
|
||||||
// 移除 "NoMoreData" 標誌
|
|
||||||
_, err := t.redis.ZremCtx(ctx, key, domain.LastOfTimelineFlag)
|
|
||||||
|
|
||||||
return err
|
|
||||||
}
|
|
|
@ -1,474 +0,0 @@
|
||||||
package repository
|
|
||||||
|
|
||||||
import (
|
|
||||||
"app-cloudep-tweeting-service/internal/config"
|
|
||||||
"app-cloudep-tweeting-service/internal/domain"
|
|
||||||
"app-cloudep-tweeting-service/internal/domain/repository"
|
|
||||||
"context"
|
|
||||||
"testing"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/alicebob/miniredis/v2"
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
"github.com/zeromicro/go-zero/core/stores/redis"
|
|
||||||
)
|
|
||||||
|
|
||||||
func NewRepo() (*miniredis.Miniredis, repository.TimelineRepository, error) {
|
|
||||||
r1, err := miniredis.Run()
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
newRedis, err := redis.NewRedis(redis.RedisConf{
|
|
||||||
Host: r1.Addr(),
|
|
||||||
Type: redis.ClusterType,
|
|
||||||
Pass: "",
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
r1.Close()
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
c := config.Config{
|
|
||||||
TimelineSetting: struct {
|
|
||||||
Expire int64
|
|
||||||
MaxLength int64
|
|
||||||
}{Expire: 86400, MaxLength: 1000},
|
|
||||||
}
|
|
||||||
|
|
||||||
timelineRepo := MustGenerateRepository(TimelineRepositoryParam{
|
|
||||||
Config: c,
|
|
||||||
Redis: *newRedis,
|
|
||||||
})
|
|
||||||
|
|
||||||
return r1, timelineRepo, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestAddPost(t *testing.T) {
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
action func(t *testing.T, repo repository.TimelineRepository) error
|
|
||||||
expectErr bool
|
|
||||||
validate func(t *testing.T, r1 *miniredis.Miniredis)
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "success",
|
|
||||||
action: func(t *testing.T, repo repository.TimelineRepository) error {
|
|
||||||
ctx := context.Background()
|
|
||||||
uid := "OOOOOOKJ"
|
|
||||||
|
|
||||||
return repo.AddPost(ctx, repository.AddPostRequest{
|
|
||||||
UID: uid,
|
|
||||||
PostItems: []repository.TimelineItem{
|
|
||||||
{PostID: "post1", Score: 100},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
},
|
|
||||||
expectErr: false,
|
|
||||||
validate: func(t *testing.T, r1 *miniredis.Miniredis) {
|
|
||||||
uid := "OOOOOOKJ"
|
|
||||||
key := domain.TimelineRedisKey.With(uid).ToString()
|
|
||||||
score, err := r1.ZScore(key, "post1")
|
|
||||||
assert.NoError(t, err)
|
|
||||||
assert.Equal(t, float64(100), score)
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "timeout",
|
|
||||||
action: func(t *testing.T, repo repository.TimelineRepository) error {
|
|
||||||
ctx := context.Background()
|
|
||||||
timeoutCtx, cancel := context.WithTimeout(ctx, 1*time.Millisecond)
|
|
||||||
defer cancel()
|
|
||||||
time.Sleep(2 * time.Millisecond)
|
|
||||||
|
|
||||||
uid := "OOOOOLK"
|
|
||||||
return repo.AddPost(timeoutCtx, repository.AddPostRequest{
|
|
||||||
UID: uid,
|
|
||||||
PostItems: []repository.TimelineItem{
|
|
||||||
{PostID: "post2", Score: 200},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
},
|
|
||||||
expectErr: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Redis error on Zadd",
|
|
||||||
action: func(t *testing.T, repo repository.TimelineRepository) error {
|
|
||||||
r1, repo, err := NewRepo()
|
|
||||||
assert.NoError(t, err)
|
|
||||||
r1.Close() // 模拟 Redis 错误
|
|
||||||
|
|
||||||
ctx := context.Background()
|
|
||||||
uid := "OOOOOWE"
|
|
||||||
return repo.AddPost(ctx, repository.AddPostRequest{
|
|
||||||
UID: uid,
|
|
||||||
PostItems: []repository.TimelineItem{
|
|
||||||
{PostID: "post3", Score: 300},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
},
|
|
||||||
expectErr: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "duplicate Key",
|
|
||||||
action: func(t *testing.T, repo repository.TimelineRepository) error {
|
|
||||||
ctx := context.Background()
|
|
||||||
uid := "OOOOODUP"
|
|
||||||
|
|
||||||
// 第一次插入
|
|
||||||
err := repo.AddPost(ctx, repository.AddPostRequest{
|
|
||||||
UID: uid,
|
|
||||||
PostItems: []repository.TimelineItem{
|
|
||||||
{PostID: "post1", Score: 100},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// 第二次插入,使用相同的 PostID 但不同的 Score
|
|
||||||
return repo.AddPost(ctx, repository.AddPostRequest{
|
|
||||||
UID: uid,
|
|
||||||
PostItems: []repository.TimelineItem{
|
|
||||||
{PostID: "post1", Score: 200},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
},
|
|
||||||
expectErr: false,
|
|
||||||
validate: func(t *testing.T, r1 *miniredis.Miniredis) {
|
|
||||||
uid := "OOOOODUP"
|
|
||||||
key := domain.TimelineRedisKey.With(uid).ToString()
|
|
||||||
score, err := r1.ZScore(key, "post1")
|
|
||||||
assert.NoError(t, err)
|
|
||||||
assert.Equal(t, float64(200), score) // 應該是第二次插入的分數
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, tt := range tests {
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
|
||||||
r1, repo, err := NewRepo()
|
|
||||||
assert.NoError(t, err)
|
|
||||||
defer r1.Close()
|
|
||||||
|
|
||||||
err = tt.action(t, repo)
|
|
||||||
|
|
||||||
if tt.expectErr {
|
|
||||||
assert.Error(t, err)
|
|
||||||
} else {
|
|
||||||
assert.NoError(t, err)
|
|
||||||
if tt.validate != nil {
|
|
||||||
tt.validate(t, r1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestFetchTimeline(t *testing.T) {
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
action func(t *testing.T, repo repository.TimelineRepository) (repository.FetchTimelineResponse, error)
|
|
||||||
expectErr bool
|
|
||||||
validate func(t *testing.T, r1 *miniredis.Miniredis, resp *repository.FetchTimelineResponse)
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "FetchTimeline - success",
|
|
||||||
action: func(t *testing.T, repo repository.TimelineRepository) (repository.FetchTimelineResponse, error) {
|
|
||||||
ctx := context.Background()
|
|
||||||
uid := "user123"
|
|
||||||
|
|
||||||
_ = repo.AddPost(ctx, repository.AddPostRequest{
|
|
||||||
UID: uid,
|
|
||||||
PostItems: []repository.TimelineItem{
|
|
||||||
{PostID: "post1", Score: 200},
|
|
||||||
{PostID: "post2", Score: 100},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
return repo.FetchTimeline(ctx, repository.FetchTimelineRequest{
|
|
||||||
UID: uid,
|
|
||||||
PageSize: 10,
|
|
||||||
PageIndex: 1,
|
|
||||||
})
|
|
||||||
},
|
|
||||||
expectErr: false,
|
|
||||||
validate: func(t *testing.T, r1 *miniredis.Miniredis, resp *repository.FetchTimelineResponse) {
|
|
||||||
assert.Equal(t, 2, len(resp.Items))
|
|
||||||
assert.Equal(t, "post1", resp.Items[0].PostID)
|
|
||||||
assert.Equal(t, "post2", resp.Items[1].PostID)
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "FetchTimeline - timeout",
|
|
||||||
action: func(t *testing.T, repo repository.TimelineRepository) (repository.FetchTimelineResponse, error) {
|
|
||||||
ctx := context.Background()
|
|
||||||
timeoutCtx, cancel := context.WithTimeout(ctx, 1*time.Millisecond)
|
|
||||||
defer cancel()
|
|
||||||
time.Sleep(2 * time.Millisecond)
|
|
||||||
|
|
||||||
uid := "user123"
|
|
||||||
return repo.FetchTimeline(timeoutCtx, repository.FetchTimelineRequest{
|
|
||||||
UID: uid,
|
|
||||||
PageSize: 10,
|
|
||||||
PageIndex: 1,
|
|
||||||
})
|
|
||||||
},
|
|
||||||
expectErr: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "FetchTimeline - Redis error on ZrangebyscoreWithScoresCtx",
|
|
||||||
action: func(t *testing.T, repo repository.TimelineRepository) (repository.FetchTimelineResponse, error) {
|
|
||||||
r1, repo, err := NewRepo()
|
|
||||||
assert.NoError(t, err)
|
|
||||||
defer r1.Close()
|
|
||||||
|
|
||||||
uid := "user123"
|
|
||||||
|
|
||||||
_ = repo.AddPost(context.Background(), repository.AddPostRequest{
|
|
||||||
UID: uid,
|
|
||||||
PostItems: []repository.TimelineItem{
|
|
||||||
{PostID: "post3", Score: 300},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
r1.Close()
|
|
||||||
|
|
||||||
return repo.FetchTimeline(context.Background(), repository.FetchTimelineRequest{
|
|
||||||
UID: uid,
|
|
||||||
PageSize: 10,
|
|
||||||
PageIndex: 1,
|
|
||||||
})
|
|
||||||
},
|
|
||||||
expectErr: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "FetchTimeline - Redis error on ZcardCtx",
|
|
||||||
action: func(t *testing.T, repo repository.TimelineRepository) (repository.FetchTimelineResponse, error) {
|
|
||||||
r1, repo, err := NewRepo()
|
|
||||||
assert.NoError(t, err)
|
|
||||||
defer r1.Close()
|
|
||||||
|
|
||||||
uid := "user123"
|
|
||||||
|
|
||||||
_ = repo.AddPost(context.Background(), repository.AddPostRequest{
|
|
||||||
UID: uid,
|
|
||||||
PostItems: []repository.TimelineItem{
|
|
||||||
{PostID: "post4", Score: 400},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
r1.Close()
|
|
||||||
|
|
||||||
return repo.FetchTimeline(context.Background(), repository.FetchTimelineRequest{
|
|
||||||
UID: uid,
|
|
||||||
PageSize: 10,
|
|
||||||
PageIndex: 1,
|
|
||||||
})
|
|
||||||
},
|
|
||||||
expectErr: true,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, tt := range tests {
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
|
||||||
r1, repo, err := NewRepo()
|
|
||||||
assert.NoError(t, err)
|
|
||||||
defer r1.Close()
|
|
||||||
|
|
||||||
resp, err := tt.action(t, repo)
|
|
||||||
|
|
||||||
if tt.expectErr {
|
|
||||||
assert.Error(t, err)
|
|
||||||
} else {
|
|
||||||
assert.NoError(t, err)
|
|
||||||
if tt.validate != nil {
|
|
||||||
tt.validate(t, r1, &resp)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestSetNoMoreDataFlag(t *testing.T) {
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
action func(t *testing.T, repo repository.TimelineRepository) error
|
|
||||||
expectErr bool
|
|
||||||
validate func(t *testing.T, r1 *miniredis.Miniredis)
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "SetNoMoreDataFlag - success",
|
|
||||||
action: func(t *testing.T, repo repository.TimelineRepository) error {
|
|
||||||
ctx := context.Background()
|
|
||||||
uid := "user123"
|
|
||||||
return repo.SetNoMoreDataFlag(ctx, uid)
|
|
||||||
},
|
|
||||||
expectErr: false,
|
|
||||||
validate: func(t *testing.T, r1 *miniredis.Miniredis) {
|
|
||||||
uid := "user123"
|
|
||||||
key := domain.TimelineRedisKey.With(uid).ToString()
|
|
||||||
score, _ := r1.ZScore(key, domain.LastOfTimelineFlag)
|
|
||||||
assert.NotZero(t, score)
|
|
||||||
|
|
||||||
// 驗證是否設定過期時間
|
|
||||||
ttl := r1.TTL(key)
|
|
||||||
assert.Equal(t, time.Duration(86400)*time.Second, ttl)
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "SetNoMoreDataFlag - Redis error on ZaddsCtx",
|
|
||||||
action: func(t *testing.T, repo repository.TimelineRepository) error {
|
|
||||||
r1, repo, err := NewRepo()
|
|
||||||
assert.NoError(t, err)
|
|
||||||
r1.Close() // 手動關閉,復現錯誤
|
|
||||||
|
|
||||||
ctx := context.Background()
|
|
||||||
uid := "user123"
|
|
||||||
return repo.SetNoMoreDataFlag(ctx, uid)
|
|
||||||
},
|
|
||||||
expectErr: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "SetNoMoreDataFlag - Redis error on ExpireCtx",
|
|
||||||
action: func(t *testing.T, repo repository.TimelineRepository) error {
|
|
||||||
r1, repo, err := NewRepo()
|
|
||||||
assert.NoError(t, err)
|
|
||||||
|
|
||||||
ctx := context.Background()
|
|
||||||
uid := "user123"
|
|
||||||
|
|
||||||
r1.Close()
|
|
||||||
|
|
||||||
return repo.SetNoMoreDataFlag(ctx, uid)
|
|
||||||
},
|
|
||||||
expectErr: true,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, tt := range tests {
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
|
||||||
r1, repo, err := NewRepo()
|
|
||||||
assert.NoError(t, err)
|
|
||||||
defer r1.Close()
|
|
||||||
|
|
||||||
err = tt.action(t, repo)
|
|
||||||
|
|
||||||
if tt.expectErr {
|
|
||||||
assert.Error(t, err)
|
|
||||||
} else {
|
|
||||||
assert.NoError(t, err)
|
|
||||||
if tt.validate != nil {
|
|
||||||
tt.validate(t, r1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestHasNoMoreData(t *testing.T) {
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
action func(t *testing.T, repo repository.TimelineRepository) (bool, error)
|
|
||||||
expectErr bool
|
|
||||||
expected bool
|
|
||||||
setup func(r1 *miniredis.Miniredis)
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "HasNoMoreData - 標誌存在",
|
|
||||||
action: func(t *testing.T, repo repository.TimelineRepository) (bool, error) {
|
|
||||||
ctx := context.Background()
|
|
||||||
uid := "user123"
|
|
||||||
return repo.HasNoMoreData(ctx, uid)
|
|
||||||
},
|
|
||||||
expectErr: false,
|
|
||||||
expected: true,
|
|
||||||
setup: func(r1 *miniredis.Miniredis) {
|
|
||||||
uid := "user123"
|
|
||||||
key := domain.TimelineRedisKey.With(uid).ToString()
|
|
||||||
_, _ = r1.ZAdd(key, float64(time.Now().UTC().Unix()), domain.LastOfTimelineFlag)
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "HasNoMoreData - 標誌不存在",
|
|
||||||
action: func(t *testing.T, repo repository.TimelineRepository) (bool, error) {
|
|
||||||
ctx := context.Background()
|
|
||||||
uid := "user123"
|
|
||||||
return repo.HasNoMoreData(ctx, uid)
|
|
||||||
},
|
|
||||||
expectErr: false,
|
|
||||||
expected: false,
|
|
||||||
setup: func(r1 *miniredis.Miniredis) {}, // 不設置標誌
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, tt := range tests {
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
|
||||||
r1, repo, err := NewRepo()
|
|
||||||
assert.NoError(t, err)
|
|
||||||
defer r1.Close()
|
|
||||||
|
|
||||||
tt.setup(r1)
|
|
||||||
|
|
||||||
result, err := tt.action(t, repo)
|
|
||||||
|
|
||||||
if tt.expectErr {
|
|
||||||
assert.Error(t, err)
|
|
||||||
} else {
|
|
||||||
assert.NoError(t, err)
|
|
||||||
assert.Equal(t, tt.expected, result)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestClearNoMoreDataFlag(t *testing.T) {
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
action func(t *testing.T, repo repository.TimelineRepository) error
|
|
||||||
expectErr bool
|
|
||||||
setup func(r1 *miniredis.Miniredis)
|
|
||||||
validate func(t *testing.T, r1 *miniredis.Miniredis)
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "ClearNoMoreDataFlag - 成功清除標誌",
|
|
||||||
action: func(t *testing.T, repo repository.TimelineRepository) error {
|
|
||||||
ctx := context.Background()
|
|
||||||
uid := "user123"
|
|
||||||
return repo.ClearNoMoreDataFlag(ctx, uid)
|
|
||||||
},
|
|
||||||
expectErr: false,
|
|
||||||
setup: func(r1 *miniredis.Miniredis) {
|
|
||||||
uid := "user123"
|
|
||||||
key := domain.TimelineRedisKey.With(uid).ToString()
|
|
||||||
_, err := r1.ZAdd(key, 100, domain.LastOfTimelineFlag) // 設置標誌
|
|
||||||
assert.NoError(t, err)
|
|
||||||
},
|
|
||||||
validate: func(t *testing.T, r1 *miniredis.Miniredis) {
|
|
||||||
uid := "user123"
|
|
||||||
key := domain.TimelineRedisKey.With(uid).ToString()
|
|
||||||
_, err := r1.ZScore(key, domain.LastOfTimelineFlag)
|
|
||||||
assert.Error(t, err) // 標誌應該已被移除
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, tt := range tests {
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
|
||||||
r1, repo, err := NewRepo()
|
|
||||||
assert.NoError(t, err)
|
|
||||||
defer r1.Close()
|
|
||||||
|
|
||||||
tt.setup(r1)
|
|
||||||
|
|
||||||
err = tt.action(t, repo)
|
|
||||||
|
|
||||||
if tt.expectErr {
|
|
||||||
assert.Error(t, err)
|
|
||||||
} else {
|
|
||||||
assert.NoError(t, err)
|
|
||||||
tt.validate(t, r1)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,59 +0,0 @@
|
||||||
// Code generated by goctl. DO NOT EDIT.
|
|
||||||
// Source: tweeting.proto
|
|
||||||
|
|
||||||
package server
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
|
|
||||||
"app-cloudep-tweeting-service/gen_result/pb/tweeting"
|
|
||||||
socialnetworkservicelogic "app-cloudep-tweeting-service/internal/logic/socialnetworkservice"
|
|
||||||
"app-cloudep-tweeting-service/internal/svc"
|
|
||||||
)
|
|
||||||
|
|
||||||
type SocialNetworkServiceServer struct {
|
|
||||||
svcCtx *svc.ServiceContext
|
|
||||||
tweeting.UnimplementedSocialNetworkServiceServer
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewSocialNetworkServiceServer(svcCtx *svc.ServiceContext) *SocialNetworkServiceServer {
|
|
||||||
return &SocialNetworkServiceServer{
|
|
||||||
svcCtx: svcCtx,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// MarkFollowRelation 關注
|
|
||||||
func (s *SocialNetworkServiceServer) MarkFollowRelation(ctx context.Context, in *tweeting.DoFollowerRelationReq) (*tweeting.OKResp, error) {
|
|
||||||
l := socialnetworkservicelogic.NewMarkFollowRelationLogic(ctx, s.svcCtx)
|
|
||||||
return l.MarkFollowRelation(in)
|
|
||||||
}
|
|
||||||
|
|
||||||
// RemoveFollowRelation 取消關注
|
|
||||||
func (s *SocialNetworkServiceServer) RemoveFollowRelation(ctx context.Context, in *tweeting.DoFollowerRelationReq) (*tweeting.OKResp, error) {
|
|
||||||
l := socialnetworkservicelogic.NewRemoveFollowRelationLogic(ctx, s.svcCtx)
|
|
||||||
return l.RemoveFollowRelation(in)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetFollower 取得跟隨者名單
|
|
||||||
func (s *SocialNetworkServiceServer) GetFollower(ctx context.Context, in *tweeting.FollowReq) (*tweeting.FollowResp, error) {
|
|
||||||
l := socialnetworkservicelogic.NewGetFollowerLogic(ctx, s.svcCtx)
|
|
||||||
return l.GetFollower(in)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetFollowee 取得我跟隨的名單
|
|
||||||
func (s *SocialNetworkServiceServer) GetFollowee(ctx context.Context, in *tweeting.FollowReq) (*tweeting.FollowResp, error) {
|
|
||||||
l := socialnetworkservicelogic.NewGetFolloweeLogic(ctx, s.svcCtx)
|
|
||||||
return l.GetFollowee(in)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetFollowerCount 取得跟隨者數量
|
|
||||||
func (s *SocialNetworkServiceServer) GetFollowerCount(ctx context.Context, in *tweeting.FollowCountReq) (*tweeting.FollowCountResp, error) {
|
|
||||||
l := socialnetworkservicelogic.NewGetFollowerCountLogic(ctx, s.svcCtx)
|
|
||||||
return l.GetFollowerCount(in)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetFolloweeCount 取得我跟隨的數量
|
|
||||||
func (s *SocialNetworkServiceServer) GetFolloweeCount(ctx context.Context, in *tweeting.FollowCountReq) (*tweeting.FollowCountResp, error) {
|
|
||||||
l := socialnetworkservicelogic.NewGetFolloweeCountLogic(ctx, s.svcCtx)
|
|
||||||
return l.GetFolloweeCount(in)
|
|
||||||
}
|
|
|
@ -1,53 +0,0 @@
|
||||||
// Code generated by goctl. DO NOT EDIT.
|
|
||||||
// Source: tweeting.proto
|
|
||||||
|
|
||||||
package server
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
|
|
||||||
"app-cloudep-tweeting-service/gen_result/pb/tweeting"
|
|
||||||
timelineservicelogic "app-cloudep-tweeting-service/internal/logic/timelineservice"
|
|
||||||
"app-cloudep-tweeting-service/internal/svc"
|
|
||||||
)
|
|
||||||
|
|
||||||
type TimelineServiceServer struct {
|
|
||||||
svcCtx *svc.ServiceContext
|
|
||||||
tweeting.UnimplementedTimelineServiceServer
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewTimelineServiceServer(svcCtx *svc.ServiceContext) *TimelineServiceServer {
|
|
||||||
return &TimelineServiceServer{
|
|
||||||
svcCtx: svcCtx,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// AddPost 加入貼文,只管一股腦全塞,這裡會自動判斷
|
|
||||||
func (s *TimelineServiceServer) AddPost(ctx context.Context, in *tweeting.AddPostToTimelineReq) (*tweeting.OKResp, error) {
|
|
||||||
l := timelineservicelogic.NewAddPostLogic(ctx, s.svcCtx)
|
|
||||||
return l.AddPost(in)
|
|
||||||
}
|
|
||||||
|
|
||||||
// FetchTimeline 取得這個人的動態時報
|
|
||||||
func (s *TimelineServiceServer) FetchTimeline(ctx context.Context, in *tweeting.GetTimelineReq) (*tweeting.FetchTimelineResponse, error) {
|
|
||||||
l := timelineservicelogic.NewFetchTimelineLogic(ctx, s.svcCtx)
|
|
||||||
return l.FetchTimeline(in)
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetNoMoreDataFlag 標記時間線已完整,避免繼續查詢資料庫。
|
|
||||||
func (s *TimelineServiceServer) SetNoMoreDataFlag(ctx context.Context, in *tweeting.DoNoMoreDataReq) (*tweeting.OKResp, error) {
|
|
||||||
l := timelineservicelogic.NewSetNoMoreDataFlagLogic(ctx, s.svcCtx)
|
|
||||||
return l.SetNoMoreDataFlag(in)
|
|
||||||
}
|
|
||||||
|
|
||||||
// HasNoMoreData 檢查時間線是否已完整,決定是否需要查詢資料庫。
|
|
||||||
func (s *TimelineServiceServer) HasNoMoreData(ctx context.Context, in *tweeting.DoNoMoreDataReq) (*tweeting.HasNoMoreDataResp, error) {
|
|
||||||
l := timelineservicelogic.NewHasNoMoreDataLogic(ctx, s.svcCtx)
|
|
||||||
return l.HasNoMoreData(in)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ClearNoMoreDataFlag 清除時間線的 "NoMoreData" 標誌。
|
|
||||||
func (s *TimelineServiceServer) ClearNoMoreDataFlag(ctx context.Context, in *tweeting.DoNoMoreDataReq) (*tweeting.OKResp, error) {
|
|
||||||
l := timelineservicelogic.NewClearNoMoreDataFlagLogic(ctx, s.svcCtx)
|
|
||||||
return l.ClearNoMoreDataFlag(in)
|
|
||||||
}
|
|
|
@ -6,7 +6,7 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
)
|
)
|
||||||
|
|
||||||
func mustMongoConnectURL(c config.Config) string {
|
func mustMongoConnectUrl(c config.Config) string {
|
||||||
return fmt.Sprintf("%s://%s:%s",
|
return fmt.Sprintf("%s://%s:%s",
|
||||||
c.Mongo.Schema,
|
c.Mongo.Schema,
|
||||||
c.Mongo.Host,
|
c.Mongo.Host,
|
||||||
|
@ -18,12 +18,10 @@ func mustMongoConnectURL(c config.Config) string {
|
||||||
|
|
||||||
func MustPostModel(c config.Config) model.PostModel {
|
func MustPostModel(c config.Config) model.PostModel {
|
||||||
postCollection := model.Post{}
|
postCollection := model.Post{}
|
||||||
|
return model.NewPostModel(mustMongoConnectUrl(c), c.Mongo.Database, postCollection.CollectionName())
|
||||||
return model.NewPostModel(mustMongoConnectURL(c), c.Mongo.Database, postCollection.CollectionName())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func MustCommentModel(c config.Config) model.CommentModel {
|
func MustCommentModel(c config.Config) model.CommentModel {
|
||||||
m := model.Comment{}
|
m := model.Comment{}
|
||||||
|
return model.NewCommentModel(mustMongoConnectUrl(c), c.Mongo.Database, m.CollectionName())
|
||||||
return model.NewCommentModel(mustMongoConnectURL(c), c.Mongo.Database, m.CollectionName())
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,52 +2,24 @@ package svc
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"app-cloudep-tweeting-service/internal/config"
|
"app-cloudep-tweeting-service/internal/config"
|
||||||
domainRepo "app-cloudep-tweeting-service/internal/domain/repository"
|
|
||||||
"app-cloudep-tweeting-service/internal/lib/neo4j"
|
|
||||||
model "app-cloudep-tweeting-service/internal/model/mongo"
|
model "app-cloudep-tweeting-service/internal/model/mongo"
|
||||||
"app-cloudep-tweeting-service/internal/repository"
|
|
||||||
|
|
||||||
"github.com/zeromicro/go-zero/core/stores/redis"
|
|
||||||
|
|
||||||
vi "code.30cm.net/digimon/library-go/validator"
|
vi "code.30cm.net/digimon/library-go/validator"
|
||||||
)
|
)
|
||||||
|
|
||||||
type ServiceContext struct {
|
type ServiceContext struct {
|
||||||
Config config.Config
|
Config config.Config
|
||||||
Validate vi.Validate
|
Validate vi.Validate
|
||||||
PostModel model.PostModel
|
|
||||||
CommentModel model.CommentModel
|
PostModel model.PostModel
|
||||||
TimelineRepo domainRepo.TimelineRepository
|
CommentModel model.CommentModel
|
||||||
SocialNetworkRepository domainRepo.SocialNetworkRepository
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewServiceContext(c config.Config) *ServiceContext {
|
func NewServiceContext(c config.Config) *ServiceContext {
|
||||||
newRedis, err := redis.NewRedis(c.RedisCluster, redis.Cluster())
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
neoClient := neo4j.NewNeo4J(&neo4j.Config{
|
|
||||||
URI: c.Neo4J.URI,
|
|
||||||
Username: c.Neo4J.Username,
|
|
||||||
Password: c.Neo4J.Password,
|
|
||||||
MaxConnectionPoolSize: c.Neo4J.MaxConnectionPoolSize,
|
|
||||||
MaxConnectionLifetime: c.Neo4J.MaxConnectionLifetime,
|
|
||||||
ConnectionTimeout: c.Neo4J.ConnectionTimeout,
|
|
||||||
}, neo4j.WithPerformance(), neo4j.WithLogLevel(c.Neo4J.LogLevel))
|
|
||||||
|
|
||||||
return &ServiceContext{
|
return &ServiceContext{
|
||||||
Config: c,
|
Config: c,
|
||||||
Validate: vi.MustValidator(),
|
Validate: vi.MustValidator(),
|
||||||
PostModel: MustPostModel(c),
|
PostModel: MustPostModel(c),
|
||||||
CommentModel: MustCommentModel(c),
|
CommentModel: MustCommentModel(c),
|
||||||
TimelineRepo: repository.MustGenerateRepository(repository.TimelineRepositoryParam{
|
|
||||||
Config: c,
|
|
||||||
Redis: *newRedis,
|
|
||||||
}),
|
|
||||||
SocialNetworkRepository: repository.MustSocialNetworkRepository(repository.SocialNetworkParam{
|
|
||||||
Config: c,
|
|
||||||
Neo4jClient: neoClient,
|
|
||||||
}),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
13
tweeting.go
13
tweeting.go
|
@ -1,15 +1,13 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
"app-cloudep-tweeting-service/gen_result/pb/tweeting"
|
"app-cloudep-tweeting-service/gen_result/pb/tweeting"
|
||||||
"app-cloudep-tweeting-service/internal/config"
|
"app-cloudep-tweeting-service/internal/config"
|
||||||
commentserviceServer "app-cloudep-tweeting-service/internal/server/commentservice"
|
|
||||||
postserviceServer "app-cloudep-tweeting-service/internal/server/postservice"
|
postserviceServer "app-cloudep-tweeting-service/internal/server/postservice"
|
||||||
socialnetworkserviceServer "app-cloudep-tweeting-service/internal/server/socialnetworkservice"
|
|
||||||
timelineserviceServer "app-cloudep-tweeting-service/internal/server/timelineservice"
|
|
||||||
"app-cloudep-tweeting-service/internal/svc"
|
"app-cloudep-tweeting-service/internal/svc"
|
||||||
"flag"
|
|
||||||
"log"
|
|
||||||
|
|
||||||
"github.com/zeromicro/go-zero/core/conf"
|
"github.com/zeromicro/go-zero/core/conf"
|
||||||
"github.com/zeromicro/go-zero/core/service"
|
"github.com/zeromicro/go-zero/core/service"
|
||||||
|
@ -29,9 +27,6 @@ func main() {
|
||||||
|
|
||||||
s := zrpc.MustNewServer(c.RpcServerConf, func(grpcServer *grpc.Server) {
|
s := zrpc.MustNewServer(c.RpcServerConf, func(grpcServer *grpc.Server) {
|
||||||
tweeting.RegisterPostServiceServer(grpcServer, postserviceServer.NewPostServiceServer(ctx))
|
tweeting.RegisterPostServiceServer(grpcServer, postserviceServer.NewPostServiceServer(ctx))
|
||||||
tweeting.RegisterCommentServiceServer(grpcServer, commentserviceServer.NewCommentServiceServer(ctx))
|
|
||||||
tweeting.RegisterTimelineServiceServer(grpcServer, timelineserviceServer.NewTimelineServiceServer(ctx))
|
|
||||||
tweeting.RegisterSocialNetworkServiceServer(grpcServer, socialnetworkserviceServer.NewSocialNetworkServiceServer(ctx))
|
|
||||||
|
|
||||||
if c.Mode == service.DevMode || c.Mode == service.TestMode {
|
if c.Mode == service.DevMode || c.Mode == service.TestMode {
|
||||||
reflection.Register(grpcServer)
|
reflection.Register(grpcServer)
|
||||||
|
@ -39,6 +34,6 @@ func main() {
|
||||||
})
|
})
|
||||||
defer s.Stop()
|
defer s.Stop()
|
||||||
|
|
||||||
log.Printf("Starting rpc server at %s...\n", c.ListenOn)
|
fmt.Printf("Starting rpc server at %s...\n", c.ListenOn)
|
||||||
s.Start()
|
s.Start()
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue