-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add endpoint to retrieve all songs persisted in the database
- Loading branch information
Showing
14 changed files
with
433 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
50 changes: 50 additions & 0 deletions
50
src/main/java/dev/migwel/sts/controller/api/DatabaseController.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
package dev.migwel.sts.controller.api; | ||
|
||
import dev.migwel.sts.controller.api.dto.GetSongsResponse; | ||
import dev.migwel.sts.controller.api.dto.database.PersistedSong; | ||
import dev.migwel.sts.database.DatabaseService; | ||
|
||
import org.springframework.beans.factory.annotation.Autowired; | ||
import org.springframework.http.HttpStatus; | ||
import org.springframework.http.ResponseEntity; | ||
import org.springframework.security.core.Authentication; | ||
import org.springframework.stereotype.Controller; | ||
import org.springframework.web.bind.annotation.GetMapping; | ||
import org.springframework.web.bind.annotation.RequestMapping; | ||
import org.springframework.web.bind.annotation.RequestParam; | ||
|
||
import java.util.Collections; | ||
import java.util.List; | ||
|
||
@Controller | ||
@RequestMapping("/api/database") | ||
public class DatabaseController { | ||
|
||
private final DatabaseService databaseService; | ||
private final ResultConverter converter; | ||
|
||
@Autowired | ||
public DatabaseController(DatabaseService databaseService, ResultConverter converter) { | ||
this.databaseService = databaseService; | ||
this.converter = converter; | ||
} | ||
|
||
@GetMapping("/songs") | ||
ResponseEntity<GetSongsResponse> getSongs( | ||
Authentication authentication, | ||
@RequestParam(value = "page", required = false) Integer page, | ||
@RequestParam(value = "limit", required = false) Integer limit) { | ||
List<dev.migwel.sts.domain.model.PersistedSong> songs = | ||
databaseService.getSongs( | ||
authentication.getName(), | ||
limit != null ? limit : 50, | ||
page != null ? page : 0); | ||
if (songs.isEmpty()) { | ||
return new ResponseEntity<>( | ||
new GetSongsResponse(Collections.emptyList()), HttpStatus.NO_CONTENT); | ||
} | ||
List<PersistedSong> dtoSongs = songs.stream().map(converter::convert).toList(); | ||
var response = new GetSongsResponse(dtoSongs); | ||
return new ResponseEntity<>(response, HttpStatus.OK); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
7 changes: 7 additions & 0 deletions
7
src/main/java/dev/migwel/sts/controller/api/dto/GetSongsResponse.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
package dev.migwel.sts.controller.api.dto; | ||
|
||
import dev.migwel.sts.controller.api.dto.database.PersistedSong; | ||
|
||
import java.util.List; | ||
|
||
public record GetSongsResponse(List<PersistedSong> songs) {} |
7 changes: 7 additions & 0 deletions
7
src/main/java/dev/migwel/sts/controller/api/dto/database/PersistedSong.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
package dev.migwel.sts.controller.api.dto.database; | ||
|
||
import dev.migwel.sts.controller.api.dto.Song; | ||
|
||
import java.util.Date; | ||
|
||
public record PersistedSong(Song song, Date creationDate) {} |
44 changes: 44 additions & 0 deletions
44
src/main/java/dev/migwel/sts/database/DatabaseService.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
package dev.migwel.sts.database; | ||
|
||
import dev.migwel.sts.database.entities.Converter; | ||
import dev.migwel.sts.domain.model.PersistedSong; | ||
|
||
import org.springframework.beans.factory.annotation.Autowired; | ||
import org.springframework.stereotype.Service; | ||
|
||
import java.util.Comparator; | ||
import java.util.List; | ||
|
||
import javax.annotation.Nonnull; | ||
import javax.annotation.ParametersAreNonnullByDefault; | ||
|
||
@Service | ||
@ParametersAreNonnullByDefault | ||
public class DatabaseService { | ||
|
||
private final SongRepository songRepository; | ||
private final Converter converter; | ||
private static final int MAX_LIMIT = 50; | ||
|
||
@Autowired | ||
public DatabaseService(SongRepository songRepository, Converter converter) { | ||
this.songRepository = songRepository; | ||
this.converter = converter; | ||
} | ||
|
||
@Nonnull | ||
public List<PersistedSong> getSongs(String username, int limit, int page) { | ||
if (page < 0) { | ||
throw new IllegalArgumentException("page cannot be negative"); | ||
} | ||
if (limit < 0) { | ||
throw new IllegalArgumentException("limit cannot be negative"); | ||
} | ||
List<dev.migwel.sts.database.entities.Song> songs = | ||
songRepository.findByUsername(username, Math.min(limit, MAX_LIMIT), page); | ||
return songs.stream() | ||
.map(converter::convertToPersistedSong) | ||
.sorted(Comparator.comparing(PersistedSong::creationDate)) | ||
.toList(); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,12 +1,26 @@ | ||
package dev.migwel.sts.database; | ||
|
||
import dev.migwel.sts.database.entities.Song; | ||
import org.springframework.data.jpa.repository.Query; | ||
import org.springframework.data.repository.Repository; | ||
|
||
import java.util.List; | ||
import java.util.Optional; | ||
|
||
public interface SongRepository extends Repository<Song, Long> { | ||
Song save(Song song); | ||
|
||
Optional<Song> findByArtistAndTitle(String artist, String title); | ||
|
||
@Query( | ||
""" | ||
select | ||
s | ||
from Song s | ||
where s.username =?1 | ||
order by s.creationDate | ||
limit ?2 | ||
offset ?3 | ||
""") | ||
List<Song> findByUsername(String username, int limit, int offset); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
17 changes: 17 additions & 0 deletions
17
src/main/java/dev/migwel/sts/domain/model/PersistedSong.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
package dev.migwel.sts.domain.model; | ||
|
||
import javax.annotation.ParametersAreNonnullByDefault; | ||
import java.util.Date; | ||
|
||
@ParametersAreNonnullByDefault | ||
public record PersistedSong(Song song, String username, Date creationDate) { | ||
public PersistedSong(Song song, String username, Date creationDate) { | ||
this.song = song; | ||
this.username = username; | ||
this.creationDate = new Date(creationDate.getTime()); | ||
} | ||
|
||
public Date creationDate() { | ||
return new Date(creationDate.getTime()); | ||
} | ||
} |
103 changes: 103 additions & 0 deletions
103
src/test/java/dev/migwel/sts/controller/api/DatabaseControllerTest.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,103 @@ | ||
package dev.migwel.sts.controller.api; | ||
|
||
import static org.mockito.ArgumentMatchers.*; | ||
import static org.mockito.Mockito.verify; | ||
import static org.mockito.Mockito.when; | ||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; | ||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; | ||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; | ||
|
||
import dev.migwel.sts.SecurityConfig; | ||
import dev.migwel.sts.database.DatabaseService; | ||
import dev.migwel.sts.domain.model.PersistedSong; | ||
import dev.migwel.sts.domain.model.Song; | ||
|
||
import dev.migwel.sts.util.FileUtil; | ||
import org.junit.jupiter.api.Test; | ||
import org.springframework.beans.factory.annotation.Autowired; | ||
import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase; | ||
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; | ||
import org.springframework.boot.test.mock.mockito.MockBean; | ||
import org.springframework.context.annotation.Import; | ||
import org.springframework.security.test.context.support.WithAnonymousUser; | ||
import org.springframework.security.test.context.support.WithMockUser; | ||
import org.springframework.test.web.servlet.MockMvc; | ||
|
||
import java.time.Instant; | ||
import java.util.Collections; | ||
import java.util.Date; | ||
import java.util.List; | ||
|
||
@Import(value = {SecurityConfig.class, Converter.class, ResultConverter.class}) | ||
@AutoConfigureTestDatabase | ||
@WebMvcTest(DatabaseController.class) | ||
class DatabaseControllerTest { | ||
|
||
@Autowired private MockMvc mockMvc; | ||
|
||
@Autowired private ResultConverter resultConverter; | ||
@MockBean private DatabaseService databaseService; | ||
|
||
@Test | ||
@WithAnonymousUser | ||
void getSongs_withoutAuthenticatedUser() throws Exception { | ||
this.mockMvc.perform(get("/api/database/songs")).andExpect(status().isUnauthorized()); | ||
; | ||
} | ||
|
||
@Test | ||
@WithMockUser | ||
void getSongs_noSongFound() throws Exception { | ||
when(databaseService.getSongs(any(), anyInt(), anyInt())) | ||
.thenReturn(Collections.emptyList()); | ||
this.mockMvc | ||
.perform(get("/api/database/songs")) | ||
.andExpect(status().isNoContent()) | ||
.andExpect(content().string("{\"songs\":[]}")); | ||
} | ||
|
||
@Test | ||
@WithMockUser | ||
void getSongs_songsFound() throws Exception { | ||
when(databaseService.getSongs(any(), anyInt(), anyInt())) | ||
.thenReturn( | ||
List.of( | ||
new PersistedSong( | ||
new Song("artist", "title", "artist - title"), | ||
"username", | ||
Date.from(Instant.parse("2023-01-01T10:00:00.00Z"))), | ||
new PersistedSong( | ||
new Song("artist", "otherTitle", "artist - otherTitle"), | ||
"username", | ||
Date.from(Instant.parse("2023-01-02T10:00:00.00Z"))))); | ||
this.mockMvc | ||
.perform(get("/api/database/songs")) | ||
.andExpect(status().isOk()) | ||
.andExpect( | ||
content() | ||
.string( | ||
FileUtil.loadFile( | ||
"database/getSongs_songsFound_response.json"))); | ||
} | ||
|
||
@Test | ||
@WithMockUser | ||
void getSongs_noLimitOrPageProvided() throws Exception { | ||
this.mockMvc.perform(get("/api/database/songs")).andExpect(status().isNoContent()); | ||
verify(databaseService).getSongs(any(), intThat(e -> e > 0), eq(0)); | ||
} | ||
|
||
@Test | ||
@WithMockUser | ||
void getSongs_limitAndPageProvided() throws Exception { | ||
int limit = 25; | ||
int page = 2; | ||
this.mockMvc | ||
.perform( | ||
get("/api/database/songs") | ||
.param("limit", Integer.toString(limit)) | ||
.param("page", Integer.toString(page))) | ||
.andExpect(status().isNoContent()); | ||
verify(databaseService).getSongs(any(), eq(limit), eq(page)); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.