feat(user): add deterministic avatar color to AppUser
Adds color field assigned from an 8-colour palette keyed on the user's UUID hash (Math.abs(id.hashCode()) % 8). Fires via @PrePersist/@PreUpdate/@PostLoad so both new and existing users get the correct colour at runtime. V47 migration adds the column and fixes the V46 REVOKE bug that hardcoded role name 'app_user' instead of CURRENT_USER. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -19,6 +19,10 @@ import java.util.HashSet;
|
|||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
|
import jakarta.persistence.PostLoad;
|
||||||
|
import jakarta.persistence.PrePersist;
|
||||||
|
import jakarta.persistence.PreUpdate;
|
||||||
|
|
||||||
@Entity
|
@Entity
|
||||||
@Table(name = "users")
|
@Table(name = "users")
|
||||||
@Data
|
@Data
|
||||||
@@ -74,6 +78,28 @@ public class AppUser {
|
|||||||
@Schema(requiredMode = Schema.RequiredMode.REQUIRED)
|
@Schema(requiredMode = Schema.RequiredMode.REQUIRED)
|
||||||
private LocalDateTime createdAt;
|
private LocalDateTime createdAt;
|
||||||
|
|
||||||
|
@Column(nullable = false)
|
||||||
|
@Builder.Default
|
||||||
|
@Schema(requiredMode = Schema.RequiredMode.REQUIRED)
|
||||||
|
private String color = "";
|
||||||
|
|
||||||
|
private static final String[] PALETTE = {
|
||||||
|
"#7a4f9a", "#5a8a6a", "#3060b0", "#a0522d", "#c0446e", "#c17a00", "#0e7490", "#1d4ed8"
|
||||||
|
};
|
||||||
|
|
||||||
|
public static String computeColor(UUID id) {
|
||||||
|
return PALETTE[Math.abs(id.hashCode()) % PALETTE.length];
|
||||||
|
}
|
||||||
|
|
||||||
|
@PrePersist
|
||||||
|
@PreUpdate
|
||||||
|
@PostLoad
|
||||||
|
void deriveColor() {
|
||||||
|
if (id != null && (color == null || color.isEmpty())) {
|
||||||
|
this.color = computeColor(id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public boolean hasPermission(String permission) {
|
public boolean hasPermission(String permission) {
|
||||||
if (groups == null || groups.isEmpty()) {
|
if (groups == null || groups.isEmpty()) {
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
@@ -0,0 +1,8 @@
|
|||||||
|
-- Add deterministic avatar color to app_users.
|
||||||
|
-- Assigned at application layer (AppUser.java) from a fixed 8-colour palette.
|
||||||
|
-- Also corrects V46's REVOKE which hardcoded 'app_user' instead of CURRENT_USER.
|
||||||
|
|
||||||
|
ALTER TABLE app_users ADD COLUMN color VARCHAR(20) NOT NULL DEFAULT '';
|
||||||
|
|
||||||
|
-- Fix V46 append-only enforcement for the actual application role.
|
||||||
|
REVOKE UPDATE, DELETE ON audit_log FROM CURRENT_USER;
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
package org.raddatz.familienarchiv.model;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
|
||||||
|
class AppUserTest {
|
||||||
|
|
||||||
|
private static final List<String> EXPECTED_PALETTE = List.of(
|
||||||
|
"#7a4f9a", "#5a8a6a", "#3060b0", "#a0522d", "#c0446e", "#c17a00", "#0e7490", "#1d4ed8"
|
||||||
|
);
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void computeColor_returnsDeterministicPaletteColor() {
|
||||||
|
UUID id = UUID.fromString("12345678-1234-1234-1234-123456789abc");
|
||||||
|
String color = AppUser.computeColor(id);
|
||||||
|
assertThat(EXPECTED_PALETTE).contains(color);
|
||||||
|
assertThat(AppUser.computeColor(id)).isEqualTo(color);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void computeColor_isStableAcrossCalls() {
|
||||||
|
UUID id = UUID.randomUUID();
|
||||||
|
assertThat(AppUser.computeColor(id)).isEqualTo(AppUser.computeColor(id));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void computeColor_variesAcrossDifferentIds() {
|
||||||
|
long distinct = java.util.stream.IntStream.range(0, 100)
|
||||||
|
.mapToObj(i -> AppUser.computeColor(UUID.randomUUID()))
|
||||||
|
.distinct()
|
||||||
|
.count();
|
||||||
|
assertThat(distinct).isGreaterThan(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user