diff --git a/src/main/java/core_service/modules/steam_adapter/api/SteamTestController.java b/src/main/java/core_service/modules/steam_adapter/api/SteamTestController.java index ab967d0..993e254 100644 --- a/src/main/java/core_service/modules/steam_adapter/api/SteamTestController.java +++ b/src/main/java/core_service/modules/steam_adapter/api/SteamTestController.java @@ -1,6 +1,6 @@ package core_service.modules.steam_adapter.api; -import core_service.modules.steam_adapter.api.dto.response.SteamPlayerSummaryResponse; +import core_service.modules.steam_adapter.api.dto.response.SteamProfileAndGamesResponse; import core_service.modules.steam_adapter.service.SteamApiService; import lombok.RequiredArgsConstructor; import org.springframework.http.ResponseEntity; @@ -21,10 +21,10 @@ public class SteamTestController { @GetMapping("/{steamId}") public ResponseEntity testSteamConnection(@PathVariable String steamId) { - Optional playerOpt = steamApiService.getPlayerSummary(steamId); + Optional profileOpt = steamApiService.getFullPlayerProfile(steamId); - if (playerOpt.isPresent()) { - return ResponseEntity.ok(playerOpt.get()); + if (profileOpt.isPresent()) { + return ResponseEntity.ok(profileOpt.get()); } else { return ResponseEntity.notFound().build(); } diff --git a/src/main/java/core_service/modules/steam_adapter/api/dto/response/SteamOwnedGamesResponse.java b/src/main/java/core_service/modules/steam_adapter/api/dto/response/SteamOwnedGamesResponse.java new file mode 100644 index 0000000..e49ee95 --- /dev/null +++ b/src/main/java/core_service/modules/steam_adapter/api/dto/response/SteamOwnedGamesResponse.java @@ -0,0 +1,20 @@ +package core_service.modules.steam_adapter.api.dto.response; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.util.List; + +public record SteamOwnedGamesResponse(OwnedGamesResponse response) { + + public record OwnedGamesResponse( + @JsonProperty("game_count") int gameCount, + List games + ) {} + + public record Game( + Integer appid, + String name, + @JsonProperty("playtime_forever") Integer playtimeForever, + @JsonProperty("img_icon_url") String imgIconUrl, + @JsonProperty("has_community_visible_stats") Boolean hasCommunityVisibleStats + ) {} +} diff --git a/src/main/java/core_service/modules/steam_adapter/api/dto/response/SteamProfileAndGamesResponse.java b/src/main/java/core_service/modules/steam_adapter/api/dto/response/SteamProfileAndGamesResponse.java new file mode 100644 index 0000000..314d9ba --- /dev/null +++ b/src/main/java/core_service/modules/steam_adapter/api/dto/response/SteamProfileAndGamesResponse.java @@ -0,0 +1,9 @@ +package core_service.modules.steam_adapter.api.dto.response; + +import java.util.List; + +public record SteamProfileAndGamesResponse( + SteamPlayerSummaryResponse.Player player, + List games +) { +} diff --git a/src/main/java/core_service/modules/steam_adapter/service/SteamApiService.java b/src/main/java/core_service/modules/steam_adapter/service/SteamApiService.java index 1dfb1a3..3eee7b1 100644 --- a/src/main/java/core_service/modules/steam_adapter/service/SteamApiService.java +++ b/src/main/java/core_service/modules/steam_adapter/service/SteamApiService.java @@ -1,12 +1,16 @@ package core_service.modules.steam_adapter.service; +import core_service.modules.steam_adapter.api.dto.response.SteamOwnedGamesResponse; import core_service.modules.steam_adapter.api.dto.response.SteamPlayerSummaryResponse; +import core_service.modules.steam_adapter.api.dto.response.SteamProfileAndGamesResponse; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; import org.springframework.web.client.RestClient; +import java.util.Collections; +import java.util.List; import java.util.Optional; @Slf4j @@ -19,6 +23,16 @@ public class SteamApiService { @Value("${app.steam.api-key}") private String apiKey; + public Optional getFullPlayerProfile(String steamId) { + var playerOpt = getPlayerSummary(steamId); + if (playerOpt.isEmpty()) { + return Optional.empty(); + } + + var games = getOwnedGames(steamId); + return Optional.of(new SteamProfileAndGamesResponse(playerOpt.get(), games)); + } + public Optional getPlayerSummary(String steamId) { try { log.info("Fetching Steam profile for SteamID: {}", steamId); @@ -42,4 +56,30 @@ public class SteamApiService { return Optional.empty(); } + + public List getOwnedGames(String steamId) { + try { + log.info("Fetching owned games for SteamID: {}", steamId); + + SteamOwnedGamesResponse response = steamRestClient.get() + .uri(uriBuilder -> uriBuilder + .path("/IPlayerService/GetOwnedGames/v0001/") + .queryParam("key", apiKey) + .queryParam("steamid", steamId) + .queryParam("include_appinfo", 1) + .queryParam("include_played_free_games", 1) + .build()) + .retrieve() + .body(SteamOwnedGamesResponse.class); + + if (response != null && response.response() != null && response.response().games() != null) { + return response.response().games(); + } + + } catch (Exception e) { + log.error("Error communicating with Steam API for games of SteamID: {}", steamId, e); + } + + return Collections.emptyList(); + } } diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index c49daff..7268cb0 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -43,6 +43,4 @@ app: steam: api-key: ${STEAM_API_KEY} base-url: https://api.steampowered.com - playerService-url: https://api.steampowered.com/IPlayerService/GetOwnedGames/v1/ - storeService-url: https://api.steampowered.com/IStoreService/GetAppList/v1/ imageService-url: https://cdn.cloudflare.steamstatic.com/steam/apps/ \ No newline at end of file diff --git a/src/test/java/core_service/modules/steam_adapter/service/SteamApiServiceTest.java b/src/test/java/core_service/modules/steam_adapter/service/SteamApiServiceTest.java new file mode 100644 index 0000000..62a69b4 --- /dev/null +++ b/src/test/java/core_service/modules/steam_adapter/service/SteamApiServiceTest.java @@ -0,0 +1,170 @@ +package core_service.modules.steam_adapter.service; + +import core_service.modules.steam_adapter.api.dto.response.SteamOwnedGamesResponse; +import core_service.modules.steam_adapter.api.dto.response.SteamPlayerSummaryResponse; +import core_service.modules.steam_adapter.api.dto.response.SteamProfileAndGamesResponse; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.http.MediaType; +import org.springframework.test.util.ReflectionTestUtils; +import org.springframework.test.web.client.MockRestServiceServer; +import org.springframework.web.client.RestClient; + +import java.util.List; +import java.util.Optional; + +import static org.hamcrest.CoreMatchers.containsString; +import static org.assertj.core.api.Assertions.assertThat; +import static org.springframework.test.web.client.match.MockRestRequestMatchers.queryParam; +import static org.springframework.test.web.client.match.MockRestRequestMatchers.requestTo; +import static org.springframework.test.web.client.response.MockRestResponseCreators.withSuccess; + +class SteamApiServiceTest { + + private SteamApiService steamApiService; + private MockRestServiceServer server; + + @BeforeEach + void setUp() { + RestClient.Builder builder = RestClient.builder(); + server = MockRestServiceServer.bindTo(builder).build(); + steamApiService = new SteamApiService(builder.build()); + ReflectionTestUtils.setField(steamApiService, "apiKey", "test-api-key"); + } + + @Test + void getPlayerSummary_shouldReturnPlayer() { + String steamId = "76561197960435530"; + String jsonResponse = """ + { + "response": { + "players": [ + { + "steamid": "76561197960435530", + "personaname": "TestUser", + "profileurl": "http://steamcommunity.com/id/testuser/", + "avatarfull": "http://media.steampowered.com/avatar.jpg" + } + ] + } + } + """; + + server.expect(requestTo(containsString("/ISteamUser/GetPlayerSummaries/v0002/"))) + .andExpect(queryParam("steamids", steamId)) + .andExpect(queryParam("key", "test-api-key")) + .andRespond(withSuccess(jsonResponse, MediaType.APPLICATION_JSON)); + + Optional result = steamApiService.getPlayerSummary(steamId); + + assertThat(result).isPresent(); + assertThat(result.get().personaname()).isEqualTo("TestUser"); + } + + @Test + void getOwnedGames_shouldReturnGames() { + String steamId = "76561197960435530"; + String jsonResponse = """ + { + "response": { + "game_count": 1, + "games": [ + { + "appid": 10, + "name": "Counter-Strike", + "playtime_forever": 32, + "img_icon_url": "icon_url", + "has_community_visible_stats": true + } + ] + } + } + """; + + server.expect(requestTo(containsString("/IPlayerService/GetOwnedGames/v0001/"))) + .andExpect(queryParam("steamid", steamId)) + .andExpect(queryParam("key", "test-api-key")) + .andRespond(withSuccess(jsonResponse, MediaType.APPLICATION_JSON)); + + List result = steamApiService.getOwnedGames(steamId); + + assertThat(result).hasSize(1); + assertThat(result.get(0).name()).isEqualTo("Counter-Strike"); + } + + @Test + void getOwnedGames_shouldReturnGamesEvenWithNullValues() { + String steamId = "76561197960435530"; + String jsonResponse = """ + { + "response": { + "game_count": 1, + "games": [ + { + "appid": 10, + "name": "Counter-Strike" + } + ] + } + } + """; + + server.expect(requestTo(containsString("/IPlayerService/GetOwnedGames/v0001/"))) + .andRespond(withSuccess(jsonResponse, MediaType.APPLICATION_JSON)); + + List result = steamApiService.getOwnedGames(steamId); + + assertThat(result).hasSize(1); + assertThat(result.get(0).name()).isEqualTo("Counter-Strike"); + assertThat(result.get(0).playtimeForever()).isNull(); + assertThat(result.get(0).hasCommunityVisibleStats()).isNull(); + } + + @Test + void getFullPlayerProfile_shouldReturnCombinedResult() { + String steamId = "76561197960435530"; + String summaryJson = """ + { + "response": { + "players": [ + { + "steamid": "76561197960435530", + "personaname": "TestUser", + "profileurl": "url", + "avatarfull": "avatar" + } + ] + } + } + """; + String gamesJson = """ + { + "response": { + "game_count": 1, + "games": [ + { + "appid": 10, + "name": "Counter-Strike", + "playtime_forever": 32, + "img_icon_url": "icon_url", + "has_community_visible_stats": true + } + ] + } + } + """; + + server.expect(requestTo(containsString("/ISteamUser/GetPlayerSummaries/v0002/"))) + .andRespond(withSuccess(summaryJson, MediaType.APPLICATION_JSON)); + + server.expect(requestTo(containsString("/IPlayerService/GetOwnedGames/v0001/"))) + .andRespond(withSuccess(gamesJson, MediaType.APPLICATION_JSON)); + + Optional result = steamApiService.getFullPlayerProfile(steamId); + + assertThat(result).isPresent(); + assertThat(result.get().player().personaname()).isEqualTo("TestUser"); + assertThat(result.get().games()).hasSize(1); + assertThat(result.get().games().get(0).name()).isEqualTo("Counter-Strike"); + } +}