Public Access
feat(settings): store a favorite platform
This commit is contained in:
1 parent
315e22c220
commit
ea4ae089ff
14 files changed
+103
-3
No files matched your search
@@ -95,6 +95,7 @@ To Be Played is split into three repositories:
|
|||||||
| `PUT` | `/api/users/settings/appearance` | access token |
|
| `PUT` | `/api/users/settings/appearance` | access token |
|
||||||
| `PUT` | `/api/users/settings/profile-style` (`{accent, corners, bannerAppid, backgroundAppid}`; `204`) | access token |
|
| `PUT` | `/api/users/settings/profile-style` (`{accent, corners, bannerAppid, backgroundAppid}`; `204`) | access token |
|
||||||
| `PUT` | `/api/users/settings/dashboard-tiles` (`{tiles: [{id, size}]}` in order, at most 20; `{tiles: null}` resets; `204`) | access token |
|
| `PUT` | `/api/users/settings/dashboard-tiles` (`{tiles: [{id, size}]}` in order, at most 20; `{tiles: null}` resets; `204`) | access token |
|
||||||
|
| `PUT` | `/api/users/settings/favorite-platform` (`{platform}`, free text up to 32, e.g. `PS5`; `null` clears it; `204`) | access token |
|
||||||
| `PUT` | `/api/users/settings/profile-tiles` (`{tiles: [{id, size}]}`: `SUMMARY`, `STATUS`, `PLAYING`, `FINISHED`, `STATS_<GROUP>`; `204`) | access token |
|
| `PUT` | `/api/users/settings/profile-tiles` (`{tiles: [{id, size}]}`: `SUMMARY`, `STATUS`, `PLAYING`, `FINISHED`, `STATS_<GROUP>`; `204`) | access token |
|
||||||
| `GET` | `/api/public/profiles/{displayName}` | public |
|
| `GET` | `/api/public/profiles/{displayName}` | public |
|
||||||
| `GET` | `/api/stats/public` | public |
|
| `GET` | `/api/stats/public` | public |
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package auth_service.modules.user.api;
|
|||||||
import auth_service.shared.security.UserPrincipal;
|
import auth_service.shared.security.UserPrincipal;
|
||||||
import auth_service.modules.user.api.dto.request.AppearanceRequest;
|
import auth_service.modules.user.api.dto.request.AppearanceRequest;
|
||||||
import auth_service.modules.user.api.dto.request.DashboardTilesRequest;
|
import auth_service.modules.user.api.dto.request.DashboardTilesRequest;
|
||||||
|
import auth_service.modules.user.api.dto.request.FavoritePlatformRequest;
|
||||||
import auth_service.modules.user.api.dto.request.ProfileTilesRequest;
|
import auth_service.modules.user.api.dto.request.ProfileTilesRequest;
|
||||||
import auth_service.modules.user.api.dto.request.ProfileStyleRequest;
|
import auth_service.modules.user.api.dto.request.ProfileStyleRequest;
|
||||||
import auth_service.modules.user.api.dto.request.UserSettingsRequest;
|
import auth_service.modules.user.api.dto.request.UserSettingsRequest;
|
||||||
@@ -62,6 +63,13 @@ public class UserController {
|
|||||||
return ResponseEntity.noContent().build();
|
return ResponseEntity.noContent().build();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@PutMapping(value = "/settings/favorite-platform", version = "1.0")
|
||||||
|
public ResponseEntity<Void> updateFavoritePlatform(@AuthenticationPrincipal UserPrincipal principal,
|
||||||
|
@Valid @RequestBody FavoritePlatformRequest request) {
|
||||||
|
this.userService.updateFavoritePlatform(principal.getIdAsLong(), request.platform());
|
||||||
|
return ResponseEntity.noContent().build();
|
||||||
|
}
|
||||||
|
|
||||||
@PutMapping(value = "/settings/profile-style", version = "1.0")
|
@PutMapping(value = "/settings/profile-style", version = "1.0")
|
||||||
public ResponseEntity<Void> updateProfileStyle(@AuthenticationPrincipal UserPrincipal principal,
|
public ResponseEntity<Void> updateProfileStyle(@AuthenticationPrincipal UserPrincipal principal,
|
||||||
@Valid @RequestBody ProfileStyleRequest request) {
|
@Valid @RequestBody ProfileStyleRequest request) {
|
||||||
|
|||||||
@@ -0,0 +1,10 @@
|
|||||||
|
package auth_service.modules.user.api.dto.request;
|
||||||
|
|
||||||
|
import jakarta.validation.constraints.Pattern;
|
||||||
|
import jakarta.validation.constraints.Size;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param platform free text like Core's game platform: the frontend offers the list; null clears it
|
||||||
|
*/
|
||||||
|
public record FavoritePlatformRequest(@Size(max = 32) @Pattern(regexp = ".*\\S.*") String platform) {
|
||||||
|
}
|
||||||
@@ -7,5 +7,6 @@ import java.util.List;
|
|||||||
@Builder
|
@Builder
|
||||||
public record SettingsResponse(long id, boolean useDarkTheme, String language, boolean publicProfile,
|
public record SettingsResponse(long id, boolean useDarkTheme, String language, boolean publicProfile,
|
||||||
AppearanceResponse appearance, ProfileStyleResponse profileStyle,
|
AppearanceResponse appearance, ProfileStyleResponse profileStyle,
|
||||||
List<TileResponse> dashboardTiles, List<TileResponse> profileTiles) {
|
List<TileResponse> dashboardTiles, List<TileResponse> profileTiles,
|
||||||
|
String favoritePlatform) {
|
||||||
}
|
}
|
||||||
@@ -47,6 +47,7 @@ public abstract class ApiMapper {
|
|||||||
.dashboardTiles(user.getSettings().getDashboardTiles() == null ? null
|
.dashboardTiles(user.getSettings().getDashboardTiles() == null ? null
|
||||||
: toApi(user.getSettings().getDashboardTiles()))
|
: toApi(user.getSettings().getDashboardTiles()))
|
||||||
.profileTiles(toApi(user.getSettings().getProfileTiles()))
|
.profileTiles(toApi(user.getSettings().getProfileTiles()))
|
||||||
|
.favoritePlatform(user.getSettings().getFavoritePlatform())
|
||||||
.build())
|
.build())
|
||||||
.build();
|
.build();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ public class UserSettings {
|
|||||||
private List<Tile> dashboardTiles;
|
private List<Tile> dashboardTiles;
|
||||||
@Builder.Default
|
@Builder.Default
|
||||||
private List<Tile> profileTiles = DEFAULT_PROFILE_TILES;
|
private List<Tile> profileTiles = DEFAULT_PROFILE_TILES;
|
||||||
|
private String favoritePlatform;
|
||||||
|
|
||||||
/** Also the column default of user_settings.profile_tiles. */
|
/** Also the column default of user_settings.profile_tiles. */
|
||||||
public static final List<Tile> DEFAULT_PROFILE_TILES = List.of(new Tile("SUMMARY", "M"), new Tile("STATUS", "M"),
|
public static final List<Tile> DEFAULT_PROFILE_TILES = List.of(new Tile("SUMMARY", "M"), new Tile("STATUS", "M"),
|
||||||
|
|||||||
@@ -38,6 +38,8 @@ public interface UserRepository {
|
|||||||
|
|
||||||
void updateProfileTiles(long userId, List<Tile> tiles);
|
void updateProfileTiles(long userId, List<Tile> tiles);
|
||||||
|
|
||||||
|
void updateFavoritePlatform(long userId, String platform);
|
||||||
|
|
||||||
void updateProfileStyle(long userId, ProfileStyle style);
|
void updateProfileStyle(long userId, ProfileStyle style);
|
||||||
|
|
||||||
Optional<SteamIdHolder> findSteamIdHolder(long steamId);
|
Optional<SteamIdHolder> findSteamIdHolder(long steamId);
|
||||||
|
|||||||
@@ -110,6 +110,12 @@ public class UserRepositoryImpl implements UserRepository {
|
|||||||
settingsOf(userId).setProfileTiles(Tile.storedAll(tiles));
|
settingsOf(userId).setProfileTiles(Tile.storedAll(tiles));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Transactional
|
||||||
|
@Override
|
||||||
|
public void updateFavoritePlatform(long userId, String platform) {
|
||||||
|
settingsOf(userId).setFavoritePlatform(platform);
|
||||||
|
}
|
||||||
|
|
||||||
@Transactional
|
@Transactional
|
||||||
@Override
|
@Override
|
||||||
public void updateProfileStyle(long userId, ProfileStyle style) {
|
public void updateProfileStyle(long userId, ProfileStyle style) {
|
||||||
|
|||||||
@@ -71,6 +71,9 @@ public class UserSettingsEntity {
|
|||||||
@Column(name = "dashboard_tiles")
|
@Column(name = "dashboard_tiles")
|
||||||
private List<String> dashboardTiles;
|
private List<String> dashboardTiles;
|
||||||
|
|
||||||
|
@Column(name = "favorite_platform")
|
||||||
|
private String favoritePlatform;
|
||||||
|
|
||||||
@JdbcTypeCode(SqlTypes.ARRAY)
|
@JdbcTypeCode(SqlTypes.ARRAY)
|
||||||
@Column(name = "profile_tiles", nullable = false)
|
@Column(name = "profile_tiles", nullable = false)
|
||||||
private List<String> profileTiles = Tile.storedAll(UserSettings.DEFAULT_PROFILE_TILES);
|
private List<String> profileTiles = Tile.storedAll(UserSettings.DEFAULT_PROFILE_TILES);
|
||||||
|
|||||||
@@ -37,6 +37,7 @@ public abstract class EntityMapper {
|
|||||||
.dashboardTiles(entity.getSettings().getDashboardTiles() == null ? null
|
.dashboardTiles(entity.getSettings().getDashboardTiles() == null ? null
|
||||||
: entity.getSettings().getDashboardTiles().stream().map(Tile::parse).toList())
|
: entity.getSettings().getDashboardTiles().stream().map(Tile::parse).toList())
|
||||||
.profileTiles(entity.getSettings().getProfileTiles().stream().map(Tile::parse).toList())
|
.profileTiles(entity.getSettings().getProfileTiles().stream().map(Tile::parse).toList())
|
||||||
|
.favoritePlatform(entity.getSettings().getFavoritePlatform())
|
||||||
.build();
|
.build();
|
||||||
} else {
|
} else {
|
||||||
// Fallback default settings for users without an entry in the database
|
// Fallback default settings for users without an entry in the database
|
||||||
|
|||||||
@@ -112,6 +112,13 @@ public class UserService {
|
|||||||
userRepository.updateProfileTiles(userId, Tile.distinct(tiles));
|
userRepository.updateProfileTiles(userId, Tile.distinct(tiles));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@RequiresRole(Role.USER)
|
||||||
|
@Transactional
|
||||||
|
@CacheEvict(value = "usersByEmail", allEntries = true)
|
||||||
|
public void updateFavoritePlatform(long userId, String platform) {
|
||||||
|
userRepository.updateFavoritePlatform(userId, platform == null ? null : platform.strip());
|
||||||
|
}
|
||||||
|
|
||||||
@RequiresRole(Role.USER)
|
@RequiresRole(Role.USER)
|
||||||
@Transactional
|
@Transactional
|
||||||
@CacheEvict(value = "usersByEmail", allEntries = true)
|
@CacheEvict(value = "usersByEmail", allEntries = true)
|
||||||
|
|||||||
@@ -0,0 +1,3 @@
|
|||||||
|
-- The platform preselected when a game is added by hand or imported (Core item 23), an IGDB abbreviation such as
|
||||||
|
-- 'PS5'. NULL: none.
|
||||||
|
ALTER TABLE user_settings ADD COLUMN favorite_platform VARCHAR(32);
|
||||||
@@ -36,6 +36,7 @@ import java.util.List;
|
|||||||
|
|
||||||
import static org.mockito.ArgumentMatchers.*;
|
import static org.mockito.ArgumentMatchers.*;
|
||||||
import static org.mockito.Mockito.doThrow;
|
import static org.mockito.Mockito.doThrow;
|
||||||
|
import static org.mockito.Mockito.never;
|
||||||
import static org.mockito.Mockito.verify;
|
import static org.mockito.Mockito.verify;
|
||||||
import static org.mockito.Mockito.verifyNoInteractions;
|
import static org.mockito.Mockito.verifyNoInteractions;
|
||||||
import static org.mockito.Mockito.when;
|
import static org.mockito.Mockito.when;
|
||||||
@@ -355,7 +356,7 @@ class UserControllerTest extends AbstractWebMvcTest {
|
|||||||
String email = "test@example.com";
|
String email = "test@example.com";
|
||||||
UserSettings settings = UserSettings.builder().language("en").theme("dark")
|
UserSettings settings = UserSettings.builder().language("en").theme("dark")
|
||||||
.dashboardTiles(List.of(new Tile("STATS_TREND", "L")))
|
.dashboardTiles(List.of(new Tile("STATS_TREND", "L")))
|
||||||
.profileTiles(List.of(new Tile("PLAYING", "S"))).build();
|
.profileTiles(List.of(new Tile("PLAYING", "S"))).favoritePlatform("PS5").build();
|
||||||
SecurityContextHolder.getContext().setAuthentication(
|
SecurityContextHolder.getContext().setAuthentication(
|
||||||
new UsernamePasswordAuthenticationToken(new UserPrincipal("1", email, null), null, Collections.emptyList())
|
new UsernamePasswordAuthenticationToken(new UserPrincipal("1", email, null), null, Collections.emptyList())
|
||||||
);
|
);
|
||||||
@@ -366,7 +367,8 @@ class UserControllerTest extends AbstractWebMvcTest {
|
|||||||
.andExpect(jsonPath("$.settings.dashboardTiles[0].id").value("STATS_TREND"))
|
.andExpect(jsonPath("$.settings.dashboardTiles[0].id").value("STATS_TREND"))
|
||||||
.andExpect(jsonPath("$.settings.dashboardTiles[0].size").value("L"))
|
.andExpect(jsonPath("$.settings.dashboardTiles[0].size").value("L"))
|
||||||
.andExpect(jsonPath("$.settings.profileTiles[0].id").value("PLAYING"))
|
.andExpect(jsonPath("$.settings.profileTiles[0].id").value("PLAYING"))
|
||||||
.andExpect(jsonPath("$.settings.profileTiles[0].size").value("S"));
|
.andExpect(jsonPath("$.settings.profileTiles[0].size").value("S"))
|
||||||
|
.andExpect(jsonPath("$.settings.favoritePlatform").value("PS5"));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -410,6 +412,38 @@ class UserControllerTest extends AbstractWebMvcTest {
|
|||||||
verify(userService).updateDashboardTiles(1L, null);
|
verify(userService).updateDashboardTiles(1L, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void updateFavoritePlatform_StoresOrClearsIt() throws Exception {
|
||||||
|
SecurityContextHolder.getContext().setAuthentication(new UsernamePasswordAuthenticationToken(
|
||||||
|
new UserPrincipal("1", "test@example.com", null), null, Collections.emptyList()));
|
||||||
|
|
||||||
|
mockMvc.perform(put("/api/users/settings/favorite-platform")
|
||||||
|
.contentType(MediaType.APPLICATION_JSON)
|
||||||
|
.content("{\"platform\":\"PS5\"}"))
|
||||||
|
.andExpect(status().isNoContent());
|
||||||
|
mockMvc.perform(put("/api/users/settings/favorite-platform")
|
||||||
|
.contentType(MediaType.APPLICATION_JSON)
|
||||||
|
.content("{\"platform\":null}"))
|
||||||
|
.andExpect(status().isNoContent());
|
||||||
|
|
||||||
|
verify(userService).updateFavoritePlatform(1L, "PS5");
|
||||||
|
verify(userService).updateFavoritePlatform(1L, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void updateFavoritePlatform_BlankOrTooLong_Is400() throws Exception {
|
||||||
|
SecurityContextHolder.getContext().setAuthentication(new UsernamePasswordAuthenticationToken(
|
||||||
|
new UserPrincipal("1", "test@example.com", null), null, Collections.emptyList()));
|
||||||
|
|
||||||
|
for (String platform : List.of(" ", "x".repeat(33))) {
|
||||||
|
mockMvc.perform(put("/api/users/settings/favorite-platform")
|
||||||
|
.contentType(MediaType.APPLICATION_JSON)
|
||||||
|
.content("{\"platform\":\"" + platform + "\"}"))
|
||||||
|
.andExpect(status().isBadRequest());
|
||||||
|
}
|
||||||
|
verify(userService, never()).updateFavoritePlatform(anyLong(), any());
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void updateProfileTiles_StoresTheTiles() throws Exception {
|
void updateProfileTiles_StoresTheTiles() throws Exception {
|
||||||
SecurityContextHolder.getContext().setAuthentication(new UsernamePasswordAuthenticationToken(
|
SecurityContextHolder.getContext().setAuthentication(new UsernamePasswordAuthenticationToken(
|
||||||
|
|||||||
@@ -330,6 +330,28 @@ class UserRepositoryTest extends AbstractPersistenceTest {
|
|||||||
assertEquals(profile, saved.getProfileTiles());
|
assertEquals(profile, saved.getProfileTiles());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void updateFavoritePlatform_StoresAndClears() {
|
||||||
|
UserEntity userEntity = entityManager.persist(UserEntity.builder()
|
||||||
|
.email("favorite@example.com")
|
||||||
|
.password("password")
|
||||||
|
.enabled(true)
|
||||||
|
.registrationDate(LocalDateTime.now())
|
||||||
|
.build());
|
||||||
|
assertNull(userRepository.findByEmail("favorite@example.com").orElseThrow().getSettings().getFavoritePlatform());
|
||||||
|
|
||||||
|
userRepository.updateFavoritePlatform(userEntity.getId(), "Switch");
|
||||||
|
entityManager.flush();
|
||||||
|
entityManager.clear();
|
||||||
|
assertEquals("Switch", userRepository.findByEmail("favorite@example.com").orElseThrow().getSettings()
|
||||||
|
.getFavoritePlatform());
|
||||||
|
|
||||||
|
userRepository.updateFavoritePlatform(userEntity.getId(), null);
|
||||||
|
entityManager.flush();
|
||||||
|
entityManager.clear();
|
||||||
|
assertNull(userRepository.findByEmail("favorite@example.com").orElseThrow().getSettings().getFavoritePlatform());
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void updateDashboardTiles_NullResetsToTheDefault() {
|
void updateDashboardTiles_NullResetsToTheDefault() {
|
||||||
UserEntity userEntity = entityManager.persist(UserEntity.builder()
|
UserEntity userEntity = entityManager.persist(UserEntity.builder()
|
||||||
|
|||||||
Reference in new issue
Block a user