From f214864503dcc17db2438e7315c911017c323799 Mon Sep 17 00:00:00 2001 From: dashevchenko Date: Tue, 7 Feb 2023 13:10:37 +0200 Subject: [PATCH 1/9] added controller methods for saving user settings in UserController --- .../server/controller/UserController.java | 46 ++++++++ .../controller/BaseUserControllerTest.java | 55 +++++++++ .../server/dao/user/UserService.java | 7 ++ .../common/data/security/UserSettings.java | 46 ++++++++ .../server/dao/model/ModelConstants.java | 7 ++ .../dao/model/sql/UserSettingsEntity.java | 78 +++++++++++++ .../dao/sql/user/JpaUserSettingsDao.java | 65 +++++++++++ .../dao/sql/user/UserSettingsRepository.java | 36 ++++++ .../server/dao/user/UserServiceImpl.java | 23 ++++ .../server/dao/user/UserSettingsDao.java | 32 ++++++ .../main/resources/sql/schema-entities.sql | 6 + .../dao/service/BaseUserServiceTest.java | 24 +++- .../dao/sql/user/JpaUserSettingsDaoTest.java | 105 ++++++++++++++++++ 13 files changed, 529 insertions(+), 1 deletion(-) create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/security/UserSettings.java create mode 100644 dao/src/main/java/org/thingsboard/server/dao/model/sql/UserSettingsEntity.java create mode 100644 dao/src/main/java/org/thingsboard/server/dao/sql/user/JpaUserSettingsDao.java create mode 100644 dao/src/main/java/org/thingsboard/server/dao/sql/user/UserSettingsRepository.java create mode 100644 dao/src/main/java/org/thingsboard/server/dao/user/UserSettingsDao.java create mode 100644 dao/src/test/java/org/thingsboard/server/dao/sql/user/JpaUserSettingsDaoTest.java diff --git a/application/src/main/java/org/thingsboard/server/controller/UserController.java b/application/src/main/java/org/thingsboard/server/controller/UserController.java index c86851fb88..6d08505fa8 100644 --- a/application/src/main/java/org/thingsboard/server/controller/UserController.java +++ b/application/src/main/java/org/thingsboard/server/controller/UserController.java @@ -24,7 +24,9 @@ import org.springframework.beans.factory.annotation.Value; import org.springframework.context.ApplicationEventPublisher; import org.springframework.http.HttpStatus; import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; @@ -33,6 +35,7 @@ import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.bind.annotation.ResponseStatus; import org.springframework.web.bind.annotation.RestController; import org.thingsboard.rule.engine.api.MailService; +import org.thingsboard.server.common.data.AdminSettings; import org.thingsboard.server.common.data.User; import org.thingsboard.server.common.data.exception.ThingsboardErrorCode; import org.thingsboard.server.common.data.exception.ThingsboardException; @@ -43,6 +46,7 @@ import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.security.Authority; import org.thingsboard.server.common.data.security.UserCredentials; +import org.thingsboard.server.common.data.security.UserSettings; import org.thingsboard.server.common.data.security.event.UserCredentialsInvalidationEvent; import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.service.entitiy.user.TbUserService; @@ -378,4 +382,46 @@ public class UserController extends BaseController { } } + @ApiOperation(value = "Save user settings (saveUserSettings)", + notes = "Save user settings for specified user id. " ) + @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')") + @PostMapping(value = "/user/{userId}/settings") + public UserSettings saveUserSettings(@ApiParam(value = USER_ID_PARAM_DESCRIPTION) + @PathVariable(USER_ID) String strUserId, @RequestBody UserSettings userSettings) throws ThingsboardException { + checkParameter(USER_ID, strUserId); + + UserId userId = new UserId(toUUID(strUserId)); + User user = checkUserId(userId, Operation.WRITE); + + userSettings.setUserId(userId); + return userService.saveUserSettings(user.getTenantId(), userId, userSettings); + } + + @ApiOperation(value = "Get user settings (getUserSettings)", + notes = "Fetch the User settings based on the provided User Id. " ) + @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')") + @GetMapping(value = "/user/{userId}/settings") + public UserSettings getUserSettings(@ApiParam(value = USER_ID_PARAM_DESCRIPTION) + @PathVariable(USER_ID) String strUserId) throws ThingsboardException { + checkParameter(USER_ID, strUserId); + + UserId userId = new UserId(toUUID(strUserId)); + User user = checkUserId(userId, Operation.READ); + + return checkNotNull(userService.findUserSettings(user.getTenantId(), user.getId()), "No user settingd found"); + } + + @ApiOperation(value = "Delete user settings (deleteUserSettings)", + notes = "Delete user settings based on the provided User Id. " ) + @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')") + @RequestMapping(value = "/user/{userId}/settings", method = RequestMethod.DELETE) + public void deleteUserSettings(@ApiParam(value = USER_ID_PARAM_DESCRIPTION) + @PathVariable(USER_ID) String strUserId) throws ThingsboardException { + checkParameter(USER_ID, strUserId); + + UserId userId = new UserId(toUUID(strUserId)); + User user = checkUserId(userId, Operation.WRITE); + + userService.deleteUserSettings(user.getTenantId(), userId); + } } diff --git a/application/src/test/java/org/thingsboard/server/controller/BaseUserControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/BaseUserControllerTest.java index c7934d13aa..87c69cb34b 100644 --- a/application/src/test/java/org/thingsboard/server/controller/BaseUserControllerTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/BaseUserControllerTest.java @@ -29,6 +29,8 @@ import org.springframework.context.annotation.Primary; import org.springframework.http.HttpHeaders; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.web.servlet.ResultActions; +import org.testcontainers.shaded.org.apache.commons.lang3.RandomStringUtils; +import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.server.common.data.Customer; import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.Tenant; @@ -40,6 +42,7 @@ import org.thingsboard.server.common.data.id.UserId; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.security.Authority; +import org.thingsboard.server.common.data.security.UserSettings; import org.thingsboard.server.dao.exception.DataValidationException; import org.thingsboard.server.dao.user.UserDao; import org.thingsboard.server.service.mail.TestMailService; @@ -741,6 +744,52 @@ public abstract class BaseUserControllerTest extends AbstractControllerTest { assertThat(getErrorMessage(result)).containsIgnoringCase("invalid sort property"); } + @Test + public void testSaveUserSettings() throws Exception { + loginSysAdmin(); + + User user = createUser(); + User savedUser = doPost("/api/user", user, User.class); + + UserSettings userSettings = createUserSettings(); + UserSettings savedSettings = doPost("/api/user/" + savedUser.getId() + "/settings", userSettings, UserSettings.class); + Assert.assertEquals(savedSettings.getSettings(), savedSettings.getSettings()); + + UserSettings retrievedSettings = doGet("/api/user/" + savedUser.getId() + "/settings", UserSettings.class); + Assert.assertEquals(retrievedSettings.getSettings(), retrievedSettings.getSettings()); + + doDelete("/api/user/" + savedUser.getId() + "/settings"); + doGet("/api/user/" + savedUser.getId() + "/settings").andExpect(status().isNotFound()); + } + + @Test + public void testShouldNotSaveSettingsForOtherUser() throws Exception { + loginSysAdmin(); + + User user = createUser(); + User savedUser = doPost("/api/user", user, User.class); + + loginCustomerUser(); + UserSettings userSettings = createUserSettings(); + doPost("/api/user/" + savedUser.getId() + "/settings", userSettings) + .andExpect(status().isForbidden()); + } + @Test + public void testShouldDeleteSettingsAfterUserDeletion() throws Exception { + loginSysAdmin(); + + User user = createUser(); + User savedUser = doPost("/api/user", user, User.class); + + UserSettings userSettings = createUserSettings(); + UserSettings savedSettings = doPost("/api/user/" + savedUser.getId() + "/settings", userSettings, UserSettings.class); + Assert.assertEquals(savedSettings.getSettings(), savedSettings.getSettings()); + + doDelete("/api/user/" + savedUser.getId()) + .andExpect(status().isOk()); + doGet("/api/user/" + savedUser.getId() + "/settings").andExpect(status().isNotFound()); + } + private User createUser() throws Exception { loginSysAdmin(); String email = "tenant2@thingsboard.org"; @@ -752,4 +801,10 @@ public abstract class BaseUserControllerTest extends AbstractControllerTest { user.setLastName("Downs"); return doPost("/api/user", user, User.class); } + + private UserSettings createUserSettings() { + UserSettings userSettings = new UserSettings(); + userSettings.setSettings(JacksonUtil.newObjectNode().put("text", RandomStringUtils.randomAlphanumeric(10))); + return userSettings; + } } diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/user/UserService.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/user/UserService.java index 9cce5e38b1..fa1ccd2130 100644 --- a/common/dao-api/src/main/java/org/thingsboard/server/dao/user/UserService.java +++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/user/UserService.java @@ -24,6 +24,7 @@ import org.thingsboard.server.common.data.id.UserId; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.security.UserCredentials; +import org.thingsboard.server.common.data.security.UserSettings; public interface UserService { @@ -73,4 +74,10 @@ public interface UserService { void setLastLoginTs(TenantId tenantId, UserId userId); + UserSettings saveUserSettings(TenantId tenantId, UserId userId, UserSettings userSettings); + + UserSettings findUserSettings(TenantId tenantId, UserId userId); + + void deleteUserSettings(TenantId tenantId, UserId userId); + } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/security/UserSettings.java b/common/data/src/main/java/org/thingsboard/server/common/data/security/UserSettings.java new file mode 100644 index 0000000000..a5f95ae9f3 --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/security/UserSettings.java @@ -0,0 +1,46 @@ +/** + * Copyright © 2016-2023 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.data.security; + +import com.fasterxml.jackson.databind.JsonNode; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.EqualsAndHashCode; +import org.thingsboard.server.common.data.id.UserId; +import org.thingsboard.server.common.data.validation.Length; +import org.thingsboard.server.common.data.validation.NoXss; + +import java.io.Serializable; + +@ApiModel +@Data +@EqualsAndHashCode +public class UserSettings implements Serializable { + + private static final long serialVersionUID = 2628320657987010348L; + + @ApiModelProperty(position = 1, value = "JSON object with User id.", accessMode = ApiModelProperty.AccessMode.READ_ONLY) + private UserId userId; + + @ApiModelProperty(position = 2, value = "JSON object with user settings.", dataType = "com.fasterxml.jackson.databind.JsonNode") + @NoXss + @Length(fieldName = "settings", max = 10000) + private transient JsonNode settings; + +} diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java b/dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java index 1bd173d42b..90d4abd292 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java @@ -86,6 +86,13 @@ public class ModelConstants { public static final String USER_CREDENTIALS_BY_ACTIVATE_TOKEN_COLUMN_FAMILY_NAME = "user_credentials_by_activate_token"; public static final String USER_CREDENTIALS_BY_RESET_TOKEN_COLUMN_FAMILY_NAME = "user_credentials_by_reset_token"; + /** + * User settings constants. + */ + public static final String USER_SETTINGS_COLUMN_FAMILY_NAME = "user_settings"; + public static final String USER_SETTINGS_USER_ID_PROPERTY = USER_ID_PROPERTY; + public static final String USER_SETTINGS_SETTINGS = "settings"; + /** * Cassandra admin_settings constants. */ diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/UserSettingsEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/UserSettingsEntity.java new file mode 100644 index 0000000000..59a0aeb081 --- /dev/null +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sql/UserSettingsEntity.java @@ -0,0 +1,78 @@ +/** + * Copyright © 2016-2023 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.dao.model.sql; + +import com.fasterxml.jackson.databind.JsonNode; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import org.hibernate.annotations.Type; +import org.hibernate.annotations.TypeDef; +import org.thingsboard.common.util.JacksonUtil; +import org.thingsboard.server.common.data.id.UserAuthSettingsId; +import org.thingsboard.server.common.data.id.UserId; +import org.thingsboard.server.common.data.security.UserAuthSettings; +import org.thingsboard.server.common.data.security.UserSettings; +import org.thingsboard.server.common.data.security.model.mfa.account.AccountTwoFaSettings; +import org.thingsboard.server.common.data.validation.Length; +import org.thingsboard.server.common.data.validation.NoXss; +import org.thingsboard.server.dao.model.BaseEntity; +import org.thingsboard.server.dao.model.BaseSqlEntity; +import org.thingsboard.server.dao.model.ModelConstants; +import org.thingsboard.server.dao.model.ToData; +import org.thingsboard.server.dao.util.mapping.JsonStringType; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.Table; +import java.util.UUID; + +@EqualsAndHashCode +@Data +@NoArgsConstructor +@TypeDef(name = "json", typeClass = JsonStringType.class) +@Entity +@Table(name = ModelConstants.USER_SETTINGS_COLUMN_FAMILY_NAME) +public class UserSettingsEntity implements ToData { + + @Id + @Column(name = ModelConstants.USER_SETTINGS_USER_ID_PROPERTY) + private UUID userId; + @Type(type = "json") + @Column(name = ModelConstants.USER_SETTINGS_SETTINGS) + private JsonNode settings; + + + public UserSettingsEntity(UserSettings userSettings) { + this.userId = userSettings.getUserId().getId(); + if (userSettings.getSettings() != null) { + this.settings= userSettings.getSettings(); + } + } + + @Override + public UserSettings toData() { + UserSettings userSettings = new UserSettings(); + userSettings.setUserId(new UserId(userId)); + if (settings != null) { + userSettings.setSettings(settings); + } + return userSettings; + } + +} diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/user/JpaUserSettingsDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/user/JpaUserSettingsDao.java new file mode 100644 index 0000000000..9296cae178 --- /dev/null +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/user/JpaUserSettingsDao.java @@ -0,0 +1,65 @@ +/** + * Copyright © 2016-2023 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.dao.sql.user; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Component; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.id.UserId; +import org.thingsboard.server.common.data.security.UserCredentials; +import org.thingsboard.server.common.data.security.UserSettings; +import org.thingsboard.server.dao.DaoUtil; +import org.thingsboard.server.dao.model.sql.EntityAlarmEntity; +import org.thingsboard.server.dao.model.sql.RelationEntity; +import org.thingsboard.server.dao.model.sql.UserCredentialsEntity; +import org.thingsboard.server.dao.model.sql.UserSettingsEntity; +import org.thingsboard.server.dao.sql.JpaAbstractDao; +import org.thingsboard.server.dao.sql.JpaAbstractDaoListeningExecutorService; +import org.thingsboard.server.dao.user.UserCredentialsDao; +import org.thingsboard.server.dao.user.UserSettingsDao; +import org.thingsboard.server.dao.util.SqlDao; + +import java.util.UUID; + +/** + * Created by Valerii Sosliuk on 4/22/2017. + */ +@Slf4j +@Component +@SqlDao +public class JpaUserSettingsDao extends JpaAbstractDaoListeningExecutorService implements UserSettingsDao { + + @Autowired + private UserSettingsRepository userSettingsRepository; + + @Override + public UserSettings saveSettings(TenantId tenantId, UserSettings userSettings) { + return DaoUtil.getData(userSettingsRepository.save(new UserSettingsEntity(userSettings))); + } + + @Override + public UserSettings findByUserId(TenantId tenantId, UserId userId) { + return DaoUtil.getData(userSettingsRepository.findById(userId.getId())); + } + + @Override + public void removeByUserId(TenantId tenantId, UserId userId) { + userSettingsRepository.deleteById(userId.getId()); + } + +} diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/user/UserSettingsRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/user/UserSettingsRepository.java new file mode 100644 index 0000000000..b4df8e72a6 --- /dev/null +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/user/UserSettingsRepository.java @@ -0,0 +1,36 @@ +/** + * Copyright © 2016-2023 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.dao.sql.user; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.thingsboard.server.common.data.security.UserSettings; +import org.thingsboard.server.dao.model.sql.UserCredentialsEntity; +import org.thingsboard.server.dao.model.sql.UserSettingsEntity; + +import java.util.UUID; + +/** + * Created by Valerii Sosliuk on 4/22/2017. + */ +public interface UserSettingsRepository extends JpaRepository { + + UserSettingsEntity save(UserSettings saveSettings); + + UserSettingsEntity findByUserId(UUID userId); + + UserSettingsEntity deleteByUserId(UUID userId); + +} diff --git a/dao/src/main/java/org/thingsboard/server/dao/user/UserServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/user/UserServiceImpl.java index c97d01e2d4..9721934d26 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/user/UserServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/user/UserServiceImpl.java @@ -37,6 +37,7 @@ import org.thingsboard.server.common.data.id.UserId; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.security.UserCredentials; +import org.thingsboard.server.common.data.security.UserSettings; import org.thingsboard.server.common.data.security.event.UserCredentialsInvalidationEvent; import org.thingsboard.server.dao.entity.AbstractEntityService; import org.thingsboard.server.dao.exception.IncorrectParameterException; @@ -72,6 +73,7 @@ public class UserServiceImpl extends AbstractEntityService implements UserServic private final UserDao userDao; private final UserCredentialsDao userCredentialsDao; private final UserAuthSettingsDao userAuthSettingsDao; + private final UserSettingsDao userSettingsDao; private final DataValidator userValidator; private final DataValidator userCredentialsValidator; private final ApplicationEventPublisher eventPublisher; @@ -322,6 +324,27 @@ public class UserServiceImpl extends AbstractEntityService implements UserServic return failedLoginAttempts; } + @Override + public UserSettings saveUserSettings(TenantId tenantId, UserId userId, UserSettings userSettings) { + log.trace("Executing saveUserSettings [{}], [{}]", userId, userSettings); + validateId(userId, INCORRECT_USER_ID + userId); + return userSettingsDao.saveSettings(tenantId, userSettings); + } + + @Override + public UserSettings findUserSettings(TenantId tenantId, UserId userId) { + log.trace("Executing findUserSettings [{}]", userId); + validateId(userId, INCORRECT_USER_ID + userId); + return userSettingsDao.findByUserId(tenantId, userId); + } + + @Override + public void deleteUserSettings(TenantId tenantId, UserId userId) { + log.trace("Executing deleteUserSettings for user [{}]", userId); + validateId(userId, INCORRECT_USER_ID + userId); + userSettingsDao.removeByUserId(tenantId, userId); + } + private int increaseFailedLoginAttempts(User user) { JsonNode additionalInfo = user.getAdditionalInfo(); if (!(additionalInfo instanceof ObjectNode)) { diff --git a/dao/src/main/java/org/thingsboard/server/dao/user/UserSettingsDao.java b/dao/src/main/java/org/thingsboard/server/dao/user/UserSettingsDao.java new file mode 100644 index 0000000000..5e162a8ad7 --- /dev/null +++ b/dao/src/main/java/org/thingsboard/server/dao/user/UserSettingsDao.java @@ -0,0 +1,32 @@ +/** + * Copyright © 2016-2023 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.dao.user; + +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.id.UserId; +import org.thingsboard.server.common.data.security.UserAuthSettings; +import org.thingsboard.server.common.data.security.UserSettings; +import org.thingsboard.server.dao.Dao; + +public interface UserSettingsDao { + + UserSettings saveSettings(TenantId tenantId, UserSettings userSettings); + + UserSettings findByUserId(TenantId tenantId, UserId userId); + + void removeByUserId(TenantId tenantId, UserId userId); + +} diff --git a/dao/src/main/resources/sql/schema-entities.sql b/dao/src/main/resources/sql/schema-entities.sql index c21d1bff38..b6ccbc4c2e 100644 --- a/dao/src/main/resources/sql/schema-entities.sql +++ b/dao/src/main/resources/sql/schema-entities.sql @@ -776,3 +776,9 @@ CREATE TABLE IF NOT EXISTS user_auth_settings ( user_id uuid UNIQUE NOT NULL CONSTRAINT fk_user_auth_settings_user_id REFERENCES tb_user(id), two_fa_settings varchar ); + +CREATE TABLE IF NOT EXISTS user_settings ( + user_id uuid NOT NULL CONSTRAINT user_settings_pkey PRIMARY KEY, + settings varchar(10000), + CONSTRAINT fk_user_id FOREIGN KEY (user_id) REFERENCES tb_user(id) ON DELETE CASCADE +); \ No newline at end of file diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/BaseUserServiceTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/BaseUserServiceTest.java index 6ad50508d7..25992d914a 100644 --- a/dao/src/test/java/org/thingsboard/server/dao/service/BaseUserServiceTest.java +++ b/dao/src/test/java/org/thingsboard/server/dao/service/BaseUserServiceTest.java @@ -19,16 +19,20 @@ import org.junit.After; import org.junit.Assert; import org.junit.Before; import org.junit.Test; +import org.testcontainers.shaded.org.apache.commons.lang3.RandomStringUtils; +import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.server.common.data.Customer; import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.Tenant; import org.thingsboard.server.common.data.User; import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.id.UserId; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.security.Authority; import org.thingsboard.server.common.data.security.UserCredentials; +import org.thingsboard.server.common.data.security.UserSettings; import org.thingsboard.server.dao.exception.DataValidationException; import java.util.ArrayList; @@ -40,6 +44,7 @@ public abstract class BaseUserServiceTest extends AbstractServiceTest { private IdComparator idComparator = new IdComparator<>(); private TenantId tenantId; + private UserSettings userSettings; @Before public void before() { @@ -65,7 +70,10 @@ public abstract class BaseUserServiceTest extends AbstractServiceTest { customerUser.setTenantId(tenantId); customerUser.setCustomerId(savedCustomer.getId()); customerUser.setEmail("customer@thingsboard.org"); - userService.saveUser(customerUser); + customerUser = userService.saveUser(customerUser); + + userSettings = createUserSettings(); + userSettings = userService.saveUserSettings(TenantId.SYS_TENANT_ID, customerUser.getId(), userSettings); } @After @@ -105,6 +113,14 @@ public abstract class BaseUserServiceTest extends AbstractServiceTest { Assert.assertNotNull(userCredentials); } + @Test + public void testFindUserSettings() { + User user = userService.findUserByEmail(SYSTEM_TENANT_ID,"customer@thingsboard.org"); + Assert.assertNotNull(user); + UserSettings userSettings = userService.findUserSettings(SYSTEM_TENANT_ID, user.getId()); + Assert.assertEquals(userSettings.getSettings(), userSettings.getSettings()); + } + @Test public void testSaveUser() { User tenantAdminUser = userService.findUserByEmail(SYSTEM_TENANT_ID,"tenant@thingsboard.org"); @@ -474,4 +490,10 @@ public abstract class BaseUserServiceTest extends AbstractServiceTest { tenantService.deleteTenant(tenantId); } + private UserSettings createUserSettings() { + UserSettings userSettings = new UserSettings(); + userSettings.setSettings(JacksonUtil.newObjectNode().put("text", RandomStringUtils.randomAlphanumeric(10))); + return userSettings; + } + } diff --git a/dao/src/test/java/org/thingsboard/server/dao/sql/user/JpaUserSettingsDaoTest.java b/dao/src/test/java/org/thingsboard/server/dao/sql/user/JpaUserSettingsDaoTest.java new file mode 100644 index 0000000000..8dd703dc84 --- /dev/null +++ b/dao/src/test/java/org/thingsboard/server/dao/sql/user/JpaUserSettingsDaoTest.java @@ -0,0 +1,105 @@ +/** + * Copyright © 2016-2023 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.dao.sql.user; + +import com.datastax.oss.driver.api.core.uuid.Uuids; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.testcontainers.shaded.org.apache.commons.lang3.RandomStringUtils; +import org.thingsboard.common.util.JacksonUtil; +import org.thingsboard.server.common.data.User; +import org.thingsboard.server.common.data.id.CustomerId; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.id.UserId; +import org.thingsboard.server.common.data.security.Authority; +import org.thingsboard.server.common.data.security.UserSettings; +import org.thingsboard.server.dao.AbstractJpaDaoTest; +import org.thingsboard.server.dao.service.AbstractServiceTest; +import org.thingsboard.server.dao.user.UserDao; +import org.thingsboard.server.dao.user.UserSettingsDao; + +import java.util.UUID; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.thingsboard.server.dao.model.ModelConstants.NULL_UUID; +import static org.thingsboard.server.dao.service.AbstractServiceTest.SYSTEM_TENANT_ID; + +/** + * Created by Valerii Sosliuk on 4/22/2017. + */ +public class JpaUserSettingsDaoTest extends AbstractJpaDaoTest { + + private UUID tenantId; + private User user; + + @Autowired + private UserSettingsDao userSettingsDao; + + @Autowired + private UserDao userDao; + + @Before + public void setUp() { + tenantId = Uuids.timeBased(); + user = saveUser(tenantId, Uuids.timeBased()); + } + + @After + public void tearDown() { + userDao.removeById(user.getTenantId(), user.getUuidId()); + } + + @Test + public void testFindSettingsByTenantAdminUserId() { + UserSettings userSettings = createUserSettings(user.getId()); + + UserSettings retrievedUserSettings = userSettingsDao.findByUserId(SYSTEM_TENANT_ID, user.getId()); + assertEquals(retrievedUserSettings.getSettings(), userSettings.getSettings()); + + userSettingsDao.removeByUserId(SYSTEM_TENANT_ID, user.getId()); + + UserSettings retrievedUserSettings2 = userSettingsDao.findByUserId(SYSTEM_TENANT_ID, user.getId()); + assertNull(retrievedUserSettings2); + } + + private UserSettings createUserSettings(UserId userId) { + UserSettings userSettings = new UserSettings(); + userSettings.setSettings(JacksonUtil.newObjectNode().put("text", RandomStringUtils.randomAlphanumeric(10))); + userSettings.setUserId(userId); + return userSettingsDao.saveSettings(SYSTEM_TENANT_ID, userSettings); + } + + private User saveUser(UUID tenantId, UUID customerId) { + User user = new User(); + UUID id = Uuids.timeBased(); + user.setId(new UserId(id)); + user.setTenantId(TenantId.fromUUID(tenantId)); + user.setCustomerId(new CustomerId(customerId)); + if (customerId == NULL_UUID) { + user.setAuthority(Authority.TENANT_ADMIN); + } else { + user.setAuthority(Authority.CUSTOMER_USER); + } + String idString = id.toString(); + String email = idString.substring(0, idString.indexOf('-')) + "@thingsboard.org"; + user.setEmail(email); + return userDao.save(AbstractServiceTest.SYSTEM_TENANT_ID, user); + } +} From c3d4925e483503d0d48dc4dc92d6e428ff7d3fb8 Mon Sep 17 00:00:00 2001 From: dashevchenko Date: Tue, 7 Feb 2023 13:59:17 +0200 Subject: [PATCH 2/9] added sql upgrade script --- .../main/data/upgrade/3.4.4/schema_update.sql | 21 +++++++++++++++++++ .../dao/model/sql/UserSettingsEntity.java | 1 - .../dao/sql/user/JpaUserSettingsDao.java | 6 +++--- .../dao/sql/user/UserSettingsRepository.java | 12 +++-------- .../server/dao/user/UserServiceImpl.java | 10 ++++----- .../server/dao/user/UserSettingsDao.java | 8 +++---- .../dao/sql/user/JpaUserSettingsDaoTest.java | 10 ++++----- 7 files changed, 40 insertions(+), 28 deletions(-) create mode 100644 application/src/main/data/upgrade/3.4.4/schema_update.sql diff --git a/application/src/main/data/upgrade/3.4.4/schema_update.sql b/application/src/main/data/upgrade/3.4.4/schema_update.sql new file mode 100644 index 0000000000..811031b195 --- /dev/null +++ b/application/src/main/data/upgrade/3.4.4/schema_update.sql @@ -0,0 +1,21 @@ +-- +-- Copyright © 2016-2023 The Thingsboard Authors +-- +-- Licensed under the Apache License, Version 2.0 (the "License"); +-- you may not use this file except in compliance with the License. +-- You may obtain a copy of the License at +-- +-- http://www.apache.org/licenses/LICENSE-2.0 +-- +-- Unless required by applicable law or agreed to in writing, software +-- distributed under the License is distributed on an "AS IS" BASIS, +-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +-- See the License for the specific language governing permissions and +-- limitations under the License. +-- + +CREATE TABLE IF NOT EXISTS user_settings ( + user_id uuid NOT NULL CONSTRAINT user_settings_pkey PRIMARY KEY, + settings varchar(10000), + CONSTRAINT fk_user_id FOREIGN KEY (user_id) REFERENCES tb_user(id) ON DELETE CASCADE +); diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/UserSettingsEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/UserSettingsEntity.java index 59a0aeb081..183b35f839 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/sql/UserSettingsEntity.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sql/UserSettingsEntity.java @@ -42,7 +42,6 @@ import javax.persistence.Id; import javax.persistence.Table; import java.util.UUID; -@EqualsAndHashCode @Data @NoArgsConstructor @TypeDef(name = "json", typeClass = JsonStringType.class) diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/user/JpaUserSettingsDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/user/JpaUserSettingsDao.java index 9296cae178..74499683dd 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/user/JpaUserSettingsDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/user/JpaUserSettingsDao.java @@ -48,17 +48,17 @@ public class JpaUserSettingsDao extends JpaAbstractDaoListeningExecutorService i private UserSettingsRepository userSettingsRepository; @Override - public UserSettings saveSettings(TenantId tenantId, UserSettings userSettings) { + public UserSettings save(TenantId tenantId, UserSettings userSettings) { return DaoUtil.getData(userSettingsRepository.save(new UserSettingsEntity(userSettings))); } @Override - public UserSettings findByUserId(TenantId tenantId, UserId userId) { + public UserSettings findById(TenantId tenantId, UserId userId) { return DaoUtil.getData(userSettingsRepository.findById(userId.getId())); } @Override - public void removeByUserId(TenantId tenantId, UserId userId) { + public void removeById(TenantId tenantId, UserId userId) { userSettingsRepository.deleteById(userId.getId()); } diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/user/UserSettingsRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/user/UserSettingsRepository.java index b4df8e72a6..6766bdb106 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/user/UserSettingsRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/user/UserSettingsRepository.java @@ -17,20 +17,14 @@ package org.thingsboard.server.dao.sql.user; import org.springframework.data.jpa.repository.JpaRepository; import org.thingsboard.server.common.data.security.UserSettings; -import org.thingsboard.server.dao.model.sql.UserCredentialsEntity; import org.thingsboard.server.dao.model.sql.UserSettingsEntity; import java.util.UUID; -/** - * Created by Valerii Sosliuk on 4/22/2017. - */ public interface UserSettingsRepository extends JpaRepository { - UserSettingsEntity save(UserSettings saveSettings); - - UserSettingsEntity findByUserId(UUID userId); - - UserSettingsEntity deleteByUserId(UUID userId); +// UserSettingsEntity save(UserSettings saveSettings); +// +// UserSettingsEntity findByUserId(UUID userId); } diff --git a/dao/src/main/java/org/thingsboard/server/dao/user/UserServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/user/UserServiceImpl.java index 9721934d26..78860df8c9 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/user/UserServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/user/UserServiceImpl.java @@ -326,23 +326,23 @@ public class UserServiceImpl extends AbstractEntityService implements UserServic @Override public UserSettings saveUserSettings(TenantId tenantId, UserId userId, UserSettings userSettings) { - log.trace("Executing saveUserSettings [{}], [{}]", userId, userSettings); + log.trace("Executing saveUserSettings for user [{}], [{}]", userId, userSettings); validateId(userId, INCORRECT_USER_ID + userId); - return userSettingsDao.saveSettings(tenantId, userSettings); + return userSettingsDao.save(tenantId, userSettings); } @Override public UserSettings findUserSettings(TenantId tenantId, UserId userId) { - log.trace("Executing findUserSettings [{}]", userId); + log.trace("Executing findUserSettings for user [{}]", userId); validateId(userId, INCORRECT_USER_ID + userId); - return userSettingsDao.findByUserId(tenantId, userId); + return userSettingsDao.findById(tenantId, userId); } @Override public void deleteUserSettings(TenantId tenantId, UserId userId) { log.trace("Executing deleteUserSettings for user [{}]", userId); validateId(userId, INCORRECT_USER_ID + userId); - userSettingsDao.removeByUserId(tenantId, userId); + userSettingsDao.removeById(tenantId, userId); } private int increaseFailedLoginAttempts(User user) { diff --git a/dao/src/main/java/org/thingsboard/server/dao/user/UserSettingsDao.java b/dao/src/main/java/org/thingsboard/server/dao/user/UserSettingsDao.java index 5e162a8ad7..16a7675a64 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/user/UserSettingsDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/user/UserSettingsDao.java @@ -17,16 +17,14 @@ package org.thingsboard.server.dao.user; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.UserId; -import org.thingsboard.server.common.data.security.UserAuthSettings; import org.thingsboard.server.common.data.security.UserSettings; -import org.thingsboard.server.dao.Dao; public interface UserSettingsDao { - UserSettings saveSettings(TenantId tenantId, UserSettings userSettings); + UserSettings save(TenantId tenantId, UserSettings userSettings); - UserSettings findByUserId(TenantId tenantId, UserId userId); + UserSettings findById(TenantId tenantId, UserId userId); - void removeByUserId(TenantId tenantId, UserId userId); + void removeById(TenantId tenantId, UserId userId); } diff --git a/dao/src/test/java/org/thingsboard/server/dao/sql/user/JpaUserSettingsDaoTest.java b/dao/src/test/java/org/thingsboard/server/dao/sql/user/JpaUserSettingsDaoTest.java index 8dd703dc84..fa07e7dae0 100644 --- a/dao/src/test/java/org/thingsboard/server/dao/sql/user/JpaUserSettingsDaoTest.java +++ b/dao/src/test/java/org/thingsboard/server/dao/sql/user/JpaUserSettingsDaoTest.java @@ -67,15 +67,15 @@ public class JpaUserSettingsDaoTest extends AbstractJpaDaoTest { } @Test - public void testFindSettingsByTenantAdminUserId() { + public void testFindSettingsByUserId() { UserSettings userSettings = createUserSettings(user.getId()); - UserSettings retrievedUserSettings = userSettingsDao.findByUserId(SYSTEM_TENANT_ID, user.getId()); + UserSettings retrievedUserSettings = userSettingsDao.findById(SYSTEM_TENANT_ID, user.getId()); assertEquals(retrievedUserSettings.getSettings(), userSettings.getSettings()); - userSettingsDao.removeByUserId(SYSTEM_TENANT_ID, user.getId()); + userSettingsDao.removeById(SYSTEM_TENANT_ID, user.getId()); - UserSettings retrievedUserSettings2 = userSettingsDao.findByUserId(SYSTEM_TENANT_ID, user.getId()); + UserSettings retrievedUserSettings2 = userSettingsDao.findById(SYSTEM_TENANT_ID, user.getId()); assertNull(retrievedUserSettings2); } @@ -83,7 +83,7 @@ public class JpaUserSettingsDaoTest extends AbstractJpaDaoTest { UserSettings userSettings = new UserSettings(); userSettings.setSettings(JacksonUtil.newObjectNode().put("text", RandomStringUtils.randomAlphanumeric(10))); userSettings.setUserId(userId); - return userSettingsDao.saveSettings(SYSTEM_TENANT_ID, userSettings); + return userSettingsDao.save(SYSTEM_TENANT_ID, userSettings); } private User saveUser(UUID tenantId, UUID customerId) { From 16c5a4f1f99e59ad8cde3db7b0904cb6a714031b Mon Sep 17 00:00:00 2001 From: dashevchenko Date: Tue, 7 Feb 2023 14:49:09 +0200 Subject: [PATCH 3/9] refactoring --- .../server/common/data/security/UserSettings.java | 4 ---- .../server/dao/model/sql/UserSettingsEntity.java | 11 ----------- .../server/dao/sql/user/JpaUserSettingsDao.java | 12 ------------ .../server/dao/sql/user/UserSettingsRepository.java | 5 ----- .../server/dao/sql/user/JpaUserSettingsDaoTest.java | 4 ---- 5 files changed, 36 deletions(-) diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/security/UserSettings.java b/common/data/src/main/java/org/thingsboard/server/common/data/security/UserSettings.java index a5f95ae9f3..49d5903049 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/security/UserSettings.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/security/UserSettings.java @@ -18,10 +18,7 @@ package org.thingsboard.server.common.data.security; import com.fasterxml.jackson.databind.JsonNode; import io.swagger.annotations.ApiModel; import io.swagger.annotations.ApiModelProperty; -import lombok.AllArgsConstructor; -import lombok.Builder; import lombok.Data; -import lombok.EqualsAndHashCode; import org.thingsboard.server.common.data.id.UserId; import org.thingsboard.server.common.data.validation.Length; import org.thingsboard.server.common.data.validation.NoXss; @@ -30,7 +27,6 @@ import java.io.Serializable; @ApiModel @Data -@EqualsAndHashCode public class UserSettings implements Serializable { private static final long serialVersionUID = 2628320657987010348L; diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/UserSettingsEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/UserSettingsEntity.java index 183b35f839..d7abf7ea23 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/sql/UserSettingsEntity.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sql/UserSettingsEntity.java @@ -16,22 +16,12 @@ package org.thingsboard.server.dao.model.sql; import com.fasterxml.jackson.databind.JsonNode; -import io.swagger.annotations.ApiModelProperty; import lombok.Data; -import lombok.EqualsAndHashCode; import lombok.NoArgsConstructor; import org.hibernate.annotations.Type; import org.hibernate.annotations.TypeDef; -import org.thingsboard.common.util.JacksonUtil; -import org.thingsboard.server.common.data.id.UserAuthSettingsId; import org.thingsboard.server.common.data.id.UserId; -import org.thingsboard.server.common.data.security.UserAuthSettings; import org.thingsboard.server.common.data.security.UserSettings; -import org.thingsboard.server.common.data.security.model.mfa.account.AccountTwoFaSettings; -import org.thingsboard.server.common.data.validation.Length; -import org.thingsboard.server.common.data.validation.NoXss; -import org.thingsboard.server.dao.model.BaseEntity; -import org.thingsboard.server.dao.model.BaseSqlEntity; import org.thingsboard.server.dao.model.ModelConstants; import org.thingsboard.server.dao.model.ToData; import org.thingsboard.server.dao.util.mapping.JsonStringType; @@ -56,7 +46,6 @@ public class UserSettingsEntity implements ToData { @Column(name = ModelConstants.USER_SETTINGS_SETTINGS) private JsonNode settings; - public UserSettingsEntity(UserSettings userSettings) { this.userId = userSettings.getUserId().getId(); if (userSettings.getSettings() != null) { diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/user/JpaUserSettingsDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/user/JpaUserSettingsDao.java index 74499683dd..13e8564206 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/user/JpaUserSettingsDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/user/JpaUserSettingsDao.java @@ -17,28 +17,16 @@ package org.thingsboard.server.dao.sql.user; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Component; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.UserId; -import org.thingsboard.server.common.data.security.UserCredentials; import org.thingsboard.server.common.data.security.UserSettings; import org.thingsboard.server.dao.DaoUtil; -import org.thingsboard.server.dao.model.sql.EntityAlarmEntity; -import org.thingsboard.server.dao.model.sql.RelationEntity; -import org.thingsboard.server.dao.model.sql.UserCredentialsEntity; import org.thingsboard.server.dao.model.sql.UserSettingsEntity; -import org.thingsboard.server.dao.sql.JpaAbstractDao; import org.thingsboard.server.dao.sql.JpaAbstractDaoListeningExecutorService; -import org.thingsboard.server.dao.user.UserCredentialsDao; import org.thingsboard.server.dao.user.UserSettingsDao; import org.thingsboard.server.dao.util.SqlDao; -import java.util.UUID; - -/** - * Created by Valerii Sosliuk on 4/22/2017. - */ @Slf4j @Component @SqlDao diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/user/UserSettingsRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/user/UserSettingsRepository.java index 6766bdb106..6b3e28abc9 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/user/UserSettingsRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/user/UserSettingsRepository.java @@ -16,15 +16,10 @@ package org.thingsboard.server.dao.sql.user; import org.springframework.data.jpa.repository.JpaRepository; -import org.thingsboard.server.common.data.security.UserSettings; import org.thingsboard.server.dao.model.sql.UserSettingsEntity; import java.util.UUID; public interface UserSettingsRepository extends JpaRepository { -// UserSettingsEntity save(UserSettings saveSettings); -// -// UserSettingsEntity findByUserId(UUID userId); - } diff --git a/dao/src/test/java/org/thingsboard/server/dao/sql/user/JpaUserSettingsDaoTest.java b/dao/src/test/java/org/thingsboard/server/dao/sql/user/JpaUserSettingsDaoTest.java index fa07e7dae0..4260221a73 100644 --- a/dao/src/test/java/org/thingsboard/server/dao/sql/user/JpaUserSettingsDaoTest.java +++ b/dao/src/test/java/org/thingsboard/server/dao/sql/user/JpaUserSettingsDaoTest.java @@ -36,14 +36,10 @@ import org.thingsboard.server.dao.user.UserSettingsDao; import java.util.UUID; import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.thingsboard.server.dao.model.ModelConstants.NULL_UUID; import static org.thingsboard.server.dao.service.AbstractServiceTest.SYSTEM_TENANT_ID; -/** - * Created by Valerii Sosliuk on 4/22/2017. - */ public class JpaUserSettingsDaoTest extends AbstractJpaDaoTest { private UUID tenantId; From 5f341a71a77589d7e2874f23feb46bea812fad6f Mon Sep 17 00:00:00 2001 From: dashevchenko Date: Tue, 7 Feb 2023 15:21:58 +0200 Subject: [PATCH 4/9] fixed sql version --- .../main/data/upgrade/3.4.3/schema_update.sql | 26 ------------------- .../main/data/upgrade/3.4.4/schema_update.sql | 11 ++++++++ .../install/SqlDatabaseUpgradeService.java | 4 +-- 3 files changed, 13 insertions(+), 28 deletions(-) delete mode 100644 application/src/main/data/upgrade/3.4.3/schema_update.sql diff --git a/application/src/main/data/upgrade/3.4.3/schema_update.sql b/application/src/main/data/upgrade/3.4.3/schema_update.sql deleted file mode 100644 index deaf8c6369..0000000000 --- a/application/src/main/data/upgrade/3.4.3/schema_update.sql +++ /dev/null @@ -1,26 +0,0 @@ --- --- Copyright © 2016-2023 The Thingsboard Authors --- --- Licensed under the Apache License, Version 2.0 (the "License"); --- you may not use this file except in compliance with the License. --- You may obtain a copy of the License at --- --- http://www.apache.org/licenses/LICENSE-2.0 --- --- Unless required by applicable law or agreed to in writing, software --- distributed under the License is distributed on an "AS IS" BASIS, --- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. --- See the License for the specific language governing permissions and --- limitations under the License. --- - -CREATE TABLE IF NOT EXISTS alarm_comment ( - id uuid NOT NULL, - created_time bigint NOT NULL, - alarm_id uuid NOT NULL, - user_id uuid, - type varchar(255) NOT NULL, - comment varchar(10000), - CONSTRAINT fk_alarm_comment_alarm_id FOREIGN KEY (alarm_id) REFERENCES alarm(id) ON DELETE CASCADE -) PARTITION BY RANGE (created_time); -CREATE INDEX IF NOT EXISTS idx_alarm_comment_alarm_id ON alarm_comment(alarm_id); diff --git a/application/src/main/data/upgrade/3.4.4/schema_update.sql b/application/src/main/data/upgrade/3.4.4/schema_update.sql index 811031b195..598212ca2a 100644 --- a/application/src/main/data/upgrade/3.4.4/schema_update.sql +++ b/application/src/main/data/upgrade/3.4.4/schema_update.sql @@ -14,6 +14,17 @@ -- limitations under the License. -- +CREATE TABLE IF NOT EXISTS alarm_comment ( + id uuid NOT NULL, + created_time bigint NOT NULL, + alarm_id uuid NOT NULL, + user_id uuid, + type varchar(255) NOT NULL, + comment varchar(10000), + CONSTRAINT fk_alarm_comment_alarm_id FOREIGN KEY (alarm_id) REFERENCES alarm(id) ON DELETE CASCADE +) PARTITION BY RANGE (created_time); +CREATE INDEX IF NOT EXISTS idx_alarm_comment_alarm_id ON alarm_comment(alarm_id); + CREATE TABLE IF NOT EXISTS user_settings ( user_id uuid NOT NULL CONSTRAINT user_settings_pkey PRIMARY KEY, settings varchar(10000), diff --git a/application/src/main/java/org/thingsboard/server/service/install/SqlDatabaseUpgradeService.java b/application/src/main/java/org/thingsboard/server/service/install/SqlDatabaseUpgradeService.java index a4330d3972..b9ff5383b5 100644 --- a/application/src/main/java/org/thingsboard/server/service/install/SqlDatabaseUpgradeService.java +++ b/application/src/main/java/org/thingsboard/server/service/install/SqlDatabaseUpgradeService.java @@ -677,11 +677,11 @@ public class SqlDatabaseUpgradeService implements DatabaseEntitiesUpgradeService log.error("Failed updating schema!!!", e); } break; - case "3.4.3": + case "3.4.4": try (Connection conn = DriverManager.getConnection(dbUrl, dbUserName, dbPassword)) { log.info("Updating schema ..."); if (isOldSchema(conn, 3004002)) { - schemaUpdateFile = Paths.get(installScripts.getDataDir(), "upgrade", "3.4.3", SCHEMA_UPDATE_SQL); + schemaUpdateFile = Paths.get(installScripts.getDataDir(), "upgrade", "3.4.4", SCHEMA_UPDATE_SQL); loadSql(schemaUpdateFile, conn); try { From 3ba16daa0346df277d7cff6cce66f8eefc99d492 Mon Sep 17 00:00:00 2001 From: dashevchenko Date: Tue, 7 Feb 2023 15:49:30 +0200 Subject: [PATCH 5/9] fixed sql version --- .../server/install/ThingsboardInstallService.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/install/ThingsboardInstallService.java b/application/src/main/java/org/thingsboard/server/install/ThingsboardInstallService.java index d756541902..b2255b9ff4 100644 --- a/application/src/main/java/org/thingsboard/server/install/ThingsboardInstallService.java +++ b/application/src/main/java/org/thingsboard/server/install/ThingsboardInstallService.java @@ -233,9 +233,9 @@ public class ThingsboardInstallService { log.info("Upgrading ThingsBoard from version 3.4.1 to 3.4.2 ..."); databaseEntitiesUpgradeService.upgradeDatabase("3.4.1"); dataUpdateService.updateData("3.4.1"); - case "3.4.3": - log.info("Upgrading ThingsBoard from version 3.4.3 to 3.5.0 ..."); - databaseEntitiesUpgradeService.upgradeDatabase("3.4.3"); + case "3.4.4": + log.info("Upgrading ThingsBoard from version 3.4.4 to 3.5.0 ..."); + databaseEntitiesUpgradeService.upgradeDatabase("3.4.4"); log.info("Updating system data..."); systemDataLoaderService.updateSystemWidgets(); break; From eea206bb6d0f15dfee71e4ce8d4ca0a5e9a8da91 Mon Sep 17 00:00:00 2001 From: dashevchenko Date: Tue, 7 Feb 2023 16:57:48 +0200 Subject: [PATCH 6/9] refactoring --- .../org/thingsboard/server/controller/UserController.java | 1 - .../thingsboard/server/controller/BaseUserControllerTest.java | 3 +-- .../thingsboard/server/dao/service/BaseUserServiceTest.java | 4 +--- 3 files changed, 2 insertions(+), 6 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/controller/UserController.java b/application/src/main/java/org/thingsboard/server/controller/UserController.java index 6d08505fa8..fc95ee2930 100644 --- a/application/src/main/java/org/thingsboard/server/controller/UserController.java +++ b/application/src/main/java/org/thingsboard/server/controller/UserController.java @@ -35,7 +35,6 @@ import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.bind.annotation.ResponseStatus; import org.springframework.web.bind.annotation.RestController; import org.thingsboard.rule.engine.api.MailService; -import org.thingsboard.server.common.data.AdminSettings; import org.thingsboard.server.common.data.User; import org.thingsboard.server.common.data.exception.ThingsboardErrorCode; import org.thingsboard.server.common.data.exception.ThingsboardException; diff --git a/application/src/test/java/org/thingsboard/server/controller/BaseUserControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/BaseUserControllerTest.java index 87c69cb34b..e9d516c68b 100644 --- a/application/src/test/java/org/thingsboard/server/controller/BaseUserControllerTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/BaseUserControllerTest.java @@ -29,7 +29,6 @@ import org.springframework.context.annotation.Primary; import org.springframework.http.HttpHeaders; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.web.servlet.ResultActions; -import org.testcontainers.shaded.org.apache.commons.lang3.RandomStringUtils; import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.server.common.data.Customer; import org.thingsboard.server.common.data.StringUtils; @@ -804,7 +803,7 @@ public abstract class BaseUserControllerTest extends AbstractControllerTest { private UserSettings createUserSettings() { UserSettings userSettings = new UserSettings(); - userSettings.setSettings(JacksonUtil.newObjectNode().put("text", RandomStringUtils.randomAlphanumeric(10))); + userSettings.setSettings(JacksonUtil.newObjectNode().put("text", StringUtils.randomAlphanumeric(10))); return userSettings; } } diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/BaseUserServiceTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/BaseUserServiceTest.java index 25992d914a..850a7b89e6 100644 --- a/dao/src/test/java/org/thingsboard/server/dao/service/BaseUserServiceTest.java +++ b/dao/src/test/java/org/thingsboard/server/dao/service/BaseUserServiceTest.java @@ -19,7 +19,6 @@ import org.junit.After; import org.junit.Assert; import org.junit.Before; import org.junit.Test; -import org.testcontainers.shaded.org.apache.commons.lang3.RandomStringUtils; import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.server.common.data.Customer; import org.thingsboard.server.common.data.StringUtils; @@ -27,7 +26,6 @@ import org.thingsboard.server.common.data.Tenant; import org.thingsboard.server.common.data.User; import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.TenantId; -import org.thingsboard.server.common.data.id.UserId; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.security.Authority; @@ -492,7 +490,7 @@ public abstract class BaseUserServiceTest extends AbstractServiceTest { private UserSettings createUserSettings() { UserSettings userSettings = new UserSettings(); - userSettings.setSettings(JacksonUtil.newObjectNode().put("text", RandomStringUtils.randomAlphanumeric(10))); + userSettings.setSettings(JacksonUtil.newObjectNode().put("text", StringUtils.randomAlphanumeric(10))); return userSettings; } From 4644401a53ac8311add842fe5bc412d304d1749f Mon Sep 17 00:00:00 2001 From: dashevchenko Date: Tue, 7 Feb 2023 17:59:44 +0200 Subject: [PATCH 7/9] unit test fix --- .../thingsboard/server/dao/service/BaseUserServiceTest.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/BaseUserServiceTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/BaseUserServiceTest.java index 850a7b89e6..79f78ed10e 100644 --- a/dao/src/test/java/org/thingsboard/server/dao/service/BaseUserServiceTest.java +++ b/dao/src/test/java/org/thingsboard/server/dao/service/BaseUserServiceTest.java @@ -26,6 +26,7 @@ import org.thingsboard.server.common.data.Tenant; import org.thingsboard.server.common.data.User; import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.id.UserId; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.security.Authority; @@ -70,7 +71,7 @@ public abstract class BaseUserServiceTest extends AbstractServiceTest { customerUser.setEmail("customer@thingsboard.org"); customerUser = userService.saveUser(customerUser); - userSettings = createUserSettings(); + userSettings = createUserSettings(customerUser.getId()); userSettings = userService.saveUserSettings(TenantId.SYS_TENANT_ID, customerUser.getId(), userSettings); } @@ -488,8 +489,9 @@ public abstract class BaseUserServiceTest extends AbstractServiceTest { tenantService.deleteTenant(tenantId); } - private UserSettings createUserSettings() { + private UserSettings createUserSettings(UserId userId) { UserSettings userSettings = new UserSettings(); + userSettings.setUserId(userId); userSettings.setSettings(JacksonUtil.newObjectNode().put("text", StringUtils.randomAlphanumeric(10))); return userSettings; } From 6c5054a6e835fb775a92e8ec4cc855bb8ed2bb38 Mon Sep 17 00:00:00 2001 From: dashevchenko Date: Fri, 10 Feb 2023 17:08:27 +0200 Subject: [PATCH 8/9] added caching, added PUT methods, refactored code --- .../main/data/upgrade/3.4.4/schema_update.sql | 2 +- .../server/controller/BaseController.java | 4 + .../server/controller/UserController.java | 67 +++++---- .../src/main/resources/thingsboard.yml | 3 + .../server/controller/AbstractWebTest.java | 17 +++ .../controller/BaseUserControllerTest.java | 64 ++++----- .../server/dao/user/UserService.java | 7 - .../server/dao/user/UserSettingsService.java | 43 ++++++ .../server/common/data/CacheConstants.java | 1 + .../common/data/security/UserSettings.java | 21 ++- dao/pom.xml | 4 + .../dao/entity/AbstractCachedService.java | 43 ++++++ .../server/dao/user/UserServiceImpl.java | 21 --- .../dao/user/UserSettingsCaffeineCache.java | 36 +++++ .../dao/user/UserSettingsEvictEvent.java | 24 ++++ .../dao/user/UserSettingsRedisCache.java | 38 +++++ .../dao/user/UserSettingsServiceImpl.java | 131 ++++++++++++++++++ .../dao/service/BaseUserServiceTest.java | 9 -- 18 files changed, 433 insertions(+), 102 deletions(-) create mode 100644 common/dao-api/src/main/java/org/thingsboard/server/dao/user/UserSettingsService.java create mode 100644 dao/src/main/java/org/thingsboard/server/dao/entity/AbstractCachedService.java create mode 100644 dao/src/main/java/org/thingsboard/server/dao/user/UserSettingsCaffeineCache.java create mode 100644 dao/src/main/java/org/thingsboard/server/dao/user/UserSettingsEvictEvent.java create mode 100644 dao/src/main/java/org/thingsboard/server/dao/user/UserSettingsRedisCache.java create mode 100644 dao/src/main/java/org/thingsboard/server/dao/user/UserSettingsServiceImpl.java diff --git a/application/src/main/data/upgrade/3.4.4/schema_update.sql b/application/src/main/data/upgrade/3.4.4/schema_update.sql index 598212ca2a..f8c133f352 100644 --- a/application/src/main/data/upgrade/3.4.4/schema_update.sql +++ b/application/src/main/data/upgrade/3.4.4/schema_update.sql @@ -27,6 +27,6 @@ CREATE INDEX IF NOT EXISTS idx_alarm_comment_alarm_id ON alarm_comment(alarm_id) CREATE TABLE IF NOT EXISTS user_settings ( user_id uuid NOT NULL CONSTRAINT user_settings_pkey PRIMARY KEY, - settings varchar(10000), + settings varchar(100000), CONSTRAINT fk_user_id FOREIGN KEY (user_id) REFERENCES tb_user(id) ON DELETE CASCADE ); diff --git a/application/src/main/java/org/thingsboard/server/controller/BaseController.java b/application/src/main/java/org/thingsboard/server/controller/BaseController.java index 3f11fc59ac..970100d938 100644 --- a/application/src/main/java/org/thingsboard/server/controller/BaseController.java +++ b/application/src/main/java/org/thingsboard/server/controller/BaseController.java @@ -128,6 +128,7 @@ import org.thingsboard.server.dao.tenant.TbTenantProfileCache; import org.thingsboard.server.dao.tenant.TenantProfileService; import org.thingsboard.server.dao.tenant.TenantService; import org.thingsboard.server.dao.user.UserService; +import org.thingsboard.server.dao.user.UserSettingsService; import org.thingsboard.server.dao.widget.WidgetTypeService; import org.thingsboard.server.dao.widget.WidgetsBundleService; import org.thingsboard.server.exception.ThingsboardErrorResponseHandler; @@ -189,6 +190,9 @@ public abstract class BaseController { @Autowired protected UserService userService; + @Autowired + protected UserSettingsService userSettingsService; + @Autowired protected DeviceService deviceService; diff --git a/application/src/main/java/org/thingsboard/server/controller/UserController.java b/application/src/main/java/org/thingsboard/server/controller/UserController.java index fc95ee2930..ed55bcf324 100644 --- a/application/src/main/java/org/thingsboard/server/controller/UserController.java +++ b/application/src/main/java/org/thingsboard/server/controller/UserController.java @@ -15,6 +15,7 @@ */ package org.thingsboard.server.controller; +import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.node.ObjectNode; import io.swagger.annotations.ApiOperation; import io.swagger.annotations.ApiParam; @@ -27,6 +28,7 @@ import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; @@ -34,6 +36,7 @@ import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.bind.annotation.ResponseStatus; import org.springframework.web.bind.annotation.RestController; +import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.rule.engine.api.MailService; import org.thingsboard.server.common.data.User; import org.thingsboard.server.common.data.exception.ThingsboardErrorCode; @@ -59,6 +62,8 @@ import org.thingsboard.server.service.security.system.SystemSecurityService; import javax.servlet.http.HttpServletRequest; +import java.util.Arrays; + import static org.thingsboard.server.controller.ControllerConstants.CUSTOMER_ID; import static org.thingsboard.server.controller.ControllerConstants.CUSTOMER_ID_PARAM_DESCRIPTION; import static org.thingsboard.server.controller.ControllerConstants.DEFAULT_DASHBOARD; @@ -87,6 +92,7 @@ import static org.thingsboard.server.controller.ControllerConstants.UUID_WIKI_LI public class UserController extends BaseController { public static final String USER_ID = "userId"; + public static final String JSON_PATHS = "jsonPaths"; public static final String YOU_DON_T_HAVE_PERMISSION_TO_PERFORM_THIS_OPERATION = "You don't have permission to perform this operation!"; public static final String ACTIVATE_URL_PATTERN = "%s/api/noauth/activate?activateToken=%s"; @@ -382,45 +388,54 @@ public class UserController extends BaseController { } @ApiOperation(value = "Save user settings (saveUserSettings)", - notes = "Save user settings for specified user id. " ) + notes = "Save user settings represented in json format for authorized user. " ) @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')") - @PostMapping(value = "/user/{userId}/settings") - public UserSettings saveUserSettings(@ApiParam(value = USER_ID_PARAM_DESCRIPTION) - @PathVariable(USER_ID) String strUserId, @RequestBody UserSettings userSettings) throws ThingsboardException { - checkParameter(USER_ID, strUserId); + @PostMapping(value = "/user/settings") + public JsonNode saveUserSettings(@RequestBody JsonNode settings) throws ThingsboardException { + SecurityUser currentUser = getCurrentUser(); - UserId userId = new UserId(toUUID(strUserId)); - User user = checkUserId(userId, Operation.WRITE); + UserSettings userSettings = new UserSettings(); + userSettings.setSettings(settings); + userSettings.setUserId(currentUser.getId()); + return userSettingsService.saveUserSettings(currentUser.getTenantId(), userSettings).getSettings(); + } + + @ApiOperation(value = "Update user settings (saveUserSettings)", + notes = "Update user settings for authorized user. Only specified json elements will be updated." + + "Example: you have such settings: {A:5, B:{C:10, D:5}}. Updating it with {A:10, E:6} will result in" + + "{A:10, B:{C:10, D:5}}, E:6") + @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')") + @PutMapping(value = "/user/settings") + public JsonNode putUserSettings(@RequestBody JsonNode settings) throws ThingsboardException { + SecurityUser currentUser = getCurrentUser(); - userSettings.setUserId(userId); - return userService.saveUserSettings(user.getTenantId(), userId, userSettings); + UserSettings userSettings = new UserSettings(); + userSettings.setSettings(settings); + userSettings.setUserId(currentUser.getId()); + return userSettingsService.updateUserSettings(currentUser.getTenantId(), userSettings).getSettings(); } @ApiOperation(value = "Get user settings (getUserSettings)", notes = "Fetch the User settings based on the provided User Id. " ) @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')") - @GetMapping(value = "/user/{userId}/settings") - public UserSettings getUserSettings(@ApiParam(value = USER_ID_PARAM_DESCRIPTION) - @PathVariable(USER_ID) String strUserId) throws ThingsboardException { - checkParameter(USER_ID, strUserId); - - UserId userId = new UserId(toUUID(strUserId)); - User user = checkUserId(userId, Operation.READ); + @GetMapping(value = "/user/settings") + public JsonNode getUserSettings() throws ThingsboardException { + SecurityUser currentUser = getCurrentUser(); - return checkNotNull(userService.findUserSettings(user.getTenantId(), user.getId()), "No user settingd found"); + UserSettings userSettings = userSettingsService.findUserSettings(currentUser.getTenantId(), currentUser.getId()); + return userSettings == null ? JacksonUtil.newObjectNode(): userSettings.getSettings(); } @ApiOperation(value = "Delete user settings (deleteUserSettings)", - notes = "Delete user settings based on the provided User Id. " ) + notes = "Delete user settings by specifying list of json element xpaths. \n " + + "Example: to delete B and C element in { \"A\": {\"B\": 5}, \"C\": 15} send A.B,C in jsonPaths request parameter" ) @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')") - @RequestMapping(value = "/user/{userId}/settings", method = RequestMethod.DELETE) - public void deleteUserSettings(@ApiParam(value = USER_ID_PARAM_DESCRIPTION) - @PathVariable(USER_ID) String strUserId) throws ThingsboardException { - checkParameter(USER_ID, strUserId); - - UserId userId = new UserId(toUUID(strUserId)); - User user = checkUserId(userId, Operation.WRITE); + @RequestMapping(value = "/user/settings/{jsonPaths}", method = RequestMethod.DELETE) + public void deleteUserSettings( @ApiParam(value = JSON_PATHS) + @PathVariable(JSON_PATHS) String jsonPaths) throws ThingsboardException { + checkParameter(USER_ID, jsonPaths); - userService.deleteUserSettings(user.getTenantId(), userId); + SecurityUser currentUser = getCurrentUser(); + userSettingsService.deleteUserSettings(currentUser.getTenantId(), currentUser.getId(), Arrays.asList(jsonPaths.split(","))); } } diff --git a/application/src/main/resources/thingsboard.yml b/application/src/main/resources/thingsboard.yml index 2fb92cdf4d..5549861950 100644 --- a/application/src/main/resources/thingsboard.yml +++ b/application/src/main/resources/thingsboard.yml @@ -457,6 +457,9 @@ cache: versionControlTask: timeToLiveInMinutes: "${CACHE_SPECS_VERSION_CONTROL_TASK_TTL:5}" maxSize: "${CACHE_SPECS_VERSION_CONTROL_TASK_MAX_SIZE:100000}" + userSettings: + timeToLiveInMinutes: "${CACHE_SPECS_USER_SETTINGS_TTL:1440}" + maxSize: "${CACHE_SPECS_USER_SETTINGS_MAX_SIZE:100000}" #Disable this because it is not required. spring.data.redis.repositories.enabled: false diff --git a/application/src/test/java/org/thingsboard/server/controller/AbstractWebTest.java b/application/src/test/java/org/thingsboard/server/controller/AbstractWebTest.java index 9de2738a8e..24ce820760 100644 --- a/application/src/test/java/org/thingsboard/server/controller/AbstractWebTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/AbstractWebTest.java @@ -103,6 +103,7 @@ import static org.springframework.test.web.servlet.request.MockMvcRequestBuilder import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.request; @@ -613,6 +614,22 @@ public abstract class AbstractWebTest extends AbstractInMemoryStorageTest { return readResponse(doPostAsync(urlTemplate, content, DEFAULT_TIMEOUT, params).andExpect(resultMatcher), responseClass); } + protected T doPut(String urlTemplate, T content, Class responseClass, String... params) { + try { + return readResponse(doPut(urlTemplate, content, params).andExpect(status().isOk()), responseClass); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + protected ResultActions doPut(String urlTemplate, T content, String... params) throws Exception { + MockHttpServletRequestBuilder postRequest = put(urlTemplate, params); + setJwtToken(postRequest); + String json = json(content); + postRequest.contentType(contentType).content(json); + return mockMvc.perform(postRequest); + } + protected T doDelete(String urlTemplate, Class responseClass, String... params) throws Exception { return readResponse(doDelete(urlTemplate, params).andExpect(status().isOk()), responseClass); } diff --git a/application/src/test/java/org/thingsboard/server/controller/BaseUserControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/BaseUserControllerTest.java index e9d516c68b..463c21ef8d 100644 --- a/application/src/test/java/org/thingsboard/server/controller/BaseUserControllerTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/BaseUserControllerTest.java @@ -745,48 +745,44 @@ public abstract class BaseUserControllerTest extends AbstractControllerTest { @Test public void testSaveUserSettings() throws Exception { - loginSysAdmin(); - - User user = createUser(); - User savedUser = doPost("/api/user", user, User.class); + loginCustomerUser(); - UserSettings userSettings = createUserSettings(); - UserSettings savedSettings = doPost("/api/user/" + savedUser.getId() + "/settings", userSettings, UserSettings.class); - Assert.assertEquals(savedSettings.getSettings(), savedSettings.getSettings()); + JsonNode userSettings = mapper.readTree("{\"A\":5, \"B\":10, \"E\":18}"); + JsonNode savedSettings = doPost("/api/user/settings", userSettings, JsonNode.class); + Assert.assertEquals(userSettings, savedSettings); - UserSettings retrievedSettings = doGet("/api/user/" + savedUser.getId() + "/settings", UserSettings.class); - Assert.assertEquals(retrievedSettings.getSettings(), retrievedSettings.getSettings()); - - doDelete("/api/user/" + savedUser.getId() + "/settings"); - doGet("/api/user/" + savedUser.getId() + "/settings").andExpect(status().isNotFound()); - } + JsonNode retrievedSettings = doGet("/api/user/settings", JsonNode.class); + Assert.assertEquals(retrievedSettings, userSettings); + } @Test - public void testShouldNotSaveSettingsForOtherUser() throws Exception { - loginSysAdmin(); + public void testUpdateUserSettings() throws Exception { + loginCustomerUser(); - User user = createUser(); - User savedUser = doPost("/api/user", user, User.class); + JsonNode userSettings = mapper.readTree("{\"A\":5, \"B\":10, \"E\":18}"); + JsonNode savedSettings = doPost("/api/user/settings", userSettings, JsonNode.class); + Assert.assertEquals(userSettings, savedSettings); - loginCustomerUser(); - UserSettings userSettings = createUserSettings(); - doPost("/api/user/" + savedUser.getId() + "/settings", userSettings) - .andExpect(status().isForbidden()); + JsonNode newSettings = mapper.readTree("{\"A\":10, \"B\":10, \"C\":{\"D\": 16}}"); + JsonNode updatedSettings = doPut("/api/user/settings", newSettings, JsonNode.class); + + JsonNode expectedSettings = mapper.readTree("{\"A\":10, \"B\":10, \"C\":{\"D\": 16}, \"E\":18}"); + Assert.assertEquals(expectedSettings, updatedSettings); } + @Test - public void testShouldDeleteSettingsAfterUserDeletion() throws Exception { - loginSysAdmin(); + public void testDeleteUserSettings() throws Exception { + loginCustomerUser(); - User user = createUser(); - User savedUser = doPost("/api/user", user, User.class); + JsonNode userSettings = mapper.readTree("{\"A\":10, \"B\":10, \"C\":{\"D\": 16}}"); + JsonNode savedSettings = doPost("/api/user/settings", userSettings, JsonNode.class); + Assert.assertEquals(userSettings, savedSettings); - UserSettings userSettings = createUserSettings(); - UserSettings savedSettings = doPost("/api/user/" + savedUser.getId() + "/settings", userSettings, UserSettings.class); - Assert.assertEquals(savedSettings.getSettings(), savedSettings.getSettings()); + doDelete("/api/user/settings/C.D,B"); - doDelete("/api/user/" + savedUser.getId()) - .andExpect(status().isOk()); - doGet("/api/user/" + savedUser.getId() + "/settings").andExpect(status().isNotFound()); + JsonNode retrievedSettings = doGet("/api/user/settings", JsonNode.class); + JsonNode expectedSettings = mapper.readTree("{\"A\":10, \"C\":{}}"); + Assert.assertEquals(expectedSettings, retrievedSettings); } private User createUser() throws Exception { @@ -800,10 +796,4 @@ public abstract class BaseUserControllerTest extends AbstractControllerTest { user.setLastName("Downs"); return doPost("/api/user", user, User.class); } - - private UserSettings createUserSettings() { - UserSettings userSettings = new UserSettings(); - userSettings.setSettings(JacksonUtil.newObjectNode().put("text", StringUtils.randomAlphanumeric(10))); - return userSettings; - } } diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/user/UserService.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/user/UserService.java index c6bfbf05b1..bb40507772 100644 --- a/common/dao-api/src/main/java/org/thingsboard/server/dao/user/UserService.java +++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/user/UserService.java @@ -25,7 +25,6 @@ import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.security.UserCredentials; import org.thingsboard.server.dao.entity.EntityDaoService; -import org.thingsboard.server.common.data.security.UserSettings; public interface UserService extends EntityDaoService { @@ -75,10 +74,4 @@ public interface UserService extends EntityDaoService { void setLastLoginTs(TenantId tenantId, UserId userId); - UserSettings saveUserSettings(TenantId tenantId, UserId userId, UserSettings userSettings); - - UserSettings findUserSettings(TenantId tenantId, UserId userId); - - void deleteUserSettings(TenantId tenantId, UserId userId); - } diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/user/UserSettingsService.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/user/UserSettingsService.java new file mode 100644 index 0000000000..5ed1eab92a --- /dev/null +++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/user/UserSettingsService.java @@ -0,0 +1,43 @@ +/** + * Copyright © 2016-2023 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.dao.user; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.google.common.util.concurrent.ListenableFuture; +import org.thingsboard.server.common.data.User; +import org.thingsboard.server.common.data.id.CustomerId; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.id.UserCredentialsId; +import org.thingsboard.server.common.data.id.UserId; +import org.thingsboard.server.common.data.page.PageData; +import org.thingsboard.server.common.data.page.PageLink; +import org.thingsboard.server.common.data.security.UserCredentials; +import org.thingsboard.server.common.data.security.UserSettings; +import org.thingsboard.server.dao.entity.EntityDaoService; + +import java.util.List; + +public interface UserSettingsService { + + UserSettings updateUserSettings(TenantId tenantId, UserSettings userSettings); + + UserSettings saveUserSettings(TenantId tenantId, UserSettings userSettings); + + UserSettings findUserSettings(TenantId tenantId, UserId userId); + + void deleteUserSettings(TenantId tenantId, UserId userId, List jsonPaths); + +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/CacheConstants.java b/common/data/src/main/java/org/thingsboard/server/common/data/CacheConstants.java index 7cfccb2c3c..c9e6e0aa46 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/CacheConstants.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/CacheConstants.java @@ -39,4 +39,5 @@ public class CacheConstants { public static final String AUTO_COMMIT_SETTINGS_CACHE = "autoCommitSettings"; public static final String TWO_FA_VERIFICATION_CODES_CACHE = "twoFaVerificationCodes"; public static final String VERSION_CONTROL_TASK_CACHE = "versionControlTask"; + public static final String USER_SETTINGS_CACHE = "userSettings"; } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/security/UserSettings.java b/common/data/src/main/java/org/thingsboard/server/common/data/security/UserSettings.java index 49d5903049..5a7fb19ffa 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/security/UserSettings.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/security/UserSettings.java @@ -15,6 +15,8 @@ */ package org.thingsboard.server.common.data.security; +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.JsonNode; import io.swagger.annotations.ApiModel; import io.swagger.annotations.ApiModelProperty; @@ -23,7 +25,14 @@ import org.thingsboard.server.common.data.id.UserId; import org.thingsboard.server.common.data.validation.Length; import org.thingsboard.server.common.data.validation.NoXss; +import java.io.ByteArrayInputStream; +import java.io.IOException; import java.io.Serializable; +import java.util.function.Consumer; +import java.util.function.Supplier; + +import static org.thingsboard.server.common.data.SearchTextBasedWithAdditionalInfo.getJson; +import static org.thingsboard.server.common.data.SearchTextBasedWithAdditionalInfo.setJson; @ApiModel @Data @@ -36,7 +45,17 @@ public class UserSettings implements Serializable { @ApiModelProperty(position = 2, value = "JSON object with user settings.", dataType = "com.fasterxml.jackson.databind.JsonNode") @NoXss - @Length(fieldName = "settings", max = 10000) + @Length(fieldName = "settings", max = 100000) private transient JsonNode settings; + @JsonIgnore + private byte[] settingsBytes; + + public JsonNode getSettings() { + return getJson(() -> settings, () -> settingsBytes); + } + + public void setSettings(JsonNode settings) { + setJson(settings, json -> this.settings = json, bytes -> this.settingsBytes = bytes); + } } diff --git a/dao/pom.xml b/dao/pom.xml index 901d967817..30dc1adcf0 100644 --- a/dao/pom.xml +++ b/dao/pom.xml @@ -233,6 +233,10 @@ org.eclipse.leshan leshan-core + + com.jayway.jsonpath + json-path + diff --git a/dao/src/main/java/org/thingsboard/server/dao/entity/AbstractCachedService.java b/dao/src/main/java/org/thingsboard/server/dao/entity/AbstractCachedService.java new file mode 100644 index 0000000000..d48acb9fc4 --- /dev/null +++ b/dao/src/main/java/org/thingsboard/server/dao/entity/AbstractCachedService.java @@ -0,0 +1,43 @@ +/** + * Copyright © 2016-2023 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.dao.entity; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.ApplicationEventPublisher; +import org.springframework.transaction.support.TransactionSynchronizationManager; +import org.thingsboard.server.cache.TbTransactionalCache; + +import java.io.Serializable; + +public abstract class AbstractCachedService { + + @Autowired + protected TbTransactionalCache cache; + + @Autowired + private ApplicationEventPublisher eventPublisher; + + protected void publishEvictEvent(E event) { + if (TransactionSynchronizationManager.isActualTransactionActive()) { + eventPublisher.publishEvent(event); + } else { + handleEvictEvent(event); + } + } + + public abstract void handleEvictEvent(E event); + +} diff --git a/dao/src/main/java/org/thingsboard/server/dao/user/UserServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/user/UserServiceImpl.java index e75bf3521c..c8c5175e5f 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/user/UserServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/user/UserServiceImpl.java @@ -328,27 +328,6 @@ public class UserServiceImpl extends AbstractEntityService implements UserServic return failedLoginAttempts; } - @Override - public UserSettings saveUserSettings(TenantId tenantId, UserId userId, UserSettings userSettings) { - log.trace("Executing saveUserSettings for user [{}], [{}]", userId, userSettings); - validateId(userId, INCORRECT_USER_ID + userId); - return userSettingsDao.save(tenantId, userSettings); - } - - @Override - public UserSettings findUserSettings(TenantId tenantId, UserId userId) { - log.trace("Executing findUserSettings for user [{}]", userId); - validateId(userId, INCORRECT_USER_ID + userId); - return userSettingsDao.findById(tenantId, userId); - } - - @Override - public void deleteUserSettings(TenantId tenantId, UserId userId) { - log.trace("Executing deleteUserSettings for user [{}]", userId); - validateId(userId, INCORRECT_USER_ID + userId); - userSettingsDao.removeById(tenantId, userId); - } - private int increaseFailedLoginAttempts(User user) { JsonNode additionalInfo = user.getAdditionalInfo(); if (!(additionalInfo instanceof ObjectNode)) { diff --git a/dao/src/main/java/org/thingsboard/server/dao/user/UserSettingsCaffeineCache.java b/dao/src/main/java/org/thingsboard/server/dao/user/UserSettingsCaffeineCache.java new file mode 100644 index 0000000000..3154e5d8c0 --- /dev/null +++ b/dao/src/main/java/org/thingsboard/server/dao/user/UserSettingsCaffeineCache.java @@ -0,0 +1,36 @@ +/** + * Copyright © 2016-2023 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.dao.user; + +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.cache.CacheManager; +import org.springframework.stereotype.Service; +import org.thingsboard.server.cache.CaffeineTbTransactionalCache; +import org.thingsboard.server.common.data.CacheConstants; +import org.thingsboard.server.common.data.asset.Asset; +import org.thingsboard.server.common.data.id.UserId; +import org.thingsboard.server.common.data.security.UserSettings; +import org.thingsboard.server.dao.asset.AssetCacheKey; + +@ConditionalOnProperty(prefix = "cache", value = "type", havingValue = "caffeine", matchIfMissing = true) +@Service("UserSettingsCache") +public class UserSettingsCaffeineCache extends CaffeineTbTransactionalCache { + + public UserSettingsCaffeineCache(CacheManager cacheManager) { + super(cacheManager, CacheConstants.USER_SETTINGS_CACHE); + } + +} diff --git a/dao/src/main/java/org/thingsboard/server/dao/user/UserSettingsEvictEvent.java b/dao/src/main/java/org/thingsboard/server/dao/user/UserSettingsEvictEvent.java new file mode 100644 index 0000000000..e0d2de7ece --- /dev/null +++ b/dao/src/main/java/org/thingsboard/server/dao/user/UserSettingsEvictEvent.java @@ -0,0 +1,24 @@ +/** + * Copyright © 2016-2023 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.dao.user; + +import lombok.Data; +import org.thingsboard.server.common.data.id.UserId; + +@Data +public class UserSettingsEvictEvent { + private final UserId userId; +} diff --git a/dao/src/main/java/org/thingsboard/server/dao/user/UserSettingsRedisCache.java b/dao/src/main/java/org/thingsboard/server/dao/user/UserSettingsRedisCache.java new file mode 100644 index 0000000000..a510e6c864 --- /dev/null +++ b/dao/src/main/java/org/thingsboard/server/dao/user/UserSettingsRedisCache.java @@ -0,0 +1,38 @@ +/** + * Copyright © 2016-2023 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.dao.user; + +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.data.redis.connection.RedisConnectionFactory; +import org.springframework.stereotype.Service; +import org.thingsboard.server.cache.CacheSpecsMap; +import org.thingsboard.server.cache.RedisTbTransactionalCache; +import org.thingsboard.server.cache.TBRedisCacheConfiguration; +import org.thingsboard.server.cache.TbFSTRedisSerializer; +import org.thingsboard.server.common.data.CacheConstants; +import org.thingsboard.server.common.data.asset.Asset; +import org.thingsboard.server.common.data.id.UserId; +import org.thingsboard.server.common.data.security.UserSettings; +import org.thingsboard.server.dao.asset.AssetCacheKey; + +@ConditionalOnProperty(prefix = "cache", value = "type", havingValue = "redis") +@Service("UserSettingsCache") +public class UserSettingsRedisCache extends RedisTbTransactionalCache { + + public UserSettingsRedisCache(TBRedisCacheConfiguration configuration, CacheSpecsMap cacheSpecsMap, RedisConnectionFactory connectionFactory) { + super(CacheConstants.USER_SETTINGS_CACHE, cacheSpecsMap, connectionFactory, configuration, new TbFSTRedisSerializer<>()); + } +} diff --git a/dao/src/main/java/org/thingsboard/server/dao/user/UserSettingsServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/user/UserSettingsServiceImpl.java new file mode 100644 index 0000000000..a5f9d2f79f --- /dev/null +++ b/dao/src/main/java/org/thingsboard/server/dao/user/UserSettingsServiceImpl.java @@ -0,0 +1,131 @@ +/** + * Copyright © 2016-2023 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.dao.user; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ObjectNode; +import com.jayway.jsonpath.JsonPath; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.springframework.transaction.event.TransactionalEventListener; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.id.UserId; +import org.thingsboard.server.common.data.security.UserSettings; +import org.thingsboard.server.dao.entity.AbstractCachedService; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +import static org.thingsboard.server.dao.service.Validator.validateId; + +@Service("UserSettingsDaoService") +@Slf4j +@RequiredArgsConstructor +public class UserSettingsServiceImpl extends AbstractCachedService implements UserSettingsService { + public static final String INCORRECT_USER_ID = "Incorrect userId "; + private final UserSettingsDao userSettingsDao; + + @Override + public UserSettings saveUserSettings(TenantId tenantId, UserSettings userSettings) { + log.trace("Executing saveUserSettings for user [{}], [{}]", userSettings.getUserId(), userSettings); + validateId(userSettings.getUserId(), INCORRECT_USER_ID + userSettings.getUserId()); + try { + UserSettings saved = userSettingsDao.save(tenantId, userSettings); + publishEvictEvent(new UserSettingsEvictEvent(userSettings.getUserId())); + return saved; + } catch (Exception t) { + handleEvictEvent(new UserSettingsEvictEvent(userSettings.getUserId())); + throw t; + } + } + + @Override + public UserSettings updateUserSettings(TenantId tenantId, UserSettings userSettings) { + log.trace("Executing updateUserSettings for user [{}], [{}]", userSettings.getUserId(), userSettings); + validateId(userSettings.getUserId(), INCORRECT_USER_ID + userSettings.getUserId()); + UserSettings oldSettings = userSettingsDao.findById(tenantId, userSettings.getUserId()); + userSettings.setSettings(merge(oldSettings.getSettings(), userSettings.getSettings())); + + try { + UserSettings saved = userSettingsDao.save(tenantId, userSettings); + publishEvictEvent(new UserSettingsEvictEvent(userSettings.getUserId())); + return saved; + } catch (Exception t) { + handleEvictEvent(new UserSettingsEvictEvent(userSettings.getUserId())); + throw t; + } + } + + @Override + public UserSettings findUserSettings(TenantId tenantId, UserId userId) { + log.trace("Executing findUserSettings for user [{}]", userId); + validateId(userId, INCORRECT_USER_ID + userId); + + return cache.getAndPutInTransaction(userId, + () -> userSettingsDao.findById(tenantId, userId), true); + } + + @Override + public void deleteUserSettings(TenantId tenantId, UserId userId, List jsonPaths) { + log.trace("Executing deleteUserSettings for user [{}]", userId); + validateId(userId, INCORRECT_USER_ID + userId); + UserSettings userSettings = userSettingsDao.findById(tenantId, userId); + ObjectNode settings = (ObjectNode) userSettings.getSettings(); + + try { + for (String s : jsonPaths) { + settings = new ObjectMapper().readValue(JsonPath.parse(settings.toString()).delete("$." + s).jsonString(), ObjectNode.class); + userSettings.setSettings(settings); + } + userSettingsDao.save(tenantId, userSettings); + publishEvictEvent(new UserSettingsEvictEvent(userSettings.getUserId())); + } catch (Exception t) { + handleEvictEvent(new UserSettingsEvictEvent(userSettings.getUserId())); + throw new RuntimeException(t); + } + } + + @TransactionalEventListener(classes = UserSettingsEvictEvent.class) + @Override + public void handleEvictEvent(UserSettingsEvictEvent event) { + List keys = new ArrayList<>(); + keys.add(event.getUserId()); + cache.evict(keys); + } + + public JsonNode merge(JsonNode mainNode, JsonNode updateNode) { + + Iterator fieldNames = updateNode.fieldNames(); + while (fieldNames.hasNext()) { + String fieldName = fieldNames.next(); + JsonNode jsonNode = mainNode.get(fieldName); + if (jsonNode != null && jsonNode.isObject()) { + merge(jsonNode, updateNode.get(fieldName)); + } + else { + if (mainNode instanceof ObjectNode) { + JsonNode value = updateNode.get(fieldName); + ((ObjectNode) mainNode).set(fieldName, value); + } + } + } + return mainNode; + } + +} diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/BaseUserServiceTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/BaseUserServiceTest.java index 79f78ed10e..d7885a1cb2 100644 --- a/dao/src/test/java/org/thingsboard/server/dao/service/BaseUserServiceTest.java +++ b/dao/src/test/java/org/thingsboard/server/dao/service/BaseUserServiceTest.java @@ -72,7 +72,6 @@ public abstract class BaseUserServiceTest extends AbstractServiceTest { customerUser = userService.saveUser(customerUser); userSettings = createUserSettings(customerUser.getId()); - userSettings = userService.saveUserSettings(TenantId.SYS_TENANT_ID, customerUser.getId(), userSettings); } @After @@ -112,14 +111,6 @@ public abstract class BaseUserServiceTest extends AbstractServiceTest { Assert.assertNotNull(userCredentials); } - @Test - public void testFindUserSettings() { - User user = userService.findUserByEmail(SYSTEM_TENANT_ID,"customer@thingsboard.org"); - Assert.assertNotNull(user); - UserSettings userSettings = userService.findUserSettings(SYSTEM_TENANT_ID, user.getId()); - Assert.assertEquals(userSettings.getSettings(), userSettings.getSettings()); - } - @Test public void testSaveUser() { User tenantAdminUser = userService.findUserByEmail(SYSTEM_TENANT_ID,"tenant@thingsboard.org"); From 565234f7a126e94de29362c6fdb4bf385dd57b3f Mon Sep 17 00:00:00 2001 From: dashevchenko Date: Fri, 10 Feb 2023 17:41:21 +0200 Subject: [PATCH 9/9] refactoring --- .../server/dao/user/UserSettingsServiceImpl.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/dao/src/main/java/org/thingsboard/server/dao/user/UserSettingsServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/user/UserSettingsServiceImpl.java index a5f9d2f79f..88087d28b1 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/user/UserSettingsServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/user/UserSettingsServiceImpl.java @@ -18,6 +18,7 @@ package org.thingsboard.server.dao.user; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.node.ObjectNode; +import com.jayway.jsonpath.DocumentContext; import com.jayway.jsonpath.JsonPath; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -86,13 +87,12 @@ public class UserSettingsServiceImpl extends AbstractCachedService