Browse Source

Merge branch 'mobileAppBundle' of github.com:dashevchenko/thingsboard into mobileAppBundle

pull/11835/head
Vladyslav_Prykhodko 2 years ago
parent
commit
325f5d8371
  1. 130
      application/src/main/data/upgrade/3.8.0/schema_update.sql
  2. 6
      application/src/main/java/org/thingsboard/server/controller/MobileAppBundleController.java
  3. 22
      application/src/main/java/org/thingsboard/server/controller/MobileAppController.java
  4. 50
      application/src/main/java/org/thingsboard/server/controller/MobileV2Controller.java
  5. 31
      application/src/main/java/org/thingsboard/server/controller/QrCodeSettingsController.java
  6. 2
      application/src/main/java/org/thingsboard/server/controller/SystemInfoController.java
  7. 48
      application/src/test/java/org/thingsboard/server/controller/MobileAppBundleControllerTest.java
  8. 20
      application/src/test/java/org/thingsboard/server/controller/QrCodeSettingsControllerTest.java
  9. 6
      common/dao-api/src/main/java/org/thingsboard/server/dao/mobile/MobileAppBundleService.java
  10. 11
      common/dao-api/src/main/java/org/thingsboard/server/dao/mobile/MobileAppService.java
  11. 7
      common/data/src/main/java/org/thingsboard/server/common/data/mobile/LoginMobileInfo.java
  12. 7
      common/data/src/main/java/org/thingsboard/server/common/data/mobile/MobileApp.java
  13. 6
      common/data/src/main/java/org/thingsboard/server/common/data/mobile/MobileAppBundleInfo.java
  14. 49
      common/data/src/main/java/org/thingsboard/server/common/data/mobile/MobileAppVersionInfo.java
  15. 1
      common/data/src/main/java/org/thingsboard/server/common/data/mobile/QrCodeConfig.java
  16. 11
      common/data/src/main/java/org/thingsboard/server/common/data/mobile/UserMobileInfo.java
  17. 14
      common/data/src/main/java/org/thingsboard/server/common/data/mobile/UserMobileSessionInfo.java
  18. 9
      dao/src/main/java/org/thingsboard/server/dao/mobile/MobileAppBundleDao.java
  19. 66
      dao/src/main/java/org/thingsboard/server/dao/mobile/MobileAppBundleServiceImpl.java
  20. 2
      dao/src/main/java/org/thingsboard/server/dao/mobile/MobileAppDao.java
  21. 15
      dao/src/main/java/org/thingsboard/server/dao/mobile/MobileAppServiceImpl.java
  22. 12
      dao/src/main/java/org/thingsboard/server/dao/mobile/QrCodeSettingService.java
  23. 21
      dao/src/main/java/org/thingsboard/server/dao/mobile/QrCodeSettingServiceImpl.java
  24. 2
      dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java
  25. 114
      dao/src/main/java/org/thingsboard/server/dao/model/sql/AbstractMobileAppBundleEntity.java
  26. 75
      dao/src/main/java/org/thingsboard/server/dao/model/sql/MobileAppBundleEntity.java
  27. 43
      dao/src/main/java/org/thingsboard/server/dao/model/sql/MobileAppBundleInfoEntity.java
  28. 5
      dao/src/main/java/org/thingsboard/server/dao/model/sql/MobileAppEntity.java
  29. 18
      dao/src/main/java/org/thingsboard/server/dao/sql/mobile/JpaMobileAppBundleDao.java
  30. 18
      dao/src/main/java/org/thingsboard/server/dao/sql/mobile/JpaMobileAppDao.java
  31. 27
      dao/src/main/java/org/thingsboard/server/dao/sql/mobile/MobileAppBundleRepository.java
  32. 3
      dao/src/main/java/org/thingsboard/server/dao/sql/mobile/MobileAppRepository.java
  33. 22
      dao/src/main/java/org/thingsboard/server/dao/sql/oauth2/OAuth2ClientRepository.java
  34. 12
      dao/src/main/java/org/thingsboard/server/dao/user/UserServiceImpl.java
  35. 23
      dao/src/main/resources/sql/schema-entities.sql

130
application/src/main/data/upgrade/3.8.0/schema_update.sql

@ -14,3 +14,133 @@
-- limitations under the License.
--
-- CREATE MOBILE APP BUNDLES FROM EXISTING APPS
CREATE TABLE IF NOT EXISTS mobile_app_bundle (
id uuid NOT NULL CONSTRAINT mobile_app_bundle_pkey PRIMARY KEY,
created_time bigint NOT NULL,
tenant_id uuid,
title varchar(255),
description varchar(1024),
android_app_id uuid UNIQUE,
ios_app_id uuid UNIQUE,
layout_config varchar(16384),
oauth2_enabled boolean,
CONSTRAINT fk_android_app_id FOREIGN KEY (android_app_id) REFERENCES mobile_app(id),
CONSTRAINT fk_ios_app_id FOREIGN KEY (ios_app_id) REFERENCES mobile_app(id)
);
ALTER TABLE mobile_app ADD COLUMN IF NOT EXISTS platform_type varchar(32),
ADD COLUMN IF NOT EXISTS status varchar(32),
ADD COLUMN IF NOT EXISTS version_info varchar(16384),
ADD COLUMN IF NOT EXISTS qr_code_config varchar(16384),
DROP CONSTRAINT IF EXISTS mobile_app_pkg_name_key;
-- rename mobile_app_oauth2_client to mobile_app_bundle_oauth2_client
DO
$$
BEGIN
-- in case of running the upgrade script a second time
IF EXISTS(SELECT * FROM information_schema.tables WHERE table_name = 'mobile_app_oauth2_client') THEN
ALTER TABLE mobile_app_oauth2_client RENAME TO mobile_app_bundle_oauth2_client;
ALTER TABLE mobile_app_bundle_oauth2_client DROP CONSTRAINT IF EXISTS fk_domain;
ALTER TABLE mobile_app_bundle_oauth2_client RENAME COLUMN mobile_app_id TO mobile_app_bundle_id;
END IF;
END;
$$;
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
-- duplicate each mobile app and create mobile app bundle for the pair of android and ios app
DO
$$
DECLARE
generatedBundleId uuid;
iosAppId uuid;
mobileAppRecord RECORD;
BEGIN
-- in case of running the upgrade script a second time
IF EXISTS(SELECT * FROM information_schema.columns WHERE table_name = 'mobile_app' and column_name = 'oauth2_enabled') THEN
UPDATE mobile_app SET platform_type = 'ANDROID' WHERE platform_type IS NULL;
UPDATE mobile_app SET status = 'PUBLISHED' WHERE mobile_app.status IS NULL;
FOR mobileAppRecord IN SELECT * FROM mobile_app
LOOP
-- duplicate app for iOS platform type
iosAppId := uuid_generate_v4();
INSERT INTO mobile_app(id, created_time, tenant_id, pkg_name, app_secret, platform_type, status)
VALUES (iosAppId, (extract(epoch from now()) * 1000), mobileAppRecord.tenant_id, mobileAppRecord.pkg_name, mobileAppRecord.app_secret, 'IOS', mobileAppRecord.status);
-- create bundle for android and iOS app
generatedBundleId := uuid_generate_v4();
INSERT INTO mobile_app_bundle(id, created_time, tenant_id, title, android_app_id, ios_app_id, oauth2_enabled)
VALUES (generatedBundleId, (extract(epoch from now()) * 1000), mobileAppRecord.tenant_id,
'App bundle ' || mobileAppRecord.pkg_name, mobileAppRecord.id, iosAppId, mobileAppRecord.oauth2_enabled);
UPDATE mobile_app_bundle_oauth2_client SET mobile_app_bundle_id = generatedBundleId WHERE mobile_app_bundle_id = mobileAppRecord.id;
END LOOP;
END IF;
ALTER TABLE mobile_app DROP COLUMN IF EXISTS oauth2_enabled;
IF NOT EXISTS(SELECT 1 FROM pg_constraint WHERE conname = 'pkg_platform_unique') THEN
ALTER TABLE mobile_app ADD CONSTRAINT pkg_platform_unique UNIQUE (pkg_name, platform_type);
END IF;
END;
$$;
ALTER TABLE IF EXISTS mobile_app_settings RENAME TO qr_code_settings;
ALTER TABLE qr_code_settings ADD COLUMN IF NOT EXISTS mobile_app_bundle_id uuid;
-- migrate mobile apps from qr code settings to mobile_app, create mobile app bundle for the pair of apps
DO
$$
DECLARE
iosPkgName varchar;
androidAppId uuid;
iosAppId uuid;
generatedBundleId uuid;
qrCodeRecord RECORD;
BEGIN
-- in case of running the upgrade script a second time
IF EXISTS(SELECT * FROM information_schema.columns WHERE table_name = 'qr_code_settings' and column_name = 'android_config') THEN
FOR qrCodeRecord IN SELECT * FROM qr_code_settings
LOOP
generatedBundleId := NULL;
-- migrate android config
SELECT id into androidAppId FROM mobile_app WHERE pkg_name = qrCodeRecord.android_config::jsonb ->> 'appPackage' AND platform_type = 'ANDROID';
IF androidAppId IS NULL THEN
androidAppId := uuid_generate_v4();
INSERT INTO mobile_app(id, created_time, tenant_id, pkg_name, platform_type, status, qr_code_config)
VALUES (androidAppId, (extract(epoch from now()) * 1000), qrCodeRecord.tenant_id,
qrCodeRecord.android_config::jsonb ->> 'appPackage', 'ANDROID', 'PUBLISHED', qrCodeRecord.android_config::jsonb || '{"type": "ANDROID"}'::jsonb);
generatedBundleId := uuid_generate_v4();
INSERT INTO mobile_app_bundle(id, created_time, tenant_id, title, android_app_id)
VALUES (generatedBundleId, (extract(epoch from now()) * 1000), qrCodeRecord.tenant_id, 'App bundle for qr code', androidAppId);
UPDATE qr_code_settings SET mobile_app_bundle_id = generatedBundleId WHERE id = qrCodeRecord.id;
ELSE
UPDATE mobile_app SET qr_code_config = qrCodeRecord.android_config::jsonb || '{"type": "ANDROID"}'::jsonb WHERE id = androidAppId;
UPDATE qr_code_settings SET mobile_app_bundle_id = (SELECT id FROM mobile_app_bundle WHERE mobile_app_bundle.android_app_id = androidAppId) WHERE id = qrCodeRecord.id;
END IF;
-- migrate ios config
iosPkgName := substring(qrCodeRecord.ios_config::jsonb ->> 'appId', strpos(qrCodeRecord.ios_config::jsonb ->> 'appId', '.') + 1);
SELECT id into iosAppId FROM mobile_app WHERE pkg_name = iosPkgName AND platform_type = 'IOS';
IF iosAppId IS NULL THEN
iosAppId := uuid_generate_v4();
INSERT INTO mobile_app(id, created_time, tenant_id, pkg_name, platform_type, status, qr_code_config)
VALUES (iosAppId, (extract(epoch from now()) * 1000), qrCodeRecord.tenant_id,
iosPkgName, 'IOS', 'PUBLISHED', qrCodeRecord.ios_config::jsonb || '{"type": "IOS"}'::jsonb);
IF generatedBundleId IS NULL THEN
generatedBundleId := uuid_generate_v4();
INSERT INTO mobile_app_bundle(id, created_time, tenant_id, title, ios_app_id)
VALUES (generatedBundleId, (extract(epoch from now()) * 1000), qrCodeRecord.tenant_id, 'App bundle for qr code', iosAppId);
UPDATE qr_code_settings SET mobile_app_bundle_id = generatedBundleId WHERE id = qrCodeRecord.id;
ELSE
UPDATE mobile_app_bundle SET ios_app_id = iosAppId WHERE id = generatedBundleId;
END IF;
ELSE
UPDATE mobile_app SET qr_code_config = qrCodeRecord.ios_config::jsonb || '{"type": "IOS"}'::jsonb WHERE id = iosAppId;
UPDATE qr_code_settings SET mobile_app_bundle_id = (SELECT id FROM mobile_app_bundle WHERE mobile_app_bundle.ios_app_id = iosAppId) WHERE id = qrCodeRecord.id;
END IF;
END LOOP;
END IF;
ALTER TABLE qr_code_settings DROP COLUMN IF EXISTS android_config, DROP COLUMN IF EXISTS ios_config;
END;
$$;

