Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

NM-31: make backend for artist page #32

Merged
merged 4 commits into from
Oct 28, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions internal/album/delivery.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,5 @@ type Handlers interface {
SearchAlbum(response http.ResponseWriter, request *http.Request)
ViewAlbum(response http.ResponseWriter, request *http.Request)
GetAll(response http.ResponseWriter, request *http.Request)
GetAllByArtistID(response http.ResponseWriter, request *http.Request)
}
39 changes: 39 additions & 0 deletions internal/album/delivery/http/handlers.go
Original file line number Diff line number Diff line change
Expand Up @@ -122,3 +122,42 @@ func (handlers *albumHandlers) GetAll(response http.ResponseWriter, request *htt

response.WriteHeader(http.StatusOK)
}

// GetAllByArtistID godoc
// @Summary Get all albums by artist ID
// @Description Retrieves a list of all albums for a given artist ID from the database.
// @Param artistID path int true "Artist ID"
// @Success 200 {array} dto.AlbumDTO "List of albums by artist"
// @Failure 404 {object} utils.ErrorResponse "No albums found for the artist"
// @Failure 500 {object} utils.ErrorResponse "Failed to load albums"
// @Router /api/v1/albums/artist/{artistID} [get]
func (handlers *albumHandlers) GetAllByArtistID(response http.ResponseWriter, request *http.Request) {
vars := mux.Vars(request)
artistIDStr := vars["artistId"]
artistID, err := strconv.ParseUint(artistIDStr, 10, 64)
if err != nil {
handlers.logger.Error(fmt.Sprintf("Invalid artist ID: %v", err))
utils.JSONError(response, http.StatusBadRequest, "Invalid artist ID")
return
}

albums, err := handlers.usecase.GetAllByArtistID(request.Context(), artistID)
if err != nil {
handlers.logger.Error(fmt.Sprintf("Failed to load albums by artist ID: %v", err))
utils.JSONError(response, http.StatusInternalServerError, "Albums load fail")
return
} else if len(albums) == 0 {
handlers.logger.Error(fmt.Sprintf("No albums found for artist ID: %d", artistID))
utils.JSONError(response, http.StatusNotFound, "No albums found for the artist")
return
}

response.Header().Set("Content-Type", "application/json")
if err := json.NewEncoder(response).Encode(albums); err != nil {
handlers.logger.Error(fmt.Sprintf("Failed to encode albums: %v", err))
utils.JSONError(response, http.StatusInternalServerError, "Encode fail")
return
}

response.WriteHeader(http.StatusOK)
}
75 changes: 75 additions & 0 deletions internal/album/delivery/http/handlers_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -204,3 +204,78 @@ func TestAlbumHandlers_GetAllAlbums(t *testing.T) {
assert.Equal(t, http.StatusNotFound, response.Code)
})
}

func TestAlbumHandlers_GetAllByArtistIDAlbums(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("Successful got all albums by artist ID", func(t *testing.T) {
releaseDate := time.Date(2024, time.October, 1, 0, 0, 0, 0, time.UTC)
albums := []*dto.AlbumDTO{
{
Name: "test", TrackCount: uint64(1), ReleaseDate: releaseDate, Image: "1", Artist: "artist1",
},
{
Name: "album", TrackCount: uint64(1), ReleaseDate: releaseDate, Image: "2", Artist: "artist1",
},
{
Name: "test", TrackCount: uint64(1), ReleaseDate: releaseDate, Image: "3", Artist: "artist1",
},
}
usecaseMock.EXPECT().GetAllByArtistID(gomock.Any(), uint64(1)).Return(albums, nil)

router := mux.NewRouter()
router.HandleFunc("/albums/byArtistId/{artistId}", albumHandlers.GetAllByArtistID).Methods("GET")

request, err := http.NewRequest(http.MethodGet, "/albums/byArtistId/1", nil)
assert.NoError(t, err)

response := httptest.NewRecorder()
router.ServeHTTP(response, request)

res := response.Result()
assert.Equal(t, http.StatusOK, res.StatusCode)

defer res.Body.Close()
var foundAlbums []*dto.AlbumDTO
err = json.NewDecoder(res.Body).Decode(&foundAlbums)
assert.NoError(t, err)

assert.Equal(t, albums, foundAlbums)
})

t.Run("Can't find albums by artist ID", func(t *testing.T) {
usecaseMock.EXPECT().GetAllByArtistID(gomock.Any(), uint64(1)).Return([]*dto.AlbumDTO{}, nil)

router := mux.NewRouter()
router.HandleFunc("/albums/byArtistId/{artistId}", albumHandlers.GetAllByArtistID).Methods("GET")

request, err := http.NewRequest(http.MethodGet, "/albums/byArtistId/1", nil)
assert.NoError(t, err)

response := httptest.NewRecorder()
router.ServeHTTP(response, request)

res := response.Result()
assert.Equal(t, http.StatusNotFound, res.StatusCode)
})

t.Run("Invalid artist ID", func(t *testing.T) {
router := mux.NewRouter()
router.HandleFunc("/albums/byArtistId/{artistId}", albumHandlers.GetAllByArtistID).Methods("GET")

request, err := http.NewRequest(http.MethodGet, "/albums/byArtistId/abc", nil)
assert.NoError(t, err)

response := httptest.NewRecorder()
router.ServeHTTP(response, request)

res := response.Result()
assert.Equal(t, http.StatusBadRequest, res.StatusCode)
})
}
2 changes: 1 addition & 1 deletion internal/album/dto/dto.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ type AlbumDTO struct {
TrackCount uint64 `json:"trackCount"`
ReleaseDate time.Time `json:"release"`
Image string `json:"image"`
Artist string `json:"artistId"`
Artist string `json:"artistName"`
}

