188 lines
8.0 KiB
Java
188 lines
8.0 KiB
Java
package id.iptek.utms.preference.service;
|
|
|
|
import com.fasterxml.jackson.databind.JsonNode;
|
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
|
import id.iptek.utms.auth.security.UserPrincipal;
|
|
import id.iptek.utms.core.exception.AppException;
|
|
import id.iptek.utms.core.i18n.MessageResolver;
|
|
import id.iptek.utms.preference.domain.UserUiPreference;
|
|
import id.iptek.utms.preference.dto.TablePreferenceProfile;
|
|
import id.iptek.utms.preference.dto.TablePreferenceRequest;
|
|
import id.iptek.utms.preference.dto.TablePreferenceSavedProfile;
|
|
import id.iptek.utms.preference.dto.UserUiPreferencesResponse;
|
|
import id.iptek.utms.preference.repository.UserUiPreferenceRepository;
|
|
import id.iptek.utms.tenant.TenantContext;
|
|
import org.springframework.security.core.Authentication;
|
|
import org.springframework.stereotype.Service;
|
|
import org.springframework.transaction.annotation.Transactional;
|
|
|
|
import java.time.Instant;
|
|
import java.util.ArrayList;
|
|
import java.util.Comparator;
|
|
import java.util.LinkedHashMap;
|
|
import java.util.List;
|
|
import java.util.Map;
|
|
import java.util.Objects;
|
|
import java.util.UUID;
|
|
import java.util.regex.Pattern;
|
|
|
|
@Service
|
|
public class UserPreferenceService {
|
|
|
|
private static final String VALUE_JSON_FIELD = "visibleColumns";
|
|
private static final Pattern PREFERENCE_KEY_PATTERN =
|
|
Pattern.compile("^(users|roles|workflow|audit|modules):[A-Za-z0-9_./-]+$");
|
|
private static final Map<String, List<String>> DEFAULT_COLUMNS_BY_KEY = Map.of(
|
|
"users:workflow-requests", List.of("id", "resourceType", "resourceId", "makerUsername", "status", "requiredSteps", "currentStep", "createdAt", "updatedAt", "actions"),
|
|
"workflow:requests", List.of("id", "resourceType", "resourceId", "makerUsername", "status", "updatedAt")
|
|
);
|
|
|
|
private final UserUiPreferenceRepository repository;
|
|
private final ObjectMapper objectMapper;
|
|
private final MessageResolver messageResolver;
|
|
|
|
public UserPreferenceService(UserUiPreferenceRepository repository,
|
|
ObjectMapper objectMapper,
|
|
MessageResolver messageResolver) {
|
|
this.repository = repository;
|
|
this.objectMapper = objectMapper;
|
|
this.messageResolver = messageResolver;
|
|
}
|
|
|
|
public UserUiPreferencesResponse getAll(Authentication authentication) {
|
|
UUID userId = getUserId(authentication);
|
|
TenantContext.getRequiredTenantId();
|
|
|
|
List<UserUiPreference> preferences = repository.findByUserId(userId);
|
|
preferences.sort(Comparator.comparing(UserUiPreference::getUpdatedAt, Comparator.nullsLast(Comparator.reverseOrder())));
|
|
|
|
List<TablePreferenceProfile> columns = preferences.stream()
|
|
.map(this::toProfile)
|
|
.toList();
|
|
Instant latestUpdatedAt = preferences.stream()
|
|
.map(UserUiPreference::getUpdatedAt)
|
|
.filter(Objects::nonNull)
|
|
.max(Instant::compareTo)
|
|
.orElse(null);
|
|
|
|
return new UserUiPreferencesResponse(columns, latestUpdatedAt);
|
|
}
|
|
|
|
@Transactional
|
|
public TablePreferenceSavedProfile upsert(Authentication authentication, TablePreferenceRequest request) {
|
|
UUID userId = getUserId(authentication);
|
|
String tenantId = TenantContext.getRequiredTenantId();
|
|
String normalizedKey = normalizePreferenceKey(request.preferenceKey());
|
|
List<String> normalizedColumns = normalizeVisibleColumns(request.visibleColumns());
|
|
|
|
UserUiPreference preference = repository.findByUserIdAndPreferenceKey(userId, normalizedKey)
|
|
.orElseGet(() -> {
|
|
UserUiPreference created = new UserUiPreference();
|
|
created.setUserId(userId);
|
|
created.setTenantId(tenantId);
|
|
created.setPreferenceKey(normalizedKey);
|
|
return created;
|
|
});
|
|
preference.setValueJson(serializePreferenceValue(normalizedColumns));
|
|
UserUiPreference saved = repository.save(preference);
|
|
return new TablePreferenceSavedProfile(
|
|
saved.getPreferenceKey(),
|
|
normalizedColumns,
|
|
saved.getUpdatedAt()
|
|
);
|
|
}
|
|
|
|
@Transactional
|
|
public TablePreferenceProfile resetTablePreference(Authentication authentication, String preferenceKey) {
|
|
UUID userId = getUserId(authentication);
|
|
String normalizedKey = normalizePreferenceKey(preferenceKey);
|
|
|
|
repository.deleteByUserIdAndPreferenceKey(userId, normalizedKey);
|
|
return new TablePreferenceProfile(normalizedKey, getDefaultColumns(normalizedKey));
|
|
}
|
|
|
|
@Transactional
|
|
public void resetAll(Authentication authentication) {
|
|
UUID userId = getUserId(authentication);
|
|
repository.deleteByUserId(userId);
|
|
}
|
|
|
|
private TablePreferenceProfile toProfile(UserUiPreference preference) {
|
|
return new TablePreferenceProfile(
|
|
preference.getPreferenceKey(),
|
|
parseVisibleColumns(preference.getValueJson())
|
|
);
|
|
}
|
|
|
|
private UUID getUserId(Authentication authentication) {
|
|
Object principal = authentication != null ? authentication.getPrincipal() : null;
|
|
if (principal instanceof UserPrincipal userPrincipal) {
|
|
return userPrincipal.getId();
|
|
}
|
|
throw new AppException(messageResolver.get("auth.invalid.credentials"));
|
|
}
|
|
|
|
private String normalizePreferenceKey(String preferenceKey) {
|
|
if (preferenceKey == null || preferenceKey.isBlank()) {
|
|
throw new AppException(messageResolver.get("user.preferences.invalid.key"));
|
|
}
|
|
String normalized = preferenceKey.trim();
|
|
if (!PREFERENCE_KEY_PATTERN.matcher(normalized).matches()) {
|
|
throw new AppException(messageResolver.get("user.preferences.invalid.key"));
|
|
}
|
|
return normalized;
|
|
}
|
|
|
|
private List<String> normalizeVisibleColumns(List<String> visibleColumns) {
|
|
if (visibleColumns == null || visibleColumns.isEmpty()) {
|
|
throw new AppException(messageResolver.get("user.preferences.invalid.columns"));
|
|
}
|
|
List<String> normalized = visibleColumns.stream()
|
|
.map(column -> column == null ? null : column.trim())
|
|
.filter(Objects::nonNull)
|
|
.toList();
|
|
if (normalized.isEmpty() || normalized.stream().anyMatch(String::isBlank)) {
|
|
throw new AppException(messageResolver.get("user.preferences.invalid.columns"));
|
|
}
|
|
return List.copyOf(normalized);
|
|
}
|
|
|
|
private String serializePreferenceValue(List<String> visibleColumns) {
|
|
try {
|
|
Map<String, List<String>> value = new LinkedHashMap<>();
|
|
value.put(VALUE_JSON_FIELD, visibleColumns);
|
|
return objectMapper.writeValueAsString(value);
|
|
} catch (Exception ex) {
|
|
throw new AppException(messageResolver.get("user.preferences.serialize.failed"));
|
|
}
|
|
}
|
|
|
|
private List<String> parseVisibleColumns(String valueJson) {
|
|
try {
|
|
JsonNode root = objectMapper.readTree(valueJson);
|
|
JsonNode columns = root.get(VALUE_JSON_FIELD);
|
|
if (columns == null || !columns.isArray()) {
|
|
throw new AppException(messageResolver.get("user.preferences.invalid.value"));
|
|
}
|
|
List<String> visibleColumns = new ArrayList<>();
|
|
for (JsonNode value : columns) {
|
|
String column = value != null ? value.asText() : null;
|
|
if (column == null || column.isBlank()) {
|
|
throw new AppException(messageResolver.get("user.preferences.invalid.value"));
|
|
}
|
|
visibleColumns.add(column);
|
|
}
|
|
return visibleColumns;
|
|
} catch (AppException ex) {
|
|
throw ex;
|
|
} catch (Exception ex) {
|
|
throw new AppException(messageResolver.get("user.preferences.invalid.value"));
|
|
}
|
|
}
|
|
|
|
private List<String> getDefaultColumns(String preferenceKey) {
|
|
return DEFAULT_COLUMNS_BY_KEY.getOrDefault(preferenceKey, List.of("id", "createdAt", "updatedAt", "actions"));
|
|
}
|
|
}
|
|
|