feature/fanout #3
			
				
			
		
		
		
	|  | @ -2,4 +2,4 @@ | |||
| CREATE DATABASE relation; | ||||
| 
 | ||||
| // 創建 User 節點 UID 是唯一鍵 | ||||
| CREATE CONSTRAINT FOR (u:User) REQUIRE u.UID IS UNIQUE | ||||
| CREATE CONSTRAINT FOR (u:User) REQUIRE u.uid IS UNIQUE | ||||
|  | @ -251,7 +251,48 @@ 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 | ||||
| { | ||||
|   rpc AddUserToNetwork(AddUserToNetworkReq) returns (OKResp); | ||||
|   // 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); | ||||
| } | ||||
|  | @ -41,6 +41,15 @@ const ( | |||
| 	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(), | ||||
|  |  | |||
|  | @ -4,4 +4,23 @@ import "context" | |||
| 
 | ||||
| type SocialNetworkRepository interface { | ||||
| 	CreateUserNode(ctx context.Context, uid string) error | ||||
| 	MarkFollowerRelation(ctx context.Context, fromUID, toUID string) error | ||||
| 	RemoveFollowerRelation(ctx context.Context, fromUID, toUID string) error | ||||
| 	GetFollower(ctx context.Context, req FollowReq) (FollowResp, error) | ||||
| 	GetFollowee(ctx context.Context, req FollowReq) (FollowResp, error) | ||||
| 	GetFollowerCount(ctx context.Context, uid string) (int64, error) | ||||
| 	GetFolloweeCount(ctx context.Context, uid string) (int64, error) | ||||
| 	GetDegreeBetweenUsers(ctx context.Context, uid1, uid2 string) (int64, error) | ||||
| 	GetUIDsWithinNDegrees(ctx context.Context, uid string, degrees, pageSize, pageIndex int64) ([]string, int64, error) | ||||
| } | ||||
| 
 | ||||
| type FollowReq struct { | ||||
| 	UID       string | ||||
| 	PageSize  int64 | ||||
| 	PageIndex int64 | ||||
| } | ||||
| 
 | ||||
| type FollowResp struct { | ||||
| 	UIDs  []string | ||||
| 	Total int64 | ||||
| } | ||||
|  |  | |||
|  | @ -1,37 +0,0 @@ | |||
| package socialnetworkservicelogic | ||||
| 
 | ||||
| import ( | ||||
| 	"context" | ||||
| 	"fmt" | ||||
| 
 | ||||
| 	"app-cloudep-tweeting-service/gen_result/pb/tweeting" | ||||
| 	"app-cloudep-tweeting-service/internal/svc" | ||||
| 
 | ||||
| 	"github.com/zeromicro/go-zero/core/logx" | ||||
| ) | ||||
| 
 | ||||
| type AddUserToNetworkLogic struct { | ||||
| 	ctx    context.Context | ||||
| 	svcCtx *svc.ServiceContext | ||||
| 	logx.Logger | ||||
| } | ||||
| 
 | ||||
| func NewAddUserToNetworkLogic(ctx context.Context, svcCtx *svc.ServiceContext) *AddUserToNetworkLogic { | ||||
| 	return &AddUserToNetworkLogic{ | ||||
| 		ctx:    ctx, | ||||
| 		svcCtx: svcCtx, | ||||
| 		Logger: logx.WithContext(ctx), | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| func (l *AddUserToNetworkLogic) AddUserToNetwork(in *tweeting.AddUserToNetworkReq) (*tweeting.OKResp, error) { | ||||
| 	// todo: add your logic here and delete this line
 | ||||
| 	err := l.svcCtx.SocialNetworkRepository.CreateUserNode(l.ctx, in.GetUid()) | ||||
| 	if err != nil { | ||||
| 		fmt.Println("gg88g88g8", err) | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	fmt.Println(err) | ||||
| 
 | ||||
| 	return &tweeting.OKResp{}, nil | ||||
| } | ||||
|  | @ -0,0 +1,58 @@ | |||
| package socialnetworkservicelogic | ||||
| 
 | ||||
| import ( | ||||
| 	"app-cloudep-tweeting-service/internal/domain" | ||||
| 	ers "code.30cm.net/digimon/library-go/errs" | ||||
| 	"context" | ||||
| 
 | ||||
| 	"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 | ||||
| } | ||||
|  | @ -0,0 +1,69 @@ | |||
| package socialnetworkservicelogic | ||||
| 
 | ||||
| import ( | ||||
| 	"app-cloudep-tweeting-service/internal/domain" | ||||
| 	"app-cloudep-tweeting-service/internal/domain/repository" | ||||
| 	ers "code.30cm.net/digimon/library-go/errs" | ||||
| 	"context" | ||||
| 
 | ||||
| 	"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 | ||||
| } | ||||
|  | @ -0,0 +1,62 @@ | |||
| package socialnetworkservicelogic | ||||
| 
 | ||||
| import ( | ||||
| 	"app-cloudep-tweeting-service/internal/domain" | ||||
| 	ers "code.30cm.net/digimon/library-go/errs" | ||||
| 	"context" | ||||
| 
 | ||||
| 	"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 | ||||
| } | ||||
|  | @ -0,0 +1,75 @@ | |||
| package socialnetworkservicelogic | ||||
| 
 | ||||
| import ( | ||||
| 	"app-cloudep-tweeting-service/internal/domain" | ||||
| 	"app-cloudep-tweeting-service/internal/domain/repository" | ||||
| 	ers "code.30cm.net/digimon/library-go/errs" | ||||
| 	"context" | ||||
| 
 | ||||
| 	"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 | ||||
| } | ||||
|  | @ -0,0 +1,62 @@ | |||
| package socialnetworkservicelogic | ||||
| 
 | ||||
| import ( | ||||
| 	"app-cloudep-tweeting-service/internal/domain" | ||||
| 	ers "code.30cm.net/digimon/library-go/errs" | ||||
| 	"context" | ||||
| 
 | ||||
| 	"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 | ||||
| } | ||||
|  | @ -0,0 +1,57 @@ | |||
| package socialnetworkservicelogic | ||||
| 
 | ||||
| import ( | ||||
| 	"app-cloudep-tweeting-service/internal/domain" | ||||
| 	ers "code.30cm.net/digimon/library-go/errs" | ||||
| 	"context" | ||||
| 
 | ||||
| 	"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 | ||||
| } | ||||
|  | @ -5,6 +5,7 @@ import ( | |||
| 	"app-cloudep-tweeting-service/internal/domain/repository" | ||||
| 	client4J "app-cloudep-tweeting-service/internal/lib/neo4j" | ||||
| 	"context" | ||||
| 	"fmt" | ||||
| 	"github.com/neo4j/neo4j-go-driver/v5/neo4j" | ||||
| ) | ||||
| 
 | ||||
|  | @ -25,7 +26,7 @@ func MustSocialNetworkRepository(param SocialNetworkParam) repository.SocialNetw | |||
| 	} | ||||
| } | ||||
| 
 | ||||
| func (s SocialNetworkRepository) CreateUserNode(ctx context.Context, uid string) error { | ||||
| func (s *SocialNetworkRepository) CreateUserNode(ctx context.Context, uid string) error { | ||||
| 	session, err := s.neo4jClient.Conn() | ||||
| 	if err != nil { | ||||
| 		return err | ||||
|  | @ -36,18 +37,356 @@ func (s SocialNetworkRepository) CreateUserNode(ctx context.Context, uid string) | |||
| 		"uid": uid, | ||||
| 	} | ||||
| 
 | ||||
| 	_, err = session.NewSession(ctx, neo4j.SessionConfig{ | ||||
| 	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) {
 | ||||
| 	// 	node := run.Record().AsMap()
 | ||||
| 	// 	fmt.Printf("Created Node: %v\n", node)
 | ||||
| 	// }
 | ||||
| 	// 處理結果
 | ||||
| 	if run.Next(ctx) { | ||||
| 		_ = run.Record().AsMap() | ||||
| 	} | ||||
| 
 | ||||
| 	return nil | ||||
| } | ||||
| 
 | ||||
| func (s *SocialNetworkRepository) MarkFollowerRelation(ctx context.Context, fromUID, toUID string) error { | ||||
| 	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) { | ||||
| 	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 uids []string | ||||
| 	for run.Next(ctx) { | ||||
| 		record := run.Record() | ||||
| 		if uid, ok := record.Get("uid"); ok { | ||||
| 			uids = append(uids, uid.(string)) | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	total, err := s.GetFollowerCount(ctx, req.UID) | ||||
| 	if err != nil { | ||||
| 		return repository.FollowResp{}, err | ||||
| 	} | ||||
| 
 | ||||
| 	return repository.FollowResp{ | ||||
| 		UIDs:  uids, | ||||
| 		Total: total, | ||||
| 	}, nil | ||||
| } | ||||
| 
 | ||||
| func (s *SocialNetworkRepository) GetFollowee(ctx context.Context, req repository.FollowReq) (repository.FollowResp, error) { | ||||
| 	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 uids []string | ||||
| 	for run.Next(ctx) { | ||||
| 		record := run.Record() | ||||
| 		if uid, ok := record.Get("uid"); ok { | ||||
| 			uids = append(uids, uid.(string)) | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	total, err := s.GetFolloweeCount(ctx, req.UID) | ||||
| 	if err != nil { | ||||
| 		return repository.FollowResp{}, err | ||||
| 	} | ||||
| 
 | ||||
| 	return repository.FollowResp{ | ||||
| 		UIDs:  uids, | ||||
| 		Total: total, | ||||
| 	}, nil | ||||
| } | ||||
| 
 | ||||
| func (s *SocialNetworkRepository) GetFollowerCount(ctx context.Context, uid string) (int64, error) { | ||||
| 	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 { | ||||
| 			count = followerCount.(int64) | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	return count, nil | ||||
| } | ||||
| 
 | ||||
| func (s *SocialNetworkRepository) GetFolloweeCount(ctx context.Context, uid string) (int64, error) { | ||||
| 	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 { | ||||
| 			count = followeeCount.(int64) | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	return count, nil | ||||
| } | ||||
| 
 | ||||
| func (s *SocialNetworkRepository) RemoveFollowerRelation(ctx context.Context, fromUID, toUID string) error { | ||||
| 	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) { | ||||
| 	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 { | ||||
| 			degree = deg.(int64) | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	return degree, nil | ||||
| } | ||||
| 
 | ||||
| // GetUIDsWithinNDegrees 取得某個節點在 n 度內關係所有 UID
 | ||||
| func (s *SocialNetworkRepository) GetUIDsWithinNDegrees(ctx context.Context, uid string, degrees, pageSize, pageIndex int64) ([]string, int64, error) { | ||||
| 	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 uids []string | ||||
| 	for run.Next(ctx) { | ||||
| 		record := run.Record() | ||||
| 		if uid, ok := record.Get("uid"); ok { | ||||
| 			uids = append(uids, uid.(string)) | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	// 計算總數
 | ||||
| 	totalCount, err := s.getTotalUIDsWithinNDegrees(ctx, uid, degrees) | ||||
| 	if err != nil { | ||||
| 		return nil, 0, err | ||||
| 	} | ||||
| 
 | ||||
| 	return uids, totalCount, nil | ||||
| } | ||||
| 
 | ||||
| func (s *SocialNetworkRepository) getTotalUIDsWithinNDegrees(ctx context.Context, uid string, degrees int64) (int64, error) { | ||||
| 	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 { | ||||
| 			totalCount = count.(int64) | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	return totalCount, nil | ||||
| } | ||||
|  |  | |||
|  | @ -22,7 +22,38 @@ func NewSocialNetworkServiceServer(svcCtx *svc.ServiceContext) *SocialNetworkSer | |||
| 	} | ||||
| } | ||||
| 
 | ||||
| func (s *SocialNetworkServiceServer) AddUserToNetwork(ctx context.Context, in *tweeting.AddUserToNetworkReq) (*tweeting.OKResp, error) { | ||||
| 	l := socialnetworkservicelogic.NewAddUserToNetworkLogic(ctx, s.svcCtx) | ||||
| 	return l.AddUserToNetwork(in) | ||||
| // 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) | ||||
| } | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue