Compare commits

..

No commits in common. "main" and "feature/post_service" have entirely different histories.

92 changed files with 512 additions and 7019 deletions

View File

@ -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:

View File

@ -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

View File

@ -4,28 +4,3 @@ Etcd:
Hosts: Hosts:
- 127.0.0.1:2379 - 127.0.0.1:2379
Key: tweeting.rpc Key: tweeting.rpc
Mongo:
Schema: mongodb
Host: 127.0.0.1
User: ""
Password: ""
Port: "27017"
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

View File

@ -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 看是否有要刪除過多的索引,要在測試一下

View File

@ -1,2 +0,0 @@
use digimon_tweeting;
db.comment.createIndex({ "post_id": 1,"createAt":1});

View File

@ -1,5 +0,0 @@
// 企業版才能用,社群版只能用預設的
CREATE DATABASE relation;
// 創建 User 節點 UID 是唯一鍵
CREATE CONSTRAINT FOR (u:User) REQUIRE u.uid IS UNIQUE

View File

@ -1,147 +1,136 @@
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 NewPostReq {
// ------ NewPost -------- int64 user_id = 1; // ID
message NewPostReq string content = 2; //
{ repeated string tags = 3; //
string uid = 1; // ID repeated string media_url = 4; // Media URL
string content = 2; // bool is_ad = 5; //
repeated string tags = 3; //
repeated Media media = 4; // Media URL
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; // int64 like_count = 9; //
int64 like_count = 9; // int64 dislike_count = 10; //
int64 dislike_count = 10; //
} }
// //
message ListPostsResp message ListPostsResp {
{ repeated PostDetailItem posts = 1; //
repeated PostDetailItem posts = 1; // Pager page =2;
}
// /
message LikeReq {
string target_id = 1; // IDID或評論ID
int64 user_id = 2; // ID
int64 like_type = 3; //
}
// /
message LikeItem {
string target_id = 1; // IDID或評論ID
int64 user_id = 2; // ID
int64 like_type = 3; //
}
// /
message LikeListReq {
string target_id = 1; // IDID或評論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; // IDID或評論ID
int64 reaction_type = 2; // int64 like_type = 2; //
bool is_increment = 3; // true false
int64 count = 4; //
} }
// ========== () ========== // /
service PostService message LikeCountResp {
{ string 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 {
// ------------ ------------ string post_id = 1; // ID
message CommentPostReq int64 user_id = 2; // ID
{ string content = 3; //
string post_id = 1; // ID
string uid = 2; // ID
string content = 3; //
} }
message CommentPostResp //
{ message GetCommentsReq {
string comment_id = 1; // ID string post_id = 1; // ID
} int32 page_index = 2; //
int32 page_size = 3; //
// ------------ ------------
message GetCommentsReq
{
string post_id = 1; // ID
int32 page_index = 2; //
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 +138,60 @@ 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; // //
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);
// 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 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);
} }

59
go.mod
View File

@ -1,58 +1,31 @@
module app-cloudep-tweeting-service module app-cloudep-feed-service
go 1.22.3 go 1.22.3
require ( require (
code.30cm.net/digimon/library-go/errs v1.2.4
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.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/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
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/gogo/protobuf v1.3.2 // indirect
github.com/golang/mock v1.6.0 // indirect github.com/golang/mock v1.6.0 // indirect
github.com/golang/protobuf v1.5.4 // 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/gnostic-models v0.6.8 // indirect
github.com/google/go-cmp v0.6.0 // indirect github.com/google/go-cmp v0.6.0 // indirect
github.com/google/gofuzz v1.2.0 // indirect github.com/google/gofuzz v1.2.0 // indirect
@ -60,51 +33,23 @@ require (
github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0 // indirect github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0 // indirect
github.com/josharian/intern v1.0.0 // indirect github.com/josharian/intern v1.0.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect github.com/json-iterator/go v1.1.12 // indirect
github.com/klauspost/compress v1.17.8 // 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/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/scram v1.1.2 // indirect
github.com/xdg-go/stringprep v1.0.4 // 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
@ -120,10 +65,8 @@ require (
go.uber.org/automaxprocs v1.5.3 // indirect go.uber.org/automaxprocs v1.5.3 // indirect
go.uber.org/multierr v1.9.0 // indirect go.uber.org/multierr v1.9.0 // indirect
go.uber.org/zap v1.24.0 // indirect go.uber.org/zap v1.24.0 // indirect
golang.org/x/crypto v0.25.0 // indirect
golang.org/x/net v0.27.0 // indirect golang.org/x/net v0.27.0 // indirect
golang.org/x/oauth2 v0.21.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/sys v0.22.0 // indirect
golang.org/x/term v0.22.0 // indirect golang.org/x/term v0.22.0 // indirect
golang.org/x/text v0.16.0 // indirect golang.org/x/text v0.16.0 // indirect

View File

@ -1,40 +1,7 @@
package config package config
import ( import "github.com/zeromicro/go-zero/zrpc"
"time"
"github.com/zeromicro/go-zero/core/stores/redis"
"github.com/zeromicro/go-zero/zrpc"
)
type Config struct { type Config struct {
zrpc.RpcServerConf zrpc.RpcServerConf
Mongo struct {
Schema string
User string
Password string
Host string
Port string
Database string
}
TimelineSetting struct {
Expire int64 // Second
MaxLength int64 // 暫存筆數
}
// Redis Cluster
RedisCluster redis.RedisConf
// 圖形資料庫
Neo4J struct {
URI string
Username string
Password string
MaxConnectionPoolSize int
MaxConnectionLifetime time.Duration
ConnectionTimeout time.Duration
LogLevel string
}
} }

View File

@ -1,17 +0,0 @@
package domain
type AdType int32
func (a AdType) ToInt32() int32 {
return int32(a)
}
const (
AdTypeAll AdType = iota
AdTypeOnlyAd
AdTypeOnlyNotAd
)
const (
LastOfTimelineFlag = "NoMoreData"
)

View File

@ -1,62 +0,0 @@
package domain
import (
"strings"
ers "code.30cm.net/digimon/library-go/errs"
"code.30cm.net/digimon/library-go/errs/code"
"github.com/zeromicro/go-zero/core/logx"
)
type ErrorCode uint32
func (e ErrorCode) ToUint32() uint32 {
return uint32(e)
}
// Error Code 統一這邊改
const (
_ = iota
PostMongoErrorCode ErrorCode = iota
CreatePostError
DelPostError
UpdatePostError
ListPostError
)
const (
CommentFoundErrorCode ErrorCode = iota + 10
CommentInsertErrorCode
CommentDeleteErrorCode
CommentUpdateErrorCode
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,
l logx.Logger, filed []logx.LogField, s ...string) *ers.LibError {
e := CommentError(ec, s...)
l.WithCallerSkip(1).WithFields(filed...).Error(e.Error())
return e
}

View File

@ -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"
)

View File

@ -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")
}

View File

@ -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
}

View File

@ -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
}

View File

@ -1,12 +0,0 @@
package domain
type TweetingStatus int8
func (t TweetingStatus) ToInt8() int8 {
return int8(t)
}
const (
TweetingStatusNotReviewedYet TweetingStatus = iota + 1
TweetingStatusPass
)

View File

@ -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
}

