Compare commits
7 Commits
main
...
feature/po
Author | SHA1 | Date |
---|---|---|
daniel.w | a76840fe97 | |
daniel.w | a3796aabdc | |
daniel.w | 30fb3b3721 | |
daniel.w | 98605573f3 | |
daniel.w | 518e1673fe | |
daniel.w | b4e7082576 | |
daniel.w | 132f1ba951 |
|
@ -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:
|
||||||
|
|
19
Makefile
19
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
|
||||||
|
@ -50,23 +49,9 @@ build-docker:
|
||||||
|
|
||||||
gen-mongo-model: # 建立 rpc 資料庫
|
gen-mongo-model: # 建立 rpc 資料庫
|
||||||
# 只產生 Model 剩下的要自己撰寫,連欄位名稱也是
|
# 只產生 Model 剩下的要自己撰寫,連欄位名稱也是
|
||||||
goctl model mongo -t post --dir ./internal/model/mongo --style $(GO_ZERO_STYLE)
|
goctl model mongo -c no -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 -c no -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
|
|
||||||
mock-gen: # 建立 mock 資料
|
|
||||||
mockgen -source=./internal/model/mongo/post_model_gen.go -destination=./internal/mock/model/post_model_gen.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.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"
|
|
||||||
|
|
||||||
.PHONY: migrate-database
|
|
||||||
migrate-database:
|
|
||||||
migrate -source file://generate/database/migrations/mongodb -database 'mongodb://127.0.0.1:27017/digimon_tweeting' up
|
|
||||||
|
|
|
@ -5,6 +5,20 @@ Etcd:
|
||||||
- 127.0.0.1:2379
|
- 127.0.0.1:2379
|
||||||
Key: tweeting.rpc
|
Key: tweeting.rpc
|
||||||
|
|
||||||
|
Cache:
|
||||||
|
- Host: 127.0.0.1:7001
|
||||||
|
type: cluster
|
||||||
|
- Host: 127.0.0.1:7002
|
||||||
|
type: cluster
|
||||||
|
- Host: 127.0.0.1:7003
|
||||||
|
type: cluster
|
||||||
|
- Host: 127.0.0.1:7004
|
||||||
|
type: cluster
|
||||||
|
- Host: 127.0.0.1:7005
|
||||||
|
type: cluster
|
||||||
|
- Host: 127.0.0.1:7006
|
||||||
|
type: cluster
|
||||||
|
|
||||||
Mongo:
|
Mongo:
|
||||||
Schema: mongodb
|
Schema: mongodb
|
||||||
Host: 127.0.0.1
|
Host: 127.0.0.1
|
||||||
|
@ -12,20 +26,3 @@ Mongo:
|
||||||
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,9 +0,0 @@
|
||||||
use digimon_tweeting;
|
|
||||||
db.post.createIndex({ "uid": 1});
|
|
||||||
db.post.createIndex({ "status": 1});
|
|
||||||
db.post.createIndex({ "is_ad": 1});
|
|
||||||
db.post.createIndex({ "createAt": 1 });
|
|
||||||
db.post.createIndex({ "uid": 1,"status": 1, "createAt": 1 });
|
|
||||||
db.post.createIndex({ "uid": 1, "createAt": 1 });
|
|
||||||
|
|
||||||
// TODO 看是否有要刪除過多的索引,要在測試一下
|
|
|
@ -0,0 +1,5 @@
|
||||||
|
use digimon_tweeting;
|
||||||
|
db.post.createIndex({ "uid": 1, "create_time": 1 });
|
||||||
|
db.post.createIndex({ "is_ad": 1, "create_time": 1 });
|
||||||
|
db.post.createIndex({ "create_time": 1 });
|
||||||
|
|
|
@ -1,2 +0,0 @@
|
||||||
use digimon_tweeting;
|
|
||||||
db.comment.createIndex({ "post_id": 1,"createAt":1});
|
|
|
@ -0,0 +1,5 @@
|
||||||
|
db.post_likes.createIndex(
|
||||||
|
{ "target_id": 1, "uid": 1, "type": 1 },
|
||||||
|
{ unique: true }
|
||||||
|
);
|
||||||
|
db.post_likes.createIndex({ "create_time": 1 });
|
|
@ -1,5 +0,0 @@
|
||||||
// 企業版才能用,社群版只能用預設的
|
|
||||||
CREATE DATABASE relation;
|
|
||||||
|
|
||||||
// 創建 User 節點 UID 是唯一鍵
|
|
||||||
CREATE CONSTRAINT FOR (u:User) REQUIRE u.uid IS UNIQUE
|
|
|
@ -1,26 +1,27 @@
|
||||||
syntax = "proto3";
|
syntax = "proto3";
|
||||||
package tweeting;
|
package tweeting;
|
||||||
option go_package = "./tweeting";
|
option go_package="./tweeting";
|
||||||
|
|
||||||
// ========== 基本回應 ===========
|
// 基本回應
|
||||||
message OKResp {}
|
message OKResp {}
|
||||||
|
|
||||||
// 空的請求
|
// 空的請求
|
||||||
message NoneReq {}
|
message NoneReq {}
|
||||||
|
|
||||||
// 分頁信息
|
// 分頁信息
|
||||||
message Pager
|
message Pager {
|
||||||
{
|
int64 total =1; // 總數量
|
||||||
int64 total = 1; // 總數量
|
int64 size=2; // 每頁數量
|
||||||
int64 size = 2; // 每頁數量
|
int64 index=3; // 當前頁碼
|
||||||
int64 index = 3; // 當前頁碼
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ========== 貼文區 ===========
|
message Media{
|
||||||
|
string type =1;
|
||||||
|
string url =2;
|
||||||
|
}
|
||||||
|
|
||||||
// ------ NewPost 新增貼文--------
|
// 新增貼文的請求
|
||||||
message NewPostReq
|
message NewPostReq {
|
||||||
{
|
|
||||||
string uid = 1; // 發佈貼文的用戶ID
|
string uid = 1; // 發佈貼文的用戶ID
|
||||||
string content = 2; // 貼文內容
|
string content = 2; // 貼文內容
|
||||||
repeated string tags = 3; // 貼文相關標籤
|
repeated string tags = 3; // 貼文相關標籤
|
||||||
|
@ -28,30 +29,18 @@ message NewPostReq
|
||||||
bool is_ad = 5; // 是否為廣告
|
bool is_ad = 5; // 是否為廣告
|
||||||
}
|
}
|
||||||
|
|
||||||
message Media
|
|
||||||
{
|
|
||||||
string type = 1;
|
|
||||||
string url = 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 貼文回應
|
// 貼文回應
|
||||||
message PostResp
|
message PostResp {
|
||||||
{
|
|
||||||
string post_id = 1; // 創建成功的貼文ID
|
string post_id = 1; // 創建成功的貼文ID
|
||||||
}
|
}
|
||||||
|
|
||||||
// ------ DeletePost 刪除貼文 ------
|
|
||||||
|
|
||||||
// 刪除貼文的請求
|
// 刪除貼文的請求
|
||||||
message DeletePostsReq
|
message DeletePostsReq {
|
||||||
{
|
|
||||||
repeated string post_id = 1; // 貼文ID
|
repeated string post_id = 1; // 貼文ID
|
||||||
}
|
}
|
||||||
|
|
||||||
// ------ UpdatePost 更新貼文 ------
|
|
||||||
// 更新貼文的請求
|
// 更新貼文的請求
|
||||||
message UpdatePostReq
|
message UpdatePostReq {
|
||||||
{
|
|
||||||
string post_id = 1; // 貼文ID
|
string post_id = 1; // 貼文ID
|
||||||
repeated string tags = 2; // 新的標籤列表
|
repeated string tags = 2; // 新的標籤列表
|
||||||
repeated Media media = 3; // 這筆文章的所有 Media URL
|
repeated Media media = 3; // 這筆文章的所有 Media URL
|
||||||
|
@ -60,20 +49,17 @@ message UpdatePostReq
|
||||||
optional int64 dislike_count = 6; // 不喜歡數量
|
optional int64 dislike_count = 6; // 不喜歡數量
|
||||||
}
|
}
|
||||||
|
|
||||||
// ------ListPosts 查詢貼文 ------
|
|
||||||
// 查詢貼文的請求
|
// 查詢貼文的請求
|
||||||
message QueryPostsReq
|
message QueryPostsReq {
|
||||||
{
|
|
||||||
repeated string uid = 1; // 可選:根據用戶ID篩選貼文
|
repeated string uid = 1; // 可選:根據用戶ID篩選貼文
|
||||||
repeated string post_id = 2; // 可選:根據貼文ID篩選貼文
|
repeated string id = 2; // 可選:根據貼文ID篩選貼文
|
||||||
optional int32 only_ads = 3; // 可選:是否只顯示廣告 0 不篩選 1 只顯示廣告 2 不顯示廣告
|
optional int32 only_ads = 4; // 可選:是否只顯示廣告 0 不篩選 1 只顯示廣告 2 不顯示廣告
|
||||||
int32 page_index = 4; // 分頁的頁碼
|
int32 page_index = 5; // 分頁的頁碼
|
||||||
int32 page_size = 5; // 每頁顯示的數量
|
int32 page_size = 6; // 每頁顯示的數量
|
||||||
}
|
}
|
||||||
|
|
||||||
// 貼文詳情
|
// 貼文詳情
|
||||||
message PostDetailItem
|
message PostDetailItem {
|
||||||
{
|
|
||||||
string post_id = 1; // 貼文ID
|
string post_id = 1; // 貼文ID
|
||||||
string uid = 2; // 發佈用戶ID
|
string uid = 2; // 發佈用戶ID
|
||||||
string content = 3; // 貼文內容
|
string content = 3; // 貼文內容
|
||||||
|
@ -87,61 +73,84 @@ message PostDetailItem
|
||||||
}
|
}
|
||||||
|
|
||||||
// 貼文列表回應
|
// 貼文列表回應
|
||||||
message ListPostsResp
|
message ListPostsResp {
|
||||||
{
|
|
||||||
repeated PostDetailItem posts = 1; // 貼文列表
|
repeated PostDetailItem posts = 1; // 貼文列表
|
||||||
|
Pager page =2;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 讚/不讚請求
|
||||||
|
message LikeReq {
|
||||||
|
string target_id = 1; // 目標ID(可以是貼文ID或評論ID)
|
||||||
|
string uid = 2; // 點讚的用戶ID
|
||||||
|
int64 like_type = 3; // 讚或爛的類型
|
||||||
|
}
|
||||||
|
|
||||||
|
message GetLikeStatusReq{
|
||||||
|
string uid = 1; // 點讚的用戶ID
|
||||||
|
repeated string target_id = 2; // 目標ID(可以是貼文ID或評論ID)
|
||||||
|
int64 like_type = 3; // 讚或爛的類型
|
||||||
|
}
|
||||||
|
|
||||||
|
message GetLikeStatusItem{
|
||||||
|
string target_id = 1; // 目標ID(可以是貼文ID或評論ID)
|
||||||
|
bool status = 2; // 是否有按過
|
||||||
|
}
|
||||||
|
|
||||||
|
message GetLikeStatusResp{
|
||||||
|
repeated GetLikeStatusItem data = 1; // 目標ID(可以是貼文ID或評論ID)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// 讚/不讚項目
|
||||||
|
message LikeItem {
|
||||||
|
string target_id = 1; // 目標ID(可以是貼文ID或評論ID)
|
||||||
|
string uid = 2; // 點讚的用戶ID
|
||||||
|
int64 like_type = 3; // 讚或爛的類型
|
||||||
|
}
|
||||||
|
|
||||||
|
// 讚/不讚列表請求
|
||||||
|
message LikeListReq {
|
||||||
|
string target_id = 1; // 目標ID(可以是貼文ID或評論ID)
|
||||||
|
int64 like_type = 2; // 讚或爛的類型
|
||||||
|
int64 page_index = 3; // 當前頁碼
|
||||||
|
int64 page_size = 4; // 每頁顯示數量
|
||||||
|
}
|
||||||
|
|
||||||
|
// 讚/不讚列表回應
|
||||||
|
message LikeListResp {
|
||||||
|
repeated LikeItem list = 1; // 讚/不讚列表
|
||||||
Pager page = 2;
|
Pager page = 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
message ModifyLikeDislikeCountReq
|
// 讚/不讚數量請求
|
||||||
{
|
message LikeCountReq {
|
||||||
string post_id = 1; // 貼文的 ID
|
string target_id = 1; // 目標ID(可以是貼文ID或評論ID)
|
||||||
int64 reaction_type = 2; // 用戶的反應類型,可能是讚或不讚
|
int64 like_type = 2; // 讚或爛的類型
|
||||||
bool is_increment = 3; // 表示是否增加(true 表示增加,false 表示減少)
|
|
||||||
int64 count = 4; // 異動數量
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ========== 定義貼文服務(最基本單位,不要把邏輯放進來,也考慮是否要做快取) ==========
|
// 讚/不讚數量回應
|
||||||
service PostService
|
message LikeCountResp {
|
||||||
{
|
int64 count = 1; // 總共按讚數量
|
||||||
// CreatePost 新增貼文
|
|
||||||
rpc CreatePost(NewPostReq) returns (PostResp);
|
|
||||||
// DeletePost 刪除貼文
|
|
||||||
rpc DeletePost(DeletePostsReq) returns (OKResp);
|
|
||||||
// UpdatePost 更新貼文
|
|
||||||
rpc UpdatePost(UpdatePostReq) returns (OKResp);
|
|
||||||
// ListPosts 查詢貼文
|
|
||||||
rpc ListPosts(QueryPostsReq) returns (ListPostsResp);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// =================================================================================================
|
// 評論貼文的請求
|
||||||
|
message CommentPostReq {
|
||||||
// ------------ 評論貼文的請求 ------------
|
|
||||||
message CommentPostReq
|
|
||||||
{
|
|
||||||
string post_id = 1; // 貼文ID
|
string post_id = 1; // 貼文ID
|
||||||
string uid = 2; // 評論者ID
|
int64 user_id = 2; // 評論者ID
|
||||||
string content = 3; // 評論內容
|
string content = 3; // 評論內容
|
||||||
}
|
}
|
||||||
|
|
||||||
message CommentPostResp
|
// 查詢評論的請求
|
||||||
{
|
message GetCommentsReq {
|
||||||
string comment_id = 1; // 回應ID
|
|
||||||
}
|
|
||||||
|
|
||||||
// ------------ 查詢評論的請求 ------------
|
|
||||||
message GetCommentsReq
|
|
||||||
{
|
|
||||||
string post_id = 1; // 貼文ID
|
string post_id = 1; // 貼文ID
|
||||||
int32 page_index = 2; // 分頁頁碼
|
int32 page_index = 2; // 分頁頁碼
|
||||||
int32 page_size = 3; // 每頁顯示數量
|
int32 page_size = 3; // 每頁顯示數量
|
||||||
}
|
}
|
||||||
|
|
||||||
// 評論詳情
|
// 評論詳情
|
||||||
message CommentDetail
|
message CommentDetail {
|
||||||
{
|
|
||||||
string comment_id = 1; // 評論ID
|
string comment_id = 1; // 評論ID
|
||||||
string uid = 2; // 評論者ID
|
int64 user_id = 2; // 評論者ID
|
||||||
string content = 3; // 評論內容
|
string content = 3; // 評論內容
|
||||||
int64 created_at = 4; // 創建時間
|
int64 created_at = 4; // 創建時間
|
||||||
int64 like_count = 5; // 讚數
|
int64 like_count = 5; // 讚數
|
||||||
|
@ -149,150 +158,74 @@ message CommentDetail
|
||||||
}
|
}
|
||||||
|
|
||||||
// 評論列表回應
|
// 評論列表回應
|
||||||
message GetCommentsResp
|
message GetCommentsResp {
|
||||||
{
|
|
||||||
repeated CommentDetail comments = 1; // 評論列表
|
repeated CommentDetail comments = 1; // 評論列表
|
||||||
Pager page = 2;
|
Pager page = 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ------------ 刪除評論請求 ------------
|
// 刪除評論請求
|
||||||
message DeleteCommentReq
|
message DeleteCommentReq {
|
||||||
{
|
string comment_id = 1; // 評論ID
|
||||||
repeated string comment_id = 1; // 評論ID
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 更新評論請求
|
// 更新評論請求
|
||||||
message UpdateCommentReq
|
message UpdateCommentReq {
|
||||||
{
|
|
||||||
string comment_id = 1; // 評論ID
|
string comment_id = 1; // 評論ID
|
||||||
string content = 2; // 更新後的評論內容
|
string content = 2; // 更新後的評論內容
|
||||||
optional int64 like_count = 3; // 讚數
|
}
|
||||||
optional int64 dislike_count = 4; // 不喜歡數量
|
|
||||||
|
message PostReactionActionResp {
|
||||||
|
string PostID =1; // 貼文的 ID
|
||||||
|
int64 reaction_type = 2; // 用戶的反應類型,可能是讚或不讚
|
||||||
|
bool is_increment = 3; // 表示是否增加(true 表示增加,false 表示減少)
|
||||||
|
}
|
||||||
|
|
||||||
|
message IncDecLikeDislikeCountReq {
|
||||||
|
string PostID =1; // 貼文的 ID
|
||||||
|
int64 reaction_type = 2; // 用戶的反應類型,可能是讚或不讚
|
||||||
|
bool is_increment = 3; // 表示是否增加(true 表示增加,false 表示減少)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 定義貼文服務
|
||||||
|
service PostService {
|
||||||
|
// NewPost 新增貼文
|
||||||
|
rpc NewPost(NewPostReq) returns(PostResp);
|
||||||
|
// DeletePost 刪除貼文
|
||||||
|
rpc DeletePost(DeletePostsReq) returns (OKResp);
|
||||||
|
// UpdatePost 更新貼文
|
||||||
|
rpc UpdatePost(UpdatePostReq) returns (OKResp);
|
||||||
|
// ListPosts 查詢貼文
|
||||||
|
rpc ListPosts(QueryPostsReq) returns (ListPostsResp);
|
||||||
|
// IncDecLikeDislikeCount 增減數量
|
||||||
|
rpc IncDecLikeDislikeCount(IncDecLikeDislikeCountReq) returns (OKResp);
|
||||||
|
|
||||||
|
// Like 點讚/取消讚 貼文
|
||||||
|
rpc Like(LikeReq) returns (PostReactionActionResp);
|
||||||
|
// GetLikeStatus 取得讚/不讚狀態
|
||||||
|
rpc GetLikeStatus(GetLikeStatusReq) returns (GetLikeStatusResp);
|
||||||
|
// LikeList 取得讚/不讚列表
|
||||||
|
rpc LikeList(LikeListReq) returns (LikeListResp);
|
||||||
|
// CountLike 取得讚/不讚數量
|
||||||
|
rpc CountLike(LikeCountReq) returns (LikeCountResp);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 定義評論服務
|
// 定義評論服務
|
||||||
service CommentService
|
service CommentService {
|
||||||
{
|
|
||||||
// NewComment 發表評論
|
// NewComment 發表評論
|
||||||
rpc NewComment(CommentPostReq) returns (CommentPostResp);
|
rpc NewComment(CommentPostReq) returns (OKResp);
|
||||||
// GetComments 查詢評論
|
// GetComments 查詢評論
|
||||||
rpc GetComments(GetCommentsReq) returns (GetCommentsResp);
|
rpc GetComments(GetCommentsReq) returns (GetCommentsResp);
|
||||||
// DeleteComment 刪除評論
|
// DeleteComment 刪除評論
|
||||||
rpc DeleteComment(DeleteCommentReq) returns (OKResp);
|
rpc DeleteComment(DeleteCommentReq) returns (OKResp);
|
||||||
// UpdateComment 更新評論
|
// UpdateComment 更新評論
|
||||||
rpc UpdateComment(UpdateCommentReq) returns (OKResp);
|
rpc UpdateComment(UpdateCommentReq) returns (OKResp);
|
||||||
}
|
|
||||||
|
// LikeComment 點讚/取消讚 評論
|
||||||
// ========== TimeLineService (個人動態時報) ==========
|
rpc LikeComment(LikeReq) returns (OKResp);
|
||||||
|
// GetLikeStatus 取得讚/不讚評論狀態
|
||||||
message GetTimelineReq
|
rpc GetLikeStatus(LikeReq) returns (OKResp);
|
||||||
{
|
// LikeList 取得讚/不讚評論列表
|
||||||
string uid = 1; // 用户ID
|
rpc LikeList(LikeListReq) returns (LikeListResp);
|
||||||
int64 pageIndex = 2; // 頁碼
|
// CountLike 取得讚/不讚評論數量
|
||||||
int64 pageSize = 3; // 每一頁大小
|
rpc CountLike(LikeCountReq) returns (LikeCountResp);
|
||||||
}
|
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
}
|
42
go.mod
42
go.mod
|
@ -5,44 +5,25 @@ 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/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.1
|
||||||
go.uber.org/mock v0.4.0
|
|
||||||
google.golang.org/grpc v1.66.0
|
google.golang.org/grpc v1.66.0
|
||||||
google.golang.org/protobuf v1.34.2
|
google.golang.org/protobuf v1.34.2
|
||||||
)
|
)
|
||||||
|
|
||||||
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 +43,28 @@ 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/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,12 @@
|
||||||
package config
|
package config
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"time"
|
"github.com/zeromicro/go-zero/core/stores/cache"
|
||||||
|
|
||||||
"github.com/zeromicro/go-zero/core/stores/redis"
|
|
||||||
"github.com/zeromicro/go-zero/zrpc"
|
"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
|
||||||
|
@ -19,22 +16,6 @@ type Config struct {
|
||||||
Database string
|
Database string
|
||||||
}
|
}
|
||||||
|
|
||||||
TimelineSetting struct {
|
// 快取
|
||||||
Expire int64 // Second
|
Cache cache.CacheConf
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,6 +12,13 @@ const (
|
||||||
AdTypeOnlyNotAd
|
AdTypeOnlyNotAd
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type LikeType int8
|
||||||
|
|
||||||
|
func (l LikeType) ToInt8() int8 {
|
||||||
|
return int8(l)
|
||||||
|
}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
LastOfTimelineFlag = "NoMoreData"
|
LikeTypeLike LikeType = iota + 1 // 按揍
|
||||||
|
LikeTypeDisLike
|
||||||
)
|
)
|
||||||
|
|
|
@ -1,9 +1,10 @@
|
||||||
package domain
|
package domain
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
ers "code.30cm.net/digimon/library-go/errs"
|
"code.30cm.net/digimon/library-go/errs"
|
||||||
"code.30cm.net/digimon/library-go/errs/code"
|
"code.30cm.net/digimon/library-go/errs/code"
|
||||||
"github.com/zeromicro/go-zero/core/logx"
|
"github.com/zeromicro/go-zero/core/logx"
|
||||||
)
|
)
|
||||||
|
@ -14,49 +15,25 @@ func (e ErrorCode) ToUint32() uint32 {
|
||||||
return uint32(e)
|
return uint32(e)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Error Code 統一這邊改
|
|
||||||
const (
|
const (
|
||||||
_ = iota
|
_ = iota
|
||||||
PostMongoErrorCode ErrorCode = iota
|
PostMongoErrorCode ErrorCode = iota
|
||||||
CreatePostError
|
|
||||||
DelPostError
|
|
||||||
UpdatePostError
|
|
||||||
ListPostError
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
// PostMongoError ...
|
||||||
CommentFoundErrorCode ErrorCode = iota + 10
|
func PostMongoError(s ...string) *errs.LibError {
|
||||||
CommentInsertErrorCode
|
return errs.NewError(code.CloudEPTweeting, code.DBError,
|
||||||
CommentDeleteErrorCode
|
PostMongoErrorCode.ToUint32(),
|
||||||
CommentUpdateErrorCode
|
fmt.Sprintf("%s", strings.Join(s, " ")))
|
||||||
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 {
|
|
||||||
return ers.NewError(code.CloudEPTweeting, code.DBError, ec.ToUint32(), strings.Join(s, " "))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func CommentErrorL(ec ErrorCode,
|
// PostMongoErrorL logs error message and returns Err
|
||||||
l logx.Logger, filed []logx.LogField, s ...string) *ers.LibError {
|
func PostMongoErrorL(l logx.Logger, filed []logx.LogField, s ...string) *errs.LibError {
|
||||||
e := CommentError(ec, s...)
|
e := PostMongoError(s...)
|
||||||
|
if filed != nil || len(filed) >= 0 {
|
||||||
l.WithCallerSkip(1).WithFields(filed...).Error(e.Error())
|
l.WithCallerSkip(1).WithFields(filed...).Error(e.Error())
|
||||||
|
}
|
||||||
|
l.WithCallerSkip(1).Error(e.Error())
|
||||||
|
|
||||||
return e
|
return e
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -0,0 +1,31 @@
|
||||||
|
package commentservicelogic
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"app-cloudep-tweeting-service/gen_result/pb/tweeting"
|
||||||
|
"app-cloudep-tweeting-service/internal/svc"
|
||||||
|
|
||||||
|
"github.com/zeromicro/go-zero/core/logx"
|
||||||
|
)
|
||||||
|
|
||||||
|
type CountLikeLogic struct {
|
||||||
|
ctx context.Context
|
||||||
|
svcCtx *svc.ServiceContext
|
||||||
|
logx.Logger
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewCountLikeLogic(ctx context.Context, svcCtx *svc.ServiceContext) *CountLikeLogic {
|
||||||
|
return &CountLikeLogic{
|
||||||
|
ctx: ctx,
|
||||||
|
svcCtx: svcCtx,
|
||||||
|
Logger: logx.WithContext(ctx),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// CountLike 取得讚/不讚評論數量
|
||||||
|
func (l *CountLikeLogic) CountLike(in *tweeting.LikeCountReq) (*tweeting.LikeCountResp, error) {
|
||||||
|
// todo: add your logic here and delete this line
|
||||||
|
|
||||||
|
return &tweeting.LikeCountResp{}, nil
|
||||||
|
}
|
|
@ -1,7 +1,6 @@
|
||||||
package commentservicelogic
|
package commentservicelogic
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"app-cloudep-tweeting-service/internal/domain"
|
|
||||||
"context"
|
"context"
|
||||||
|
|
||||||
"app-cloudep-tweeting-service/gen_result/pb/tweeting"
|
"app-cloudep-tweeting-service/gen_result/pb/tweeting"
|
||||||
|
@ -26,20 +25,7 @@ func NewDeleteCommentLogic(ctx context.Context, svcCtx *svc.ServiceContext) *Del
|
||||||
|
|
||||||
// DeleteComment 刪除評論
|
// DeleteComment 刪除評論
|
||||||
func (l *DeleteCommentLogic) DeleteComment(in *tweeting.DeleteCommentReq) (*tweeting.OKResp, error) {
|
func (l *DeleteCommentLogic) DeleteComment(in *tweeting.DeleteCommentReq) (*tweeting.OKResp, error) {
|
||||||
_, err := l.svcCtx.CommentModel.DeleteMany(l.ctx, in.GetCommentId()...)
|
// todo: add your logic here and delete this line
|
||||||
if err != nil {
|
|
||||||
e := domain.CommentErrorL(
|
|
||||||
domain.CommentDeleteErrorCode,
|
|
||||||
logx.WithContext(l.ctx),
|
|
||||||
[]logx.LogField{
|
|
||||||
{Key: "req", Value: in},
|
|
||||||
{Key: "func", Value: "CommentModel.DeleteMany"},
|
|
||||||
{Key: "err", Value: err},
|
|
||||||
},
|
|
||||||
"failed to del comment").Wrap(err)
|
|
||||||
|
|
||||||
return nil, e
|
|
||||||
}
|
|
||||||
|
|
||||||
return &tweeting.OKResp{}, nil
|
return &tweeting.OKResp{}, nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,85 +0,0 @@
|
||||||
package commentservicelogic
|
|
||||||
|
|
||||||
import (
|
|
||||||
"app-cloudep-tweeting-service/gen_result/pb/tweeting"
|
|
||||||
"app-cloudep-tweeting-service/internal/svc"
|
|
||||||
"context"
|
|
||||||
"errors"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
"go.uber.org/mock/gomock"
|
|
||||||
|
|
||||||
mockmodel "app-cloudep-tweeting-service/internal/mock/model"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestDeleteComment(t *testing.T) {
|
|
||||||
ctrl := gomock.NewController(t)
|
|
||||||
defer ctrl.Finish()
|
|
||||||
|
|
||||||
// 初始化 mock 依賴
|
|
||||||
mockCommentModel := mockmodel.NewMockCommentModel(ctrl)
|
|
||||||
|
|
||||||
// 初始化服務上下文
|
|
||||||
svcCtx := &svc.ServiceContext{
|
|
||||||
CommentModel: mockCommentModel,
|
|
||||||
}
|
|
||||||
|
|
||||||
// 測試數據
|
|
||||||
commentReq := &tweeting.DeleteCommentReq{
|
|
||||||
CommentId: []string{"12345", "67890"},
|
|
||||||
}
|
|
||||||
|
|
||||||
// 測試數據集
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
input *tweeting.DeleteCommentReq
|
|
||||||
prepare func()
|
|
||||||
expectErr bool
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "成功刪除評論",
|
|
||||||
input: commentReq,
|
|
||||||
prepare: func() {
|
|
||||||
// 模擬 DeleteMany 成功
|
|
||||||
mockCommentModel.EXPECT().DeleteMany(gomock.Any(), "12345", "67890").Return(int64(2), nil).Times(1)
|
|
||||||
},
|
|
||||||
expectErr: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "刪除評論失敗",
|
|
||||||
input: commentReq,
|
|
||||||
prepare: func() {
|
|
||||||
// 模擬 DeleteMany 失敗
|
|
||||||
mockCommentModel.EXPECT().DeleteMany(gomock.Any(), "12345", "67890").Return(int64(0), errors.New("delete failed")).Times(1)
|
|
||||||
},
|
|
||||||
expectErr: true,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
// 執行測試
|
|
||||||
for _, tt := range tests {
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
|
||||||
// 設置測試環境
|
|
||||||
tt.prepare()
|
|
||||||
|
|
||||||
// 初始化 DeleteCommentLogic
|
|
||||||
logic := DeleteCommentLogic{
|
|
||||||
svcCtx: svcCtx,
|
|
||||||
ctx: context.TODO(),
|
|
||||||
}
|
|
||||||
|
|
||||||
// 執行 DeleteComment
|
|
||||||
resp, err := logic.DeleteComment(tt.input)
|
|
||||||
|
|
||||||
// 驗證結果
|
|
||||||
if tt.expectErr {
|
|
||||||
assert.Error(t, err)
|
|
||||||
assert.Nil(t, resp)
|
|
||||||
} else {
|
|
||||||
assert.NoError(t, err)
|
|
||||||
assert.NotNil(t, resp)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,12 +1,8 @@
|
||||||
package commentservicelogic
|
package commentservicelogic
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"app-cloudep-tweeting-service/internal/domain"
|
|
||||||
model "app-cloudep-tweeting-service/internal/model/mongo"
|
|
||||||
"context"
|
"context"
|
||||||
|
|
||||||
ers "code.30cm.net/digimon/library-go/errs"
|
|
||||||
|
|
||||||
"app-cloudep-tweeting-service/gen_result/pb/tweeting"
|
"app-cloudep-tweeting-service/gen_result/pb/tweeting"
|
||||||
"app-cloudep-tweeting-service/internal/svc"
|
"app-cloudep-tweeting-service/internal/svc"
|
||||||
|
|
||||||
|
@ -27,78 +23,9 @@ func NewGetCommentsLogic(ctx context.Context, svcCtx *svc.ServiceContext) *GetCo
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 只列出要驗證的資料
|
|
||||||
type listReq struct {
|
|
||||||
PostID string `json:"post_id" validate:"required"`
|
|
||||||
PageSize int64 `json:"page_size" validate:"required"`
|
|
||||||
PageIndex int64 `json:"page_index" validate:"required"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// 將單個 Post 轉換為 PostDetailItem
|
|
||||||
func convertToCommentDetailItem(item *model.Comment) *tweeting.CommentDetail {
|
|
||||||
return &tweeting.CommentDetail{
|
|
||||||
CommentId: item.ID.Hex(),
|
|
||||||
Uid: item.UID,
|
|
||||||
Content: item.Content,
|
|
||||||
CreatedAt: item.CreateAt,
|
|
||||||
LikeCount: item.LikeCount,
|
|
||||||
DislikeCount: item.DisLikeCount,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetComments 查詢評論
|
// GetComments 查詢評論
|
||||||
// 目前應該是沒有需求是要看 uid 在哪裡留過言,如果未來業務邏輯有再新增
|
|
||||||
func (l *GetCommentsLogic) GetComments(in *tweeting.GetCommentsReq) (*tweeting.GetCommentsResp, error) {
|
func (l *GetCommentsLogic) GetComments(in *tweeting.GetCommentsReq) (*tweeting.GetCommentsResp, error) {
|
||||||
// 將 PageSize 和 PageIndex 提前轉換為 int64
|
// todo: add your logic here and delete this line
|
||||||
pageSize := int64(in.GetPageSize())
|
|
||||||
pageIndex := int64(in.GetPageIndex())
|
|
||||||
|
|
||||||
// 驗證資料
|
return &tweeting.GetCommentsResp{}, nil
|
||||||
if err := l.svcCtx.Validate.ValidateAll(&listReq{
|
|
||||||
PageSize: pageSize,
|
|
||||||
PageIndex: pageIndex,
|
|
||||||
PostID: in.GetPostId(),
|
|
||||||
}); err != nil {
|
|
||||||
// 錯誤代碼 05-011-00
|
|
||||||
return nil, ers.InvalidFormat(err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
// 構建查詢條件
|
|
||||||
query := &model.QueryCommentModelReq{
|
|
||||||
PostID: in.GetPostId(),
|
|
||||||
PageSize: pageSize,
|
|
||||||
PageIndex: pageIndex,
|
|
||||||
}
|
|
||||||
|
|
||||||
// 執行查詢
|
|
||||||
find, count, err := l.svcCtx.CommentModel.Find(l.ctx, query)
|
|
||||||
if err != nil {
|
|
||||||
e := domain.CommentErrorL(
|
|
||||||
domain.CommentListErrorCode,
|
|
||||||
logx.WithContext(l.ctx),
|
|
||||||
[]logx.LogField{
|
|
||||||
{Key: "query", Value: query},
|
|
||||||
{Key: "func", Value: "CommentModel.Find"},
|
|
||||||
{Key: "err", Value: err},
|
|
||||||
},
|
|
||||||
"failed to find comment").Wrap(err)
|
|
||||||
|
|
||||||
return nil, e
|
|
||||||
}
|
|
||||||
|
|
||||||
// 將查詢結果轉換為 API 回應格式
|
|
||||||
result := make([]*tweeting.CommentDetail, 0, count)
|
|
||||||
for _, item := range find {
|
|
||||||
result = append(result, convertToCommentDetailItem(item))
|
|
||||||
}
|
|
||||||
|
|
||||||
// 返回結果
|
|
||||||
return &tweeting.GetCommentsResp{
|
|
||||||
Comments: result,
|
|
||||||
Page: &tweeting.Pager{
|
|
||||||
Total: count,
|
|
||||||
Index: pageIndex,
|
|
||||||
Size: pageSize,
|
|
||||||
},
|
|
||||||
}, nil
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,120 +0,0 @@
|
||||||
package commentservicelogic
|
|
||||||
|
|
||||||
import (
|
|
||||||
"app-cloudep-tweeting-service/gen_result/pb/tweeting"
|
|
||||||
model "app-cloudep-tweeting-service/internal/model/mongo"
|
|
||||||
"app-cloudep-tweeting-service/internal/svc"
|
|
||||||
"context"
|
|
||||||
"errors"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
"go.mongodb.org/mongo-driver/bson/primitive"
|
|
||||||
"go.uber.org/mock/gomock"
|
|
||||||
|
|
||||||
mocklib "app-cloudep-tweeting-service/internal/mock/lib"
|
|
||||||
mockmodel "app-cloudep-tweeting-service/internal/mock/model"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestGetComments(t *testing.T) {
|
|
||||||
ctrl := gomock.NewController(t)
|
|
||||||
defer ctrl.Finish()
|
|
||||||
|
|
||||||
// 初始化 mock 依賴
|
|
||||||
mockCommentModel := mockmodel.NewMockCommentModel(ctrl)
|
|
||||||
mockValidate := mocklib.NewMockValidate(ctrl)
|
|
||||||
|
|
||||||
// 初始化服務上下文
|
|
||||||
svcCtx := &svc.ServiceContext{
|
|
||||||
CommentModel: mockCommentModel,
|
|
||||||
Validate: mockValidate,
|
|
||||||
}
|
|
||||||
|
|
||||||
// 測試數據
|
|
||||||
getCommentsReq := &tweeting.GetCommentsReq{
|
|
||||||
PostId: "12345",
|
|
||||||
PageSize: 10,
|
|
||||||
PageIndex: 1,
|
|
||||||
}
|
|
||||||
|
|
||||||
mockComments := []*model.Comment{
|
|
||||||
{
|
|
||||||
ID: primitive.NewObjectID(),
|
|
||||||
PostID: "12345",
|
|
||||||
UID: "54321",
|
|
||||||
Content: "This is a comment",
|
|
||||||
LikeCount: 10,
|
|
||||||
DisLikeCount: 2,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
ID: primitive.NewObjectID(),
|
|
||||||
PostID: "12345",
|
|
||||||
UID: "67890",
|
|
||||||
Content: "This is another comment",
|
|
||||||
LikeCount: 5,
|
|
||||||
DisLikeCount: 1,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
// 測試數據集
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
input *tweeting.GetCommentsReq
|
|
||||||
prepare func()
|
|
||||||
expectErr bool
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "成功查詢評論",
|
|
||||||
input: getCommentsReq,
|
|
||||||
prepare: func() {
|
|
||||||
mockValidate.EXPECT().ValidateAll(gomock.Any()).Return(nil).Times(1)
|
|
||||||
mockCommentModel.EXPECT().Find(gomock.Any(), gomock.Any()).Return(mockComments, int64(len(mockComments)), nil).Times(1)
|
|
||||||
},
|
|
||||||
expectErr: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "查詢評論失敗",
|
|
||||||
input: getCommentsReq,
|
|
||||||
prepare: func() {
|
|
||||||
mockValidate.EXPECT().ValidateAll(gomock.Any()).Return(nil).Times(1)
|
|
||||||
mockCommentModel.EXPECT().Find(gomock.Any(), gomock.Any()).Return(nil, int64(0), errors.New("find failed")).Times(1)
|
|
||||||
},
|
|
||||||
expectErr: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "驗證失敗",
|
|
||||||
input: getCommentsReq,
|
|
||||||
prepare: func() {
|
|
||||||
mockValidate.EXPECT().ValidateAll(gomock.Any()).Return(errors.New("validation failed")).Times(1)
|
|
||||||
},
|
|
||||||
expectErr: true,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
// 執行測試
|
|
||||||
for _, tt := range tests {
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
|
||||||
// 設置測試環境
|
|
||||||
tt.prepare()
|
|
||||||
|
|
||||||
// 初始化 GetCommentsLogic
|
|
||||||
logic := GetCommentsLogic{
|
|
||||||
svcCtx: svcCtx,
|
|
||||||
ctx: context.TODO(),
|
|
||||||
}
|
|
||||||
|
|
||||||
// 執行 GetComments
|
|
||||||
resp, err := logic.GetComments(tt.input)
|
|
||||||
|
|
||||||
// 驗證結果
|
|
||||||
if tt.expectErr {
|
|
||||||
assert.Error(t, err)
|
|
||||||
assert.Nil(t, resp)
|
|
||||||
} else {
|
|
||||||
assert.NoError(t, err)
|
|
||||||
assert.NotNil(t, resp)
|
|
||||||
assert.Len(t, resp.Comments, len(mockComments))
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -0,0 +1,31 @@
|
||||||
|
package commentservicelogic
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"app-cloudep-tweeting-service/gen_result/pb/tweeting"
|
||||||
|
"app-cloudep-tweeting-service/internal/svc"
|
||||||
|
|
||||||
|
"github.com/zeromicro/go-zero/core/logx"
|
||||||
|
)
|
||||||
|
|
||||||
|
type GetLikeStatusLogic struct {
|
||||||
|
ctx context.Context
|
||||||
|
svcCtx *svc.ServiceContext
|
||||||
|
logx.Logger
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewGetLikeStatusLogic(ctx context.Context, svcCtx *svc.ServiceContext) *GetLikeStatusLogic {
|
||||||
|
return &GetLikeStatusLogic{
|
||||||
|
ctx: ctx,
|
||||||
|
svcCtx: svcCtx,
|
||||||
|
Logger: logx.WithContext(ctx),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetLikeStatus 取得讚/不讚評論狀態
|
||||||
|
func (l *GetLikeStatusLogic) GetLikeStatus(in *tweeting.LikeReq) (*tweeting.OKResp, error) {
|
||||||
|
// todo: add your logic here and delete this line
|
||||||
|
|
||||||
|
return &tweeting.OKResp{}, nil
|
||||||
|
}
|
|
@ -0,0 +1,31 @@
|
||||||
|
package commentservicelogic
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"app-cloudep-tweeting-service/gen_result/pb/tweeting"
|
||||||
|
"app-cloudep-tweeting-service/internal/svc"
|
||||||
|
|
||||||
|
"github.com/zeromicro/go-zero/core/logx"
|
||||||
|
)
|
||||||
|
|
||||||
|
type LikeCommentLogic struct {
|
||||||
|
ctx context.Context
|
||||||
|
svcCtx *svc.ServiceContext
|
||||||
|
logx.Logger
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewLikeCommentLogic(ctx context.Context, svcCtx *svc.ServiceContext) *LikeCommentLogic {
|
||||||
|
return &LikeCommentLogic{
|
||||||
|
ctx: ctx,
|
||||||
|
svcCtx: svcCtx,
|
||||||
|
Logger: logx.WithContext(ctx),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// LikeComment 點讚/取消讚 評論
|
||||||
|
func (l *LikeCommentLogic) LikeComment(in *tweeting.LikeReq) (*tweeting.OKResp, error) {
|
||||||
|
// todo: add your logic here and delete this line
|
||||||
|
|
||||||
|
return &tweeting.OKResp{}, nil
|
||||||
|
}
|
|
@ -0,0 +1,31 @@
|
||||||
|
package commentservicelogic
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"app-cloudep-tweeting-service/gen_result/pb/tweeting"
|
||||||
|
"app-cloudep-tweeting-service/internal/svc"
|
||||||
|
|
||||||
|
"github.com/zeromicro/go-zero/core/logx"
|
||||||
|
)
|
||||||
|
|
||||||
|
type LikeListLogic struct {
|
||||||
|
ctx context.Context
|
||||||
|
svcCtx *svc.ServiceContext
|
||||||
|
logx.Logger
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewLikeListLogic(ctx context.Context, svcCtx *svc.ServiceContext) *LikeListLogic {
|
||||||
|
return &LikeListLogic{
|
||||||
|
ctx: ctx,
|
||||||
|
svcCtx: svcCtx,
|
||||||
|
Logger: logx.WithContext(ctx),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// LikeList 取得讚/不讚評論列表
|
||||||
|
func (l *LikeListLogic) LikeList(in *tweeting.LikeListReq) (*tweeting.LikeListResp, error) {
|
||||||
|
// todo: add your logic here and delete this line
|
||||||
|
|
||||||
|
return &tweeting.LikeListResp{}, nil
|
||||||
|
}
|
|
@ -1,14 +1,10 @@
|
||||||
package commentservicelogic
|
package commentservicelogic
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"app-cloudep-tweeting-service/gen_result/pb/tweeting"
|
|
||||||
"app-cloudep-tweeting-service/internal/domain"
|
|
||||||
model "app-cloudep-tweeting-service/internal/model/mongo"
|
|
||||||
"app-cloudep-tweeting-service/internal/svc"
|
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
|
||||||
|
|
||||||
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"
|
"github.com/zeromicro/go-zero/core/logx"
|
||||||
)
|
)
|
||||||
|
@ -27,70 +23,9 @@ func NewNewCommentLogic(ctx context.Context, svcCtx *svc.ServiceContext) *NewCom
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 輸入的定義 -> 檢查用
|
|
||||||
type newCommentReq struct {
|
|
||||||
UID string `json:"uid" validate:"required"`
|
|
||||||
Content string `json:"content" validate:"required,lte=500"` // 貼文限制 500 字內
|
|
||||||
PostID string `json:"post_id" validate:"required"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewComment 發表評論
|
// NewComment 發表評論
|
||||||
func (l *NewCommentLogic) NewComment(in *tweeting.CommentPostReq) (*tweeting.CommentPostResp, error) {
|
func (l *NewCommentLogic) NewComment(in *tweeting.CommentPostReq) (*tweeting.OKResp, error) {
|
||||||
// 驗證資料
|
// todo: add your logic here and delete this line
|
||||||
if err := l.svcCtx.Validate.ValidateAll(&newCommentReq{
|
|
||||||
UID: in.GetUid(),
|
|
||||||
Content: in.GetContent(),
|
|
||||||
PostID: in.GetPostId(),
|
|
||||||
}); err != nil {
|
|
||||||
// 錯誤代碼 05-011-00
|
|
||||||
return nil, ers.InvalidFormat(err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
// 檢查是否有這個文章
|
return &tweeting.OKResp{}, nil
|
||||||
_, err := l.svcCtx.PostModel.FindOne(l.ctx, in.GetPostId())
|
|
||||||
if err != nil {
|
|
||||||
if errors.Is(model.ErrNotFound, err) {
|
|
||||||
// 錯誤代碼 05-031-00
|
|
||||||
return nil, ers.ResourceNotFound("failed to find post: ", in.GetPostId())
|
|
||||||
}
|
|
||||||
// 錯誤代碼 05-021-10
|
|
||||||
e := domain.CommentErrorL(
|
|
||||||
domain.CommentFoundErrorCode,
|
|
||||||
logx.WithContext(l.ctx),
|
|
||||||
[]logx.LogField{
|
|
||||||
{Key: "req", Value: in},
|
|
||||||
{Key: "func", Value: "PostModel.FindOne"},
|
|
||||||
{Key: "err", Value: err},
|
|
||||||
},
|
|
||||||
"failed to find post:", in.GetPostId()).Wrap(err)
|
|
||||||
|
|
||||||
return nil, e
|
|
||||||
}
|
|
||||||
|
|
||||||
data := &model.Comment{
|
|
||||||
PostID: in.GetPostId(),
|
|
||||||
UID: in.GetUid(),
|
|
||||||
Content: in.GetContent(),
|
|
||||||
LikeCount: 0,
|
|
||||||
DisLikeCount: 0,
|
|
||||||
}
|
|
||||||
err = l.svcCtx.CommentModel.Insert(l.ctx, data)
|
|
||||||
if err != nil {
|
|
||||||
// 錯誤代碼 05-021-11
|
|
||||||
e := domain.CommentErrorL(
|
|
||||||
domain.CommentInsertErrorCode,
|
|
||||||
logx.WithContext(l.ctx),
|
|
||||||
[]logx.LogField{
|
|
||||||
{Key: "req", Value: in},
|
|
||||||
{Key: "func", Value: "CommentModel.Insert"},
|
|
||||||
{Key: "err", Value: err},
|
|
||||||
},
|
|
||||||
"failed to insert comment:", in.GetPostId()).Wrap(err)
|
|
||||||
|
|
||||||
return nil, e
|
|
||||||
}
|
|
||||||
|
|
||||||
return &tweeting.CommentPostResp{
|
|
||||||
CommentId: data.ID.Hex(),
|
|
||||||
}, nil
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,124 +0,0 @@
|
||||||
package commentservicelogic
|
|
||||||
|
|
||||||
import (
|
|
||||||
"app-cloudep-tweeting-service/gen_result/pb/tweeting"
|
|
||||||
model "app-cloudep-tweeting-service/internal/model/mongo"
|
|
||||||
"app-cloudep-tweeting-service/internal/svc"
|
|
||||||
"context"
|
|
||||||
"errors"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
"go.mongodb.org/mongo-driver/bson/primitive"
|
|
||||||
"go.uber.org/mock/gomock"
|
|
||||||
|
|
||||||
mocklib "app-cloudep-tweeting-service/internal/mock/lib"
|
|
||||||
mockmodel "app-cloudep-tweeting-service/internal/mock/model"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestNewComment(t *testing.T) {
|
|
||||||
ctrl := gomock.NewController(t)
|
|
||||||
defer ctrl.Finish()
|
|
||||||
|
|
||||||
// 初始化 mock 依賴
|
|
||||||
mockPostModel := mockmodel.NewMockPostModel(ctrl)
|
|
||||||
mockCommentModel := mockmodel.NewMockCommentModel(ctrl)
|
|
||||||
mockValidate := mocklib.NewMockValidate(ctrl)
|
|
||||||
|
|
||||||
// 初始化服務上下文
|
|
||||||
svcCtx := &svc.ServiceContext{
|
|
||||||
PostModel: mockPostModel,
|
|
||||||
CommentModel: mockCommentModel,
|
|
||||||
Validate: mockValidate,
|
|
||||||
}
|
|
||||||
|
|
||||||
// 測試數據
|
|
||||||
postID := primitive.NewObjectID().Hex()
|
|
||||||
commentReq := &tweeting.CommentPostReq{
|
|
||||||
Uid: "12345",
|
|
||||||
Content: "This is a comment",
|
|
||||||
PostId: postID,
|
|
||||||
}
|
|
||||||
|
|
||||||
// 測試數據集
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
input *tweeting.CommentPostReq
|
|
||||||
prepare func()
|
|
||||||
expectErr bool
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "成功發表評論",
|
|
||||||
input: commentReq,
|
|
||||||
prepare: func() {
|
|
||||||
// 模擬 Validate 成功
|
|
||||||
mockValidate.EXPECT().ValidateAll(gomock.Any()).Return(nil).Times(1)
|
|
||||||
// 模擬 FindOne 成功找到文章
|
|
||||||
mockPostModel.EXPECT().FindOne(gomock.Any(), postID).Return(&model.Post{}, nil).Times(1)
|
|
||||||
// 模擬 Insert 成功
|
|
||||||
mockCommentModel.EXPECT().Insert(gomock.Any(), gomock.Any()).Return(nil).Times(1)
|
|
||||||
},
|
|
||||||
expectErr: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "驗證失敗",
|
|
||||||
input: commentReq,
|
|
||||||
prepare: func() {
|
|
||||||
// 模擬 Validate 失敗
|
|
||||||
mockValidate.EXPECT().ValidateAll(gomock.Any()).Return(errors.New("validation failed")).Times(1)
|
|
||||||
},
|
|
||||||
expectErr: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "文章不存在",
|
|
||||||
input: commentReq,
|
|
||||||
prepare: func() {
|
|
||||||
// 模擬 Validate 成功
|
|
||||||
mockValidate.EXPECT().ValidateAll(gomock.Any()).Return(nil).Times(1)
|
|
||||||
// 模擬 FindOne 找不到文章
|
|
||||||
mockPostModel.EXPECT().FindOne(gomock.Any(), postID).Return(nil, model.ErrNotFound).Times(1)
|
|
||||||
},
|
|
||||||
expectErr: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "插入評論失敗",
|
|
||||||
input: commentReq,
|
|
||||||
prepare: func() {
|
|
||||||
// 模擬 Validate 成功
|
|
||||||
mockValidate.EXPECT().ValidateAll(gomock.Any()).Return(nil).Times(1)
|
|
||||||
// 模擬 FindOne 成功找到文章
|
|
||||||
mockPostModel.EXPECT().FindOne(gomock.Any(), postID).Return(&model.Post{}, nil).Times(1)
|
|
||||||
// 模擬 Insert 失敗
|
|
||||||
mockCommentModel.EXPECT().Insert(gomock.Any(), gomock.Any()).Return(errors.New("insert failed")).Times(1)
|
|
||||||
},
|
|
||||||
expectErr: true,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
// 執行測試
|
|
||||||
for _, tt := range tests {
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
|
||||||
// 設置測試環境
|
|
||||||
tt.prepare()
|
|
||||||
|
|
||||||
// 初始化 NewCommentLogic
|
|
||||||
logic := NewCommentLogic{
|
|
||||||
svcCtx: svcCtx,
|
|
||||||
ctx: context.TODO(),
|
|
||||||
}
|
|
||||||
|
|
||||||
// 執行 NewComment
|
|
||||||
resp, err := logic.NewComment(tt.input)
|
|
||||||
|
|
||||||
// 驗證結果
|
|
||||||
if tt.expectErr {
|
|
||||||
assert.Error(t, err)
|
|
||||||
assert.Nil(t, resp)
|
|
||||||
} else {
|
|
||||||
assert.NoError(t, err)
|
|
||||||
assert.NotNil(t, resp)
|
|
||||||
assert.NotEmpty(t, resp.CommentId)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,14 +1,10 @@
|
||||||
package commentservicelogic
|
package commentservicelogic
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"app-cloudep-tweeting-service/gen_result/pb/tweeting"
|
|
||||||
"app-cloudep-tweeting-service/internal/domain"
|
|
||||||
model "app-cloudep-tweeting-service/internal/model/mongo"
|
|
||||||
"app-cloudep-tweeting-service/internal/svc"
|
|
||||||
"context"
|
"context"
|
||||||
|
|
||||||
ers "code.30cm.net/digimon/library-go/errs"
|
"app-cloudep-tweeting-service/gen_result/pb/tweeting"
|
||||||
"go.mongodb.org/mongo-driver/bson/primitive"
|
"app-cloudep-tweeting-service/internal/svc"
|
||||||
|
|
||||||
"github.com/zeromicro/go-zero/core/logx"
|
"github.com/zeromicro/go-zero/core/logx"
|
||||||
)
|
)
|
||||||
|
@ -27,57 +23,9 @@ func NewUpdateCommentLogic(ctx context.Context, svcCtx *svc.ServiceContext) *Upd
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type checkCommentID struct {
|
|
||||||
CommentID string `validate:"required"`
|
|
||||||
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) {
|
||||||
// 驗證資料
|
// todo: add your logic here and delete this line
|
||||||
if err := l.svcCtx.Validate.ValidateAll(&checkCommentID{
|
|
||||||
CommentID: in.GetCommentId(),
|
|
||||||
Content: in.GetContent(),
|
|
||||||
}); err != nil {
|
|
||||||
// 錯誤代碼 05-011-00
|
|
||||||
return nil, ers.InvalidFormat(err.Error())
|
|
||||||
}
|
|
||||||
// 沒有就沒有,有就走全覆蓋
|
|
||||||
update := model.Comment{}
|
|
||||||
oid, err := primitive.ObjectIDFromHex(in.GetCommentId())
|
|
||||||
if err != nil {
|
|
||||||
// 錯誤代碼 05-011-00
|
|
||||||
return nil, ers.InvalidFormat("failed to get correct comment id")
|
|
||||||
}
|
|
||||||
update.ID = oid
|
|
||||||
update.Content = in.GetContent()
|
|
||||||
// 因為 0 也有意義,所以如果是真的沒帶進來,用 -1 帶進去表示不作動
|
|
||||||
if in.LikeCount == nil {
|
|
||||||
update.LikeCount = -1
|
|
||||||
} else {
|
|
||||||
update.LikeCount = in.GetLikeCount()
|
|
||||||
}
|
|
||||||
if in.DislikeCount == nil {
|
|
||||||
update.DisLikeCount = -1
|
|
||||||
} else {
|
|
||||||
update.DisLikeCount = in.GetDislikeCount()
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = l.svcCtx.CommentModel.UpdateOptional(l.ctx, &update)
|
|
||||||
if err != nil {
|
|
||||||
// 錯誤代碼 05-021-13
|
|
||||||
e := domain.CommentErrorL(
|
|
||||||
domain.CommentUpdateErrorCode,
|
|
||||||
logx.WithContext(l.ctx),
|
|
||||||
[]logx.LogField{
|
|
||||||
{Key: "req", Value: in},
|
|
||||||
{Key: "func", Value: "CommentModel.UpdateOptional"},
|
|
||||||
{Key: "err", Value: err},
|
|
||||||
},
|
|
||||||
"failed to update comment:", in.CommentId).Wrap(err)
|
|
||||||
|
|
||||||
return nil, e
|
|
||||||
}
|
|
||||||
|
|
||||||
return &tweeting.OKResp{}, nil
|
return &tweeting.OKResp{}, nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,124 +0,0 @@
|
||||||
package commentservicelogic
|
|
||||||
|
|
||||||
import (
|
|
||||||
"app-cloudep-tweeting-service/gen_result/pb/tweeting"
|
|
||||||
mocklib "app-cloudep-tweeting-service/internal/mock/lib"
|
|
||||||
mockmodel "app-cloudep-tweeting-service/internal/mock/model"
|
|
||||||
"app-cloudep-tweeting-service/internal/svc"
|
|
||||||
"context"
|
|
||||||
"errors"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
"go.mongodb.org/mongo-driver/bson/primitive"
|
|
||||||
"go.mongodb.org/mongo-driver/mongo"
|
|
||||||
"go.uber.org/mock/gomock"
|
|
||||||
"google.golang.org/protobuf/proto"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestUpdateComment(t *testing.T) {
|
|
||||||
ctrl := gomock.NewController(t)
|
|
||||||
defer ctrl.Finish()
|
|
||||||
|
|
||||||
// 初始化 mock 依賴
|
|
||||||
mockCommentModel := mockmodel.NewMockCommentModel(ctrl)
|
|
||||||
mockValidate := mocklib.NewMockValidate(ctrl)
|
|
||||||
|
|
||||||
// 初始化服務上下文
|
|
||||||
svcCtx := &svc.ServiceContext{
|
|
||||||
CommentModel: mockCommentModel,
|
|
||||||
Validate: mockValidate,
|
|
||||||
}
|
|
||||||
|
|
||||||
// 測試數據
|
|
||||||
commentID := primitive.NewObjectID().Hex()
|
|
||||||
updateCommentReq := &tweeting.UpdateCommentReq{
|
|
||||||
CommentId: commentID,
|
|
||||||
Content: "Updated content",
|
|
||||||
LikeCount: proto.Int64(5),
|
|
||||||
DislikeCount: proto.Int64(2),
|
|
||||||
}
|
|
||||||
|
|
||||||
// 測試數據集
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
input *tweeting.UpdateCommentReq
|
|
||||||
prepare func()
|
|
||||||
expectErr bool
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "成功更新評論",
|
|
||||||
input: updateCommentReq,
|
|
||||||
prepare: func() {
|
|
||||||
// 模擬 Validate 成功
|
|
||||||
mockValidate.EXPECT().ValidateAll(gomock.Any()).Return(nil).Times(1)
|
|
||||||
// 模擬 UpdateOptional 成功
|
|
||||||
mockCommentModel.EXPECT().UpdateOptional(gomock.Any(), gomock.Any()).Return(&mongo.UpdateResult{
|
|
||||||
ModifiedCount: 1,
|
|
||||||
}, nil).Times(1)
|
|
||||||
},
|
|
||||||
expectErr: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "驗證失敗",
|
|
||||||
input: updateCommentReq,
|
|
||||||
prepare: func() {
|
|
||||||
// 模擬 Validate 失敗
|
|
||||||
mockValidate.EXPECT().ValidateAll(gomock.Any()).Return(errors.New("validation failed")).Times(1)
|
|
||||||
},
|
|
||||||
expectErr: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "更新評論失敗",
|
|
||||||
input: updateCommentReq,
|
|
||||||
prepare: func() {
|
|
||||||
// 模擬 Validate 成功
|
|
||||||
mockValidate.EXPECT().ValidateAll(gomock.Any()).Return(nil).Times(1)
|
|
||||||
// 模擬 UpdateOptional 失敗
|
|
||||||
mockCommentModel.EXPECT().UpdateOptional(gomock.Any(), gomock.Any()).Return(
|
|
||||||
&mongo.UpdateResult{
|
|
||||||
ModifiedCount: 0,
|
|
||||||
}, errors.New("update failed")).Times(1)
|
|
||||||
},
|
|
||||||
expectErr: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "無效的評論ID",
|
|
||||||
input: &tweeting.UpdateCommentReq{
|
|
||||||
CommentId: "invalid_id",
|
|
||||||
Content: "Updated content",
|
|
||||||
},
|
|
||||||
prepare: func() {
|
|
||||||
// 模擬 Validate 成功
|
|
||||||
mockValidate.EXPECT().ValidateAll(gomock.Any()).Return(nil).Times(1)
|
|
||||||
},
|
|
||||||
expectErr: true,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
// 執行測試
|
|
||||||
for _, tt := range tests {
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
|
||||||
// 設置測試環境
|
|
||||||
tt.prepare()
|
|
||||||
|
|
||||||
// 初始化 UpdateCommentLogic
|
|
||||||
logic := UpdateCommentLogic{
|
|
||||||
svcCtx: svcCtx,
|
|
||||||
ctx: context.TODO(),
|
|
||||||
}
|
|
||||||
|
|
||||||
// 執行 UpdateComment
|
|
||||||
resp, err := logic.UpdateComment(tt.input)
|
|
||||||
|
|
||||||
// 驗證結果
|
|
||||||
if tt.expectErr {
|
|
||||||
assert.Error(t, err)
|
|
||||||
assert.Nil(t, resp)
|
|
||||||
} else {
|
|
||||||
assert.NoError(t, err)
|
|
||||||
assert.NotNil(t, resp)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -0,0 +1,59 @@
|
||||||
|
package postservicelogic
|
||||||
|
|
||||||
|
import (
|
||||||
|
"app-cloudep-tweeting-service/gen_result/pb/tweeting"
|
||||||
|
"app-cloudep-tweeting-service/internal/domain"
|
||||||
|
"app-cloudep-tweeting-service/internal/svc"
|
||||||
|
"context"
|
||||||
|
|
||||||
|
ers "code.30cm.net/digimon/library-go/errs"
|
||||||
|
|
||||||
|
"github.com/zeromicro/go-zero/core/logx"
|
||||||
|
)
|
||||||
|
|
||||||
|
type CountLikeLogic struct {
|
||||||
|
ctx context.Context
|
||||||
|
svcCtx *svc.ServiceContext
|
||||||
|
logx.Logger
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewCountLikeLogic(ctx context.Context, svcCtx *svc.ServiceContext) *CountLikeLogic {
|
||||||
|
return &CountLikeLogic{
|
||||||
|
ctx: ctx,
|
||||||
|
svcCtx: svcCtx,
|
||||||
|
Logger: logx.WithContext(ctx),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type countLikeReq struct {
|
||||||
|
PostID string `json:"post_id" validate:"required"` // 貼文的 ID
|
||||||
|
ReactionType domain.LikeType `json:"reaction_type" validate:"required,oneof=1 2"` // 用戶的反應類型,可能是讚或不讚
|
||||||
|
}
|
||||||
|
|
||||||
|
// CountLike 取得讚/不讚數量
|
||||||
|
func (l *CountLikeLogic) CountLike(in *tweeting.LikeCountReq) (*tweeting.LikeCountResp, error) {
|
||||||
|
// 驗證資料
|
||||||
|
if err := l.svcCtx.Validate.ValidateAll(&countLikeReq{
|
||||||
|
PostID: in.GetTargetId(),
|
||||||
|
ReactionType: domain.LikeType(in.GetLikeType()),
|
||||||
|
}); err != nil {
|
||||||
|
return nil, ers.InvalidFormat(err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
count, err := l.svcCtx.PostLikeModel.Count(l.ctx, in.GetTargetId(), domain.LikeType(in.GetLikeType()))
|
||||||
|
if err != nil {
|
||||||
|
e := domain.PostMongoErrorL(
|
||||||
|
logx.WithContext(l.ctx),
|
||||||
|
[]logx.LogField{
|
||||||
|
{Key: "req", Value: in},
|
||||||
|
{Key: "func", Value: "PostLikeModel.Count"},
|
||||||
|
{Key: "err", Value: err},
|
||||||
|
},
|
||||||
|
"failed to count like or dislike").Wrap(err)
|
||||||
|
return nil, e
|
||||||
|
}
|
||||||
|
|
||||||
|
return &tweeting.LikeCountResp{
|
||||||
|
Count: count,
|
||||||
|
}, nil
|
||||||
|
}
|
|
@ -1,107 +0,0 @@
|
||||||
package postservicelogic
|
|
||||||
|
|
||||||
import (
|
|
||||||
"app-cloudep-tweeting-service/gen_result/pb/tweeting"
|
|
||||||
"app-cloudep-tweeting-service/internal/svc"
|
|
||||||
"context"
|
|
||||||
"errors"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
mocklib "app-cloudep-tweeting-service/internal/mock/lib"
|
|
||||||
mockmodel "app-cloudep-tweeting-service/internal/mock/model"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
"go.uber.org/mock/gomock"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestCreatePost(t *testing.T) {
|
|
||||||
ctrl := gomock.NewController(t)
|
|
||||||
defer ctrl.Finish()
|
|
||||||
|
|
||||||
// 初始化 mock 依賴
|
|
||||||
mockPostModel := mockmodel.NewMockPostModel(ctrl)
|
|
||||||
mockValidate := mocklib.NewMockValidate(ctrl)
|
|
||||||
|
|
||||||
// 初始化服務上下文
|
|
||||||
svcCtx := &svc.ServiceContext{
|
|
||||||
PostModel: mockPostModel,
|
|
||||||
Validate: mockValidate,
|
|
||||||
}
|
|
||||||
|
|
||||||
// 測試數據集
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
input *tweeting.NewPostReq
|
|
||||||
prepare func()
|
|
||||||
expectErr bool
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "成功創建貼文",
|
|
||||||
input: &tweeting.NewPostReq{
|
|
||||||
Uid: "12345",
|
|
||||||
Content: "Test content",
|
|
||||||
IsAd: false,
|
|
||||||
Tags: []string{"tag1", "tag2"},
|
|
||||||
Media: []*tweeting.Media{
|
|
||||||
{
|
|
||||||
Url: "http://example.com/image.png",
|
|
||||||
Type: "image",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
prepare: func() {
|
|
||||||
mockValidate.EXPECT().ValidateAll(gomock.Any()).Return(nil).Times(1)
|
|
||||||
mockPostModel.EXPECT().Insert(gomock.Any(), gomock.Any()).Return(nil).Times(1)
|
|
||||||
},
|
|
||||||
expectErr: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "驗證失敗",
|
|
||||||
input: &tweeting.NewPostReq{
|
|
||||||
Uid: "",
|
|
||||||
Content: "",
|
|
||||||
},
|
|
||||||
prepare: func() {
|
|
||||||
mockValidate.EXPECT().ValidateAll(gomock.Any()).Return(errors.New("validation failed")).Times(1)
|
|
||||||
},
|
|
||||||
expectErr: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "插入貼文失敗",
|
|
||||||
input: &tweeting.NewPostReq{
|
|
||||||
Uid: "12345",
|
|
||||||
Content: "Test content",
|
|
||||||
IsAd: false,
|
|
||||||
},
|
|
||||||
prepare: func() {
|
|
||||||
mockValidate.EXPECT().ValidateAll(gomock.Any()).Return(nil).Times(1)
|
|
||||||
mockPostModel.EXPECT().Insert(gomock.Any(), gomock.Any()).Return(errors.New("insert failed")).Times(1)
|
|
||||||
},
|
|
||||||
expectErr: true,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
// 執行測試
|
|
||||||
for _, tt := range tests {
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
|
||||||
// 設置測試環境
|
|
||||||
tt.prepare()
|
|
||||||
|
|
||||||
// 初始化 CreatePostLogic
|
|
||||||
logic := CreatePostLogic{
|
|
||||||
svcCtx: svcCtx,
|
|
||||||
ctx: context.TODO(),
|
|
||||||
}
|
|
||||||
|
|
||||||
// 執行 CreatePost
|
|
||||||
_, err := logic.CreatePost(tt.input)
|
|
||||||
|
|
||||||
// 驗證結果
|
|
||||||
if tt.expectErr {
|
|
||||||
assert.Error(t, err)
|
|
||||||
} else {
|
|
||||||
assert.NoError(t, err)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,11 +1,12 @@
|
||||||
package postservicelogic
|
package postservicelogic
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"app-cloudep-tweeting-service/gen_result/pb/tweeting"
|
|
||||||
"app-cloudep-tweeting-service/internal/domain"
|
"app-cloudep-tweeting-service/internal/domain"
|
||||||
"app-cloudep-tweeting-service/internal/svc"
|
|
||||||
"context"
|
"context"
|
||||||
|
|
||||||
|
"app-cloudep-tweeting-service/gen_result/pb/tweeting"
|
||||||
|
"app-cloudep-tweeting-service/internal/svc"
|
||||||
|
|
||||||
"github.com/zeromicro/go-zero/core/logx"
|
"github.com/zeromicro/go-zero/core/logx"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -27,17 +28,14 @@ func NewDeletePostLogic(ctx context.Context, svcCtx *svc.ServiceContext) *Delete
|
||||||
func (l *DeletePostLogic) DeletePost(in *tweeting.DeletePostsReq) (*tweeting.OKResp, error) {
|
func (l *DeletePostLogic) DeletePost(in *tweeting.DeletePostsReq) (*tweeting.OKResp, error) {
|
||||||
_, err := l.svcCtx.PostModel.DeleteMany(l.ctx, in.GetPostId()...)
|
_, err := l.svcCtx.PostModel.DeleteMany(l.ctx, in.GetPostId()...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// 錯誤代碼 05-021-03
|
e := domain.PostMongoErrorL(
|
||||||
e := domain.CommentErrorL(
|
|
||||||
domain.DelPostError,
|
|
||||||
logx.WithContext(l.ctx),
|
logx.WithContext(l.ctx),
|
||||||
[]logx.LogField{
|
[]logx.LogField{
|
||||||
{Key: "req", Value: in},
|
{Key: "req", Value: in},
|
||||||
{Key: "func", Value: "PostModel.DeleteMany"},
|
{Key: "func", Value: "PostModel.DeletePost"},
|
||||||
{Key: "err", Value: err},
|
{Key: "err", Value: err},
|
||||||
},
|
},
|
||||||
"failed to del post").Wrap(err)
|
"failed to add del post").Wrap(err)
|
||||||
|
|
||||||
return nil, e
|
return nil, e
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,81 +0,0 @@
|
||||||
package postservicelogic
|
|
||||||
|
|
||||||
import (
|
|
||||||
"app-cloudep-tweeting-service/gen_result/pb/tweeting"
|
|
||||||
mockmodel "app-cloudep-tweeting-service/internal/mock/model"
|
|
||||||
"app-cloudep-tweeting-service/internal/svc"
|
|
||||||
"context"
|
|
||||||
"errors"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
"go.uber.org/mock/gomock"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestDeletePost(t *testing.T) {
|
|
||||||
ctrl := gomock.NewController(t)
|
|
||||||
defer ctrl.Finish()
|
|
||||||
|
|
||||||
// 初始化 mock 依賴
|
|
||||||
mockPostModel := mockmodel.NewMockPostModel(ctrl)
|
|
||||||
|
|
||||||
// 初始化服務上下文
|
|
||||||
svcCtx := &svc.ServiceContext{
|
|
||||||
PostModel: mockPostModel,
|
|
||||||
}
|
|
||||||
|
|
||||||
// 測試數據集
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
input *tweeting.DeletePostsReq
|
|
||||||
prepare func()
|
|
||||||
expectErr bool
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "成功刪除貼文",
|
|
||||||
input: &tweeting.DeletePostsReq{
|
|
||||||
PostId: []string{"12345", "67890"},
|
|
||||||
},
|
|
||||||
prepare: func() {
|
|
||||||
// 模擬 DeleteMany 成功
|
|
||||||
mockPostModel.EXPECT().DeleteMany(gomock.Any(), "12345", "67890").Return(int64(2), nil).Times(1)
|
|
||||||
},
|
|
||||||
expectErr: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "刪除貼文失敗",
|
|
||||||
input: &tweeting.DeletePostsReq{
|
|
||||||
PostId: []string{"12345", "67890"},
|
|
||||||
},
|
|
||||||
prepare: func() {
|
|
||||||
// 模擬 DeleteMany 失敗
|
|
||||||
mockPostModel.EXPECT().DeleteMany(gomock.Any(), "12345", "67890").Return(int64(0), errors.New("delete failed")).Times(1)
|
|
||||||
},
|
|
||||||
expectErr: true,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
// 執行測試
|
|
||||||
for _, tt := range tests {
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
|
||||||
// 設置測試環境
|
|
||||||
tt.prepare()
|
|
||||||
|
|
||||||
// 初始化 DeletePostLogic
|
|
||||||
logic := DeletePostLogic{
|
|
||||||
svcCtx: svcCtx,
|
|
||||||
ctx: context.TODO(),
|
|
||||||
}
|
|
||||||
|
|
||||||
// 執行 DeletePost
|
|
||||||
_, err := logic.DeletePost(tt.input)
|
|
||||||
|
|
||||||
// 驗證結果
|
|
||||||
if tt.expectErr {
|
|
||||||
assert.Error(t, err)
|
|
||||||
} else {
|
|
||||||
assert.NoError(t, err)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -0,0 +1,76 @@
|
||||||
|
package postservicelogic
|
||||||
|
|
||||||
|
import (
|
||||||
|
"app-cloudep-tweeting-service/internal/domain"
|
||||||
|
model "app-cloudep-tweeting-service/internal/model/mongo"
|
||||||
|
"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 GetLikeStatusLogic struct {
|
||||||
|
ctx context.Context
|
||||||
|
svcCtx *svc.ServiceContext
|
||||||
|
logx.Logger
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewGetLikeStatusLogic(ctx context.Context, svcCtx *svc.ServiceContext) *GetLikeStatusLogic {
|
||||||
|
return &GetLikeStatusLogic{
|
||||||
|
ctx: ctx,
|
||||||
|
svcCtx: svcCtx,
|
||||||
|
Logger: logx.WithContext(ctx),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 這個人按讚的文章列表(輸入UID 以及文章id,返回這個人有沒有對這些文章按讚)
|
||||||
|
type getLikeStatusReq struct {
|
||||||
|
Targets []string `json:"targets" validate:"required"`
|
||||||
|
LikeType domain.LikeType `json:"like_type" validate:"required,oneof=1 2"`
|
||||||
|
UID string `json:"uid" validate:"required"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetLikeStatus 取得讚/不讚狀態
|
||||||
|
func (l *GetLikeStatusLogic) GetLikeStatus(in *tweeting.GetLikeStatusReq) (*tweeting.GetLikeStatusResp, error) {
|
||||||
|
// 驗證資料
|
||||||
|
if err := l.svcCtx.Validate.ValidateAll(&getLikeStatusReq{
|
||||||
|
Targets: in.GetTargetId(),
|
||||||
|
LikeType: domain.LikeType(in.GetLikeType()),
|
||||||
|
UID: in.GetUid(),
|
||||||
|
}); err != nil {
|
||||||
|
return nil, ers.InvalidFormat(err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
list, err := l.svcCtx.PostLikeModel.FindUIDPostLikeStatus(l.ctx, &model.QueryUIDPostLikeStatusReq{
|
||||||
|
Targets: in.GetTargetId(),
|
||||||
|
LikeType: domain.LikeType(in.GetLikeType()),
|
||||||
|
UID: in.GetUid(),
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
e := domain.PostMongoErrorL(
|
||||||
|
logx.WithContext(l.ctx),
|
||||||
|
[]logx.LogField{
|
||||||
|
{Key: "req", Value: in},
|
||||||
|
{Key: "func", Value: "PostModel.FindUIDPostLikeStatus"},
|
||||||
|
{Key: "err", Value: err},
|
||||||
|
},
|
||||||
|
"failed to find uid post like status list").Wrap(err)
|
||||||
|
return nil, e
|
||||||
|
}
|
||||||
|
var result = make([]*tweeting.GetLikeStatusItem, 0, len(list))
|
||||||
|
|
||||||
|
for _, item := range list {
|
||||||
|
result = append(result, &tweeting.GetLikeStatusItem{
|
||||||
|
TargetId: item.TargetID,
|
||||||
|
Status: item.LikeStatus,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return &tweeting.GetLikeStatusResp{
|
||||||
|
Data: result,
|
||||||
|
}, nil
|
||||||
|
}
|
|
@ -0,0 +1,65 @@
|
||||||
|
package postservicelogic
|
||||||
|
|
||||||
|
import (
|
||||||
|
"app-cloudep-tweeting-service/internal/domain"
|
||||||
|
model "app-cloudep-tweeting-service/internal/model/mongo"
|
||||||
|
"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 IncDecLikeDislikeCountLogic struct {
|
||||||
|
ctx context.Context
|
||||||
|
svcCtx *svc.ServiceContext
|
||||||
|
logx.Logger
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewIncDecLikeDislikeCountLogic(ctx context.Context, svcCtx *svc.ServiceContext) *IncDecLikeDislikeCountLogic {
|
||||||
|
return &IncDecLikeDislikeCountLogic{
|
||||||
|
ctx: ctx,
|
||||||
|
svcCtx: svcCtx,
|
||||||
|
Logger: logx.WithContext(ctx),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type postReactionAction struct {
|
||||||
|
PostID string `json:"post_id" validate:"required"` // 貼文的 ID
|
||||||
|
ReactionType domain.LikeType `json:"reaction_type" validate:"required,oneof=1 2"` // 用戶的反應類型,可能是讚或不讚
|
||||||
|
IsIncrement bool `json:"is_increment" validate:"required"` // 表示是否增加(true 表示增加,false 表示減少)
|
||||||
|
}
|
||||||
|
|
||||||
|
// IncDecLikeDislikeCount 增減數量
|
||||||
|
func (l *IncDecLikeDislikeCountLogic) IncDecLikeDislikeCount(in *tweeting.IncDecLikeDislikeCountReq) (*tweeting.OKResp, error) {
|
||||||
|
// 驗證資料
|
||||||
|
if err := l.svcCtx.Validate.ValidateAll(&postReactionAction{
|
||||||
|
PostID: in.GetPostID(),
|
||||||
|
ReactionType: domain.LikeType(in.GetReactionType()),
|
||||||
|
IsIncrement: in.GetIsIncrement(),
|
||||||
|
}); err != nil {
|
||||||
|
return nil, ers.InvalidFormat(err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
err := l.svcCtx.PostModel.IncDecLikeDislikeCountLogic(l.ctx, &model.PostReactionAction{
|
||||||
|
PostID: in.GetPostID(),
|
||||||
|
ReactionType: domain.LikeType(in.GetReactionType()),
|
||||||
|
IsIncrement: in.GetIsIncrement(),
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
e := domain.PostMongoErrorL(
|
||||||
|
logx.WithContext(l.ctx),
|
||||||
|
[]logx.LogField{
|
||||||
|
{Key: "req", Value: in},
|
||||||
|
{Key: "func", Value: "PostModel.IncDecLikeDislikeCountLogic"},
|
||||||
|
{Key: "err", Value: err},
|
||||||
|
},
|
||||||
|
"failed to inc like or dislike").Wrap(err)
|
||||||
|
return nil, e
|
||||||
|
}
|
||||||
|
|
||||||
|
return &tweeting.OKResp{}, nil
|
||||||
|
}
|
|
@ -0,0 +1,86 @@
|
||||||
|
package postservicelogic
|
||||||
|
|
||||||
|
import (
|
||||||
|
"app-cloudep-tweeting-service/internal/domain"
|
||||||
|
model "app-cloudep-tweeting-service/internal/model/mongo"
|
||||||
|
"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 LikeListLogic struct {
|
||||||
|
ctx context.Context
|
||||||
|
svcCtx *svc.ServiceContext
|
||||||
|
logx.Logger
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewLikeListLogic(ctx context.Context, svcCtx *svc.ServiceContext) *LikeListLogic {
|
||||||
|
return &LikeListLogic{
|
||||||
|
ctx: ctx,
|
||||||
|
svcCtx: svcCtx,
|
||||||
|
Logger: logx.WithContext(ctx),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 換句話說就是對這個文章按讚的人的列表
|
||||||
|
|
||||||
|
type likeListReq struct {
|
||||||
|
Target string `json:"target" validate:"required"`
|
||||||
|
LikeType domain.LikeType `json:"like_type" validate:"required,oneof=1 2"`
|
||||||
|
PageSize int64 `json:"page_size" validate:"required"`
|
||||||
|
PageIndex int64 `json:"page_index" validate:"required"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// LikeList 取得讚/不讚列表
|
||||||
|
func (l *LikeListLogic) LikeList(in *tweeting.LikeListReq) (*tweeting.LikeListResp, error) {
|
||||||
|
// 驗證資料
|
||||||
|
if err := l.svcCtx.Validate.ValidateAll(&likeListReq{
|
||||||
|
Target: in.TargetId,
|
||||||
|
LikeType: domain.LikeType(in.GetLikeType()),
|
||||||
|
PageSize: in.GetPageSize(),
|
||||||
|
PageIndex: in.GetPageIndex(),
|
||||||
|
}); err != nil {
|
||||||
|
return nil, ers.InvalidFormat(err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
result, total, err := l.svcCtx.PostLikeModel.FindLikeUsers(l.ctx, &model.QueryPostLikeReq{
|
||||||
|
Target: in.GetTargetId(),
|
||||||
|
LikeType: domain.LikeType(in.GetLikeType()),
|
||||||
|
PageSize: in.GetPageSize(),
|
||||||
|
PageIndex: in.GetPageIndex(),
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
e := domain.PostMongoErrorL(
|
||||||
|
logx.WithContext(l.ctx),
|
||||||
|
[]logx.LogField{
|
||||||
|
{Key: "req", Value: in},
|
||||||
|
{Key: "func", Value: "PostModel.LikeDislike"},
|
||||||
|
{Key: "err", Value: err},
|
||||||
|
},
|
||||||
|
"failed to like or dislike").Wrap(err)
|
||||||
|
return nil, e
|
||||||
|
}
|
||||||
|
|
||||||
|
var list = make([]*tweeting.LikeItem, 0, len(result))
|
||||||
|
for _, item := range result {
|
||||||
|
list = append(list, &tweeting.LikeItem{
|
||||||
|
LikeType: int64(item.Type),
|
||||||
|
TargetId: item.TargetID,
|
||||||
|
Uid: item.UID,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return &tweeting.LikeListResp{
|
||||||
|
List: list,
|
||||||
|
Page: &tweeting.Pager{
|
||||||
|
Size: in.GetPageSize(),
|
||||||
|
Index: in.GetPageIndex(),
|
||||||
|
Total: total,
|
||||||
|
},
|
||||||
|
}, nil
|
||||||
|
}
|
|
@ -0,0 +1,69 @@
|
||||||
|
package postservicelogic
|
||||||
|
|
||||||
|
import (
|
||||||
|
"app-cloudep-tweeting-service/internal/domain"
|
||||||
|
model "app-cloudep-tweeting-service/internal/model/mongo"
|
||||||
|
"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 LikeLogic struct {
|
||||||
|
ctx context.Context
|
||||||
|
svcCtx *svc.ServiceContext
|
||||||
|
logx.Logger
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewLikeLogic(ctx context.Context, svcCtx *svc.ServiceContext) *LikeLogic {
|
||||||
|
return &LikeLogic{
|
||||||
|
ctx: ctx,
|
||||||
|
svcCtx: svcCtx,
|
||||||
|
Logger: logx.WithContext(ctx),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type likeReq struct {
|
||||||
|
Target string `json:"target" validate:"required"`
|
||||||
|
UID string `json:"uid" validate:"required"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Like 點讚/取消讚 貼文
|
||||||
|
func (l *LikeLogic) Like(in *tweeting.LikeReq) (*tweeting.PostReactionActionResp, error) {
|
||||||
|
// 驗證資料
|
||||||
|
if err := l.svcCtx.Validate.ValidateAll(&likeReq{
|
||||||
|
Target: in.TargetId,
|
||||||
|
UID: in.GetUid(),
|
||||||
|
}); err != nil {
|
||||||
|
return nil, ers.InvalidFormat(err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
likeResp, err := l.svcCtx.PostLikeModel.LikeDislike(l.ctx, &model.PostLikes{
|
||||||
|
TargetID: in.GetTargetId(),
|
||||||
|
UID: in.GetUid(),
|
||||||
|
Type: int8(in.GetLikeType()),
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
e := domain.PostMongoErrorL(
|
||||||
|
logx.WithContext(l.ctx),
|
||||||
|
[]logx.LogField{
|
||||||
|
{Key: "req", Value: in},
|
||||||
|
{Key: "func", Value: "PostModel.LikeDislike"},
|
||||||
|
{Key: "err", Value: err},
|
||||||
|
},
|
||||||
|
"failed to like or dislike").Wrap(err)
|
||||||
|
return nil, e
|
||||||
|
}
|
||||||
|
|
||||||
|
// 將文章的數量增加或減少的功能,是業務邏輯,從外面再決定要不要丟MQ 後算,或是怎麼算
|
||||||
|
|
||||||
|
return &tweeting.PostReactionActionResp{
|
||||||
|
PostID: likeResp.PostID,
|
||||||
|
ReactionType: int64(likeResp.ReactionType),
|
||||||
|
IsIncrement: likeResp.IsIncrement,
|
||||||
|
}, nil
|
||||||
|
}
|
|
@ -1,16 +1,15 @@
|
||||||
package postservicelogic
|
package postservicelogic
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"app-cloudep-tweeting-service/gen_result/pb/tweeting"
|
||||||
"app-cloudep-tweeting-service/internal/domain"
|
"app-cloudep-tweeting-service/internal/domain"
|
||||||
model "app-cloudep-tweeting-service/internal/model/mongo"
|
model "app-cloudep-tweeting-service/internal/model/mongo"
|
||||||
|
"app-cloudep-tweeting-service/internal/svc"
|
||||||
"context"
|
"context"
|
||||||
|
|
||||||
ers "code.30cm.net/digimon/library-go/errs"
|
ers "code.30cm.net/digimon/library-go/errs"
|
||||||
"google.golang.org/protobuf/proto"
|
"google.golang.org/protobuf/proto"
|
||||||
|
|
||||||
"app-cloudep-tweeting-service/gen_result/pb/tweeting"
|
|
||||||
"app-cloudep-tweeting-service/internal/svc"
|
|
||||||
|
|
||||||
"github.com/zeromicro/go-zero/core/logx"
|
"github.com/zeromicro/go-zero/core/logx"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -28,24 +27,65 @@ func NewListPostsLogic(ctx context.Context, svcCtx *svc.ServiceContext) *ListPos
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 只列出要驗證的資料
|
|
||||||
type listReq struct {
|
type listReq struct {
|
||||||
OnlyAdds int32 `json:"only_adds" validate:"oneof=0 1 2 3"`
|
OnlyAdds int32 `json:"only_adds" validate:"oneof=0 1 2 3"`
|
||||||
PageSize int64 `json:"page_size" validate:"required"`
|
PageSize int64 `json:"page_size" validate:"required"`
|
||||||
PageIndex int64 `json:"page_index" validate:"required"`
|
PageIndex int64 `json:"page_index" validate:"required"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// 將單個 Post 轉換為 PostDetailItem
|
// ListPosts 查詢貼文
|
||||||
func convertToPostDetailItem(item *model.Post) *tweeting.PostDetailItem {
|
func (l *ListPostsLogic) ListPosts(in *tweeting.QueryPostsReq) (*tweeting.ListPostsResp, error) {
|
||||||
media := make([]*tweeting.Media, 0, len(item.MediaURL))
|
// 驗證資料
|
||||||
for _, subItem := range item.MediaURL {
|
if err := l.svcCtx.Validate.ValidateAll(&listReq{
|
||||||
|
PageSize: int64(in.GetPageSize()),
|
||||||
|
PageIndex: int64(in.GetPageIndex()),
|
||||||
|
OnlyAdds: in.GetOnlyAds(),
|
||||||
|
}); err != nil {
|
||||||
|
return nil, ers.InvalidFormat(err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
query := &model.QueryPostModelReq{
|
||||||
|
UID: in.GetUid(),
|
||||||
|
Id: in.GetId(),
|
||||||
|
PageSize: int64(in.GetPageSize()),
|
||||||
|
PageIndex: int64(in.GetPageIndex()),
|
||||||
|
}
|
||||||
|
|
||||||
|
if in.OnlyAds != nil {
|
||||||
|
switch in.GetOnlyAds() {
|
||||||
|
case domain.AdTypeOnlyAd.ToInt32():
|
||||||
|
query.OnlyAds = proto.Bool(true)
|
||||||
|
case domain.AdTypeOnlyNotAd.ToInt32():
|
||||||
|
query.OnlyAds = proto.Bool(false)
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
find, count, err := l.svcCtx.PostModel.Find(l.ctx, query)
|
||||||
|
if err != nil {
|
||||||
|
e := domain.PostMongoErrorL(
|
||||||
|
logx.WithContext(l.ctx),
|
||||||
|
[]logx.LogField{
|
||||||
|
{Key: "query", Value: query},
|
||||||
|
{Key: "func", Value: "PostModel.Find"},
|
||||||
|
{Key: "err", Value: err},
|
||||||
|
},
|
||||||
|
"failed to add new post").Wrap(err)
|
||||||
|
return nil, e
|
||||||
|
}
|
||||||
|
|
||||||
|
result := make([]*tweeting.PostDetailItem, 0, count)
|
||||||
|
|
||||||
|
for _, item := range find {
|
||||||
|
media := make([]*tweeting.Media, 0, len(item.Media))
|
||||||
|
for _, subItem := range item.Media {
|
||||||
media = append(media, &tweeting.Media{
|
media = append(media, &tweeting.Media{
|
||||||
Type: subItem.Type,
|
Type: subItem.Type,
|
||||||
Url: subItem.Links,
|
Url: subItem.Links,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
return &tweeting.PostDetailItem{
|
result = append(result, &tweeting.PostDetailItem{
|
||||||
PostId: item.ID.Hex(),
|
PostId: item.ID.Hex(),
|
||||||
Uid: item.UID,
|
Uid: item.UID,
|
||||||
Content: item.Content,
|
Content: item.Content,
|
||||||
|
@ -54,71 +94,17 @@ 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),
|
||||||
}
|
})
|
||||||
}
|
|
||||||
|
|
||||||
// ListPosts 查詢貼文 -> 主流程
|
|
||||||
func (l *ListPostsLogic) ListPosts(in *tweeting.QueryPostsReq) (*tweeting.ListPostsResp, error) {
|
|
||||||
// 將 PageSize 和 PageIndex 提前轉換為 int64
|
|
||||||
pageSize := int64(in.GetPageSize())
|
|
||||||
pageIndex := int64(in.GetPageIndex())
|
|
||||||
|
|
||||||
// 驗證資料
|
|
||||||
if err := l.svcCtx.Validate.ValidateAll(&listReq{
|
|
||||||
PageSize: pageSize,
|
|
||||||
PageIndex: pageIndex,
|
|
||||||
OnlyAdds: in.GetOnlyAds(),
|
|
||||||
}); err != nil {
|
|
||||||
// 錯誤代碼 05-011-00
|
|
||||||
return nil, ers.InvalidFormat(err.Error())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 構建查詢條件
|
|
||||||
query := &model.QueryPostModelReq{
|
|
||||||
UID: in.GetUid(),
|
|
||||||
Id: in.GetPostId(),
|
|
||||||
PageSize: pageSize,
|
|
||||||
PageIndex: pageIndex,
|
|
||||||
}
|
|
||||||
|
|
||||||
// 處理 OnlyAds 條件
|
|
||||||
if in.OnlyAds != nil {
|
|
||||||
onlyAds := in.GetOnlyAds()
|
|
||||||
query.OnlyAds = proto.Bool(onlyAds == domain.AdTypeOnlyAd.ToInt32())
|
|
||||||
}
|
|
||||||
|
|
||||||
// 執行查詢
|
|
||||||
find, count, err := l.svcCtx.PostModel.Find(l.ctx, query)
|
|
||||||
if err != nil {
|
|
||||||
// 錯誤代碼 05-021-05
|
|
||||||
e := domain.CommentErrorL(
|
|
||||||
domain.ListPostError,
|
|
||||||
logx.WithContext(l.ctx),
|
|
||||||
[]logx.LogField{
|
|
||||||
{Key: "query", Value: query},
|
|
||||||
{Key: "func", Value: "PostModel.Find"},
|
|
||||||
{Key: "err", Value: err},
|
|
||||||
},
|
|
||||||
"failed to find posts").Wrap(err)
|
|
||||||
|
|
||||||
return nil, e
|
|
||||||
}
|
|
||||||
|
|
||||||
// 將查詢結果轉換為 API 回應格式
|
|
||||||
result := make([]*tweeting.PostDetailItem, 0, count)
|
|
||||||
for _, item := range find {
|
|
||||||
result = append(result, convertToPostDetailItem(item))
|
|
||||||
}
|
|
||||||
|
|
||||||
// 返回結果
|
|
||||||
return &tweeting.ListPostsResp{
|
return &tweeting.ListPostsResp{
|
||||||
Posts: result,
|
Posts: result,
|
||||||
Page: &tweeting.Pager{
|
Page: &tweeting.Pager{
|
||||||
Total: count,
|
Total: count,
|
||||||
Index: pageIndex,
|
Index: int64(in.GetPageIndex()),
|
||||||
Size: pageSize,
|
Size: int64(in.GetPageSize()),
|
||||||
},
|
},
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,164 +0,0 @@
|
||||||
package postservicelogic
|
|
||||||
|
|
||||||
import (
|
|
||||||
"app-cloudep-tweeting-service/gen_result/pb/tweeting"
|
|
||||||
mocklib "app-cloudep-tweeting-service/internal/mock/lib"
|
|
||||||
mockmodel "app-cloudep-tweeting-service/internal/mock/model"
|
|
||||||
model "app-cloudep-tweeting-service/internal/model/mongo"
|
|
||||||
"app-cloudep-tweeting-service/internal/svc"
|
|
||||||
"context"
|
|
||||||
"errors"
|
|
||||||
"testing"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"google.golang.org/protobuf/proto"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
"go.mongodb.org/mongo-driver/bson/primitive"
|
|
||||||
"go.uber.org/mock/gomock"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestConvertToPostDetailItem(t *testing.T) {
|
|
||||||
// 構建測試數據
|
|
||||||
postID := primitive.NewObjectID()
|
|
||||||
post := &model.Post{
|
|
||||||
ID: postID,
|
|
||||||
UID: "12345",
|
|
||||||
Content: "Test Content",
|
|
||||||
Tags: []string{"tag1", "tag2"},
|
|
||||||
MediaURL: []model.Media{
|
|
||||||
{Type: "image", Links: "http://example.com/image.png"},
|
|
||||||
{Type: "video", Links: "http://example.com/video.mp4"},
|
|
||||||
},
|
|
||||||
IsAd: true,
|
|
||||||
CreateAt: time.Now().Unix(),
|
|
||||||
UpdateAt: time.Now().Unix(),
|
|
||||||
Like: 10,
|
|
||||||
DisLike: 2,
|
|
||||||
}
|
|
||||||
|
|
||||||
// 執行轉換
|
|
||||||
result := convertToPostDetailItem(post)
|
|
||||||
|
|
||||||
// 驗證結果
|
|
||||||
assert.Equal(t, postID.Hex(), result.PostId)
|
|
||||||
assert.Equal(t, "12345", result.Uid)
|
|
||||||
assert.Equal(t, "Test Content", result.Content)
|
|
||||||
assert.Equal(t, []string{"tag1", "tag2"}, result.Tags)
|
|
||||||
assert.Equal(t, true, result.IsAd)
|
|
||||||
assert.Equal(t, int64(10), result.LikeCount)
|
|
||||||
assert.Equal(t, int64(2), result.DislikeCount)
|
|
||||||
|
|
||||||
// 驗證 Media 的轉換
|
|
||||||
assert.Len(t, result.Media, 2)
|
|
||||||
assert.Equal(t, "image", result.Media[0].Type)
|
|
||||||
assert.Equal(t, "http://example.com/image.png", result.Media[0].Url)
|
|
||||||
assert.Equal(t, "video", result.Media[1].Type)
|
|
||||||
assert.Equal(t, "http://example.com/video.mp4", result.Media[1].Url)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestListPosts(t *testing.T) {
|
|
||||||
ctrl := gomock.NewController(t)
|
|
||||||
defer ctrl.Finish()
|
|
||||||
|
|
||||||
// 初始化 mock 依賴
|
|
||||||
mockPostModel := mockmodel.NewMockPostModel(ctrl)
|
|
||||||
mockValidate := mocklib.NewMockValidate(ctrl)
|
|
||||||
|
|
||||||
// 初始化服務上下文
|
|
||||||
svcCtx := &svc.ServiceContext{
|
|
||||||
PostModel: mockPostModel,
|
|
||||||
Validate: mockValidate,
|
|
||||||
}
|
|
||||||
|
|
||||||
// 構建測試數據
|
|
||||||
queryReq := &tweeting.QueryPostsReq{
|
|
||||||
PageSize: 10,
|
|
||||||
PageIndex: 1,
|
|
||||||
OnlyAds: proto.Int32(1),
|
|
||||||
}
|
|
||||||
|
|
||||||
mockPosts := []*model.Post{
|
|
||||||
{
|
|
||||||
ID: primitive.NewObjectID(),
|
|
||||||
UID: "12345",
|
|
||||||
Content: "Test Content 1",
|
|
||||||
Tags: []string{"tag1", "tag2"},
|
|
||||||
MediaURL: []model.Media{
|
|
||||||
{Type: "image", Links: "http://example.com/image1.png"},
|
|
||||||
},
|
|
||||||
IsAd: false,
|
|
||||||
CreateAt: time.Now().Unix(),
|
|
||||||
UpdateAt: time.Now().Unix(),
|
|
||||||
Like: 5,
|
|
||||||
DisLike: 1,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
ID: primitive.NewObjectID(),
|
|
||||||
UID: "67890",
|
|
||||||
Content: "Test Content 2",
|
|
||||||
Tags: []string{"tag3", "tag4"},
|
|
||||||
MediaURL: []model.Media{
|
|
||||||
{Type: "video", Links: "http://example.com/video1.mp4"},
|
|
||||||
},
|
|
||||||
IsAd: true,
|
|
||||||
CreateAt: time.Now().Unix(),
|
|
||||||
UpdateAt: time.Now().Unix(),
|
|
||||||
Like: 3,
|
|
||||||
DisLike: 0,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
// 測試數據集
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
input *tweeting.QueryPostsReq
|
|
||||||
prepare func()
|
|
||||||
expectErr bool
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "成功查詢貼文",
|
|
||||||
input: queryReq,
|
|
||||||
prepare: func() {
|
|
||||||
mockValidate.EXPECT().ValidateAll(gomock.Any()).Return(nil).Times(1)
|
|
||||||
mockPostModel.EXPECT().Find(gomock.Any(), gomock.Any()).Return(mockPosts, int64(len(mockPosts)), nil).Times(1)
|
|
||||||
},
|
|
||||||
expectErr: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "查詢貼文失敗",
|
|
||||||
input: queryReq,
|
|
||||||
prepare: func() {
|
|
||||||
mockValidate.EXPECT().ValidateAll(gomock.Any()).Return(nil).Times(1)
|
|
||||||
mockPostModel.EXPECT().Find(gomock.Any(), gomock.Any()).Return(nil, int64(0), errors.New("find failed")).Times(1)
|
|
||||||
},
|
|
||||||
expectErr: true,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
// 執行測試
|
|
||||||
for _, tt := range tests {
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
|
||||||
// 設置測試環境
|
|
||||||
tt.prepare()
|
|
||||||
|
|
||||||
// 初始化 ListPostsLogic
|
|
||||||
logic := ListPostsLogic{
|
|
||||||
svcCtx: svcCtx,
|
|
||||||
ctx: context.TODO(),
|
|
||||||
}
|
|
||||||
|
|
||||||
// 執行 ListPosts
|
|
||||||
resp, err := logic.ListPosts(tt.input)
|
|
||||||
|
|
||||||
// 驗證結果
|
|
||||||
if tt.expectErr {
|
|
||||||
assert.Error(t, err)
|
|
||||||
} else {
|
|
||||||
assert.NoError(t, err)
|
|
||||||
assert.NotNil(t, resp)
|
|
||||||
assert.Len(t, resp.Posts, len(mockPosts))
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,49 +1,46 @@
|
||||||
package postservicelogic
|
package postservicelogic
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"app-cloudep-tweeting-service/gen_result/pb/tweeting"
|
||||||
"app-cloudep-tweeting-service/internal/domain"
|
"app-cloudep-tweeting-service/internal/domain"
|
||||||
model "app-cloudep-tweeting-service/internal/model/mongo"
|
model "app-cloudep-tweeting-service/internal/model/mongo"
|
||||||
|
|
||||||
"app-cloudep-tweeting-service/gen_result/pb/tweeting"
|
|
||||||
"app-cloudep-tweeting-service/internal/svc"
|
"app-cloudep-tweeting-service/internal/svc"
|
||||||
"context"
|
"context"
|
||||||
|
|
||||||
ers "code.30cm.net/digimon/library-go/errs"
|
ers "code.30cm.net/digimon/library-go/errs"
|
||||||
|
|
||||||
"github.com/zeromicro/go-zero/core/logx"
|
"github.com/zeromicro/go-zero/core/logx"
|
||||||
)
|
)
|
||||||
|
|
||||||
type CreatePostLogic struct {
|
type NewPostLogic struct {
|
||||||
ctx context.Context
|
ctx context.Context
|
||||||
svcCtx *svc.ServiceContext
|
svcCtx *svc.ServiceContext
|
||||||
logx.Logger
|
logx.Logger
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewCreatePostLogic(ctx context.Context, svcCtx *svc.ServiceContext) *CreatePostLogic {
|
func NewNewPostLogic(ctx context.Context, svcCtx *svc.ServiceContext) *NewPostLogic {
|
||||||
return &CreatePostLogic{
|
return &NewPostLogic{
|
||||||
ctx: ctx,
|
ctx: ctx,
|
||||||
svcCtx: svcCtx,
|
svcCtx: svcCtx,
|
||||||
Logger: logx.WithContext(ctx),
|
Logger: logx.WithContext(ctx),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO 要調查一下內容如果存 html 是否有需要Encode
|
|
||||||
// 輸入的定義 -> 檢查用
|
|
||||||
type newTweetingReq struct {
|
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
|
||||||
}
|
}
|
||||||
|
|
||||||
// CreatePost 新增貼文
|
// NewPost 新增貼文
|
||||||
func (l *CreatePostLogic) CreatePost(in *tweeting.NewPostReq) (*tweeting.PostResp, error) {
|
func (l *NewPostLogic) NewPost(in *tweeting.NewPostReq) (*tweeting.PostResp, error) {
|
||||||
// 驗證資料
|
// 驗證資料
|
||||||
if err := l.svcCtx.Validate.ValidateAll(&newTweetingReq{
|
if err := l.svcCtx.Validate.ValidateAll(&newTweetingReq{
|
||||||
UID: in.GetUid(),
|
UID: in.GetUid(),
|
||||||
Content: in.GetContent(),
|
Content: in.GetContent(),
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
// 錯誤代碼 05-011-00
|
|
||||||
return nil, ers.InvalidFormat(err.Error())
|
return nil, ers.InvalidFormat(err.Error())
|
||||||
}
|
}
|
||||||
// ============ prepare ============
|
// ============ prepare ============
|
||||||
|
@ -70,14 +67,12 @@ func (l *CreatePostLogic) CreatePost(in *tweeting.NewPostReq) (*tweeting.PostRes
|
||||||
Type: item.Type,
|
Type: item.Type,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
tweet.MediaURL = media
|
tweet.Media = media
|
||||||
}
|
}
|
||||||
// ============ insert ============
|
// ============ insert ============
|
||||||
err := l.svcCtx.PostModel.Insert(l.ctx, tweet)
|
err := l.svcCtx.PostModel.Insert(l.ctx, tweet)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// 錯誤代碼 05-021-02
|
e := domain.PostMongoErrorL(
|
||||||
e := domain.CommentErrorL(
|
|
||||||
domain.CreatePostError,
|
|
||||||
logx.WithContext(l.ctx),
|
logx.WithContext(l.ctx),
|
||||||
[]logx.LogField{
|
[]logx.LogField{
|
||||||
{Key: "req", Value: in},
|
{Key: "req", Value: in},
|
||||||
|
@ -85,7 +80,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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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,59 +36,43 @@ 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 {
|
||||||
// 錯誤代碼 05-011-00
|
|
||||||
return nil, ers.InvalidFormat(err.Error())
|
return nil, ers.InvalidFormat(err.Error())
|
||||||
}
|
}
|
||||||
// 沒有就沒有,有就走全覆蓋
|
// 沒有就沒有,有就走全覆蓋
|
||||||
update := model.Post{}
|
update := model.Post{}
|
||||||
oid, err := primitive.ObjectIDFromHex(in.GetPostId())
|
oid, err := primitive.ObjectIDFromHex(in.GetPostId())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// 錯誤代碼 05-011-00
|
|
||||||
return nil, ers.InvalidFormat("failed to get correct post id")
|
return nil, ers.InvalidFormat("failed to get correct post id")
|
||||||
}
|
}
|
||||||
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,
|
||||||
Type: item.Type,
|
Type: item.Type,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
update.MediaURL = media
|
update.Media = media
|
||||||
update.Content = in.GetContent()
|
update.Content = in.GetContent()
|
||||||
|
update.Like = uint64(in.GetLikeCount())
|
||||||
// 因為 0 也有意義,所以如果是真的沒帶進來,用 -1 帶進去表示不作動
|
update.DisLike = uint64(in.GetDislikeCount())
|
||||||
if in.LikeCount == nil {
|
|
||||||
update.Like = -1
|
|
||||||
} else {
|
|
||||||
update.Like = in.GetLikeCount()
|
|
||||||
}
|
|
||||||
|
|
||||||
if in.DislikeCount == nil {
|
|
||||||
update.DisLike = -1
|
|
||||||
} else {
|
|
||||||
update.DisLike = in.GetDislikeCount()
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = l.svcCtx.PostModel.UpdateOptional(l.ctx, &update)
|
_, err = l.svcCtx.PostModel.UpdateOptional(l.ctx, &update)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// 錯誤代碼 05-021-04
|
e := domain.PostMongoErrorL(
|
||||||
e := domain.CommentErrorL(
|
|
||||||
domain.UpdatePostError,
|
|
||||||
logx.WithContext(l.ctx),
|
logx.WithContext(l.ctx),
|
||||||
[]logx.LogField{
|
[]logx.LogField{
|
||||||
{Key: "req", Value: in},
|
{Key: "req", Value: in},
|
||||||
{Key: "func", Value: "PostModel.UpdateOptional"},
|
{Key: "func", Value: "PostModel.UpdateOptional"},
|
||||||
{Key: "err", Value: err},
|
{Key: "err", Value: err},
|
||||||
},
|
},
|
||||||
"failed to update post", in.PostId).Wrap(err)
|
"failed to add new post").Wrap(err)
|
||||||
|
|
||||||
return nil, e
|
return nil, e
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,118 +0,0 @@
|
||||||
package postservicelogic
|
|
||||||
|
|
||||||
import (
|
|
||||||
"app-cloudep-tweeting-service/gen_result/pb/tweeting"
|
|
||||||
mocklib "app-cloudep-tweeting-service/internal/mock/lib"
|
|
||||||
mockmodel "app-cloudep-tweeting-service/internal/mock/model"
|
|
||||||
"app-cloudep-tweeting-service/internal/svc"
|
|
||||||
"context"
|
|
||||||
"errors"
|
|
||||||
|
|
||||||
"go.mongodb.org/mongo-driver/mongo"
|
|
||||||
"google.golang.org/protobuf/proto"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
"go.uber.org/mock/gomock"
|
|
||||||
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestUpdatePost(t *testing.T) {
|
|
||||||
ctrl := gomock.NewController(t)
|
|
||||||
defer ctrl.Finish()
|
|
||||||
|
|
||||||
// 初始化 mock 依賴
|
|
||||||
mockPostModel := mockmodel.NewMockPostModel(ctrl)
|
|
||||||
mockValidate := mocklib.NewMockValidate(ctrl)
|
|
||||||
|
|
||||||
// 初始化服務上下文
|
|
||||||
svcCtx := &svc.ServiceContext{
|
|
||||||
PostModel: mockPostModel,
|
|
||||||
Validate: mockValidate,
|
|
||||||
}
|
|
||||||
|
|
||||||
// 測試數據集
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
input *tweeting.UpdatePostReq
|
|
||||||
prepare func()
|
|
||||||
expectErr bool
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "成功更新貼文",
|
|
||||||
input: &tweeting.UpdatePostReq{
|
|
||||||
PostId: "66cfdc1d6f8fe7eac1e52523",
|
|
||||||
Content: proto.String("Updated content"),
|
|
||||||
Tags: []string{"tag1", "tag2"},
|
|
||||||
Media: []*tweeting.Media{
|
|
||||||
{
|
|
||||||
Url: "http://example.com/image.png",
|
|
||||||
Type: "image",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
LikeCount: proto.Int64(10),
|
|
||||||
DislikeCount: proto.Int64(2),
|
|
||||||
},
|
|
||||||
prepare: func() {
|
|
||||||
// 模擬 Validate 成功
|
|
||||||
mockValidate.EXPECT().ValidateAll(gomock.Any()).Return(nil).Times(1)
|
|
||||||
// 模擬 UpdateOptional 成功
|
|
||||||
mockPostModel.EXPECT().UpdateOptional(gomock.Any(), gomock.Any()).Return(&mongo.UpdateResult{
|
|
||||||
ModifiedCount: 1,
|
|
||||||
}, nil).Times(1)
|
|
||||||
},
|
|
||||||
expectErr: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "驗證失敗",
|
|
||||||
input: &tweeting.UpdatePostReq{
|
|
||||||
PostId: "",
|
|
||||||
},
|
|
||||||
prepare: func() {
|
|
||||||
// 模擬 Validate 失敗
|
|
||||||
mockValidate.EXPECT().ValidateAll(gomock.Any()).Return(errors.New("validation failed")).Times(1)
|
|
||||||
},
|
|
||||||
expectErr: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "更新貼文失敗",
|
|
||||||
input: &tweeting.UpdatePostReq{
|
|
||||||
PostId: "66cfdc1d6f8fe7eac1e52523",
|
|
||||||
Content: proto.String("Updated content"),
|
|
||||||
},
|
|
||||||
prepare: func() {
|
|
||||||
// 模擬 Validate 成功
|
|
||||||
mockValidate.EXPECT().ValidateAll(gomock.Any()).Return(nil).Times(1)
|
|
||||||
// 模擬 UpdateOptional 失敗
|
|
||||||
mockPostModel.EXPECT().UpdateOptional(gomock.Any(), gomock.Any()).Return(&mongo.UpdateResult{
|
|
||||||
ModifiedCount: 0,
|
|
||||||
}, errors.New("update failed")).Times(1)
|
|
||||||
},
|
|
||||||
expectErr: true,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
// 執行測試
|
|
||||||
for _, tt := range tests {
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
|
||||||
// 設置測試環境
|
|
||||||
tt.prepare()
|
|
||||||
|
|
||||||
// 初始化 UpdatePostLogic
|
|
||||||
logic := UpdatePostLogic{
|
|
||||||
svcCtx: svcCtx,
|
|
||||||
ctx: context.TODO(),
|
|
||||||
}
|
|
||||||
|
|
||||||
// 執行 UpdatePost
|
|
||||||
_, err := logic.UpdatePost(tt.input)
|
|
||||||
|
|
||||||
// 驗證結果
|
|
||||||
if tt.expectErr {
|
|
||||||
assert.Error(t, err)
|
|
||||||
} else {
|
|
||||||
assert.NoError(t, err)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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,73 +0,0 @@
|
||||||
// Code generated by MockGen. DO NOT EDIT.
|
|
||||||
// Source: ./validate.go
|
|
||||||
//
|
|
||||||
// Generated by this command:
|
|
||||||
//
|
|
||||||
// mockgen -source=./validate.go -destination=../../mock/lib/validate.go -package=lib
|
|
||||||
//
|
|
||||||
|
|
||||||
// Package lib is a generated GoMock package.
|
|
||||||
package lib
|
|
||||||
|
|
||||||
import (
|
|
||||||
reflect "reflect"
|
|
||||||
|
|
||||||
required "code.30cm.net/digimon/library-go/validator"
|
|
||||||
|
|
||||||
gomock "go.uber.org/mock/gomock"
|
|
||||||
)
|
|
||||||
|
|
||||||
// MockValidate is a mock of Validate interface.
|
|
||||||
type MockValidate struct {
|
|
||||||
ctrl *gomock.Controller
|
|
||||||
recorder *MockValidateMockRecorder
|
|
||||||
}
|
|
||||||
|
|
||||||
// MockValidateMockRecorder is the mock recorder for MockValidate.
|
|
||||||
type MockValidateMockRecorder struct {
|
|
||||||
mock *MockValidate
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewMockValidate creates a new mock instance.
|
|
||||||
func NewMockValidate(ctrl *gomock.Controller) *MockValidate {
|
|
||||||
mock := &MockValidate{ctrl: ctrl}
|
|
||||||
mock.recorder = &MockValidateMockRecorder{mock}
|
|
||||||
return mock
|
|
||||||
}
|
|
||||||
|
|
||||||
// EXPECT returns an object that allows the caller to indicate expected use.
|
|
||||||
func (m *MockValidate) EXPECT() *MockValidateMockRecorder {
|
|
||||||
return m.recorder
|
|
||||||
}
|
|
||||||
|
|
||||||
// BindToValidator mocks base method.
|
|
||||||
func (m *MockValidate) BindToValidator(opts ...required.Option) error {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
varargs := []any{}
|
|
||||||
for _, a := range opts {
|
|
||||||
varargs = append(varargs, a)
|
|
||||||
}
|
|
||||||
ret := m.ctrl.Call(m, "BindToValidator", varargs...)
|
|
||||||
ret0, _ := ret[0].(error)
|
|
||||||
return ret0
|
|
||||||
}
|
|
||||||
|
|
||||||
// BindToValidator indicates an expected call of BindToValidator.
|
|
||||||
func (mr *MockValidateMockRecorder) BindToValidator(opts ...any) *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "BindToValidator", reflect.TypeOf((*MockValidate)(nil).BindToValidator), opts...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ValidateAll mocks base method.
|
|
||||||
func (m *MockValidate) ValidateAll(obj any) error {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
ret := m.ctrl.Call(m, "ValidateAll", obj)
|
|
||||||
ret0, _ := ret[0].(error)
|
|
||||||
return ret0
|
|
||||||
}
|
|
||||||
|
|
||||||
// ValidateAll indicates an expected call of ValidateAll.
|
|
||||||
func (mr *MockValidateMockRecorder) ValidateAll(obj any) *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ValidateAll", reflect.TypeOf((*MockValidate)(nil).ValidateAll), obj)
|
|
||||||
}
|
|
|
@ -1,152 +0,0 @@
|
||||||
// Code generated by MockGen. DO NOT EDIT.
|
|
||||||
// Source: ./internal/model/mongo/comment_model.go
|
|
||||||
//
|
|
||||||
// Generated by this command:
|
|
||||||
//
|
|
||||||
// mockgen -source=./internal/model/mongo/comment_model.go -destination=./internal/mock/model/comment_model.go -package=mock
|
|
||||||
//
|
|
||||||
|
|
||||||
// Package mock is a generated GoMock package.
|
|
||||||
package mock
|
|
||||||
|
|
||||||
import (
|
|
||||||
model "app-cloudep-tweeting-service/internal/model/mongo"
|
|
||||||
context "context"
|
|
||||||
reflect "reflect"
|
|
||||||
|
|
||||||
mongo "go.mongodb.org/mongo-driver/mongo"
|
|
||||||
gomock "go.uber.org/mock/gomock"
|
|
||||||
)
|
|
||||||
|
|
||||||
// MockCommentModel is a mock of CommentModel interface.
|
|
||||||
type MockCommentModel struct {
|
|
||||||
ctrl *gomock.Controller
|
|
||||||
recorder *MockCommentModelMockRecorder
|
|
||||||
}
|
|
||||||
|
|
||||||
// MockCommentModelMockRecorder is the mock recorder for MockCommentModel.
|
|
||||||
type MockCommentModelMockRecorder struct {
|
|
||||||
mock *MockCommentModel
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewMockCommentModel creates a new mock instance.
|
|
||||||
func NewMockCommentModel(ctrl *gomock.Controller) *MockCommentModel {
|
|
||||||
mock := &MockCommentModel{ctrl: ctrl}
|
|
||||||
mock.recorder = &MockCommentModelMockRecorder{mock}
|
|
||||||
return mock
|
|
||||||
}
|
|
||||||
|
|
||||||
// EXPECT returns an object that allows the caller to indicate expected use.
|
|
||||||
func (m *MockCommentModel) EXPECT() *MockCommentModelMockRecorder {
|
|
||||||
return m.recorder
|
|
||||||
}
|
|
||||||
|
|
||||||
// Delete mocks base method.
|
|
||||||
func (m *MockCommentModel) Delete(ctx context.Context, id string) (int64, error) {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
ret := m.ctrl.Call(m, "Delete", ctx, id)
|
|
||||||
ret0, _ := ret[0].(int64)
|
|
||||||
ret1, _ := ret[1].(error)
|
|
||||||
return ret0, ret1
|
|
||||||
}
|
|
||||||
|
|
||||||
// Delete indicates an expected call of Delete.
|
|
||||||
func (mr *MockCommentModelMockRecorder) Delete(ctx, id any) *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Delete", reflect.TypeOf((*MockCommentModel)(nil).Delete), ctx, id)
|
|
||||||
}
|
|
||||||
|
|
||||||
// DeleteMany mocks base method.
|
|
||||||
func (m *MockCommentModel) DeleteMany(ctx context.Context, id ...string) (int64, error) {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
varargs := []any{ctx}
|
|
||||||
for _, a := range id {
|
|
||||||
varargs = append(varargs, a)
|
|
||||||
}
|
|
||||||
ret := m.ctrl.Call(m, "DeleteMany", varargs...)
|
|
||||||
ret0, _ := ret[0].(int64)
|
|
||||||
ret1, _ := ret[1].(error)
|
|
||||||
return ret0, ret1
|
|
||||||
}
|
|
||||||
|
|
||||||
// DeleteMany indicates an expected call of DeleteMany.
|
|
||||||
func (mr *MockCommentModelMockRecorder) DeleteMany(ctx any, id ...any) *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
varargs := append([]any{ctx}, id...)
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteMany", reflect.TypeOf((*MockCommentModel)(nil).DeleteMany), varargs...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Find mocks base method.
|
|
||||||
func (m *MockCommentModel) Find(ctx context.Context, param *model.QueryCommentModelReq) ([]*model.Comment, int64, error) {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
ret := m.ctrl.Call(m, "Find", ctx, param)
|
|
||||||
ret0, _ := ret[0].([]*model.Comment)
|
|
||||||
ret1, _ := ret[1].(int64)
|
|
||||||
ret2, _ := ret[2].(error)
|
|
||||||
return ret0, ret1, ret2
|
|
||||||
}
|
|
||||||
|
|
||||||
// Find indicates an expected call of Find.
|
|
||||||
func (mr *MockCommentModelMockRecorder) Find(ctx, param any) *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Find", reflect.TypeOf((*MockCommentModel)(nil).Find), ctx, param)
|
|
||||||
}
|
|
||||||
|
|
||||||
// FindOne mocks base method.
|
|
||||||
func (m *MockCommentModel) FindOne(ctx context.Context, id string) (*model.Comment, error) {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
ret := m.ctrl.Call(m, "FindOne", ctx, id)
|
|
||||||
ret0, _ := ret[0].(*model.Comment)
|
|
||||||
ret1, _ := ret[1].(error)
|
|
||||||
return ret0, ret1
|
|
||||||
}
|
|
||||||
|
|
||||||
// FindOne indicates an expected call of FindOne.
|
|
||||||
func (mr *MockCommentModelMockRecorder) FindOne(ctx, id any) *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "FindOne", reflect.TypeOf((*MockCommentModel)(nil).FindOne), ctx, id)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Insert mocks base method.
|
|
||||||
func (m *MockCommentModel) Insert(ctx context.Context, data *model.Comment) error {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
ret := m.ctrl.Call(m, "Insert", ctx, data)
|
|
||||||
ret0, _ := ret[0].(error)
|
|
||||||
return ret0
|
|
||||||
}
|
|
||||||
|
|
||||||
// Insert indicates an expected call of Insert.
|
|
||||||
func (mr *MockCommentModelMockRecorder) Insert(ctx, data any) *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Insert", reflect.TypeOf((*MockCommentModel)(nil).Insert), ctx, data)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update mocks base method.
|
|
||||||
func (m *MockCommentModel) Update(ctx context.Context, data *model.Comment) (*mongo.UpdateResult, error) {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
ret := m.ctrl.Call(m, "Update", ctx, data)
|
|
||||||
ret0, _ := ret[0].(*mongo.UpdateResult)
|
|
||||||
ret1, _ := ret[1].(error)
|
|
||||||
return ret0, ret1
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update indicates an expected call of Update.
|
|
||||||
func (mr *MockCommentModelMockRecorder) Update(ctx, data any) *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Update", reflect.TypeOf((*MockCommentModel)(nil).Update), ctx, data)
|
|
||||||
}
|
|
||||||
|
|
||||||
// UpdateOptional mocks base method.
|
|
||||||
func (m *MockCommentModel) UpdateOptional(ctx context.Context, data *model.Comment) (*mongo.UpdateResult, error) {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
ret := m.ctrl.Call(m, "UpdateOptional", ctx, data)
|
|
||||||
ret0, _ := ret[0].(*mongo.UpdateResult)
|
|
||||||
ret1, _ := ret[1].(error)
|
|
||||||
return ret0, ret1
|
|
||||||
}
|
|
||||||
|
|
||||||
// UpdateOptional indicates an expected call of UpdateOptional.
|
|
||||||
func (mr *MockCommentModelMockRecorder) UpdateOptional(ctx, data any) *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateOptional", reflect.TypeOf((*MockCommentModel)(nil).UpdateOptional), ctx, data)
|
|
||||||
}
|
|
|
@ -1,101 +0,0 @@
|
||||||
// Code generated by MockGen. DO NOT EDIT.
|
|
||||||
// Source: ./internal/model/mongo/comment_model_gen.go
|
|
||||||
//
|
|
||||||
// Generated by this command:
|
|
||||||
//
|
|
||||||
// mockgen -source=./internal/model/mongo/comment_model_gen.go -destination=./internal/mock/model/comment_model_gen.go -package=mock
|
|
||||||
//
|
|
||||||
|
|
||||||
// Package mock is a generated GoMock package.
|
|
||||||
package mock
|
|
||||||
|
|
||||||
import (
|
|
||||||
model "app-cloudep-tweeting-service/internal/model/mongo"
|
|
||||||
context "context"
|
|
||||||
reflect "reflect"
|
|
||||||
|
|
||||||
mongo "go.mongodb.org/mongo-driver/mongo"
|
|
||||||
gomock "go.uber.org/mock/gomock"
|
|
||||||
)
|
|
||||||
|
|
||||||
// MockcommentModel is a mock of commentModel interface.
|
|
||||||
type MockcommentModel struct {
|
|
||||||
ctrl *gomock.Controller
|
|
||||||
recorder *MockcommentModelMockRecorder
|
|
||||||
}
|
|
||||||
|
|
||||||
// MockcommentModelMockRecorder is the mock recorder for MockcommentModel.
|
|
||||||
type MockcommentModelMockRecorder struct {
|
|
||||||
mock *MockcommentModel
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewMockcommentModel creates a new mock instance.
|
|
||||||
func NewMockcommentModel(ctrl *gomock.Controller) *MockcommentModel {
|
|
||||||
mock := &MockcommentModel{ctrl: ctrl}
|
|
||||||
mock.recorder = &MockcommentModelMockRecorder{mock}
|
|
||||||
return mock
|
|
||||||
}
|
|
||||||
|
|
||||||
// EXPECT returns an object that allows the caller to indicate expected use.
|
|
||||||
func (m *MockcommentModel) EXPECT() *MockcommentModelMockRecorder {
|
|
||||||
return m.recorder
|
|
||||||
}
|
|
||||||
|
|
||||||
// Delete mocks base method.
|
|
||||||
func (m *MockcommentModel) Delete(ctx context.Context, id string) (int64, error) {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
ret := m.ctrl.Call(m, "Delete", ctx, id)
|
|
||||||
ret0, _ := ret[0].(int64)
|
|
||||||
ret1, _ := ret[1].(error)
|
|
||||||
return ret0, ret1
|
|
||||||
}
|
|
||||||
|
|
||||||
// Delete indicates an expected call of Delete.
|
|
||||||
func (mr *MockcommentModelMockRecorder) Delete(ctx, id any) *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Delete", reflect.TypeOf((*MockcommentModel)(nil).Delete), ctx, id)
|
|
||||||
}
|
|
||||||
|
|
||||||
// FindOne mocks base method.
|
|
||||||
func (m *MockcommentModel) FindOne(ctx context.Context, id string) (*model.Comment, error) {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
ret := m.ctrl.Call(m, "FindOne", ctx, id)
|
|
||||||
ret0, _ := ret[0].(*model.Comment)
|
|
||||||
ret1, _ := ret[1].(error)
|
|
||||||
return ret0, ret1
|
|
||||||
}
|
|
||||||
|
|
||||||
// FindOne indicates an expected call of FindOne.
|
|
||||||
func (mr *MockcommentModelMockRecorder) FindOne(ctx, id any) *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "FindOne", reflect.TypeOf((*MockcommentModel)(nil).FindOne), ctx, id)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Insert mocks base method.
|
|
||||||
func (m *MockcommentModel) Insert(ctx context.Context, data *model.Comment) error {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
ret := m.ctrl.Call(m, "Insert", ctx, data)
|
|
||||||
ret0, _ := ret[0].(error)
|
|
||||||
return ret0
|
|
||||||
}
|
|
||||||
|
|
||||||
// Insert indicates an expected call of Insert.
|
|
||||||
func (mr *MockcommentModelMockRecorder) Insert(ctx, data any) *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Insert", reflect.TypeOf((*MockcommentModel)(nil).Insert), ctx, data)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update mocks base method.
|
|
||||||
func (m *MockcommentModel) Update(ctx context.Context, data *model.Comment) (*mongo.UpdateResult, error) {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
ret := m.ctrl.Call(m, "Update", ctx, data)
|
|
||||||
ret0, _ := ret[0].(*mongo.UpdateResult)
|
|
||||||
ret1, _ := ret[1].(error)
|
|
||||||
return ret0, ret1
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update indicates an expected call of Update.
|
|
||||||
func (mr *MockcommentModelMockRecorder) Update(ctx, data any) *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Update", reflect.TypeOf((*MockcommentModel)(nil).Update), ctx, data)
|
|
||||||
}
|
|
|
@ -1,152 +0,0 @@
|
||||||
// Code generated by MockGen. DO NOT EDIT.
|
|
||||||
// Source: ./internal/model/mongo/post_model.go
|
|
||||||
//
|
|
||||||
// Generated by this command:
|
|
||||||
//
|
|
||||||
// mockgen -source=./internal/model/mongo/post_model.go -destination=./internal/mock/model/post_model.go -package=mock
|
|
||||||
//
|
|
||||||
|
|
||||||
// Package mock is a generated GoMock package.
|
|
||||||
package mock
|
|
||||||
|
|
||||||
import (
|
|
||||||
model "app-cloudep-tweeting-service/internal/model/mongo"
|
|
||||||
context "context"
|
|
||||||
reflect "reflect"
|
|
||||||
|
|
||||||
mongo "go.mongodb.org/mongo-driver/mongo"
|
|
||||||
gomock "go.uber.org/mock/gomock"
|
|
||||||
)
|
|
||||||
|
|
||||||
// MockPostModel is a mock of PostModel interface.
|
|
||||||
type MockPostModel struct {
|
|
||||||
ctrl *gomock.Controller
|
|
||||||
recorder *MockPostModelMockRecorder
|
|
||||||
}
|
|
||||||
|
|
||||||
// MockPostModelMockRecorder is the mock recorder for MockPostModel.
|
|
||||||
type MockPostModelMockRecorder struct {
|
|
||||||
mock *MockPostModel
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewMockPostModel creates a new mock instance.
|
|
||||||
func NewMockPostModel(ctrl *gomock.Controller) *MockPostModel {
|
|
||||||
mock := &MockPostModel{ctrl: ctrl}
|
|
||||||
mock.recorder = &MockPostModelMockRecorder{mock}
|
|
||||||
return mock
|
|
||||||
}
|
|
||||||
|
|
||||||
// EXPECT returns an object that allows the caller to indicate expected use.
|
|
||||||
func (m *MockPostModel) EXPECT() *MockPostModelMockRecorder {
|
|
||||||
return m.recorder
|
|
||||||
}
|
|
||||||
|
|
||||||
// Delete mocks base method.
|
|
||||||
func (m *MockPostModel) Delete(ctx context.Context, id string) (int64, error) {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
ret := m.ctrl.Call(m, "Delete", ctx, id)
|
|
||||||
ret0, _ := ret[0].(int64)
|
|
||||||
ret1, _ := ret[1].(error)
|
|
||||||
return ret0, ret1
|
|
||||||
}
|
|
||||||
|
|
||||||
// Delete indicates an expected call of Delete.
|
|
||||||
func (mr *MockPostModelMockRecorder) Delete(ctx, id any) *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Delete", reflect.TypeOf((*MockPostModel)(nil).Delete), ctx, id)
|
|
||||||
}
|
|
||||||
|
|
||||||
// DeleteMany mocks base method.
|
|
||||||
func (m *MockPostModel) DeleteMany(ctx context.Context, id ...string) (int64, error) {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
varargs := []any{ctx}
|
|
||||||
for _, a := range id {
|
|
||||||
varargs = append(varargs, a)
|
|
||||||
}
|
|
||||||
ret := m.ctrl.Call(m, "DeleteMany", varargs...)
|
|
||||||
ret0, _ := ret[0].(int64)
|
|
||||||
ret1, _ := ret[1].(error)
|
|
||||||
return ret0, ret1
|
|
||||||
}
|
|
||||||
|
|
||||||
// DeleteMany indicates an expected call of DeleteMany.
|
|
||||||
func (mr *MockPostModelMockRecorder) DeleteMany(ctx any, id ...any) *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
varargs := append([]any{ctx}, id...)
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteMany", reflect.TypeOf((*MockPostModel)(nil).DeleteMany), varargs...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Find mocks base method.
|
|
||||||
func (m *MockPostModel) Find(ctx context.Context, param *model.QueryPostModelReq) ([]*model.Post, int64, error) {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
ret := m.ctrl.Call(m, "Find", ctx, param)
|
|
||||||
ret0, _ := ret[0].([]*model.Post)
|
|
||||||
ret1, _ := ret[1].(int64)
|
|
||||||
ret2, _ := ret[2].(error)
|
|
||||||
return ret0, ret1, ret2
|
|
||||||
}
|
|
||||||
|
|
||||||
// Find indicates an expected call of Find.
|
|
||||||
func (mr *MockPostModelMockRecorder) Find(ctx, param any) *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Find", reflect.TypeOf((*MockPostModel)(nil).Find), ctx, param)
|
|
||||||
}
|
|
||||||
|
|
||||||
// FindOne mocks base method.
|
|
||||||
func (m *MockPostModel) FindOne(ctx context.Context, id string) (*model.Post, error) {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
ret := m.ctrl.Call(m, "FindOne", ctx, id)
|
|
||||||
ret0, _ := ret[0].(*model.Post)
|
|
||||||
ret1, _ := ret[1].(error)
|
|
||||||
return ret0, ret1
|
|
||||||
}
|
|
||||||
|
|
||||||
// FindOne indicates an expected call of FindOne.
|
|
||||||
func (mr *MockPostModelMockRecorder) FindOne(ctx, id any) *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "FindOne", reflect.TypeOf((*MockPostModel)(nil).FindOne), ctx, id)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Insert mocks base method.
|
|
||||||
func (m *MockPostModel) Insert(ctx context.Context, data *model.Post) error {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
ret := m.ctrl.Call(m, "Insert", ctx, data)
|
|
||||||
ret0, _ := ret[0].(error)
|
|
||||||
return ret0
|
|
||||||
}
|
|
||||||
|
|
||||||
// Insert indicates an expected call of Insert.
|
|
||||||
func (mr *MockPostModelMockRecorder) Insert(ctx, data any) *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Insert", reflect.TypeOf((*MockPostModel)(nil).Insert), ctx, data)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update mocks base method.
|
|
||||||
func (m *MockPostModel) Update(ctx context.Context, data *model.Post) (*mongo.UpdateResult, error) {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
ret := m.ctrl.Call(m, "Update", ctx, data)
|
|
||||||
ret0, _ := ret[0].(*mongo.UpdateResult)
|
|
||||||
ret1, _ := ret[1].(error)
|
|
||||||
return ret0, ret1
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update indicates an expected call of Update.
|
|
||||||
func (mr *MockPostModelMockRecorder) Update(ctx, data any) *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Update", reflect.TypeOf((*MockPostModel)(nil).Update), ctx, data)
|
|
||||||
}
|
|
||||||
|
|
||||||
// UpdateOptional mocks base method.
|
|
||||||
func (m *MockPostModel) UpdateOptional(ctx context.Context, data *model.Post) (*mongo.UpdateResult, error) {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
ret := m.ctrl.Call(m, "UpdateOptional", ctx, data)
|
|
||||||
ret0, _ := ret[0].(*mongo.UpdateResult)
|
|
||||||
ret1, _ := ret[1].(error)
|
|
||||||
return ret0, ret1
|
|
||||||
}
|
|
||||||
|
|
||||||
// UpdateOptional indicates an expected call of UpdateOptional.
|
|
||||||
func (mr *MockPostModelMockRecorder) UpdateOptional(ctx, data any) *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateOptional", reflect.TypeOf((*MockPostModel)(nil).UpdateOptional), ctx, data)
|
|
||||||
}
|
|
|
@ -1,101 +0,0 @@
|
||||||
// Code generated by MockGen. DO NOT EDIT.
|
|
||||||
// Source: ./internal/model/mongo/post_model_gen.go
|
|
||||||
//
|
|
||||||
// Generated by this command:
|
|
||||||
//
|
|
||||||
// mockgen -source=./internal/model/mongo/post_model_gen.go -destination=./internal/mock/model/post_model_gen.go -package=mock
|
|
||||||
//
|
|
||||||
|
|
||||||
// Package mock is a generated GoMock package.
|
|
||||||
package mock
|
|
||||||
|
|
||||||
import (
|
|
||||||
model "app-cloudep-tweeting-service/internal/model/mongo"
|
|
||||||
context "context"
|
|
||||||
reflect "reflect"
|
|
||||||
|
|
||||||
mongo "go.mongodb.org/mongo-driver/mongo"
|
|
||||||
gomock "go.uber.org/mock/gomock"
|
|
||||||
)
|
|
||||||
|
|
||||||
// MockpostModel is a mock of postModel interface.
|
|
||||||
type MockpostModel struct {
|
|
||||||
ctrl *gomock.Controller
|
|
||||||
recorder *MockpostModelMockRecorder
|
|
||||||
}
|
|
||||||
|
|
||||||
// MockpostModelMockRecorder is the mock recorder for MockpostModel.
|
|
||||||
type MockpostModelMockRecorder struct {
|
|
||||||
mock *MockpostModel
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewMockpostModel creates a new mock instance.
|
|
||||||
func NewMockpostModel(ctrl *gomock.Controller) *MockpostModel {
|
|
||||||
mock := &MockpostModel{ctrl: ctrl}
|
|
||||||
mock.recorder = &MockpostModelMockRecorder{mock}
|
|
||||||
return mock
|
|
||||||
}
|
|
||||||
|
|
||||||
// EXPECT returns an object that allows the caller to indicate expected use.
|
|
||||||
func (m *MockpostModel) EXPECT() *MockpostModelMockRecorder {
|
|
||||||
return m.recorder
|
|
||||||
}
|
|
||||||
|
|
||||||
// Delete mocks base method.
|
|
||||||
func (m *MockpostModel) Delete(ctx context.Context, id string) (int64, error) {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
ret := m.ctrl.Call(m, "Delete", ctx, id)
|
|
||||||
ret0, _ := ret[0].(int64)
|
|
||||||
ret1, _ := ret[1].(error)
|
|
||||||
return ret0, ret1
|
|
||||||
}
|
|
||||||
|
|
||||||
// Delete indicates an expected call of Delete.
|
|
||||||
func (mr *MockpostModelMockRecorder) Delete(ctx, id any) *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Delete", reflect.TypeOf((*MockpostModel)(nil).Delete), ctx, id)
|
|
||||||
}
|
|
||||||
|
|
||||||
// FindOne mocks base method.
|
|
||||||
func (m *MockpostModel) FindOne(ctx context.Context, id string) (*model.Post, error) {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
ret := m.ctrl.Call(m, "FindOne", ctx, id)
|
|
||||||
ret0, _ := ret[0].(*model.Post)
|
|
||||||
ret1, _ := ret[1].(error)
|
|
||||||
return ret0, ret1
|
|
||||||
}
|
|
||||||
|
|
||||||
// FindOne indicates an expected call of FindOne.
|
|
||||||
func (mr *MockpostModelMockRecorder) FindOne(ctx, id any) *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "FindOne", reflect.TypeOf((*MockpostModel)(nil).FindOne), ctx, id)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Insert mocks base method.
|
|
||||||
func (m *MockpostModel) Insert(ctx context.Context, data *model.Post) error {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
ret := m.ctrl.Call(m, "Insert", ctx, data)
|
|
||||||
ret0, _ := ret[0].(error)
|
|
||||||
return ret0
|
|
||||||
}
|
|
||||||
|
|
||||||
// Insert indicates an expected call of Insert.
|
|
||||||
func (mr *MockpostModelMockRecorder) Insert(ctx, data any) *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Insert", reflect.TypeOf((*MockpostModel)(nil).Insert), ctx, data)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update mocks base method.
|
|
||||||
func (m *MockpostModel) Update(ctx context.Context, data *model.Post) (*mongo.UpdateResult, error) {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
ret := m.ctrl.Call(m, "Update", ctx, data)
|
|
||||||
ret0, _ := ret[0].(*mongo.UpdateResult)
|
|
||||||
ret1, _ := ret[1].(error)
|
|
||||||
return ret0, ret1
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update indicates an expected call of Update.
|
|
||||||
func (mr *MockpostModelMockRecorder) Update(ctx, data any) *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Update", reflect.TypeOf((*MockpostModel)(nil).Update), ctx, data)
|
|
||||||
}
|
|
|
@ -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)
|
|
||||||
}
|
|
|
@ -0,0 +1,25 @@
|
||||||
|
package model
|
||||||
|
|
||||||
|
import "github.com/zeromicro/go-zero/core/stores/mon"
|
||||||
|
|
||||||
|
var _ Comment_likesModel = (*customComment_likesModel)(nil)
|
||||||
|
|
||||||
|
type (
|
||||||
|
// Comment_likesModel is an interface to be customized, add more methods here,
|
||||||
|
// and implement the added methods in customComment_likesModel.
|
||||||
|
Comment_likesModel interface {
|
||||||
|
comment_likesModel
|
||||||
|
}
|
||||||
|
|
||||||
|
customComment_likesModel struct {
|
||||||
|
*defaultComment_likesModel
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
// NewComment_likesModel returns a model for the mongo.
|
||||||
|
func NewComment_likesModel(url, db, collection string) Comment_likesModel {
|
||||||
|
conn := mon.MustNewModel(url, db, collection)
|
||||||
|
return &customComment_likesModel{
|
||||||
|
defaultComment_likesModel: newDefaultComment_likesModel(conn),
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,74 @@
|
||||||
|
// Code generated by goctl. DO NOT EDIT.
|
||||||
|
package model
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/zeromicro/go-zero/core/stores/mon"
|
||||||
|
"go.mongodb.org/mongo-driver/bson"
|
||||||
|
"go.mongodb.org/mongo-driver/bson/primitive"
|
||||||
|
"go.mongodb.org/mongo-driver/mongo"
|
||||||
|
)
|
||||||
|
|
||||||
|
type comment_likesModel interface {
|
||||||
|
Insert(ctx context.Context, data *CommentLikes) error
|
||||||
|
FindOne(ctx context.Context, id string) (*CommentLikes, error)
|
||||||
|
Update(ctx context.Context, data *CommentLikes) (*mongo.UpdateResult, error)
|
||||||
|
Delete(ctx context.Context, id string) (int64, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type defaultComment_likesModel struct {
|
||||||
|
conn *mon.Model
|
||||||
|
}
|
||||||
|
|
||||||
|
func newDefaultComment_likesModel(conn *mon.Model) *defaultComment_likesModel {
|
||||||
|
return &defaultComment_likesModel{conn: conn}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *defaultComment_likesModel) Insert(ctx context.Context, data *CommentLikes) error {
|
||||||
|
if data.ID.IsZero() {
|
||||||
|
data.ID = primitive.NewObjectID()
|
||||||
|
data.CreateAt = time.Now()
|
||||||
|
data.UpdateAt = time.Now()
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := m.conn.InsertOne(ctx, data)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *defaultComment_likesModel) FindOne(ctx context.Context, id string) (*CommentLikes, error) {
|
||||||
|
oid, err := primitive.ObjectIDFromHex(id)
|
||||||
|
if err != nil {
|
||||||
|
return nil, ErrInvalidObjectId
|
||||||
|
}
|
||||||
|
|
||||||
|
var data CommentLikes
|
||||||
|
|
||||||
|
err = m.conn.FindOne(ctx, &data, bson.M{"_id": oid})
|
||||||
|
switch err {
|
||||||
|
case nil:
|
||||||
|
return &data, nil
|
||||||
|
case mon.ErrNotFound:
|
||||||
|
return nil, ErrNotFound
|
||||||
|
default:
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *defaultComment_likesModel) Update(ctx context.Context, data *CommentLikes) (*mongo.UpdateResult, error) {
|
||||||
|
data.UpdateAt = time.Now()
|
||||||
|
|
||||||
|
res, err := m.conn.UpdateOne(ctx, bson.M{"_id": data.ID}, bson.M{"$set": data})
|
||||||
|
return res, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *defaultComment_likesModel) Delete(ctx context.Context, id string) (int64, error) {
|
||||||
|
oid, err := primitive.ObjectIDFromHex(id)
|
||||||
|
if err != nil {
|
||||||
|
return 0, ErrInvalidObjectId
|
||||||
|
}
|
||||||
|
|
||||||
|
res, err := m.conn.DeleteOne(ctx, bson.M{"_id": oid})
|
||||||
|
return res, err
|
||||||
|
}
|
|
@ -0,0 +1,14 @@
|
||||||
|
package model
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"go.mongodb.org/mongo-driver/bson/primitive"
|
||||||
|
)
|
||||||
|
|
||||||
|
type CommentLikes struct {
|
||||||
|
ID primitive.ObjectID `bson:"_id,omitempty" json:"id,omitempty"`
|
||||||
|
// TODO: Fill your own fields
|
||||||
|
UpdateAt time.Time `bson:"updateAt,omitempty" json:"updateAt,omitempty"`
|
||||||
|
CreateAt time.Time `bson:"createAt,omitempty" json:"createAt,omitempty"`
|
||||||
|
}
|
|
@ -1,14 +1,8 @@
|
||||||
package model
|
package model
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"github.com/zeromicro/go-zero/core/stores/cache"
|
||||||
"time"
|
"github.com/zeromicro/go-zero/core/stores/monc"
|
||||||
|
|
||||||
"github.com/zeromicro/go-zero/core/logx"
|
|
||||||
"github.com/zeromicro/go-zero/core/stores/mon"
|
|
||||||
"go.mongodb.org/mongo-driver/bson"
|
|
||||||
"go.mongodb.org/mongo-driver/bson/primitive"
|
|
||||||
"go.mongodb.org/mongo-driver/mongo"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var _ CommentModel = (*customCommentModel)(nil)
|
var _ CommentModel = (*customCommentModel)(nil)
|
||||||
|
@ -18,84 +12,17 @@ type (
|
||||||
// and implement the added methods in customCommentModel.
|
// and implement the added methods in customCommentModel.
|
||||||
CommentModel interface {
|
CommentModel interface {
|
||||||
commentModel
|
commentModel
|
||||||
DeleteMany(ctx context.Context, id ...string) (int64, error)
|
|
||||||
UpdateOptional(ctx context.Context, data *Comment) (*mongo.UpdateResult, error)
|
|
||||||
Find(ctx context.Context, param *QueryCommentModelReq) ([]*Comment, int64, error)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
customCommentModel struct {
|
customCommentModel struct {
|
||||||
*defaultCommentModel
|
*defaultCommentModel
|
||||||
}
|
}
|
||||||
|
|
||||||
QueryCommentModelReq struct {
|
|
||||||
PostID string
|
|
||||||
PageSize int64
|
|
||||||
PageIndex int64
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func (xw customCommentModel) Find(ctx context.Context, param *QueryCommentModelReq) ([]*Comment, int64, error) {
|
|
||||||
// TODO implement me
|
|
||||||
panic("implement me")
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewCommentModel returns a model for the mongo.
|
// NewCommentModel returns a model for the mongo.
|
||||||
func NewCommentModel(url, db, collection string) CommentModel {
|
func NewCommentModel(url, db, collection string, c cache.CacheConf) CommentModel {
|
||||||
conn := mon.MustNewModel(url, db, collection)
|
conn := monc.MustNewModel(url, db, collection, c)
|
||||||
return &customCommentModel{
|
return &customCommentModel{
|
||||||
defaultCommentModel: newDefaultCommentModel(conn),
|
defaultCommentModel: newDefaultCommentModel(conn),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *defaultCommentModel) DeleteMany(ctx context.Context, id ...string) (int64, error) {
|
|
||||||
objectIDs := make([]primitive.ObjectID, 0, len(id))
|
|
||||||
|
|
||||||
// prepare
|
|
||||||
for _, item := range id {
|
|
||||||
oid, err := primitive.ObjectIDFromHex(item)
|
|
||||||
if err != nil {
|
|
||||||
logx.WithCallerSkip(1).WithFields(
|
|
||||||
logx.Field("func", "defaultPostModel.DeleteMany"),
|
|
||||||
logx.Field("id", item),
|
|
||||||
).Error(err.Error())
|
|
||||||
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
objectIDs = append(objectIDs, oid)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 檢查是否有有效的 ObjectIDs
|
|
||||||
if len(objectIDs) == 0 {
|
|
||||||
return 0, ErrNotFound
|
|
||||||
}
|
|
||||||
|
|
||||||
// 刪除文檔
|
|
||||||
res, err := m.conn.DeleteMany(ctx, bson.M{"_id": bson.M{"$in": objectIDs}})
|
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return res, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *defaultCommentModel) UpdateOptional(ctx context.Context, data *Comment) (*mongo.UpdateResult, error) {
|
|
||||||
update := bson.M{"$set": bson.M{}}
|
|
||||||
|
|
||||||
if data.Content != "" {
|
|
||||||
update["$set"].(bson.M)["content"] = data.Content
|
|
||||||
}
|
|
||||||
|
|
||||||
if data.LikeCount != -1 {
|
|
||||||
update["$set"].(bson.M)["like_count"] = data.LikeCount
|
|
||||||
}
|
|
||||||
|
|
||||||
if data.DisLikeCount != -1 {
|
|
||||||
update["$set"].(bson.M)["dis_like_count"] = data.DisLikeCount
|
|
||||||
}
|
|
||||||
|
|
||||||
// UpdateAt 是每次都需要更新的,不用檢查
|
|
||||||
update["$set"].(bson.M)["updateAt"] = time.Now().UTC().UnixNano()
|
|
||||||
res, err := m.conn.UpdateOne(ctx, bson.M{"_id": data.ID}, update)
|
|
||||||
|
|
||||||
return res, err
|
|
||||||
}
|
|
||||||
|
|
|
@ -5,12 +5,14 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/zeromicro/go-zero/core/stores/mon"
|
"github.com/zeromicro/go-zero/core/stores/monc"
|
||||||
"go.mongodb.org/mongo-driver/bson"
|
"go.mongodb.org/mongo-driver/bson"
|
||||||
"go.mongodb.org/mongo-driver/bson/primitive"
|
"go.mongodb.org/mongo-driver/bson/primitive"
|
||||||
"go.mongodb.org/mongo-driver/mongo"
|
"go.mongodb.org/mongo-driver/mongo"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var prefixCommentCacheKey = "cache:comment:"
|
||||||
|
|
||||||
type commentModel interface {
|
type commentModel interface {
|
||||||
Insert(ctx context.Context, data *Comment) error
|
Insert(ctx context.Context, data *Comment) error
|
||||||
FindOne(ctx context.Context, id string) (*Comment, error)
|
FindOne(ctx context.Context, id string) (*Comment, error)
|
||||||
|
@ -19,21 +21,22 @@ type commentModel interface {
|
||||||
}
|
}
|
||||||
|
|
||||||
type defaultCommentModel struct {
|
type defaultCommentModel struct {
|
||||||
conn *mon.Model
|
conn *monc.Model
|
||||||
}
|
}
|
||||||
|
|
||||||
func newDefaultCommentModel(conn *mon.Model) *defaultCommentModel {
|
func newDefaultCommentModel(conn *monc.Model) *defaultCommentModel {
|
||||||
return &defaultCommentModel{conn: conn}
|
return &defaultCommentModel{conn: conn}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *defaultCommentModel) Insert(ctx context.Context, data *Comment) error {
|
func (m *defaultCommentModel) Insert(ctx context.Context, data *Comment) error {
|
||||||
if data.ID.IsZero() {
|
if data.ID.IsZero() {
|
||||||
data.ID = primitive.NewObjectID()
|
data.ID = primitive.NewObjectID()
|
||||||
data.CreateAt = time.Now().UTC().UnixNano()
|
data.CreateAt = time.Now()
|
||||||
data.UpdateAt = time.Now().UTC().UnixNano()
|
data.UpdateAt = time.Now()
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err := m.conn.InsertOne(ctx, data)
|
key := prefixCommentCacheKey + data.ID.Hex()
|
||||||
|
_, err := m.conn.InsertOne(ctx, key, data)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -44,12 +47,12 @@ func (m *defaultCommentModel) FindOne(ctx context.Context, id string) (*Comment,
|
||||||
}
|
}
|
||||||
|
|
||||||
var data Comment
|
var data Comment
|
||||||
|
key := prefixCommentCacheKey + id
|
||||||
err = m.conn.FindOne(ctx, &data, bson.M{"_id": oid})
|
err = m.conn.FindOne(ctx, key, &data, bson.M{"_id": oid})
|
||||||
switch err {
|
switch err {
|
||||||
case nil:
|
case nil:
|
||||||
return &data, nil
|
return &data, nil
|
||||||
case mon.ErrNotFound:
|
case monc.ErrNotFound:
|
||||||
return nil, ErrNotFound
|
return nil, ErrNotFound
|
||||||
default:
|
default:
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -57,9 +60,9 @@ func (m *defaultCommentModel) FindOne(ctx context.Context, id string) (*Comment,
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *defaultCommentModel) Update(ctx context.Context, data *Comment) (*mongo.UpdateResult, error) {
|
func (m *defaultCommentModel) Update(ctx context.Context, data *Comment) (*mongo.UpdateResult, error) {
|
||||||
data.UpdateAt = time.Now().UTC().UnixNano()
|
data.UpdateAt = time.Now()
|
||||||
|
key := prefixCommentCacheKey + data.ID.Hex()
|
||||||
res, err := m.conn.UpdateOne(ctx, bson.M{"_id": data.ID}, bson.M{"$set": data})
|
res, err := m.conn.UpdateOne(ctx, key, bson.M{"_id": data.ID}, bson.M{"$set": data})
|
||||||
return res, err
|
return res, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -68,7 +71,7 @@ func (m *defaultCommentModel) Delete(ctx context.Context, id string) (int64, err
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, ErrInvalidObjectId
|
return 0, ErrInvalidObjectId
|
||||||
}
|
}
|
||||||
|
key := prefixCommentCacheKey + id
|
||||||
res, err := m.conn.DeleteOne(ctx, bson.M{"_id": oid})
|
res, err := m.conn.DeleteOne(ctx, key, bson.M{"_id": oid})
|
||||||
return res, err
|
return res, err
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,23 +1,14 @@
|
||||||
package model
|
package model
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
"go.mongodb.org/mongo-driver/bson/primitive"
|
"go.mongodb.org/mongo-driver/bson/primitive"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Comment struct {
|
type Comment struct {
|
||||||
ID primitive.ObjectID `bson:"_id,omitempty" json:"id,omitempty"`
|
ID primitive.ObjectID `bson:"_id,omitempty" json:"id,omitempty"`
|
||||||
PostID string `bson:"post_id" json:"post_id"`
|
// TODO: Fill your own fields
|
||||||
UID string `bson:"uid" json:"uid"` // 留下留言的人
|
UpdateAt time.Time `bson:"updateAt,omitempty" json:"updateAt,omitempty"`
|
||||||
Content string `bson:"content" json:"content"` // 留言內容
|
CreateAt time.Time `bson:"createAt,omitempty" json:"createAt,omitempty"`
|
||||||
LikeCount int64 `bson:"like_count" json:"like_count"` // 喜歡這則留言的人
|
|
||||||
DisLikeCount int64 `bson:"dis_like_count" json:"dis_like_count"` // 不喜歡這則留言的人
|
|
||||||
UpdateAt int64 `bson:"updateAt,omitempty" json:"updateAt,omitempty"`
|
|
||||||
CreateAt int64 `bson:"createAt,omitempty" json:"createAt,omitempty"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c Comment) CollectionName() string {
|
|
||||||
return "comment"
|
|
||||||
}
|
|
||||||
|
|
||||||
// 照邏輯,應該需要建立的索引有
|
|
||||||
// 複合索引:(PostID + CreateAt)
|
|
||||||
|
|
|
@ -0,0 +1,195 @@
|
||||||
|
package model
|
||||||
|
|
||||||
|
import (
|
||||||
|
"app-cloudep-tweeting-service/internal/domain"
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/zeromicro/go-zero/core/stores/mon"
|
||||||
|
"go.mongodb.org/mongo-driver/bson"
|
||||||
|
"go.mongodb.org/mongo-driver/bson/primitive"
|
||||||
|
"go.mongodb.org/mongo-driver/mongo"
|
||||||
|
"go.mongodb.org/mongo-driver/mongo/options"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ Post_likesModel = (*customPost_likesModel)(nil)
|
||||||
|
|
||||||
|
type (
|
||||||
|
// Post_likesModel is an interface to be customized, add more methods here,
|
||||||
|
// and implement the added methods in customPost_likesModel.
|
||||||
|
Post_likesModel interface {
|
||||||
|
post_likesModel
|
||||||
|
LikeDislike(ctx context.Context, postLike *PostLikes) (*PostReactionAction, error)
|
||||||
|
FindLikeUsers(ctx context.Context, param *QueryPostLikeReq) ([]*PostLikes, int64, error)
|
||||||
|
FindUIDPostLikeStatus(ctx context.Context, param *QueryUIDPostLikeStatusReq) ([]UIDPostLikeStatusResp, error)
|
||||||
|
Count(ctx context.Context, target string, likeType domain.LikeType) (int64, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
PostReactionAction struct {
|
||||||
|
PostID string // 貼文的 ID
|
||||||
|
ReactionType domain.LikeType // 用戶的反應類型,可能是讚或不讚
|
||||||
|
IsIncrement bool // 表示是否增加(true 表示增加,false 表示減少)
|
||||||
|
}
|
||||||
|
|
||||||
|
QueryPostLikeReq struct {
|
||||||
|
Target string `bson:"target"`
|
||||||
|
LikeType domain.LikeType `bson:"like_type"`
|
||||||
|
PageIndex int64 `bson:"page_index"`
|
||||||
|
PageSize int64 `bson:"page_size"`
|
||||||
|
}
|
||||||
|
|
||||||
|
QueryUIDPostLikeStatusReq struct {
|
||||||
|
Targets []string `bson:"target"`
|
||||||
|
LikeType domain.LikeType `bson:"like_type"`
|
||||||
|
UID string
|
||||||
|
}
|
||||||
|
|
||||||
|
UIDPostLikeStatusResp struct {
|
||||||
|
TargetID string `json:"target_id"`
|
||||||
|
LikeStatus bool `json:"like_status"`
|
||||||
|
}
|
||||||
|
|
||||||
|
customPost_likesModel struct {
|
||||||
|
*defaultPost_likesModel
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
// NewPost_likesModel returns a model for the mongo.
|
||||||
|
func NewPost_likesModel(url, db, collection string) Post_likesModel {
|
||||||
|
conn := mon.MustNewModel(url, db, collection)
|
||||||
|
return &customPost_likesModel{
|
||||||
|
defaultPost_likesModel: newDefaultPost_likesModel(conn),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *defaultPost_likesModel) LikeDislike(ctx context.Context, postLike *PostLikes) (*PostReactionAction, error) {
|
||||||
|
result := &PostReactionAction{
|
||||||
|
PostID: postLike.TargetID,
|
||||||
|
ReactionType: domain.LikeType(postLike.Type),
|
||||||
|
}
|
||||||
|
|
||||||
|
// 使用 target_id、uid、type 來查詢資料是否存在
|
||||||
|
filter := bson.M{
|
||||||
|
"target_id": postLike.TargetID,
|
||||||
|
"uid": postLike.UID,
|
||||||
|
"type": postLike.Type,
|
||||||
|
}
|
||||||
|
|
||||||
|
// 查詢資料是否存在
|
||||||
|
var existingPostLike PostLikes
|
||||||
|
err := m.conn.FindOne(ctx, &existingPostLike, filter)
|
||||||
|
|
||||||
|
if err == nil {
|
||||||
|
// 資料存在,進行刪除操作
|
||||||
|
result.IsIncrement = false
|
||||||
|
_, err = m.conn.DeleteOne(ctx, filter)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err // 刪除失敗
|
||||||
|
}
|
||||||
|
|
||||||
|
return result, nil // 刪除成功
|
||||||
|
} else if errors.Is(mongo.ErrNoDocuments, err) {
|
||||||
|
// 資料不存在,進行插入操作
|
||||||
|
result.IsIncrement = true
|
||||||
|
postLike.ID = primitive.NewObjectID() // 設置新的 ObjectID
|
||||||
|
postLike.CreateAt = time.Now().UTC().UnixNano()
|
||||||
|
_, err = m.conn.InsertOne(ctx, postLike)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err // 插入失敗
|
||||||
|
}
|
||||||
|
|
||||||
|
return result, nil // 插入成功
|
||||||
|
} else {
|
||||||
|
// 其他錯誤
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *defaultPost_likesModel) FindLikeUsers(ctx context.Context, param *QueryPostLikeReq) ([]*PostLikes, int64, error) {
|
||||||
|
// 建立篩選條件
|
||||||
|
filter := bson.M{}
|
||||||
|
|
||||||
|
// 如果指定了 Target 條件,將其添加到篩選條件中
|
||||||
|
if param.Target != "" {
|
||||||
|
filter["target_id"] = param.Target
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果指定了 LikeType 條件,將其添加到篩選條件中
|
||||||
|
if param.LikeType != 0 {
|
||||||
|
filter["type"] = param.LikeType
|
||||||
|
}
|
||||||
|
|
||||||
|
// 計算符合條件的文檔總數
|
||||||
|
totalCount, err := m.conn.CountDocuments(ctx, filter)
|
||||||
|
if err != nil {
|
||||||
|
return nil, 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// 設置分頁和排序選項
|
||||||
|
opts := options.Find()
|
||||||
|
opts.SetSort(bson.D{{"createAt", -1}}) // 按照創建時間倒序排序
|
||||||
|
if param.PageSize > 0 {
|
||||||
|
opts.SetLimit(param.PageSize)
|
||||||
|
}
|
||||||
|
if param.PageIndex > 0 && param.PageSize > 0 {
|
||||||
|
opts.SetSkip((param.PageIndex - 1) * param.PageSize)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 查詢符合條件的文檔
|
||||||
|
var results []*PostLikes
|
||||||
|
err = m.conn.Find(ctx, &results, filter, opts)
|
||||||
|
if err != nil {
|
||||||
|
return nil, 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// 返回結果集、總數和錯誤信息
|
||||||
|
return results, totalCount, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *defaultPost_likesModel) FindUIDPostLikeStatus(ctx context.Context, param *QueryUIDPostLikeStatusReq) ([]UIDPostLikeStatusResp, error) {
|
||||||
|
// 初始化返回結果的切片
|
||||||
|
var results []UIDPostLikeStatusResp
|
||||||
|
|
||||||
|
// 建立篩選條件
|
||||||
|
filter := bson.M{
|
||||||
|
"uid": param.UID, // 篩選指定的 UID
|
||||||
|
"target_id": bson.M{"$in": param.Targets}, // 篩選多個目標 ID
|
||||||
|
"type": param.LikeType, // 篩選指定的 LikeType
|
||||||
|
}
|
||||||
|
|
||||||
|
// 查詢符合條件的點讚記錄
|
||||||
|
var postLikes []PostLikes
|
||||||
|
err := m.conn.Find(ctx, &postLikes, filter)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// 構建一個 map 來保存查詢到的點讚記錄
|
||||||
|
targetLikeMap := make(map[string]bool)
|
||||||
|
for _, like := range postLikes {
|
||||||
|
targetLikeMap[like.TargetID] = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// 構建每個目標的點讚狀態
|
||||||
|
for _, targetID := range param.Targets {
|
||||||
|
likeStatus := UIDPostLikeStatusResp{
|
||||||
|
TargetID: targetID,
|
||||||
|
LikeStatus: targetLikeMap[targetID], // 如果 map 中有該 targetID,返回 true,否則返回 false
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果該 targetID 沒有點讚記錄,默認 LikeStatus 為 false
|
||||||
|
if _, found := targetLikeMap[targetID]; !found {
|
||||||
|
likeStatus.LikeStatus = false
|
||||||
|
}
|
||||||
|
|
||||||
|
results = append(results, likeStatus)
|
||||||
|
}
|
||||||
|
|
||||||
|
return results, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c customPost_likesModel) Count(ctx context.Context, target string, likeType domain.LikeType) (int64, error) {
|
||||||
|
// TODO implement me
|
||||||
|
panic("implement me")
|
||||||
|
}
|
|
@ -0,0 +1,71 @@
|
||||||
|
// Code generated by goctl. DO NOT EDIT.
|
||||||
|
package model
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/zeromicro/go-zero/core/stores/mon"
|
||||||
|
"go.mongodb.org/mongo-driver/bson"
|
||||||
|
"go.mongodb.org/mongo-driver/bson/primitive"
|
||||||
|
"go.mongodb.org/mongo-driver/mongo"
|
||||||
|
)
|
||||||
|
|
||||||
|
type post_likesModel interface {
|
||||||
|
Insert(ctx context.Context, data *PostLikes) error
|
||||||
|
FindOne(ctx context.Context, id string) (*PostLikes, error)
|
||||||
|
Update(ctx context.Context, data *PostLikes) (*mongo.UpdateResult, error)
|
||||||
|
Delete(ctx context.Context, id string) (int64, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type defaultPost_likesModel struct {
|
||||||
|
conn *mon.Model
|
||||||
|
}
|
||||||
|
|
||||||
|
func newDefaultPost_likesModel(conn *mon.Model) *defaultPost_likesModel {
|
||||||
|
return &defaultPost_likesModel{conn: conn}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *defaultPost_likesModel) Insert(ctx context.Context, data *PostLikes) error {
|
||||||
|
if data.ID.IsZero() {
|
||||||
|
data.ID = primitive.NewObjectID()
|
||||||
|
data.CreateAt = time.Now().UTC().UnixNano()
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := m.conn.InsertOne(ctx, data)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *defaultPost_likesModel) FindOne(ctx context.Context, id string) (*PostLikes, error) {
|
||||||
|
oid, err := primitive.ObjectIDFromHex(id)
|
||||||
|
if err != nil {
|
||||||
|
return nil, ErrInvalidObjectId
|
||||||
|
}
|
||||||
|
|
||||||
|
var data PostLikes
|
||||||
|
|
||||||
|
err = m.conn.FindOne(ctx, &data, bson.M{"_id": oid})
|
||||||
|
switch err {
|
||||||
|
case nil:
|
||||||
|
return &data, nil
|
||||||
|
case mon.ErrNotFound:
|
||||||
|
return nil, ErrNotFound
|
||||||
|
default:
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *defaultPost_likesModel) Update(ctx context.Context, data *PostLikes) (*mongo.UpdateResult, error) {
|
||||||
|
res, err := m.conn.UpdateOne(ctx, bson.M{"_id": data.ID}, bson.M{"$set": data})
|
||||||
|
return res, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *defaultPost_likesModel) Delete(ctx context.Context, id string) (int64, error) {
|
||||||
|
oid, err := primitive.ObjectIDFromHex(id)
|
||||||
|
if err != nil {
|
||||||
|
return 0, ErrInvalidObjectId
|
||||||
|
}
|
||||||
|
|
||||||
|
res, err := m.conn.DeleteOne(ctx, bson.M{"_id": oid})
|
||||||
|
return res, err
|
||||||
|
}
|
|
@ -0,0 +1,17 @@
|
||||||
|
package model
|
||||||
|
|
||||||
|
import (
|
||||||
|
"go.mongodb.org/mongo-driver/bson/primitive"
|
||||||
|
)
|
||||||
|
|
||||||
|
type PostLikes struct {
|
||||||
|
ID primitive.ObjectID `bson:"_id,omitempty" json:"id,omitempty"`
|
||||||
|
TargetID string `bson:"target_id" json:"target_id"`
|
||||||
|
UID string `bson:"uid" json:"uid"`
|
||||||
|
Type int8 `bson:"type" json:"type"`
|
||||||
|
CreateAt int64 `bson:"createAt,omitempty" json:"createAt,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *PostLikes) CollectionName() string {
|
||||||
|
return "post_like"
|
||||||
|
}
|
|
@ -1,18 +1,19 @@
|
||||||
package model
|
package model
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"app-cloudep-tweeting-service/internal/domain"
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
|
"fmt"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/zeromicro/go-zero/core/stores/monc"
|
|
||||||
"go.mongodb.org/mongo-driver/mongo/options"
|
|
||||||
|
|
||||||
"github.com/zeromicro/go-zero/core/logx"
|
"github.com/zeromicro/go-zero/core/logx"
|
||||||
"github.com/zeromicro/go-zero/core/stores/mon"
|
"github.com/zeromicro/go-zero/core/stores/cache"
|
||||||
|
"github.com/zeromicro/go-zero/core/stores/monc"
|
||||||
"go.mongodb.org/mongo-driver/bson"
|
"go.mongodb.org/mongo-driver/bson"
|
||||||
"go.mongodb.org/mongo-driver/bson/primitive"
|
"go.mongodb.org/mongo-driver/bson/primitive"
|
||||||
"go.mongodb.org/mongo-driver/mongo"
|
"go.mongodb.org/mongo-driver/mongo"
|
||||||
|
"go.mongodb.org/mongo-driver/mongo/options"
|
||||||
)
|
)
|
||||||
|
|
||||||
var _ PostModel = (*customPostModel)(nil)
|
var _ PostModel = (*customPostModel)(nil)
|
||||||
|
@ -25,6 +26,7 @@ type (
|
||||||
DeleteMany(ctx context.Context, id ...string) (int64, error)
|
DeleteMany(ctx context.Context, id ...string) (int64, error)
|
||||||
UpdateOptional(ctx context.Context, data *Post) (*mongo.UpdateResult, error)
|
UpdateOptional(ctx context.Context, data *Post) (*mongo.UpdateResult, error)
|
||||||
Find(ctx context.Context, param *QueryPostModelReq) ([]*Post, int64, error)
|
Find(ctx context.Context, param *QueryPostModelReq) ([]*Post, int64, error)
|
||||||
|
IncDecLikeDislikeCountLogic(ctx context.Context, param *PostReactionAction) error
|
||||||
}
|
}
|
||||||
|
|
||||||
customPostModel struct {
|
customPostModel struct {
|
||||||
|
@ -41,8 +43,8 @@ type (
|
||||||
)
|
)
|
||||||
|
|
||||||
// NewPostModel returns a model for the mongo.
|
// NewPostModel returns a model for the mongo.
|
||||||
func NewPostModel(url, db, collection string) PostModel {
|
func NewPostModel(url, db, collection string, c cache.CacheConf) PostModel {
|
||||||
conn := mon.MustNewModel(url, db, collection)
|
conn := monc.MustNewModel(url, db, collection, c)
|
||||||
return &customPostModel{
|
return &customPostModel{
|
||||||
defaultPostModel: newDefaultPostModel(conn),
|
defaultPostModel: newDefaultPostModel(conn),
|
||||||
}
|
}
|
||||||
|
@ -50,6 +52,7 @@ func NewPostModel(url, db, collection string) PostModel {
|
||||||
|
|
||||||
func (m *defaultPostModel) DeleteMany(ctx context.Context, id ...string) (int64, error) {
|
func (m *defaultPostModel) DeleteMany(ctx context.Context, id ...string) (int64, error) {
|
||||||
objectIDs := make([]primitive.ObjectID, 0, len(id))
|
objectIDs := make([]primitive.ObjectID, 0, len(id))
|
||||||
|
key := make([]string, 0, len(id))
|
||||||
|
|
||||||
// prepare
|
// prepare
|
||||||
for _, item := range id {
|
for _, item := range id {
|
||||||
|
@ -63,6 +66,7 @@ func (m *defaultPostModel) DeleteMany(ctx context.Context, id ...string) (int64,
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
objectIDs = append(objectIDs, oid)
|
objectIDs = append(objectIDs, oid)
|
||||||
|
key = append(key, prefixPostCacheKey+item)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 檢查是否有有效的 ObjectIDs
|
// 檢查是否有有效的 ObjectIDs
|
||||||
|
@ -76,6 +80,11 @@ func (m *defaultPostModel) DeleteMany(ctx context.Context, id ...string) (int64,
|
||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
err = m.conn.DelCache(ctx, key...)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
return res, err
|
return res, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -102,21 +111,24 @@ func (m *defaultPostModel) UpdateOptional(ctx context.Context, data *Post) (*mon
|
||||||
update["$set"].(bson.M)["tags"] = data.Tags
|
update["$set"].(bson.M)["tags"] = data.Tags
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(data.MediaURL) > 0 {
|
if len(data.Media) > 0 {
|
||||||
update["$set"].(bson.M)["media_url"] = data.MediaURL
|
update["$set"].(bson.M)["media_url"] = data.Media
|
||||||
}
|
}
|
||||||
|
|
||||||
if data.Like != -1 {
|
if data.Like != 0 {
|
||||||
update["$set"].(bson.M)["like"] = data.Like
|
update["$set"].(bson.M)["like"] = data.Like
|
||||||
}
|
}
|
||||||
|
|
||||||
if data.DisLike != -1 {
|
if data.DisLike != 0 {
|
||||||
update["$set"].(bson.M)["dislike"] = data.DisLike
|
update["$set"].(bson.M)["dislike"] = data.DisLike
|
||||||
}
|
}
|
||||||
|
|
||||||
// UpdateAt 是每次都需要更新的,不用檢查
|
// UpdateAt 是每次都需要更新的,不用檢查
|
||||||
update["$set"].(bson.M)["updateAt"] = time.Now().UTC().UnixNano()
|
update["$set"].(bson.M)["updateAt"] = time.Now().UTC().UnixNano()
|
||||||
res, err := m.conn.UpdateOne(ctx, bson.M{"_id": data.ID}, update)
|
fmt.Println("update map", update)
|
||||||
|
|
||||||
|
key := prefixPostCacheKey + data.ID.Hex()
|
||||||
|
res, err := m.conn.UpdateOne(ctx, key, bson.M{"_id": data.ID}, update)
|
||||||
|
|
||||||
return res, err
|
return res, err
|
||||||
}
|
}
|
||||||
|
@ -183,3 +195,39 @@ func (m *defaultPostModel) Find(ctx context.Context, param *QueryPostModelReq) (
|
||||||
return nil, 0, err
|
return nil, 0, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *customPostModel) IncDecLikeDislikeCountLogic(ctx context.Context, param *PostReactionAction) error {
|
||||||
|
// 建立篩選條件,找到要更新的貼文
|
||||||
|
filter := bson.M{"_id": param.PostID}
|
||||||
|
|
||||||
|
// 初始化更新操作
|
||||||
|
update := bson.M{}
|
||||||
|
|
||||||
|
// 根據 ReactionType 和 IsIncrement 決定更新邏輯
|
||||||
|
if param.ReactionType == domain.LikeTypeLike {
|
||||||
|
if param.IsIncrement {
|
||||||
|
// 增加 like 計數
|
||||||
|
update = bson.M{"$inc": bson.M{"like": 1}}
|
||||||
|
} else {
|
||||||
|
// 減少 like 計數
|
||||||
|
update = bson.M{"$inc": bson.M{"like": -1}}
|
||||||
|
}
|
||||||
|
} else if param.ReactionType == domain.LikeTypeDisLike {
|
||||||
|
if param.IsIncrement {
|
||||||
|
// 增加 dislike 計數
|
||||||
|
update = bson.M{"$inc": bson.M{"dislike": 1}}
|
||||||
|
} else {
|
||||||
|
// 減少 dislike 計數
|
||||||
|
update = bson.M{"$inc": bson.M{"dislike": -1}}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 執行更新操作
|
||||||
|
key := prefixPostCacheKey + param.PostID
|
||||||
|
_, err := c.conn.UpdateOne(ctx, key, filter, update)
|
||||||
|
if err != nil {
|
||||||
|
return err // 返回錯誤信息
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil // 成功返回 nil
|
||||||
|
}
|
||||||
|
|
|
@ -5,12 +5,14 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/zeromicro/go-zero/core/stores/mon"
|
"github.com/zeromicro/go-zero/core/stores/monc"
|
||||||
"go.mongodb.org/mongo-driver/bson"
|
"go.mongodb.org/mongo-driver/bson"
|
||||||
"go.mongodb.org/mongo-driver/bson/primitive"
|
"go.mongodb.org/mongo-driver/bson/primitive"
|
||||||
"go.mongodb.org/mongo-driver/mongo"
|
"go.mongodb.org/mongo-driver/mongo"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var prefixPostCacheKey = "cache:post:"
|
||||||
|
|
||||||
type postModel interface {
|
type postModel interface {
|
||||||
Insert(ctx context.Context, data *Post) error
|
Insert(ctx context.Context, data *Post) error
|
||||||
FindOne(ctx context.Context, id string) (*Post, error)
|
FindOne(ctx context.Context, id string) (*Post, error)
|
||||||
|
@ -19,10 +21,10 @@ type postModel interface {
|
||||||
}
|
}
|
||||||
|
|
||||||
type defaultPostModel struct {
|
type defaultPostModel struct {
|
||||||
conn *mon.Model
|
conn *monc.Model
|
||||||
}
|
}
|
||||||
|
|
||||||
func newDefaultPostModel(conn *mon.Model) *defaultPostModel {
|
func newDefaultPostModel(conn *monc.Model) *defaultPostModel {
|
||||||
return &defaultPostModel{conn: conn}
|
return &defaultPostModel{conn: conn}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -33,7 +35,8 @@ func (m *defaultPostModel) Insert(ctx context.Context, data *Post) error {
|
||||||
data.UpdateAt = time.Now().UTC().UnixNano()
|
data.UpdateAt = time.Now().UTC().UnixNano()
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err := m.conn.InsertOne(ctx, data)
|
key := prefixPostCacheKey + data.ID.Hex()
|
||||||
|
_, err := m.conn.InsertOne(ctx, key, data)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -44,12 +47,12 @@ func (m *defaultPostModel) FindOne(ctx context.Context, id string) (*Post, error
|
||||||
}
|
}
|
||||||
|
|
||||||
var data Post
|
var data Post
|
||||||
|
key := prefixPostCacheKey + id
|
||||||
err = m.conn.FindOne(ctx, &data, bson.M{"_id": oid})
|
err = m.conn.FindOne(ctx, key, &data, bson.M{"_id": oid})
|
||||||
switch err {
|
switch err {
|
||||||
case nil:
|
case nil:
|
||||||
return &data, nil
|
return &data, nil
|
||||||
case mon.ErrNotFound:
|
case monc.ErrNotFound:
|
||||||
return nil, ErrNotFound
|
return nil, ErrNotFound
|
||||||
default:
|
default:
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -58,8 +61,8 @@ func (m *defaultPostModel) FindOne(ctx context.Context, id string) (*Post, error
|
||||||
|
|
||||||
func (m *defaultPostModel) Update(ctx context.Context, data *Post) (*mongo.UpdateResult, error) {
|
func (m *defaultPostModel) Update(ctx context.Context, data *Post) (*mongo.UpdateResult, error) {
|
||||||
data.UpdateAt = time.Now().UTC().UnixNano()
|
data.UpdateAt = time.Now().UTC().UnixNano()
|
||||||
|
key := prefixPostCacheKey + data.ID.Hex()
|
||||||
res, err := m.conn.UpdateOne(ctx, bson.M{"_id": data.ID}, bson.M{"$set": data})
|
res, err := m.conn.UpdateOne(ctx, key, bson.M{"_id": data.ID}, bson.M{"$set": data})
|
||||||
return res, err
|
return res, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -68,7 +71,7 @@ func (m *defaultPostModel) Delete(ctx context.Context, id string) (int64, error)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, ErrInvalidObjectId
|
return 0, ErrInvalidObjectId
|
||||||
}
|
}
|
||||||
|
key := prefixPostCacheKey + id
|
||||||
res, err := m.conn.DeleteOne(ctx, bson.M{"_id": oid})
|
res, err := m.conn.DeleteOne(ctx, key, bson.M{"_id": oid})
|
||||||
return res, err
|
return res, err
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,18 +4,23 @@ import (
|
||||||
"go.mongodb.org/mongo-driver/bson/primitive"
|
"go.mongodb.org/mongo-driver/bson/primitive"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// TODO Tag 這裡在效能與正確性之間做取捨
|
||||||
|
// 存在貼文內的不提供搜尋,純顯示用,只不過在原始的tag 發生變動的時候,並不會一起改變
|
||||||
|
// 搜尋會貼文與Tag 的表會再另外一邊做關聯
|
||||||
|
// 暫時業務邏輯上tag 只提供新增,不提供修改以及刪除,故目前版本可行
|
||||||
|
|
||||||
type Post struct {
|
type Post struct {
|
||||||
ID primitive.ObjectID `bson:"_id,omitempty" json:"id,omitempty"`
|
ID primitive.ObjectID `bson:"_id,omitempty" json:"id,omitempty"`
|
||||||
UID string `bson:"uid" json:"uid"` // 搜尋條件
|
UID string `bson:"uid" json:"uid"`
|
||||||
Content string `bson:"content" json:"content"` // 內容
|
Content string `bson:"content" json:"content"`
|
||||||
Status int8 `bson:"status" json:"status"` // 1. 等待審核中 , 2 審核通過,預設為 1 ->過濾條件
|
Status int8 `bson:"status" json:"status"` // 1. 等待審核中 , 2 審核通過,預設為 1
|
||||||
IsAd bool `bson:"is_ad" json:"is_ad"` // 此則貼文是否為廣告貼文 -> 過濾條件
|
IsAd bool `bson:"is_ad" json:"is_ad"` // 此則貼文是否為廣告貼文
|
||||||
Tags []string `bson:"tags" json:"tags"` // 本則貼文的標籤,不提供搜尋,僅提供顯示(存名字,ID 建立之後就不提供修改與刪除)
|
Tags []string `bson:"tags" json:"tags"` // 本則貼文的標籤,不提供搜尋,僅提供顯示(存名字,ID 建立之後就不提供修改與刪除)
|
||||||
MediaURL []Media `bson:"media_url" json:"media_url"` // 網址
|
Media []Media `bson:"media_url" json:"media_url"` // 網址
|
||||||
Like int64 `bson:"like" json:"like"` // 讚數量
|
Like uint64 `bson:"like" json:"like"` // 讚
|
||||||
DisLike int64 `bson:"dislike" json:"dislike"` // 不讚數量
|
DisLike uint64 `bson:"dislike" json:"dislike"` // 不讚
|
||||||
UpdateAt int64 `bson:"updateAt,omitempty" json:"updateAt,omitempty"`
|
UpdateAt int64 `bson:"updateAt,omitempty" json:"updateAt,omitempty"`
|
||||||
CreateAt int64 `bson:"createAt,omitempty" json:"createAt,omitempty"` // -> 排序條件
|
CreateAt int64 `bson:"createAt,omitempty" json:"createAt,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Media struct {
|
type Media struct {
|
||||||
|
@ -26,7 +31,3 @@ type Media struct {
|
||||||
func (p *Post) CollectionName() string {
|
func (p *Post) CollectionName() string {
|
||||||
return "post"
|
return "post"
|
||||||
}
|
}
|
||||||
|
|
||||||
// 照邏輯,應該需要建立的索引有
|
|
||||||
// 單列索引:UID、Status、IsAd、CreateAt
|
|
||||||
// 複合索引:(UID + Status + IsAd)、(UID + CreateAt)
|
|
||||||
|
|
|
@ -0,0 +1,25 @@
|
||||||
|
package model
|
||||||
|
|
||||||
|
import "github.com/zeromicro/go-zero/core/stores/mon"
|
||||||
|
|
||||||
|
var _ TagsModel = (*customTagsModel)(nil)
|
||||||
|
|
||||||
|
type (
|
||||||
|
// TagsModel is an interface to be customized, add more methods here,
|
||||||
|
// and implement the added methods in customTagsModel.
|
||||||
|
TagsModel interface {
|
||||||
|
tagsModel
|
||||||
|
}
|
||||||
|
|
||||||
|
customTagsModel struct {
|
||||||
|
*defaultTagsModel
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
// NewTagsModel returns a model for the mongo.
|
||||||
|
func NewTagsModel(url, db, collection string) TagsModel {
|
||||||
|
conn := mon.MustNewModel(url, db, collection)
|
||||||
|
return &customTagsModel{
|
||||||
|
defaultTagsModel: newDefaultTagsModel(conn),
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,74 @@
|
||||||
|
// Code generated by goctl. DO NOT EDIT.
|
||||||
|
package model
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/zeromicro/go-zero/core/stores/mon"
|
||||||
|
"go.mongodb.org/mongo-driver/bson"
|
||||||
|
"go.mongodb.org/mongo-driver/bson/primitive"
|
||||||
|
"go.mongodb.org/mongo-driver/mongo"
|
||||||
|
)
|
||||||
|
|
||||||
|
type tagsModel interface {
|
||||||
|
Insert(ctx context.Context, data *Tags) error
|
||||||
|
FindOne(ctx context.Context, id string) (*Tags, error)
|
||||||
|
Update(ctx context.Context, data *Tags) (*mongo.UpdateResult, error)
|
||||||
|
Delete(ctx context.Context, id string) (int64, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type defaultTagsModel struct {
|
||||||
|
conn *mon.Model
|
||||||
|
}
|
||||||
|
|
||||||
|
func newDefaultTagsModel(conn *mon.Model) *defaultTagsModel {
|
||||||
|
return &defaultTagsModel{conn: conn}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *defaultTagsModel) Insert(ctx context.Context, data *Tags) error {
|
||||||
|
if data.ID.IsZero() {
|
||||||
|
data.ID = primitive.NewObjectID()
|
||||||
|
data.CreateAt = time.Now()
|
||||||
|
data.UpdateAt = time.Now()
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := m.conn.InsertOne(ctx, data)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *defaultTagsModel) FindOne(ctx context.Context, id string) (*Tags, error) {
|
||||||
|
oid, err := primitive.ObjectIDFromHex(id)
|
||||||
|
if err != nil {
|
||||||
|
return nil, ErrInvalidObjectId
|
||||||
|
}
|
||||||
|
|
||||||
|
var data Tags
|
||||||
|
|
||||||
|
err = m.conn.FindOne(ctx, &data, bson.M{"_id": oid})
|
||||||
|
switch err {
|
||||||
|
case nil:
|
||||||
|
return &data, nil
|
||||||
|
case mon.ErrNotFound:
|
||||||
|
return nil, ErrNotFound
|
||||||
|
default:
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *defaultTagsModel) Update(ctx context.Context, data *Tags) (*mongo.UpdateResult, error) {
|
||||||
|
data.UpdateAt = time.Now()
|
||||||
|
|
||||||
|
res, err := m.conn.UpdateOne(ctx, bson.M{"_id": data.ID}, bson.M{"$set": data})
|
||||||
|
return res, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *defaultTagsModel) Delete(ctx context.Context, id string) (int64, error) {
|
||||||
|
oid, err := primitive.ObjectIDFromHex(id)
|
||||||
|
if err != nil {
|
||||||
|
return 0, ErrInvalidObjectId
|
||||||
|
}
|
||||||
|
|
||||||
|
res, err := m.conn.DeleteOne(ctx, bson.M{"_id": oid})
|
||||||
|
return res, err
|
||||||
|
}
|
|
@ -0,0 +1,14 @@
|
||||||
|
package model
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"go.mongodb.org/mongo-driver/bson/primitive"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Tags struct {
|
||||||
|
ID primitive.ObjectID `bson:"_id,omitempty" json:"id,omitempty"`
|
||||||
|
// TODO: Fill your own fields
|
||||||
|
UpdateAt time.Time `bson:"updateAt,omitempty" json:"updateAt,omitempty"`
|
||||||
|
CreateAt time.Time `bson:"createAt,omitempty" json:"createAt,omitempty"`
|
||||||
|
}
|
|
@ -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)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -23,7 +23,7 @@ func NewCommentServiceServer(svcCtx *svc.ServiceContext) *CommentServiceServer {
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewComment 發表評論
|
// NewComment 發表評論
|
||||||
func (s *CommentServiceServer) NewComment(ctx context.Context, in *tweeting.CommentPostReq) (*tweeting.CommentPostResp, error) {
|
func (s *CommentServiceServer) NewComment(ctx context.Context, in *tweeting.CommentPostReq) (*tweeting.OKResp, error) {
|
||||||
l := commentservicelogic.NewNewCommentLogic(ctx, s.svcCtx)
|
l := commentservicelogic.NewNewCommentLogic(ctx, s.svcCtx)
|
||||||
return l.NewComment(in)
|
return l.NewComment(in)
|
||||||
}
|
}
|
||||||
|
@ -45,3 +45,27 @@ func (s *CommentServiceServer) UpdateComment(ctx context.Context, in *tweeting.U
|
||||||
l := commentservicelogic.NewUpdateCommentLogic(ctx, s.svcCtx)
|
l := commentservicelogic.NewUpdateCommentLogic(ctx, s.svcCtx)
|
||||||
return l.UpdateComment(in)
|
return l.UpdateComment(in)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// LikeComment 點讚/取消讚 評論
|
||||||
|
func (s *CommentServiceServer) LikeComment(ctx context.Context, in *tweeting.LikeReq) (*tweeting.OKResp, error) {
|
||||||
|
l := commentservicelogic.NewLikeCommentLogic(ctx, s.svcCtx)
|
||||||
|
return l.LikeComment(in)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetLikeStatus 取得讚/不讚評論狀態
|
||||||
|
func (s *CommentServiceServer) GetLikeStatus(ctx context.Context, in *tweeting.LikeReq) (*tweeting.OKResp, error) {
|
||||||
|
l := commentservicelogic.NewGetLikeStatusLogic(ctx, s.svcCtx)
|
||||||
|
return l.GetLikeStatus(in)
|
||||||
|
}
|
||||||
|
|
||||||
|
// LikeList 取得讚/不讚評論列表
|
||||||
|
func (s *CommentServiceServer) LikeList(ctx context.Context, in *tweeting.LikeListReq) (*tweeting.LikeListResp, error) {
|
||||||
|
l := commentservicelogic.NewLikeListLogic(ctx, s.svcCtx)
|
||||||
|
return l.LikeList(in)
|
||||||
|
}
|
||||||
|
|
||||||
|
// CountLike 取得讚/不讚評論數量
|
||||||
|
func (s *CommentServiceServer) CountLike(ctx context.Context, in *tweeting.LikeCountReq) (*tweeting.LikeCountResp, error) {
|
||||||
|
l := commentservicelogic.NewCountLikeLogic(ctx, s.svcCtx)
|
||||||
|
return l.CountLike(in)
|
||||||
|
}
|
||||||
|
|
|
@ -22,10 +22,10 @@ func NewPostServiceServer(svcCtx *svc.ServiceContext) *PostServiceServer {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// CreatePost 新增貼文
|
// NewPost 新增貼文
|
||||||
func (s *PostServiceServer) CreatePost(ctx context.Context, in *tweeting.NewPostReq) (*tweeting.PostResp, error) {
|
func (s *PostServiceServer) NewPost(ctx context.Context, in *tweeting.NewPostReq) (*tweeting.PostResp, error) {
|
||||||
l := postservicelogic.NewCreatePostLogic(ctx, s.svcCtx)
|
l := postservicelogic.NewNewPostLogic(ctx, s.svcCtx)
|
||||||
return l.CreatePost(in)
|
return l.NewPost(in)
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeletePost 刪除貼文
|
// DeletePost 刪除貼文
|
||||||
|
@ -45,3 +45,33 @@ func (s *PostServiceServer) ListPosts(ctx context.Context, in *tweeting.QueryPos
|
||||||
l := postservicelogic.NewListPostsLogic(ctx, s.svcCtx)
|
l := postservicelogic.NewListPostsLogic(ctx, s.svcCtx)
|
||||||
return l.ListPosts(in)
|
return l.ListPosts(in)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// IncDecLikeDislikeCount 增減數量
|
||||||
|
func (s *PostServiceServer) IncDecLikeDislikeCount(ctx context.Context, in *tweeting.IncDecLikeDislikeCountReq) (*tweeting.OKResp, error) {
|
||||||
|
l := postservicelogic.NewIncDecLikeDislikeCountLogic(ctx, s.svcCtx)
|
||||||
|
return l.IncDecLikeDislikeCount(in)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Like 點讚/取消讚 貼文
|
||||||
|
func (s *PostServiceServer) Like(ctx context.Context, in *tweeting.LikeReq) (*tweeting.PostReactionActionResp, error) {
|
||||||
|
l := postservicelogic.NewLikeLogic(ctx, s.svcCtx)
|
||||||
|
return l.Like(in)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetLikeStatus 取得讚/不讚狀態
|
||||||
|
func (s *PostServiceServer) GetLikeStatus(ctx context.Context, in *tweeting.GetLikeStatusReq) (*tweeting.GetLikeStatusResp, error) {
|
||||||
|
l := postservicelogic.NewGetLikeStatusLogic(ctx, s.svcCtx)
|
||||||
|
return l.GetLikeStatus(in)
|
||||||
|
}
|
||||||
|
|
||||||
|
// LikeList 取得讚/不讚列表
|
||||||
|
func (s *PostServiceServer) LikeList(ctx context.Context, in *tweeting.LikeListReq) (*tweeting.LikeListResp, error) {
|
||||||
|
l := postservicelogic.NewLikeListLogic(ctx, s.svcCtx)
|
||||||
|
return l.LikeList(in)
|
||||||
|
}
|
||||||
|
|
||||||
|
// CountLike 取得讚/不讚數量
|
||||||
|
func (s *PostServiceServer) CountLike(ctx context.Context, in *tweeting.LikeCountReq) (*tweeting.LikeCountResp, error) {
|
||||||
|
l := postservicelogic.NewCountLikeLogic(ctx, s.svcCtx)
|
||||||
|
return l.CountLike(in)
|
||||||
|
}
|
||||||
|
|
|
@ -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)
|
|
||||||
}
|
|
|
@ -1,29 +0,0 @@
|
||||||
package svc
|
|
||||||
|
|
||||||
import (
|
|
||||||
"app-cloudep-tweeting-service/internal/config"
|
|
||||||
model "app-cloudep-tweeting-service/internal/model/mongo"
|
|
||||||
"fmt"
|
|
||||||
)
|
|
||||||
|
|
||||||
func mustMongoConnectURL(c config.Config) string {
|
|
||||||
return fmt.Sprintf("%s://%s:%s",
|
|
||||||
c.Mongo.Schema,
|
|
||||||
c.Mongo.Host,
|
|
||||||
c.Mongo.Port,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO 思考快取做在那邊
|
|
||||||
|
|
||||||
func MustPostModel(c config.Config) model.PostModel {
|
|
||||||
postCollection := model.Post{}
|
|
||||||
|
|
||||||
return model.NewPostModel(mustMongoConnectURL(c), c.Mongo.Database, postCollection.CollectionName())
|
|
||||||
}
|
|
||||||
|
|
||||||
func MustCommentModel(c config.Config) model.CommentModel {
|
|
||||||
m := model.Comment{}
|
|
||||||
|
|
||||||
return model.NewCommentModel(mustMongoConnectURL(c), c.Mongo.Database, m.CollectionName())
|
|
||||||
}
|
|
|
@ -2,15 +2,8 @@ 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"
|
"fmt"
|
||||||
|
|
||||||
ers "code.30cm.net/digimon/library-go/errs"
|
|
||||||
"code.30cm.net/digimon/library-go/errs/code"
|
|
||||||
|
|
||||||
"github.com/zeromicro/go-zero/core/stores/redis"
|
|
||||||
|
|
||||||
vi "code.30cm.net/digimon/library-go/validator"
|
vi "code.30cm.net/digimon/library-go/validator"
|
||||||
)
|
)
|
||||||
|
@ -18,40 +11,26 @@ import (
|
||||||
type ServiceContext struct {
|
type ServiceContext struct {
|
||||||
Config config.Config
|
Config config.Config
|
||||||
Validate vi.Validate
|
Validate vi.Validate
|
||||||
|
|
||||||
PostModel model.PostModel
|
PostModel model.PostModel
|
||||||
CommentModel model.CommentModel
|
PostLikeModel model.Post_likesModel
|
||||||
TimelineRepo domainRepo.TimelineRepository
|
|
||||||
SocialNetworkRepository domainRepo.SocialNetworkRepository
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewServiceContext(c config.Config) *ServiceContext {
|
func NewServiceContext(c config.Config) *ServiceContext {
|
||||||
ers.Scope = code.CloudEPTweeting
|
baseMongo := MustMongoConnectUrl(c)
|
||||||
newRedis, err := redis.NewRedis(c.RedisCluster, redis.Cluster())
|
postCollection := model.Post{}
|
||||||
if err != nil {
|
postLikeCollection := model.PostLikes{}
|
||||||
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: model.NewPostModel(baseMongo, c.Mongo.Database, postCollection.CollectionName(), c.Cache),
|
||||||
CommentModel: MustCommentModel(c),
|
PostLikeModel: model.NewPost_likesModel(baseMongo, c.Mongo.Database, postLikeCollection.CollectionName()),
|
||||||
TimelineRepo: repository.MustGenerateRepository(repository.TimelineRepositoryParam{
|
|
||||||
Config: c,
|
|
||||||
Redis: *newRedis,
|
|
||||||
}),
|
|
||||||
SocialNetworkRepository: repository.MustSocialNetworkRepository(repository.SocialNetworkParam{
|
|
||||||
Config: c,
|
|
||||||
Neo4jClient: neoClient,
|
|
||||||
}),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func MustMongoConnectUrl(c config.Config) string {
|
||||||
|
return fmt.Sprintf(
|
||||||
|
"%s://%s:%s", c.Mongo.Schema,
|
||||||
|
c.Mongo.Host, c.Mongo.Port)
|
||||||
|
}
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue