Public Access
feat(settings): store chart form choice per stat group
This commit is contained in:
1 parent
19463a1a4d
commit
3410cbc598
19 files changed
+310
-8
No files matched your search
@@ -35,6 +35,7 @@ public class PublicProfileController {
|
|||||||
private static PublicProfileResponse.Style style(PublicProfile profile) {
|
private static PublicProfileResponse.Style style(PublicProfile profile) {
|
||||||
PublicProfile.Style style = profile.style();
|
PublicProfile.Style style = profile.style();
|
||||||
return new PublicProfileResponse.Style(style.accent(), style.corners(), style.bannerAppid(), style.backgroundAppid(),
|
return new PublicProfileResponse.Style(style.accent(), style.corners(), style.bannerAppid(), style.backgroundAppid(),
|
||||||
profile.tiles().stream().map(tile -> new PublicProfileResponse.Tile(tile.id(), tile.size())).toList());
|
profile.tiles().stream().map(tile -> new PublicProfileResponse.Tile(tile.id(), tile.size())).toList(),
|
||||||
|
style.statCharts());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2,6 +2,7 @@ package auth_service.modules.profile.api.dto;
|
|||||||
|
|
||||||
import java.time.LocalDate;
|
import java.time.LocalDate;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param ticket for Core's public profile endpoint; valid for a few minutes
|
* @param ticket for Core's public profile endpoint; valid for a few minutes
|
||||||
@@ -11,8 +12,10 @@ public record PublicProfileResponse(String displayName, LocalDate memberSince, S
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* @param tiles the shown parts of the profile in their order
|
* @param tiles the shown parts of the profile in their order
|
||||||
|
* @param statCharts the owner's chart form per stat group
|
||||||
*/
|
*/
|
||||||
public record Style(String accent, String corners, Long bannerAppid, Long backgroundAppid, List<Tile> tiles) {}
|
public record Style(String accent, String corners, Long bannerAppid, Long backgroundAppid, List<Tile> tiles,
|
||||||
|
Map<String, String> statCharts) {}
|
||||||
|
|
||||||
public record Tile(String id, String size) {}
|
public record Tile(String id, String size) {}
|
||||||
}
|
}
|
||||||
@@ -4,10 +4,11 @@ import auth_service.shared.security.Role;
|
|||||||
|
|
||||||
import java.time.LocalDate;
|
import java.time.LocalDate;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
public record PublicProfile(long userId, Role role, String displayName, LocalDate memberSince, Style style, List<Tile> tiles) {
|
public record PublicProfile(long userId, Role role, String displayName, LocalDate memberSince, Style style, List<Tile> tiles) {
|
||||||
|
|
||||||
public record Style(String accent, String corners, Long bannerAppid, Long backgroundAppid) {}
|
public record Style(String accent, String corners, Long bannerAppid, Long backgroundAppid, Map<String, String> statCharts) {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param id a part of the profile, or STATS_ and a stat group of Core
|
* @param id a part of the profile, or STATS_ and a stat group of Core
|
||||||
|
|||||||
@@ -7,7 +7,9 @@ import org.springframework.jdbc.core.JdbcTemplate;
|
|||||||
import org.springframework.stereotype.Repository;
|
import org.springframework.stereotype.Repository;
|
||||||
|
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
|
||||||
@Repository
|
@Repository
|
||||||
@@ -26,13 +28,25 @@ public class PublicProfileRepository {
|
|||||||
: new PublicProfile.Tile(stored.substring(0, colon), stored.substring(colon + 1));
|
: new PublicProfile.Tile(stored.substring(0, colon), stored.substring(colon + 1));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Stored as "GROUP:CHART", like user.model.StatCharts.parse, which this module may not use.
|
||||||
|
private static Map<String, String> statCharts(String[] stored) {
|
||||||
|
Map<String, String> charts = new LinkedHashMap<>();
|
||||||
|
for (String entry : stored) {
|
||||||
|
int colon = entry.indexOf(':');
|
||||||
|
if (colon >= 0) {
|
||||||
|
charts.put(entry.substring(0, colon), entry.substring(colon + 1));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return charts;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Only enabled, not deleted users who switched the public profile on. The name ignores case, like its unique index.
|
* Only enabled, not deleted users who switched the public profile on. The name ignores case, like its unique index.
|
||||||
*/
|
*/
|
||||||
public Optional<PublicProfile> findPublicByDisplayName(String displayName) {
|
public Optional<PublicProfile> findPublicByDisplayName(String displayName) {
|
||||||
return jdbc.query("""
|
return jdbc.query("""
|
||||||
SELECT u.id, u.role, u.display_name, u.registration_date, s.profile_accent, s.profile_corners,
|
SELECT u.id, u.role, u.display_name, u.registration_date, s.profile_accent, s.profile_corners,
|
||||||
s.profile_banner_appid, s.profile_background_appid, s.profile_tiles
|
s.profile_banner_appid, s.profile_background_appid, s.profile_tiles, s.stat_charts
|
||||||
FROM users u JOIN user_settings s ON s.user_id = u.id
|
FROM users u JOIN user_settings s ON s.user_id = u.id
|
||||||
WHERE LOWER(u.display_name) = LOWER(?) AND %s AND s.public_profile
|
WHERE LOWER(u.display_name) = LOWER(?) AND %s AND s.public_profile
|
||||||
""".formatted(ActiveUser.of("u")),
|
""".formatted(ActiveUser.of("u")),
|
||||||
@@ -41,7 +55,8 @@ public class PublicProfileRepository {
|
|||||||
? rs.getTimestamp("registration_date").toLocalDateTime().toLocalDate() : null,
|
? rs.getTimestamp("registration_date").toLocalDateTime().toLocalDate() : null,
|
||||||
new PublicProfile.Style(rs.getString("profile_accent"), rs.getString("profile_corners"),
|
new PublicProfile.Style(rs.getString("profile_accent"), rs.getString("profile_corners"),
|
||||||
rs.getObject("profile_banner_appid", Long.class),
|
rs.getObject("profile_banner_appid", Long.class),
|
||||||
rs.getObject("profile_background_appid", Long.class)),
|
rs.getObject("profile_background_appid", Long.class),
|
||||||
|
statCharts((String[]) rs.getArray("stat_charts").getArray())),
|
||||||
Arrays.stream((String[]) rs.getArray("profile_tiles").getArray()).map(PublicProfileRepository::tile).toList()),
|
Arrays.stream((String[]) rs.getArray("profile_tiles").getArray()).map(PublicProfileRepository::tile).toList()),
|
||||||
displayName).stream().findFirst();
|
displayName).stream().findFirst();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ 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.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.StatChartRequest;
|
||||||
import auth_service.modules.user.api.dto.request.UserSettingsRequest;
|
import auth_service.modules.user.api.dto.request.UserSettingsRequest;
|
||||||
import auth_service.modules.user.api.dto.response.UserProfileResponse;
|
import auth_service.modules.user.api.dto.response.UserProfileResponse;
|
||||||
import auth_service.modules.user.api.mapper.ApiMapper;
|
import auth_service.modules.user.api.mapper.ApiMapper;
|
||||||
@@ -57,6 +58,13 @@ public class UserController {
|
|||||||
return ResponseEntity.noContent().build();
|
return ResponseEntity.noContent().build();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@PutMapping(value = "/settings/stat-chart")
|
||||||
|
public ResponseEntity<Void> updateStatChart(@AuthenticationPrincipal UserPrincipal principal,
|
||||||
|
@Valid @RequestBody StatChartRequest request) {
|
||||||
|
this.userService.updateStatChart(principal.getIdAsLong(), request.group(), request.chart());
|
||||||
|
return ResponseEntity.noContent().build();
|
||||||
|
}
|
||||||
|
|
||||||
@PutMapping(value = "/settings/favorite-platform")
|
@PutMapping(value = "/settings/favorite-platform")
|
||||||
public ResponseEntity<Void> updateFavoritePlatform(@AuthenticationPrincipal UserPrincipal principal,
|
public ResponseEntity<Void> updateFavoritePlatform(@AuthenticationPrincipal UserPrincipal principal,
|
||||||
@Valid @RequestBody FavoritePlatformRequest request) {
|
@Valid @RequestBody FavoritePlatformRequest request) {
|
||||||
|
|||||||
@@ -0,0 +1,21 @@
|
|||||||
|
package auth_service.modules.user.api.dto.request;
|
||||||
|
|
||||||
|
import auth_service.modules.user.model.StatCharts;
|
||||||
|
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||||
|
import jakarta.validation.constraints.AssertTrue;
|
||||||
|
import jakarta.validation.constraints.NotNull;
|
||||||
|
import jakarta.validation.constraints.Pattern;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param group a stat group of the Frontend
|
||||||
|
* @param chart the form for the group; null puts the group back to its default
|
||||||
|
*/
|
||||||
|
public record StatChartRequest(@NotNull @Pattern(regexp = "[A-Z_]{1,20}") String group,
|
||||||
|
@Pattern(regexp = "[A-Z]{1,10}") String chart) {
|
||||||
|
|
||||||
|
@AssertTrue(message = "Unknown stat group or chart")
|
||||||
|
@JsonIgnore
|
||||||
|
public boolean isKnownChoice() {
|
||||||
|
return group == null || (chart == null ? StatCharts.knows(group) : StatCharts.allows(group, chart));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,10 +4,11 @@ import lombok.Builder;
|
|||||||
|
|
||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
@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, Instant welcomeSeenAt) {
|
Map<String, String> statCharts, String favoritePlatform, Instant welcomeSeenAt) {
|
||||||
}
|
}
|
||||||
@@ -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()))
|
||||||
|
.statCharts(user.getSettings().getStatCharts())
|
||||||
.favoritePlatform(user.getSettings().getFavoritePlatform())
|
.favoritePlatform(user.getSettings().getFavoritePlatform())
|
||||||
.welcomeSeenAt(user.getSettings().getWelcomeSeenAt())
|
.welcomeSeenAt(user.getSettings().getWelcomeSeenAt())
|
||||||
.build())
|
.build())
|
||||||
|
|||||||
@@ -0,0 +1,51 @@
|
|||||||
|
package auth_service.modules.user.model;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The chart form a user picked per stat group. Keep the allowed forms in line with the Frontend
|
||||||
|
* {@code utils/statCharts.js}.
|
||||||
|
*/
|
||||||
|
public final class StatCharts {
|
||||||
|
|
||||||
|
private static final Map<String, Set<String>> ALLOWED = Map.of(
|
||||||
|
"TREND", Set.of("LINE", "BARS"),
|
||||||
|
"FINISHED", Set.of("BARS", "LINE", "LIST"),
|
||||||
|
"TASTE", Set.of("BARS", "PIE", "DONUT", "LIST"),
|
||||||
|
"PLATFORMS", Set.of("BARS", "PIE", "DONUT", "LIST"));
|
||||||
|
|
||||||
|
private StatCharts() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean knows(String group) {
|
||||||
|
return ALLOWED.containsKey(group);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean allows(String group, String chart) {
|
||||||
|
return ALLOWED.getOrDefault(group, Set.of()).contains(chart);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param stored "GROUP:CHART" entries, as the column holds them; an entry without ":" is skipped
|
||||||
|
*/
|
||||||
|
public static Map<String, String> parse(List<String> stored) {
|
||||||
|
Map<String, String> charts = new LinkedHashMap<>();
|
||||||
|
for (String entry : stored) {
|
||||||
|
int colon = entry.indexOf(':');
|
||||||
|
if (colon >= 0) {
|
||||||
|
charts.put(entry.substring(0, colon), entry.substring(colon + 1));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return charts;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ArrayList<String> stored(Map<String, String> charts) {
|
||||||
|
ArrayList<String> entries = new ArrayList<>();
|
||||||
|
charts.forEach((group, chart) -> entries.add(group + ":" + chart));
|
||||||
|
return entries;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -5,6 +5,7 @@ import lombok.Data;
|
|||||||
|
|
||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
@Data
|
@Data
|
||||||
@Builder
|
@Builder
|
||||||
@@ -20,6 +21,9 @@ 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;
|
||||||
|
/** Stat group to chart form; a group not in it shows its default. */
|
||||||
|
@Builder.Default
|
||||||
|
private Map<String, String> statCharts = Map.of();
|
||||||
private String favoritePlatform;
|
private String favoritePlatform;
|
||||||
/** Null: the welcome page was not seen yet. */
|
/** Null: the welcome page was not seen yet. */
|
||||||
private Instant welcomeSeenAt;
|
private Instant welcomeSeenAt;
|
||||||
|
|||||||
@@ -77,6 +77,11 @@ public class UserSettingsEntity {
|
|||||||
@Column(name = "profile_tiles", nullable = false)
|
@Column(name = "profile_tiles", nullable = false)
|
||||||
private List<String> profileTiles;
|
private List<String> profileTiles;
|
||||||
|
|
||||||
|
/** "GROUP:CHART" entries, see {@link auth_service.modules.user.model.StatCharts}. */
|
||||||
|
@JdbcTypeCode(SqlTypes.ARRAY)
|
||||||
|
@Column(name = "stat_charts", nullable = false)
|
||||||
|
private List<String> statCharts;
|
||||||
|
|
||||||
@Column(name = "welcome_seen_at")
|
@Column(name = "welcome_seen_at")
|
||||||
private Instant welcomeSeenAt;
|
private Instant welcomeSeenAt;
|
||||||
}
|
}
|
||||||
@@ -2,6 +2,7 @@ package auth_service.modules.user.persistence.mapper;
|
|||||||
|
|
||||||
import auth_service.modules.user.model.Appearance;
|
import auth_service.modules.user.model.Appearance;
|
||||||
import auth_service.modules.user.model.ProfileStyle;
|
import auth_service.modules.user.model.ProfileStyle;
|
||||||
|
import auth_service.modules.user.model.StatCharts;
|
||||||
import auth_service.modules.user.model.Tile;
|
import auth_service.modules.user.model.Tile;
|
||||||
import auth_service.modules.user.model.User;
|
import auth_service.modules.user.model.User;
|
||||||
import auth_service.modules.user.model.UserSettings;
|
import auth_service.modules.user.model.UserSettings;
|
||||||
@@ -35,6 +36,7 @@ public abstract class EntityMapper {
|
|||||||
.dashboardTiles(settings.getDashboardTiles() == null ? null
|
.dashboardTiles(settings.getDashboardTiles() == null ? null
|
||||||
: settings.getDashboardTiles().stream().map(Tile::parse).toList())
|
: settings.getDashboardTiles().stream().map(Tile::parse).toList())
|
||||||
.profileTiles(settings.getProfileTiles().stream().map(Tile::parse).toList())
|
.profileTiles(settings.getProfileTiles().stream().map(Tile::parse).toList())
|
||||||
|
.statCharts(StatCharts.parse(settings.getStatCharts()))
|
||||||
.favoritePlatform(settings.getFavoritePlatform())
|
.favoritePlatform(settings.getFavoritePlatform())
|
||||||
.welcomeSeenAt(settings.getWelcomeSeenAt())
|
.welcomeSeenAt(settings.getWelcomeSeenAt())
|
||||||
.build();
|
.build();
|
||||||
@@ -75,6 +77,7 @@ public abstract class EntityMapper {
|
|||||||
target.setDashboardTiles(settings.getDashboardTiles() == null ? null
|
target.setDashboardTiles(settings.getDashboardTiles() == null ? null
|
||||||
: Tile.storedAll(settings.getDashboardTiles()));
|
: Tile.storedAll(settings.getDashboardTiles()));
|
||||||
target.setProfileTiles(Tile.storedAll(settings.getProfileTiles()));
|
target.setProfileTiles(Tile.storedAll(settings.getProfileTiles()));
|
||||||
|
target.setStatCharts(StatCharts.stored(settings.getStatCharts()));
|
||||||
target.setFavoritePlatform(settings.getFavoritePlatform());
|
target.setFavoritePlatform(settings.getFavoritePlatform());
|
||||||
target.setWelcomeSeenAt(settings.getWelcomeSeenAt());
|
target.setWelcomeSeenAt(settings.getWelcomeSeenAt());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,7 +23,9 @@ import org.springframework.stereotype.Service;
|
|||||||
import org.springframework.transaction.annotation.Transactional;
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
|
||||||
@Slf4j
|
@Slf4j
|
||||||
@@ -126,6 +128,24 @@ public class UserService {
|
|||||||
userRepository.update(userId, user -> user.getSettings().setProfileTiles(Tile.distinct(tiles)));
|
userRepository.update(userId, user -> user.getSettings().setProfileTiles(Tile.distinct(tiles)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param chart null puts the group back to its default form
|
||||||
|
*/
|
||||||
|
@RequiresRole(Role.USER)
|
||||||
|
@Transactional
|
||||||
|
@CacheEvict(value = "users", key = "#userId")
|
||||||
|
public void updateStatChart(long userId, String group, String chart) {
|
||||||
|
userRepository.update(userId, user -> {
|
||||||
|
Map<String, String> charts = new LinkedHashMap<>(user.getSettings().getStatCharts());
|
||||||
|
if (chart == null) {
|
||||||
|
charts.remove(group);
|
||||||
|
} else {
|
||||||
|
charts.put(group, chart);
|
||||||
|
}
|
||||||
|
user.getSettings().setStatCharts(charts);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
@RequiresRole(Role.USER)
|
@RequiresRole(Role.USER)
|
||||||
@Transactional
|
@Transactional
|
||||||
@CacheEvict(value = "users", key = "#userId")
|
@CacheEvict(value = "users", key = "#userId")
|
||||||
|
|||||||
@@ -0,0 +1,2 @@
|
|||||||
|
-- GROUP:CHART per changed stat group; a group not listed shows its default form
|
||||||
|
ALTER TABLE user_settings ADD COLUMN stat_charts VARCHAR(32)[] NOT NULL DEFAULT '{}';
|
||||||
@@ -102,6 +102,23 @@ class PublicProfileControllerTest extends AbstractIntegrationTest {
|
|||||||
.andExpect(jsonPath("$.style.tiles[1].size").value("L"));
|
.andExpect(jsonPath("$.style.tiles[1].size").value("L"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void publicProfile_AnswersWithTheOwnersStatCharts() throws Exception {
|
||||||
|
jdbc.update("update user_settings set stat_charts = '{PLATFORMS:DONUT,TREND:BARS}' where user_id = ?",
|
||||||
|
publicUserId);
|
||||||
|
|
||||||
|
mockMvc.perform(get("/api/public/profiles/Lobo"))
|
||||||
|
.andExpect(status().isOk())
|
||||||
|
.andExpect(jsonPath("$.style.statCharts.PLATFORMS").value("DONUT"))
|
||||||
|
.andExpect(jsonPath("$.style.statCharts.TREND").value("BARS"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void publicProfile_NewRowHasNoStatCharts() throws Exception {
|
||||||
|
mockMvc.perform(get("/api/public/profiles/Lobo"))
|
||||||
|
.andExpect(jsonPath("$.style.statCharts").isEmpty());
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void publicProfile_NewRowShowsTheDefaultTiles() throws Exception {
|
void publicProfile_NewRowShowsTheDefaultTiles() throws Exception {
|
||||||
mockMvc.perform(get("/api/public/profiles/Lobo"))
|
mockMvc.perform(get("/api/public/profiles/Lobo"))
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ import org.springframework.test.web.servlet.MockMvc;
|
|||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
import static org.hamcrest.Matchers.nullValue;
|
import static org.hamcrest.Matchers.nullValue;
|
||||||
import static org.mockito.ArgumentMatchers.*;
|
import static org.mockito.ArgumentMatchers.*;
|
||||||
@@ -369,7 +370,8 @@ 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"))).favoritePlatform("PS5").build();
|
.profileTiles(List.of(new Tile("PLAYING", "S"))).favoritePlatform("PS5")
|
||||||
|
.statCharts(Map.of("PLATFORMS", "PIE")).build();
|
||||||
SecurityContextHolder.getContext().setAuthentication(
|
SecurityContextHolder.getContext().setAuthentication(
|
||||||
new UsernamePasswordAuthenticationToken(new UserPrincipal("1", null), null, Collections.emptyList())
|
new UsernamePasswordAuthenticationToken(new UserPrincipal("1", null), null, Collections.emptyList())
|
||||||
);
|
);
|
||||||
@@ -381,7 +383,8 @@ class UserControllerTest extends AbstractWebMvcTest {
|
|||||||
.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"));
|
.andExpect(jsonPath("$.settings.favoritePlatform").value("PS5"))
|
||||||
|
.andExpect(jsonPath("$.settings.statCharts.PLATFORMS").value("PIE"));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -459,6 +462,47 @@ class UserControllerTest extends AbstractWebMvcTest {
|
|||||||
verify(userService).updateDashboardTiles(1L, null);
|
verify(userService).updateDashboardTiles(1L, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void updateStatChart_StoresOrClearsTheChoice() throws Exception {
|
||||||
|
SecurityContextHolder.getContext().setAuthentication(new UsernamePasswordAuthenticationToken(
|
||||||
|
new UserPrincipal("1", null), null, Collections.emptyList()));
|
||||||
|
|
||||||
|
mockMvc.perform(put("/api/users/settings/stat-chart")
|
||||||
|
.contentType(MediaType.APPLICATION_JSON)
|
||||||
|
.content("{\"group\":\"PLATFORMS\",\"chart\":\"PIE\"}"))
|
||||||
|
.andExpect(status().isNoContent());
|
||||||
|
mockMvc.perform(put("/api/users/settings/stat-chart")
|
||||||
|
.contentType(MediaType.APPLICATION_JSON)
|
||||||
|
.content("{\"group\":\"FINISHED\",\"chart\":null}"))
|
||||||
|
.andExpect(status().isNoContent());
|
||||||
|
|
||||||
|
verify(userService).updateStatChart(1L, "PLATFORMS", "PIE");
|
||||||
|
verify(userService).updateStatChart(1L, "FINISHED", null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@ParameterizedTest
|
||||||
|
@ValueSource(strings = {
|
||||||
|
"{\"group\":\"TASTE\",\"chart\":\"LINE\"}",
|
||||||
|
"{\"group\":\"TOP_GAMES\",\"chart\":\"LIST\"}",
|
||||||
|
"{\"group\":\"TOP_GAMES\",\"chart\":null}",
|
||||||
|
"{\"group\":\"UNKNOWN\",\"chart\":\"BARS\"}",
|
||||||
|
"{\"group\":\"platforms\",\"chart\":\"PIE\"}",
|
||||||
|
"{\"group\":\"PLATFORMS\",\"chart\":\"RADAR\"}",
|
||||||
|
"{\"chart\":\"PIE\"}",
|
||||||
|
"{}"
|
||||||
|
})
|
||||||
|
void updateStatChart_UnknownGroupOrChart_Is400(String body) throws Exception {
|
||||||
|
SecurityContextHolder.getContext().setAuthentication(new UsernamePasswordAuthenticationToken(
|
||||||
|
new UserPrincipal("1", null), null, Collections.emptyList()));
|
||||||
|
|
||||||
|
mockMvc.perform(put("/api/users/settings/stat-chart")
|
||||||
|
.contentType(MediaType.APPLICATION_JSON)
|
||||||
|
.content(body))
|
||||||
|
.andExpect(status().isBadRequest());
|
||||||
|
|
||||||
|
verifyNoInteractions(userService);
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void updateFavoritePlatform_StoresOrClearsIt() throws Exception {
|
void updateFavoritePlatform_StoresOrClearsIt() throws Exception {
|
||||||
SecurityContextHolder.getContext().setAuthentication(new UsernamePasswordAuthenticationToken(
|
SecurityContextHolder.getContext().setAuthentication(new UsernamePasswordAuthenticationToken(
|
||||||
|
|||||||
@@ -0,0 +1,54 @@
|
|||||||
|
package auth_service.modules.user.model;
|
||||||
|
|
||||||
|
import auth_service.shared.test.AbstractUnitTest;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.junit.jupiter.params.ParameterizedTest;
|
||||||
|
import org.junit.jupiter.params.provider.CsvSource;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
|
|
||||||
|
class StatChartsTest extends AbstractUnitTest {
|
||||||
|
|
||||||
|
@ParameterizedTest
|
||||||
|
@CsvSource({"TREND,LINE", "TREND,BARS", "FINISHED,BARS", "FINISHED,LINE", "FINISHED,LIST", "TASTE,BARS",
|
||||||
|
"TASTE,PIE", "TASTE,DONUT", "TASTE,LIST", "PLATFORMS,BARS", "PLATFORMS,PIE", "PLATFORMS,DONUT",
|
||||||
|
"PLATFORMS,LIST"})
|
||||||
|
void allows_EachAllowedPair(String group, String chart) {
|
||||||
|
assertTrue(StatCharts.allows(group, chart));
|
||||||
|
}
|
||||||
|
|
||||||
|
@ParameterizedTest
|
||||||
|
@CsvSource({"TASTE,LINE", "TOP_GAMES,LIST", "UNKNOWN,BARS", "platforms,PIE", "PLATFORMS,pie", "PLATFORMS,RADAR",
|
||||||
|
"TREND,PIE"})
|
||||||
|
void allows_OtherPairs_AreRefused(String group, String chart) {
|
||||||
|
assertFalse(StatCharts.allows(group, chart));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void knows_OnlyTheChartableGroups() {
|
||||||
|
assertTrue(StatCharts.knows("PLATFORMS"));
|
||||||
|
assertFalse(StatCharts.knows("TOP_GAMES"));
|
||||||
|
assertFalse(StatCharts.knows("platforms"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void parseAndStored_RoundTripKeepsTheOrder() {
|
||||||
|
List<String> stored = List.of("TREND:BARS", "PLATFORMS:PIE", "FINISHED:LINE");
|
||||||
|
|
||||||
|
Map<String, String> parsed = StatCharts.parse(stored);
|
||||||
|
|
||||||
|
assertEquals(stored, List.copyOf(StatCharts.stored(parsed)));
|
||||||
|
assertEquals("PIE", parsed.get("PLATFORMS"));
|
||||||
|
assertEquals(List.of("TREND", "PLATFORMS", "FINISHED"), List.copyOf(parsed.keySet()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void parse_SkipsAnEntryWithoutColon() {
|
||||||
|
assertEquals(Map.of("TREND", "BARS"), StatCharts.parse(List.of("BROKEN", "TREND:BARS")));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -17,7 +17,9 @@ import org.springframework.jdbc.core.JdbcTemplate;
|
|||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
import java.time.temporal.ChronoUnit;
|
import java.time.temporal.ChronoUnit;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.*;
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
@@ -318,6 +320,29 @@ class UserRepositoryTest extends AbstractPersistenceTest {
|
|||||||
assertEquals(profile, saved.getProfileTiles());
|
assertEquals(profile, saved.getProfileTiles());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void updateStatCharts_StartEmptyAreReadBackAndKeptByUpdateSettings() {
|
||||||
|
UserEntity userEntity = persisted(UserEntity.builder()
|
||||||
|
.email("charts@example.com")
|
||||||
|
.password("password")
|
||||||
|
.enabled(true)
|
||||||
|
.registrationDate(LocalDateTime.now())
|
||||||
|
.build());
|
||||||
|
assertEquals(Map.of(), findByEmail("charts@example.com").orElseThrow().getSettings().getStatCharts());
|
||||||
|
|
||||||
|
Map<String, String> charts = new LinkedHashMap<>();
|
||||||
|
charts.put("TREND", "BARS");
|
||||||
|
charts.put("PLATFORMS", "PIE");
|
||||||
|
userRepository.update(userEntity.getId(), user -> user.getSettings().setStatCharts(charts));
|
||||||
|
entityManager.flush();
|
||||||
|
updateSettings(userEntity.getId(), null,
|
||||||
|
UserSettings.builder().language("de").theme("dark").build());
|
||||||
|
entityManager.flush();
|
||||||
|
entityManager.clear();
|
||||||
|
|
||||||
|
assertEquals(charts, findByEmail("charts@example.com").orElseThrow().getSettings().getStatCharts());
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void updateFavoritePlatform_StoresAndClears() {
|
void updateFavoritePlatform_StoresAndClears() {
|
||||||
UserEntity userEntity = persisted(UserEntity.builder()
|
UserEntity userEntity = persisted(UserEntity.builder()
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ import org.mockito.Mock;
|
|||||||
import java.time.Duration;
|
import java.time.Duration;
|
||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
@@ -76,6 +77,31 @@ class UserServiceTest extends AbstractUnitTest {
|
|||||||
assertEquals(List.of(new Tile("SUMMARY", "M")), changed.get(1).getSettings().getProfileTiles());
|
assertEquals(List.of(new Tile("SUMMARY", "M")), changed.get(1).getSettings().getProfileTiles());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void updateStatChart_PutsTheFormOfTheGroup() {
|
||||||
|
userService.updateStatChart(1L, "PLATFORMS", "PIE");
|
||||||
|
userService.updateStatChart(1L, "TREND", "BARS");
|
||||||
|
|
||||||
|
List<User> changed = appliedChanges(2);
|
||||||
|
assertEquals(Map.of("PLATFORMS", "PIE"), changed.get(0).getSettings().getStatCharts());
|
||||||
|
assertEquals(Map.of("TREND", "BARS"), changed.get(1).getSettings().getStatCharts());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void updateStatChart_KeepsOtherGroupsAndNullRemovesTheEntry() {
|
||||||
|
UserSettings settings = UserSettings.builder().statCharts(Map.of("PLATFORMS", "PIE", "TREND", "BARS")).build();
|
||||||
|
User user = User.builder().id(1L).settings(settings).build();
|
||||||
|
userService.updateStatChart(1L, "PLATFORMS", null);
|
||||||
|
userService.updateStatChart(1L, "FINISHED", "LINE");
|
||||||
|
|
||||||
|
ArgumentCaptor<Consumer<User>> change = ArgumentCaptor.forClass(Consumer.class);
|
||||||
|
verify(userRepository, times(2)).update(eq(1L), change.capture());
|
||||||
|
change.getAllValues().get(0).accept(user);
|
||||||
|
assertEquals(Map.of("TREND", "BARS"), user.getSettings().getStatCharts());
|
||||||
|
change.getAllValues().get(1).accept(user);
|
||||||
|
assertEquals(Map.of("TREND", "BARS", "FINISHED", "LINE"), user.getSettings().getStatCharts());
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void updateDashboardTiles_NullStaysNull() {
|
void updateDashboardTiles_NullStaysNull() {
|
||||||
userService.updateDashboardTiles(1L, null);
|
userService.updateDashboardTiles(1L, null);
|
||||||
|
|||||||
Reference in new issue
Block a user