View File

@ -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
}

View File

@ -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)
}()
}
})
}
}

View File

@ -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
}
}
}

View File

@ -0,0 +1,31 @@
package commentservicelogic
import (
"context"
"app-cloudep-feed-service/gen_result/pb/tweeting"
"app-cloudep-feed-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
}

View File

@ -1,11 +1,10 @@
package commentservicelogic package commentservicelogic
import ( import (
"app-cloudep-tweeting-service/internal/domain"
"context" "context"
"app-cloudep-tweeting-service/gen_result/pb/tweeting" "app-cloudep-feed-service/gen_result/pb/tweeting"
"app-cloudep-tweeting-service/internal/svc" "app-cloudep-feed-service/internal/svc"
"github.com/zeromicro/go-zero/core/logx" "github.com/zeromicro/go-zero/core/logx"
) )
@ -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
} }

View File

@ -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)
}
})
}
}

View File

@ -1,14 +1,10 @@
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-feed-service/gen_result/pb/tweeting"
"app-cloudep-feed-service/internal/svc"
"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,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
} }

View File

@ -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))
}
})
}
}

View File

@ -0,0 +1,31 @@
package commentservicelogic
import (
"context"
"app-cloudep-feed-service/gen_result/pb/tweeting"
"app-cloudep-feed-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
}