func NewAlbumDTO(album *models.Album, artist *models.Artist) *AlbumDTO {
Expand Down
19 changes: 17 additions & 2 deletions internal/album/mock/repository_mock.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

19 changes: 17 additions & 2 deletions internal/album/mock/usecase_mock.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions internal/album/repository.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,6 @@ type Repo interface {
Create(ctx context.Context, album *models.Album) (*models.Album, error)
FindById(ctx context.Context, albumID uint64) (*models.Album, error)
GetAll(ctx context.Context) ([]*models.Album, error)
GetAllByArtistID(ctx context.Context, artistID uint64) ([]*models.Album, error)
FindByName(ctx context.Context, name string) ([]*models.Album, error)
}
29 changes: 29 additions & 0 deletions internal/album/repository/pg_repository.go
Original file line number Diff line number Diff line change
Expand Up @@ -123,3 +123,32 @@ func (r *AlbumRepository) GetAll(ctx context.Context) ([]*models.Album, error) {

return albums, nil
}

func (r *AlbumRepository) GetAllByArtistID(ctx context.Context, artistID uint64) ([]*models.Album, error) {
var albums []*models.Album
rows, err := r.db.QueryContext(ctx, getByArtistIDQuery, artistID)
if err != nil {
return nil, errors.Wrap(err, "GetAllByArtistID.Query")
}
defer rows.Close()

for rows.Next() {
album := &models.Album{}
err := rows.Scan(
&album.ID,
&album.Name,
&album.TrackCount,
&album.ReleaseDate,
&album.Image,
&album.ArtistID,
&album.CreatedAt,
&album.UpdatedAt,
)
if err != nil {
return nil, errors.Wrap(err, "GetAllByArtistID.Query")
}
albums = append(albums, album)
}

return albums, nil
}
54 changes: 54 additions & 0 deletions internal/album/repository/pg_repository_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -217,3 +217,57 @@ func TestAlbumRepositoryGetAll(t *testing.T) {
require.NotNil(t, foundAlbums)
require.Equal(t, foundAlbums, expectedAlbums)
}

func TestAlbumRepositoryGetAllByArtistID(t *testing.T) {
t.Parallel()
db, mock, err := sqlmock.New(sqlmock.QueryMatcherOption(sqlmock.QueryMatcherEqual))
require.NoError(t, err)
defer db.Close()

albumPGRepository := NewAlbumPGRepository(db)
albums := []models.Album{
{
ID: 1,
Name: "Album for test 1",
TrackCount: 12,
ReleaseDate: time.Date(2024, 07, 19, 0, 0, 0, 0, time.UTC),
Image: "/imgs/albums/album_1.jpg",
ArtistID: 1,
CreatedAt: time.Now(),
UpdatedAt: time.Now(),
},
{
ID: 2,
Name: "Album for test 2",
TrackCount: 9,
ReleaseDate: time.Date(2021, 02, 3, 0, 0, 0, 0, time.UTC),
Image: "/imgs/albums/album_2.jpg",
ArtistID: 1,
CreatedAt: time.Now(),
UpdatedAt: time.Now(),
},
}

columns := []string{"id", "name", "track_count", "release", "image", "artist_id", "created_at", "updated_at"}
rows := sqlmock.NewRows(columns)
for _, album := range albums {
rows.AddRow(
album.ID,
album.Name,
album.TrackCount,
album.ReleaseDate,
album.Image,
album.ArtistID,
album.CreatedAt,
album.UpdatedAt,
)
}

expectedAlbums := []*models.Album{&albums[0], &albums[1]}
mock.ExpectQuery(getByArtistIDQuery).WithArgs(uint64(1)).WillReturnRows(rows)

foundAlbums, err := albumPGRepository.GetAllByArtistID(context.Background(), uint64(1))
require.NoError(t, err)
require.NotNil(t, foundAlbums)
require.Equal(t, foundAlbums, expectedAlbums)
}
2 changes: 2 additions & 0 deletions internal/album/repository/sql_queries.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,6 @@ const (
getAllQuery = `SELECT id, name, track_count, release_date, image, artist_id, created_at, updated_at FROM album`

findByNameQuery = `SELECT id, name, track_count, release_date, image, artist_id, created_at, updated_at FROM album WHERE name = $1`

getByArtistIDQuery = `SELECT id, name, track_count, release_date, image, artist_id, created_at, updated_at FROM album WHERE artist_id = $1`
)
1 change: 1 addition & 0 deletions internal/album/usecase.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,5 @@ type Usecase interface {
View(ctx context.Context, albumID uint64) (*dto.AlbumDTO, error)
Search(ctx context.Context, name string) ([]*dto.AlbumDTO, error)
GetAll(ctx context.Context) ([]*dto.AlbumDTO, error)
GetAllByArtistID(ctx context.Context, artistID uint64) ([]*dto.AlbumDTO, error)
}
21 changes: 21 additions & 0 deletions internal/album/usecase/usecase.go
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,27 @@ func (usecase *albumUsecase) GetAll(ctx context.Context) ([]*dto.AlbumDTO, error
return dtoAlbums, nil
}

func (usecase *albumUsecase) GetAllByArtistID(ctx context.Context, artistID uint64) ([]*dto.AlbumDTO, error) {
albums, err := usecase.albumRepo.GetAllByArtistID(ctx, artistID)
if err != nil {
usecase.logger.Warn(fmt.Sprintf("Can't load albums by artist ID %d: %v", artistID, err))
return nil, fmt.Errorf("Can't load albums by artist ID %d", artistID)
}
usecase.logger.Infof("Found %d albums for artist ID %d", len(albums), artistID)

var dtoAlbums []*dto.AlbumDTO
for _, album := range albums {
dtoAlbum, err := usecase.convertAlbumToDTO(ctx, album)
if err != nil {
usecase.logger.Errorf("Can't create DTO for %s album: %v", album.Name, err)
return nil, fmt.Errorf("Can't create DTO")
}
dtoAlbums = append(dtoAlbums, dtoAlbum)
}

return dtoAlbums, nil
}

func (usecase *albumUsecase) convertAlbumToDTO(ctx context.Context, album *models.Album) (*dto.AlbumDTO, error) {
artist, err := usecase.artistRepo.FindById(ctx, album.ArtistID)
if err != nil {
Expand Down
Loading