6
application/src/main/java/org/thingsboard/server/controller/MobileAppBundleController.java

@ -64,10 +64,10 @@ public class MobileAppBundleController extends BaseController {
private final TbMobileAppBundleService tbMobileAppBundleService;
@ApiOperation(value = "Save Or update Mobile app bundle (saveMobileAppBundle)",
notes = "Create or update the Mobile app bundle that represents tha pair of apps for ANDROID and IOS platforms." +
notes = "Create or update the Mobile app bundle that represents tha pair of ANDROID and IOS app and " +
"mobile settings like oauth2 clients, self-registration and layout configuration." +
"When creating mobile app bundle, platform generates Mobile App Bundle Id as " + UUID_WIKI_LINK +
"The newly created Mobile App Bundle Id will be present in the response. " +
"Specify existing Mobile App Bundle Id to configure application settings like oauth2 settings, self-registration or layout settings. " +
"Referencing non-existing Mobile App Bundle Id will cause 'Not Found' error." + SYSTEM_AUTHORITY_PARAGRAPH)
@PreAuthorize("hasAnyAuthority('SYS_ADMIN')")
@PostMapping(value = "/mobile/bundle")
@ -106,7 +106,7 @@ public class MobileAppBundleController extends BaseController {
@RequestParam(required = false) String sortProperty,
@Parameter(description = SORT_ORDER_DESCRIPTION)
@RequestParam(required = false) String sortOrder) throws ThingsboardException {
accessControlService.checkPermission(getCurrentUser(), Resource.MOBILE_APP, Operation.READ);
accessControlService.checkPermission(getCurrentUser(), Resource.MOBILE_APP_BUNDLE, Operation.READ);
PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder);
return mobileAppBundleService.findMobileAppBundleInfosByTenantId(getTenantId(), pageLink);
}

22
application/src/main/java/org/thingsboard/server/controller/MobileAppController.java

