From 8d529df1ecf998e5c45b132e619ed3cc40d64b73 Mon Sep 17 00:00:00 2001 From: Salem Ba-Rabuod Date: Tue, 9 Jun 2026 22:13:18 +0200 Subject: [PATCH 1/8] step-up database connection --- postify/pom.xml | 12 ++++++++++ .../checker/DatabaseConnectionCheck.java | 22 +++++++++++++++++++ .../postify/repository/StreamRepository.java | 14 ++++++++++++ .../src/main/resources/application.properties | 5 ++++- 4 files changed, 52 insertions(+), 1 deletion(-) create mode 100644 postify/src/main/java/net/hackyourfuture/backend/week6/postify/checker/DatabaseConnectionCheck.java create mode 100644 postify/src/main/java/net/hackyourfuture/backend/week6/postify/repository/StreamRepository.java diff --git a/postify/pom.xml b/postify/pom.xml index 7cea5f7..3b2baa3 100644 --- a/postify/pom.xml +++ b/postify/pom.xml @@ -50,6 +50,18 @@ spring-boot-starter-webmvc-test test + + + org.springframework.boot + spring-boot-starter-jdbc + + + + + org.postgresql + postgresql + runtime + diff --git a/postify/src/main/java/net/hackyourfuture/backend/week6/postify/checker/DatabaseConnectionCheck.java b/postify/src/main/java/net/hackyourfuture/backend/week6/postify/checker/DatabaseConnectionCheck.java new file mode 100644 index 0000000..e20e0d6 --- /dev/null +++ b/postify/src/main/java/net/hackyourfuture/backend/week6/postify/checker/DatabaseConnectionCheck.java @@ -0,0 +1,22 @@ +package net.hackyourfuture.backend.week6.postify.checker; + +import org.springframework.boot.context.event.ApplicationReadyEvent; +import org.springframework.context.event.EventListener; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.stereotype.Component; + +@Component +public class DatabaseConnectionCheck { + + private final JdbcTemplate jdbcTemplate; + + public DatabaseConnectionCheck(JdbcTemplate jdbcTemplate) { + this.jdbcTemplate = jdbcTemplate; + } + + @EventListener(ApplicationReadyEvent.class) + public void checkConnection() { + String result = this.jdbcTemplate.queryForObject("SELECT 'connected'", String.class); + System.out.println("Database status: " + result); + } +} \ No newline at end of file diff --git a/postify/src/main/java/net/hackyourfuture/backend/week6/postify/repository/StreamRepository.java b/postify/src/main/java/net/hackyourfuture/backend/week6/postify/repository/StreamRepository.java new file mode 100644 index 0000000..bf60a1e --- /dev/null +++ b/postify/src/main/java/net/hackyourfuture/backend/week6/postify/repository/StreamRepository.java @@ -0,0 +1,14 @@ +package net.hackyourfuture.backend.week6.postify.repository; + +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.stereotype.Repository; + +@Repository +public class StreamRepository { + + private final JdbcTemplate jdbcTemplate; + + public StreamRepository(JdbcTemplate jdbcTemplate) { + this.jdbcTemplate = jdbcTemplate; + } +} \ No newline at end of file diff --git a/postify/src/main/resources/application.properties b/postify/src/main/resources/application.properties index 8f4842f..fc3ab0d 100644 --- a/postify/src/main/resources/application.properties +++ b/postify/src/main/resources/application.properties @@ -1 +1,4 @@ -spring.application.name=postify +spring.datasource.url=jdbc:postgresql://localhost:5432/postify +spring.datasource.username=hyfuser +spring.datasource.password=hyfpassword +spring.datasource.driver-class-name=org.postgresql.Driver \ No newline at end of file From f634b0037b286bd90ddbf9ba552e3154a1f7b27a Mon Sep 17 00:00:00 2001 From: Salem Ba-Rabuod Date: Tue, 9 Jun 2026 22:25:47 +0200 Subject: [PATCH 2/8] basis userStats End point --- .../postify/controller/UserController.java | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 postify/src/main/java/net/hackyourfuture/backend/week6/postify/controller/UserController.java diff --git a/postify/src/main/java/net/hackyourfuture/backend/week6/postify/controller/UserController.java b/postify/src/main/java/net/hackyourfuture/backend/week6/postify/controller/UserController.java new file mode 100644 index 0000000..cc8cceb --- /dev/null +++ b/postify/src/main/java/net/hackyourfuture/backend/week6/postify/controller/UserController.java @@ -0,0 +1,20 @@ +package net.hackyourfuture.backend.week6.postify.controller; + + +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RestController; + +@RestController +public class UserController { + + + @GetMapping("/users/{id}/stats") + public String getUserStatsById(@PathVariable Long id) { + return "Hello!" + id; + } + + +} + + From cb962a37b29a674c553158b06f5d673d6c5fb2ec Mon Sep 17 00:00:00 2001 From: Salem Ba-Rabuod Date: Tue, 9 Jun 2026 23:11:53 +0200 Subject: [PATCH 3/8] basic flow Endpoint userStats --- .../checker/DatabaseConnectionCheck.java | 22 ------------- .../postify/controller/UserController.java | 22 +++++++++++-- .../week6/postify/dto/UserStatsResponse.java | 14 +++++++++ .../postify/repository/StreamRepository.java | 14 --------- .../postify/repository/UserRepository.java | 31 +++++++++++++++++++ 5 files changed, 64 insertions(+), 39 deletions(-) delete mode 100644 postify/src/main/java/net/hackyourfuture/backend/week6/postify/checker/DatabaseConnectionCheck.java create mode 100644 postify/src/main/java/net/hackyourfuture/backend/week6/postify/dto/UserStatsResponse.java delete mode 100644 postify/src/main/java/net/hackyourfuture/backend/week6/postify/repository/StreamRepository.java create mode 100644 postify/src/main/java/net/hackyourfuture/backend/week6/postify/repository/UserRepository.java diff --git a/postify/src/main/java/net/hackyourfuture/backend/week6/postify/checker/DatabaseConnectionCheck.java b/postify/src/main/java/net/hackyourfuture/backend/week6/postify/checker/DatabaseConnectionCheck.java deleted file mode 100644 index e20e0d6..0000000 --- a/postify/src/main/java/net/hackyourfuture/backend/week6/postify/checker/DatabaseConnectionCheck.java +++ /dev/null @@ -1,22 +0,0 @@ -package net.hackyourfuture.backend.week6.postify.checker; - -import org.springframework.boot.context.event.ApplicationReadyEvent; -import org.springframework.context.event.EventListener; -import org.springframework.jdbc.core.JdbcTemplate; -import org.springframework.stereotype.Component; - -@Component -public class DatabaseConnectionCheck { - - private final JdbcTemplate jdbcTemplate; - - public DatabaseConnectionCheck(JdbcTemplate jdbcTemplate) { - this.jdbcTemplate = jdbcTemplate; - } - - @EventListener(ApplicationReadyEvent.class) - public void checkConnection() { - String result = this.jdbcTemplate.queryForObject("SELECT 'connected'", String.class); - System.out.println("Database status: " + result); - } -} \ No newline at end of file diff --git a/postify/src/main/java/net/hackyourfuture/backend/week6/postify/controller/UserController.java b/postify/src/main/java/net/hackyourfuture/backend/week6/postify/controller/UserController.java index cc8cceb..ccc0192 100644 --- a/postify/src/main/java/net/hackyourfuture/backend/week6/postify/controller/UserController.java +++ b/postify/src/main/java/net/hackyourfuture/backend/week6/postify/controller/UserController.java @@ -1,17 +1,33 @@ package net.hackyourfuture.backend.week6.postify.controller; +import net.hackyourfuture.backend.week6.postify.dto.UserStatsResponse; +import org.springframework.http.ResponseEntity; +import net.hackyourfuture.backend.week6.postify.repository.UserRepository; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @RestController +@RequestMapping("/users") public class UserController { + private final UserRepository userRepository; - @GetMapping("/users/{id}/stats") - public String getUserStatsById(@PathVariable Long id) { - return "Hello!" + id; + public UserController(UserRepository userRepository) { + this.userRepository = userRepository; + } + + @GetMapping("/{id}/stats") + public ResponseEntity getUserStatsById(@PathVariable Long id) { + UserStatsResponse stats = userRepository.getUserStatsById(id); + + if (stats != null) { + return ResponseEntity.ok(stats); + } + + return ResponseEntity.notFound().build(); } diff --git a/postify/src/main/java/net/hackyourfuture/backend/week6/postify/dto/UserStatsResponse.java b/postify/src/main/java/net/hackyourfuture/backend/week6/postify/dto/UserStatsResponse.java new file mode 100644 index 0000000..2fdf35f --- /dev/null +++ b/postify/src/main/java/net/hackyourfuture/backend/week6/postify/dto/UserStatsResponse.java @@ -0,0 +1,14 @@ +package net.hackyourfuture.backend.week6.postify.dto; + + +import lombok.AllArgsConstructor; +import lombok.Getter; + + +@Getter +@AllArgsConstructor +public class UserStatsResponse { + private final Long userId; + private final String userName; + +} \ No newline at end of file diff --git a/postify/src/main/java/net/hackyourfuture/backend/week6/postify/repository/StreamRepository.java b/postify/src/main/java/net/hackyourfuture/backend/week6/postify/repository/StreamRepository.java deleted file mode 100644 index bf60a1e..0000000 --- a/postify/src/main/java/net/hackyourfuture/backend/week6/postify/repository/StreamRepository.java +++ /dev/null @@ -1,14 +0,0 @@ -package net.hackyourfuture.backend.week6.postify.repository; - -import org.springframework.jdbc.core.JdbcTemplate; -import org.springframework.stereotype.Repository; - -@Repository -public class StreamRepository { - - private final JdbcTemplate jdbcTemplate; - - public StreamRepository(JdbcTemplate jdbcTemplate) { - this.jdbcTemplate = jdbcTemplate; - } -} \ No newline at end of file diff --git a/postify/src/main/java/net/hackyourfuture/backend/week6/postify/repository/UserRepository.java b/postify/src/main/java/net/hackyourfuture/backend/week6/postify/repository/UserRepository.java new file mode 100644 index 0000000..b113795 --- /dev/null +++ b/postify/src/main/java/net/hackyourfuture/backend/week6/postify/repository/UserRepository.java @@ -0,0 +1,31 @@ +package net.hackyourfuture.backend.week6.postify.repository; + +import net.hackyourfuture.backend.week6.postify.dto.UserStatsResponse; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.stereotype.Repository; + +@Repository +public class UserRepository { + + private final JdbcTemplate jdbcTemplate; + + public UserRepository(JdbcTemplate jdbcTemplate) { + this.jdbcTemplate = jdbcTemplate; + } + + public UserStatsResponse getUserStatsById(Long id) { + String sql = "SELECT user_id, user_name FROM users WHERE user_id = ?"; + + try { + return jdbcTemplate.queryForObject(sql, (rs, rowNum) -> + new UserStatsResponse( + rs.getLong("user_id"), + rs.getString("user_name") + ), + id + ); + } catch (Exception e) { + return null; + } + } +} \ No newline at end of file From ecb88ef393f955d74f0f80e1d26192ee9cc15992 Mon Sep 17 00:00:00 2001 From: Salem Ba-Rabuod Date: Wed, 10 Jun 2026 13:57:01 +0200 Subject: [PATCH 4/8] Implement ENDpoint user stats --- .../postify/controller/UserController.java | 7 ++- .../week6/postify/dto/UserStatsResponse.java | 7 ++- .../postify/repository/UserRepository.java | 49 +++++++++++++++++-- 3 files changed, 54 insertions(+), 9 deletions(-) diff --git a/postify/src/main/java/net/hackyourfuture/backend/week6/postify/controller/UserController.java b/postify/src/main/java/net/hackyourfuture/backend/week6/postify/controller/UserController.java index ccc0192..eea12f9 100644 --- a/postify/src/main/java/net/hackyourfuture/backend/week6/postify/controller/UserController.java +++ b/postify/src/main/java/net/hackyourfuture/backend/week6/postify/controller/UserController.java @@ -2,6 +2,7 @@ import net.hackyourfuture.backend.week6.postify.dto.UserStatsResponse; +import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import net.hackyourfuture.backend.week6.postify.repository.UserRepository; import org.springframework.web.bind.annotation.GetMapping; @@ -23,11 +24,9 @@ public UserController(UserRepository userRepository) { public ResponseEntity getUserStatsById(@PathVariable Long id) { UserStatsResponse stats = userRepository.getUserStatsById(id); - if (stats != null) { - return ResponseEntity.ok(stats); - } + return stats != null ? ResponseEntity.ok(stats) + : ResponseEntity.status(HttpStatus.NOT_FOUND).body("User not found"); - return ResponseEntity.notFound().build(); } diff --git a/postify/src/main/java/net/hackyourfuture/backend/week6/postify/dto/UserStatsResponse.java b/postify/src/main/java/net/hackyourfuture/backend/week6/postify/dto/UserStatsResponse.java index 2fdf35f..8d342b0 100644 --- a/postify/src/main/java/net/hackyourfuture/backend/week6/postify/dto/UserStatsResponse.java +++ b/postify/src/main/java/net/hackyourfuture/backend/week6/postify/dto/UserStatsResponse.java @@ -10,5 +10,10 @@ public class UserStatsResponse { private final Long userId; private final String userName; - + private final String userCountry; + private final Long totalStreams; + private final Long uniqueTracksStreamed; + private final Long uniqueArtistsStreamed; + private final String favoriteGenre; + private final Long totalListeningTimeSeconds; } \ No newline at end of file diff --git a/postify/src/main/java/net/hackyourfuture/backend/week6/postify/repository/UserRepository.java b/postify/src/main/java/net/hackyourfuture/backend/week6/postify/repository/UserRepository.java index b113795..595dd02 100644 --- a/postify/src/main/java/net/hackyourfuture/backend/week6/postify/repository/UserRepository.java +++ b/postify/src/main/java/net/hackyourfuture/backend/week6/postify/repository/UserRepository.java @@ -14,16 +14,57 @@ public UserRepository(JdbcTemplate jdbcTemplate) { } public UserStatsResponse getUserStatsById(Long id) { - String sql = "SELECT user_id, user_name FROM users WHERE user_id = ?"; + + String mainSql = "SELECT " + + " users.user_id AS userId, " + + " users.user_name AS userName, " + + " users.user_country AS userCountry, " + + " COUNT(streams.stream_id) AS totalStreams, " + + " COUNT(DISTINCT streams.track_id) AS uniqueTracksStreamed, " + + " COUNT(DISTINCT albums.artist_id) AS uniqueArtistsStreamed, " + + " COALESCE(SUM(tracks.track_duration_s), 0) AS totalListeningTimeSeconds " + + "FROM users " + + "LEFT JOIN streams ON users.user_id = streams.user_id " + + "LEFT JOIN tracks ON streams.track_id = tracks.track_id " + + "LEFT JOIN albums ON tracks.album_id = albums.album_id " + + "WHERE users.user_id = ? " + + "GROUP BY users.user_id, users.user_name, users.user_country;"; + + String genreSql = "SELECT tracks.genre " + + "FROM streams " + + "JOIN tracks ON streams.track_id = tracks.track_id " + + "WHERE streams.user_id = ? " + + "GROUP BY tracks.genre " + + "ORDER BY COUNT(streams.stream_id) DESC " + + "LIMIT 1;"; try { - return jdbcTemplate.queryForObject(sql, (rs, rowNum) -> + String favoriteGenre; + try { + favoriteGenre = jdbcTemplate.queryForObject(genreSql, String.class, id); + if (favoriteGenre == null) { + favoriteGenre = "None"; + } + } catch (Exception e) { + favoriteGenre = "None"; + } + + final String finalGenre = favoriteGenre; + + return jdbcTemplate.queryForObject(mainSql, (rs, rowNum) -> new UserStatsResponse( - rs.getLong("user_id"), - rs.getString("user_name") + rs.getLong("userId"), + rs.getString("userName"), + rs.getString("userCountry"), + rs.getLong("totalStreams"), + rs.getLong("uniqueTracksStreamed"), + rs.getLong("uniqueArtistsStreamed"), + finalGenre, + rs.getLong("totalListeningTimeSeconds") ), id ); + } catch (Exception e) { return null; } From 0c13eb1bbe2c38a0c629faba068998a43964090a Mon Sep 17 00:00:00 2001 From: Salem Ba-Rabuod Date: Wed, 10 Jun 2026 18:25:15 +0200 Subject: [PATCH 5/8] two MockMvc for user stat --- .../controller/UserControllerTest.java | 36 +++++++++++++++++++ 1 file changed, 36 insertions(+) create mode 100644 postify/src/test/java/net/hackyourfuture/backend/week6/postify/controller/UserControllerTest.java diff --git a/postify/src/test/java/net/hackyourfuture/backend/week6/postify/controller/UserControllerTest.java b/postify/src/test/java/net/hackyourfuture/backend/week6/postify/controller/UserControllerTest.java new file mode 100644 index 0000000..4f1f86f --- /dev/null +++ b/postify/src/test/java/net/hackyourfuture/backend/week6/postify/controller/UserControllerTest.java @@ -0,0 +1,36 @@ +package net.hackyourfuture.backend.week6.postify.controller; + +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.webmvc.test.autoconfigure.AutoConfigureMockMvc; +import org.springframework.test.web.servlet.MockMvc; + +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.MOCK) +@AutoConfigureMockMvc +class UserControllerTest { + + @Autowired + private MockMvc mockMvc; + + @Test + void shouldReturnUserStats() throws Exception { + + mockMvc.perform(get("/users/1/stats")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.userId").value(1)) + .andExpect(jsonPath("$.userName").value("lena_v")) + .andExpect(jsonPath("$.userCountry").value("NL")); + } + + @Test + void shouldReturn404WhenUserNotFound() throws Exception { + + mockMvc.perform(get("/users/99999/stats")) + .andExpect(status().isNotFound()); + } +} \ No newline at end of file From 6db6c68ad62b7ec6363ee8e28aba3317a5ab5378 Mon Sep 17 00:00:00 2001 From: Salem Ba-Rabuod Date: Wed, 10 Jun 2026 18:55:59 +0200 Subject: [PATCH 6/8] dto fron task-2 --- .../week6/postify/dto/ExternalLyricsResponse.java | 10 ++++++++++ .../week6/postify/dto/TrackLyricsResponse.java | 15 +++++++++++++++ 2 files changed, 25 insertions(+) create mode 100644 postify/src/main/java/net/hackyourfuture/backend/week6/postify/dto/ExternalLyricsResponse.java create mode 100644 postify/src/main/java/net/hackyourfuture/backend/week6/postify/dto/TrackLyricsResponse.java diff --git a/postify/src/main/java/net/hackyourfuture/backend/week6/postify/dto/ExternalLyricsResponse.java b/postify/src/main/java/net/hackyourfuture/backend/week6/postify/dto/ExternalLyricsResponse.java new file mode 100644 index 0000000..2d56f43 --- /dev/null +++ b/postify/src/main/java/net/hackyourfuture/backend/week6/postify/dto/ExternalLyricsResponse.java @@ -0,0 +1,10 @@ +package net.hackyourfuture.backend.week6.postify.dto; + +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +public class ExternalLyricsResponse { + private String lyrics; +} \ No newline at end of file diff --git a/postify/src/main/java/net/hackyourfuture/backend/week6/postify/dto/TrackLyricsResponse.java b/postify/src/main/java/net/hackyourfuture/backend/week6/postify/dto/TrackLyricsResponse.java new file mode 100644 index 0000000..e86c363 --- /dev/null +++ b/postify/src/main/java/net/hackyourfuture/backend/week6/postify/dto/TrackLyricsResponse.java @@ -0,0 +1,15 @@ +package net.hackyourfuture.backend.week6.postify.dto; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +@AllArgsConstructor +public class TrackLyricsResponse { + private Long trackId; + private String trackTitle; + private String artistName; + private String lyrics; +} \ No newline at end of file From f68f83d1522f3e98a2a0e462e2e7fc174b309f20 Mon Sep 17 00:00:00 2001 From: Salem Ba-Rabuod Date: Wed, 10 Jun 2026 21:26:10 +0200 Subject: [PATCH 7/8] Track Service class --- .../postify/config/RestClientConfig.java | 4 ++ .../postify/controller/TrackController.java | 4 ++ .../postify/repository/TrackRepository.java | 4 ++ .../week6/postify/service/TrackService.java | 58 +++++++++++++++++++ .../controller/TrackControllerTest.java | 4 ++ 5 files changed, 74 insertions(+) create mode 100644 postify/src/main/java/net/hackyourfuture/backend/week6/postify/config/RestClientConfig.java create mode 100644 postify/src/main/java/net/hackyourfuture/backend/week6/postify/controller/TrackController.java create mode 100644 postify/src/main/java/net/hackyourfuture/backend/week6/postify/repository/TrackRepository.java create mode 100644 postify/src/main/java/net/hackyourfuture/backend/week6/postify/service/TrackService.java create mode 100644 postify/src/test/java/net/hackyourfuture/backend/week6/postify/controller/TrackControllerTest.java diff --git a/postify/src/main/java/net/hackyourfuture/backend/week6/postify/config/RestClientConfig.java b/postify/src/main/java/net/hackyourfuture/backend/week6/postify/config/RestClientConfig.java new file mode 100644 index 0000000..eddc589 --- /dev/null +++ b/postify/src/main/java/net/hackyourfuture/backend/week6/postify/config/RestClientConfig.java @@ -0,0 +1,4 @@ +package net.hackyourfuture.backend.week6.postify.config; + +public class RestClientConfig { +} diff --git a/postify/src/main/java/net/hackyourfuture/backend/week6/postify/controller/TrackController.java b/postify/src/main/java/net/hackyourfuture/backend/week6/postify/controller/TrackController.java new file mode 100644 index 0000000..6cba769 --- /dev/null +++ b/postify/src/main/java/net/hackyourfuture/backend/week6/postify/controller/TrackController.java @@ -0,0 +1,4 @@ +package net.hackyourfuture.backend.week6.postify.controller; + +public class TrackController { +} diff --git a/postify/src/main/java/net/hackyourfuture/backend/week6/postify/repository/TrackRepository.java b/postify/src/main/java/net/hackyourfuture/backend/week6/postify/repository/TrackRepository.java new file mode 100644 index 0000000..e9efcb4 --- /dev/null +++ b/postify/src/main/java/net/hackyourfuture/backend/week6/postify/repository/TrackRepository.java @@ -0,0 +1,4 @@ +package net.hackyourfuture.backend.week6.postify.repository; + +public class TrackRepository { +} diff --git a/postify/src/main/java/net/hackyourfuture/backend/week6/postify/service/TrackService.java b/postify/src/main/java/net/hackyourfuture/backend/week6/postify/service/TrackService.java new file mode 100644 index 0000000..fab1b56 --- /dev/null +++ b/postify/src/main/java/net/hackyourfuture/backend/week6/postify/service/TrackService.java @@ -0,0 +1,58 @@ +package net.hackyourfuture.backend.week6.postify.service; + + +import net.hackyourfuture.backend.week6.postify.dto.ExternalLyricsResponse; +import net.hackyourfuture.backend.week6.postify.dto.TrackLyricsResponse; +import net.hackyourfuture.backend.week6.postify.repository.TrackRepository; +import org.springframework.http.HttpStatus; +import org.springframework.stereotype.Service; +import org.springframework.web.client.RestClient; +import org.springframework.web.server.ResponseStatusException; + +@Service +public class TrackService { + + private final TrackRepository trackRepository; + private final RestClient restClient; + + public TrackService(TrackRepository trackRepository, RestClient restClient) { + this.trackRepository = trackRepository; + this.restClient = restClient; + } + + public TrackLyricsResponse getTrackLyrics(Long trackId) { + + TrackLyricsResponse trackInfo = trackRepository.getTrackAndArtistById(trackId); + + if (trackInfo == null) { + throw new ResponseStatusException(HttpStatus.NOT_FOUND, "Track not found in our database"); + } + + + String url = "https://api.lyrics.ovh/v1/" + trackInfo.getArtistName() + "/" + trackInfo.getTrackTitle(); + + try { + + ExternalLyricsResponse externalResponse = restClient.get() + .uri(url) + .retrieve() + .body(ExternalLyricsResponse.class); + + + if (externalResponse != null && externalResponse.getLyrics() != null) { + return new TrackLyricsResponse( + trackInfo.getTrackId(), + trackInfo.getTrackTitle(), + trackInfo.getArtistName(), + externalResponse.getLyrics() + ); + } else { + throw new ResponseStatusException(HttpStatus.NOT_FOUND, "Lyrics not found for this track"); + } + + } catch (Exception e) { + + throw new ResponseStatusException(HttpStatus.NOT_FOUND, "The lyrics API has no lyrics for this track."); + } + } +} \ No newline at end of file diff --git a/postify/src/test/java/net/hackyourfuture/backend/week6/postify/controller/TrackControllerTest.java b/postify/src/test/java/net/hackyourfuture/backend/week6/postify/controller/TrackControllerTest.java new file mode 100644 index 0000000..bd52951 --- /dev/null +++ b/postify/src/test/java/net/hackyourfuture/backend/week6/postify/controller/TrackControllerTest.java @@ -0,0 +1,4 @@ +package net.hackyourfuture.backend.week6.postify.controller; + +public class TrackControllerTest { +} From ae996ed41dbf7c87ccde44e34505deb03f79ad61 Mon Sep 17 00:00:00 2001 From: Salem Ba-Rabuod Date: Wed, 10 Jun 2026 21:29:02 +0200 Subject: [PATCH 8/8] track endpoint with mocktest --- postify/pom.xml | 13 ++-- .../postify/config/RestClientConfig.java | 12 ++- .../postify/controller/TrackController.java | 21 +++++- .../postify/controller/UserController.java | 7 +- .../postify/repository/TrackRepository.java | 39 +++++++++- .../controller/TrackControllerTest.java | 74 ++++++++++++++++++- 6 files changed, 151 insertions(+), 15 deletions(-) diff --git a/postify/pom.xml b/postify/pom.xml index 3b2baa3..abf0fba 100644 --- a/postify/pom.xml +++ b/postify/pom.xml @@ -34,12 +34,6 @@ org.springframework.boot spring-boot-starter-webmvc - - - org.postgresql - postgresql - runtime - org.projectlombok lombok @@ -62,6 +56,13 @@ postgresql runtime + + + org.springframework.boot + spring-boot-starter-test + test + + diff --git a/postify/src/main/java/net/hackyourfuture/backend/week6/postify/config/RestClientConfig.java b/postify/src/main/java/net/hackyourfuture/backend/week6/postify/config/RestClientConfig.java index eddc589..f213438 100644 --- a/postify/src/main/java/net/hackyourfuture/backend/week6/postify/config/RestClientConfig.java +++ b/postify/src/main/java/net/hackyourfuture/backend/week6/postify/config/RestClientConfig.java @@ -1,4 +1,14 @@ package net.hackyourfuture.backend.week6.postify.config; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.client.RestClient; + +@Configuration public class RestClientConfig { -} + + @Bean + public RestClient restClient() { + return RestClient.create(); + } +} \ No newline at end of file diff --git a/postify/src/main/java/net/hackyourfuture/backend/week6/postify/controller/TrackController.java b/postify/src/main/java/net/hackyourfuture/backend/week6/postify/controller/TrackController.java index 6cba769..c4b375c 100644 --- a/postify/src/main/java/net/hackyourfuture/backend/week6/postify/controller/TrackController.java +++ b/postify/src/main/java/net/hackyourfuture/backend/week6/postify/controller/TrackController.java @@ -1,4 +1,23 @@ package net.hackyourfuture.backend.week6.postify.controller; + +import lombok.AllArgsConstructor; +import net.hackyourfuture.backend.week6.postify.dto.TrackLyricsResponse; +import net.hackyourfuture.backend.week6.postify.service.TrackService; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@AllArgsConstructor +@RestController +@RequestMapping("/tracks") public class TrackController { -} + + private final TrackService trackService; + + @GetMapping("/{id}/lyrics") + public TrackLyricsResponse getTrackLyrics(@PathVariable Long id) { + return trackService.getTrackLyrics(id); + } +} \ No newline at end of file diff --git a/postify/src/main/java/net/hackyourfuture/backend/week6/postify/controller/UserController.java b/postify/src/main/java/net/hackyourfuture/backend/week6/postify/controller/UserController.java index eea12f9..6f1b34c 100644 --- a/postify/src/main/java/net/hackyourfuture/backend/week6/postify/controller/UserController.java +++ b/postify/src/main/java/net/hackyourfuture/backend/week6/postify/controller/UserController.java @@ -1,6 +1,7 @@ package net.hackyourfuture.backend.week6.postify.controller; +import lombok.AllArgsConstructor; import net.hackyourfuture.backend.week6.postify.dto.UserStatsResponse; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; @@ -10,16 +11,14 @@ import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; + +@AllArgsConstructor @RestController @RequestMapping("/users") public class UserController { private final UserRepository userRepository; - public UserController(UserRepository userRepository) { - this.userRepository = userRepository; - } - @GetMapping("/{id}/stats") public ResponseEntity getUserStatsById(@PathVariable Long id) { UserStatsResponse stats = userRepository.getUserStatsById(id); diff --git a/postify/src/main/java/net/hackyourfuture/backend/week6/postify/repository/TrackRepository.java b/postify/src/main/java/net/hackyourfuture/backend/week6/postify/repository/TrackRepository.java index e9efcb4..2e147a1 100644 --- a/postify/src/main/java/net/hackyourfuture/backend/week6/postify/repository/TrackRepository.java +++ b/postify/src/main/java/net/hackyourfuture/backend/week6/postify/repository/TrackRepository.java @@ -1,4 +1,41 @@ package net.hackyourfuture.backend.week6.postify.repository; + +import net.hackyourfuture.backend.week6.postify.dto.TrackLyricsResponse; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.stereotype.Repository; + +@Repository public class TrackRepository { -} + + private final JdbcTemplate jdbcTemplate; + + public TrackRepository(JdbcTemplate jdbcTemplate) { + this.jdbcTemplate = jdbcTemplate; + } + + public TrackLyricsResponse getTrackAndArtistById(Long trackId) { + String sql = "SELECT " + + " tracks.track_id AS trackId, " + + " tracks.track_title AS trackTitle, " + + " artists.artist_name AS artistName " + + "FROM tracks " + + "JOIN albums ON tracks.album_id = albums.album_id " + + "JOIN artists ON albums.artist_id = artists.artist_id " + + "WHERE tracks.track_id = ?;"; + + try { + return jdbcTemplate.queryForObject(sql, (rs, rowNum) -> + new TrackLyricsResponse( + rs.getLong("trackId"), + rs.getString("trackTitle"), + rs.getString("artistName"), + null // from external API + ), + trackId + ); + } catch (Exception e) { + return null; + } + } +} \ No newline at end of file diff --git a/postify/src/test/java/net/hackyourfuture/backend/week6/postify/controller/TrackControllerTest.java b/postify/src/test/java/net/hackyourfuture/backend/week6/postify/controller/TrackControllerTest.java index bd52951..89791c5 100644 --- a/postify/src/test/java/net/hackyourfuture/backend/week6/postify/controller/TrackControllerTest.java +++ b/postify/src/test/java/net/hackyourfuture/backend/week6/postify/controller/TrackControllerTest.java @@ -1,4 +1,74 @@ package net.hackyourfuture.backend.week6.postify.controller; -public class TrackControllerTest { -} +import net.hackyourfuture.backend.week6.postify.dto.TrackLyricsResponse; +import net.hackyourfuture.backend.week6.postify.service.TrackService; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; + +import org.springframework.boot.webmvc.test.autoconfigure.AutoConfigureMockMvc; +import org.springframework.http.HttpStatus; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.web.server.ResponseStatusException; +import org.springframework.test.context.bean.override.mockito.MockitoBean; + +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.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.MOCK) +@AutoConfigureMockMvc +class TrackControllerTest { + + @Autowired + private MockMvc mockMvc; + + + @MockitoBean + private TrackService trackService; + + + @Test + void shouldReturnLyricsForExistingTrack() throws Exception { + + TrackLyricsResponse fakeResponse = new TrackLyricsResponse( + 67L, + "the 1", + "Taylor Swift", + "I'm doing good, I'm on some new shi..." + ); + + + when(trackService.getTrackLyrics(67L)).thenReturn(fakeResponse); + + mockMvc.perform(get("/tracks/67/lyrics")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.trackId").value(67L)) + .andExpect(jsonPath("$.trackTitle").value("the 1")) + .andExpect(jsonPath("$.artistName").value("Taylor Swift")) + .andExpect(jsonPath("$.lyrics").value("I'm doing good, I'm on some new shi...")); + } + + + @Test + void shouldReturn404WhenTrackNotFound() throws Exception { + + when(trackService.getTrackLyrics(99999L)) + .thenThrow(new ResponseStatusException(HttpStatus.NOT_FOUND)); + + mockMvc.perform(get("/tracks/99999/lyrics")) + .andExpect(status().isNotFound()); + } + + + @Test + void shouldReturn404WhenLyricsNotAvailable() throws Exception { + + when(trackService.getTrackLyrics(90L)) + .thenThrow(new ResponseStatusException(HttpStatus.NOT_FOUND)); + + mockMvc.perform(get("/tracks/90/lyrics")) + .andExpect(status().isNotFound()); + } +} \ No newline at end of file