From b40134e6a7eea6ae162afc206287f138c9c0b731 Mon Sep 17 00:00:00 2001 From: sgiannopoulos Date: Wed, 29 Nov 2023 17:41:15 +0200 Subject: [PATCH] user info changes --- .../java/eu/eudat/audit/AuditableAction.java | 5 +- .../eu/eudat/service/user/UserService.java | 3 + .../eudat/service/user/UserServiceImpl.java | 88 ++++++++++++++++++- .../controllers/v2/PrincipalController.java | 4 + .../eudat/controllers/v2/UserController.java | 33 +++++++ .../main/java/eu/eudat/models/v2/Account.java | 53 +++++++++++ .../eu/eudat/models/v2/AccountBuilder.java | 27 +++++- 7 files changed, 207 insertions(+), 6 deletions(-) diff --git a/dmp-backend/core/src/main/java/eu/eudat/audit/AuditableAction.java b/dmp-backend/core/src/main/java/eu/eudat/audit/AuditableAction.java index 7865bbf97..2d09a243d 100644 --- a/dmp-backend/core/src/main/java/eu/eudat/audit/AuditableAction.java +++ b/dmp-backend/core/src/main/java/eu/eudat/audit/AuditableAction.java @@ -73,7 +73,10 @@ public class AuditableAction { public static final EventId User_Delete = new EventId(11003, "User_Delete"); public static final EventId User_LookupByEmail = new EventId(11004, "User_LookupByEmail"); public static final EventId User_ExportCsv = new EventId(11005, "User_ExportCsv"); - public static final EventId User_PersistRoles = new EventId(11004, "User_PersistRoles"); + public static final EventId User_PersistRoles = new EventId(11006, "User_PersistRoles"); + public static final EventId User_LanguageMine = new EventId(11007, "User_LanguageMine"); + public static final EventId User_TimezoneMine = new EventId(11008, "User_TimezoneMine"); + public static final EventId User_CultureMine = new EventId(11009, "User_CultureMine"); public static final EventId Tenant_Query = new EventId(12000, "Tenant_Query"); public static final EventId Tenant_Lookup = new EventId(12001, "Tenant_Lookup"); diff --git a/dmp-backend/core/src/main/java/eu/eudat/service/user/UserService.java b/dmp-backend/core/src/main/java/eu/eudat/service/user/UserService.java index 06d90ab78..d18df22c4 100644 --- a/dmp-backend/core/src/main/java/eu/eudat/service/user/UserService.java +++ b/dmp-backend/core/src/main/java/eu/eudat/service/user/UserService.java @@ -19,6 +19,9 @@ import java.util.UUID; public interface UserService { User persist(UserPersist model, FieldSet fields) throws MyForbiddenException, MyValidationException, MyApplicationException, MyNotFoundException, InvalidApplicationException, JsonProcessingException; + void updateLanguageMine(String language) throws InvalidApplicationException, JsonProcessingException; + void updateTimezoneMine(String timezone) throws JsonProcessingException; + void updateCultureMine(String culture) throws JsonProcessingException; void deleteAndSave(UUID id) throws MyForbiddenException, InvalidApplicationException; diff --git a/dmp-backend/core/src/main/java/eu/eudat/service/user/UserServiceImpl.java b/dmp-backend/core/src/main/java/eu/eudat/service/user/UserServiceImpl.java index 4b74540c9..7d2456556 100644 --- a/dmp-backend/core/src/main/java/eu/eudat/service/user/UserServiceImpl.java +++ b/dmp-backend/core/src/main/java/eu/eudat/service/user/UserServiceImpl.java @@ -7,6 +7,7 @@ import eu.eudat.authorization.Permission; import eu.eudat.commons.JsonHandlingService; import eu.eudat.commons.enums.ContactInfoType; import eu.eudat.commons.enums.IsActive; +import eu.eudat.commons.scope.user.UserScope; import eu.eudat.commons.types.user.AdditionalInfoEntity; import eu.eudat.convention.ConventionService; import eu.eudat.data.UserEntity; @@ -78,6 +79,7 @@ public class UserServiceImpl implements UserService { private final EventBroker eventBroker; private final JsonHandlingService jsonHandlingService; private final QueryFactory queryFactory; + private final UserScope userScope; @Autowired public UserServiceImpl( EntityManager entityManager, @@ -89,7 +91,8 @@ public class UserServiceImpl implements UserService { MessageSource messageSource, EventBroker eventBroker, JsonHandlingService jsonHandlingService, - QueryFactory queryFactory) { + QueryFactory queryFactory, + UserScope userScope) { this.entityManager = entityManager; this.authorizationService = authorizationService; this.deleterFactory = deleterFactory; @@ -100,6 +103,7 @@ public class UserServiceImpl implements UserService { this.eventBroker = eventBroker; this.jsonHandlingService = jsonHandlingService; this.queryFactory = queryFactory; + this.userScope = userScope; } //region persist @@ -221,4 +225,86 @@ public class UserServiceImpl implements UserService { this.eventBroker.emit(new UserTouchedEvent(data.getId())); return this.builderFactory.builder(UserBuilder.class).authorize(AuthorizationFlags.OwnerOrDmpAssociatedOrPermissionOrPublic).build(BaseFieldSet.build(fields, User._id), data); } + + //region mine + + @Override + public void updateLanguageMine(String language) throws JsonProcessingException { + logger.debug(new MapLogEntry("persisting User language").And("language", language)); + + UUID userId = this.userScope.getUserIdSafe(); + + if (userId == null) throw new MyForbiddenException(this.errors.getForbidden().getCode(), this.errors.getForbidden().getMessage()); + + + UserEntity data = this.entityManager.find(UserEntity.class, userId); + if (data == null) throw new MyNotFoundException(messageSource.getMessage("General_ItemNotFound", new Object[]{model.getId(), User.class.getSimpleName()}, LocaleContextHolder.getLocale())); + + AdditionalInfoEntity additionalInfoEntity = this.jsonHandlingService.fromJsonSafe(AdditionalInfoEntity.class, data.getAdditionalInfo()); + if (additionalInfoEntity == null) additionalInfoEntity = new AdditionalInfoEntity(); + additionalInfoEntity.setLanguage(language); + + data.setAdditionalInfo(this.jsonHandlingService.toJson(additionalInfoEntity)); + + data.setUpdatedAt(Instant.now()); + this.entityManager.merge(data); + + this.entityManager.flush(); + + this.eventBroker.emit(new UserTouchedEvent(data.getId())); + } + + @Override + public void updateTimezoneMine(String timezone) throws JsonProcessingException { + logger.debug(new MapLogEntry("persisting User timezone").And("timezone", timezone)); + + UUID userId = this.userScope.getUserIdSafe(); + + if (userId == null) throw new MyForbiddenException(this.errors.getForbidden().getCode(), this.errors.getForbidden().getMessage()); + + + UserEntity data = this.entityManager.find(UserEntity.class, userId); + if (data == null) throw new MyNotFoundException(messageSource.getMessage("General_ItemNotFound", new Object[]{model.getId(), User.class.getSimpleName()}, LocaleContextHolder.getLocale())); + + AdditionalInfoEntity additionalInfoEntity = this.jsonHandlingService.fromJsonSafe(AdditionalInfoEntity.class, data.getAdditionalInfo()); + if (additionalInfoEntity == null) additionalInfoEntity = new AdditionalInfoEntity(); + additionalInfoEntity.setTimezone(timezone); + + data.setAdditionalInfo(this.jsonHandlingService.toJson(additionalInfoEntity)); + + data.setUpdatedAt(Instant.now()); + this.entityManager.merge(data); + + this.entityManager.flush(); + + this.eventBroker.emit(new UserTouchedEvent(data.getId())); + } + + @Override + public void updateCultureMine(String culture) throws JsonProcessingException { + logger.debug(new MapLogEntry("persisting User culture").And("culture", culture)); + + UUID userId = this.userScope.getUserIdSafe(); + + if (userId == null) throw new MyForbiddenException(this.errors.getForbidden().getCode(), this.errors.getForbidden().getMessage()); + + + UserEntity data = this.entityManager.find(UserEntity.class, userId); + if (data == null) throw new MyNotFoundException(messageSource.getMessage("General_ItemNotFound", new Object[]{model.getId(), User.class.getSimpleName()}, LocaleContextHolder.getLocale())); + + AdditionalInfoEntity additionalInfoEntity = this.jsonHandlingService.fromJsonSafe(AdditionalInfoEntity.class, data.getAdditionalInfo()); + if (additionalInfoEntity == null) additionalInfoEntity = new AdditionalInfoEntity(); + additionalInfoEntity.setCulture(culture); + + data.setAdditionalInfo(this.jsonHandlingService.toJson(additionalInfoEntity)); + + data.setUpdatedAt(Instant.now()); + this.entityManager.merge(data); + + this.entityManager.flush(); + + this.eventBroker.emit(new UserTouchedEvent(data.getId())); + } + + //endregion } \ No newline at end of file diff --git a/dmp-backend/web/src/main/java/eu/eudat/controllers/v2/PrincipalController.java b/dmp-backend/web/src/main/java/eu/eudat/controllers/v2/PrincipalController.java index 653f6ef0c..c50e9f156 100644 --- a/dmp-backend/web/src/main/java/eu/eudat/controllers/v2/PrincipalController.java +++ b/dmp-backend/web/src/main/java/eu/eudat/controllers/v2/PrincipalController.java @@ -56,6 +56,10 @@ public class PrincipalController { BaseFieldSet.asIndexer(Account._principal, Account.PrincipalInfo._authenticatedAt), BaseFieldSet.asIndexer(Account._principal, Account.PrincipalInfo._expiresAt), BaseFieldSet.asIndexer(Account._principal, Account.PrincipalInfo._more), + BaseFieldSet.asIndexer(Account._profile, Account.UserProfileInfo._avatarUrl), + BaseFieldSet.asIndexer(Account._profile, Account.UserProfileInfo._language), + BaseFieldSet.asIndexer(Account._profile, Account.UserProfileInfo._culture), + BaseFieldSet.asIndexer(Account._profile, Account.UserProfileInfo._timezone), Account._roles, Account._permissions); } diff --git a/dmp-backend/web/src/main/java/eu/eudat/controllers/v2/UserController.java b/dmp-backend/web/src/main/java/eu/eudat/controllers/v2/UserController.java index 7524887fe..4bd3941ae 100644 --- a/dmp-backend/web/src/main/java/eu/eudat/controllers/v2/UserController.java +++ b/dmp-backend/web/src/main/java/eu/eudat/controllers/v2/UserController.java @@ -173,6 +173,39 @@ public class UserController { return model; } + @GetMapping("mine/language/{language}") + @Transactional + public void updateLanguageMine(@PathVariable("language") String language) throws MyApplicationException, MyForbiddenException, MyNotFoundException, InvalidApplicationException, JsonProcessingException { + logger.debug(new MapLogEntry("persisting" + User.class.getSimpleName()).And("language", language)); + this.userTypeService.updateLanguageMine(language); + + this.auditService.track(AuditableAction.User_LanguageMine, Map.ofEntries( + new AbstractMap.SimpleEntry("language", language) + )); + } + + @GetMapping("mine/timezone/{timezone}") + @Transactional + public void updateTimezoneMine(@PathVariable("timezone") String timezone) throws MyApplicationException, MyForbiddenException, MyNotFoundException, InvalidApplicationException, JsonProcessingException { + logger.debug(new MapLogEntry("persisting" + User.class.getSimpleName()).And("timezone", timezone)); + this.userTypeService.updateTimezoneMine(timezone); + + this.auditService.track(AuditableAction.User_TimezoneMine, Map.ofEntries( + new AbstractMap.SimpleEntry("timezone", timezone) + )); + } + + @GetMapping("mine/culture/{culture}") + @Transactional + public void updateCultureMine(@PathVariable("culture") String culture) throws MyApplicationException, MyForbiddenException, MyNotFoundException, InvalidApplicationException, JsonProcessingException { + logger.debug(new MapLogEntry("persisting" + User.class.getSimpleName()).And("culture", culture)); + this.userTypeService.updateCultureMine(culture); + + this.auditService.track(AuditableAction.User_CultureMine, Map.ofEntries( + new AbstractMap.SimpleEntry("culture", culture) + )); + } + @PostMapping("persist") @Transactional public User persist(@MyValidate @RequestBody UserPersist model, FieldSet fieldSet) throws MyApplicationException, MyForbiddenException, MyNotFoundException, InvalidApplicationException, JAXBException, ParserConfigurationException, JsonProcessingException, TransformerException { diff --git a/dmp-backend/web/src/main/java/eu/eudat/models/v2/Account.java b/dmp-backend/web/src/main/java/eu/eudat/models/v2/Account.java index d27b45edb..40ed7e9ad 100644 --- a/dmp-backend/web/src/main/java/eu/eudat/models/v2/Account.java +++ b/dmp-backend/web/src/main/java/eu/eudat/models/v2/Account.java @@ -124,6 +124,48 @@ public class Account { } } + public static class UserProfileInfo{ + public String culture; + public final static String _culture = "culture"; + public String language; + public final static String _language = "language"; + public String timezone; + public final static String _timezone = "timezone"; + public String avatarUrl; + public final static String _avatarUrl = "avatarUrl"; + + public String getCulture() { + return culture; + } + + public void setCulture(String culture) { + this.culture = culture; + } + + public String getLanguage() { + return language; + } + + public void setLanguage(String language) { + this.language = language; + } + + public String getTimezone() { + return timezone; + } + + public void setTimezone(String timezone) { + this.timezone = timezone; + } + + public String getAvatarUrl() { + return avatarUrl; + } + + public void setAvatarUrl(String avatarUrl) { + this.avatarUrl = avatarUrl; + } + } public final static String _isAuthenticated = "isAuthenticated"; private Boolean isAuthenticated; @@ -139,6 +181,9 @@ public class Account { public final static String _principal = "principal"; private PrincipalInfo principal; + public final static String _profile = "profile"; + private UserProfileInfo profile; + public final static String _permissions = "permissions"; private List permissions; @@ -177,4 +222,12 @@ public class Account { public void setPermissions(List permissions) { this.permissions = permissions; } + + public UserProfileInfo getProfile() { + return profile; + } + + public void setProfile(UserProfileInfo profile) { + this.profile = profile; + } } diff --git a/dmp-backend/web/src/main/java/eu/eudat/models/v2/AccountBuilder.java b/dmp-backend/web/src/main/java/eu/eudat/models/v2/AccountBuilder.java index 0a38532c5..ca4b26194 100644 --- a/dmp-backend/web/src/main/java/eu/eudat/models/v2/AccountBuilder.java +++ b/dmp-backend/web/src/main/java/eu/eudat/models/v2/AccountBuilder.java @@ -1,19 +1,22 @@ package eu.eudat.models.v2; +import eu.eudat.commons.JsonHandlingService; import eu.eudat.commons.scope.user.UserScope; +import eu.eudat.commons.types.user.AdditionalInfoEntity; import eu.eudat.data.UserEntity; -import eu.eudat.data.UserRoleEntity; -import eu.eudat.logic.services.ApiContext; +import eu.eudat.model.User; import gr.cite.commons.web.authz.configuration.AuthorizationConfiguration; import gr.cite.commons.web.oidc.principal.CurrentPrincipalResolver; import gr.cite.commons.web.oidc.principal.MyPrincipal; import gr.cite.commons.web.oidc.principal.extractor.ClaimExtractor; import gr.cite.commons.web.oidc.principal.extractor.ClaimExtractorKeys; -import gr.cite.tools.data.query.QueryFactory; +import gr.cite.tools.exception.MyNotFoundException; import gr.cite.tools.fieldset.BaseFieldSet; import gr.cite.tools.fieldset.FieldSet; +import jakarta.persistence.EntityManager; import org.springframework.beans.factory.config.ConfigurableBeanFactory; import org.springframework.context.annotation.Scope; +import org.springframework.context.i18n.LocaleContextHolder; import org.springframework.stereotype.Component; import javax.management.InvalidApplicationException; @@ -27,12 +30,16 @@ public class AccountBuilder { private final Set excludeMoreClaim; private final CurrentPrincipalResolver currentPrincipalResolver; private final AuthorizationConfiguration authorizationConfiguration; + private final JsonHandlingService jsonHandlingService; private final UserScope userScope; - public AccountBuilder(ClaimExtractor claimExtractor, CurrentPrincipalResolver currentPrincipalResolver, AuthorizationConfiguration authorizationConfiguration,UserScope userScope) { + private final EntityManager entityManager; + public AccountBuilder(ClaimExtractor claimExtractor, CurrentPrincipalResolver currentPrincipalResolver, AuthorizationConfiguration authorizationConfiguration, JsonHandlingService jsonHandlingService, UserScope userScope, EntityManager entityManager) { this.claimExtractor = claimExtractor; this.currentPrincipalResolver = currentPrincipalResolver; this.authorizationConfiguration = authorizationConfiguration; + this.jsonHandlingService = jsonHandlingService; this.userScope = userScope; + this.entityManager = entityManager; this.excludeMoreClaim = Set.of( ClaimExtractorKeys.Subject, ClaimExtractorKeys.Name, @@ -94,6 +101,18 @@ public class AccountBuilder { Set permissions = authorizationConfiguration.permissionsOfRoles(roles); model.setPermissions(new ArrayList<>(permissions)); } + + FieldSet profileFields = fields.extractPrefixed(BaseFieldSet.asIndexerPrefix(Account._profile)); + if (!profileFields.isEmpty() && this.userScope.getUserIdSafe() != null){ + model.setProfile(new Account.UserProfileInfo()); + UserEntity data = this.entityManager.find(UserEntity.class, this.userScope.getUserIdSafe()); + AdditionalInfoEntity additionalInfoEntity = data == null ? null : this.jsonHandlingService.fromJsonSafe(AdditionalInfoEntity.class, data.getAdditionalInfo()); + if (profileFields.hasField(Account.UserProfileInfo._avatarUrl) && additionalInfoEntity != null) model.getProfile().setAvatarUrl(additionalInfoEntity.getAvatarUrl()); + if (profileFields.hasField(Account.UserProfileInfo._language) && additionalInfoEntity != null) model.getProfile().setLanguage(additionalInfoEntity.getLanguage()); + if (profileFields.hasField(Account.UserProfileInfo._timezone) && additionalInfoEntity != null) model.getProfile().setTimezone(additionalInfoEntity.getTimezone()); + if (profileFields.hasField(Account.UserProfileInfo._culture) && additionalInfoEntity != null) model.getProfile().setCulture(additionalInfoEntity.getCulture()); + } + return model; } }