Support for multiple mobile sessions
This commit is contained in:
parent
e0d37c31dc
commit
f44c05f285
@ -33,5 +33,3 @@ $$
|
||||
END IF;
|
||||
END;
|
||||
$$;
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_user_settings_mobile_fcm_token ON user_settings ((settings ->> 'fcmToken')) WHERE type = 'MOBILE';
|
||||
|
||||
@ -26,11 +26,13 @@ import org.springframework.context.ApplicationEventPublisher;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.security.core.annotation.AuthenticationPrincipal;
|
||||
import org.springframework.web.bind.annotation.DeleteMapping;
|
||||
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.RequestHeader;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMethod;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
@ -42,7 +44,6 @@ import org.thingsboard.rule.engine.api.MailService;
|
||||
import org.thingsboard.server.common.data.EntityType;
|
||||
import org.thingsboard.server.common.data.User;
|
||||
import org.thingsboard.server.common.data.UserEmailInfo;
|
||||
import org.thingsboard.server.common.data.UserMobileInfo;
|
||||
import org.thingsboard.server.common.data.alarm.Alarm;
|
||||
import org.thingsboard.server.common.data.exception.ThingsboardErrorCode;
|
||||
import org.thingsboard.server.common.data.exception.ThingsboardException;
|
||||
@ -51,6 +52,7 @@ import org.thingsboard.server.common.data.id.CustomerId;
|
||||
import org.thingsboard.server.common.data.id.DashboardId;
|
||||
import org.thingsboard.server.common.data.id.TenantId;
|
||||
import org.thingsboard.server.common.data.id.UserId;
|
||||
import org.thingsboard.server.common.data.mobile.MobileSessionInfo;
|
||||
import org.thingsboard.server.common.data.page.PageData;
|
||||
import org.thingsboard.server.common.data.page.PageLink;
|
||||
import org.thingsboard.server.common.data.query.EntityDataPageLink;
|
||||
@ -83,7 +85,6 @@ import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
|
||||
import static org.thingsboard.server.common.data.query.EntityKeyType.ENTITY_FIELD;
|
||||
import static org.thingsboard.server.controller.ControllerConstants.ALARM_ID_PARAM_DESCRIPTION;
|
||||
@ -120,6 +121,7 @@ public class UserController extends BaseController {
|
||||
public static final String PATHS = "paths";
|
||||
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";
|
||||
public static final String MOBILE_TOKEN_HEADER = "X-Mobile-Token";
|
||||
|
||||
@Value("${security.user_token_access_enabled}")
|
||||
private boolean userTokenAccessEnabled;
|
||||
@ -588,17 +590,25 @@ public class UserController extends BaseController {
|
||||
}
|
||||
|
||||
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')")
|
||||
@GetMapping("/user/mobile/info")
|
||||
public UserMobileInfo getMobileInfo(@AuthenticationPrincipal SecurityUser securityUser) {
|
||||
return Optional.ofNullable(userService.findMobileInfo(securityUser.getTenantId(), securityUser.getId()))
|
||||
.orElseGet(UserMobileInfo::new);
|
||||
@GetMapping("/user/mobile/session")
|
||||
public MobileSessionInfo getMobileSession(@RequestHeader(MOBILE_TOKEN_HEADER) String mobileToken,
|
||||
@AuthenticationPrincipal SecurityUser user) {
|
||||
return userService.findMobileSession(user.getTenantId(), user.getId(), mobileToken);
|
||||
}
|
||||
|
||||
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')")
|
||||
@PostMapping("/user/mobile/info")
|
||||
public void saveMobileInfo(@RequestBody UserMobileInfo mobileInfo,
|
||||
@AuthenticationPrincipal SecurityUser securityUser) {
|
||||
userService.saveMobileInfo(securityUser.getTenantId(), securityUser.getId(), mobileInfo);
|
||||
@PostMapping("/user/mobile/session")
|
||||
public void saveMobileSession(@RequestBody MobileSessionInfo sessionInfo,
|
||||
@RequestHeader(MOBILE_TOKEN_HEADER) String mobileToken,
|
||||
@AuthenticationPrincipal SecurityUser user) {
|
||||
userService.saveMobileSession(user.getTenantId(), user.getId(), mobileToken, sessionInfo);
|
||||
}
|
||||
|
||||
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')")
|
||||
@DeleteMapping("/user/mobile/session")
|
||||
public void removeMobileSession(@RequestHeader(MOBILE_TOKEN_HEADER) String mobileToken,
|
||||
@AuthenticationPrincipal SecurityUser user) {
|
||||
userService.removeMobileSession(user.getTenantId(), mobileToken);
|
||||
}
|
||||
|
||||
private void checkNotReserved(String strType, UserSettingsType type) throws ThingsboardException {
|
||||
|
||||
@ -18,10 +18,10 @@ package org.thingsboard.server.service.notification.channels;
|
||||
import com.google.firebase.messaging.FirebaseMessagingException;
|
||||
import com.google.firebase.messaging.MessagingErrorCode;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.thingsboard.rule.engine.api.notification.FirebaseService;
|
||||
import org.thingsboard.server.common.data.User;
|
||||
import org.thingsboard.server.common.data.UserMobileInfo;
|
||||
import org.thingsboard.server.common.data.id.TenantId;
|
||||
import org.thingsboard.server.common.data.notification.NotificationDeliveryMethod;
|
||||
import org.thingsboard.server.common.data.notification.settings.MobileAppNotificationDeliveryMethodConfig;
|
||||
@ -31,10 +31,12 @@ import org.thingsboard.server.dao.notification.NotificationSettingsService;
|
||||
import org.thingsboard.server.dao.user.UserService;
|
||||
import org.thingsboard.server.service.notification.NotificationProcessingContext;
|
||||
|
||||
import java.util.Optional;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
@Slf4j
|
||||
public class MobileAppNotificationChannel implements NotificationChannel<User, MobileAppDeliveryMethodNotificationTemplate> {
|
||||
|
||||
private final FirebaseService firebaseService;
|
||||
@ -43,24 +45,29 @@ public class MobileAppNotificationChannel implements NotificationChannel<User, M
|
||||
|
||||
@Override
|
||||
public void sendNotification(User recipient, MobileAppDeliveryMethodNotificationTemplate processedTemplate, NotificationProcessingContext ctx) throws Exception {
|
||||
UserMobileInfo mobileInfo = userService.findMobileInfo(recipient.getTenantId(), recipient.getId());
|
||||
String fcmToken = Optional.ofNullable(mobileInfo)
|
||||
.map(UserMobileInfo::getFcmToken)
|
||||
.orElseThrow(() -> new IllegalArgumentException("User doesn't use the mobile app"));
|
||||
var mobileSessions = userService.findMobileSessions(recipient.getTenantId(), recipient.getId());
|
||||
if (mobileSessions.isEmpty()) {
|
||||
throw new IllegalArgumentException("User doesn't use the mobile app");
|
||||
}
|
||||
|
||||
MobileAppNotificationDeliveryMethodConfig config = ctx.getDeliveryMethodConfig(NotificationDeliveryMethod.MOBILE_APP);
|
||||
try {
|
||||
firebaseService.sendMessage(ctx.getTenantId(), config.getFirebaseServiceAccountCredentials(),
|
||||
fcmToken, processedTemplate.getSubject(), processedTemplate.getBody());
|
||||
} catch (FirebaseMessagingException e) {
|
||||
MessagingErrorCode errorCode = e.getMessagingErrorCode();
|
||||
if (errorCode == MessagingErrorCode.UNREGISTERED || errorCode == MessagingErrorCode.INVALID_ARGUMENT) {
|
||||
// the token is no longer valid
|
||||
mobileInfo.setFcmToken(null);
|
||||
userService.saveMobileInfo(recipient.getTenantId(), recipient.getId(), mobileInfo);
|
||||
throw new IllegalArgumentException("User doesn't use the mobile app");
|
||||
String credentials = config.getFirebaseServiceAccountCredentials();
|
||||
Set<String> validTokens = new HashSet<>(mobileSessions.keySet());
|
||||
for (String token : mobileSessions.keySet()) {
|
||||
try {
|
||||
firebaseService.sendMessage(ctx.getTenantId(), credentials, token, processedTemplate.getSubject(), processedTemplate.getBody());
|
||||
} catch (FirebaseMessagingException e) {
|
||||
MessagingErrorCode errorCode = e.getMessagingErrorCode();
|
||||
if (errorCode == MessagingErrorCode.UNREGISTERED || errorCode == MessagingErrorCode.INVALID_ARGUMENT) {
|
||||
validTokens.remove(token);
|
||||
userService.removeMobileSession(recipient.getTenantId(), token);
|
||||
continue;
|
||||
}
|
||||
throw new RuntimeException("Failed to send message via FCM: " + e.getMessage(), e);
|
||||
}
|
||||
throw new RuntimeException("Failed to send message via FCM: " + e.getMessage(), e);
|
||||
}
|
||||
if (validTokens.isEmpty()) {
|
||||
throw new IllegalArgumentException("User doesn't use the mobile app");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -189,6 +189,7 @@ public abstract class AbstractWebTest extends AbstractInMemoryStorageTest {
|
||||
|
||||
protected String token;
|
||||
protected String refreshToken;
|
||||
protected String mobileToken;
|
||||
protected String username;
|
||||
|
||||
protected TenantId tenantId;
|
||||
@ -573,6 +574,9 @@ public abstract class AbstractWebTest extends AbstractInMemoryStorageTest {
|
||||
if (this.token != null) {
|
||||
request.header(ThingsboardSecurityConfiguration.JWT_TOKEN_HEADER_PARAM, "Bearer " + this.token);
|
||||
}
|
||||
if (this.mobileToken != null) {
|
||||
request.header(UserController.MOBILE_TOKEN_HEADER, this.mobileToken);
|
||||
}
|
||||
}
|
||||
|
||||
protected DeviceProfile createDeviceProfile(String name) {
|
||||
|
||||
@ -31,13 +31,13 @@ import org.thingsboard.rule.engine.api.NotificationCenter;
|
||||
import org.thingsboard.rule.engine.api.notification.FirebaseService;
|
||||
import org.thingsboard.server.common.data.EntityType;
|
||||
import org.thingsboard.server.common.data.User;
|
||||
import org.thingsboard.server.common.data.UserMobileInfo;
|
||||
import org.thingsboard.server.common.data.audit.ActionType;
|
||||
import org.thingsboard.server.common.data.id.DeviceId;
|
||||
import org.thingsboard.server.common.data.id.NotificationRequestId;
|
||||
import org.thingsboard.server.common.data.id.NotificationRuleId;
|
||||
import org.thingsboard.server.common.data.id.NotificationTargetId;
|
||||
import org.thingsboard.server.common.data.id.TenantId;
|
||||
import org.thingsboard.server.common.data.mobile.MobileSessionInfo;
|
||||
import org.thingsboard.server.common.data.notification.Notification;
|
||||
import org.thingsboard.server.common.data.notification.NotificationDeliveryMethod;
|
||||
import org.thingsboard.server.common.data.notification.NotificationRequest;
|
||||
@ -93,6 +93,7 @@ import static org.assertj.core.api.InstanceOfAssertFactories.type;
|
||||
import static org.awaitility.Awaitility.await;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.clearInvocations;
|
||||
import static org.mockito.Mockito.doThrow;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.timeout;
|
||||
@ -109,8 +110,6 @@ public class NotificationApiTest extends AbstractNotificationApiTest {
|
||||
@Autowired
|
||||
private NotificationDao notificationDao;
|
||||
@Autowired
|
||||
private DbCallbackExecutorService executor;
|
||||
@Autowired
|
||||
private MicrosoftTeamsNotificationChannel microsoftTeamsNotificationChannel;
|
||||
@MockBean
|
||||
private FirebaseService firebaseService;
|
||||
@ -726,14 +725,14 @@ public class NotificationApiTest extends AbstractNotificationApiTest {
|
||||
saveNotificationSettings(config);
|
||||
|
||||
loginCustomerUser();
|
||||
UserMobileInfo customerMobileInfo = new UserMobileInfo();
|
||||
customerMobileInfo.setFcmToken("customerFcmToken");
|
||||
doPost("/api/user/mobile/info", customerMobileInfo).andExpect(status().isOk());
|
||||
mobileToken = "customerFcmToken";
|
||||
doPost("/api/user/mobile/session", new MobileSessionInfo()).andExpect(status().isOk());
|
||||
|
||||
loginTenantAdmin();
|
||||
UserMobileInfo tenantMobileInfo = new UserMobileInfo();
|
||||
tenantMobileInfo.setFcmToken("tenantFcmToken");
|
||||
doPost("/api/user/mobile/info", tenantMobileInfo).andExpect(status().isOk());
|
||||
mobileToken = "tenantFcmToken1";
|
||||
doPost("/api/user/mobile/session", new MobileSessionInfo()).andExpect(status().isOk());
|
||||
mobileToken = "tenantFcmToken2";
|
||||
doPost("/api/user/mobile/session", new MobileSessionInfo()).andExpect(status().isOk());
|
||||
|
||||
loginDifferentCustomer(); // with no mobile info
|
||||
|
||||
@ -748,7 +747,19 @@ public class NotificationApiTest extends AbstractNotificationApiTest {
|
||||
.contains("doesn't use the mobile app");
|
||||
|
||||
verify(firebaseService).sendMessage(eq(tenantId), eq("testCredentials"),
|
||||
eq("tenantFcmToken"), eq("Title"), eq("Message"));
|
||||
eq("tenantFcmToken1"), eq("Title"), eq("Message"));
|
||||
verify(firebaseService).sendMessage(eq(tenantId), eq("testCredentials"),
|
||||
eq("tenantFcmToken2"), eq("Title"), eq("Message"));
|
||||
verify(firebaseService).sendMessage(eq(tenantId), eq("testCredentials"),
|
||||
eq("customerFcmToken"), eq("Title"), eq("Message"));
|
||||
verifyNoMoreInteractions(firebaseService);
|
||||
clearInvocations(firebaseService);
|
||||
|
||||
doDelete("/api/user/mobile/session").andExpect(status().isOk());
|
||||
request = submitNotificationRequest(List.of(target.getId()), template.getId(), 0);
|
||||
awaitNotificationRequest(request.getId());
|
||||
verify(firebaseService).sendMessage(eq(tenantId), eq("testCredentials"),
|
||||
eq("tenantFcmToken1"), eq("Title"), eq("Message"));
|
||||
verify(firebaseService).sendMessage(eq(tenantId), eq("testCredentials"),
|
||||
eq("customerFcmToken"), eq("Title"), eq("Message"));
|
||||
verifyNoMoreInteractions(firebaseService);
|
||||
|
||||
@ -17,7 +17,7 @@ package org.thingsboard.server.dao.user;
|
||||
|
||||
import com.google.common.util.concurrent.ListenableFuture;
|
||||
import org.thingsboard.server.common.data.User;
|
||||
import org.thingsboard.server.common.data.UserMobileInfo;
|
||||
import org.thingsboard.server.common.data.mobile.MobileSessionInfo;
|
||||
import org.thingsboard.server.common.data.id.CustomerId;
|
||||
import org.thingsboard.server.common.data.id.TenantId;
|
||||
import org.thingsboard.server.common.data.id.TenantProfileId;
|
||||
@ -29,6 +29,7 @@ import org.thingsboard.server.common.data.security.UserCredentials;
|
||||
import org.thingsboard.server.dao.entity.EntityDaoService;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public interface UserService extends EntityDaoService {
|
||||
|
||||
@ -90,8 +91,12 @@ public interface UserService extends EntityDaoService {
|
||||
|
||||
void setLastLoginTs(TenantId tenantId, UserId userId);
|
||||
|
||||
void saveMobileInfo(TenantId tenantId, UserId userId, UserMobileInfo mobileInfo);
|
||||
void saveMobileSession(TenantId tenantId, UserId userId, String mobileToken, MobileSessionInfo sessionInfo);
|
||||
|
||||
UserMobileInfo findMobileInfo(TenantId tenantId, UserId userId);
|
||||
Map<String, MobileSessionInfo> findMobileSessions(TenantId tenantId, UserId userId);
|
||||
|
||||
MobileSessionInfo findMobileSession(TenantId tenantId, UserId userId, String mobileToken);
|
||||
|
||||
void removeMobileSession(TenantId tenantId, String mobileToken);
|
||||
|
||||
}
|
||||
|
||||
@ -0,0 +1,23 @@
|
||||
/**
|
||||
* Copyright © 2016-2024 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.mobile;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class MobileSessionInfo {
|
||||
private long fcmTokenTimestamp;
|
||||
}
|
||||
@ -13,12 +13,15 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.thingsboard.server.common.data;
|
||||
package org.thingsboard.server.common.data.mobile;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
@Data
|
||||
public class UserMobileInfo {
|
||||
private String fcmToken;
|
||||
private long fcmTokenTimestamp;
|
||||
|
||||
private Map<String, MobileSessionInfo> sessions;
|
||||
|
||||
}
|
||||
@ -33,7 +33,6 @@ import org.thingsboard.server.dao.user.UserDao;
|
||||
import org.thingsboard.server.dao.util.SqlDao;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.UUID;
|
||||
|
||||
import static org.thingsboard.server.dao.model.ModelConstants.NULL_UUID;
|
||||
@ -135,11 +134,6 @@ public class JpaUserDao extends JpaAbstractDao<UserEntity, User> implements User
|
||||
DaoUtil.toPageable(pageLink)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unassignFcmToken(TenantId tenantId, String fcmToken) {
|
||||
userRepository.unassignFcmToken(fcmToken);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Long countByTenantId(TenantId tenantId) {
|
||||
return userRepository.countByTenantId(tenantId.getId());
|
||||
|
||||
@ -21,12 +21,15 @@ import org.springframework.stereotype.Component;
|
||||
import org.thingsboard.server.common.data.id.TenantId;
|
||||
import org.thingsboard.server.common.data.settings.UserSettings;
|
||||
import org.thingsboard.server.common.data.settings.UserSettingsCompositeKey;
|
||||
import org.thingsboard.server.common.data.settings.UserSettingsType;
|
||||
import org.thingsboard.server.dao.DaoUtil;
|
||||
import org.thingsboard.server.dao.model.sql.UserSettingsEntity;
|
||||
import org.thingsboard.server.dao.sql.JpaAbstractDaoListeningExecutorService;
|
||||
import org.thingsboard.server.dao.user.UserSettingsDao;
|
||||
import org.thingsboard.server.dao.util.SqlDao;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Slf4j
|
||||
@Component
|
||||
@SqlDao
|
||||
@ -50,4 +53,9 @@ public class JpaUserSettingsDao extends JpaAbstractDaoListeningExecutorService i
|
||||
userSettingsRepository.deleteById(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<UserSettings> findByTypeAndPath(TenantId tenantId, UserSettingsType type, String... path) {
|
||||
return DaoUtil.convertDataList(userSettingsRepository.findByTypeAndPathExisting(type.name(), path));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -18,10 +18,8 @@ package org.thingsboard.server.dao.sql.user;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.data.jpa.repository.Modifying;
|
||||
import org.springframework.data.jpa.repository.Query;
|
||||
import org.springframework.data.repository.query.Param;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.thingsboard.server.common.data.security.Authority;
|
||||
import org.thingsboard.server.dao.model.sql.UserEntity;
|
||||
|
||||
@ -73,10 +71,4 @@ public interface UserRepository extends JpaRepository<UserEntity, UUID> {
|
||||
|
||||
Long countByTenantId(UUID tenantId);
|
||||
|
||||
@Query(value = "UPDATE user_settings SET settings = settings - 'fcmToken' " +
|
||||
"WHERE type = 'MOBILE' AND (settings ->> 'fcmToken') = :fcmToken", nativeQuery = true)
|
||||
@Modifying
|
||||
@Transactional
|
||||
void unassignFcmToken(@Param("fcmToken") String fcmToken);
|
||||
|
||||
}
|
||||
|
||||
@ -16,9 +16,16 @@
|
||||
package org.thingsboard.server.dao.sql.user;
|
||||
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.data.jpa.repository.Query;
|
||||
import org.springframework.data.repository.query.Param;
|
||||
import org.thingsboard.server.common.data.settings.UserSettingsCompositeKey;
|
||||
import org.thingsboard.server.dao.model.sql.UserSettingsEntity;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public interface UserSettingsRepository extends JpaRepository<UserSettingsEntity, UserSettingsCompositeKey> {
|
||||
|
||||
@Query(value = "SELECT * FROM user_settings WHERE type = :type AND (settings #> :path) IS NOT NULL", nativeQuery = true)
|
||||
List<UserSettingsEntity> findByTypeAndPathExisting(@Param("type") String type, @Param("path") String[] path);
|
||||
|
||||
}
|
||||
|
||||
@ -101,6 +101,4 @@ public interface UserDao extends Dao<User>, TenantEntityDao {
|
||||
|
||||
PageData<User> findByAuthorityAndTenantProfilesIds(Authority authority, List<TenantProfileId> tenantProfilesIds, PageLink pageLink);
|
||||
|
||||
void unassignFcmToken(TenantId tenantId, String fcmToken);
|
||||
|
||||
}
|
||||
|
||||
@ -21,7 +21,6 @@ import com.fasterxml.jackson.databind.node.ObjectNode;
|
||||
import com.google.common.util.concurrent.ListenableFuture;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.context.ApplicationEventPublisher;
|
||||
import org.springframework.security.authentication.DisabledException;
|
||||
@ -31,7 +30,6 @@ import org.springframework.transaction.annotation.Transactional;
|
||||
import org.thingsboard.common.util.JacksonUtil;
|
||||
import org.thingsboard.server.common.data.EntityType;
|
||||
import org.thingsboard.server.common.data.User;
|
||||
import org.thingsboard.server.common.data.UserMobileInfo;
|
||||
import org.thingsboard.server.common.data.audit.ActionType;
|
||||
import org.thingsboard.server.common.data.id.CustomerId;
|
||||
import org.thingsboard.server.common.data.id.EntityId;
|
||||
@ -40,6 +38,8 @@ import org.thingsboard.server.common.data.id.TenantId;
|
||||
import org.thingsboard.server.common.data.id.TenantProfileId;
|
||||
import org.thingsboard.server.common.data.id.UserCredentialsId;
|
||||
import org.thingsboard.server.common.data.id.UserId;
|
||||
import org.thingsboard.server.common.data.mobile.MobileSessionInfo;
|
||||
import org.thingsboard.server.common.data.mobile.UserMobileInfo;
|
||||
import org.thingsboard.server.common.data.page.PageData;
|
||||
import org.thingsboard.server.common.data.page.PageLink;
|
||||
import org.thingsboard.server.common.data.security.Authority;
|
||||
@ -56,6 +56,7 @@ import org.thingsboard.server.dao.exception.IncorrectParameterException;
|
||||
import org.thingsboard.server.dao.service.DataValidator;
|
||||
import org.thingsboard.server.dao.service.PaginatedRemover;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
@ -90,6 +91,7 @@ public class UserServiceImpl extends AbstractEntityService implements UserServic
|
||||
private final UserCredentialsDao userCredentialsDao;
|
||||
private final UserAuthSettingsDao userAuthSettingsDao;
|
||||
private final UserSettingsService userSettingsService;
|
||||
private final UserSettingsDao userSettingsDao;
|
||||
private final DataValidator<User> userValidator;
|
||||
private final DataValidator<UserCredentials> userCredentialsValidator;
|
||||
private final ApplicationEventPublisher eventPublisher;
|
||||
@ -397,19 +399,39 @@ public class UserServiceImpl extends AbstractEntityService implements UserServic
|
||||
}
|
||||
|
||||
@Override
|
||||
public void saveMobileInfo(TenantId tenantId, UserId userId, UserMobileInfo mobileInfo) {
|
||||
if (StringUtils.isNotEmpty(mobileInfo.getFcmToken())) {
|
||||
// unassigning fcm token from other users, in case we didn't clean up it on log out or mobile app uninstall
|
||||
userDao.unassignFcmToken(tenantId, mobileInfo.getFcmToken());
|
||||
}
|
||||
public void saveMobileSession(TenantId tenantId, UserId userId, String mobileToken, MobileSessionInfo sessionInfo) {
|
||||
removeMobileSession(tenantId, mobileToken); // unassigning fcm token from other users, in case we didn't clean up it on log out or mobile app uninstall
|
||||
|
||||
UserMobileInfo mobileInfo = findMobileInfo(tenantId, userId).orElseGet(() -> {
|
||||
UserMobileInfo newMobileInfo = new UserMobileInfo();
|
||||
newMobileInfo.setSessions(new HashMap<>());
|
||||
return newMobileInfo;
|
||||
});
|
||||
mobileInfo.getSessions().put(mobileToken, sessionInfo);
|
||||
userSettingsService.updateUserSettings(tenantId, userId, UserSettingsType.MOBILE, JacksonUtil.valueToTree(mobileInfo));
|
||||
}
|
||||
|
||||
@Override
|
||||
public UserMobileInfo findMobileInfo(TenantId tenantId, UserId userId) {
|
||||
public Map<String, MobileSessionInfo> findMobileSessions(TenantId tenantId, UserId userId) {
|
||||
return findMobileInfo(tenantId, userId).map(UserMobileInfo::getSessions).orElse(Collections.emptyMap());
|
||||
}
|
||||
|
||||
@Override
|
||||
public MobileSessionInfo findMobileSession(TenantId tenantId, UserId userId, String mobileToken) {
|
||||
return findMobileInfo(tenantId, userId).map(mobileInfo -> mobileInfo.getSessions().get(mobileToken)).orElse(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeMobileSession(TenantId tenantId, String mobileToken) {
|
||||
for (UserSettings userSettings : userSettingsDao.findByTypeAndPath(tenantId, UserSettingsType.MOBILE, "sessions", mobileToken)) {
|
||||
((ObjectNode) userSettings.getSettings().get("sessions")).remove(mobileToken);
|
||||
userSettingsService.saveUserSettings(tenantId, userSettings);
|
||||
}
|
||||
}
|
||||
|
||||
private Optional<UserMobileInfo> findMobileInfo(TenantId tenantId, UserId userId) {
|
||||
return Optional.ofNullable(userSettingsService.findUserSettings(tenantId, userId, UserSettingsType.MOBILE))
|
||||
.map(UserSettings::getSettings).map(settings -> JacksonUtil.treeToValue(settings, UserMobileInfo.class))
|
||||
.orElse(null);
|
||||
.map(UserSettings::getSettings).map(settings -> JacksonUtil.treeToValue(settings, UserMobileInfo.class));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@ -18,6 +18,9 @@ package org.thingsboard.server.dao.user;
|
||||
import org.thingsboard.server.common.data.id.TenantId;
|
||||
import org.thingsboard.server.common.data.settings.UserSettings;
|
||||
import org.thingsboard.server.common.data.settings.UserSettingsCompositeKey;
|
||||
import org.thingsboard.server.common.data.settings.UserSettingsType;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public interface UserSettingsDao {
|
||||
|
||||
@ -27,4 +30,6 @@ public interface UserSettingsDao {
|
||||
|
||||
void removeById(TenantId tenantId, UserSettingsCompositeKey key);
|
||||
|
||||
List<UserSettings> findByTypeAndPath(TenantId tenantId, UserSettingsType type, String... path);
|
||||
|
||||
}
|
||||
|
||||
@ -127,5 +127,3 @@ CREATE INDEX IF NOT EXISTS idx_resource_etag ON resource(tenant_id, etag);
|
||||
CREATE INDEX IF NOT EXISTS idx_resource_etag ON resource(tenant_id, etag);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_resource_type_public_resource_key ON resource(resource_type, public_resource_key);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_user_settings_mobile_fcm_token ON user_settings ((settings ->> 'fcmToken')) WHERE type = 'MOBILE';
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user