diff --git a/microservices/album/delivery.go b/microservices/album/delivery.go index ef8e243..0d330d1 100644 --- a/microservices/album/delivery.go +++ b/microservices/album/delivery.go @@ -11,4 +11,6 @@ type Handlers interface { DeleteFavoriteAlbum(response http.ResponseWriter, request *http.Request) IsFavoriteAlbum(response http.ResponseWriter, request *http.Request) GetFavoriteAlbums(response http.ResponseWriter, request *http.Request) + GetFavoriteAlbumsCount(response http.ResponseWriter, request *http.Request) + GetAlbumLikesCount(response http.ResponseWriter, request *http.Request) } diff --git a/microservices/album/delivery/http/handlers.go b/microservices/album/delivery/http/handlers.go index ec6d3b3..d273580 100644 --- a/microservices/album/delivery/http/handlers.go +++ b/microservices/album/delivery/http/handlers.go @@ -282,3 +282,60 @@ func (handlers *albumHandlers) GetFavoriteAlbums(response http.ResponseWriter, r response.WriteHeader(http.StatusOK) } + +func (handlers *albumHandlers) GetFavoriteAlbumsCount(response http.ResponseWriter, request *http.Request) { + requestID := request.Context().Value(utils.RequestIDKey{}) + vars := mux.Vars(request) + userID, err := uuid.Parse(vars["userID"]) + if err != nil { + handlers.logger.Error(fmt.Sprintf("Get '%s' wrong user id: %v", vars["userID"], err), requestID) + utils.JSONError(response, http.StatusBadRequest, "Wrong id value") + return + } + + count, err := handlers.usecase.GetFavoriteAlbumsCount(request.Context(), userID) + if err != nil { + handlers.logger.Error(fmt.Sprintf("Failed to get favorite albums count: %v", err), requestID) + utils.JSONError(response, http.StatusInternalServerError, fmt.Sprintf("Failed to get favorite albums count: %v", err)) + return + } else if count == 0 { + utils.JSONError(response, http.StatusNotFound, "No favorite albums were found") + return + } + + response.Header().Set("Content-Type", "application/json") + if err := json.NewEncoder(response).Encode(map[string]uint64{"favoriteAlbumsCount": count}); err != nil { + handlers.logger.Error(fmt.Sprintf("Failed to encode favorite albums count: %v", err), requestID) + utils.JSONError(response, http.StatusInternalServerError, fmt.Sprintf("Failed to encode favorite albums count: %v", err)) + return + } + + response.WriteHeader(http.StatusOK) +} + +func (handlers *albumHandlers) GetAlbumLikesCount(response http.ResponseWriter, request *http.Request) { + requestID := request.Context().Value(utils.RequestIDKey{}) + vars := mux.Vars(request) + albumID, err := strconv.ParseUint(vars["albumID"], 10, 64) + if err != nil { + handlers.logger.Error(fmt.Sprintf("Invalid album ID: %v", err), requestID) + utils.JSONError(response, http.StatusBadRequest, fmt.Sprintf("Invalid album ID: %v", err)) + return + } + + likesCount, err := handlers.usecase.GetAlbumLikesCount(request.Context(), albumID) + if err != nil { + handlers.logger.Error("Failed to get album likes count", requestID) + utils.JSONError(response, http.StatusInternalServerError, "Can't check is album in favorite") + return + } + + response.Header().Set("Content-Type", "application/json") + if err := json.NewEncoder(response).Encode(map[string]uint64{"albumLikesCount": likesCount}); err != nil { + handlers.logger.Error(fmt.Sprintf("Failed to encode: %v", err), requestID) + utils.JSONError(response, http.StatusInternalServerError, fmt.Sprintf("Failed to encode: %v", err)) + return + } + + response.WriteHeader(http.StatusOK) +} diff --git a/microservices/album/delivery/http/handlers_test.go b/microservices/album/delivery/http/handlers_test.go index b8cf48f..70a5c10 100644 --- a/microservices/album/delivery/http/handlers_test.go +++ b/microservices/album/delivery/http/handlers_test.go @@ -632,3 +632,139 @@ func TestAlbumHandlers_GetFavoriteAlbums(t *testing.T) { assert.Contains(t, response.Body.String(), "No favorite albums were found") }) } + +func TestAlbumHandlers_GetFavoriteAlbumsCount(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + cfg := &config.Config{} + logger := logger.New(&cfg.Service.Logger) + usecaseMock := mocks.NewMockUsecase(ctrl) + albumHandlers := NewAlbumHandlers(usecaseMock, logger) + + t.Run("Success", func(t *testing.T) { + userID := uuid.New() + count := uint64(2) + + usecaseMock.EXPECT().GetFavoriteAlbumsCount(gomock.Any(), userID).Return(count, nil) + + router := mux.NewRouter() + router.HandleFunc("/api/v1/albums/favorite/count/{userID:[0-9a-fA-F-]+}", albumHandlers.GetFavoriteAlbumsCount).Methods("GET") + + request, err := http.NewRequest(http.MethodGet, fmt.Sprintf("/api/v1/albums/favorite/count/%s", userID.String()), nil) + assert.NoError(t, err) + + response := httptest.NewRecorder() + router.ServeHTTP(response, request) + + res := response.Result() + assert.Equal(t, http.StatusOK, res.StatusCode) + + var result map[string]uint64 + err = json.NewDecoder(res.Body).Decode(&result) + assert.NoError(t, err) + assert.Equal(t, count, result["favoriteAlbumsCount"]) + }) + + t.Run("Wrong id value", func(t *testing.T) { + router := mux.NewRouter() + router.HandleFunc("/api/v1/albums/favorite/count/{userID:[0-9a-fA-F-]+}", albumHandlers.GetFavoriteAlbumsCount).Methods("GET") + + request, err := http.NewRequest(http.MethodGet, "/api/v1/albums/favorite/count/123", nil) + assert.NoError(t, err) + + response := httptest.NewRecorder() + router.ServeHTTP(response, request) + + res := response.Result() + assert.Equal(t, http.StatusBadRequest, res.StatusCode) + assert.Contains(t, response.Body.String(), "Wrong id value") + }) + + t.Run("Error while getting favorite albums count", func(t *testing.T) { + userID := uuid.New() + mockError := fmt.Errorf("usecase error") + usecaseMock.EXPECT().GetFavoriteAlbumsCount(gomock.Any(), userID).Return(uint64(0), mockError) + + router := mux.NewRouter() + router.HandleFunc("/api/v1/albums/favorite/count/{userID:[0-9a-fA-F-]+}", albumHandlers.GetFavoriteAlbumsCount).Methods("GET") + + request, err := http.NewRequest(http.MethodGet, fmt.Sprintf("/api/v1/albums/favorite/count/%s", userID.String()), nil) + assert.NoError(t, err) + + response := httptest.NewRecorder() + router.ServeHTTP(response, request) + + res := response.Result() + assert.Equal(t, http.StatusInternalServerError, res.StatusCode) + assert.Contains(t, response.Body.String(), "Failed to get favorite albums count") + }) +} + +func TestAlbumHandlers_GetAlbumLikesCount(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + cfg := &config.Config{} + logger := logger.New(&cfg.Service.Logger) + usecaseMock := mocks.NewMockUsecase(ctrl) + albumHandlers := NewAlbumHandlers(usecaseMock, logger) + + t.Run("Success", func(t *testing.T) { + albumID := uint64(123) + likesCount := uint64(10) + + usecaseMock.EXPECT().GetAlbumLikesCount(gomock.Any(), albumID).Return(likesCount, nil) + + router := mux.NewRouter() + router.HandleFunc("/api/v1/albums/likes/{albumID:[0-9]+}", albumHandlers.GetAlbumLikesCount).Methods("GET") + + request, err := http.NewRequest(http.MethodGet, fmt.Sprintf("/api/v1/albums/likes/%d", albumID), nil) + assert.NoError(t, err) + + response := httptest.NewRecorder() + router.ServeHTTP(response, request) + + res := response.Result() + assert.Equal(t, http.StatusOK, res.StatusCode) + + var result map[string]uint64 + err = json.NewDecoder(res.Body).Decode(&result) + assert.NoError(t, err) + assert.Equal(t, likesCount, result["albumLikesCount"]) + }) + + t.Run("Invalid album ID", func(t *testing.T) { + router := mux.NewRouter() + router.HandleFunc("/api/v1/albums/likes/{albumID}", albumHandlers.GetAlbumLikesCount).Methods("GET") + + request, err := http.NewRequest(http.MethodGet, "/api/v1/albums/likes/abc", nil) + assert.NoError(t, err) + + response := httptest.NewRecorder() + router.ServeHTTP(response, request) + + res := response.Result() + assert.Equal(t, http.StatusBadRequest, res.StatusCode) + assert.Contains(t, response.Body.String(), "Invalid album ID") + }) + + t.Run("Error while getting album likes count", func(t *testing.T) { + albumID := uint64(123) + mockError := fmt.Errorf("usecase error") + usecaseMock.EXPECT().GetAlbumLikesCount(gomock.Any(), albumID).Return(uint64(0), mockError) + + router := mux.NewRouter() + router.HandleFunc("/api/v1/albums/likes/{albumID:[0-9]+}", albumHandlers.GetAlbumLikesCount).Methods("GET") + + request, err := http.NewRequest(http.MethodGet, fmt.Sprintf("/api/v1/albums/likes/%d", albumID), nil) + assert.NoError(t, err) + + response := httptest.NewRecorder() + router.ServeHTTP(response, request) + + res := response.Result() + assert.Equal(t, http.StatusInternalServerError, res.StatusCode) + assert.Contains(t, response.Body.String(), "Can't check is album in favorite") + }) +} diff --git a/microservices/album/delivery/http/routes.go b/microservices/album/delivery/http/routes.go index dd38225..2567bce 100644 --- a/microservices/album/delivery/http/routes.go +++ b/microservices/album/delivery/http/routes.go @@ -28,6 +28,11 @@ func BindRoutes(s *httpServer.Server, artistClient artistService.ArtistServiceCl middleware.AuthMiddleware(&s.CFG.Service.Auth, s.Logger, http.HandlerFunc(albumHandleres.GetFavoriteAlbums)), ).Methods("GET") + s.MUX.Handle( + "/api/v1/albums/favorite/count/{userID:[0-9a-fA-F-]+}", + middleware.AuthMiddleware(&s.CFG.Service.Auth, s.Logger, http.HandlerFunc(albumHandleres.GetFavoriteAlbumsCount)), + ).Methods("GET") + s.MUX.Handle( "/api/v1/albums/favorite/{albumID:[0-9]+}", middleware.AuthMiddleware(&s.CFG.Service.Auth, s.Logger, http.HandlerFunc(albumHandleres.IsFavoriteAlbum)), @@ -42,4 +47,9 @@ func BindRoutes(s *httpServer.Server, artistClient artistService.ArtistServiceCl "/api/v1/albums/favorite/{albumID:[0-9]+}", middleware.AuthMiddleware(&s.CFG.Service.Auth, s.Logger, http.HandlerFunc(albumHandleres.DeleteFavoriteAlbum)), ).Methods("DELETE") + + s.MUX.Handle( + "/api/v1/albums/likes/{albumID:[0-9]+}", + middleware.AuthMiddleware(&s.CFG.Service.Auth, s.Logger, http.HandlerFunc(albumHandleres.GetAlbumLikesCount)), + ).Methods("GET") } diff --git a/microservices/album/mock/repository_mock.go b/microservices/album/mock/repository_mock.go index 2b95c72..417fe9b 100644 --- a/microservices/album/mock/repository_mock.go +++ b/microservices/album/mock/repository_mock.go @@ -109,6 +109,21 @@ func (mr *MockRepoMockRecorder) FindByQuery(ctx, query interface{}) *gomock.Call return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "FindByQuery", reflect.TypeOf((*MockRepo)(nil).FindByQuery), ctx, query) } +// GetAlbumLikesCount mocks base method. +func (m *MockRepo) GetAlbumLikesCount(ctx context.Context, albumID uint64) (uint64, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetAlbumLikesCount", ctx, albumID) + ret0, _ := ret[0].(uint64) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetAlbumLikesCount indicates an expected call of GetAlbumLikesCount. +func (mr *MockRepoMockRecorder) GetAlbumLikesCount(ctx, albumID interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetAlbumLikesCount", reflect.TypeOf((*MockRepo)(nil).GetAlbumLikesCount), ctx, albumID) +} + // GetAll mocks base method. func (m *MockRepo) GetAll(ctx context.Context) ([]*models.Album, error) { m.ctrl.T.Helper() @@ -154,6 +169,21 @@ func (mr *MockRepoMockRecorder) GetFavoriteAlbums(ctx, userID interface{}) *gomo return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetFavoriteAlbums", reflect.TypeOf((*MockRepo)(nil).GetFavoriteAlbums), ctx, userID) } +// GetFavoriteAlbumsCount mocks base method. +func (m *MockRepo) GetFavoriteAlbumsCount(ctx context.Context, userID uuid.UUID) (uint64, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetFavoriteAlbumsCount", ctx, userID) + ret0, _ := ret[0].(uint64) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetFavoriteAlbumsCount indicates an expected call of GetFavoriteAlbumsCount. +func (mr *MockRepoMockRecorder) GetFavoriteAlbumsCount(ctx, userID interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetFavoriteAlbumsCount", reflect.TypeOf((*MockRepo)(nil).GetFavoriteAlbumsCount), ctx, userID) +} + // IsFavoriteAlbum mocks base method. func (m *MockRepo) IsFavoriteAlbum(ctx context.Context, userID uuid.UUID, albumID uint64) (bool, error) { m.ctrl.T.Helper() diff --git a/microservices/album/mock/usecase_mock.go b/microservices/album/mock/usecase_mock.go index ae4f45f..195d29b 100644 --- a/microservices/album/mock/usecase_mock.go +++ b/microservices/album/mock/usecase_mock.go @@ -64,6 +64,21 @@ func (mr *MockUsecaseMockRecorder) DeleteFavoriteAlbum(ctx, userID, albumID inte return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteFavoriteAlbum", reflect.TypeOf((*MockUsecase)(nil).DeleteFavoriteAlbum), ctx, userID, albumID) } +// GetAlbumLikesCount mocks base method. +func (m *MockUsecase) GetAlbumLikesCount(ctx context.Context, albumID uint64) (uint64, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetAlbumLikesCount", ctx, albumID) + ret0, _ := ret[0].(uint64) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetAlbumLikesCount indicates an expected call of GetAlbumLikesCount. +func (mr *MockUsecaseMockRecorder) GetAlbumLikesCount(ctx, albumID interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetAlbumLikesCount", reflect.TypeOf((*MockUsecase)(nil).GetAlbumLikesCount), ctx, albumID) +} + // GetAll mocks base method. func (m *MockUsecase) GetAll(ctx context.Context) ([]*dto.AlbumDTO, error) { m.ctrl.T.Helper() @@ -109,6 +124,21 @@ func (mr *MockUsecaseMockRecorder) GetFavoriteAlbums(ctx, userID interface{}) *g return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetFavoriteAlbums", reflect.TypeOf((*MockUsecase)(nil).GetFavoriteAlbums), ctx, userID) } +// GetFavoriteAlbumsCount mocks base method. +func (m *MockUsecase) GetFavoriteAlbumsCount(ctx context.Context, userID uuid.UUID) (uint64, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetFavoriteAlbumsCount", ctx, userID) + ret0, _ := ret[0].(uint64) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetFavoriteAlbumsCount indicates an expected call of GetFavoriteAlbumsCount. +func (mr *MockUsecaseMockRecorder) GetFavoriteAlbumsCount(ctx, userID interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetFavoriteAlbumsCount", reflect.TypeOf((*MockUsecase)(nil).GetFavoriteAlbumsCount), ctx, userID) +} + // IsFavoriteAlbum mocks base method. func (m *MockUsecase) IsFavoriteAlbum(ctx context.Context, userID uuid.UUID, albumID uint64) (bool, error) { m.ctrl.T.Helper() diff --git a/microservices/album/repository.go b/microservices/album/repository.go index 38148c6..824fa5d 100644 --- a/microservices/album/repository.go +++ b/microservices/album/repository.go @@ -17,4 +17,6 @@ type Repo interface { DeleteFavoriteAlbum(ctx context.Context, userID uuid.UUID, albumID uint64) error IsFavoriteAlbum(ctx context.Context, userID uuid.UUID, albumID uint64) (bool, error) GetFavoriteAlbums(ctx context.Context, userID uuid.UUID) ([]*models.Album, error) + GetFavoriteAlbumsCount(ctx context.Context, userID uuid.UUID) (uint64, error) + GetAlbumLikesCount(ctx context.Context, albumID uint64) (uint64, error) } diff --git a/microservices/album/repository/pg_repository.go b/microservices/album/repository/pg_repository.go index dd2a237..979fed1 100644 --- a/microservices/album/repository/pg_repository.go +++ b/microservices/album/repository/pg_repository.go @@ -205,3 +205,23 @@ func (r *AlbumRepository) GetFavoriteAlbums(ctx context.Context, userID uuid.UUI return albums, nil } + +func (r *AlbumRepository) GetFavoriteAlbumsCount(ctx context.Context, userID uuid.UUID) (uint64, error) { + var count uint64 + err := r.db.QueryRowContext(ctx, getFavoriteCountQuery, userID).Scan(&count) + if err != nil && err != sql.ErrNoRows { + return 0, errors.Wrap(err, "GetFavoriteCount.Query") + } + + return count, nil +} + +func (r *AlbumRepository) GetAlbumLikesCount(ctx context.Context, albumID uint64) (uint64, error) { + var likesCount uint64 + err := r.db.QueryRowContext(ctx, getLikesCountQuery, albumID).Scan(&likesCount) + if err != nil && err != sql.ErrNoRows { + return 0, errors.Wrap(err, "GetLikesCount.Query") + } + + return likesCount, nil +} diff --git a/microservices/album/repository/pg_repository_test.go b/microservices/album/repository/pg_repository_test.go index 532c697..c4f7719 100644 --- a/microservices/album/repository/pg_repository_test.go +++ b/microservices/album/repository/pg_repository_test.go @@ -369,3 +369,47 @@ func TestAlbumRepositoryGetFavoriteAlbums(t *testing.T) { require.NotNil(t, foundAlbums) require.Equal(t, foundAlbums, expectedAlbums) } + +func TestAlbumRepositoryGetFavoriteAlbumsCount(t *testing.T) { + t.Parallel() + + db, mock, err := sqlmock.New(sqlmock.QueryMatcherOption(sqlmock.QueryMatcherEqual)) + require.NoError(t, err) + defer db.Close() + + albumRepository := NewAlbumPGRepository(db) + + userID := uuid.New() + expectedCount := uint64(2) + + mock.ExpectQuery(getFavoriteCountQuery).WithArgs(userID).WillReturnRows(sqlmock.NewRows([]string{"count"}).AddRow(expectedCount)) + + count, err := albumRepository.GetFavoriteAlbumsCount(context.Background(), userID) + require.NoError(t, err) + require.Equal(t, expectedCount, count) + + err = mock.ExpectationsWereMet() + require.NoError(t, err) +} + +func TestAlbumRepositoryGetAlbumLikesCount(t *testing.T) { + t.Parallel() + + db, mock, err := sqlmock.New(sqlmock.QueryMatcherOption(sqlmock.QueryMatcherEqual)) + require.NoError(t, err) + defer db.Close() + + albumRepository := NewAlbumPGRepository(db) + + albumID := uint64(123) + expectedLikesCount := uint64(10) + + mock.ExpectQuery(getLikesCountQuery).WithArgs(albumID).WillReturnRows(sqlmock.NewRows([]string{"likes_count"}).AddRow(expectedLikesCount)) + + likesCount, err := albumRepository.GetAlbumLikesCount(context.Background(), albumID) + require.NoError(t, err) + require.Equal(t, expectedLikesCount, likesCount) + + err = mock.ExpectationsWereMet() + require.NoError(t, err) +} diff --git a/microservices/album/repository/sql_queries.go b/microservices/album/repository/sql_queries.go index 9a99eac..062fba5 100644 --- a/microservices/album/repository/sql_queries.go +++ b/microservices/album/repository/sql_queries.go @@ -37,4 +37,15 @@ const ( JOIN favorite_album AS fa ON a.id = fa.album_id WHERE fa.user_id = $1` + + getFavoriteCountQuery = ` + SELECT COUNT(*) + FROM album AS a + JOIN favorite_album AS fa + ON a.id = fa.album_id + WHERE fa.user_id = $1` + + getLikesCountQuery = ` + SELECT COUNT(*) + FROM favorite_album WHERE album_id = $1` ) diff --git a/microservices/album/usecase.go b/microservices/album/usecase.go index a288220..b0759ea 100644 --- a/microservices/album/usecase.go +++ b/microservices/album/usecase.go @@ -16,4 +16,6 @@ type Usecase interface { DeleteFavoriteAlbum(ctx context.Context, userID uuid.UUID, albumID uint64) error IsFavoriteAlbum(ctx context.Context, userID uuid.UUID, albumID uint64) (bool, error) GetFavoriteAlbums(ctx context.Context, userID uuid.UUID) ([]*dto.AlbumDTO, error) + GetFavoriteAlbumsCount(ctx context.Context, userID uuid.UUID) (uint64, error) + GetAlbumLikesCount(ctx context.Context, albumID uint64) (uint64, error) } diff --git a/microservices/album/usecase/usecase.go b/microservices/album/usecase/usecase.go index 96ef310..e122b80 100644 --- a/microservices/album/usecase/usecase.go +++ b/microservices/album/usecase/usecase.go @@ -165,6 +165,30 @@ func (usecase *albumUsecase) GetFavoriteAlbums(ctx context.Context, userID uuid. return dtoAlbums, nil } +func (usecase *albumUsecase) GetFavoriteAlbumsCount(ctx context.Context, userID uuid.UUID) (uint64, error) { + requestID := ctx.Value(utils.RequestIDKey{}) + count, err := usecase.albumRepo.GetFavoriteAlbumsCount(ctx, userID) + if err != nil { + usecase.logger.Warn(fmt.Sprintf("Can't load favorite albums count by user ID %v: %v", userID, err), requestID) + return 0, fmt.Errorf("Can't load albums by user ID %v", userID) + } + usecase.logger.Infof("Found %d favorite albums for user ID %v", count, userID) + + return count, nil +} + +func (usecase *albumUsecase) GetAlbumLikesCount(ctx context.Context, albumID uint64) (uint64, error) { + requestID := ctx.Value(utils.RequestIDKey{}) + likesCount, err := usecase.albumRepo.GetAlbumLikesCount(ctx, albumID) + if err != nil { + usecase.logger.Warn(fmt.Sprintf("Can't load album likes count by user ID %v: %v", albumID, err), requestID) + return 0, fmt.Errorf("Can't load albums by user ID %v", albumID) + } + usecase.logger.Infof("Found %d favorite albums for user ID %v", likesCount, albumID) + + return likesCount, nil +} + func (usecase *albumUsecase) convertAlbumToDTO(ctx context.Context, album *models.Album) (*dto.AlbumDTO, error) { requestID := ctx.Value(utils.RequestIDKey{}) artist, err := usecase.artistClient.FindByID(ctx, &artistService.FindByIDRequest{Id: album.ArtistID}) diff --git a/microservices/album/usecase/usecase_test.go b/microservices/album/usecase/usecase_test.go index 62227d7..66add02 100644 --- a/microservices/album/usecase/usecase_test.go +++ b/microservices/album/usecase/usecase_test.go @@ -636,3 +636,133 @@ func TestUsecase_GetFavoriteAlbums_NotFoundAlbums(t *testing.T) { require.Nil(t, dtoAlbums) require.EqualError(t, err, fmt.Sprintf("Can't load albums by user ID %v", userID)) } + +func TestUsecase_GetFavoriteAlbumsCount_FoundAlbums(t *testing.T) { + t.Parallel() + + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + cfg := &config.Config{ + Service: config.ServiceConfig{ + Logger: config.LoggerConfig{ + Level: "info", + Format: "json", + }, + }, + } + + logger := logger.New(&cfg.Service.Logger) + albumRepoMock := mockAlbum.NewMockRepo(ctrl) + artistClientMock := mockArtist.NewMockArtistServiceClient(ctrl) + albumUsecase := NewAlbumUsecase(albumRepoMock, artistClientMock, logger) + + userID := uuid.New() + ctx := context.Background() + expectedCount := uint64(3) + albumRepoMock.EXPECT().GetFavoriteAlbumsCount(ctx, userID).Return(expectedCount, nil) + + count, err := albumUsecase.GetFavoriteAlbumsCount(ctx, userID) + + require.NoError(t, err) + require.Equal(t, expectedCount, count) +} + +func TestUsecase_GetFavoriteAlbumsCount_ErrorGettingCount(t *testing.T) { + t.Parallel() + + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + cfg := &config.Config{ + Service: config.ServiceConfig{ + Logger: config.LoggerConfig{ + Level: "info", + Format: "json", + }, + }, + } + + logger := logger.New(&cfg.Service.Logger) + albumRepoMock := mockAlbum.NewMockRepo(ctrl) + artistClientMock := mockArtist.NewMockArtistServiceClient(ctrl) + albumUsecase := NewAlbumUsecase(albumRepoMock, artistClientMock, logger) + + userID := uuid.New() + ctx := context.Background() + expectedError := fmt.Errorf("Can't load albums by user ID %v", userID) + albumRepoMock.EXPECT().GetFavoriteAlbumsCount(ctx, userID).Return(uint64(0), expectedError) + + count, err := albumUsecase.GetFavoriteAlbumsCount(ctx, userID) + + require.Error(t, err) + require.EqualError(t, err, expectedError.Error()) + require.Equal(t, uint64(0), count) +} + +func TestUsecase_GetAlbumLikesCount_Success(t *testing.T) { + t.Parallel() + + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + cfg := &config.Config{ + Service: config.ServiceConfig{ + Logger: config.LoggerConfig{ + Level: "info", + Format: "json", + }, + }, + } + + logger := logger.New(&cfg.Service.Logger) + albumRepoMock := mockAlbum.NewMockRepo(ctrl) + artistClientMock := mockArtist.NewMockArtistServiceClient(ctrl) + albumUsecase := NewAlbumUsecase(albumRepoMock, artistClientMock, logger) + + ctx := context.Background() + ctx = context.WithValue(ctx, utils.RequestIDKey{}, "test-request-id") + + albumID := uint64(123) + expectedLikesCount := uint64(10) + + albumRepoMock.EXPECT().GetAlbumLikesCount(ctx, albumID).Return(expectedLikesCount, nil) + + likesCount, err := albumUsecase.GetAlbumLikesCount(ctx, albumID) + require.NoError(t, err) + require.Equal(t, expectedLikesCount, likesCount) +} + +func TestUsecase_GetAlbumLikesCount_ErrorGettingLikesCount(t *testing.T) { + t.Parallel() + + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + cfg := &config.Config{ + Service: config.ServiceConfig{ + Logger: config.LoggerConfig{ + Level: "info", + Format: "json", + }, + }, + } + + logger := logger.New(&cfg.Service.Logger) + albumRepoMock := mockAlbum.NewMockRepo(ctrl) + artistClientMock := mockArtist.NewMockArtistServiceClient(ctrl) + albumUsecase := NewAlbumUsecase(albumRepoMock, artistClientMock, logger) + + ctx := context.Background() + ctx = context.WithValue(ctx, utils.RequestIDKey{}, "test-request-id") + + albumID := uint64(123) + expectedError := fmt.Errorf("Can't load albums by user ID %v", albumID) + + albumRepoMock.EXPECT().GetAlbumLikesCount(ctx, albumID).Return(uint64(0), expectedError) + + likesCount, err := albumUsecase.GetAlbumLikesCount(ctx, albumID) + require.Error(t, err) + require.EqualError(t, err, expectedError.Error()) + require.Equal(t, uint64(0), likesCount) +} diff --git a/microservices/artist/delivery.go b/microservices/artist/delivery.go index 98bc869..f7dbf22 100644 --- a/microservices/artist/delivery.go +++ b/microservices/artist/delivery.go @@ -10,4 +10,6 @@ type Handlers interface { DeleteFavoriteArtist(response http.ResponseWriter, request *http.Request) IsFavoriteArtist(response http.ResponseWriter, request *http.Request) GetFavoriteArtists(response http.ResponseWriter, request *http.Request) + GetFavoriteArtistsCount(response http.ResponseWriter, request *http.Request) + GetArtistLikesCount(response http.ResponseWriter, request *http.Request) } diff --git a/microservices/artist/delivery/http/handlers.go b/microservices/artist/delivery/http/handlers.go index beb1d1e..16a1d12 100644 --- a/microservices/artist/delivery/http/handlers.go +++ b/microservices/artist/delivery/http/handlers.go @@ -275,3 +275,60 @@ func (handlers *artistHandlers) GetFavoriteArtists(response http.ResponseWriter, response.WriteHeader(http.StatusOK) } + +func (handlers *artistHandlers) GetFavoriteArtistsCount(response http.ResponseWriter, request *http.Request) { + requestID := request.Context().Value(utils.RequestIDKey{}) + vars := mux.Vars(request) + userID, err := uuid.Parse(vars["userID"]) + if err != nil { + handlers.logger.Error(fmt.Sprintf("Get '%s' wrong user id: %v", vars["userID"], err), requestID) + utils.JSONError(response, http.StatusBadRequest, "Wrong id value") + return + } + + count, err := handlers.usecase.GetFavoriteArtistsCount(request.Context(), userID) + if err != nil { + handlers.logger.Error(fmt.Sprintf("Failed to get favorite artists count: %v", err), requestID) + utils.JSONError(response, http.StatusInternalServerError, fmt.Sprintf("Failed to get favorite artists count: %v", err)) + return + } else if count == 0 { + utils.JSONError(response, http.StatusNotFound, "No favorite artists were found") + return + } + + response.Header().Set("Content-Type", "application/json") + if err := json.NewEncoder(response).Encode(map[string]uint64{"favoriteArtistsCount": count}); err != nil { + handlers.logger.Error(fmt.Sprintf("Failed to encode favorite artists count: %v", err), requestID) + utils.JSONError(response, http.StatusInternalServerError, fmt.Sprintf("Failed to encode favorite artists count: %v", err)) + return + } + + response.WriteHeader(http.StatusOK) +} + +func (handlers *artistHandlers) GetArtistLikesCount(response http.ResponseWriter, request *http.Request) { + requestID := request.Context().Value(utils.RequestIDKey{}) + vars := mux.Vars(request) + artistID, err := strconv.ParseUint(vars["artistID"], 10, 64) + if err != nil { + handlers.logger.Error(fmt.Sprintf("Invalid artist ID: %v", err), requestID) + utils.JSONError(response, http.StatusBadRequest, fmt.Sprintf("Invalid artist ID: %v", err)) + return + } + + likesCount, err := handlers.usecase.GetArtistLikesCount(request.Context(), artistID) + if err != nil { + handlers.logger.Error("Failed to get artist likes count", requestID) + utils.JSONError(response, http.StatusInternalServerError, "Can't get artist likes count") + return + } + + response.Header().Set("Content-Type", "application/json") + if err := json.NewEncoder(response).Encode(map[string]uint64{"artistLikesCount": likesCount}); err != nil { + handlers.logger.Error(fmt.Sprintf("Failed to encode: %v", err), requestID) + utils.JSONError(response, http.StatusInternalServerError, fmt.Sprintf("Failed to encode: %v", err)) + return + } + + response.WriteHeader(http.StatusOK) +} diff --git a/microservices/artist/delivery/http/handlers_test.go b/microservices/artist/delivery/http/handlers_test.go index 564a3f8..69f2d0d 100644 --- a/microservices/artist/delivery/http/handlers_test.go +++ b/microservices/artist/delivery/http/handlers_test.go @@ -473,3 +473,157 @@ func TestArtistHandlers_GetFavoriteArtists(t *testing.T) { assert.Equal(t, http.StatusInternalServerError, res.StatusCode) }) } + +func TestArtistHandlers_GetFavoriteArtistsCount(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + cfg := &config.Config{} + logger := logger.New(&cfg.Service.Logger) + usecaseMock := mocks.NewMockUsecase(ctrl) + artistHandlers := NewArtistHandlers(usecaseMock, logger) + + t.Run("Success", func(t *testing.T) { + userID := uuid.New() + count := uint64(2) + + usecaseMock.EXPECT().GetFavoriteArtistsCount(gomock.Any(), userID).Return(count, nil) + + router := mux.NewRouter() + router.HandleFunc("/api/v1/artists/favorite/count/{userID:[0-9a-fA-F-]+}", artistHandlers.GetFavoriteArtistsCount).Methods("GET") + + request, err := http.NewRequest(http.MethodGet, fmt.Sprintf("/api/v1/artists/favorite/count/%s", userID.String()), nil) + assert.NoError(t, err) + + response := httptest.NewRecorder() + router.ServeHTTP(response, request) + + res := response.Result() + assert.Equal(t, http.StatusOK, res.StatusCode) + + var result map[string]uint64 + err = json.NewDecoder(res.Body).Decode(&result) + assert.NoError(t, err) + assert.Equal(t, count, result["favoriteArtistsCount"]) + }) + + t.Run("Wrong id value", func(t *testing.T) { + router := mux.NewRouter() + router.HandleFunc("/api/v1/artists/favorite/count/{userID:[0-9a-fA-F-]+}", artistHandlers.GetFavoriteArtistsCount).Methods("GET") + + request, err := http.NewRequest(http.MethodGet, "/api/v1/artists/favorite/count/123", nil) + assert.NoError(t, err) + + response := httptest.NewRecorder() + router.ServeHTTP(response, request) + + res := response.Result() + assert.Equal(t, http.StatusBadRequest, res.StatusCode) + assert.Contains(t, response.Body.String(), "Wrong id value") + }) + + t.Run("Error while getting favorite artists count", func(t *testing.T) { + userID := uuid.New() + mockError := fmt.Errorf("usecase error") + usecaseMock.EXPECT().GetFavoriteArtistsCount(gomock.Any(), userID).Return(uint64(0), mockError) + + router := mux.NewRouter() + router.HandleFunc("/api/v1/artists/favorite/count/{userID:[0-9a-fA-F-]+}", artistHandlers.GetFavoriteArtistsCount).Methods("GET") + + request, err := http.NewRequest(http.MethodGet, fmt.Sprintf("/api/v1/artists/favorite/count/%s", userID.String()), nil) + assert.NoError(t, err) + + response := httptest.NewRecorder() + router.ServeHTTP(response, request) + + res := response.Result() + assert.Equal(t, http.StatusInternalServerError, res.StatusCode) + assert.Contains(t, response.Body.String(), "Failed to get favorite artists count") + }) + + t.Run("No favorite artists found", func(t *testing.T) { + userID := uuid.New() + usecaseMock.EXPECT().GetFavoriteArtistsCount(gomock.Any(), userID).Return(uint64(0), nil) + + router := mux.NewRouter() + router.HandleFunc("/api/v1/artists/favorite/count/{userID:[0-9a-fA-F-]+}", artistHandlers.GetFavoriteArtistsCount).Methods("GET") + + request, err := http.NewRequest(http.MethodGet, fmt.Sprintf("/api/v1/artists/favorite/count/%s", userID.String()), nil) + assert.NoError(t, err) + + response := httptest.NewRecorder() + router.ServeHTTP(response, request) + + res := response.Result() + assert.Equal(t, http.StatusNotFound, res.StatusCode) + assert.Contains(t, response.Body.String(), "No favorite artists were found") + }) +} + +func TestAlbumHandlers_GetArtistLikesCount(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + cfg := &config.Config{} + logger := logger.New(&cfg.Service.Logger) + usecaseMock := mocks.NewMockUsecase(ctrl) + albumHandlers := NewArtistHandlers(usecaseMock, logger) + + t.Run("Success", func(t *testing.T) { + artistID := uint64(123) + likesCount := uint64(10) + + usecaseMock.EXPECT().GetArtistLikesCount(gomock.Any(), artistID).Return(likesCount, nil) + + router := mux.NewRouter() + router.HandleFunc("/api/v1/artists/likes/{artistID:[0-9]+}", albumHandlers.GetArtistLikesCount).Methods("GET") + + request, err := http.NewRequest(http.MethodGet, fmt.Sprintf("/api/v1/artists/likes/%d", artistID), nil) + assert.NoError(t, err) + + response := httptest.NewRecorder() + router.ServeHTTP(response, request) + + res := response.Result() + assert.Equal(t, http.StatusOK, res.StatusCode) + + var result map[string]uint64 + err = json.NewDecoder(res.Body).Decode(&result) + assert.NoError(t, err) + assert.Equal(t, likesCount, result["artistLikesCount"]) + }) + + t.Run("Invalid artist ID", func(t *testing.T) { + router := mux.NewRouter() + router.HandleFunc("/api/v1/artists/likes/{artistID}", albumHandlers.GetArtistLikesCount).Methods("GET") + + request, err := http.NewRequest(http.MethodGet, "/api/v1/artists/likes/abc", nil) + assert.NoError(t, err) + + response := httptest.NewRecorder() + router.ServeHTTP(response, request) + + res := response.Result() + assert.Equal(t, http.StatusBadRequest, res.StatusCode) + assert.Contains(t, response.Body.String(), "Invalid artist ID") + }) + + t.Run("Error while getting artist likes count", func(t *testing.T) { + artistID := uint64(123) + mockError := fmt.Errorf("usecase error") + usecaseMock.EXPECT().GetArtistLikesCount(gomock.Any(), artistID).Return(uint64(0), mockError) + + router := mux.NewRouter() + router.HandleFunc("/api/v1/artists/likes/{artistID:[0-9]+}", albumHandlers.GetArtistLikesCount).Methods("GET") + + request, err := http.NewRequest(http.MethodGet, fmt.Sprintf("/api/v1/artists/likes/%d", artistID), nil) + assert.NoError(t, err) + + response := httptest.NewRecorder() + router.ServeHTTP(response, request) + + res := response.Result() + assert.Equal(t, http.StatusInternalServerError, res.StatusCode) + assert.Contains(t, response.Body.String(), "Can't get artist likes count") + }) +} diff --git a/microservices/artist/delivery/http/routes.go b/microservices/artist/delivery/http/routes.go index 617d7cf..c300705 100644 --- a/microservices/artist/delivery/http/routes.go +++ b/microservices/artist/delivery/http/routes.go @@ -26,6 +26,11 @@ func BindRoutes(s *httpServer.Server) { middleware.AuthMiddleware(&s.CFG.Service.Auth, s.Logger, http.HandlerFunc(artistHandlers.GetFavoriteArtists)), ).Methods("GET") + s.MUX.Handle( + "/api/v1/artists/favorite/count/{userID:[0-9a-fA-F-]+}", + middleware.AuthMiddleware(&s.CFG.Service.Auth, s.Logger, http.HandlerFunc(artistHandlers.GetFavoriteArtistsCount)), + ).Methods("GET") + s.MUX.Handle( "/api/v1/artists/favorite/{artistID:[0-9]+}", middleware.AuthMiddleware(&s.CFG.Service.Auth, s.Logger, http.HandlerFunc(artistHandlers.IsFavoriteArtist)), @@ -40,4 +45,9 @@ func BindRoutes(s *httpServer.Server) { "/api/v1/artists/favorite/{artistID:[0-9]+}", middleware.AuthMiddleware(&s.CFG.Service.Auth, s.Logger, http.HandlerFunc(artistHandlers.DeleteFavoriteArtist)), ).Methods("DELETE") + + s.MUX.Handle( + "/api/v1/artists/likes/{artistID:[0-9]+}", + middleware.AuthMiddleware(&s.CFG.Service.Auth, s.Logger, http.HandlerFunc(artistHandlers.GetArtistLikesCount)), + ).Methods("GET") } diff --git a/microservices/artist/mock/repository_mock.go b/microservices/artist/mock/repository_mock.go index ba4acfc..9dadb24 100644 --- a/microservices/artist/mock/repository_mock.go +++ b/microservices/artist/mock/repository_mock.go @@ -124,6 +124,21 @@ func (mr *MockRepoMockRecorder) GetAll(ctx interface{}) *gomock.Call { return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetAll", reflect.TypeOf((*MockRepo)(nil).GetAll), ctx) } +// GetArtistLikesCount mocks base method. +func (m *MockRepo) GetArtistLikesCount(ctx context.Context, artistID uint64) (uint64, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetArtistLikesCount", ctx, artistID) + ret0, _ := ret[0].(uint64) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetArtistLikesCount indicates an expected call of GetArtistLikesCount. +func (mr *MockRepoMockRecorder) GetArtistLikesCount(ctx, artistID interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetArtistLikesCount", reflect.TypeOf((*MockRepo)(nil).GetArtistLikesCount), ctx, artistID) +} + // GetFavoriteArtists mocks base method. func (m *MockRepo) GetFavoriteArtists(ctx context.Context, userID uuid.UUID) ([]*models.Artist, error) { m.ctrl.T.Helper() @@ -139,6 +154,21 @@ func (mr *MockRepoMockRecorder) GetFavoriteArtists(ctx, userID interface{}) *gom return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetFavoriteArtists", reflect.TypeOf((*MockRepo)(nil).GetFavoriteArtists), ctx, userID) } +// GetFavoriteArtistsCount mocks base method. +func (m *MockRepo) GetFavoriteArtistsCount(ctx context.Context, userID uuid.UUID) (uint64, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetFavoriteArtistsCount", ctx, userID) + ret0, _ := ret[0].(uint64) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetFavoriteArtistsCount indicates an expected call of GetFavoriteArtistsCount. +func (mr *MockRepoMockRecorder) GetFavoriteArtistsCount(ctx, userID interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetFavoriteArtistsCount", reflect.TypeOf((*MockRepo)(nil).GetFavoriteArtistsCount), ctx, userID) +} + // IsFavoriteArtist mocks base method. func (m *MockRepo) IsFavoriteArtist(ctx context.Context, userID uuid.UUID, artistID uint64) (bool, error) { m.ctrl.T.Helper() diff --git a/microservices/artist/mock/usecase_mock.go b/microservices/artist/mock/usecase_mock.go index a1bdf96..5e12777 100644 --- a/microservices/artist/mock/usecase_mock.go +++ b/microservices/artist/mock/usecase_mock.go @@ -79,6 +79,21 @@ func (mr *MockUsecaseMockRecorder) GetAll(ctx interface{}) *gomock.Call { return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetAll", reflect.TypeOf((*MockUsecase)(nil).GetAll), ctx) } +// GetArtistLikesCount mocks base method. +func (m *MockUsecase) GetArtistLikesCount(ctx context.Context, artistID uint64) (uint64, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetArtistLikesCount", ctx, artistID) + ret0, _ := ret[0].(uint64) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetArtistLikesCount indicates an expected call of GetArtistLikesCount. +func (mr *MockUsecaseMockRecorder) GetArtistLikesCount(ctx, artistID interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetArtistLikesCount", reflect.TypeOf((*MockUsecase)(nil).GetArtistLikesCount), ctx, artistID) +} + // GetFavoriteArtists mocks base method. func (m *MockUsecase) GetFavoriteArtists(ctx context.Context, userID uuid.UUID) ([]*dto.ArtistDTO, error) { m.ctrl.T.Helper() @@ -94,6 +109,21 @@ func (mr *MockUsecaseMockRecorder) GetFavoriteArtists(ctx, userID interface{}) * return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetFavoriteArtists", reflect.TypeOf((*MockUsecase)(nil).GetFavoriteArtists), ctx, userID) } +// GetFavoriteArtistsCount mocks base method. +func (m *MockUsecase) GetFavoriteArtistsCount(ctx context.Context, userID uuid.UUID) (uint64, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetFavoriteArtistsCount", ctx, userID) + ret0, _ := ret[0].(uint64) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetFavoriteArtistsCount indicates an expected call of GetFavoriteArtistsCount. +func (mr *MockUsecaseMockRecorder) GetFavoriteArtistsCount(ctx, userID interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetFavoriteArtistsCount", reflect.TypeOf((*MockUsecase)(nil).GetFavoriteArtistsCount), ctx, userID) +} + // IsFavoriteArtist mocks base method. func (m *MockUsecase) IsFavoriteArtist(ctx context.Context, userID uuid.UUID, artistID uint64) (bool, error) { m.ctrl.T.Helper() diff --git a/microservices/artist/repository.go b/microservices/artist/repository.go index 5967a6b..04aa508 100644 --- a/microservices/artist/repository.go +++ b/microservices/artist/repository.go @@ -16,4 +16,6 @@ type Repo interface { DeleteFavoriteArtist(ctx context.Context, userID uuid.UUID, artistID uint64) error IsFavoriteArtist(ctx context.Context, userID uuid.UUID, artistID uint64) (bool, error) GetFavoriteArtists(ctx context.Context, userID uuid.UUID) ([]*models.Artist, error) + GetFavoriteArtistsCount(ctx context.Context, userID uuid.UUID) (uint64, error) + GetArtistLikesCount(ctx context.Context, artistID uint64) (uint64, error) } diff --git a/microservices/artist/repository/pg_repository.go b/microservices/artist/repository/pg_repository.go index 4089d6c..345f1a6 100644 --- a/microservices/artist/repository/pg_repository.go +++ b/microservices/artist/repository/pg_repository.go @@ -176,3 +176,23 @@ func (r *ArtistRepository) GetFavoriteArtists(ctx context.Context, userID uuid.U return artists, nil } + +func (r *ArtistRepository) GetFavoriteArtistsCount(ctx context.Context, userID uuid.UUID) (uint64, error) { + var count uint64 + err := r.db.QueryRowContext(ctx, getFavoriteCountQuery, userID).Scan(&count) + if err != nil && err != sql.ErrNoRows { + return 0, errors.Wrap(err, "GetFavoriteArtistsCount.Query") + } + + return count, nil +} + +func (r *ArtistRepository) GetArtistLikesCount(ctx context.Context, artistID uint64) (uint64, error) { + var likesCount uint64 + err := r.db.QueryRowContext(ctx, getLikesCountQuery, artistID).Scan(&likesCount) + if err != nil && err != sql.ErrNoRows { + return 0, errors.Wrap(err, "GetLikesCount.Query") + } + + return likesCount, nil +} diff --git a/microservices/artist/repository/pg_repository_test.go b/microservices/artist/repository/pg_repository_test.go index 8a622f7..35ed778 100644 --- a/microservices/artist/repository/pg_repository_test.go +++ b/microservices/artist/repository/pg_repository_test.go @@ -317,3 +317,47 @@ func TestArtistRepositoryGetFavoriteArtists(t *testing.T) { require.NotNil(t, foundArtists) require.Equal(t, foundArtists, expectedArtists) } + +func TestArtistRepositoryGetFavoriteArtistsCount(t *testing.T) { + t.Parallel() + + db, mock, err := sqlmock.New(sqlmock.QueryMatcherOption(sqlmock.QueryMatcherEqual)) + require.NoError(t, err) + defer db.Close() + + artistRepository := NewArtistPGRepository(db) + + userID := uuid.New() + expectedCount := uint64(3) + + mock.ExpectQuery(getFavoriteCountQuery).WithArgs(userID).WillReturnRows(sqlmock.NewRows([]string{"count"}).AddRow(expectedCount)) + + count, err := artistRepository.GetFavoriteArtistsCount(context.Background(), userID) + require.NoError(t, err) + require.Equal(t, expectedCount, count) + + err = mock.ExpectationsWereMet() + require.NoError(t, err) +} + +func TestAlbumRepositoryGetArtistLikesCount(t *testing.T) { + t.Parallel() + + db, mock, err := sqlmock.New(sqlmock.QueryMatcherOption(sqlmock.QueryMatcherEqual)) + require.NoError(t, err) + defer db.Close() + + albumRepository := NewArtistPGRepository(db) + + artistID := uint64(456) + expectedLikesCount := uint64(20) + + mock.ExpectQuery(getLikesCountQuery).WithArgs(artistID).WillReturnRows(sqlmock.NewRows([]string{"likes_count"}).AddRow(expectedLikesCount)) + + likesCount, err := albumRepository.GetArtistLikesCount(context.Background(), artistID) + require.NoError(t, err) + require.Equal(t, expectedLikesCount, likesCount) + + err = mock.ExpectationsWereMet() + require.NoError(t, err) +} diff --git a/microservices/artist/repository/sql_queries.go b/microservices/artist/repository/sql_queries.go index e4d3ca3..ae97616 100644 --- a/microservices/artist/repository/sql_queries.go +++ b/microservices/artist/repository/sql_queries.go @@ -35,4 +35,15 @@ const ( JOIN favorite_artist AS fa ON a.id = fa.artist_id WHERE fa.user_id = $1` + + getFavoriteCountQuery = ` + SELECT COUNT(*) + FROM artist AS a + JOIN favorite_artist AS fa + ON a.id = fa.artist_id + WHERE fa.user_id = $1` + + getLikesCountQuery = ` + SELECT COUNT(*) + FROM favorite_artist WHERE artist_id = $1` ) diff --git a/microservices/artist/usecase.go b/microservices/artist/usecase.go index 8ba3454..341a930 100644 --- a/microservices/artist/usecase.go +++ b/microservices/artist/usecase.go @@ -15,4 +15,6 @@ type Usecase interface { DeleteFavoriteArtist(ctx context.Context, userID uuid.UUID, artistID uint64) error IsFavoriteArtist(ctx context.Context, userID uuid.UUID, artistID uint64) (bool, error) GetFavoriteArtists(ctx context.Context, userID uuid.UUID) ([]*dto.ArtistDTO, error) + GetFavoriteArtistsCount(ctx context.Context, userID uuid.UUID) (uint64, error) + GetArtistLikesCount(ctx context.Context, artistID uint64) (uint64, error) } diff --git a/microservices/artist/usecase/usecase.go b/microservices/artist/usecase/usecase.go index 92390a8..fe1aa78 100644 --- a/microservices/artist/usecase/usecase.go +++ b/microservices/artist/usecase/usecase.go @@ -136,6 +136,30 @@ func (usecase *artistUsecase) GetFavoriteArtists(ctx context.Context, userID uui return dtoArtists, nil } +func (usecase *artistUsecase) GetFavoriteArtistsCount(ctx context.Context, userID uuid.UUID) (uint64, error) { + requestID := ctx.Value(utils.RequestIDKey{}) + count, err := usecase.artistRepo.GetFavoriteArtistsCount(ctx, userID) + if err != nil { + usecase.logger.Warn(fmt.Sprintf("Can't load favorite artists count by user ID %v: %v", userID, err), requestID) + return 0, fmt.Errorf("Can't load artists by user ID %v", userID) + } + usecase.logger.Infof("Found %d favorite artists for user ID %v", count, userID) + + return count, nil +} + +func (usecase *artistUsecase) GetArtistLikesCount(ctx context.Context, artistID uint64) (uint64, error) { + requestID := ctx.Value(utils.RequestIDKey{}) + likesCount, err := usecase.artistRepo.GetArtistLikesCount(ctx, artistID) + if err != nil { + usecase.logger.Warn(fmt.Sprintf("Can't load artist likes count by artist ID %v: %v", artistID, err), requestID) + return 0, fmt.Errorf("Can't load artist likes count by artist ID %v", artistID) + } + usecase.logger.Infof("Found %d likes for artist ID %v", likesCount, artistID) + + return likesCount, nil +} + func (usecase *artistUsecase) convertArtistToDTO(artist *models.Artist) (*dto.ArtistDTO, error) { return dto.NewArtistDTO(artist), nil } diff --git a/microservices/artist/usecase/usecase_test.go b/microservices/artist/usecase/usecase_test.go index 356df28..3d0c542 100644 --- a/microservices/artist/usecase/usecase_test.go +++ b/microservices/artist/usecase/usecase_test.go @@ -351,3 +351,129 @@ func TestArtistUsecase_IsFavoriteArtist(t *testing.T) { require.False(t, exists) }) } + +func TestUsecase_GetFavoriteArtistsCount_FoundArtists(t *testing.T) { + t.Parallel() + + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + cfg := &config.Config{ + Service: config.ServiceConfig{ + Logger: config.LoggerConfig{ + Level: "info", + Format: "json", + }, + }, + } + + logger := logger.New(&cfg.Service.Logger) + artistRepoMock := mockArtist.NewMockRepo(ctrl) + artistUsecase := NewArtistUsecase(artistRepoMock, logger) + + userID := uuid.New() + ctx := context.Background() + expectedCount := uint64(5) + artistRepoMock.EXPECT().GetFavoriteArtistsCount(ctx, userID).Return(expectedCount, nil) + + count, err := artistUsecase.GetFavoriteArtistsCount(ctx, userID) + + require.NoError(t, err) + require.Equal(t, expectedCount, count) +} + +func TestUsecase_GetFavoriteArtistsCount_ErrorGettingCount(t *testing.T) { + t.Parallel() + + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + cfg := &config.Config{ + Service: config.ServiceConfig{ + Logger: config.LoggerConfig{ + Level: "info", + Format: "json", + }, + }, + } + + logger := logger.New(&cfg.Service.Logger) + artistRepoMock := mockArtist.NewMockRepo(ctrl) + artistUsecase := NewArtistUsecase(artistRepoMock, logger) + + userID := uuid.New() + ctx := context.Background() + expectedError := fmt.Errorf("Can't load artists by user ID %v", userID) + artistRepoMock.EXPECT().GetFavoriteArtistsCount(ctx, userID).Return(uint64(0), expectedError) + + count, err := artistUsecase.GetFavoriteArtistsCount(ctx, userID) + + require.Error(t, err) + require.EqualError(t, err, expectedError.Error()) + require.Equal(t, uint64(0), count) +} + +func TestUsecase_GetArtistLikesCount_Success(t *testing.T) { + t.Parallel() + + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + cfg := &config.Config{ + Service: config.ServiceConfig{ + Logger: config.LoggerConfig{ + Level: "info", + Format: "json", + }, + }, + } + + logger := logger.New(&cfg.Service.Logger) + artistRepoMock := mockArtist.NewMockRepo(ctrl) + artistUsecase := NewArtistUsecase(artistRepoMock, logger) + + ctx := context.Background() + ctx = context.WithValue(ctx, utils.RequestIDKey{}, "test-request-id") + + artistID := uint64(123) + expectedLikesCount := uint64(10) + + artistRepoMock.EXPECT().GetArtistLikesCount(ctx, artistID).Return(expectedLikesCount, nil) + + likesCount, err := artistUsecase.GetArtistLikesCount(ctx, artistID) + require.NoError(t, err) + require.Equal(t, expectedLikesCount, likesCount) +} + +func TestUsecase_GetArtistLikesCount_ErrorGettingLikesCount(t *testing.T) { + t.Parallel() + + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + cfg := &config.Config{ + Service: config.ServiceConfig{ + Logger: config.LoggerConfig{ + Level: "info", + Format: "json", + }, + }, + } + + logger := logger.New(&cfg.Service.Logger) + artistRepoMock := mockArtist.NewMockRepo(ctrl) + artistUsecase := NewArtistUsecase(artistRepoMock, logger) + + ctx := context.Background() + ctx = context.WithValue(ctx, utils.RequestIDKey{}, "test-request-id") + + artistID := uint64(123) + expectedError := fmt.Errorf("Can't load artist likes count by artist ID %v", artistID) + + artistRepoMock.EXPECT().GetArtistLikesCount(ctx, artistID).Return(uint64(0), expectedError) + + likesCount, err := artistUsecase.GetArtistLikesCount(ctx, artistID) + require.Error(t, err) + require.EqualError(t, err, expectedError.Error()) + require.Equal(t, uint64(0), likesCount) +} diff --git a/microservices/playlist/delivery.go b/microservices/playlist/delivery.go index 23c9fae..acb7dcd 100644 --- a/microservices/playlist/delivery.go +++ b/microservices/playlist/delivery.go @@ -14,4 +14,6 @@ type Handlers interface { DeleteFavoritePlaylist(response http.ResponseWriter, request *http.Request) IsFavoritePlaylist(response http.ResponseWriter, request *http.Request) GetFavoritePlaylists(response http.ResponseWriter, request *http.Request) + GetFavoritePlaylistsCount(response http.ResponseWriter, request *http.Request) + GetPlaylistLikesCount(response http.ResponseWriter, request *http.Request) } diff --git a/microservices/playlist/delivery/http/handlers.go b/microservices/playlist/delivery/http/handlers.go index 1da3b4e..82fbe46 100644 --- a/microservices/playlist/delivery/http/handlers.go +++ b/microservices/playlist/delivery/http/handlers.go @@ -348,3 +348,60 @@ func (h *playlistHandlers) GetFavoritePlaylists(response http.ResponseWriter, re response.WriteHeader(http.StatusOK) } + +func (handlers *playlistHandlers) GetFavoritePlaylistsCount(response http.ResponseWriter, request *http.Request) { + requestID := request.Context().Value(utils.RequestIDKey{}) + vars := mux.Vars(request) + userID, err := uuid.Parse(vars["userID"]) + if err != nil { + handlers.logger.Error(fmt.Sprintf("Get '%s' wrong user id: %v", vars["userID"], err), requestID) + utils.JSONError(response, http.StatusBadRequest, "Wrong id value") + return + } + + count, err := handlers.usecase.GetFavoritePlaylistsCount(request.Context(), userID) + if err != nil { + handlers.logger.Error(fmt.Sprintf("Failed to get playlists count: %v", err), requestID) + utils.JSONError(response, http.StatusInternalServerError, fmt.Sprintf("Failed to get playlists count: %v", err)) + return + } else if count == 0 { + utils.JSONError(response, http.StatusNotFound, "No playlists were found") + return + } + + response.Header().Set("Content-Type", "application/json") + if err := json.NewEncoder(response).Encode(map[string]uint64{"favoritePlaylistsCount": count}); err != nil { + handlers.logger.Error(fmt.Sprintf("Failed to encode playlists count: %v", err), requestID) + utils.JSONError(response, http.StatusInternalServerError, fmt.Sprintf("Failed to encode playlists count: %v", err)) + return + } + + response.WriteHeader(http.StatusOK) +} + +func (handlers *playlistHandlers) GetPlaylistLikesCount(response http.ResponseWriter, request *http.Request) { + requestID := request.Context().Value(utils.RequestIDKey{}) + vars := mux.Vars(request) + playlistID, err := strconv.ParseUint(vars["playlistID"], 10, 64) + if err != nil { + handlers.logger.Error(fmt.Sprintf("Invalid playlist ID: %v", err), requestID) + utils.JSONError(response, http.StatusBadRequest, fmt.Sprintf("Invalid playlist ID: %v", err)) + return + } + + likesCount, err := handlers.usecase.GetPlaylistLikesCount(request.Context(), playlistID) + if err != nil { + handlers.logger.Error("Failed to get playlist likes count", requestID) + utils.JSONError(response, http.StatusInternalServerError, "Can't get playlist likes count") + return + } + + response.Header().Set("Content-Type", "application/json") + if err := json.NewEncoder(response).Encode(map[string]uint64{"playlistLikesCount": likesCount}); err != nil { + handlers.logger.Error(fmt.Sprintf("Failed to encode: %v", err), requestID) + utils.JSONError(response, http.StatusInternalServerError, fmt.Sprintf("Failed to encode: %v", err)) + return + } + + response.WriteHeader(http.StatusOK) +} diff --git a/microservices/playlist/delivery/http/routes.go b/microservices/playlist/delivery/http/routes.go index 185a23a..6966682 100644 --- a/microservices/playlist/delivery/http/routes.go +++ b/microservices/playlist/delivery/http/routes.go @@ -49,6 +49,11 @@ func BindRoutes(s *httpServer.Server, userClient userService.UserServiceClient) middleware.AuthMiddleware(&s.CFG.Service.Auth, s.Logger, http.HandlerFunc(playlistHandleres.GetFavoritePlaylists)), ).Methods("GET") + s.MUX.Handle( + "/api/v1/playlists/favorite/count/{userID:[0-9a-fA-F-]+}", + middleware.AuthMiddleware(&s.CFG.Service.Auth, s.Logger, http.HandlerFunc(playlistHandleres.GetFavoritePlaylistsCount)), + ).Methods("GET") + s.MUX.Handle( "/api/v1/playlists/favorite/{playlistID:[0-9]+}", middleware.AuthMiddleware(&s.CFG.Service.Auth, s.Logger, http.HandlerFunc(playlistHandleres.IsFavoritePlaylist)), @@ -63,4 +68,9 @@ func BindRoutes(s *httpServer.Server, userClient userService.UserServiceClient) "/api/v1/playlists/favorite/{playlistID:[0-9]+}", middleware.AuthMiddleware(&s.CFG.Service.Auth, s.Logger, http.HandlerFunc(playlistHandleres.DeleteFavoritePlaylist)), ).Methods("DELETE") + + s.MUX.Handle( + "/api/v1/playlists/likes/{playlistID:[0-9]+}", + middleware.AuthMiddleware(&s.CFG.Service.Auth, s.Logger, http.HandlerFunc(playlistHandleres.GetPlaylistLikesCount)), + ).Methods("GET") } diff --git a/microservices/playlist/mock/repository_mock.go b/microservices/playlist/mock/repository_mock.go index d4a0d29..b02fd51 100644 --- a/microservices/playlist/mock/repository_mock.go +++ b/microservices/playlist/mock/repository_mock.go @@ -140,6 +140,21 @@ func (mr *MockRepositoryMockRecorder) GetFavoritePlaylists(ctx, userID interface return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetFavoritePlaylists", reflect.TypeOf((*MockRepository)(nil).GetFavoritePlaylists), ctx, userID) } +// GetFavoritePlaylistsCount mocks base method. +func (m *MockRepository) GetFavoritePlaylistsCount(ctx context.Context, userID uuid.UUID) (uint64, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetFavoritePlaylistsCount", ctx, userID) + ret0, _ := ret[0].(uint64) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetFavoritePlaylistsCount indicates an expected call of GetFavoritePlaylistsCount. +func (mr *MockRepositoryMockRecorder) GetFavoritePlaylistsCount(ctx, userID interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetFavoritePlaylistsCount", reflect.TypeOf((*MockRepository)(nil).GetFavoritePlaylistsCount), ctx, userID) +} + // GetLengthPlaylist mocks base method. func (m *MockRepository) GetLengthPlaylist(ctx context.Context, playlistID uint64) (uint64, error) { m.ctrl.T.Helper() @@ -170,6 +185,21 @@ func (mr *MockRepositoryMockRecorder) GetPlaylist(ctx, playlistID interface{}) * return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetPlaylist", reflect.TypeOf((*MockRepository)(nil).GetPlaylist), ctx, playlistID) } +// GetPlaylistLikesCount mocks base method. +func (m *MockRepository) GetPlaylistLikesCount(ctx context.Context, playlistID uint64) (uint64, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetPlaylistLikesCount", ctx, playlistID) + ret0, _ := ret[0].(uint64) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetPlaylistLikesCount indicates an expected call of GetPlaylistLikesCount. +func (mr *MockRepositoryMockRecorder) GetPlaylistLikesCount(ctx, playlistID interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetPlaylistLikesCount", reflect.TypeOf((*MockRepository)(nil).GetPlaylistLikesCount), ctx, playlistID) +} + // GetUserPlaylists mocks base method. func (m *MockRepository) GetUserPlaylists(ctx context.Context, userID uuid.UUID) ([]*models.Playlist, error) { m.ctrl.T.Helper() diff --git a/microservices/playlist/mock/usecase_mock.go b/microservices/playlist/mock/usecase_mock.go index 42d01aa..e762094 100644 --- a/microservices/playlist/mock/usecase_mock.go +++ b/microservices/playlist/mock/usecase_mock.go @@ -139,6 +139,21 @@ func (mr *MockUsecaseMockRecorder) GetFavoritePlaylists(ctx, userID interface{}) return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetFavoritePlaylists", reflect.TypeOf((*MockUsecase)(nil).GetFavoritePlaylists), ctx, userID) } +// GetFavoritePlaylistsCount mocks base method. +func (m *MockUsecase) GetFavoritePlaylistsCount(ctx context.Context, userID uuid.UUID) (uint64, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetFavoritePlaylistsCount", ctx, userID) + ret0, _ := ret[0].(uint64) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetFavoritePlaylistsCount indicates an expected call of GetFavoritePlaylistsCount. +func (mr *MockUsecaseMockRecorder) GetFavoritePlaylistsCount(ctx, userID interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetFavoritePlaylistsCount", reflect.TypeOf((*MockUsecase)(nil).GetFavoritePlaylistsCount), ctx, userID) +} + // GetPlaylist mocks base method. func (m *MockUsecase) GetPlaylist(ctx context.Context, playlistID uint64) (*dto.PlaylistDTO, error) { m.ctrl.T.Helper() @@ -154,6 +169,21 @@ func (mr *MockUsecaseMockRecorder) GetPlaylist(ctx, playlistID interface{}) *gom return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetPlaylist", reflect.TypeOf((*MockUsecase)(nil).GetPlaylist), ctx, playlistID) } +// GetPlaylistLikesCount mocks base method. +func (m *MockUsecase) GetPlaylistLikesCount(ctx context.Context, playlistID uint64) (uint64, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetPlaylistLikesCount", ctx, playlistID) + ret0, _ := ret[0].(uint64) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetPlaylistLikesCount indicates an expected call of GetPlaylistLikesCount. +func (mr *MockUsecaseMockRecorder) GetPlaylistLikesCount(ctx, playlistID interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetPlaylistLikesCount", reflect.TypeOf((*MockUsecase)(nil).GetPlaylistLikesCount), ctx, playlistID) +} + // GetUserPlaylists mocks base method. func (m *MockUsecase) GetUserPlaylists(ctx context.Context, userID uuid.UUID) ([]*dto.PlaylistDTO, error) { m.ctrl.T.Helper() diff --git a/microservices/playlist/repository.go b/microservices/playlist/repository.go index 513d2ed..8bfca72 100644 --- a/microservices/playlist/repository.go +++ b/microservices/playlist/repository.go @@ -21,4 +21,6 @@ type Repository interface { DeleteFavoritePlaylist(ctx context.Context, userID uuid.UUID, playlistID uint64) error IsFavoritePlaylist(ctx context.Context, userID uuid.UUID, playlistID uint64) (bool, error) GetFavoritePlaylists(ctx context.Context, userID uuid.UUID) ([]*models.Playlist, error) + GetFavoritePlaylistsCount(ctx context.Context, userID uuid.UUID) (uint64, error) + GetPlaylistLikesCount(ctx context.Context, playlistID uint64) (uint64, error) } diff --git a/microservices/playlist/repository/pg_repository.go b/microservices/playlist/repository/pg_repository.go index 02b9452..a6dfe02 100644 --- a/microservices/playlist/repository/pg_repository.go +++ b/microservices/playlist/repository/pg_repository.go @@ -235,3 +235,23 @@ func (r *PlaylistRepository) GetFavoritePlaylists(ctx context.Context, userID uu return playlists, nil } + +func (r *PlaylistRepository) GetFavoritePlaylistsCount(ctx context.Context, userID uuid.UUID) (uint64, error) { + var count uint64 + err := r.db.QueryRowContext(ctx, getFavoriteCountQuery, userID).Scan(&count) + if err != nil && err != sql.ErrNoRows { + return 0, errors.Wrap(err, "GetFavoritePlaylistsCount.Query") + } + + return count, nil +} + +func (r *PlaylistRepository) GetPlaylistLikesCount(ctx context.Context, playlistID uint64) (uint64, error) { + var likesCount uint64 + err := r.db.QueryRowContext(ctx, getLikesCountQuery, playlistID).Scan(&likesCount) + if err != nil && err != sql.ErrNoRows { + return 0, errors.Wrap(err, "GetLikesCount.Query") + } + + return likesCount, nil +} diff --git a/microservices/playlist/repository/pg_repository_test.go b/microservices/playlist/repository/pg_repository_test.go index 1af08e7..2db342b 100644 --- a/microservices/playlist/repository/pg_repository_test.go +++ b/microservices/playlist/repository/pg_repository_test.go @@ -322,3 +322,47 @@ func TestPlaylistRepositoryGetFavoritePlaylists(t *testing.T) { require.NotNil(t, foundPlaylists) require.Equal(t, foundPlaylists, expectedPlaylists) } + +func TestPlaylistRepositoryGetFavoritePlaylistsCount(t *testing.T) { + t.Parallel() + + db, mock, err := sqlmock.New(sqlmock.QueryMatcherOption(sqlmock.QueryMatcherEqual)) + require.NoError(t, err) + defer db.Close() + + playlistRepository := NewPlaylistRepository(db) + + userID := uuid.New() + expectedCount := uint64(5) + + mock.ExpectQuery(getFavoriteCountQuery).WithArgs(userID).WillReturnRows(sqlmock.NewRows([]string{"count"}).AddRow(expectedCount)) + + count, err := playlistRepository.GetFavoritePlaylistsCount(context.Background(), userID) + require.NoError(t, err) + require.Equal(t, expectedCount, count) + + err = mock.ExpectationsWereMet() + require.NoError(t, err) +} + +func TestPlaylistRepositoryGetPlaylistLikesCount(t *testing.T) { + t.Parallel() + + db, mock, err := sqlmock.New(sqlmock.QueryMatcherOption(sqlmock.QueryMatcherEqual)) + require.NoError(t, err) + defer db.Close() + + playlistRepository := NewPlaylistRepository(db) + + playlistID := uint64(789) + expectedLikesCount := uint64(15) + + mock.ExpectQuery(getLikesCountQuery).WithArgs(playlistID).WillReturnRows(sqlmock.NewRows([]string{"likes_count"}).AddRow(expectedLikesCount)) + + likesCount, err := playlistRepository.GetPlaylistLikesCount(context.Background(), playlistID) + require.NoError(t, err) + require.Equal(t, expectedLikesCount, likesCount) + + err = mock.ExpectationsWereMet() + require.NoError(t, err) +} diff --git a/microservices/playlist/repository/sql_queries.go b/microservices/playlist/repository/sql_queries.go index aed4fdb..86ead74 100644 --- a/microservices/playlist/repository/sql_queries.go +++ b/microservices/playlist/repository/sql_queries.go @@ -41,4 +41,15 @@ RETURNING id, playlist_id, track_order_in_playlist, track_id, created_at` JOIN favorite_playlist AS fp ON p.id = fp.playlist_id WHERE fp.user_id = $1` + + getFavoriteCountQuery = ` + SELECT COUNT(*) + FROM playlist AS p + JOIN favorite_playlist AS fp + ON p.id = fp.playlist_id + WHERE fp.user_id = $1` + + getLikesCountQuery = ` + SELECT COUNT(*) + FROM favorite_playlist WHERE playlist_id = $1` ) diff --git a/microservices/playlist/usecase.go b/microservices/playlist/usecase.go index 4530ae3..2287ab9 100644 --- a/microservices/playlist/usecase.go +++ b/microservices/playlist/usecase.go @@ -20,4 +20,6 @@ type Usecase interface { DeleteFavoritePlaylist(ctx context.Context, userID uuid.UUID, playlistID uint64) error IsFavoritePlaylist(ctx context.Context, userID uuid.UUID, playlistID uint64) (bool, error) GetFavoritePlaylists(ctx context.Context, userID uuid.UUID) ([]*pldto.PlaylistDTO, error) + GetFavoritePlaylistsCount(ctx context.Context, userID uuid.UUID) (uint64, error) + GetPlaylistLikesCount(ctx context.Context, playlistID uint64) (uint64, error) } diff --git a/microservices/playlist/usecase/usecase.go b/microservices/playlist/usecase/usecase.go index 057648e..8f7acb1 100644 --- a/microservices/playlist/usecase/usecase.go +++ b/microservices/playlist/usecase/usecase.go @@ -191,3 +191,27 @@ func (u *PlaylistUsecase) GetFavoritePlaylists(ctx context.Context, userID uuid. return dtoPlaylists, nil } + +func (usecase *PlaylistUsecase) GetFavoritePlaylistsCount(ctx context.Context, userID uuid.UUID) (uint64, error) { + requestID := ctx.Value(utils.RequestIDKey{}) + count, err := usecase.playlistRepo.GetFavoritePlaylistsCount(ctx, userID) + if err != nil { + usecase.logger.Warn(fmt.Sprintf("Can't load playlists count by user ID %v: %v", userID, err), requestID) + return 0, fmt.Errorf("Can't load playlists by user ID %v", userID) + } + usecase.logger.Infof("Found %d playlists for user ID %v", count, userID) + + return count, nil +} + +func (usecase *PlaylistUsecase) GetPlaylistLikesCount(ctx context.Context, playlistID uint64) (uint64, error) { + requestID := ctx.Value(utils.RequestIDKey{}) + likesCount, err := usecase.playlistRepo.GetPlaylistLikesCount(ctx, playlistID) + if err != nil { + usecase.logger.Warn(fmt.Sprintf("Can't load playlist likes count by playlist ID %v: %v", playlistID, err), requestID) + return 0, fmt.Errorf("Can't load playlist likes count by playlist ID %v", playlistID) + } + usecase.logger.Infof("Found %d likes for playlist ID %v", likesCount, playlistID) + + return likesCount, nil +} diff --git a/microservices/playlist/usecase/usecase_test.go b/microservices/playlist/usecase/usecase_test.go index 3b146ec..28a60e2 100644 --- a/microservices/playlist/usecase/usecase_test.go +++ b/microservices/playlist/usecase/usecase_test.go @@ -723,3 +723,141 @@ func TestPlaylistUsecase_GetFavoritePlaylists_NotFoundPlaylists(t *testing.T) { require.Nil(t, dtoPlaylists) require.EqualError(t, err, fmt.Sprintf("Can't load playlists by user ID %v", userID)) } + +func TestUsecase_GetFavoritePlaylistsCount_FoundPlaylists(t *testing.T) { + t.Parallel() + + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + cfg := &config.Config{ + Service: config.ServiceConfig{ + Logger: config.LoggerConfig{ + Level: "info", + Format: "json", + }, + }, + } + + logger := logger.New(&cfg.Service.Logger) + playlistRepoMock := mock.NewMockRepository(ctrl) + playlistUsecase := &PlaylistUsecase{ + playlistRepo: playlistRepoMock, + logger: logger, + } + + userID := uuid.New() + ctx := context.Background() + expectedCount := uint64(5) + playlistRepoMock.EXPECT().GetFavoritePlaylistsCount(ctx, userID).Return(expectedCount, nil) + + count, err := playlistUsecase.GetFavoritePlaylistsCount(ctx, userID) + + require.NoError(t, err) + require.Equal(t, expectedCount, count) +} + +func TestUsecase_GetFavoritePlaylistsCount_ErrorGettingCount(t *testing.T) { + t.Parallel() + + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + cfg := &config.Config{ + Service: config.ServiceConfig{ + Logger: config.LoggerConfig{ + Level: "info", + Format: "json", + }, + }, + } + + logger := logger.New(&cfg.Service.Logger) + playlistRepoMock := mock.NewMockRepository(ctrl) + playlistUsecase := &PlaylistUsecase{ + playlistRepo: playlistRepoMock, + logger: logger, + } + + userID := uuid.New() + ctx := context.Background() + expectedError := fmt.Errorf("Can't load playlists by user ID %v", userID) + playlistRepoMock.EXPECT().GetFavoritePlaylistsCount(ctx, userID).Return(uint64(0), expectedError) + + count, err := playlistUsecase.GetFavoritePlaylistsCount(ctx, userID) + + require.Error(t, err) + require.EqualError(t, err, expectedError.Error()) + require.Equal(t, uint64(0), count) +} + +func TestUsecase_GetPlaylistLikesCount_Success(t *testing.T) { + t.Parallel() + + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + cfg := &config.Config{ + Service: config.ServiceConfig{ + Logger: config.LoggerConfig{ + Level: "info", + Format: "json", + }, + }, + } + + logger := logger.New(&cfg.Service.Logger) + playlistRepoMock := mock.NewMockRepository(ctrl) + playlistUsecase := &PlaylistUsecase{ + playlistRepo: playlistRepoMock, + logger: logger, + } + + ctx := context.Background() + ctx = context.WithValue(ctx, utils.RequestIDKey{}, "test-request-id") + + playlistID := uint64(123) + expectedLikesCount := uint64(10) + + playlistRepoMock.EXPECT().GetPlaylistLikesCount(ctx, playlistID).Return(expectedLikesCount, nil) + + likesCount, err := playlistUsecase.GetPlaylistLikesCount(ctx, playlistID) + require.NoError(t, err) + require.Equal(t, expectedLikesCount, likesCount) +} + +func TestUsecase_GetPlaylistLikesCount_ErrorGettingLikesCount(t *testing.T) { + t.Parallel() + + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + cfg := &config.Config{ + Service: config.ServiceConfig{ + Logger: config.LoggerConfig{ + Level: "info", + Format: "json", + }, + }, + } + + logger := logger.New(&cfg.Service.Logger) + playlistRepoMock := mock.NewMockRepository(ctrl) + playlistUsecase := &PlaylistUsecase{ + playlistRepo: playlistRepoMock, + logger: logger, + } + + ctx := context.Background() + ctx = context.WithValue(ctx, utils.RequestIDKey{}, "test-request-id") + + playlistID := uint64(123) + expectedError := fmt.Errorf("Can't load playlist likes count by playlist ID %v", playlistID) + + playlistRepoMock.EXPECT().GetPlaylistLikesCount(ctx, playlistID).Return(uint64(0), expectedError) + + likesCount, err := playlistUsecase.GetPlaylistLikesCount(ctx, playlistID) + require.Error(t, err) + require.EqualError(t, err, expectedError.Error()) + require.Equal(t, uint64(0), likesCount) +}