From 6103f51de27f23376ba8ee356ef2ba5b46983701 Mon Sep 17 00:00:00 2001 From: sulinehk Date: Sun, 3 Mar 2024 19:40:29 +0800 Subject: [PATCH] feat(blog): add ListByUser --- .../rpc/internal/logic/list_by_user_logic.go | 63 +++++- .../internal/test/list_by_user_logic_test.go | 186 ++++++++++++++++++ service/blog/rpc/model/blog_model.go | 24 ++- service/blog/rpc/model/mock_blog_model.go | 15 ++ 4 files changed, 285 insertions(+), 3 deletions(-) create mode 100644 service/blog/rpc/internal/test/list_by_user_logic_test.go diff --git a/service/blog/rpc/internal/logic/list_by_user_logic.go b/service/blog/rpc/internal/logic/list_by_user_logic.go index 7b5bdf7..9a567a1 100644 --- a/service/blog/rpc/internal/logic/list_by_user_logic.go +++ b/service/blog/rpc/internal/logic/list_by_user_logic.go @@ -2,9 +2,17 @@ package logic import ( "context" + "errors" + "strconv" + "github.com/linehk/go-microservices-blogger/convert" + "github.com/linehk/go-microservices-blogger/errcode" "github.com/linehk/go-microservices-blogger/service/blog/rpc/blog" "github.com/linehk/go-microservices-blogger/service/blog/rpc/internal/svc" + "github.com/linehk/go-microservices-blogger/service/blog/rpc/model" + "github.com/linehk/go-microservices-blogger/service/page/rpc/page" + "github.com/linehk/go-microservices-blogger/service/post/rpc/post" + "google.golang.org/protobuf/types/known/timestamppb" "github.com/zeromicro/go-zero/core/logx" ) @@ -24,7 +32,58 @@ func NewListByUserLogic(ctx context.Context, svcCtx *svc.ServiceContext) *ListBy } func (l *ListByUserLogic) ListByUser(in *blog.ListByUserReq) (*blog.ListByUserResp, error) { - // todo: add your logic here and delete this line + blogModelList, err := l.svcCtx.BlogModel.ListByAppUserUuid(l.ctx, in.GetUserId()) + if errors.Is(err, model.ErrNotFound) { + l.Error(errcode.Msg(errcode.BlogNotExist)) + return nil, errcode.Wrap(errcode.BlogNotExist) + } + if err != nil { + l.Error(errcode.Msg(errcode.Database)) + return nil, errcode.Wrap(errcode.Database) + } + + var listByUserResp blog.ListByUserResp + listByUserResp.Kind = "blogger#blogList" + + for _, blogModel := range blogModelList { + var blogResp blog.Blog + convert.Copy(&blogResp, blogModel) + blogResp.Kind = "blogger#blog" + blogResp.Id = blogModel.Uuid + if blogModel.Published.Valid { + blogResp.Published = timestamppb.New(blogModel.Published.Time) + } + if blogModel.Updated.Valid { + blogResp.Updated = timestamppb.New(blogModel.Updated.Time) + } + + listPostReq := &post.ListReq{ + BlogId: blogModel.Uuid, + } + listPostResp, err := l.svcCtx.PostService.List(l.ctx, listPostReq) + if err != nil { + l.Error(errcode.Msg(errcode.Service)) + return nil, errcode.Wrap(errcode.Service) + } + postTotalItems := strconv.Itoa(len(listPostResp.GetItems())) + for _, postItem := range listPostResp.GetItems() { + blogResp.Posts = append(blogResp.Posts, &blog.Posts{TotalItems: postTotalItems, SelfLink: postItem.GetSelfLink()}) + } + + listPageReq := &page.ListReq{ + BlogId: blogModel.Uuid, + } + listPageResp, err := l.svcCtx.PageService.List(l.ctx, listPageReq) + if err != nil { + l.Error(errcode.Msg(errcode.Service)) + return nil, errcode.Wrap(errcode.Service) + } + pageTotalItems := strconv.Itoa(len(listPageResp.GetItems())) + for _, pageItem := range listPageResp.GetItems() { + blogResp.Pages = append(blogResp.Pages, &blog.Pages{TotalItems: pageTotalItems, SelfLink: pageItem.GetSelfLink()}) + } + listByUserResp.Items = append(listByUserResp.Items, &blogResp) + } - return &blog.ListByUserResp{}, nil + return &listByUserResp, nil } diff --git a/service/blog/rpc/internal/test/list_by_user_logic_test.go b/service/blog/rpc/internal/test/list_by_user_logic_test.go new file mode 100644 index 0000000..b0b8fc8 --- /dev/null +++ b/service/blog/rpc/internal/test/list_by_user_logic_test.go @@ -0,0 +1,186 @@ +package test + +import ( + "context" + "database/sql" + "testing" + "time" + + "github.com/google/uuid" + "github.com/linehk/go-microservices-blogger/errcode" + "github.com/linehk/go-microservices-blogger/service/blog/rpc/blog" + "github.com/linehk/go-microservices-blogger/service/blog/rpc/internal/logic" + "github.com/linehk/go-microservices-blogger/service/blog/rpc/internal/svc" + "github.com/linehk/go-microservices-blogger/service/blog/rpc/model" + "github.com/linehk/go-microservices-blogger/service/page/rpc/page" + "github.com/linehk/go-microservices-blogger/service/page/rpc/pageservice" + "github.com/linehk/go-microservices-blogger/service/post/rpc/post" + "github.com/linehk/go-microservices-blogger/service/post/rpc/postservice" + "github.com/stretchr/testify/assert" + "go.uber.org/mock/gomock" + "google.golang.org/protobuf/types/known/timestamppb" +) + +func TestListByUser(t *testing.T) { + ctrl := gomock.NewController(t) + ctx := context.Background() + blogRepo := model.NewMockBlogModel(ctrl) + postService := postservice.NewMockPostService(ctrl) + pageService := pageservice.NewMockPageService(ctrl) + logicService := logic.NewListByUserLogic(ctx, &svc.ServiceContext{ + BlogModel: blogRepo, + PostService: postService, + PageService: pageService, + }) + defer ctrl.Finish() + + userId := uuid.NewString() + blogId1 := uuid.NewString() + blogId2 := uuid.NewString() + listByUserReq := &blog.ListByUserReq{UserId: userId} + + name := "Name" + description := "Description" + published := time.Now() + updated := time.Now() + url := "Url" + selfLink := "SelfLink" + customMetaData := "CustomMetaData" + blogModelList := []*model.Blog{ + { + Id: 1, + Uuid: blogId1, + AppUserUuid: userId, + Name: sql.NullString{String: name, Valid: true}, + Description: sql.NullString{String: description, Valid: true}, + Published: sql.NullTime{Time: published, Valid: true}, + Updated: sql.NullTime{Time: updated, Valid: true}, + Url: sql.NullString{String: url, Valid: true}, + SelfLink: sql.NullString{String: selfLink, Valid: true}, + CustomMetaData: sql.NullString{String: customMetaData, Valid: true}, + }, + { + Id: 2, + Uuid: blogId2, + AppUserUuid: userId, + Name: sql.NullString{String: name, Valid: true}, + Description: sql.NullString{String: description, Valid: true}, + Published: sql.NullTime{Time: published, Valid: true}, + Updated: sql.NullTime{Time: updated, Valid: true}, + Url: sql.NullString{String: url, Valid: true}, + SelfLink: sql.NullString{String: selfLink, Valid: true}, + CustomMetaData: sql.NullString{String: customMetaData, Valid: true}, + }, + } + + listPostReq1 := &post.ListReq{ + BlogId: blogId1, + } + listPostReq2 := &post.ListReq{ + BlogId: blogId2, + } + + postSelfLink1 := "postSelfLink1" + postSelfLink2 := "postSelfLink2" + listPostResp := &post.ListResp{ + Kind: "blogger#post", + Items: []*post.Post{{ + SelfLink: postSelfLink1, + }, { + SelfLink: postSelfLink2, + }}, + } + + pageSelfLink1 := "pageSelfLink1" + pageSelfLink2 := "pageSelfLink2" + listPageReq1 := &page.ListReq{ + BlogId: blogId1, + } + listPageReq2 := &page.ListReq{ + BlogId: blogId2, + } + listPageResp := &page.ListResp{ + Kind: "blogger#page", + Items: []*page.Page{{ + SelfLink: pageSelfLink1, + }, { + SelfLink: pageSelfLink2, + }}, + } + + postTotalItems := "2" + pageTotalItems := "2" + expected := &blog.ListByUserResp{ + Kind: "blogger#blogList", + Items: []*blog.Blog{ + { + Kind: "blogger#blog", + Id: blogId1, + Name: name, + Description: description, + Published: timestamppb.New(published), + Updated: timestamppb.New(updated), + Url: url, + SelfLink: selfLink, + Posts: []*blog.Posts{{TotalItems: postTotalItems, SelfLink: postSelfLink1}, {TotalItems: postTotalItems, SelfLink: postSelfLink2}}, + Pages: []*blog.Pages{{TotalItems: pageTotalItems, SelfLink: pageSelfLink1}, {TotalItems: pageTotalItems, SelfLink: pageSelfLink2}}, + CustomMetaData: customMetaData, + }, { + Kind: "blogger#blog", + Id: blogId2, + Name: name, + Description: description, + Published: timestamppb.New(published), + Updated: timestamppb.New(updated), + Url: url, + SelfLink: selfLink, + Posts: []*blog.Posts{{TotalItems: postTotalItems, SelfLink: postSelfLink1}, {TotalItems: postTotalItems, SelfLink: postSelfLink2}}, + Pages: []*blog.Pages{{TotalItems: pageTotalItems, SelfLink: pageSelfLink1}, {TotalItems: pageTotalItems, SelfLink: pageSelfLink2}}, + CustomMetaData: customMetaData, + }, + }, + } + + // BlogNotExist + expectedErr := errcode.Wrap(errcode.BlogNotExist) + blogRepo.EXPECT().ListByAppUserUuid(ctx, userId).Return(nil, model.ErrNotFound) + actual, actualErr := logicService.ListByUser(listByUserReq) + assert.Nil(t, actual) + assert.Equal(t, expectedErr, actualErr) + + // Database + expectedErr = errcode.Wrap(errcode.Database) + blogRepo.EXPECT().ListByAppUserUuid(ctx, userId).Return(nil, expectedErr) + actual, actualErr = logicService.ListByUser(listByUserReq) + assert.Nil(t, actual) + assert.Equal(t, expectedErr, actualErr) + + // Post Service + expectedErr = errcode.Wrap(errcode.Service) + blogRepo.EXPECT().ListByAppUserUuid(ctx, userId).Return(blogModelList, nil) + postService.EXPECT().List(ctx, listPostReq1).Return(nil, expectedErr) + + actual, actualErr = logicService.ListByUser(listByUserReq) + assert.Nil(t, actual) + assert.Equal(t, expectedErr, actualErr) + + // Page Service + expectedErr = errcode.Wrap(errcode.Service) + blogRepo.EXPECT().ListByAppUserUuid(ctx, userId).Return(blogModelList, nil) + postService.EXPECT().List(ctx, listPostReq1).Return(listPostResp, nil) + + pageService.EXPECT().List(ctx, listPageReq1).Return(nil, expectedErr) + actual, actualErr = logicService.ListByUser(listByUserReq) + assert.Nil(t, actual) + assert.Equal(t, expectedErr, actualErr) + + // Success + blogRepo.EXPECT().ListByAppUserUuid(ctx, userId).Return(blogModelList, nil) + postService.EXPECT().List(ctx, listPostReq1).Return(listPostResp, nil) + postService.EXPECT().List(ctx, listPostReq2).Return(listPostResp, nil) + pageService.EXPECT().List(ctx, listPageReq1).Return(listPageResp, nil) + pageService.EXPECT().List(ctx, listPageReq2).Return(listPageResp, nil) + actual, actualErr = logicService.ListByUser(listByUserReq) + assert.Equal(t, actual, expected) + assert.Nil(t, actualErr) +} diff --git a/service/blog/rpc/model/blog_model.go b/service/blog/rpc/model/blog_model.go index 58ed06d..42fd957 100755 --- a/service/blog/rpc/model/blog_model.go +++ b/service/blog/rpc/model/blog_model.go @@ -18,6 +18,7 @@ type ( BlogModel interface { blogModel FindOneByUrl(ctx context.Context, url string) (*Blog, error) + ListByAppUserUuid(ctx context.Context, appUserUuid string) ([]*Blog, error) } customBlogModel struct { @@ -33,7 +34,8 @@ func NewBlogModel(conn sqlx.SqlConn, c cache.CacheConf, opts ...cache.Option) Bl } var ( - cachePublicBlogUrlPrefix = "cache:public:blog:url:" + cachePublicBlogUrlPrefix = "cache:public:blog:url:" + cachePublicBlogListAppUserUuidPrefix = "cache:public:blog:list:appUserUuid:" ) func (c *customBlogModel) FindOneByUrl(ctx context.Context, url string) (*Blog, error) { @@ -55,3 +57,23 @@ func (c *customBlogModel) FindOneByUrl(ctx context.Context, url string) (*Blog, return nil, err } } + +func (m *defaultBlogModel) ListByAppUserUuid(ctx context.Context, appUserUuid string) ([]*Blog, error) { + publicBlogListAppUserUuidKey := fmt.Sprintf("%s%v", cachePublicBlogListAppUserUuidPrefix, appUserUuid) + var resp []*Blog + err := m.QueryRowIndexCtx(ctx, &resp, publicBlogListAppUserUuidKey, m.formatPrimary, func(ctx context.Context, conn sqlx.SqlConn, v any) (i any, e error) { + query := fmt.Sprintf("select %s from %s where app_user_uuid = $1", blogRows, m.table) + if err := conn.QueryRowCtx(ctx, &resp, query, appUserUuid); err != nil { + return nil, err + } + return resp[0].Id, nil + }, m.queryPrimary) + switch err { + case nil: + return resp, nil + case sqlc.ErrNotFound: + return nil, ErrNotFound + default: + return nil, err + } +} diff --git a/service/blog/rpc/model/mock_blog_model.go b/service/blog/rpc/model/mock_blog_model.go index 860dd22..004454e 100644 --- a/service/blog/rpc/model/mock_blog_model.go +++ b/service/blog/rpc/model/mock_blog_model.go @@ -129,6 +129,21 @@ func (mr *MockBlogModelMockRecorder) Insert(arg0, arg1 any) *gomock.Call { return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Insert", reflect.TypeOf((*MockBlogModel)(nil).Insert), arg0, arg1) } +// ListByAppUserUuid mocks base method. +func (m *MockBlogModel) ListByAppUserUuid(arg0 context.Context, arg1 string) ([]*Blog, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ListByAppUserUuid", arg0, arg1) + ret0, _ := ret[0].([]*Blog) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ListByAppUserUuid indicates an expected call of ListByAppUserUuid. +func (mr *MockBlogModelMockRecorder) ListByAppUserUuid(arg0, arg1 any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListByAppUserUuid", reflect.TypeOf((*MockBlogModel)(nil).ListByAppUserUuid), arg0, arg1) +} + // Update mocks base method. func (m *MockBlogModel) Update(arg0 context.Context, arg1 *Blog) error { m.ctrl.T.Helper()