View File

@ -0,0 +1,31 @@
package commentservicelogic
import (
"context"
"app-cloudep-feed-service/gen_result/pb/tweeting"
"app-cloudep-feed-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
}

View File

@ -0,0 +1,31 @@
package commentservicelogic
import (
"context"
"app-cloudep-feed-service/gen_result/pb/tweeting"
"app-cloudep-feed-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
}

View File

@ -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-feed-service/gen_result/pb/tweeting"
"app-cloudep-feed-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
} }

View File

@ -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)
}
})
}
}

View File

@ -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-feed-service/gen_result/pb/tweeting"
"go.mongodb.org/mongo-driver/bson/primitive" "app-cloudep-feed-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
} }

View File

@ -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)
}
})
}
}

View File

@ -0,0 +1,31 @@
package postservicelogic
import (
"context"
"app-cloudep-feed-service/gen_result/pb/tweeting"
"app-cloudep-feed-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
}

View File

@ -1,95 +0,0 @@
package postservicelogic
import (
"app-cloudep-tweeting-service/internal/domain"
model "app-cloudep-tweeting-service/internal/model/mongo"
"app-cloudep-tweeting-service/gen_result/pb/tweeting"
"app-cloudep-tweeting-service/internal/svc"
"context"
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
}
// 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 {
// 錯誤代碼 05-021-02
e := domain.CommentErrorL(
domain.CreatePostError,
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
}

View File

@ -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)
}
})
}
}

View File

@ -1,11 +1,11 @@
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/svc"
"context" "context"
"app-cloudep-feed-service/gen_result/pb/tweeting"
"app-cloudep-feed-service/internal/svc"
"github.com/zeromicro/go-zero/core/logx" "github.com/zeromicro/go-zero/core/logx"
) )
@ -25,21 +25,7 @@ func NewDeletePostLogic(ctx context.Context, svcCtx *svc.ServiceContext) *Delete
// DeletePost 刪除貼文 // DeletePost 刪除貼文
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()...) // todo: add your logic here and delete this line
if err != nil {
// 錯誤代碼 05-021-03
e := domain.CommentErrorL(
domain.DelPostError,
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 return &tweeting.OKResp{}, nil
} }

View File

@ -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)
}
})
}
}

View File

@ -0,0 +1,31 @@
package postservicelogic
import (
"context"
"app-cloudep-feed-service/gen_result/pb/tweeting"
"app-cloudep-feed-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
}

View File

@ -0,0 +1,31 @@
package postservicelogic
import (
"context"
"app-cloudep-feed-service/gen_result/pb/tweeting"
"app-cloudep-feed-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
}

View File

@ -0,0 +1,31 @@
package postservicelogic
import (
"context"
"app-cloudep-feed-service/gen_result/pb/tweeting"
"app-cloudep-feed-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),
}
}
// Like 點讚/取消讚 貼文
func (l *LikeLogic) Like(in *tweeting.LikeReq) (*tweeting.OKResp, error) {
// todo: add your logic here and delete this line
return &tweeting.OKResp{}, nil
}

View File

@ -1,15 +1,10 @@
package postservicelogic package postservicelogic
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-feed-service/gen_result/pb/tweeting"
"google.golang.org/protobuf/proto" "app-cloudep-feed-service/internal/svc"
"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,97 +23,9 @@ func NewListPostsLogic(ctx context.Context, svcCtx *svc.ServiceContext) *ListPos
} }
} }
// 只列出要驗證的資料 // ListPosts 查詢貼文
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"`
}
// 將單個 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: item.Like,
DislikeCount: item.DisLike,
}
}
// ListPosts 查詢貼文 -> 主流程
func (l *ListPostsLogic) ListPosts(in *tweeting.QueryPostsReq) (*tweeting.ListPostsResp, error) { func (l *ListPostsLogic) ListPosts(in *tweeting.QueryPostsReq) (*tweeting.ListPostsResp, error) {
// 將 PageSize 和 PageIndex 提前轉換為 int64 // todo: add your logic here and delete this line
pageSize := int64(in.GetPageSize())
pageIndex := int64(in.GetPageIndex())
// 驗證資料 return &tweeting.ListPostsResp{}, nil
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{
Posts: result,
Page: &tweeting.Pager{
Total: count,
Index: pageIndex,
Size: pageSize,
},
}, nil
} }

View File

@ -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))
}
})
}
}

View File

@ -0,0 +1,31 @@
package postservicelogic
import (
"context"
"app-cloudep-feed-service/gen_result/pb/tweeting"
"app-cloudep-feed-service/internal/svc"
"github.com/zeromicro/go-zero/core/logx"
)
type NewPostLogic struct {
ctx context.Context
svcCtx *svc.ServiceContext
logx.Logger
}
func NewNewPostLogic(ctx context.Context, svcCtx *svc.ServiceContext) *NewPostLogic {
return &NewPostLogic{
ctx: ctx,
svcCtx: svcCtx,
Logger: logx.WithContext(ctx),
}
}
// NewPost 新增貼文
func (l *NewPostLogic) NewPost(in *tweeting.NewPostReq) (*tweeting.PostResp, error) {
// todo: add your logic here and delete this line
return &tweeting.PostResp{}, nil
}

View File

@ -1,15 +1,10 @@
package postservicelogic package postservicelogic
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-feed-service/gen_result/pb/tweeting"
"go.mongodb.org/mongo-driver/bson/primitive" "app-cloudep-feed-service/internal/svc"
"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,69 +23,9 @@ func NewUpdatePostLogic(ctx context.Context, svcCtx *svc.ServiceContext) *Update
} }
} }
type checkPostID struct {
PostID string `validate:"required"`
Content string `json:"content,omitempty" validate:"lte=500"`
}
// UpdatePost 更新貼文 // UpdatePost 更新貼文
func (l *UpdatePostLogic) UpdatePost(in *tweeting.UpdatePostReq) (*tweeting.OKResp, error) { func (l *UpdatePostLogic) UpdatePost(in *tweeting.UpdatePostReq) (*tweeting.OKResp, error) {
// 驗證資料 // todo: add your logic here and delete this line
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 存入
media := make([]model.Media, 0, len(in.GetMedia()))
for _, item := range in.GetMedia() {
media = append(media, model.Media{
Links: item.Url,
Type: item.Type,
})
}
update.MediaURL = media
update.Content = in.GetContent()
// 因為 0 也有意義,所以如果是真的沒帶進來,用 -1 帶進去表示不作動
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)
if err != nil {
// 錯誤代碼 05-021-04
e := domain.CommentErrorL(
domain.UpdatePostError,
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 return &tweeting.OKResp{}, nil
} }

View File

@ -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)
}
})
}
}

View File

@ -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
}

View File

@ -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)
}
})
}
}

View File

@ -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
}

View File

@ -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)
}
})
}
}

View File

@ -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
}

View File

@ -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)
}
})
}
}

View File

@ -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
}

View File

@ -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)
}
})
}
}

View File

@ -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
}

View File

@ -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)
}
})
}
}

View File

@ -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
}

View File

@ -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)
}
})
}
}

View File

@ -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
}

View File

@ -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)
}
})
}
}

View File

@ -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
}

View File

@ -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)
}
})
}
}

View File

@ -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
}

View File

@ -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)
}
})
}
}

View File

@ -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
}

View File

@ -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)
}
})
}
}

View File

@ -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
}

View File

@ -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)
}
})
}
}

View File

@ -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)
}

View File

@ -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)
}

View File

@ -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)
}

View File

@ -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)
}

View File

@ -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)
}

View File

@ -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)
}

View File

@ -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)
}

View File

@ -1,101 +0,0 @@
package model
import (
"context"
"time"
"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)
type (
// CommentModel is an interface to be customized, add more methods here,
// and implement the added methods in customCommentModel.
CommentModel interface {
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 {
*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.
func NewCommentModel(url, db, collection string) CommentModel {
conn := mon.MustNewModel(url, db, collection)
return &customCommentModel{
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
}

View File

@ -1,74 +0,0 @@
// 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().UTC().UnixNano()
data.UpdateAt = time.Now().UTC().UnixNano()
}
_, 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().UTC().UnixNano()
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
}

View File

@ -1,23 +0,0 @@
package model
import (
"go.mongodb.org/mongo-driver/bson/primitive"
)
type Comment struct {
ID primitive.ObjectID `bson:"_id,omitempty" json:"id,omitempty"`
PostID string `bson:"post_id" json:"post_id"`
UID string `bson:"uid" json:"uid"` // 留下留言的人
Content string `bson:"content" json:"content"` // 留言內容
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)

View File

@ -1,12 +0,0 @@
package model
import (
"errors"
"github.com/zeromicro/go-zero/core/stores/mon"
)
var (
ErrNotFound = mon.ErrNotFound
ErrInvalidObjectId = errors.New("invalid objectId")
)

View File

@ -1,185 +0,0 @@
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 != -1 {
update["$set"].(bson.M)["like"] = data.Like
}
if data.DisLike != -1 {
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
}
}

View File

@ -1,74 +0,0 @@
// 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
}

View File

@ -1,32 +0,0 @@
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 int64 `bson:"like" json:"like"` // 讚數量
DisLike int64 `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)

View File

@ -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
}

View File

@ -1 +0,0 @@
package repository

View File

@ -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
}

View File

@ -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)
}
})
}
}

View File

@ -6,9 +6,9 @@ package server
import ( import (
"context" "context"
"app-cloudep-tweeting-service/gen_result/pb/tweeting" "app-cloudep-feed-service/gen_result/pb/tweeting"
commentservicelogic "app-cloudep-tweeting-service/internal/logic/commentservice" "app-cloudep-feed-service/internal/logic/commentservice"
"app-cloudep-tweeting-service/internal/svc" "app-cloudep-feed-service/internal/svc"
) )
type CommentServiceServer struct { type CommentServiceServer struct {
@ -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)
}

View File

@ -6,9 +6,9 @@ package server
import ( import (
"context" "context"
"app-cloudep-tweeting-service/gen_result/pb/tweeting" "app-cloudep-feed-service/gen_result/pb/tweeting"
postservicelogic "app-cloudep-tweeting-service/internal/logic/postservice" "app-cloudep-feed-service/internal/logic/postservice"
"app-cloudep-tweeting-service/internal/svc" "app-cloudep-feed-service/internal/svc"
) )
type PostServiceServer struct { type PostServiceServer struct {
@ -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,27 @@ 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)
} }
// Like 點讚/取消讚 貼文
func (s *PostServiceServer) Like(ctx context.Context, in *tweeting.LikeReq) (*tweeting.OKResp, error) {
l := postservicelogic.NewLikeLogic(ctx, s.svcCtx)
return l.Like(in)
}
// GetLikeStatus 取得讚/不讚狀態
func (s *PostServiceServer) GetLikeStatus(ctx context.Context, in *tweeting.LikeReq) (*tweeting.OKResp, 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)
}

View File

@ -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)
}

View File

@ -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)
}

View File

@ -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())
}

View File

@ -1,57 +1,13 @@
package svc package svc
import ( import "app-cloudep-feed-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"
"app-cloudep-tweeting-service/internal/repository"
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"
)
type ServiceContext struct { type ServiceContext struct {
Config config.Config Config config.Config
Validate vi.Validate
PostModel model.PostModel
CommentModel model.CommentModel
TimelineRepo domainRepo.TimelineRepository
SocialNetworkRepository domainRepo.SocialNetworkRepository
} }
func NewServiceContext(c config.Config) *ServiceContext { func NewServiceContext(c config.Config) *ServiceContext {
ers.Scope = code.CloudEPTweeting
newRedis, err := redis.NewRedis(c.RedisCluster, redis.Cluster())
if err != nil {
panic(err)
}
neoClient := neo4j.NewNeo4J(&neo4j.Config{
URI: c.Neo4J.URI,
Username: c.Neo4J.Username,
Password: c.Neo4J.Password,
MaxConnectionPoolSize: c.Neo4J.MaxConnectionPoolSize,
MaxConnectionLifetime: c.Neo4J.MaxConnectionLifetime,
ConnectionTimeout: c.Neo4J.ConnectionTimeout,
}, neo4j.WithPerformance(), neo4j.WithLogLevel(c.Neo4J.LogLevel))
return &ServiceContext{ return &ServiceContext{
Config: c, Config: c,
Validate: vi.MustValidator(),
PostModel: MustPostModel(c),
CommentModel: MustCommentModel(c),
TimelineRepo: repository.MustGenerateRepository(repository.TimelineRepositoryParam{
Config: c,
Redis: *newRedis,
}),
SocialNetworkRepository: repository.MustSocialNetworkRepository(repository.SocialNetworkParam{
Config: c,
Neo4jClient: neoClient,
}),
} }
} }

View File

@ -1,15 +1,14 @@
package main package main
import ( import (
"app-cloudep-tweeting-service/gen_result/pb/tweeting"
"app-cloudep-tweeting-service/internal/config"
commentserviceServer "app-cloudep-tweeting-service/internal/server/commentservice"
postserviceServer "app-cloudep-tweeting-service/internal/server/postservice"
socialnetworkserviceServer "app-cloudep-tweeting-service/internal/server/socialnetworkservice"
timelineserviceServer "app-cloudep-tweeting-service/internal/server/timelineservice"
"app-cloudep-tweeting-service/internal/svc"
"flag" "flag"
"log" "fmt"
"app-cloudep-feed-service/gen_result/pb/tweeting"
"app-cloudep-feed-service/internal/config"
commentserviceServer "app-cloudep-feed-service/internal/server/commentservice"
postserviceServer "app-cloudep-feed-service/internal/server/postservice"
"app-cloudep-feed-service/internal/svc"
"github.com/zeromicro/go-zero/core/conf" "github.com/zeromicro/go-zero/core/conf"
"github.com/zeromicro/go-zero/core/service" "github.com/zeromicro/go-zero/core/service"
@ -30,8 +29,6 @@ func main() {
s := zrpc.MustNewServer(c.RpcServerConf, func(grpcServer *grpc.Server) { s := zrpc.MustNewServer(c.RpcServerConf, func(grpcServer *grpc.Server) {
tweeting.RegisterPostServiceServer(grpcServer, postserviceServer.NewPostServiceServer(ctx)) tweeting.RegisterPostServiceServer(grpcServer, postserviceServer.NewPostServiceServer(ctx))
tweeting.RegisterCommentServiceServer(grpcServer, commentserviceServer.NewCommentServiceServer(ctx)) tweeting.RegisterCommentServiceServer(grpcServer, commentserviceServer.NewCommentServiceServer(ctx))
tweeting.RegisterTimelineServiceServer(grpcServer, timelineserviceServer.NewTimelineServiceServer(ctx))
tweeting.RegisterSocialNetworkServiceServer(grpcServer, socialnetworkserviceServer.NewSocialNetworkServiceServer(ctx))
if c.Mode == service.DevMode || c.Mode == service.TestMode { if c.Mode == service.DevMode || c.Mode == service.TestMode {
reflection.Register(grpcServer) reflection.Register(grpcServer)
@ -39,6 +36,6 @@ func main() {
}) })
defer s.Stop() defer s.Stop()
log.Printf("Starting rpc server at %s...\n", c.ListenOn) fmt.Printf("Starting rpc server at %s...\n", c.ListenOn)
s.Start() s.Start()
} }