@ -76,16 +76,16 @@ public class MobileAppController extends BaseController {
@ApiOperation(value = "Get mobile app infos (getTenantMobileAppInfos)", notes = SYSTEM_AUTHORITY_PARAGRAPH)
@PreAuthorize("hasAnyAuthority('SYS_ADMIN')")
@GetMapping(value = "/mobile/app")
public PageData<MobileApp> getTenantMobileAppInfos(@Parameter(description = PAGE_SIZE_DESCRIPTION, required = true)
@RequestParam int pageSize,
@Parameter(description = PAGE_NUMBER_DESCRIPTION, required = true)
@RequestParam int page,
@Parameter(description = "Case-insensitive 'substring' filter based on app's name")
@RequestParam(required = false) String textSearch,
@Parameter(description = SORT_PROPERTY_DESCRIPTION)
@RequestParam(required = false) String sortProperty,
@Parameter(description = SORT_ORDER_DESCRIPTION)
@RequestParam(required = false) String sortOrder) throws ThingsboardException {
public PageData<MobileApp> getTenantMobileApps(@Parameter(description = PAGE_SIZE_DESCRIPTION, required = true)
@RequestParam int pageSize,
@Parameter(description = PAGE_NUMBER_DESCRIPTION, required = true)
@RequestParam int page,
@Parameter(description = "Case-insensitive 'substring' filter based on app's name")
@RequestParam(required = false) String textSearch,
@Parameter(description = SORT_PROPERTY_DESCRIPTION)
@RequestParam(required = false) String sortProperty,
@Parameter(description = SORT_ORDER_DESCRIPTION)
@RequestParam(required = false) String sortOrder) throws ThingsboardException {
accessControlService.checkPermission(getCurrentUser(), Resource.MOBILE_APP, Operation.READ);
PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder);
return mobileAppService.findMobileAppsByTenantId(getTenantId(), pageLink);
@ -94,7 +94,7 @@ public class MobileAppController extends BaseController {
@ApiOperation(value = "Get mobile info by id (getMobileAppInfoById)", notes = SYSTEM_AUTHORITY_PARAGRAPH)
@PreAuthorize("hasAnyAuthority('SYS_ADMIN')")
@GetMapping(value = "/mobile/app/{id}")
public MobileApp getMobileAppInfoById(@PathVariable UUID id) throws ThingsboardException {
public MobileApp getMobileAppById(@PathVariable UUID id) throws ThingsboardException {
MobileAppId mobileAppId = new MobileAppId(id);
return checkEntityId(mobileAppId, mobileAppService::findMobileAppById, Operation.READ);
}

50
application/src/main/java/org/thingsboard/server/controller/MobileV2Controller.java

@ -18,48 +18,66 @@ package org.thingsboard.server.controller;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.RequiredArgsConstructor;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.thingsboard.server.common.data.HomeDashboardInfo;
import org.thingsboard.server.common.data.User;
import org.thingsboard.server.common.data.exception.ThingsboardException;
import org.thingsboard.server.common.data.mobile.LoginMobileInfo;
import org.thingsboard.server.common.data.mobile.MobileApp;
import org.thingsboard.server.common.data.mobile.MobileAppBundle;
import org.thingsboard.server.common.data.mobile.MobileLoginInfo;
import org.thingsboard.server.common.data.mobile.MobileUserInfo;
import org.thingsboard.server.common.data.mobile.MobileAppVersionInfo;
import org.thingsboard.server.common.data.mobile.UserMobileInfo;
import org.thingsboard.server.common.data.oauth2.OAuth2ClientLoginInfo;
import org.thingsboard.server.common.data.oauth2.PlatformType;
import org.thingsboard.server.config.annotations.ApiOperation;
import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.service.security.model.SecurityUser;
import java.util.List;
import static org.thingsboard.server.controller.ControllerConstants.AVAILABLE_FOR_ANY_AUTHORIZED_USER;
@RequiredArgsConstructor
@RestController
@TbCoreComponent
public class MobileV2Controller extends BaseController {
@ApiOperation(value = "Get mobile app login info (getLoginMobileInfo)")
@GetMapping(value = "/api/noauth/mobile")
public MobileLoginInfo getMobileUserLoginSettings(@Parameter(description = "Mobile application package name")
@RequestParam String pkgName,
@Parameter(description = "Platform type",
schema = @Schema(allowableValues = {"ANDROID", "IOS"}))
@RequestParam PlatformType platform) {
public LoginMobileInfo getLoginMobileInfo(@Parameter(description = "Mobile application package name")
@RequestParam String pkgName,
@Parameter(description = "Platform type", schema = @Schema(allowableValues = {"ANDROID", "IOS"}))
@RequestParam PlatformType platform) {
List<OAuth2ClientLoginInfo> oauth2Clients = oAuth2ClientService.findOAuth2ClientLoginInfosByMobilePkgNameAndPlatformType(pkgName, platform);
return new MobileLoginInfo(oauth2Clients);
MobileApp mobileApp = mobileAppService.findMobileAppByPkgNameAndPlatformType(pkgName, platform);
return new LoginMobileInfo(oauth2Clients, mobileApp != null ? mobileApp.getVersionInfo() : null);
}
@GetMapping(value = "/api/auth/mobile")
public MobileUserInfo getMobileUserSettings(@Parameter(description = "Mobile application package name")
@RequestParam String pkgName,
@Parameter(description = "Platform type",
schema = @Schema(allowableValues = {"ANDROID", "IOS"}))
@RequestParam PlatformType platform) throws ThingsboardException {
@ApiOperation(value = "Get user mobile app basic info (getUserMobileInfo)", notes = AVAILABLE_FOR_ANY_AUTHORIZED_USER)
@PreAuthorize("hasAnyAuthority('SYS_ADMIN','TENANT_ADMIN', 'CUSTOMER_USER')")
@GetMapping(value = "/api/mobile")
public UserMobileInfo getUserMobileInfo(@Parameter(description = "Mobile application package name")
@RequestParam String pkgName,
@Parameter(description = "Platform type", schema = @Schema(allowableValues = {"ANDROID", "IOS"}))
@RequestParam PlatformType platform) throws ThingsboardException {
SecurityUser securityUser = getCurrentUser();
User user = userService.findUserById(securityUser.getTenantId(), securityUser.getId());
HomeDashboardInfo homeDashboardInfo = getHomeDashboardInfo(securityUser, user.getAdditionalInfo());
HomeDashboardInfo homeDashboardInfo = securityUser.isSystemAdmin() ? null : getHomeDashboardInfo(securityUser, user.getAdditionalInfo());
MobileAppBundle mobileAppBundle = mobileAppBundleService.findMobileAppBundleByPkgNameAndPlatform(securityUser.getTenantId(), pkgName, platform);
return new MobileUserInfo(user, homeDashboardInfo, mobileAppBundle != null ? mobileAppBundle.getLayoutConfig() : null);
return new UserMobileInfo(user, homeDashboardInfo, mobileAppBundle != null ? mobileAppBundle.getLayoutConfig() : null);
}
@ApiOperation(value = "Get mobile app version info (getMobileVersionInfo)")
@GetMapping(value = "/api/mobile/versionInfo")
public MobileAppVersionInfo getMobileVersionInfo(@Parameter(description = "Mobile application package name")
@RequestParam String pkgName,
@Parameter(description = "Platform type", schema = @Schema(allowableValues = {"ANDROID", "IOS"}))
@RequestParam PlatformType platform) {
MobileApp mobileApp = mobileAppService.findMobileAppByPkgNameAndPlatformType(pkgName, platform);
return mobileApp != null ? mobileApp.getVersionInfo() : null;
}
}

31
application/src/main/java/org/thingsboard/server/controller/QrCodeSettingsController.java

@ -31,10 +31,13 @@ import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RestController;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.common.data.exception.ThingsboardException;
import org.thingsboard.server.common.data.id.MobileAppBundleId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.mobile.AndroidQrCodeConfig;
import org.thingsboard.server.common.data.mobile.IosQrCodeConfig;
import org.thingsboard.server.common.data.mobile.MobileApp;
import org.thingsboard.server.common.data.mobile.QrCodeSettings;
import org.thingsboard.server.common.data.oauth2.PlatformType;
import org.thingsboard.server.common.data.security.model.JwtPair;
import org.thingsboard.server.config.annotations.ApiOperation;
import org.thingsboard.server.dao.mobile.MobileAppService;
@ -49,6 +52,8 @@ import org.thingsboard.server.service.security.system.SystemSecurityService;
import java.net.URI;
import java.net.URISyntaxException;
import static org.thingsboard.server.common.data.oauth2.PlatformType.ANDROID;
import static org.thingsboard.server.common.data.oauth2.PlatformType.IOS;
import static org.thingsboard.server.controller.ControllerConstants.AVAILABLE_FOR_ANY_AUTHORIZED_USER;
import static org.thingsboard.server.controller.ControllerConstants.SYSTEM_AUTHORITY_PARAGRAPH;
@ -96,7 +101,7 @@ public class QrCodeSettingsController extends BaseController {
@ApiOperation(value = "Get associated android applications (getAssetLinks)")
@GetMapping(value = "/.well-known/assetlinks.json")
public ResponseEntity<JsonNode> getAssetLinks() {
AndroidQrCodeConfig androidQrConfig = qrCodeSettingService.getAndroidQrCodeConfig(TenantId.SYS_TENANT_ID);
AndroidQrCodeConfig androidQrConfig = (AndroidQrCodeConfig) qrCodeSettingService.findAppQrCodeConfig(TenantId.SYS_TENANT_ID, ANDROID);
if (androidQrConfig != null && androidQrConfig.isEnabled()) {
return ResponseEntity.ok(JacksonUtil.toJsonNode(String.format(ASSET_LINKS_PATTERN, androidQrConfig.getAppPackage(), androidQrConfig.getSha256CertFingerprints())));
} else {
@ -107,8 +112,8 @@ public class QrCodeSettingsController extends BaseController {
@ApiOperation(value = "Get associated ios applications (getAppleAppSiteAssociation)")
@GetMapping(value = "/.well-known/apple-app-site-association")
public ResponseEntity<JsonNode> getAppleAppSiteAssociation() {
IosQrCodeConfig iosQrCodeConfig = qrCodeSettingService.getIosQrCodeConfig(TenantId.SYS_TENANT_ID);
if (iosQrCodeConfig != null && iosQrCodeConfig.isEnabled() && iosQrCodeConfig.getAppId() != null) {
IosQrCodeConfig iosQrCodeConfig = (IosQrCodeConfig) qrCodeSettingService.findAppQrCodeConfig(TenantId.SYS_TENANT_ID, IOS);
if (iosQrCodeConfig != null && iosQrCodeConfig.isEnabled()) {
return ResponseEntity.ok(JacksonUtil.toJsonNode(String.format(APPLE_APP_SITE_ASSOCIATION_PATTERN, iosQrCodeConfig.getAppId())));
} else {
return ResponseEntity.notFound().build();
@ -134,7 +139,7 @@ public class QrCodeSettingsController extends BaseController {
public QrCodeSettings getMobileAppSettings() throws ThingsboardException {
SecurityUser currentUser = getCurrentUser();
accessControlService.checkPermission(currentUser, Resource.MOBILE_APP_SETTINGS, Operation.READ);
return qrCodeSettingService.getQrCodeSettings(TenantId.SYS_TENANT_ID);
return qrCodeSettingService.findQrCodeSettings(TenantId.SYS_TENANT_ID);
}
@ApiOperation(value = "Get the deep link to the associated mobile application (getMobileAppDeepLink)",
@ -145,7 +150,7 @@ public class QrCodeSettingsController extends BaseController {
String secret = mobileAppSecretService.generateMobileAppSecret(getCurrentUser());
String baseUrl = systemSecurityService.getBaseUrl(TenantId.SYS_TENANT_ID, null, request);
String platformDomain = new URI(baseUrl).getHost();
QrCodeSettings qrCodeSettings = qrCodeSettingService.getQrCodeSettings(TenantId.SYS_TENANT_ID);
QrCodeSettings qrCodeSettings = qrCodeSettingService.findQrCodeSettings(TenantId.SYS_TENANT_ID);
String appDomain;
if (!qrCodeSettings.isUseDefaultApp()) {
appDomain = platformDomain;
@ -170,17 +175,15 @@ public class QrCodeSettingsController extends BaseController {
@GetMapping(value = "/api/noauth/qr")
public ResponseEntity<?> getApplicationRedirect(@RequestHeader(value = "User-Agent") String userAgent) {
QrCodeSettings qrCodeSettings = qrCodeSettingService.getQrCodeSettings(TenantId.SYS_TENANT_ID);
QrCodeSettings qrCodeSettings = qrCodeSettingService.findQrCodeSettings(TenantId.SYS_TENANT_ID);
boolean useDefaultApp = qrCodeSettings.isUseDefaultApp();
if (userAgent.contains("Android")) {
String googlePlayLink = useDefaultApp ? qrCodeSettings.getDefaultGooglePlayLink() :
mobileAppService.findAndroidQrCodeConfig(TenantId.SYS_TENANT_ID, qrCodeSettings.getMobileAppBundleId()).getStoreLink();
String googlePlayLink = useDefaultApp ? qrCodeSettings.getDefaultGooglePlayLink() : getStoreLink(qrCodeSettings.getMobileAppBundleId(), ANDROID);
return ResponseEntity.status(HttpStatus.FOUND)
.header("Location", googlePlayLink)
.build();
} else if (userAgent.contains("iPhone") || userAgent.contains("iPad")) {
String appStoreLink = useDefaultApp ? qrCodeSettings.getDefaultAppStoreLink() :
mobileAppService.findIosQrCodeConfig(TenantId.SYS_TENANT_ID, qrCodeSettings.getMobileAppBundleId()).getStoreLink();
String appStoreLink = useDefaultApp ? qrCodeSettings.getDefaultAppStoreLink() : getStoreLink(qrCodeSettings.getMobileAppBundleId(), IOS);
return ResponseEntity.status(HttpStatus.FOUND)
.header("Location", appStoreLink)
.build();
@ -189,4 +192,12 @@ public class QrCodeSettingsController extends BaseController {
}
}
private String getStoreLink(MobileAppBundleId mobileAppBundleId, PlatformType platformType) {
if (mobileAppBundleId == null) {
return null;
}
MobileApp mobileApp = mobileAppService.findByBundleIdAndPlatformType(TenantId.SYS_TENANT_ID, mobileAppBundleId, platformType);
return (mobileApp != null && mobileApp.getQrCodeConfig() != null) ? mobileApp.getQrCodeConfig().getStoreLink() : null;
}
}

2
application/src/main/java/org/thingsboard/server/controller/SystemInfoController.java

@ -142,7 +142,7 @@ public class SystemInfoController extends BaseController {
DefaultTenantProfileConfiguration tenantProfileConfiguration = tenantProfileCache.get(tenantId).getDefaultProfileConfiguration();
systemParams.setMaxResourceSize(tenantProfileConfiguration.getMaxResourceSize());
}
systemParams.setMobileQrEnabled(Optional.ofNullable(qrCodeSettingService.getQrCodeSettings(TenantId.SYS_TENANT_ID))
systemParams.setMobileQrEnabled(Optional.ofNullable(qrCodeSettingService.findQrCodeSettings(TenantId.SYS_TENANT_ID))
.map(QrCodeSettings::getQrCodeConfig).map(QRCodeConfig::isShowOnHomePage)
.orElse(false));
return systemParams;

48
application/src/test/java/org/thingsboard/server/controller/MobileAppBundleControllerTest.java

@ -32,7 +32,10 @@ import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.dao.service.DaoSqlTest;
import java.util.Comparator;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import static org.assertj.core.api.Assertions.assertThat;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@ -55,24 +58,24 @@ public class MobileAppBundleControllerTest extends AbstractControllerTest {
public void setUp() throws Exception {
loginSysAdmin();
androidApp = validMobileApp(TenantId.SYS_TENANT_ID, "my.android.package", PlatformType.ANDROID, true);
androidApp = validMobileApp(TenantId.SYS_TENANT_ID, "my.android.package", PlatformType.ANDROID);
androidApp = doPost("/api/mobile/app", androidApp, MobileApp.class);
iosApp = validMobileApp(TenantId.SYS_TENANT_ID, "my.ios.package", PlatformType.IOS, true);
iosApp = validMobileApp(TenantId.SYS_TENANT_ID, "my.ios.package", PlatformType.IOS);
iosApp = doPost("/api/mobile/app", iosApp, MobileApp.class);
}
@After
public void tearDown() throws Exception {
PageData<MobileApp> pageData = doGetTypedWithPageLink("/api/mobile/app?", PAGE_DATA_MOBILE_APP_TYPE_REF, new PageLink(10, 0));
for (MobileApp mobileApp : pageData.getData()) {
doDelete("/api/mobile/app/" + mobileApp.getId().getId())
PageData<MobileAppBundleInfo> pageData2 = doGetTypedWithPageLink("/api/mobile/bundle/infos?", PAGE_DATA_MOBILE_APP_BUNDLE_TYPE_REF, new PageLink(10, 0));
for (MobileAppBundleInfo appBundleInfo : pageData2.getData()) {
doDelete("/api/mobile/bundle/" + appBundleInfo.getId().getId())
.andExpect(status().isOk());
}
PageData<MobileAppBundleInfo> pageData2 = doGetTypedWithPageLink("/api/mobile/bundle?", PAGE_DATA_MOBILE_APP_BUNDLE_TYPE_REF, new PageLink(10, 0));
for (MobileAppBundleInfo appBundleInfo : pageData2.getData()) {
doDelete("/api/mobile/bundle/" + appBundleInfo.getId().getId())
PageData<MobileApp> pageData = doGetTypedWithPageLink("/api/mobile/app?", PAGE_DATA_MOBILE_APP_TYPE_REF, new PageLink(10, 0));
for (MobileApp mobileApp : pageData.getData()) {
doDelete("/api/mobile/app/" + mobileApp.getId().getId())
.andExpect(status().isOk());
}
@ -84,7 +87,7 @@ public class MobileAppBundleControllerTest extends AbstractControllerTest {
}
@Test
public void testSaveMobileApp() throws Exception {
public void testSaveMobileAppBundle() {
MobileAppBundle mobileAppBundle = new MobileAppBundle();
mobileAppBundle.setTitle("Test bundle");
mobileAppBundle.setAndroidAppId(androidApp.getId());
@ -97,7 +100,7 @@ public class MobileAppBundleControllerTest extends AbstractControllerTest {
@Test
public void testUpdateMobileAppOauth2Clients() throws Exception {
public void testUpdateMobileAppBundleOauth2Clients() throws Exception {
MobileAppBundle mobileAppBundle = new MobileAppBundle();
mobileAppBundle.setTitle("Test bundle");
mobileAppBundle.setAndroidAppId(androidApp.getId());
@ -113,11 +116,14 @@ public class MobileAppBundleControllerTest extends AbstractControllerTest {
doPut("/api/mobile/bundle/" + savedAppBundle.getId() + "/oauth2Clients", List.of(savedOAuth2Client.getId().getId(), savedOAuth2Client2.getId().getId()));
MobileAppBundleInfo retrievedMobileAppInfo = doGet("/api/mobile/bundle/info/{id}", MobileAppBundleInfo.class, savedAppBundle.getId().getId());
assertThat(retrievedMobileAppInfo).isEqualTo(new MobileAppBundleInfo(savedAppBundle, androidApp.getPkgName(), iosApp.getPkgName(), List.of(new OAuth2ClientInfo(oAuth2Client))));
MobileAppBundleInfo retrievedMobileAppBundleInfo = doGet("/api/mobile/bundle/info/{id}", MobileAppBundleInfo.class, savedAppBundle.getId().getId());
assertThat(retrievedMobileAppBundleInfo).isEqualTo(new MobileAppBundleInfo(savedAppBundle, androidApp.getPkgName(), iosApp.getPkgName(),
Stream.of(new OAuth2ClientInfo(savedOAuth2Client), new OAuth2ClientInfo(savedOAuth2Client2))
.sorted(Comparator.comparing(OAuth2ClientInfo::getTitle)).collect(Collectors.toList())
));
doPut("/api/mobile/bundle/" + savedAppBundle.getId() + "/oauth2Clients", List.of(savedOAuth2Client2.getId().getId()));
MobileAppBundleInfo retrievedMobileAppInfo2 = doGet("/api/mobileApp/info/{id}", MobileAppBundleInfo.class, savedOAuth2Client.getId().getId());
MobileAppBundleInfo retrievedMobileAppInfo2 = doGet("/api/mobile/bundle/info/{id}", MobileAppBundleInfo.class, savedAppBundle.getId().getId());
assertThat(retrievedMobileAppInfo2).isEqualTo(new MobileAppBundleInfo(savedAppBundle, androidApp.getPkgName(), iosApp.getPkgName(), List.of(new OAuth2ClientInfo(savedOAuth2Client2))));
}
@ -133,17 +139,17 @@ public class MobileAppBundleControllerTest extends AbstractControllerTest {
MobileAppBundle savedMobileAppBundle = doPost("/api/mobile/bundle?oauth2ClientIds=" + savedOAuth2Client.getId().getId(), mobileAppBundle, MobileAppBundle.class);
MobileAppBundleInfo retrievedMobileAppInfo = doGet("/api/mobileApp/info/{id}", MobileAppBundleInfo.class, savedMobileAppBundle.getId().getId());
MobileAppBundleInfo retrievedMobileAppInfo = doGet("/api/mobile/bundle/info/{id}", MobileAppBundleInfo.class, savedMobileAppBundle.getId().getId());
assertThat(retrievedMobileAppInfo).isEqualTo(new MobileAppBundleInfo(savedMobileAppBundle, androidApp.getPkgName(), iosApp.getPkgName(), List.of(new OAuth2ClientInfo(savedOAuth2Client))));
}
private MobileApp validMobileApp(TenantId tenantId, String mobileAppName, PlatformType platformType, boolean oauth2Enabled) {
MobileApp MobileApp = new MobileApp();
MobileApp.setTenantId(tenantId);
MobileApp.setPkgName(mobileAppName);
MobileApp.setPlatformType(platformType);
MobileApp.setAppSecret(StringUtils.randomAlphanumeric(24));
return MobileApp;
private MobileApp validMobileApp(TenantId tenantId, String mobileAppName, PlatformType platformType) {
MobileApp mobileApp = new MobileApp();
mobileApp.setTenantId(tenantId);
mobileApp.setPkgName(mobileAppName);
mobileApp.setPlatformType(platformType);
mobileApp.setAppSecret(StringUtils.randomAlphanumeric(24));
return mobileApp;
}
}

20
application/src/test/java/org/thingsboard/server/controller/QrCodeSettingsControllerTest.java

@ -107,17 +107,17 @@ public class QrCodeSettingsControllerTest extends AbstractControllerTest {
@After
public void tearDown() throws Exception {
PageData<MobileApp> pageData = doGetTypedWithPageLink("/api/mobile/app?", PAGE_DATA_MOBILE_APP_TYPE_REF, new PageLink(10, 0));
for (MobileApp mobileApp : pageData.getData()) {
doDelete("/api/mobile/app/" + mobileApp.getId().getId())
.andExpect(status().isOk());
}
loginSysAdmin();
PageData<MobileAppBundleInfo> pageData2 = doGetTypedWithPageLink("/api/mobile/bundle/infos?", PAGE_DATA_MOBILE_APP_BUNDLE_TYPE_REF, new PageLink(10, 0));
for (MobileAppBundleInfo appBundleInfo : pageData2.getData()) {
doDelete("/api/mobile/bundle/" + appBundleInfo.getId().getId())
.andExpect(status().isOk());
}
PageData<MobileApp> pageData = doGetTypedWithPageLink("/api/mobile/app?", PAGE_DATA_MOBILE_APP_TYPE_REF, new PageLink(10, 0));
for (MobileApp mobileApp : pageData.getData()) {
doDelete("/api/mobile/app/" + mobileApp.getId().getId())
.andExpect(status().isOk());
}
}
@Test
@ -148,7 +148,7 @@ public class QrCodeSettingsControllerTest extends AbstractControllerTest {
doPost("/api/qr/settings", qrCodeSettings)
.andExpect(status().isBadRequest())
.andExpect(statusReason(containsString("Bundle required to use custom application!")));
.andExpect(statusReason(containsString("Mobile app bundle is required to use custom application!")));
qrCodeSettings.setMobileAppBundleId(mobileAppBundle.getId());
doPost("/api/qr/settings", qrCodeSettings)
@ -175,7 +175,8 @@ public class QrCodeSettingsControllerTest extends AbstractControllerTest {
public void testGetApplicationAssociations() throws Exception {
loginSysAdmin();
QrCodeSettings qrCodeSettings = doGet("/api/qr/settings", QrCodeSettings.class);
qrCodeSettings.setUseDefaultApp(false);
qrCodeSettings.setUseDefaultApp(true);
qrCodeSettings.setMobileAppBundleId(mobileAppBundle.getId());
doPost("/api/qr/settings", qrCodeSettings)
.andExpect(status().isOk());
@ -226,7 +227,8 @@ public class QrCodeSettingsControllerTest extends AbstractControllerTest {
loginSysAdmin();
QrCodeSettings qrCodeSettings = doGet("/api/qr/settings", QrCodeSettings.class);
qrCodeSettings.setUseDefaultApp(false);
doPost("/api/mobile/app/settings", qrCodeSettings);
qrCodeSettings.setMobileAppBundleId(mobileAppBundle.getId());
doPost("/api/qr/settings", qrCodeSettings);
String customAppDeepLink = doGet("/api/qr/deepLink", String.class);
Pattern customAppExpectedPattern = Pattern.compile("\"https://([^/]+)/api/noauth/qr\\?secret=([^&]+)&ttl=([^&]+)\"");

6
common/dao-api/src/main/java/org/thingsboard/server/dao/mobile/MobileAppBundleService.java

@ -31,8 +31,6 @@ public interface MobileAppBundleService extends EntityDaoService {
MobileAppBundle saveMobileAppBundle(TenantId tenantId, MobileAppBundle mobileAppBundle);
void deleteMobileAppBundleById(TenantId tenantId, MobileAppBundleId mobileAppBundleId);
MobileAppBundle findMobileAppBundleById(TenantId tenantId, MobileAppBundleId mobileAppBundleId);
PageData<MobileAppBundleInfo> findMobileAppBundleInfosByTenantId(TenantId tenantId, PageLink pageLink);
@ -43,5 +41,7 @@ public interface MobileAppBundleService extends EntityDaoService {
MobileAppBundle findMobileAppBundleByPkgNameAndPlatform(TenantId tenantId, String pkgName, PlatformType platform);
void deleteMobileAppsByTenantId(TenantId tenantId);
void deleteMobileAppBundleById(TenantId tenantId, MobileAppBundleId mobileAppBundleId);
void deleteMobileAppBundlesByTenantId(TenantId tenantId);
}

11
common/dao-api/src/main/java/org/thingsboard/server/dao/mobile/MobileAppService.java

@ -18,9 +18,8 @@ package org.thingsboard.server.dao.mobile;
import org.thingsboard.server.common.data.id.MobileAppBundleId;
import org.thingsboard.server.common.data.id.MobileAppId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.mobile.AndroidQrCodeConfig;
import org.thingsboard.server.common.data.mobile.IosQrCodeConfig;
import org.thingsboard.server.common.data.mobile.MobileApp;
import org.thingsboard.server.common.data.oauth2.PlatformType;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.dao.entity.EntityDaoService;
@ -29,15 +28,15 @@ public interface MobileAppService extends EntityDaoService {
MobileApp saveMobileApp(TenantId tenantId, MobileApp mobileApp);
void deleteMobileAppById(TenantId tenantId, MobileAppId mobileAppId);
MobileApp findMobileAppById(TenantId tenantId, MobileAppId mobileAppId);
PageData<MobileApp> findMobileAppsByTenantId(TenantId tenantId, PageLink pageLink);
AndroidQrCodeConfig findAndroidQrCodeConfig(TenantId tenantId, MobileAppBundleId mobileAppBundleId);
MobileApp findByBundleIdAndPlatformType(TenantId tenantId, MobileAppBundleId mobileAppBundleId, PlatformType platformType);
IosQrCodeConfig findIosQrCodeConfig(TenantId tenantId, MobileAppBundleId mobileAppBundleId);
MobileApp findMobileAppByPkgNameAndPlatformType(String pkgName, PlatformType platform);
void deleteMobileAppById(TenantId tenantId, MobileAppId mobileAppId);
void deleteMobileAppsByTenantId(TenantId tenantId);

7
common/data/src/main/java/org/thingsboard/server/common/data/mobile/MobileLoginInfo.java → common/data/src/main/java/org/thingsboard/server/common/data/mobile/LoginMobileInfo.java

@ -15,14 +15,9 @@
*/
package org.thingsboard.server.common.data.mobile;
import lombok.AllArgsConstructor;
import lombok.Data;
import org.thingsboard.server.common.data.oauth2.OAuth2ClientLoginInfo;
import java.util.List;
@Data
@AllArgsConstructor
public class MobileLoginInfo {
List<OAuth2ClientLoginInfo> oAuth2ClientLoginInfos;
public record LoginMobileInfo(List<OAuth2ClientLoginInfo> oAuth2ClientLoginInfos, MobileAppVersionInfo versionInfo) {
}

7
common/data/src/main/java/org/thingsboard/server/common/data/mobile/MobileApp.java

@ -16,7 +16,6 @@
package org.thingsboard.server.common.data.mobile;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.JsonNode;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.Valid;
import jakarta.validation.constraints.NotBlank;
@ -31,7 +30,6 @@ import org.thingsboard.server.common.data.id.MobileAppId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.oauth2.PlatformType;
import org.thingsboard.server.common.data.validation.Length;
import org.thingsboard.server.common.data.validation.NoXss;
@EqualsAndHashCode(callSuper = true)
@Data
@ -53,9 +51,8 @@ public class MobileApp extends BaseData<MobileAppId> implements HasTenantId, Has
@Schema(description = "Application status: PUBLISHED, DEPRECATED, SUSPENDED", requiredMode = Schema.RequiredMode.REQUIRED)
private MobileAppStatus status;
@Schema(description = "Application version info")
@NoXss
@Length(fieldName = "versionInfo", max = 16384)
private JsonNode versionInfo;
@Valid
private MobileAppVersionInfo versionInfo;
@Schema(description = "Application qr code configuration")
@Valid
private QrCodeConfig qrCodeConfig;

6
common/data/src/main/java/org/thingsboard/server/common/data/mobile/MobileAppBundleInfo.java

@ -35,6 +35,12 @@ public class MobileAppBundleInfo extends MobileAppBundle {
@Schema(description = "List of available oauth2 clients")
private List<OAuth2ClientInfo> oauth2ClientInfos;
public MobileAppBundleInfo(MobileAppBundle mobileApp, String androidPkgName, String iosPkgName) {
super(mobileApp);
this.androidPkgName = androidPkgName;
this.iosPkgName = iosPkgName;
}
public MobileAppBundleInfo(MobileAppBundle mobileApp, String androidPkgName, String iosPkgName, List<OAuth2ClientInfo> oauth2ClientInfos) {
super(mobileApp);
this.androidPkgName = androidPkgName;

49
common/data/src/main/java/org/thingsboard/server/common/data/mobile/MobileAppVersionInfo.java

@ -0,0 +1,49 @@
/**
* 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 io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
import org.thingsboard.server.common.data.validation.Length;
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@EqualsAndHashCode
public class MobileAppVersionInfo {
@Schema(description = "Minimum supported version")
@Length(fieldName = "minVersion", max = 20)
private String minVersion;
@Schema(description = "Release notes of minimum supported version")
@Length(fieldName = "minVersionReleaseNotes", max = 10000)
private String minVersionReleaseNotes;
@Schema(description = "Latest supported version")
@Length(fieldName = "latestVersion", max = 20)
private String latestVersion;
@Schema(description = "Release notes of latest supported version")
@Length(fieldName = "latestVersionReleaseNotes", max = 10000)
private String latestVersionReleaseNotes;
}

1
common/data/src/main/java/org/thingsboard/server/common/data/mobile/QrCodeConfig.java

@ -33,5 +33,6 @@ public interface QrCodeConfig {
PlatformType getType();
String getStoreLink();
}

11
common/data/src/main/java/org/thingsboard/server/common/data/mobile/UserMobileInfo.java

@ -15,13 +15,8 @@
*/
package org.thingsboard.server.common.data.mobile;
import lombok.Data;
import java.util.Map;
@Data
public class UserMobileInfo {
private Map<String, MobileSessionInfo> sessions;
import org.thingsboard.server.common.data.HomeDashboardInfo;
import org.thingsboard.server.common.data.User;
public record UserMobileInfo(User user, HomeDashboardInfo homeDashboardInfo, MobileLayoutConfig layoutConfig) {
}

14
common/data/src/main/java/org/thingsboard/server/common/data/mobile/MobileUserInfo.java → common/data/src/main/java/org/thingsboard/server/common/data/mobile/UserMobileSessionInfo.java

@ -15,15 +15,13 @@
*/
package org.thingsboard.server.common.data.mobile;
import lombok.AllArgsConstructor;
import lombok.Data;
import org.thingsboard.server.common.data.HomeDashboardInfo;
import org.thingsboard.server.common.data.User;
import java.util.Map;
@Data
@AllArgsConstructor
public class MobileUserInfo {
User user;
HomeDashboardInfo homeDashboardInfo;
MobileLayoutConfig layoutConfig;
public class UserMobileSessionInfo {
private Map<String, MobileSessionInfo> sessions;
}

9
dao/src/main/java/org/thingsboard/server/dao/mobile/MobileAppBundleDao.java

@ -18,6 +18,7 @@ package org.thingsboard.server.dao.mobile;
import org.thingsboard.server.common.data.id.MobileAppBundleId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.mobile.MobileAppBundle;
import org.thingsboard.server.common.data.mobile.MobileAppBundleInfo;
import org.thingsboard.server.common.data.mobile.MobileAppBundleOauth2Client;
import org.thingsboard.server.common.data.oauth2.PlatformType;
import org.thingsboard.server.common.data.page.PageData;
@ -28,13 +29,15 @@ import java.util.List;
public interface MobileAppBundleDao extends Dao<MobileAppBundle> {
PageData<MobileAppBundle> findByTenantId(TenantId tenantId, PageLink pageLink);
PageData<MobileAppBundleInfo> findInfosByTenantId(TenantId tenantId, PageLink pageLink);
MobileAppBundleInfo findInfoById(TenantId tenantId, MobileAppBundleId mobileAppBundleId);
List<MobileAppBundleOauth2Client> findOauth2ClientsByMobileAppBundleId(TenantId tenantId, MobileAppBundleId mobileAppBundleId);
void addOauth2Client(MobileAppBundleOauth2Client mobileAppBundleOauth2Client);
void addOauth2Client(TenantId tenantId, MobileAppBundleOauth2Client mobileAppBundleOauth2Client);
void removeOauth2Client(MobileAppBundleOauth2Client mobileAppBundleOauth2Client);
void removeOauth2Client(TenantId tenantId, MobileAppBundleOauth2Client mobileAppBundleOauth2Client);
MobileAppBundle findByPkgNameAndPlatform(TenantId tenantId, String pkgName, PlatformType platform);

66
dao/src/main/java/org/thingsboard/server/dao/mobile/MobileAppBundleServiceImpl.java

@ -25,7 +25,6 @@ import org.thingsboard.server.common.data.id.HasId;
import org.thingsboard.server.common.data.id.MobileAppBundleId;
import org.thingsboard.server.common.data.id.OAuth2ClientId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.mobile.MobileApp;
import org.thingsboard.server.common.data.mobile.MobileAppBundle;
import org.thingsboard.server.common.data.mobile.MobileAppBundleInfo;
import org.thingsboard.server.common.data.mobile.MobileAppBundleOauth2Client;
@ -53,14 +52,12 @@ public class MobileAppBundleServiceImpl extends AbstractEntityService implements
private OAuth2ClientDao oauth2ClientDao;
@Autowired
private MobileAppBundleDao mobileAppBundleDao;
@Autowired
private MobileAppService mobileAppService;
@Override
public MobileAppBundle saveMobileAppBundle(TenantId tenantId, MobileAppBundle mobileApp) {
log.trace("Executing saveMobileApp [{}]", mobileApp);
public MobileAppBundle saveMobileAppBundle(TenantId tenantId, MobileAppBundle mobileAppBundle) {
log.trace("Executing saveMobileAppBundle [{}]", mobileAppBundle);
try {
MobileAppBundle savedMobileApp = mobileAppBundleDao.save(tenantId, mobileApp);
MobileAppBundle savedMobileApp = mobileAppBundleDao.save(tenantId, mobileAppBundle);
eventPublisher.publishEvent(SaveEntityEvent.builder().tenantId(tenantId).entity(savedMobileApp).build());
return savedMobileApp;
} catch (Exception e) {
@ -73,9 +70,18 @@ public class MobileAppBundleServiceImpl extends AbstractEntityService implements
@Override
public void deleteMobileAppBundleById(TenantId tenantId, MobileAppBundleId mobileAppBundleId) {
log.trace("Executing deleteMobileAppById [{}]", mobileAppBundleId.getId());
log.trace("Executing deleteMobileAppBundleById [{}]", mobileAppBundleId.getId());
mobileAppBundleDao.removeById(tenantId, mobileAppBundleId.getId());
eventPublisher.publishEvent(DeleteEntityEvent.builder().tenantId(tenantId).entityId(mobileAppBundleId).build());
try {
mobileAppBundleDao.removeById(tenantId, mobileAppBundleId.getId());
eventPublisher.publishEvent(DeleteEntityEvent.builder().tenantId(tenantId).entityId(mobileAppBundleId).build());
} catch (Exception e) {
checkConstraintViolation(e, "fk_android_app_id", "The mobile app referenced by the mobile bundle cannot be deleted!",
"fk_ios_app_id", "The mobile app referenced by the mobile bundle cannot be deleted!");
throw e;
}
}
@Override
@ -86,19 +92,20 @@ public class MobileAppBundleServiceImpl extends AbstractEntityService implements
@Override
public PageData<MobileAppBundleInfo> findMobileAppBundleInfosByTenantId(TenantId tenantId, PageLink pageLink) {
log.trace("Executing findMobileAppInfosByTenantId [{}]", tenantId);
PageData<MobileAppBundle> mobileBundles = mobileAppBundleDao.findByTenantId(tenantId, pageLink);
return mobileBundles.mapData(this::getMobileAppBundleInfo);
log.trace("Executing findMobileAppBundleInfosByTenantId [{}]", tenantId);
PageData<MobileAppBundleInfo> mobileBundles = mobileAppBundleDao.findInfosByTenantId(tenantId, pageLink);
mobileBundles.getData().forEach(this::fetchOauth2Clients);
return mobileBundles;
}
@Override
public MobileAppBundleInfo findMobileAppBundleInfoById(TenantId tenantId, MobileAppBundleId mobileAppIdBundle) {
log.trace("Executing findMobileAppInfoById [{}] [{}]", tenantId, mobileAppIdBundle);
MobileAppBundle mobileAppBundle = mobileAppBundleDao.findById(tenantId, mobileAppIdBundle.getId());
if (mobileAppBundle == null) {
return null;
log.trace("Executing findMobileAppBundleInfoById [{}] [{}]", tenantId, mobileAppIdBundle);
MobileAppBundleInfo mobileAppBundleInfo = mobileAppBundleDao.findInfoById(tenantId, mobileAppIdBundle);
if (mobileAppBundleInfo != null) {
fetchOauth2Clients(mobileAppBundleInfo);
}
return getMobileAppBundleInfo(mobileAppBundle);
return mobileAppBundleInfo;
}
@Override
@ -115,10 +122,10 @@ public class MobileAppBundleServiceImpl extends AbstractEntityService implements
newClientList.removeIf(existingClients::contains);
for (MobileAppBundleOauth2Client client : toRemoveList) {
mobileAppBundleDao.removeOauth2Client(client);
mobileAppBundleDao.removeOauth2Client(tenantId, client);
}
for (MobileAppBundleOauth2Client client : newClientList) {
mobileAppBundleDao.addOauth2Client(client);
mobileAppBundleDao.addOauth2Client(tenantId, client);
}
eventPublisher.publishEvent(SaveEntityEvent.builder().tenantId(tenantId)
.entityId(mobileAppBundleId).created(false).build());
@ -126,7 +133,7 @@ public class MobileAppBundleServiceImpl extends AbstractEntityService implements
@Override
public MobileAppBundle findMobileAppBundleByPkgNameAndPlatform(TenantId tenantId, String pkgName, PlatformType platform) {
log.trace("Executing findMobileAppBundle, tenantId [{}], pkgName [{}], platform [{}]", tenantId, pkgName, platform);
log.trace("Executing findMobileAppBundleByPkgNameAndPlatform, tenantId [{}], pkgName [{}], platform [{}]", tenantId, pkgName, platform);
return mobileAppBundleDao.findByPkgNameAndPlatform(tenantId, pkgName, platform);
}
@ -142,29 +149,26 @@ public class MobileAppBundleServiceImpl extends AbstractEntityService implements
}
@Override
public void deleteMobileAppsByTenantId(TenantId tenantId) {
public void deleteMobileAppBundlesByTenantId(TenantId tenantId) {
log.trace("Executing deleteMobileAppsByTenantId, tenantId [{}]", tenantId);
mobileAppBundleDao.deleteByTenantId(tenantId);
}
@Override
public void deleteByTenantId(TenantId tenantId) {
deleteMobileAppsByTenantId(tenantId);
}
private MobileAppBundleInfo getMobileAppBundleInfo(MobileAppBundle mobileAppBundle) {
List<OAuth2ClientInfo> clients = oauth2ClientDao.findByMobileAppBundleId(mobileAppBundle.getUuidId()).stream()
.map(OAuth2ClientInfo::new)
.sorted(Comparator.comparing(OAuth2ClientInfo::getTitle))
.collect(Collectors.toList());
MobileApp androidApp = mobileAppService.findMobileAppById(mobileAppBundle.getTenantId(), mobileAppBundle.getAndroidAppId());
MobileApp iosApp = mobileAppService.findMobileAppById(mobileAppBundle.getTenantId(), mobileAppBundle.getIosAppId());
return new MobileAppBundleInfo(mobileAppBundle, androidApp != null ? androidApp.getPkgName() : null,
iosApp != null ? iosApp.getPkgName() : null, clients);
deleteMobileAppBundlesByTenantId(tenantId);
}
@Override
public EntityType getEntityType() {
return EntityType.MOBILE_APP_BUNDLE;
}
private void fetchOauth2Clients(MobileAppBundleInfo mobileAppBundleInfo) {
List<OAuth2ClientInfo> clients = oauth2ClientDao.findByMobileAppBundleId(mobileAppBundleInfo.getUuidId()).stream()
.map(OAuth2ClientInfo::new)
.sorted(Comparator.comparing(OAuth2ClientInfo::getTitle))
.collect(Collectors.toList());
mobileAppBundleInfo.setOauth2ClientInfos(clients);
}
}

2
dao/src/main/java/org/thingsboard/server/dao/mobile/MobileAppDao.java

@ -30,4 +30,6 @@ public interface MobileAppDao extends Dao<MobileApp> {
PageData<MobileApp> findByTenantId(TenantId tenantId, PageLink pageLink);
void deleteByTenantId(TenantId tenantId);
MobileApp findByPkgNameAndPlatformType(TenantId tenantId, String pkgName, PlatformType platform);
}

15
dao/src/main/java/org/thingsboard/server/dao/mobile/MobileAppServiceImpl.java

@ -19,15 +19,12 @@ import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
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.id.EntityId;
import org.thingsboard.server.common.data.id.HasId;
import org.thingsboard.server.common.data.id.MobileAppBundleId;
import org.thingsboard.server.common.data.id.MobileAppId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.mobile.AndroidQrCodeConfig;
import org.thingsboard.server.common.data.mobile.IosQrCodeConfig;
import org.thingsboard.server.common.data.mobile.MobileApp;
import org.thingsboard.server.common.data.oauth2.PlatformType;
import org.thingsboard.server.common.data.page.PageData;
@ -97,17 +94,15 @@ public class MobileAppServiceImpl extends AbstractEntityService implements Mobil
}
@Override
public AndroidQrCodeConfig findAndroidQrCodeConfig(TenantId tenantId, MobileAppBundleId mobileAppBundleId) {
public MobileApp findByBundleIdAndPlatformType(TenantId tenantId, MobileAppBundleId mobileAppBundleId, PlatformType platformType) {
log.trace("Executing findAndroidQrConfig, tenantId [{}], mobileAppBundleId [{}]", tenantId, mobileAppBundleId);
MobileApp mobileApp = mobileAppDao.findByBundleIdAndPlatformType(tenantId, mobileAppBundleId, PlatformType.ANDROID);
return mobileApp != null ? JacksonUtil.convertValue(mobileApp.getQrCodeConfig(), AndroidQrCodeConfig.class) : null;
return mobileAppDao.findByBundleIdAndPlatformType(tenantId, mobileAppBundleId, platformType);
}
@Override
public IosQrCodeConfig findIosQrCodeConfig(TenantId tenantId, MobileAppBundleId mobileAppBundleId) {
log.trace("Executing findAndroidQrConfig, tenantId [{}], mobileAppBundleId [{}]", tenantId, mobileAppBundleId);
MobileApp mobileApp = mobileAppDao.findByBundleIdAndPlatformType(tenantId, mobileAppBundleId, PlatformType.IOS);
return mobileApp != null ? JacksonUtil.convertValue(mobileApp.getQrCodeConfig(), IosQrCodeConfig.class) : null;
public MobileApp findMobileAppByPkgNameAndPlatformType(String pkgName, PlatformType platform) {
log.trace("Executing findMobileAppByPkgNameAndPlatformType, pkgName [{}], platform [{}]", pkgName, platform);
return mobileAppDao.findByPkgNameAndPlatformType(TenantId.SYS_TENANT_ID, pkgName, platform);
}
@Override

12
dao/src/main/java/org/thingsboard/server/dao/mobile/QrCodeSettingService.java

@ -16,19 +16,17 @@
package org.thingsboard.server.dao.mobile;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.mobile.AndroidQrCodeConfig;
import org.thingsboard.server.common.data.mobile.IosQrCodeConfig;
import org.thingsboard.server.common.data.mobile.QrCodeConfig;
import org.thingsboard.server.common.data.mobile.QrCodeSettings;
import org.thingsboard.server.common.data.oauth2.PlatformType;
public interface QrCodeSettingService {
QrCodeSettings saveQrCodeSettings(TenantId tenantId, QrCodeSettings settings);
QrCodeSettings saveQrCodeSettings(TenantId tenantId, QrCodeSettings qrCodeSettings);
QrCodeSettings getQrCodeSettings(TenantId tenantId);
QrCodeSettings findQrCodeSettings(TenantId tenantId);
AndroidQrCodeConfig getAndroidQrCodeConfig(TenantId sysTenantId);
IosQrCodeConfig getIosQrCodeConfig(TenantId sysTenantId);
QrCodeConfig findAppQrCodeConfig(TenantId sysTenantId, PlatformType platformType);
void deleteByTenantId(TenantId tenantId);

21
dao/src/main/java/org/thingsboard/server/dao/mobile/QrCodeSettingServiceImpl.java

@ -21,11 +21,11 @@ import org.springframework.beans.factory.annotation.Value;
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.mobile.AndroidQrCodeConfig;
import org.thingsboard.server.common.data.mobile.BadgePosition;
import org.thingsboard.server.common.data.mobile.IosQrCodeConfig;
import org.thingsboard.server.common.data.mobile.QrCodeConfig;
import org.thingsboard.server.common.data.mobile.QrCodeSettings;
import org.thingsboard.server.common.data.mobile.QRCodeConfig;
import org.thingsboard.server.common.data.oauth2.PlatformType;
import org.thingsboard.server.dao.entity.AbstractCachedEntityService;
import org.thingsboard.server.dao.service.DataValidator;
@ -67,7 +67,7 @@ public class QrCodeSettingServiceImpl extends AbstractCachedEntityService<Tenant
}
@Override
public QrCodeSettings getQrCodeSettings(TenantId tenantId) {
public QrCodeSettings findQrCodeSettings(TenantId tenantId) {
log.trace("Executing getMobileAppSettings for tenant [{}] ", tenantId);
QrCodeSettings qrCodeSettings = cache.getAndPutInTransaction(tenantId,
() -> qrCodeSettingsDao.findByTenantId(tenantId), true);
@ -75,17 +75,10 @@ public class QrCodeSettingServiceImpl extends AbstractCachedEntityService<Tenant
}
@Override
public AndroidQrCodeConfig getAndroidQrCodeConfig(TenantId tenantId) {
log.trace("Executing getAndroidAppConfig for tenant [{}] ", tenantId);
QrCodeSettings qrCodeSettings = getQrCodeSettings(tenantId);
return qrCodeSettings.getMobileAppBundleId() != null ? mobileAppService.findAndroidQrCodeConfig(tenantId, qrCodeSettings.getMobileAppBundleId()) : null;
}
@Override
public IosQrCodeConfig getIosQrCodeConfig(TenantId tenantId) {
log.trace("Executing getIOSAppConfig for tenant [{}] ", tenantId);
QrCodeSettings qrCodeSettings = getQrCodeSettings(tenantId);
return qrCodeSettings.getMobileAppBundleId() != null ? mobileAppService.findIosQrCodeConfig(tenantId, qrCodeSettings.getMobileAppBundleId()) : null;
public QrCodeConfig findAppQrCodeConfig(TenantId tenantId, PlatformType platformType) {
log.trace("Executing findAppQrCodeConfig for tenant [{}] ", tenantId);
QrCodeSettings qrCodeSettings = findQrCodeSettings(tenantId);
return qrCodeSettings.getMobileAppBundleId() != null ? mobileAppService.findByBundleIdAndPlatformType(tenantId, qrCodeSettings.getMobileAppBundleId(), platformType).getQrCodeConfig() : null;
}
@Override

2
dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java

@ -464,7 +464,7 @@ public class ModelConstants {
public static final String MOBILE_APP_BUNDLE_LAYOUT_CONFIG_PROPERTY = "layout_config";
public static final String MOBILE_APP_BUNDLE_OAUTH2_ENABLED_PROPERTY = "oauth2_enabled";
public static final String MOBILE_APP_BUNDLE_OAUTH2_CLIENT_TABLE_NAME = "mobile_app_oauth2_client";
public static final String MOBILE_APP_BUNDLE_OAUTH2_CLIENT_TABLE_NAME = "mobile_app_bundle_oauth2_client";
public static final String MOBILE_APP_BUNDLE_OAUTH2_CLIENT_CLIENT_ID_PROPERTY = "oauth2_client_id";
public static final String MOBILE_APP_BUNDLE_OAUTH2_CLIENT_MOBILE_APP_BUNDLE_ID_PROPERTY = "mobile_app_bundle_id";

114
dao/src/main/java/org/thingsboard/server/dao/model/sql/AbstractMobileAppBundleEntity.java

@ -0,0 +1,114 @@
/**
* 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.dao.model.sql;
import com.fasterxml.jackson.databind.JsonNode;
import jakarta.persistence.Column;
import jakarta.persistence.Convert;
import jakarta.persistence.MappedSuperclass;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.thingsboard.server.common.data.id.MobileAppBundleId;
import org.thingsboard.server.common.data.id.MobileAppId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.mobile.MobileAppBundle;
import org.thingsboard.server.common.data.mobile.MobileLayoutConfig;
import org.thingsboard.server.dao.model.BaseSqlEntity;
import org.thingsboard.server.dao.model.ModelConstants;
import org.thingsboard.server.dao.util.mapping.JsonConverter;
import java.util.UUID;
import static org.thingsboard.server.dao.model.ModelConstants.TENANT_ID_COLUMN;
@Data
@EqualsAndHashCode(callSuper = true)
@MappedSuperclass
public abstract class AbstractMobileAppBundleEntity<T extends MobileAppBundle> extends BaseSqlEntity<T> {
@Column(name = TENANT_ID_COLUMN)
private UUID tenantId;
@Column(name = ModelConstants.MOBILE_APP_BUNDLE_TITLE_PROPERTY)
private String title;
@Column(name = ModelConstants.MOBILE_APP_BUNDLE_DESCRIPTION_PROPERTY)
private String description;
@Column(name = ModelConstants.MOBILE_APP_BUNDLE_ANDROID_APP_ID_PROPERTY)
private UUID androidAppId;
@Column(name = ModelConstants.MOBILE_APP_BUNDLE_IOS_APP_ID_PROPERTY)
private UUID iosAppID;
@Convert(converter = JsonConverter.class)
@Column(name = ModelConstants.MOBILE_APP_BUNDLE_LAYOUT_CONFIG_PROPERTY)
private JsonNode layoutConfig;
@Column(name = ModelConstants.MOBILE_APP_BUNDLE_OAUTH2_ENABLED_PROPERTY)
private Boolean oauth2Enabled;
public AbstractMobileAppBundleEntity() {
super();
}
public AbstractMobileAppBundleEntity(MobileAppBundleEntity mobileAppBundleEntity) {
super(mobileAppBundleEntity);
this.tenantId = mobileAppBundleEntity.getTenantId();
this.title = mobileAppBundleEntity.getTitle();
this.description = mobileAppBundleEntity.getDescription();
this.androidAppId = mobileAppBundleEntity.getAndroidAppId();
this.iosAppID = mobileAppBundleEntity.getIosAppID();
this.layoutConfig = mobileAppBundleEntity.getLayoutConfig();
this.oauth2Enabled = mobileAppBundleEntity.getOauth2Enabled();
}
public AbstractMobileAppBundleEntity(T mobileAppBundle) {
super(mobileAppBundle);
if (mobileAppBundle.getTenantId() != null) {
this.tenantId = mobileAppBundle.getTenantId().getId();
}
this.title = mobileAppBundle.getTitle();
this.description = mobileAppBundle.getDescription();
if (mobileAppBundle.getAndroidAppId() != null) {
this.androidAppId = mobileAppBundle.getAndroidAppId().getId();
}
if (mobileAppBundle.getIosAppId() != null) {
this.iosAppID = mobileAppBundle.getIosAppId().getId();
}
this.layoutConfig = toJson(mobileAppBundle.getLayoutConfig());
this.oauth2Enabled = mobileAppBundle.getOauth2Enabled();
}
protected MobileAppBundle toMobileAppBundle() {
MobileAppBundle mobileAppBundle = new MobileAppBundle(new MobileAppBundleId(id));
mobileAppBundle.setCreatedTime(createdTime);
mobileAppBundle.setTitle(title);
mobileAppBundle.setDescription(description);
if (tenantId != null) {
mobileAppBundle.setTenantId(TenantId.fromUUID(tenantId));
}
if (androidAppId != null) {
mobileAppBundle.setAndroidAppId(new MobileAppId(androidAppId));
}
if (iosAppID != null) {
mobileAppBundle.setIosAppId(new MobileAppId(iosAppID));
}
mobileAppBundle.setLayoutConfig(fromJson(layoutConfig, MobileLayoutConfig.class));
mobileAppBundle.setOauth2Enabled(oauth2Enabled);
return mobileAppBundle;
}
}

75
dao/src/main/java/org/thingsboard/server/dao/model/sql/MobileAppBundleEntity.java

@ -15,93 +15,30 @@
*/
package org.thingsboard.server.dao.model.sql;
import com.fasterxml.jackson.databind.JsonNode;
import jakarta.persistence.Column;
import jakarta.persistence.Convert;
import jakarta.persistence.Entity;
import jakarta.persistence.Table;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.thingsboard.server.common.data.id.MobileAppBundleId;
import org.thingsboard.server.common.data.id.MobileAppId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.mobile.MobileAppBundle;
import org.thingsboard.server.common.data.mobile.MobileLayoutConfig;
import org.thingsboard.server.dao.model.BaseSqlEntity;
import org.thingsboard.server.dao.model.ModelConstants;
import org.thingsboard.server.dao.util.mapping.JsonConverter;
import java.util.UUID;
import static org.thingsboard.server.dao.model.ModelConstants.TENANT_ID_COLUMN;
import static org.thingsboard.server.dao.model.ModelConstants.MOBILE_APP_BUNDLE_TABLE_NAME;
@Data
@EqualsAndHashCode(callSuper = true)
@Entity
@Table(name = ModelConstants.MOBILE_APP_BUNDLE_TABLE_NAME)
public class MobileAppBundleEntity extends BaseSqlEntity<MobileAppBundle> {
@Column(name = TENANT_ID_COLUMN)
private UUID tenantId;
@Column(name = ModelConstants.MOBILE_APP_BUNDLE_TITLE_PROPERTY)
private String title;
@Column(name = ModelConstants.MOBILE_APP_BUNDLE_DESCRIPTION_PROPERTY)
private String description;
@Column(name = ModelConstants.MOBILE_APP_BUNDLE_ANDROID_APP_ID_PROPERTY)
private UUID androidAppId;
@Column(name = ModelConstants.MOBILE_APP_BUNDLE_IOS_APP_ID_PROPERTY)
private UUID iosAppID;
@Convert(converter = JsonConverter.class)
@Column(name = ModelConstants.MOBILE_APP_BUNDLE_LAYOUT_CONFIG_PROPERTY)
private JsonNode layoutConfig;
@Column(name = ModelConstants.MOBILE_APP_BUNDLE_OAUTH2_ENABLED_PROPERTY)
private Boolean oauth2Enabled;
@Table(name = MOBILE_APP_BUNDLE_TABLE_NAME)
public final class MobileAppBundleEntity extends AbstractMobileAppBundleEntity<MobileAppBundle> {
public MobileAppBundleEntity() {
super();
}
public MobileAppBundleEntity(MobileAppBundle mobile) {
super(mobile);
if (mobile.getTenantId() != null) {
this.tenantId = mobile.getTenantId().getId();
}
this.title = mobile.getTitle();
this.description = mobile.getDescription();
if (mobile.getAndroidAppId() != null) {
this.androidAppId = mobile.getAndroidAppId().getId();
}
if (mobile.getIosAppId() != null) {
this.iosAppID = mobile.getIosAppId().getId();
}
this.layoutConfig = toJson(mobile.getLayoutConfig());
this.oauth2Enabled = mobile.getOauth2Enabled();
public MobileAppBundleEntity(MobileAppBundle mobileAppBundle) {
super(mobileAppBundle);
}
@Override
public MobileAppBundle toData() {
MobileAppBundle mobileAppBundle = new MobileAppBundle();
mobileAppBundle.setId(new MobileAppBundleId(id));
mobileAppBundle.setCreatedTime(createdTime);
mobileAppBundle.setTitle(title);
mobileAppBundle.setDescription(description);
if (tenantId != null) {
mobileAppBundle.setTenantId(TenantId.fromUUID(tenantId));
}
if (androidAppId != null) {
mobileAppBundle.setAndroidAppId(new MobileAppId(androidAppId));
}
if (iosAppID != null) {
mobileAppBundle.setIosAppId(new MobileAppId(iosAppID));
}
mobileAppBundle.setLayoutConfig(fromJson(layoutConfig, MobileLayoutConfig.class));
mobileAppBundle.setOauth2Enabled(oauth2Enabled);
return mobileAppBundle;
return super.toMobileAppBundle();
}
}

43
dao/src/main/java/org/thingsboard/server/dao/model/sql/MobileAppBundleInfoEntity.java

@ -0,0 +1,43 @@
/**
* 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.dao.model.sql;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.thingsboard.server.common.data.mobile.MobileAppBundleInfo;
@Data
@EqualsAndHashCode(callSuper = true)
public class MobileAppBundleInfoEntity extends AbstractMobileAppBundleEntity<MobileAppBundleInfo> {
private String androidPkgName;
private String iosPkgName;
public MobileAppBundleInfoEntity() {
super();
}
public MobileAppBundleInfoEntity(MobileAppBundleEntity mobileAppBundleEntity, String androidPkgName, String iosPkgName) {
super(mobileAppBundleEntity);
this.androidPkgName = androidPkgName;
this.iosPkgName = iosPkgName;
}
@Override
public MobileAppBundleInfo toData() {
return new MobileAppBundleInfo(super.toMobileAppBundle(), androidPkgName, iosPkgName);
}
}

5
dao/src/main/java/org/thingsboard/server/dao/model/sql/MobileAppEntity.java

@ -28,6 +28,7 @@ import org.thingsboard.server.common.data.id.MobileAppId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.mobile.MobileAppStatus;
import org.thingsboard.server.common.data.mobile.MobileApp;
import org.thingsboard.server.common.data.mobile.MobileAppVersionInfo;
import org.thingsboard.server.common.data.mobile.QrCodeConfig;
import org.thingsboard.server.common.data.oauth2.PlatformType;
import org.thingsboard.server.dao.model.BaseSqlEntity;
@ -82,7 +83,7 @@ public class MobileAppEntity extends BaseSqlEntity<MobileApp> {
this.appSecret = mobile.getAppSecret();
this.platformType = mobile.getPlatformType();
this.status = mobile.getStatus();
this.versionInfo = mobile.getVersionInfo();
this.versionInfo = toJson(mobile.getVersionInfo());
this.qrCodeConfig = toJson(mobile.getQrCodeConfig());
}
@ -98,7 +99,7 @@ public class MobileAppEntity extends BaseSqlEntity<MobileApp> {
mobile.setAppSecret(appSecret);
mobile.setPlatformType(platformType);
mobile.setStatus(status);
mobile.setVersionInfo(versionInfo);
mobile.setVersionInfo(fromJson(versionInfo, MobileAppVersionInfo.class));
mobile.setQrCodeConfig(fromJson(qrCodeConfig, QrCodeConfig.class));
return mobile;
}

18
dao/src/main/java/org/thingsboard/server/dao/sql/mobile/JpaMobileAppBundleDao.java

@ -22,6 +22,7 @@ import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.id.MobileAppBundleId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.mobile.MobileAppBundle;
import org.thingsboard.server.common.data.mobile.MobileAppBundleInfo;
import org.thingsboard.server.common.data.mobile.MobileAppBundleOauth2Client;
import org.thingsboard.server.common.data.oauth2.PlatformType;
import org.thingsboard.server.common.data.page.PageData;
@ -29,8 +30,8 @@ import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.dao.DaoUtil;
import org.thingsboard.server.dao.mobile.MobileAppBundleDao;
import org.thingsboard.server.dao.model.sql.MobileAppBundleEntity;
import org.thingsboard.server.dao.model.sql.MobileAppOauth2ClientCompositeKey;
import org.thingsboard.server.dao.model.sql.MobileAppBundleOauth2ClientEntity;
import org.thingsboard.server.dao.model.sql.MobileAppOauth2ClientCompositeKey;
import org.thingsboard.server.dao.sql.JpaAbstractDao;
import org.thingsboard.server.dao.util.SqlDao;
@ -56,8 +57,13 @@ public class JpaMobileAppBundleDao extends JpaAbstractDao<MobileAppBundleEntity,
}
@Override
public PageData<MobileAppBundle> findByTenantId(TenantId tenantId, PageLink pageLink) {
return DaoUtil.toPageData(mobileAppBundleRepository.findByTenantId(tenantId.getId(), pageLink.getTextSearch(), DaoUtil.toPageable(pageLink)));
public PageData<MobileAppBundleInfo> findInfosByTenantId(TenantId tenantId, PageLink pageLink) {
return DaoUtil.toPageData(mobileAppBundleRepository.findInfoByTenantId(tenantId.getId(), pageLink.getTextSearch(), DaoUtil.toPageable(pageLink)));
}
@Override
public MobileAppBundleInfo findInfoById(TenantId tenantId, MobileAppBundleId mobileAppBundleId) {
return DaoUtil.getData(mobileAppBundleRepository.findInfoById(mobileAppBundleId.getId()));
}
@Override
@ -66,19 +72,19 @@ public class JpaMobileAppBundleDao extends JpaAbstractDao<MobileAppBundleEntity,
}
@Override
public void addOauth2Client(MobileAppBundleOauth2Client mobileAppBundleOauth2Client) {
public void addOauth2Client(TenantId tenantId, MobileAppBundleOauth2Client mobileAppBundleOauth2Client) {
mobileOauth2ProviderRepository.save(new MobileAppBundleOauth2ClientEntity(mobileAppBundleOauth2Client));
}
@Override
public void removeOauth2Client(MobileAppBundleOauth2Client mobileAppBundleOauth2Client) {
public void removeOauth2Client(TenantId tenantId, MobileAppBundleOauth2Client mobileAppBundleOauth2Client) {
mobileOauth2ProviderRepository.deleteById(new MobileAppOauth2ClientCompositeKey(mobileAppBundleOauth2Client.getMobileAppBundleId().getId(),
mobileAppBundleOauth2Client.getOAuth2ClientId().getId()));
}
@Override
public MobileAppBundle findByPkgNameAndPlatform(TenantId tenantId, String pkgName, PlatformType platform) {
return DaoUtil.getData(mobileAppBundleRepository.findByPkgNameAndPlatformType(pkgName, platform.name()));
return DaoUtil.getData(mobileAppBundleRepository.findByPkgNameAndPlatformType(pkgName, platform));
}
@Override

18
dao/src/main/java/org/thingsboard/server/dao/sql/mobile/JpaMobileAppDao.java

@ -52,14 +52,11 @@ public class JpaMobileAppDao extends JpaAbstractDao<MobileAppEntity, MobileApp>
@Override
public MobileApp findByBundleIdAndPlatformType(TenantId tenantId, MobileAppBundleId mobileAppBundleId, PlatformType platformType) {
switch (platformType) {
case ANDROID:
return DaoUtil.getData(mobileAppRepository.findAndroidAppByBundleId(mobileAppBundleId.getId()));
case IOS:
return DaoUtil.getData(mobileAppRepository.findIOSAppByBundleId(mobileAppBundleId.getId()));
default:
return null;
}
return switch (platformType) {
case ANDROID -> DaoUtil.getData(mobileAppRepository.findAndroidAppByBundleId(mobileAppBundleId.getId()));
case IOS -> DaoUtil.getData(mobileAppRepository.findIOSAppByBundleId(mobileAppBundleId.getId()));
default -> null;
};
}
@Override
@ -72,6 +69,11 @@ public class JpaMobileAppDao extends JpaAbstractDao<MobileAppEntity, MobileApp>
mobileAppRepository.deleteByTenantId(tenantId.getId());
}
@Override
public MobileApp findByPkgNameAndPlatformType(TenantId tenantId, String pkgName, PlatformType platform) {
return DaoUtil.getData(mobileAppRepository.findByPkgNameAndPlatformType(pkgName, platform));
}
@Override
public EntityType getEntityType() {
return EntityType.MOBILE_APP;

27
dao/src/main/java/org/thingsboard/server/dao/sql/mobile/MobileAppBundleRepository.java

@ -22,26 +22,43 @@ 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.oauth2.PlatformType;
import org.thingsboard.server.dao.model.sql.MobileAppBundleEntity;
import org.thingsboard.server.dao.model.sql.OAuth2ClientEntity;
import org.thingsboard.server.dao.model.sql.MobileAppBundleInfoEntity;
import java.util.List;
import java.util.UUID;
public interface MobileAppBundleRepository extends JpaRepository<MobileAppBundleEntity, UUID> {
@Query("SELECT new org.thingsboard.server.dao.model.sql.MobileAppBundleInfoEntity(b, andApp.pkgName, iosApp.pkgName) " +
"FROM MobileAppBundleEntity b " +
"LEFT JOIN MobileAppEntity andApp on b.androidAppId = andApp.id " +
"LEFT JOIN MobileAppEntity iosApp on b.iosAppID = iosApp.id " +
"WHERE b.tenantId = :tenantId AND " +
"(:searchText is NULL OR ilike(b.title, concat('%', :searchText, '%')) = true)")
Page<MobileAppBundleInfoEntity> findInfoByTenantId(@Param("tenantId") UUID tenantId,
@Param("searchText") String searchText,
Pageable pageable);
@Query("SELECT new org.thingsboard.server.dao.model.sql.MobileAppBundleInfoEntity(b, andApp.pkgName, iosApp.pkgName) " +
"FROM MobileAppBundleEntity b " +
"LEFT JOIN MobileAppEntity andApp on b.androidAppId = andApp.id " +
"LEFT JOIN MobileAppEntity iosApp on b.iosAppID = iosApp.id " +
"WHERE b.id = :bundleId ")
MobileAppBundleInfoEntity findInfoById(UUID bundleId);
@Query("SELECT b " +
"FROM MobileAppBundleEntity b " +
"LEFT JOIN MobileAppEntity a on b.androidAppId = a.id or b.iosAppID = a.id " +
"WHERE a.pkgName = :pkgName AND a.platformType = :platformType")
MobileAppBundleEntity findByPkgNameAndPlatformType(@Param("pkgName") String pkgName,
@Param("platformType") String platformType);
@Param("platformType") PlatformType platformType);
@Query("SELECT b FROM MobileAppBundleEntity b WHERE b.tenantId = :tenantId AND " +
"(:searchText is NULL OR ilike(b.title, concat('%', :searchText, '%')) = true)")
Page<MobileAppBundleEntity> findByTenantId(@Param("tenantId") UUID tenantId,
@Param("searchText") String searchText,
Pageable pageable);
@Param("searchText") String searchText,
Pageable pageable);
@Transactional
@Modifying

3
dao/src/main/java/org/thingsboard/server/dao/sql/mobile/MobileAppRepository.java

@ -22,6 +22,7 @@ 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.oauth2.PlatformType;
import org.thingsboard.server.dao.model.sql.MobileAppEntity;
import java.util.UUID;
@ -34,6 +35,8 @@ public interface MobileAppRepository extends JpaRepository<MobileAppEntity, UUID
@Param("searchText") String searchText,
Pageable pageable);
MobileAppEntity findByPkgNameAndPlatformType(@Param("pkgName") String pkgName, @Param("platformType") PlatformType platformType);
@Transactional
@Modifying
@Query("DELETE FROM MobileAppEntity r WHERE r.tenantId = :tenantId")

22
dao/src/main/java/org/thingsboard/server/dao/sql/oauth2/OAuth2ClientRepository.java

@ -37,8 +37,8 @@ public interface OAuth2ClientRepository extends JpaRepository<OAuth2ClientEntity
@Query("SELECT c " +
"FROM OAuth2ClientEntity c " +
"LEFT JOIN DomainOauth2ClientEntity dc on c.id = dc.oauth2ClientId " +
"LEFT JOIN DomainEntity domain on dc.domainId = domain.id " +
"LEFT JOIN DomainOauth2ClientEntity dc ON c.id = dc.oauth2ClientId " +
"LEFT JOIN DomainEntity domain ON dc.domainId = domain.id " +
"WHERE domain.name = :domainName AND domain.oauth2Enabled = true " +
"AND (:platformFilter IS NULL OR c.platforms IS NULL OR c.platforms = '' OR ilike(c.platforms, CONCAT('%', :platformFilter, '%')) = true)")
List<OAuth2ClientEntity> findEnabledByDomainNameAndPlatformType(@Param("domainName") String domainName,
@ -46,31 +46,31 @@ public interface OAuth2ClientRepository extends JpaRepository<OAuth2ClientEntity
@Query("SELECT c " +
"FROM OAuth2ClientEntity c " +
"LEFT JOIN MobileAppBundleOauth2ClientEntity ac on c.id = ac.oauth2ClientId " +
"LEFT JOIN MobileAppBundleEntity b on ac.mobileAppBundleId = b.id " +
"LEFT JOIN MobileAppEntity a on (b.androidAppId = a.id or b.iosAppID = a.id) " +
"WHERE a.pkgName = :pkgName AND b.oauth2Enabled = true " +
"LEFT JOIN MobileAppBundleOauth2ClientEntity ac ON c.id = ac.oauth2ClientId " +
"LEFT JOIN MobileAppBundleEntity b ON ac.mobileAppBundleId = b.id " +
"LEFT JOIN MobileAppEntity andApp ON b.androidAppId = andApp.id LEFT JOIN MobileAppEntity iosApp ON b.iosAppID = iosApp.id " +
"WHERE andApp.pkgName = :pkgName OR iosApp.pkgName = :pkgName AND b.oauth2Enabled = true " +
"AND (:platformFilter IS NULL OR c.platforms IS NULL OR c.platforms = '' OR ilike(c.platforms, CONCAT('%', :platformFilter, '%')) = true)")
List<OAuth2ClientEntity> findEnabledByPkgNameAndPlatformType(@Param("pkgName") String pkgName,
@Param("platformFilter") String platformFilter);
@Query("SELECT c " +
"FROM OAuth2ClientEntity c " +
"LEFT JOIN DomainOauth2ClientEntity dc on dc.oauth2ClientId = c.id " +
"LEFT JOIN DomainOauth2ClientEntity dc ON dc.oauth2ClientId = c.id " +
"WHERE dc.domainId = :domainId ")
List<OAuth2ClientEntity> findByDomainId(@Param("domainId") UUID domainId);
@Query("SELECT c " +
"FROM OAuth2ClientEntity c " +
"LEFT JOIN MobileAppBundleOauth2ClientEntity bc on bc.oauth2ClientId = c.id " +
"LEFT JOIN MobileAppBundleOauth2ClientEntity bc ON bc.oauth2ClientId = c.id " +
"WHERE bc.mobileAppBundleId = :mobileAppBundleId ")
List<OAuth2ClientEntity> findByMobileAppBundleId(@Param("mobileAppBundleId") UUID mobileAppBundleId);
@Query("SELECT a.appSecret " +
"FROM MobileAppEntity a " +
"LEFT JOIN MobileAppBundleEntity b on (b.androidAppId = a.id or b.iosAppID = a.id) " +
"LEFT JOIN MobileAppBundleOauth2ClientEntity bc on bc.mobileAppBundleId = b.id " +
"LEFT JOIN OAuth2ClientEntity c on bc.oauth2ClientId = c.id " +
"LEFT JOIN MobileAppBundleEntity b ON (b.androidAppId = a.id OR b.iosAppID = a.id) " +
"LEFT JOIN MobileAppBundleOauth2ClientEntity bc ON bc.mobileAppBundleId = b.id " +
"LEFT JOIN OAuth2ClientEntity c ON bc.oauth2ClientId = c.id " +
"WHERE c.id = :clientId " +
"AND a.pkgName = :pkgName")
String findAppSecret(@Param("clientId") UUID id,

12
dao/src/main/java/org/thingsboard/server/dao/user/UserServiceImpl.java

@ -46,7 +46,7 @@ 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.mobile.UserMobileSessionInfo;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.common.data.security.Authority;
@ -468,8 +468,8 @@ public class UserServiceImpl extends AbstractCachedEntityService<UserCacheKey, U
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();
UserMobileSessionInfo mobileInfo = findMobileInfo(tenantId, userId).orElseGet(() -> {
UserMobileSessionInfo newMobileInfo = new UserMobileSessionInfo();
newMobileInfo.setSessions(new HashMap<>());
return newMobileInfo;
});
@ -479,7 +479,7 @@ public class UserServiceImpl extends AbstractCachedEntityService<UserCacheKey, U
@Override
public Map<String, MobileSessionInfo> findMobileSessions(TenantId tenantId, UserId userId) {
return findMobileInfo(tenantId, userId).map(UserMobileInfo::getSessions).orElse(Collections.emptyMap());
return findMobileInfo(tenantId, userId).map(UserMobileSessionInfo::getSessions).orElse(Collections.emptyMap());
}
@Override
@ -495,9 +495,9 @@ public class UserServiceImpl extends AbstractCachedEntityService<UserCacheKey, U
}
}
private Optional<UserMobileInfo> findMobileInfo(TenantId tenantId, UserId userId) {
private Optional<UserMobileSessionInfo> findMobileInfo(TenantId tenantId, UserId userId) {
return Optional.ofNullable(userSettingsService.findUserSettings(tenantId, userId, UserSettingsType.MOBILE))
.map(UserSettings::getSettings).map(settings -> JacksonUtil.treeToValue(settings, UserMobileInfo.class));
.map(UserSettings::getSettings).map(settings -> JacksonUtil.treeToValue(settings, UserMobileSessionInfo.class));
}
@Override

23
dao/src/main/resources/sql/schema-entities.sql

@ -637,19 +637,22 @@ CREATE TABLE IF NOT EXISTS mobile_app (
platform_type varchar(32),
status varchar(32),
version_info varchar(16384),
qr_code_config varchar(16384)
qr_code_config varchar(16384),
CONSTRAINT pkg_platform_unique UNIQUE (pkg_name, platform_type)
);
CREATE TABLE IF NOT EXISTS mobile_app_bundle (
id uuid NOT NULL CONSTRAINT mobile_app_bundle_pkey PRIMARY KEY,
created_time bigint NOT NULL,
tenant_id uuid,
title varchar(255),
android_app_id uuid,
ios_app_id uuid,
description varchar(1024),
layout_config varchar(16384),
oauth2_enabled boolean
id uuid NOT NULL CONSTRAINT mobile_app_bundle_pkey PRIMARY KEY,
created_time bigint NOT NULL,
tenant_id uuid,
title varchar(255),
description varchar(1024),
android_app_id uuid UNIQUE,
ios_app_id uuid UNIQUE,
layout_config varchar(16384),
oauth2_enabled boolean,
CONSTRAINT fk_android_app_id FOREIGN KEY (android_app_id) REFERENCES mobile_app(id),
CONSTRAINT fk_ios_app_id FOREIGN KEY (ios_app_id) REFERENCES mobile_app(id)
);
CREATE TABLE IF NOT EXISTS domain_oauth2_client (

Loading…
Cancel
Save