Skip to content

Commit

Permalink
feat: added endpoints to get artist and artist setlists
Browse files Browse the repository at this point in the history
fix: return JSON when error happens
  • Loading branch information
ArnauBlanch committed Dec 24, 2022
1 parent 32135f4 commit b0b026c
Show file tree
Hide file tree
Showing 47 changed files with 808 additions and 96 deletions.
1 change: 1 addition & 0 deletions api/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ dependencies {
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.boot:spring-boot-starter-cache'
implementation 'org.springframework.boot:spring-boot-starter-validation'
implementation 'org.springframework.boot:spring-boot-devtools'
implementation 'com.github.ben-manes.caffeine:caffeine:3.1.2'
implementation 'com.squareup.retrofit2:retrofit:2.9.0'
implementation 'com.squareup.retrofit2:converter-gson:2.9.0'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import xyz.arnau.setlisttoplaylist.domain.entities.Artist;
import xyz.arnau.setlisttoplaylist.domain.exceptions.ArtistNotFoundException;
import xyz.arnau.setlisttoplaylist.domain.ports.ArtistRepository;

import java.util.List;
Expand All @@ -19,4 +20,9 @@ public List<Artist> getTopArtists() {
public List<Artist> searchByName(String nameQuery) {
return artistRepository.searchByName(nameQuery);
}

public Artist getById(String artistId) {
return artistRepository.getById(artistId)
.orElseThrow(ArtistNotFoundException::new);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ public class PlaylistService {
"Setlist for ${artistName} concert at ${venueName} (${venueCity}, ${venueCountryCode}) on ${date}.";

public Playlist createFromSetlist(String setlistId, boolean isPublic, String authorizationHeader) {
var setlist = setlistService.getSetlist(setlistId);
var setlist = setlistService.getById(setlistId);

CreatePlaylistCommand command = CreatePlaylistCommand.builder()
.name(fillData(PLAYLIST_NAME, setlist))
Expand All @@ -43,7 +43,7 @@ public Playlist createFromSetlist(String setlistId, boolean isPublic, String aut
}

public byte[] getCoverImage(String setlistId) {
var setlist = setlistService.getSetlist(setlistId);
var setlist = setlistService.getById(setlistId);
return playlistImageGenerator.generateImage(setlist);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@

import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import xyz.arnau.setlisttoplaylist.domain.entities.BasicSetlist;
import xyz.arnau.setlisttoplaylist.domain.entities.PagedList;
import xyz.arnau.setlisttoplaylist.domain.entities.Setlist;
import xyz.arnau.setlisttoplaylist.domain.exceptions.ArtistSetlistsNotFoundException;
import xyz.arnau.setlisttoplaylist.domain.exceptions.SetlistNotFoundException;
import xyz.arnau.setlisttoplaylist.domain.ports.SetlistRepository;

Expand All @@ -12,9 +15,14 @@ public class SetlistService {

private final SetlistRepository setlistRepository;

public Setlist getSetlist(String setlistId) {
public Setlist getById(String setlistId) {
return setlistRepository.getSetlist(setlistId)
.filter(setlist -> setlist.songs() != null && !setlist.songs().isEmpty())
.orElseThrow(SetlistNotFoundException::new);
}

public PagedList<BasicSetlist> getByArtistId(String artistId, int page) {
return setlistRepository.getArtistSetlists(artistId, page)
.orElseThrow(ArtistSetlistsNotFoundException::new);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package xyz.arnau.setlisttoplaylist.config;

import jakarta.servlet.http.HttpServletRequest;
import org.springframework.boot.autoconfigure.web.servlet.error.AbstractErrorController;
import org.springframework.boot.web.servlet.error.ErrorAttributes;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

import java.util.Map;

import static org.springframework.boot.web.error.ErrorAttributeOptions.Include.MESSAGE;
import static org.springframework.boot.web.error.ErrorAttributeOptions.of;

@Controller
@RequestMapping("${server.error.path:${error.path:/error}}")
public class BasicErrorController extends AbstractErrorController {
public BasicErrorController(ErrorAttributes errorAttributes) {
super(errorAttributes);
}

@RequestMapping
public ResponseEntity<Map<String, Object>> error(HttpServletRequest request) {
Map<String, Object> body = getErrorAttributes(request, of(MESSAGE));
HttpStatus status = getStatus(request);
return new ResponseEntity<>(body, status);
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package xyz.arnau.setlisttoplaylist.infrastructure;
package xyz.arnau.setlisttoplaylist.config;

import com.github.benmanes.caffeine.cache.Caffeine;
import org.springframework.cache.CacheManager;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

@Builder
public record Artist(
String id,
String musicPlatformId,
String name,
String imageUrl
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package xyz.arnau.setlisttoplaylist.domain.entities;

import lombok.Builder;

import java.time.LocalDate;

@Builder
public record BasicSetlist(
String id,
LocalDate date,
Venue venue,
int numSongs
) {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package xyz.arnau.setlisttoplaylist.domain.entities;

import lombok.Builder;

import java.util.List;

@Builder
public record PagedList<T>(
List<T> items,
int page,
int totalItems,
int itemsPerPage
) {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package xyz.arnau.setlisttoplaylist.domain.exceptions;

import org.springframework.web.bind.annotation.ResponseStatus;

import static org.springframework.http.HttpStatus.NOT_FOUND;

@ResponseStatus(value = NOT_FOUND, reason = "Artist not found")
public class ArtistNotFoundException extends RuntimeException {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package xyz.arnau.setlisttoplaylist.domain.exceptions;

import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus;

import static org.springframework.http.HttpStatus.NOT_FOUND;

@ResponseStatus(value = NOT_FOUND, reason = "Artist setlists not found")
@ResponseBody
public class ArtistSetlistsNotFoundException extends RuntimeException {
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,11 @@
import xyz.arnau.setlisttoplaylist.domain.entities.Artist;

import java.util.List;
import java.util.Optional;

public interface ArtistRepository {
List<Artist> getTopArtists();
List<Artist> searchByName(String nameQuery);

Optional<Artist> getById(String artistId);
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
package xyz.arnau.setlisttoplaylist.domain.ports;

import xyz.arnau.setlisttoplaylist.domain.entities.BasicSetlist;
import xyz.arnau.setlisttoplaylist.domain.entities.PagedList;
import xyz.arnau.setlisttoplaylist.domain.entities.Setlist;

import java.util.Optional;

public interface SetlistRepository {
Optional<Setlist> getSetlist(String setlistId);

Optional<PagedList<BasicSetlist>> getArtistSetlists(String artistId, int page);
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,13 @@
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.bind.annotation.*;
import xyz.arnau.setlisttoplaylist.application.ArtistService;
import xyz.arnau.setlisttoplaylist.application.SetlistService;
import xyz.arnau.setlisttoplaylist.infrastructure.controller.mapper.ArtistMapper;
import xyz.arnau.setlisttoplaylist.infrastructure.controller.mapper.SetlistMapper;
import xyz.arnau.setlisttoplaylist.infrastructure.controller.response.ArtistResponse;
import xyz.arnau.setlisttoplaylist.infrastructure.controller.response.ArtistSetlistsResponse;
import xyz.arnau.setlisttoplaylist.infrastructure.controller.response.PlaylistResponse;

import java.util.ArrayList;
Expand All @@ -32,6 +32,7 @@
public class ArtistController {

private final ArtistService artistService;
private final SetlistService setlistService;

@GetMapping("top")
@Operation(summary = "Get top artists")
Expand Down Expand Up @@ -64,4 +65,38 @@ public ResponseEntity<List<ArtistResponse>> searchArtistsByName(
.map(ArtistMapper.MAPPER::toResponse)
.collect(toList()));
}
}

@GetMapping("{artistId}")
@Operation(summary = "Get artist")
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = "Artist found", content = {
@Content(mediaType = APPLICATION_JSON_VALUE, schema = @Schema(implementation = ArtistResponse.class))
}),
@ApiResponse(responseCode = "404", description = "Artist not found", content = @Content)
})
public ResponseEntity<ArtistResponse> getArtistSetlists(
@PathVariable @Parameter(description = "Artist ID", example = "d15721d8-56b4-453d-b506-fc915b14cba2") String artistId) {
var artist = artistService.getById(artistId);
return ResponseEntity.ok(ArtistMapper.MAPPER.toResponse(artist));
}

@GetMapping("{artistId}/setlists")
@Operation(summary = "Get artist setlists")
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = "Artist setlists found", content = {
@Content(mediaType = APPLICATION_JSON_VALUE, schema = @Schema(implementation = ArtistSetlistsResponse.class))
}),
@ApiResponse(responseCode = "404", description = "Artist setlists not found", content = @Content)
})
public ResponseEntity<ArtistSetlistsResponse> getArtistSetlists(
@PathVariable @Parameter(description = "Artist ID", example = "d15721d8-56b4-453d-b506-fc915b14cba2") String artistId,
@RequestParam(defaultValue = "1") @Parameter(description = "Page number, starting from 1", example = "1") int page) {
var setlistsPage = setlistService.getByArtistId(artistId, page);
return ResponseEntity.ok(ArtistSetlistsResponse.builder()
.setlists(setlistsPage.items().stream().map(SetlistMapper.MAPPER::toResponse).toList())
.page(setlistsPage.page())
.totalItems(setlistsPage.totalItems())
.itemsPerPage(setlistsPage.itemsPerPage())
.build());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ public class SetlistController {
})
public ResponseEntity<SetlistResponse> getSetlist(
@PathVariable @Parameter(description = "Setlist ID", example = "6bb43616") String setlistId) {
Setlist setlist = setlistService.getSetlist(setlistId);
Setlist setlist = setlistService.getById(setlistId);
return ok(SetlistMapper.MAPPER.toResponse(setlist));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@

import org.mapstruct.Mapper;
import org.mapstruct.factory.Mappers;
import xyz.arnau.setlisttoplaylist.domain.entities.BasicSetlist;
import xyz.arnau.setlisttoplaylist.domain.entities.Setlist;
import xyz.arnau.setlisttoplaylist.infrastructure.controller.response.BasicSetlistResponse;
import xyz.arnau.setlisttoplaylist.infrastructure.controller.response.SetlistResponse;

@Mapper
Expand All @@ -11,4 +13,5 @@ public interface SetlistMapper {
SetlistMapper MAPPER = Mappers.getMapper(SetlistMapper.class);

SetlistResponse toResponse(Setlist setlist);
BasicSetlistResponse toResponse(BasicSetlist setlist);
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@

@Schema(requiredProperties = {"musicPlatformId", "name", "imageUrl"})
public record ArtistResponse(
@Schema(description = "Artist ID", example = "d15721d8-56b4-453d-b506-fc915b14cba2")
String id,
@Schema(description = "Music platform artist ID", example = "7mnBLXK823vNxN3UWB7Gfz")
String musicPlatformId,
@Schema(description = "Artist name", example = "The Black Keys")
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package xyz.arnau.setlisttoplaylist.infrastructure.controller.response;

import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Builder;

import java.util.List;

@Schema(requiredProperties = {"setlists", "page", "totalItems", "itemsPerPage"})
@Builder
public record ArtistSetlistsResponse(
@Schema(description = "Artist setlists")
List<BasicSetlistResponse> setlists,
@Schema(description = "Page number", example = "1")
int page,
@Schema(description = "Total number of setlists for the artist", example = "56")
int totalItems,
@Schema(description = "Number of items per page", example = "20")
int itemsPerPage
) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package xyz.arnau.setlisttoplaylist.infrastructure.controller.response;

import io.swagger.v3.oas.annotations.media.Schema;

import java.time.LocalDate;

@Schema(requiredProperties = {"id", "date", "venue", "numSongs"})
public record BasicSetlistResponse(
@Schema(description = "Setlist ID", example = "6bbf4e1e")
String id,
@Schema(description = "Setlist date")
LocalDate date,
VenueResponse venue,
@Schema(description = "Number of songs", example = "23")
int numSongs
) {
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,10 @@
import java.time.LocalDate;
import java.util.List;

@Schema(requiredProperties = {"date", "artist", "venue", "songs"})
@Schema(requiredProperties = {"id", "date", "artist", "venue", "songs"})
public record SetlistResponse(
@Schema(description = "Setlist ID", example = "6bbf4e1e")
String id,
@Schema(description = "Setlist date")
LocalDate date,
ArtistResponse artist,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
import xyz.arnau.setlisttoplaylist.domain.entities.Artist;
import xyz.arnau.setlisttoplaylist.domain.ports.ArtistRepository;
import xyz.arnau.setlisttoplaylist.infrastructure.repository.setlistfm.SetlistFmApiService;
import xyz.arnau.setlisttoplaylist.infrastructure.repository.setlistfm.model.SetlistFmArtist;

import java.util.List;
import java.util.Optional;
Expand All @@ -28,13 +27,18 @@ public List<Artist> getTopArtists() {
public List<Artist> searchByName(String nameQuery) {
try {
return setlistFmApiService.searchArtists(nameQuery).stream()
.map(SetlistFmArtist::getSortName)
.collect(
parallel(musicPlatformRepository::getArtist, toList(), executorService, 10))
parallel(setlistArtist -> musicPlatformRepository.getArtist(setlistArtist.getId(), setlistArtist.getSortName()),
toList(), executorService, 10))
.get().stream()
.filter(Optional::isPresent).map(Optional::get).collect(toList());
} catch (ExecutionException | InterruptedException e) {
throw new RuntimeException(e);
}
}

public Optional<Artist> getById(String artistId) {
return setlistFmApiService.getArtist(artistId)
.flatMap(setlistArtist -> musicPlatformRepository.getArtist(setlistArtist.getId(), setlistArtist.getSortName()));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
import java.util.Optional;

public interface MusicPlatformRepository {
Optional<Artist> getArtist(String nameQuery);
Optional<Artist> getArtist(String id, String name);
Optional<Song> getSong(String artistName, String songName, boolean isCover);
List<Artist> getTopArtists();
}
Loading

0 comments on commit b0b026c

Please sign in to comment.