將 post service 寫完加上測試
This commit is contained in:
parent
c99ac1a6b3
commit
f0816e0c93
20
Makefile
20
Makefile
|
@ -49,9 +49,19 @@ build-docker:
|
||||||
|
|
||||||
gen-mongo-model: # 建立 rpc 資料庫
|
gen-mongo-model: # 建立 rpc 資料庫
|
||||||
# 只產生 Model 剩下的要自己撰寫,連欄位名稱也是
|
# 只產生 Model 剩下的要自己撰寫,連欄位名稱也是
|
||||||
goctl model mongo -c no -t post --dir ./internal/model/mongo --style $(GO_ZERO_STYLE)
|
goctl model mongo -t post --dir ./internal/model/mongo --style $(GO_ZERO_STYLE)
|
||||||
goctl model mongo -c no -t comment --dir ./internal/model/mongo --style $(GO_ZERO_STYLE)
|
goctl model mongo -t comment --dir ./internal/model/mongo --style $(GO_ZERO_STYLE)
|
||||||
goctl model mongo -t tags --dir ./internal/model/mongo --style $(GO_ZERO_STYLE)
|
# goctl model mongo -t tags --dir ./internal/model/mongo --style $(GO_ZERO_STYLE)
|
||||||
goctl model mongo -t post_likes --dir ./internal/model/mongo --style $(GO_ZERO_STYLE)
|
# goctl model mongo -t post_likes --dir ./internal/model/mongo --style $(GO_ZERO_STYLE)
|
||||||
goctl model mongo -t comment_likes --dir ./internal/model/mongo --style $(GO_ZERO_STYLE)
|
# goctl model mongo -t comment_likes --dir ./internal/model/mongo --style $(GO_ZERO_STYLE)
|
||||||
@echo "Generate mongo model files successfully"
|
@echo "Generate mongo model files successfully"
|
||||||
|
|
||||||
|
.PHONY: mock-gen
|
||||||
|
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
|
||||||
|
@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
|
||||||
|
|
|
@ -0,0 +1,14 @@
|
||||||
|
Name: tweeting.rpc
|
||||||
|
ListenOn: 0.0.0.0:8080
|
||||||
|
Etcd:
|
||||||
|
Hosts:
|
||||||
|
- 127.0.0.1:2379
|
||||||
|
Key: tweeting.rpc
|
||||||
|
|
||||||
|
Mongo:
|
||||||
|
Schema: mongodb
|
||||||
|
Host: 127.0.0.1
|
||||||
|
User: ""
|
||||||
|
Password: ""
|
||||||
|
Port: "27017"
|
||||||
|
Database: digimon_tweeting
|
|
@ -0,0 +1,9 @@
|
||||||
|
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 看是否有要刪除過多的索引,要在測試一下
|
|
@ -1,8 +1,8 @@
|
||||||
syntax = "proto3";
|
syntax = "proto3";
|
||||||
package tweeting;
|
package tweeting;
|
||||||
option go_package="./tweeting";
|
option go_package = "./tweeting";
|
||||||
|
|
||||||
// 基本回應
|
// ========== 基本回應 ===========
|
||||||
message OKResp {}
|
message OKResp {}
|
||||||
|
|
||||||
// 空的請求
|
// 空的請求
|
||||||
|
@ -10,57 +10,67 @@ message NoneReq {}
|
||||||
|
|
||||||
// 分頁信息
|
// 分頁信息
|
||||||
message Pager {
|
message Pager {
|
||||||
int64 total =1; // 總數量
|
int64 total = 1; // 總數量
|
||||||
int64 size=2; // 每頁數量
|
int64 size = 2; // 每頁數量
|
||||||
int64 index=3; // 當前頁碼
|
int64 index = 3; // 當前頁碼
|
||||||
}
|
}
|
||||||
|
|
||||||
// 新增貼文的請求
|
// ========== 貼文區 ===========
|
||||||
|
|
||||||
|
// ------ NewPost 新增貼文--------
|
||||||
message NewPostReq {
|
message NewPostReq {
|
||||||
int64 user_id = 1; // 發佈貼文的用戶ID
|
string uid = 1; // 發佈貼文的用戶ID
|
||||||
string content = 2; // 貼文內容
|
string content = 2; // 貼文內容
|
||||||
repeated string tags = 3; // 貼文相關標籤
|
repeated string tags = 3; // 貼文相關標籤
|
||||||
repeated string media_url = 4; // 這筆文章的所有 Media URL
|
repeated Media media = 4; // 這筆文章的所有 Media URL
|
||||||
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 string media_url = 3; // 這筆文章的所有 Media URL
|
repeated Media media = 3; // 這筆文章的所有 Media URL
|
||||||
optional string content = 4; // 新的貼文內容
|
optional string content = 4; // 新的貼文內容
|
||||||
optional int64 like_count = 5; // 喜歡數量
|
optional int64 like_count = 5; // 喜歡數量
|
||||||
optional int64 dislike_count = 6; // 不喜歡數量
|
optional int64 dislike_count = 6; // 不喜歡數量
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ------ListPosts 查詢貼文 ------
|
||||||
// 查詢貼文的請求
|
// 查詢貼文的請求
|
||||||
message QueryPostsReq {
|
message QueryPostsReq {
|
||||||
repeated int64 user_id = 1; // 可選:根據用戶ID篩選貼文
|
repeated string uid = 1; // 可選:根據用戶ID篩選貼文
|
||||||
repeated int64 id = 2; // 可選:根據貼文ID篩選貼文
|
repeated string post_id = 2; // 可選:根據貼文ID篩選貼文
|
||||||
repeated string tags = 3; // 可選:根據標籤篩選貼文
|
optional int32 only_ads = 3; // 可選:是否只顯示廣告 0 不篩選 1 只顯示廣告 2 不顯示廣告
|
||||||
optional bool only_ads = 4; // 可選:是否只顯示廣告
|
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
|
||||||
int64 user_id = 2; // 發佈用戶ID
|
string uid = 2; // 發佈用戶ID
|
||||||
string content = 3; // 貼文內容
|
string content = 3; // 貼文內容
|
||||||
repeated string tags = 4; // 標籤
|
repeated string tags = 4; // 標籤
|
||||||
repeated string media_url = 5; // 圖片URL
|
repeated Media media = 5; // 圖片URL
|
||||||
bool is_ad = 6; // 是否為廣告
|
bool is_ad = 6; // 是否為廣告
|
||||||
int64 created_at = 7; // 發佈時間
|
int64 created_at = 7; // 發佈時間
|
||||||
int64 update_at = 8; // 更新時間
|
int64 update_at = 8; // 更新時間
|
||||||
|
@ -71,127 +81,24 @@ 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)
|
|
||||||
int64 user_id = 2; // 點讚的用戶ID
|
|
||||||
int64 like_type = 3; // 讚或爛的類型
|
|
||||||
}
|
|
||||||
|
|
||||||
// 讚/不讚項目
|
|
||||||
message LikeItem {
|
|
||||||
string target_id = 1; // 目標ID(可以是貼文ID或評論ID)
|
|
||||||
int64 user_id = 2; // 點讚的用戶ID
|
|
||||||
int64 like_type = 3; // 讚或爛的類型
|
|
||||||
}
|
|
||||||
|
|
||||||
// 讚/不讚列表請求
|
|
||||||
message LikeListReq {
|
|
||||||
string target_id = 1; // 目標ID(可以是貼文ID或評論ID)
|
|
||||||
int64 like_type = 2; // 讚或爛的類型
|
|
||||||
int32 page_index = 3; // 當前頁碼
|
|
||||||
int32 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; // 異動數量
|
||||||
}
|
}
|
||||||
|
|
||||||
// 讚/不讚數量回應
|
// ========== 定義貼文服務(最基本單位,不要把邏輯放進來,也考慮是否要做快取) ==========
|
||||||
message LikeCountResp {
|
|
||||||
string count = 1; // 總共按讚數量
|
|
||||||
}
|
|
||||||
|
|
||||||
// 評論貼文的請求
|
|
||||||
message CommentPostReq {
|
|
||||||
string post_id = 1; // 貼文ID
|
|
||||||
int64 user_id = 2; // 評論者ID
|
|
||||||
string content = 3; // 評論內容
|
|
||||||
}
|
|
||||||
|
|
||||||
// 查詢評論的請求
|
|
||||||
message GetCommentsReq {
|
|
||||||
string post_id = 1; // 貼文ID
|
|
||||||
int32 page_index = 2; // 分頁頁碼
|
|
||||||
int32 page_size = 3; // 每頁顯示數量
|
|
||||||
}
|
|
||||||
|
|
||||||
// 評論詳情
|
|
||||||
message CommentDetail {
|
|
||||||
string comment_id = 1; // 評論ID
|
|
||||||
int64 user_id = 2; // 評論者ID
|
|
||||||
string content = 3; // 評論內容
|
|
||||||
int64 created_at = 4; // 創建時間
|
|
||||||
int64 like_count = 5; // 讚數
|
|
||||||
int64 dislike_count = 6; // 不喜歡數量
|
|
||||||
}
|
|
||||||
|
|
||||||
// 評論列表回應
|
|
||||||
message GetCommentsResp {
|
|
||||||
repeated CommentDetail comments = 1; // 評論列表
|
|
||||||
Pager page = 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 刪除評論請求
|
|
||||||
message DeleteCommentReq {
|
|
||||||
string comment_id = 1; // 評論ID
|
|
||||||
}
|
|
||||||
|
|
||||||
// 更新評論請求
|
|
||||||
message UpdateCommentReq {
|
|
||||||
string comment_id = 1; // 評論ID
|
|
||||||
string content = 2; // 更新後的評論內容
|
|
||||||
}
|
|
||||||
|
|
||||||
// 定義貼文服務
|
|
||||||
service PostService {
|
service PostService {
|
||||||
// NewPost 新增貼文
|
// CreatePost 新增貼文
|
||||||
rpc NewPost(NewPostReq) returns(PostResp);
|
rpc CreatePost(NewPostReq) returns (PostResp);
|
||||||
// DeletePost 刪除貼文
|
// DeletePost 刪除貼文
|
||||||
rpc DeletePost(DeletePostsReq) returns (OKResp);
|
rpc DeletePost(DeletePostsReq) returns (OKResp);
|
||||||
// UpdatePost 更新貼文
|
// UpdatePost 更新貼文
|
||||||
rpc UpdatePost(UpdatePostReq) returns (OKResp);
|
rpc UpdatePost(UpdatePostReq) returns (OKResp);
|
||||||
// ListPosts 查詢貼文
|
// ListPosts 查詢貼文
|
||||||
rpc ListPosts(QueryPostsReq) returns (ListPostsResp);
|
rpc ListPosts(QueryPostsReq) returns (ListPostsResp);
|
||||||
|
|
||||||
// Like 點讚/取消讚 貼文
|
|
||||||
rpc Like(LikeReq) returns (OKResp);
|
|
||||||
// GetLikeStatus 取得讚/不讚狀態
|
|
||||||
rpc GetLikeStatus(LikeReq) returns (OKResp);
|
|
||||||
// LikeList 取得讚/不讚列表
|
|
||||||
rpc LikeList(LikeListReq) returns (LikeListResp);
|
|
||||||
// CountLike 取得讚/不讚數量
|
|
||||||
rpc CountLike(LikeCountReq) returns (LikeCountResp);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 定義評論服務
|
|
||||||
service CommentService {
|
|
||||||
// NewComment 發表評論
|
|
||||||
rpc NewComment(CommentPostReq) returns (OKResp);
|
|
||||||
// GetComments 查詢評論
|
|
||||||
rpc GetComments(GetCommentsReq) returns (GetCommentsResp);
|
|
||||||
// DeleteComment 刪除評論
|
|
||||||
rpc DeleteComment(DeleteCommentReq) returns (OKResp);
|
|
||||||
// UpdateComment 更新評論
|
|
||||||
rpc UpdateComment(UpdateCommentReq) returns (OKResp);
|
|
||||||
|
|
||||||
// LikeComment 點讚/取消讚 評論
|
|
||||||
rpc LikeComment(LikeReq) returns (OKResp);
|
|
||||||
// GetLikeStatus 取得讚/不讚評論狀態
|
|
||||||
rpc GetLikeStatus(LikeReq) returns (OKResp);
|
|
||||||
// LikeList 取得讚/不讚評論列表
|
|
||||||
rpc LikeList(LikeListReq) returns (LikeListResp);
|
|
||||||
// CountLike 取得讚/不讚評論數量
|
|
||||||
rpc CountLike(LikeCountReq) returns (LikeCountResp);
|
|
||||||
}
|
}
|
|
@ -0,0 +1,108 @@
|
||||||
|
module app-cloudep-tweeting-service
|
||||||
|
|
||||||
|
go 1.22.3
|
||||||
|
|
||||||
|
require (
|
||||||
|
code.30cm.net/digimon/library-go/errs v1.2.4
|
||||||
|
code.30cm.net/digimon/library-go/validator v1.0.0
|
||||||
|
github.com/stretchr/testify v1.9.0
|
||||||
|
github.com/zeromicro/go-zero v1.7.0
|
||||||
|
go.mongodb.org/mongo-driver v1.16.0
|
||||||
|
go.uber.org/mock v0.4.0
|
||||||
|
google.golang.org/grpc v1.66.0
|
||||||
|
google.golang.org/protobuf v1.34.2
|
||||||
|
)
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/beorn7/perks v1.0.1 // indirect
|
||||||
|
github.com/cenkalti/backoff/v4 v4.3.0 // indirect
|
||||||
|
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||||
|
github.com/coreos/go-semver v0.3.1 // indirect
|
||||||
|
github.com/coreos/go-systemd/v22 v22.5.0 // indirect
|
||||||
|
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||||
|
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
|
||||||
|
github.com/emicklei/go-restful/v3 v3.11.0 // indirect
|
||||||
|
github.com/fatih/color v1.17.0 // indirect
|
||||||
|
github.com/gabriel-vasile/mimetype v1.4.3 // indirect
|
||||||
|
github.com/go-logr/logr v1.4.2 // indirect
|
||||||
|
github.com/go-logr/stdr v1.2.2 // indirect
|
||||||
|
github.com/go-openapi/jsonpointer v0.19.6 // indirect
|
||||||
|
github.com/go-openapi/jsonreference v0.20.2 // indirect
|
||||||
|
github.com/go-openapi/swag v0.22.4 // indirect
|
||||||
|
github.com/go-playground/locales v0.14.1 // indirect
|
||||||
|
github.com/go-playground/universal-translator v0.18.1 // indirect
|
||||||
|
github.com/go-playground/validator/v10 v10.22.0 // indirect
|
||||||
|
github.com/gogo/protobuf v1.3.2 // indirect
|
||||||
|
github.com/golang/mock v1.6.0 // indirect
|
||||||
|
github.com/golang/protobuf v1.5.4 // indirect
|
||||||
|
github.com/golang/snappy v0.0.4 // indirect
|
||||||
|
github.com/google/gnostic-models v0.6.8 // indirect
|
||||||
|
github.com/google/go-cmp v0.6.0 // indirect
|
||||||
|
github.com/google/gofuzz v1.2.0 // indirect
|
||||||
|
github.com/google/uuid v1.6.0 // indirect
|
||||||
|
github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0 // indirect
|
||||||
|
github.com/josharian/intern v1.0.0 // indirect
|
||||||
|
github.com/json-iterator/go v1.1.12 // indirect
|
||||||
|
github.com/klauspost/compress v1.17.8 // indirect
|
||||||
|
github.com/leodido/go-urn v1.4.0 // indirect
|
||||||
|
github.com/mailru/easyjson v0.7.7 // indirect
|
||||||
|
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||||
|
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||||
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||||
|
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||||
|
github.com/montanaflynn/stats v0.7.1 // indirect
|
||||||
|
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
|
||||||
|
github.com/openzipkin/zipkin-go v0.4.3 // indirect
|
||||||
|
github.com/pelletier/go-toml/v2 v2.2.2 // indirect
|
||||||
|
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||||
|
github.com/prometheus/client_golang v1.19.1 // indirect
|
||||||
|
github.com/prometheus/client_model v0.5.0 // indirect
|
||||||
|
github.com/prometheus/common v0.48.0 // indirect
|
||||||
|
github.com/prometheus/procfs v0.12.0 // indirect
|
||||||
|
github.com/redis/go-redis/v9 v9.6.1 // indirect
|
||||||
|
github.com/spaolacci/murmur3 v1.1.0 // indirect
|
||||||
|
github.com/xdg-go/pbkdf2 v1.0.0 // indirect
|
||||||
|
github.com/xdg-go/scram v1.1.2 // indirect
|
||||||
|
github.com/xdg-go/stringprep v1.0.4 // indirect
|
||||||
|
github.com/youmark/pkcs8 v0.0.0-20201027041543-1326539a0a0a // indirect
|
||||||
|
go.etcd.io/etcd/api/v3 v3.5.15 // indirect
|
||||||
|
go.etcd.io/etcd/client/pkg/v3 v3.5.15 // indirect
|
||||||
|
go.etcd.io/etcd/client/v3 v3.5.15 // indirect
|
||||||
|
go.opentelemetry.io/otel v1.24.0 // indirect
|
||||||
|
go.opentelemetry.io/otel/exporters/jaeger v1.17.0 // indirect
|
||||||
|
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.24.0 // indirect
|
||||||
|
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.24.0 // indirect
|
||||||
|
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.24.0 // indirect
|
||||||
|
go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.24.0 // indirect
|
||||||
|
go.opentelemetry.io/otel/exporters/zipkin v1.24.0 // indirect
|
||||||
|
go.opentelemetry.io/otel/metric v1.24.0 // indirect
|
||||||
|
go.opentelemetry.io/otel/sdk v1.24.0 // indirect
|
||||||
|
go.opentelemetry.io/otel/trace v1.24.0 // indirect
|
||||||
|
go.opentelemetry.io/proto/otlp v1.3.1 // indirect
|
||||||
|
go.uber.org/atomic v1.10.0 // indirect
|
||||||
|
go.uber.org/automaxprocs v1.5.3 // indirect
|
||||||
|
go.uber.org/multierr v1.9.0 // indirect
|
||||||
|
go.uber.org/zap v1.24.0 // indirect
|
||||||
|
golang.org/x/crypto v0.25.0 // indirect
|
||||||
|
golang.org/x/net v0.27.0 // indirect
|
||||||
|
golang.org/x/oauth2 v0.21.0 // indirect
|
||||||
|
golang.org/x/sync v0.7.0 // indirect
|
||||||
|
golang.org/x/sys v0.22.0 // indirect
|
||||||
|
golang.org/x/term v0.22.0 // indirect
|
||||||
|
golang.org/x/text v0.16.0 // indirect
|
||||||
|
golang.org/x/time v0.5.0 // indirect
|
||||||
|
google.golang.org/genproto/googleapis/api v0.0.0-20240711142825-46eb208f015d // indirect
|
||||||
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20240701130421-f6361c86f094 // indirect
|
||||||
|
gopkg.in/inf.v0 v0.9.1 // indirect
|
||||||
|
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||||
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
|
k8s.io/api v0.29.3 // indirect
|
||||||
|
k8s.io/apimachinery v0.29.4 // indirect
|
||||||
|
k8s.io/client-go v0.29.3 // indirect
|
||||||
|
k8s.io/klog/v2 v2.110.1 // indirect
|
||||||
|
k8s.io/kube-openapi v0.0.0-20231010175941-2dd684a91f00 // indirect
|
||||||
|
k8s.io/utils v0.0.0-20240711033017-18e509b52bc8 // indirect
|
||||||
|
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect
|
||||||
|
sigs.k8s.io/structured-merge-diff/v4 v4.4.1 // indirect
|
||||||
|
sigs.k8s.io/yaml v1.3.0 // indirect
|
||||||
|
)
|
|
@ -0,0 +1,15 @@
|
||||||
|
package config
|
||||||
|
|
||||||
|
import "github.com/zeromicro/go-zero/zrpc"
|
||||||
|
|
||||||
|
type Config struct {
|
||||||
|
zrpc.RpcServerConf
|
||||||
|
Mongo struct {
|
||||||
|
Schema string
|
||||||
|
User string
|
||||||
|
Password string
|
||||||
|
Host string
|
||||||
|
Port string
|
||||||
|
Database string
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,13 @@
|
||||||
|
package domain
|
||||||
|
|
||||||
|
type AdType int32
|
||||||
|
|
||||||
|
func (a AdType) ToInt32() int32 {
|
||||||
|
return int32(a)
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
AdTypeAll AdType = iota
|
||||||
|
AdTypeOnlyAd
|
||||||
|
AdTypeOnlyNotAd
|
||||||
|
)
|
|
@ -0,0 +1,17 @@
|
||||||
|
package domain
|
||||||
|
|
||||||
|
type ErrorCode uint32
|
||||||
|
|
||||||
|
func (e ErrorCode) ToUint32() uint32 {
|
||||||
|
return uint32(e)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Error Code 統一這邊改
|
||||||
|
const (
|
||||||
|
_ = iota
|
||||||
|
PostMongoErrorCode ErrorCode = iota
|
||||||
|
CreatePostError
|
||||||
|
DelPostError
|
||||||
|
UpdatePostError
|
||||||
|
ListPostError
|
||||||
|
)
|
|
@ -0,0 +1,12 @@
|
||||||
|
package domain
|
||||||
|
|
||||||
|
type TweetingStatus int8
|
||||||
|
|
||||||
|
func (t TweetingStatus) ToInt8() int8 {
|
||||||
|
return int8(t)
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
TweetingStatusNotReviewedYet TweetingStatus = iota + 1
|
||||||
|
TweetingStatusPass
|
||||||
|
)
|
|
@ -0,0 +1,114 @@
|
||||||
|
package postservicelogic
|
||||||
|
|
||||||
|
import (
|
||||||
|
"app-cloudep-tweeting-service/internal/domain"
|
||||||
|
model "app-cloudep-tweeting-service/internal/model/mongo"
|
||||||
|
|
||||||
|
"code.30cm.net/digimon/library-go/errs/code"
|
||||||
|
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"app-cloudep-tweeting-service/gen_result/pb/tweeting"
|
||||||
|
"app-cloudep-tweeting-service/internal/svc"
|
||||||
|
|
||||||
|
ers "code.30cm.net/digimon/library-go/errs"
|
||||||
|
"github.com/zeromicro/go-zero/core/logx"
|
||||||
|
)
|
||||||
|
|
||||||
|
type CreatePostLogic struct {
|
||||||
|
ctx context.Context
|
||||||
|
svcCtx *svc.ServiceContext
|
||||||
|
logx.Logger
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewCreatePostLogic(ctx context.Context, svcCtx *svc.ServiceContext) *CreatePostLogic {
|
||||||
|
return &CreatePostLogic{
|
||||||
|
ctx: ctx,
|
||||||
|
svcCtx: svcCtx,
|
||||||
|
Logger: logx.WithContext(ctx),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO 要調查一下內容如果存 html 是否有需要Encode
|
||||||
|
// 輸入的定義 -> 檢查用
|
||||||
|
type newTweetingReq struct {
|
||||||
|
UID string `json:"uid" validate:"required"`
|
||||||
|
Content string `json:"content" validate:"required,lte=500"` // 貼文限制 500 字內
|
||||||
|
Tags []string `json:"tags"`
|
||||||
|
MediaUrl []string `json:"media_url"`
|
||||||
|
IsAd bool `json:"is_ad"` // default false
|
||||||
|
}
|
||||||
|
|
||||||
|
// 定義 Error
|
||||||
|
|
||||||
|
// CreatePostError 0502102 資料庫錯誤
|
||||||
|
func CreatePostError(s ...string) *ers.LibError {
|
||||||
|
return ers.NewError(code.CloudEPTweeting, code.DBError,
|
||||||
|
domain.CreatePostError.ToUint32(),
|
||||||
|
fmt.Sprintf("%s", strings.Join(s, " ")))
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreatePostErrorL logs error message and returns Error
|
||||||
|
func CreatePostErrorL(l logx.Logger, filed []logx.LogField, s ...string) *ers.LibError {
|
||||||
|
e := CreatePostError(s...)
|
||||||
|
l.WithCallerSkip(1).WithFields(filed...).Error(e.Error())
|
||||||
|
|
||||||
|
return e
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreatePost 新增貼文
|
||||||
|
func (l *CreatePostLogic) CreatePost(in *tweeting.NewPostReq) (*tweeting.PostResp, error) {
|
||||||
|
// 驗證資料
|
||||||
|
if err := l.svcCtx.Validate.ValidateAll(&newTweetingReq{
|
||||||
|
UID: in.GetUid(),
|
||||||
|
Content: in.GetContent(),
|
||||||
|
}); err != nil {
|
||||||
|
// 錯誤代碼 05-011-00
|
||||||
|
return nil, ers.InvalidFormat(err.Error())
|
||||||
|
}
|
||||||
|
// ============ prepare ============
|
||||||
|
// 新增資料
|
||||||
|
tweet := &model.Post{
|
||||||
|
UID: in.GetUid(),
|
||||||
|
Content: in.GetContent(),
|
||||||
|
Status: domain.TweetingStatusNotReviewedYet.ToInt8(),
|
||||||
|
IsAd: in.IsAd,
|
||||||
|
}
|
||||||
|
if len(in.GetTags()) > 0 {
|
||||||
|
// 存在貼文內的不提供搜尋,純顯示用,只不過在原始的tag 發生變動的時候,並不會一起改變
|
||||||
|
// 搜尋會貼文與Tag 的表會再另外一邊做關聯
|
||||||
|
// 暫時業務邏輯上tag 只提供新增,不提供修改以及刪除,故目前版本可行
|
||||||
|
tweet.Tags = in.GetTags()
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(in.Media) > 0 {
|
||||||
|
// 將 Media 存入
|
||||||
|
var media []model.Media
|
||||||
|
for _, item := range in.GetMedia() {
|
||||||
|
media = append(media, model.Media{
|
||||||
|
Links: item.Url,
|
||||||
|
Type: item.Type,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
tweet.MediaURL = media
|
||||||
|
}
|
||||||
|
// ============ insert ============
|
||||||
|
err := l.svcCtx.PostModel.Insert(l.ctx, tweet)
|
||||||
|
if err != nil {
|
||||||
|
e := CreatePostErrorL(
|
||||||
|
logx.WithContext(l.ctx),
|
||||||
|
[]logx.LogField{
|
||||||
|
{Key: "req", Value: in},
|
||||||
|
{Key: "func", Value: "PostModel.Insert"},
|
||||||
|
{Key: "err", Value: err},
|
||||||
|
},
|
||||||
|
"failed to add new post").Wrap(err)
|
||||||
|
return nil, e
|
||||||
|
}
|
||||||
|
|
||||||
|
return &tweeting.PostResp{
|
||||||
|
PostId: tweet.ID.Hex(),
|
||||||
|
}, nil
|
||||||
|
}
|
|
@ -0,0 +1,107 @@
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,63 @@
|
||||||
|
package postservicelogic
|
||||||
|
|
||||||
|
import (
|
||||||
|
"app-cloudep-tweeting-service/internal/domain"
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
ers "code.30cm.net/digimon/library-go/errs"
|
||||||
|
"code.30cm.net/digimon/library-go/errs/code"
|
||||||
|
|
||||||
|
"app-cloudep-tweeting-service/gen_result/pb/tweeting"
|
||||||
|
"app-cloudep-tweeting-service/internal/svc"
|
||||||
|
|
||||||
|
"github.com/zeromicro/go-zero/core/logx"
|
||||||
|
)
|
||||||
|
|
||||||
|
type DeletePostLogic struct {
|
||||||
|
ctx context.Context
|
||||||
|
svcCtx *svc.ServiceContext
|
||||||
|
logx.Logger
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewDeletePostLogic(ctx context.Context, svcCtx *svc.ServiceContext) *DeletePostLogic {
|
||||||
|
return &DeletePostLogic{
|
||||||
|
ctx: ctx,
|
||||||
|
svcCtx: svcCtx,
|
||||||
|
Logger: logx.WithContext(ctx),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeletePostError 0502103 資料庫錯誤
|
||||||
|
func DeletePostError(s ...string) *ers.LibError {
|
||||||
|
return ers.NewError(code.CloudEPTweeting, code.DBError,
|
||||||
|
domain.DelPostError.ToUint32(),
|
||||||
|
fmt.Sprintf("%s", strings.Join(s, " ")))
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeletePostErrorL logs error message and returns Error
|
||||||
|
func DeletePostErrorL(l logx.Logger, filed []logx.LogField, s ...string) *ers.LibError {
|
||||||
|
e := DeletePostError(s...)
|
||||||
|
l.WithCallerSkip(1).WithFields(filed...).Error(e.Error())
|
||||||
|
|
||||||
|
return e
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeletePost 刪除貼文
|
||||||
|
func (l *DeletePostLogic) DeletePost(in *tweeting.DeletePostsReq) (*tweeting.OKResp, error) {
|
||||||
|
_, err := l.svcCtx.PostModel.DeleteMany(l.ctx, in.GetPostId()...)
|
||||||
|
if err != nil {
|
||||||
|
e := DeletePostErrorL(
|
||||||
|
logx.WithContext(l.ctx),
|
||||||
|
[]logx.LogField{
|
||||||
|
{Key: "req", Value: in},
|
||||||
|
{Key: "func", Value: "PostModel.DeleteMany"},
|
||||||
|
{Key: "err", Value: err},
|
||||||
|
},
|
||||||
|
"failed to del post").Wrap(err)
|
||||||
|
return nil, e
|
||||||
|
}
|
||||||
|
|
||||||
|
return &tweeting.OKResp{}, nil
|
||||||
|
}
|
|
@ -0,0 +1,81 @@
|
||||||
|
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,139 @@
|
||||||
|
package postservicelogic
|
||||||
|
|
||||||
|
import (
|
||||||
|
"app-cloudep-tweeting-service/internal/domain"
|
||||||
|
model "app-cloudep-tweeting-service/internal/model/mongo"
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
ers "code.30cm.net/digimon/library-go/errs"
|
||||||
|
"code.30cm.net/digimon/library-go/errs/code"
|
||||||
|
"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"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ListPostsLogic struct {
|
||||||
|
ctx context.Context
|
||||||
|
svcCtx *svc.ServiceContext
|
||||||
|
logx.Logger
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewListPostsLogic(ctx context.Context, svcCtx *svc.ServiceContext) *ListPostsLogic {
|
||||||
|
return &ListPostsLogic{
|
||||||
|
ctx: ctx,
|
||||||
|
svcCtx: svcCtx,
|
||||||
|
Logger: logx.WithContext(ctx),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 只列出要驗證的資料
|
||||||
|
type listReq struct {
|
||||||
|
OnlyAdds int32 `json:"only_adds" validate:"oneof=0 1 2 3"`
|
||||||
|
PageSize int64 `json:"page_size" validate:"required"`
|
||||||
|
PageIndex int64 `json:"page_index" validate:"required"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListPostError 0502105 資料庫錯誤
|
||||||
|
func ListPostError(s ...string) *ers.LibError {
|
||||||
|
return ers.NewError(code.CloudEPTweeting, code.DBError,
|
||||||
|
domain.ListPostError.ToUint32(),
|
||||||
|
fmt.Sprintf("%s", strings.Join(s, " ")))
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListPostErrorL logs error message and returns Error
|
||||||
|
func ListPostErrorL(l logx.Logger, filed []logx.LogField, s ...string) *ers.LibError {
|
||||||
|
e := ListPostError(s...)
|
||||||
|
l.WithCallerSkip(1).WithFields(filed...).Error(e.Error())
|
||||||
|
|
||||||
|
return e
|
||||||
|
}
|
||||||
|
|
||||||
|
// 將單個 Post 轉換為 PostDetailItem
|
||||||
|
func convertToPostDetailItem(item *model.Post) *tweeting.PostDetailItem {
|
||||||
|
media := make([]*tweeting.Media, 0, len(item.MediaURL))
|
||||||
|
for _, subItem := range item.MediaURL {
|
||||||
|
media = append(media, &tweeting.Media{
|
||||||
|
Type: subItem.Type,
|
||||||
|
Url: subItem.Links,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return &tweeting.PostDetailItem{
|
||||||
|
PostId: item.ID.Hex(),
|
||||||
|
Uid: item.UID,
|
||||||
|
Content: item.Content,
|
||||||
|
Tags: item.Tags,
|
||||||
|
Media: media,
|
||||||
|
IsAd: item.IsAd,
|
||||||
|
CreatedAt: item.CreateAt,
|
||||||
|
UpdateAt: item.UpdateAt,
|
||||||
|
LikeCount: int64(item.Like),
|
||||||
|
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 {
|
||||||
|
e := ListPostErrorL(
|
||||||
|
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{
|
||||||
|
Posts: result,
|
||||||
|
Page: &tweeting.Pager{
|
||||||
|
Total: count,
|
||||||
|
Index: pageIndex,
|
||||||
|
Size: pageSize,
|
||||||
|
},
|
||||||
|
}, nil
|
||||||
|
}
|
|
@ -0,0 +1,164 @@
|
||||||
|
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))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,100 @@
|
||||||
|
package postservicelogic
|
||||||
|
|
||||||
|
import (
|
||||||
|
"app-cloudep-tweeting-service/internal/domain"
|
||||||
|
model "app-cloudep-tweeting-service/internal/model/mongo"
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
ers "code.30cm.net/digimon/library-go/errs"
|
||||||
|
"code.30cm.net/digimon/library-go/errs/code"
|
||||||
|
"go.mongodb.org/mongo-driver/bson/primitive"
|
||||||
|
|
||||||
|
"app-cloudep-tweeting-service/gen_result/pb/tweeting"
|
||||||
|
"app-cloudep-tweeting-service/internal/svc"
|
||||||
|
|
||||||
|
"github.com/zeromicro/go-zero/core/logx"
|
||||||
|
)
|
||||||
|
|
||||||
|
type UpdatePostLogic struct {
|
||||||
|
ctx context.Context
|
||||||
|
svcCtx *svc.ServiceContext
|
||||||
|
logx.Logger
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewUpdatePostLogic(ctx context.Context, svcCtx *svc.ServiceContext) *UpdatePostLogic {
|
||||||
|
return &UpdatePostLogic{
|
||||||
|
ctx: ctx,
|
||||||
|
svcCtx: svcCtx,
|
||||||
|
Logger: logx.WithContext(ctx),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type checkPostId struct {
|
||||||
|
PostID string `validate:"required"`
|
||||||
|
Content string `json:"content,omitempty" validate:"lte=500"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdatePostError 0502104 資料庫錯誤
|
||||||
|
func UpdatePostError(s ...string) *ers.LibError {
|
||||||
|
return ers.NewError(code.CloudEPTweeting, code.DBError,
|
||||||
|
domain.UpdatePostError.ToUint32(),
|
||||||
|
fmt.Sprintf("%s", strings.Join(s, " ")))
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdatePostErrorL logs error message and returns Error
|
||||||
|
func UpdatePostErrorL(l logx.Logger, filed []logx.LogField, s ...string) *ers.LibError {
|
||||||
|
e := UpdatePostError(s...)
|
||||||
|
l.WithCallerSkip(1).WithFields(filed...).Error(e.Error())
|
||||||
|
|
||||||
|
return e
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdatePost 更新貼文
|
||||||
|
func (l *UpdatePostLogic) UpdatePost(in *tweeting.UpdatePostReq) (*tweeting.OKResp, error) {
|
||||||
|
// 驗證資料
|
||||||
|
if err := l.svcCtx.Validate.ValidateAll(&checkPostId{
|
||||||
|
PostID: in.GetPostId(),
|
||||||
|
Content: in.GetContent(),
|
||||||
|
}); err != nil {
|
||||||
|
// 錯誤代碼 05-011-00
|
||||||
|
return nil, ers.InvalidFormat(err.Error())
|
||||||
|
}
|
||||||
|
// 沒有就沒有,有就走全覆蓋
|
||||||
|
update := model.Post{}
|
||||||
|
oid, err := primitive.ObjectIDFromHex(in.GetPostId())
|
||||||
|
if err != nil {
|
||||||
|
// 錯誤代碼 05-011-00
|
||||||
|
return nil, ers.InvalidFormat("failed to get correct post id")
|
||||||
|
}
|
||||||
|
update.ID = oid
|
||||||
|
update.Tags = in.GetTags()
|
||||||
|
// 將 Media 存入
|
||||||
|
var media []model.Media
|
||||||
|
for _, item := range in.GetMedia() {
|
||||||
|
media = append(media, model.Media{
|
||||||
|
Links: item.Url,
|
||||||
|
Type: item.Type,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
update.MediaURL = media
|
||||||
|
update.Content = in.GetContent()
|
||||||
|
update.Like = uint64(in.GetLikeCount())
|
||||||
|
update.DisLike = uint64(in.GetDislikeCount())
|
||||||
|
|
||||||
|
_, err = l.svcCtx.PostModel.UpdateOptional(l.ctx, &update)
|
||||||
|
if err != nil {
|
||||||
|
e := UpdatePostErrorL(
|
||||||
|
logx.WithContext(l.ctx),
|
||||||
|
[]logx.LogField{
|
||||||
|
{Key: "req", Value: in},
|
||||||
|
{Key: "func", Value: "PostModel.UpdateOptional"},
|
||||||
|
{Key: "err", Value: err},
|
||||||
|
},
|
||||||
|
"failed to update post", in.PostId).Wrap(err)
|
||||||
|
return nil, e
|
||||||
|
}
|
||||||
|
|
||||||
|
return &tweeting.OKResp{}, nil
|
||||||
|
}
|
|
@ -0,0 +1,118 @@
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,73 @@
|
||||||
|
// 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)
|
||||||
|
}
|
|
@ -0,0 +1,152 @@
|
||||||
|
// 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)
|
||||||
|
}
|
|
@ -0,0 +1,101 @@
|
||||||
|
// 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)
|
||||||
|
}
|
|
@ -0,0 +1,25 @@
|
||||||
|
package model
|
||||||
|
|
||||||
|
import "github.com/zeromicro/go-zero/core/stores/mon"
|
||||||
|
|
||||||
|
var _ CommentModel = (*customCommentModel)(nil)
|
||||||
|
|
||||||
|
type (
|
||||||
|
// CommentModel is an interface to be customized, add more methods here,
|
||||||
|
// and implement the added methods in customCommentModel.
|
||||||
|
CommentModel interface {
|
||||||
|
commentModel
|
||||||
|
}
|
||||||
|
|
||||||
|
customCommentModel struct {
|
||||||
|
*defaultCommentModel
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
// NewCommentModel returns a model for the mongo.
|
||||||
|
func NewCommentModel(url, db, collection string) CommentModel {
|
||||||
|
conn := mon.MustNewModel(url, db, collection)
|
||||||
|
return &customCommentModel{
|
||||||
|
defaultCommentModel: newDefaultCommentModel(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 commentModel interface {
|
||||||
|
Insert(ctx context.Context, data *Comment) error
|
||||||
|
FindOne(ctx context.Context, id string) (*Comment, error)
|
||||||
|
Update(ctx context.Context, data *Comment) (*mongo.UpdateResult, error)
|
||||||
|
Delete(ctx context.Context, id string) (int64, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type defaultCommentModel struct {
|
||||||
|
conn *mon.Model
|
||||||
|
}
|
||||||
|
|
||||||
|
func newDefaultCommentModel(conn *mon.Model) *defaultCommentModel {
|
||||||
|
return &defaultCommentModel{conn: conn}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *defaultCommentModel) Insert(ctx context.Context, data *Comment) 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 *defaultCommentModel) FindOne(ctx context.Context, id string) (*Comment, error) {
|
||||||
|
oid, err := primitive.ObjectIDFromHex(id)
|
||||||
|
if err != nil {
|
||||||
|
return nil, ErrInvalidObjectId
|
||||||
|
}
|
||||||
|
|
||||||
|
var data Comment
|
||||||
|
|
||||||
|
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 *defaultCommentModel) Update(ctx context.Context, data *Comment) (*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 *defaultCommentModel) 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 Comment 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"`
|
||||||
|
}
|
|
@ -0,0 +1,12 @@
|
||||||
|
package model
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
|
||||||
|
"github.com/zeromicro/go-zero/core/stores/mon"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
ErrNotFound = mon.ErrNotFound
|
||||||
|
ErrInvalidObjectId = errors.New("invalid objectId")
|
||||||
|
)
|
|
@ -0,0 +1,185 @@
|
||||||
|
package model
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"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/stores/mon"
|
||||||
|
"go.mongodb.org/mongo-driver/bson"
|
||||||
|
"go.mongodb.org/mongo-driver/bson/primitive"
|
||||||
|
"go.mongodb.org/mongo-driver/mongo"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ PostModel = (*customPostModel)(nil)
|
||||||
|
|
||||||
|
type (
|
||||||
|
// PostModel is an interface to be customized, add more methods here,
|
||||||
|
// and implement the added methods in customPostModel.
|
||||||
|
PostModel interface {
|
||||||
|
postModel
|
||||||
|
DeleteMany(ctx context.Context, id ...string) (int64, error)
|
||||||
|
UpdateOptional(ctx context.Context, data *Post) (*mongo.UpdateResult, error)
|
||||||
|
Find(ctx context.Context, param *QueryPostModelReq) ([]*Post, int64, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
customPostModel struct {
|
||||||
|
*defaultPostModel
|
||||||
|
}
|
||||||
|
|
||||||
|
QueryPostModelReq struct {
|
||||||
|
UID []string
|
||||||
|
Id []string
|
||||||
|
OnlyAds *bool
|
||||||
|
PageSize int64
|
||||||
|
PageIndex int64
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
// NewPostModel returns a model for the mongo.
|
||||||
|
func NewPostModel(url, db, collection string) PostModel {
|
||||||
|
conn := mon.MustNewModel(url, db, collection)
|
||||||
|
return &customPostModel{
|
||||||
|
defaultPostModel: newDefaultPostModel(conn),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *defaultPostModel) 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 *defaultPostModel) UpdateOptional(ctx context.Context, data *Post) (*mongo.UpdateResult, error) {
|
||||||
|
update := bson.M{"$set": bson.M{}}
|
||||||
|
|
||||||
|
if data.UID != "" {
|
||||||
|
update["$set"].(bson.M)["uid"] = data.UID
|
||||||
|
}
|
||||||
|
|
||||||
|
if data.Content != "" {
|
||||||
|
update["$set"].(bson.M)["content"] = data.Content
|
||||||
|
}
|
||||||
|
|
||||||
|
if data.Status != 0 {
|
||||||
|
update["$set"].(bson.M)["status"] = data.Status
|
||||||
|
}
|
||||||
|
|
||||||
|
if data.IsAd {
|
||||||
|
update["$set"].(bson.M)["is_ad"] = data.IsAd
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(data.Tags) > 0 {
|
||||||
|
update["$set"].(bson.M)["tags"] = data.Tags
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(data.MediaURL) > 0 {
|
||||||
|
update["$set"].(bson.M)["media_url"] = data.MediaURL
|
||||||
|
}
|
||||||
|
|
||||||
|
if data.Like != 0 {
|
||||||
|
update["$set"].(bson.M)["like"] = data.Like
|
||||||
|
}
|
||||||
|
|
||||||
|
if data.DisLike != 0 {
|
||||||
|
update["$set"].(bson.M)["dislike"] = data.DisLike
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find 貼文列表
|
||||||
|
func (m *defaultPostModel) Find(ctx context.Context, param *QueryPostModelReq) ([]*Post, int64, error) {
|
||||||
|
filter := bson.M{}
|
||||||
|
|
||||||
|
// 添加 UID 過濾條件
|
||||||
|
if len(param.UID) > 0 {
|
||||||
|
filter["uid"] = bson.M{"$in": param.UID}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 添加 ID 過濾條件
|
||||||
|
if len(param.Id) > 0 {
|
||||||
|
var ids []primitive.ObjectID
|
||||||
|
for _, item := range param.Id {
|
||||||
|
oid, err := primitive.ObjectIDFromHex(item)
|
||||||
|
if err != nil {
|
||||||
|
// log
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
ids = append(ids, oid)
|
||||||
|
}
|
||||||
|
|
||||||
|
filter["_id"] = bson.M{"$in": ids}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 添加 OnlyAds 過濾條件
|
||||||
|
if param.OnlyAds != nil {
|
||||||
|
// true 是廣告 false 不是廣告 , 沒寫就是不過率
|
||||||
|
filter["is_ad"] = *param.OnlyAds
|
||||||
|
}
|
||||||
|
|
||||||
|
// 分頁處理
|
||||||
|
opts := options.Find()
|
||||||
|
opts.SetSort(bson.D{{"create_time", -1}})
|
||||||
|
if param.PageSize > 0 {
|
||||||
|
opts.SetLimit(param.PageSize)
|
||||||
|
}
|
||||||
|
if param.PageIndex > 0 {
|
||||||
|
opts.SetSkip((param.PageIndex - 1) * param.PageSize)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 計算總數(不考慮分頁)
|
||||||
|
totalCount, err := m.conn.CountDocuments(ctx, filter)
|
||||||
|
if err != nil {
|
||||||
|
return nil, 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
result := make([]*Post, 0, param.PageSize)
|
||||||
|
// 執行查詢
|
||||||
|
err = m.conn.Find(ctx, &result, filter, opts)
|
||||||
|
if err != nil {
|
||||||
|
return nil, 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case err == nil:
|
||||||
|
return result, totalCount, nil
|
||||||
|
case errors.Is(err, monc.ErrNotFound):
|
||||||
|
return nil, 0, ErrNotFound
|
||||||
|
default:
|
||||||
|
return nil, 0, err
|
||||||
|
}
|
||||||
|
}
|
|
@ -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 postModel interface {
|
||||||
|
Insert(ctx context.Context, data *Post) error
|
||||||
|
FindOne(ctx context.Context, id string) (*Post, error)
|
||||||
|
Update(ctx context.Context, data *Post) (*mongo.UpdateResult, error)
|
||||||
|
Delete(ctx context.Context, id string) (int64, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type defaultPostModel struct {
|
||||||
|
conn *mon.Model
|
||||||
|
}
|
||||||
|
|
||||||
|
func newDefaultPostModel(conn *mon.Model) *defaultPostModel {
|
||||||
|
return &defaultPostModel{conn: conn}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *defaultPostModel) Insert(ctx context.Context, data *Post) error {
|
||||||
|
if data.ID.IsZero() {
|
||||||
|
data.ID = primitive.NewObjectID()
|
||||||
|
data.CreateAt = time.Now().UTC().UnixNano()
|
||||||
|
data.UpdateAt = time.Now().UTC().UnixNano()
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := m.conn.InsertOne(ctx, data)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *defaultPostModel) FindOne(ctx context.Context, id string) (*Post, error) {
|
||||||
|
oid, err := primitive.ObjectIDFromHex(id)
|
||||||
|
if err != nil {
|
||||||
|
return nil, ErrInvalidObjectId
|
||||||
|
}
|
||||||
|
|
||||||
|
var data Post
|
||||||
|
|
||||||
|
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 *defaultPostModel) Update(ctx context.Context, data *Post) (*mongo.UpdateResult, error) {
|
||||||
|
data.UpdateAt = time.Now().UTC().UnixNano()
|
||||||
|
|
||||||
|
res, err := m.conn.UpdateOne(ctx, bson.M{"_id": data.ID}, bson.M{"$set": data})
|
||||||
|
return res, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *defaultPostModel) 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,32 @@
|
||||||
|
package model
|
||||||
|
|
||||||
|
import (
|
||||||
|
"go.mongodb.org/mongo-driver/bson/primitive"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Post struct {
|
||||||
|
ID primitive.ObjectID `bson:"_id,omitempty" json:"id,omitempty"`
|
||||||
|
UID string `bson:"uid" json:"uid"` // 搜尋條件
|
||||||
|
Content string `bson:"content" json:"content"` // 內容
|
||||||
|
Status int8 `bson:"status" json:"status"` // 1. 等待審核中 , 2 審核通過,預設為 1 ->過濾條件
|
||||||
|
IsAd bool `bson:"is_ad" json:"is_ad"` // 此則貼文是否為廣告貼文 -> 過濾條件
|
||||||
|
Tags []string `bson:"tags" json:"tags"` // 本則貼文的標籤,不提供搜尋,僅提供顯示(存名字,ID 建立之後就不提供修改與刪除)
|
||||||
|
MediaURL []Media `bson:"media_url" json:"media_url"` // 網址
|
||||||
|
Like uint64 `bson:"like" json:"like"` // 讚數量
|
||||||
|
DisLike uint64 `bson:"dislike" json:"dislike"` // 不讚數量
|
||||||
|
UpdateAt int64 `bson:"updateAt,omitempty" json:"updateAt,omitempty"`
|
||||||
|
CreateAt int64 `bson:"createAt,omitempty" json:"createAt,omitempty"` // -> 排序條件
|
||||||
|
}
|
||||||
|
|
||||||
|
type Media struct {
|
||||||
|
Type string // media type jpeg, m3u8 之類的
|
||||||
|
Links string // 連結的網址
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Post) CollectionName() string {
|
||||||
|
return "post"
|
||||||
|
}
|
||||||
|
|
||||||
|
// 照邏輯,應該需要建立的索引有
|
||||||
|
// 單列索引:UID、Status、IsAd、CreateAt
|
||||||
|
// 複合索引:(UID + Status + IsAd)、(UID + CreateAt)
|
|
@ -0,0 +1,53 @@
|
||||||
|
// Code generated by goctl. DO NOT EDIT.
|
||||||
|
// Source: tweeting.proto
|
||||||
|
|
||||||
|
package server
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"app-cloudep-tweeting-service/gen_result/pb/tweeting"
|
||||||
|
postservicelogic "app-cloudep-tweeting-service/internal/logic/postservice"
|
||||||
|
"app-cloudep-tweeting-service/internal/svc"
|
||||||
|
)
|
||||||
|
|
||||||
|
type PostServiceServer struct {
|
||||||
|
svcCtx *svc.ServiceContext
|
||||||
|
tweeting.UnimplementedPostServiceServer
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewPostServiceServer(svcCtx *svc.ServiceContext) *PostServiceServer {
|
||||||
|
return &PostServiceServer{
|
||||||
|
svcCtx: svcCtx,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreatePost 新增貼文
|
||||||
|
func (s *PostServiceServer) CreatePost(ctx context.Context, in *tweeting.NewPostReq) (*tweeting.PostResp, error) {
|
||||||
|
l := postservicelogic.NewCreatePostLogic(ctx, s.svcCtx)
|
||||||
|
return l.CreatePost(in)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeletePost 刪除貼文
|
||||||
|
func (s *PostServiceServer) DeletePost(ctx context.Context, in *tweeting.DeletePostsReq) (*tweeting.OKResp, error) {
|
||||||
|
l := postservicelogic.NewDeletePostLogic(ctx, s.svcCtx)
|
||||||
|
return l.DeletePost(in)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdatePost 更新貼文
|
||||||
|
func (s *PostServiceServer) UpdatePost(ctx context.Context, in *tweeting.UpdatePostReq) (*tweeting.OKResp, error) {
|
||||||
|
l := postservicelogic.NewUpdatePostLogic(ctx, s.svcCtx)
|
||||||
|
return l.UpdatePost(in)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListPosts 查詢貼文
|
||||||
|
func (s *PostServiceServer) ListPosts(ctx context.Context, in *tweeting.QueryPostsReq) (*tweeting.ListPostsResp, error) {
|
||||||
|
l := postservicelogic.NewListPostsLogic(ctx, s.svcCtx)
|
||||||
|
return l.ListPosts(in)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ModifyLikeDislikeCount 調整讚或不讚數量
|
||||||
|
func (s *PostServiceServer) ModifyLikeDislikeCount(ctx context.Context, in *tweeting.ModifyLikeDislikeCountReq) (*tweeting.OKResp, error) {
|
||||||
|
l := postservicelogic.NewModifyLikeDislikeCountLogic(ctx, s.svcCtx)
|
||||||
|
return l.ModifyLikeDislikeCount(in)
|
||||||
|
}
|
|
@ -0,0 +1,20 @@
|
||||||
|
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,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func MustPostModel(c config.Config) model.PostModel {
|
||||||
|
postCollection := model.Post{}
|
||||||
|
return model.NewPostModel(mustMongoConnectUrl(c), c.Mongo.Database, postCollection.CollectionName())
|
||||||
|
}
|
|
@ -0,0 +1,24 @@
|
||||||
|
package svc
|
||||||
|
|
||||||
|
import (
|
||||||
|
"app-cloudep-tweeting-service/internal/config"
|
||||||
|
model "app-cloudep-tweeting-service/internal/model/mongo"
|
||||||
|
|
||||||
|
vi "code.30cm.net/digimon/library-go/validator"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ServiceContext struct {
|
||||||
|
Config config.Config
|
||||||
|
Validate vi.Validate
|
||||||
|
|
||||||
|
PostModel model.PostModel
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewServiceContext(c config.Config) *ServiceContext {
|
||||||
|
return &ServiceContext{
|
||||||
|
Config: c,
|
||||||
|
Validate: vi.MustValidator(),
|
||||||
|
|
||||||
|
PostModel: MustPostModel(c),
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,39 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"app-cloudep-tweeting-service/gen_result/pb/tweeting"
|
||||||
|
"app-cloudep-tweeting-service/internal/config"
|
||||||
|
postserviceServer "app-cloudep-tweeting-service/internal/server/postservice"
|
||||||
|
"app-cloudep-tweeting-service/internal/svc"
|
||||||
|
|
||||||
|
"github.com/zeromicro/go-zero/core/conf"
|
||||||
|
"github.com/zeromicro/go-zero/core/service"
|
||||||
|
"github.com/zeromicro/go-zero/zrpc"
|
||||||
|
"google.golang.org/grpc"
|
||||||
|
"google.golang.org/grpc/reflection"
|
||||||
|
)
|
||||||
|
|
||||||
|
var configFile = flag.String("f", "etc/tweeting.yaml", "the config file")
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
flag.Parse()
|
||||||
|
|
||||||
|
var c config.Config
|
||||||
|
conf.MustLoad(*configFile, &c)
|
||||||
|
ctx := svc.NewServiceContext(c)
|
||||||
|
|
||||||
|
s := zrpc.MustNewServer(c.RpcServerConf, func(grpcServer *grpc.Server) {
|
||||||
|
tweeting.RegisterPostServiceServer(grpcServer, postserviceServer.NewPostServiceServer(ctx))
|
||||||
|
|
||||||
|
if c.Mode == service.DevMode || c.Mode == service.TestMode {
|
||||||
|
reflection.Register(grpcServer)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
defer s.Stop()
|
||||||
|
|
||||||
|
fmt.Printf("Starting rpc server at %s...\n", c.ListenOn)
|
||||||
|
s.Start()
|
||||||
|
}
|
Loading…
Reference in New Issue