diff --git a/application/src/main/java/org/thingsboard/server/controller/BaseController.java b/application/src/main/java/org/thingsboard/server/controller/BaseController.java index 9b7616d3fa..d2f0884ba6 100644 --- a/application/src/main/java/org/thingsboard/server/controller/BaseController.java +++ b/application/src/main/java/org/thingsboard/server/controller/BaseController.java @@ -50,6 +50,7 @@ import org.thingsboard.server.common.data.EntityView; import org.thingsboard.server.common.data.EntityViewInfo; import org.thingsboard.server.common.data.HasName; import org.thingsboard.server.common.data.HasTenantId; +import org.thingsboard.server.common.data.HomeDashboardInfo; import org.thingsboard.server.common.data.OtaPackage; import org.thingsboard.server.common.data.OtaPackageInfo; import org.thingsboard.server.common.data.StringUtils; @@ -86,6 +87,7 @@ import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.EntityIdFactory; import org.thingsboard.server.common.data.id.EntityViewId; 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.OAuth2ClientId; import org.thingsboard.server.common.data.id.OtaPackageId; @@ -101,6 +103,7 @@ import org.thingsboard.server.common.data.id.UserId; import org.thingsboard.server.common.data.id.WidgetTypeId; import org.thingsboard.server.common.data.id.WidgetsBundleId; import org.thingsboard.server.common.data.mobile.MobileApp; +import org.thingsboard.server.common.data.mobile.MobileAppBundle; import org.thingsboard.server.common.data.oauth2.OAuth2Client; import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.page.SortOrder; @@ -134,6 +137,7 @@ import org.thingsboard.server.dao.edge.EdgeService; import org.thingsboard.server.dao.entityview.EntityViewService; import org.thingsboard.server.dao.exception.DataValidationException; import org.thingsboard.server.dao.exception.IncorrectParameterException; +import org.thingsboard.server.dao.mobile.MobileAppBundleService; import org.thingsboard.server.dao.mobile.MobileAppService; import org.thingsboard.server.dao.model.ModelConstants; import org.thingsboard.server.dao.oauth2.OAuth2ClientService; @@ -197,6 +201,11 @@ import static org.thingsboard.server.dao.service.Validator.validateId; @TbCoreComponent public abstract class BaseController { + public static final String DASHBOARD_ID = "dashboardId"; + + public static final String HOME_DASHBOARD_ID = "homeDashboardId"; + public static final String HOME_DASHBOARD_HIDE_TOOLBAR = "homeDashboardHideToolbar"; + protected final Logger log = org.slf4j.LoggerFactory.getLogger(getClass()); /*Swagger UI description*/ @@ -261,6 +270,9 @@ public abstract class BaseController { @Autowired protected MobileAppService mobileAppService; + @Autowired + protected MobileAppBundleService mobileAppBundleService; + @Autowired protected OAuth2ConfigTemplateService oAuth2ConfigTemplateService; @@ -644,6 +656,9 @@ public abstract class BaseController { case MOBILE_APP: checkMobileAppId(new MobileAppId(entityId.getId()), operation); return; + case MOBILE_APP_BUNDLE: + checkMobileAppBundleId(new MobileAppBundleId(entityId.getId()), operation); + return; default: checkEntityId(entityId, entitiesService::findEntityByTenantIdAndId, operation); } @@ -832,6 +847,10 @@ public abstract class BaseController { return checkEntityId(mobileAppId, mobileAppService::findMobileAppById, operation); } + MobileAppBundle checkMobileAppBundleId(MobileAppBundleId mobileAppBundleId, Operation operation) throws ThingsboardException { + return checkEntityId(mobileAppBundleId, mobileAppBundleService::findMobileAppBundleById, operation); + } + protected I emptyId(EntityType entityType) { return (I) EntityIdFactory.getByTypeAndUuid(entityType, ModelConstants.NULL_UUID); } @@ -910,6 +929,37 @@ public abstract class BaseController { } } + protected HomeDashboardInfo getHomeDashboardInfo(SecurityUser securityUser, JsonNode additionalInfo) { + HomeDashboardInfo homeDashboardInfo = extractHomeDashboardInfoFromAdditionalInfo(additionalInfo); + if (homeDashboardInfo == null) { + if (securityUser.isCustomerUser()) { + Customer customer = customerService.findCustomerById(securityUser.getTenantId(), securityUser.getCustomerId()); + homeDashboardInfo = extractHomeDashboardInfoFromAdditionalInfo(customer.getAdditionalInfo()); + } + if (homeDashboardInfo == null) { + Tenant tenant = tenantService.findTenantById(securityUser.getTenantId()); + homeDashboardInfo = extractHomeDashboardInfoFromAdditionalInfo(tenant.getAdditionalInfo()); + } + } + return homeDashboardInfo; + } + + private HomeDashboardInfo extractHomeDashboardInfoFromAdditionalInfo(JsonNode additionalInfo) { + try { + if (additionalInfo != null && additionalInfo.has(HOME_DASHBOARD_ID) && !additionalInfo.get(HOME_DASHBOARD_ID).isNull()) { + String strDashboardId = additionalInfo.get(HOME_DASHBOARD_ID).asText(); + DashboardId dashboardId = new DashboardId(toUUID(strDashboardId)); + checkDashboardId(dashboardId, Operation.READ); + boolean hideDashboardToolbar = true; + if (additionalInfo.has(HOME_DASHBOARD_HIDE_TOOLBAR)) { + hideDashboardToolbar = additionalInfo.get(HOME_DASHBOARD_HIDE_TOOLBAR).asBoolean(); + } + return new HomeDashboardInfo(dashboardId, hideDashboardToolbar); + } + } catch (Exception ignored) {} + return null; + } + protected MediaType parseMediaType(String contentType) { try { return MediaType.parseMediaType(contentType); diff --git a/application/src/main/java/org/thingsboard/server/controller/DashboardController.java b/application/src/main/java/org/thingsboard/server/controller/DashboardController.java index 2949df35a7..4183fc45e5 100644 --- a/application/src/main/java/org/thingsboard/server/controller/DashboardController.java +++ b/application/src/main/java/org/thingsboard/server/controller/DashboardController.java @@ -96,10 +96,6 @@ public class DashboardController extends BaseController { private final TbDashboardService tbDashboardService; private final ImageService imageService; - public static final String DASHBOARD_ID = "dashboardId"; - - private static final String HOME_DASHBOARD_ID = "homeDashboardId"; - private static final String HOME_DASHBOARD_HIDE_TOOLBAR = "homeDashboardHideToolbar"; public static final String DASHBOARD_INFO_DEFINITION = "The Dashboard Info object contains lightweight information about the dashboard (e.g. title, image, assigned customers) but does not contain the heavyweight configuration JSON."; public static final String DASHBOARD_DEFINITION = "The Dashboard object is a heavyweight object that contains information about the dashboard (e.g. title, image, assigned customers) and also configuration JSON (e.g. layouts, widgets, entity aliases)."; public static final String HIDDEN_FOR_MOBILE = "Exclude dashboards that are hidden for mobile"; @@ -460,21 +456,7 @@ public class DashboardController extends BaseController { } User user = userService.findUserById(securityUser.getTenantId(), securityUser.getId()); JsonNode additionalInfo = user.getAdditionalInfo(); - HomeDashboardInfo homeDashboardInfo; - homeDashboardInfo = extractHomeDashboardInfoFromAdditionalInfo(additionalInfo); - if (homeDashboardInfo == null) { - if (securityUser.isCustomerUser()) { - Customer customer = customerService.findCustomerById(securityUser.getTenantId(), securityUser.getCustomerId()); - additionalInfo = customer.getAdditionalInfo(); - homeDashboardInfo = extractHomeDashboardInfoFromAdditionalInfo(additionalInfo); - } - if (homeDashboardInfo == null) { - Tenant tenant = tenantService.findTenantById(securityUser.getTenantId()); - additionalInfo = tenant.getAdditionalInfo(); - homeDashboardInfo = extractHomeDashboardInfoFromAdditionalInfo(additionalInfo); - } - } - return homeDashboardInfo; + return getHomeDashboardInfo(securityUser, additionalInfo); } @ApiOperation(value = "Get Tenant Home Dashboard Info (getTenantHomeDashboardInfo)", @@ -527,22 +509,6 @@ public class DashboardController extends BaseController { tenantService.saveTenant(tenant); } - private HomeDashboardInfo extractHomeDashboardInfoFromAdditionalInfo(JsonNode additionalInfo) { - try { - if (additionalInfo != null && additionalInfo.has(HOME_DASHBOARD_ID) && !additionalInfo.get(HOME_DASHBOARD_ID).isNull()) { - String strDashboardId = additionalInfo.get(HOME_DASHBOARD_ID).asText(); - DashboardId dashboardId = new DashboardId(toUUID(strDashboardId)); - checkDashboardId(dashboardId, Operation.READ); - boolean hideDashboardToolbar = true; - if (additionalInfo.has(HOME_DASHBOARD_HIDE_TOOLBAR)) { - hideDashboardToolbar = additionalInfo.get(HOME_DASHBOARD_HIDE_TOOLBAR).asBoolean(); - } - return new HomeDashboardInfo(dashboardId, hideDashboardToolbar); - } - } catch (Exception ignored) {} - return null; - } - private HomeDashboard extractHomeDashboardFromAdditionalInfo(JsonNode additionalInfo) { try { if (additionalInfo != null && additionalInfo.has(HOME_DASHBOARD_ID) && !additionalInfo.get(HOME_DASHBOARD_ID).isNull()) { diff --git a/application/src/main/java/org/thingsboard/server/controller/MobileAppBundleController.java b/application/src/main/java/org/thingsboard/server/controller/MobileAppBundleController.java new file mode 100644 index 0000000000..f54314bb9a --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/controller/MobileAppBundleController.java @@ -0,0 +1,132 @@ +/** + * 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.controller; + +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.media.ArraySchema; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.security.access.prepost.PreAuthorize; +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.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; +import org.thingsboard.server.common.data.exception.ThingsboardException; +import org.thingsboard.server.common.data.id.MobileAppBundleId; +import org.thingsboard.server.common.data.id.OAuth2ClientId; +import org.thingsboard.server.common.data.mobile.MobileAppBundle; +import org.thingsboard.server.common.data.mobile.MobileAppBundleInfo; +import org.thingsboard.server.common.data.page.PageData; +import org.thingsboard.server.common.data.page.PageLink; +import org.thingsboard.server.config.annotations.ApiOperation; +import org.thingsboard.server.queue.util.TbCoreComponent; +import org.thingsboard.server.service.entitiy.mobile.TbMobileAppBundleService; +import org.thingsboard.server.service.security.permission.Operation; +import org.thingsboard.server.service.security.permission.Resource; + +import java.util.List; +import java.util.UUID; + +import static org.thingsboard.server.controller.ControllerConstants.PAGE_NUMBER_DESCRIPTION; +import static org.thingsboard.server.controller.ControllerConstants.PAGE_SIZE_DESCRIPTION; +import static org.thingsboard.server.controller.ControllerConstants.SORT_ORDER_DESCRIPTION; +import static org.thingsboard.server.controller.ControllerConstants.SORT_PROPERTY_DESCRIPTION; +import static org.thingsboard.server.controller.ControllerConstants.SYSTEM_AUTHORITY_PARAGRAPH; +import static org.thingsboard.server.controller.ControllerConstants.UUID_WIKI_LINK; + +@RestController +@TbCoreComponent +@RequestMapping("/api") +@RequiredArgsConstructor +@Slf4j +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." + + "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") + public MobileAppBundle saveMobileAppBundle( + @Parameter(description = "A JSON value representing the Mobile Application Bundle.", required = true) + @RequestBody @Valid MobileAppBundle mobileAppBundle, + @Parameter(description = "A list of oauth2 client ids, separated by comma ','", array = @ArraySchema(schema = @Schema(type = "string"))) + @RequestParam(name = "oauth2ClientIds", required = false) UUID[] ids) throws Exception { + mobileAppBundle.setTenantId(getTenantId()); + checkEntity(mobileAppBundle.getId(), mobileAppBundle, Resource.MOBILE_APP_BUNDLE); + return tbMobileAppBundleService.save(mobileAppBundle, getOAuth2ClientIds(ids), getCurrentUser()); + } + + @ApiOperation(value = "Update oauth2 clients (updateOauth2Clients)", + notes = "Update oauth2 clients of the specified mobile app bundle. ") + @PreAuthorize("hasAnyAuthority('SYS_ADMIN')") + @PutMapping(value = "/mobile/bundle/{id}/oauth2Clients") + public void updateOauth2Clients(@PathVariable UUID id, + @RequestBody UUID[] clientIds) throws ThingsboardException { + MobileAppBundleId mobileAppBundleId = new MobileAppBundleId(id); + MobileAppBundle mobileAppBundle = checkMobileAppBundleId(mobileAppBundleId, Operation.WRITE); + List oAuth2ClientIds = getOAuth2ClientIds(clientIds); + tbMobileAppBundleService.updateOauth2Clients(mobileAppBundle, oAuth2ClientIds, getCurrentUser()); + } + + @ApiOperation(value = "Get mobile app bundle infos (getTenantMobileAppBundleInfos)", notes = SYSTEM_AUTHORITY_PARAGRAPH) + @PreAuthorize("hasAnyAuthority('SYS_ADMIN')") + @GetMapping(value = "/mobile/bundle/infos") + public PageData getTenantMobileAppBundleInfos(@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 mobileAppBundleService.findMobileAppBundleInfosByTenantId(getTenantId(), pageLink); + } + + @ApiOperation(value = "Get mobile app bundle info by id (getMobileAppBundleInfoById)", notes = SYSTEM_AUTHORITY_PARAGRAPH) + @PreAuthorize("hasAnyAuthority('SYS_ADMIN')") + @GetMapping(value = "/mobile/bundle/info/{id}") + public MobileAppBundleInfo getMobileAppBundleInfoById(@PathVariable UUID id) throws ThingsboardException { + MobileAppBundleId mobileAppBundleId = new MobileAppBundleId(id); + return checkEntityId(mobileAppBundleId, mobileAppBundleService::findMobileAppBundleInfoById, Operation.READ); + } + + @ApiOperation(value = "Delete Mobile App Bundle by ID (deleteMobileAppBundle)", + notes = "Deletes Mobile App Bundle by ID. Referencing non-existing mobile app bundle Id will cause an error." + SYSTEM_AUTHORITY_PARAGRAPH) + @PreAuthorize("hasAuthority('SYS_ADMIN')") + @DeleteMapping(value = "/mobile/bundle/{id}") + public void deleteMobileAppBundle(@PathVariable UUID id) throws Exception { + MobileAppBundleId mobileAppBundleId = new MobileAppBundleId(id); + MobileAppBundle mobileAppBundle = checkMobileAppBundleId(mobileAppBundleId, Operation.DELETE); + tbMobileAppBundleService.delete(mobileAppBundle, getCurrentUser()); + } + +} diff --git a/application/src/main/java/org/thingsboard/server/controller/MobileAppController.java b/application/src/main/java/org/thingsboard/server/controller/MobileAppController.java index fba17d7119..b19267d44a 100644 --- a/application/src/main/java/org/thingsboard/server/controller/MobileAppController.java +++ b/application/src/main/java/org/thingsboard/server/controller/MobileAppController.java @@ -16,8 +16,6 @@ package org.thingsboard.server.controller; import io.swagger.v3.oas.annotations.Parameter; -import io.swagger.v3.oas.annotations.media.ArraySchema; -import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -26,17 +24,13 @@ 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.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; import org.thingsboard.server.common.data.exception.ThingsboardException; import org.thingsboard.server.common.data.id.MobileAppId; -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.MobileAppInfo; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.config.annotations.ApiOperation; @@ -45,7 +39,6 @@ import org.thingsboard.server.service.entitiy.mobile.TbMobileAppService; import org.thingsboard.server.service.security.permission.Operation; import org.thingsboard.server.service.security.permission.Resource; -import java.util.List; import java.util.UUID; import static org.thingsboard.server.controller.ControllerConstants.PAGE_NUMBER_DESCRIPTION; @@ -69,35 +62,21 @@ public class MobileAppController extends BaseController { "The newly created Mobile App Id will be present in the response. " + "Specify existing Mobile App Id to update the mobile app. " + "Referencing non-existing Mobile App Id will cause 'Not Found' error." + - "\n\nMobile app package name is unique for entire platform setup.\n\n" + SYSTEM_AUTHORITY_PARAGRAPH) + "\n\nThe pair of mobile app package name and platform type is unique for entire platform setup.\n\n" + SYSTEM_AUTHORITY_PARAGRAPH) @PreAuthorize("hasAnyAuthority('SYS_ADMIN')") - @PostMapping(value = "/mobileApp") + @PostMapping(value = "/mobile/app") public MobileApp saveMobileApp( @Parameter(description = "A JSON value representing the Mobile Application.", required = true) - @RequestBody @Valid MobileApp mobileApp, - @Parameter(description = "A list of entity oauth2 client ids, separated by comma ','", array = @ArraySchema(schema = @Schema(type = "string"))) - @RequestParam(name = "oauth2ClientIds", required = false) UUID[] ids) throws Exception { + @RequestBody @Valid MobileApp mobileApp) throws Exception { mobileApp.setTenantId(getTenantId()); checkEntity(mobileApp.getId(), mobileApp, Resource.MOBILE_APP); - return tbMobileAppService.save(mobileApp, getOAuth2ClientIds(ids), getCurrentUser()); - } - - @ApiOperation(value = "Update oauth2 clients (updateOauth2Clients)", - notes = "Update oauth2 clients of the specified mobile app. ") - @PreAuthorize("hasAnyAuthority('SYS_ADMIN')") - @PutMapping(value = "/mobileApp/{id}/oauth2Clients") - public void updateOauth2Clients(@PathVariable UUID id, - @RequestBody UUID[] clientIds) throws ThingsboardException { - MobileAppId mobileAppId = new MobileAppId(id); - MobileApp mobileApp = checkMobileAppId(mobileAppId, Operation.WRITE); - List oAuth2ClientIds = getOAuth2ClientIds(clientIds); - tbMobileAppService.updateOauth2Clients(mobileApp, oAuth2ClientIds, getCurrentUser()); + return tbMobileAppService.save(mobileApp, getCurrentUser()); } @ApiOperation(value = "Get mobile app infos (getTenantMobileAppInfos)", notes = SYSTEM_AUTHORITY_PARAGRAPH) @PreAuthorize("hasAnyAuthority('SYS_ADMIN')") - @GetMapping(value = "/mobileApp/infos") - public PageData getTenantMobileAppInfos(@Parameter(description = PAGE_SIZE_DESCRIPTION, required = true) + @GetMapping(value = "/mobile/app") + public PageData getTenantMobileAppInfos(@Parameter(description = PAGE_SIZE_DESCRIPTION, required = true) @RequestParam int pageSize, @Parameter(description = PAGE_NUMBER_DESCRIPTION, required = true) @RequestParam int page, @@ -109,21 +88,21 @@ public class MobileAppController extends BaseController { @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.findMobileAppInfosByTenantId(getTenantId(), pageLink); + return mobileAppService.findMobileAppsByTenantId(getTenantId(), pageLink); } @ApiOperation(value = "Get mobile info by id (getMobileAppInfoById)", notes = SYSTEM_AUTHORITY_PARAGRAPH) @PreAuthorize("hasAnyAuthority('SYS_ADMIN')") - @GetMapping(value = "/mobileApp/info/{id}") - public MobileAppInfo getMobileAppInfoById(@PathVariable UUID id) throws ThingsboardException { + @GetMapping(value = "/mobile/app/{id}") + public MobileApp getMobileAppInfoById(@PathVariable UUID id) throws ThingsboardException { MobileAppId mobileAppId = new MobileAppId(id); - return checkEntityId(mobileAppId, mobileAppService::findMobileAppInfoById, Operation.READ); + return checkEntityId(mobileAppId, mobileAppService::findMobileAppById, Operation.READ); } @ApiOperation(value = "Delete Mobile App by ID (deleteMobileApp)", notes = "Deletes Mobile App by ID. Referencing non-existing mobile app Id will cause an error." + SYSTEM_AUTHORITY_PARAGRAPH) @PreAuthorize("hasAuthority('SYS_ADMIN')") - @DeleteMapping(value = "/mobileApp/{id}") + @DeleteMapping(value = "/mobile/app/{id}") public void deleteMobileApp(@PathVariable UUID id) throws Exception { MobileAppId mobileAppId = new MobileAppId(id); MobileApp mobileApp = checkMobileAppId(mobileAppId, Operation.DELETE); diff --git a/application/src/main/java/org/thingsboard/server/controller/MobileV2Controller.java b/application/src/main/java/org/thingsboard/server/controller/MobileV2Controller.java new file mode 100644 index 0000000000..1f8cb1da77 --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/controller/MobileV2Controller.java @@ -0,0 +1,65 @@ +/** + * 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.controller; + +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.RequiredArgsConstructor; +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.MobileAppBundle; +import org.thingsboard.server.common.data.mobile.MobileLoginInfo; +import org.thingsboard.server.common.data.mobile.MobileUserInfo; +import org.thingsboard.server.common.data.oauth2.OAuth2ClientLoginInfo; +import org.thingsboard.server.common.data.oauth2.PlatformType; +import org.thingsboard.server.queue.util.TbCoreComponent; +import org.thingsboard.server.service.security.model.SecurityUser; + +import java.util.List; + +@RequiredArgsConstructor +@RestController +@TbCoreComponent +public class MobileV2Controller extends BaseController { + + @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) { + List oauth2Clients = oAuth2ClientService.findOAuth2ClientLoginInfosByMobilePkgNameAndPlatformType(pkgName, platform); + return new MobileLoginInfo(oauth2Clients); + } + + @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 { + SecurityUser securityUser = getCurrentUser(); + User user = userService.findUserById(securityUser.getTenantId(), securityUser.getId()); + HomeDashboardInfo homeDashboardInfo = getHomeDashboardInfo(securityUser, user.getAdditionalInfo()); + MobileAppBundle mobileAppBundle = mobileAppBundleService.findMobileAppBundleByPkgNameAndPlatform(securityUser.getTenantId(), pkgName, platform); + return new MobileUserInfo(user, homeDashboardInfo, mobileAppBundle != null ? mobileAppBundle.getLayoutConfig() : null); + } + +} diff --git a/application/src/main/java/org/thingsboard/server/controller/MobileApplicationController.java b/application/src/main/java/org/thingsboard/server/controller/QrCodeSettingsController.java similarity index 75% rename from application/src/main/java/org/thingsboard/server/controller/MobileApplicationController.java rename to application/src/main/java/org/thingsboard/server/controller/QrCodeSettingsController.java index 11f017b85f..1de38b5671 100644 --- a/application/src/main/java/org/thingsboard/server/controller/MobileApplicationController.java +++ b/application/src/main/java/org/thingsboard/server/controller/QrCodeSettingsController.java @@ -32,12 +32,13 @@ 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.TenantId; -import org.thingsboard.server.common.data.mobile.AndroidConfig; -import org.thingsboard.server.common.data.mobile.IosConfig; -import org.thingsboard.server.common.data.mobile.MobileAppSettings; +import org.thingsboard.server.common.data.mobile.AndroidQrCodeConfig; +import org.thingsboard.server.common.data.mobile.IosQrCodeConfig; +import org.thingsboard.server.common.data.mobile.QrCodeSettings; import org.thingsboard.server.common.data.security.model.JwtPair; import org.thingsboard.server.config.annotations.ApiOperation; -import org.thingsboard.server.dao.mobile.MobileAppSettingsService; +import org.thingsboard.server.dao.mobile.MobileAppService; +import org.thingsboard.server.dao.mobile.QrCodeSettingService; import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.service.mobile.secret.MobileAppSecretService; import org.thingsboard.server.service.security.model.SecurityUser; @@ -54,7 +55,7 @@ import static org.thingsboard.server.controller.ControllerConstants.SYSTEM_AUTHO @RequiredArgsConstructor @RestController @TbCoreComponent -public class MobileApplicationController extends BaseController { +public class QrCodeSettingsController extends BaseController { @Value("${cache.specs.mobileSecretKey.timeToLiveInMinutes:2}") private int mobileSecretKeyTtl; @@ -89,15 +90,15 @@ public class MobileApplicationController extends BaseController { private final SystemSecurityService systemSecurityService; private final MobileAppSecretService mobileAppSecretService; - private final MobileAppSettingsService mobileAppSettingsService; + private final QrCodeSettingService qrCodeSettingService; + private final MobileAppService mobileAppService; @ApiOperation(value = "Get associated android applications (getAssetLinks)") @GetMapping(value = "/.well-known/assetlinks.json") public ResponseEntity getAssetLinks() { - MobileAppSettings mobileAppSettings = mobileAppSettingsService.getMobileAppSettings(TenantId.SYS_TENANT_ID); - AndroidConfig androidConfig = mobileAppSettings.getAndroidConfig(); - if (androidConfig != null && androidConfig.isEnabled() && androidConfig.getAppPackage() != null && androidConfig.getSha256CertFingerprints() != null) { - return ResponseEntity.ok(JacksonUtil.toJsonNode(String.format(ASSET_LINKS_PATTERN, androidConfig.getAppPackage(), androidConfig.getSha256CertFingerprints()))); + AndroidQrCodeConfig androidQrConfig = qrCodeSettingService.getAndroidQrCodeConfig(TenantId.SYS_TENANT_ID); + if (androidQrConfig != null && androidQrConfig.isEnabled()) { + return ResponseEntity.ok(JacksonUtil.toJsonNode(String.format(ASSET_LINKS_PATTERN, androidQrConfig.getAppPackage(), androidQrConfig.getSha256CertFingerprints()))); } else { return ResponseEntity.notFound().build(); } @@ -106,10 +107,9 @@ public class MobileApplicationController extends BaseController { @ApiOperation(value = "Get associated ios applications (getAppleAppSiteAssociation)") @GetMapping(value = "/.well-known/apple-app-site-association") public ResponseEntity getAppleAppSiteAssociation() { - MobileAppSettings mobileAppSettings = mobileAppSettingsService.getMobileAppSettings(TenantId.SYS_TENANT_ID); - IosConfig iosConfig = mobileAppSettings.getIosConfig(); - if (iosConfig != null && iosConfig.isEnabled() && iosConfig.getAppId() != null) { - return ResponseEntity.ok(JacksonUtil.toJsonNode(String.format(APPLE_APP_SITE_ASSOCIATION_PATTERN, iosConfig.getAppId()))); + IosQrCodeConfig iosQrCodeConfig = qrCodeSettingService.getIosQrCodeConfig(TenantId.SYS_TENANT_ID); + if (iosQrCodeConfig != null && iosQrCodeConfig.isEnabled() && iosQrCodeConfig.getAppId() != null) { + return ResponseEntity.ok(JacksonUtil.toJsonNode(String.format(APPLE_APP_SITE_ASSOCIATION_PATTERN, iosQrCodeConfig.getAppId()))); } else { return ResponseEntity.notFound().build(); } @@ -118,36 +118,36 @@ public class MobileApplicationController extends BaseController { @ApiOperation(value = "Create Or Update the Mobile application settings (saveMobileAppSettings)", notes = "The request payload contains configuration for android/iOS applications and platform qr code widget settings." + SYSTEM_AUTHORITY_PARAGRAPH) @PreAuthorize("hasAnyAuthority('SYS_ADMIN')") - @PostMapping(value = "/api/mobile/app/settings") - public MobileAppSettings saveMobileAppSettings(@Parameter(description = "A JSON value representing the mobile apps configuration") - @RequestBody MobileAppSettings mobileAppSettings) throws ThingsboardException { + @PostMapping(value = "/api/qr/settings") + public QrCodeSettings saveMobileAppSettings(@Parameter(description = "A JSON value representing the mobile apps configuration") + @RequestBody QrCodeSettings qrCodeSettings) throws ThingsboardException { SecurityUser currentUser = getCurrentUser(); accessControlService.checkPermission(currentUser, Resource.MOBILE_APP_SETTINGS, Operation.WRITE); - mobileAppSettings.setTenantId(getTenantId()); - return mobileAppSettingsService.saveMobileAppSettings(currentUser.getTenantId(), mobileAppSettings); + qrCodeSettings.setTenantId(getTenantId()); + return qrCodeSettingService.saveQrCodeSettings(currentUser.getTenantId(), qrCodeSettings); } @ApiOperation(value = "Get Mobile application settings (getMobileAppSettings)", notes = "The response payload contains configuration for android/iOS applications and platform qr code widget settings." + AVAILABLE_FOR_ANY_AUTHORIZED_USER) @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')") - @GetMapping(value = "/api/mobile/app/settings") - public MobileAppSettings getMobileAppSettings() throws ThingsboardException { + @GetMapping(value = "/api/qr/settings") + public QrCodeSettings getMobileAppSettings() throws ThingsboardException { SecurityUser currentUser = getCurrentUser(); accessControlService.checkPermission(currentUser, Resource.MOBILE_APP_SETTINGS, Operation.READ); - return mobileAppSettingsService.getMobileAppSettings(TenantId.SYS_TENANT_ID); + return qrCodeSettingService.getQrCodeSettings(TenantId.SYS_TENANT_ID); } @ApiOperation(value = "Get the deep link to the associated mobile application (getMobileAppDeepLink)", notes = "Fetch the url that takes user to linked mobile application " + AVAILABLE_FOR_ANY_AUTHORIZED_USER) @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')") - @GetMapping(value = "/api/mobile/deepLink", produces = "text/plain") + @GetMapping(value = "/api/qr/deepLink", produces = "text/plain") public String getMobileAppDeepLink(HttpServletRequest request) throws ThingsboardException, URISyntaxException { String secret = mobileAppSecretService.generateMobileAppSecret(getCurrentUser()); String baseUrl = systemSecurityService.getBaseUrl(TenantId.SYS_TENANT_ID, null, request); String platformDomain = new URI(baseUrl).getHost(); - MobileAppSettings mobileAppSettings = mobileAppSettingsService.getMobileAppSettings(TenantId.SYS_TENANT_ID); + QrCodeSettings qrCodeSettings = qrCodeSettingService.getQrCodeSettings(TenantId.SYS_TENANT_ID); String appDomain; - if (!mobileAppSettings.isUseDefaultApp()) { + if (!qrCodeSettings.isUseDefaultApp()) { appDomain = platformDomain; } else { appDomain = defaultAppDomain; @@ -170,15 +170,17 @@ public class MobileApplicationController extends BaseController { @GetMapping(value = "/api/noauth/qr") public ResponseEntity getApplicationRedirect(@RequestHeader(value = "User-Agent") String userAgent) { - MobileAppSettings mobileAppSettings = mobileAppSettingsService.getMobileAppSettings(TenantId.SYS_TENANT_ID); - boolean useDefaultApp = mobileAppSettings.isUseDefaultApp(); - String googlePlayLink = useDefaultApp ? mobileAppSettings.getDefaultGooglePlayLink() : mobileAppSettings.getAndroidConfig().getStoreLink(); - String appStoreLink = useDefaultApp ? mobileAppSettings.getDefaultAppStoreLink() : mobileAppSettings.getIosConfig().getStoreLink(); + QrCodeSettings qrCodeSettings = qrCodeSettingService.getQrCodeSettings(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(); 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(); return ResponseEntity.status(HttpStatus.FOUND) .header("Location", appStoreLink) .build(); diff --git a/application/src/main/java/org/thingsboard/server/controller/SystemInfoController.java b/application/src/main/java/org/thingsboard/server/controller/SystemInfoController.java index 9600ac0221..18d632345b 100644 --- a/application/src/main/java/org/thingsboard/server/controller/SystemInfoController.java +++ b/application/src/main/java/org/thingsboard/server/controller/SystemInfoController.java @@ -34,13 +34,13 @@ import org.thingsboard.server.common.data.SystemParams; import org.thingsboard.server.common.data.exception.ThingsboardException; import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.TenantId; -import org.thingsboard.server.common.data.mobile.MobileAppSettings; +import org.thingsboard.server.common.data.mobile.QrCodeSettings; import org.thingsboard.server.common.data.mobile.QRCodeConfig; import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.settings.UserSettings; import org.thingsboard.server.common.data.settings.UserSettingsType; import org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileConfiguration; -import org.thingsboard.server.dao.mobile.MobileAppSettingsService; +import org.thingsboard.server.dao.mobile.QrCodeSettingService; import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.service.security.model.SecurityUser; import org.thingsboard.server.service.security.model.UserPrincipal; @@ -77,7 +77,7 @@ public class SystemInfoController extends BaseController { private EntitiesVersionControlService versionControlService; @Autowired - private MobileAppSettingsService mobileAppSettingsService; + private QrCodeSettingService qrCodeSettingService; @PostConstruct public void init() { @@ -142,8 +142,8 @@ public class SystemInfoController extends BaseController { DefaultTenantProfileConfiguration tenantProfileConfiguration = tenantProfileCache.get(tenantId).getDefaultProfileConfiguration(); systemParams.setMaxResourceSize(tenantProfileConfiguration.getMaxResourceSize()); } - systemParams.setMobileQrEnabled(Optional.ofNullable(mobileAppSettingsService.getMobileAppSettings(TenantId.SYS_TENANT_ID)) - .map(MobileAppSettings::getQrCodeConfig).map(QRCodeConfig::isShowOnHomePage) + systemParams.setMobileQrEnabled(Optional.ofNullable(qrCodeSettingService.getQrCodeSettings(TenantId.SYS_TENANT_ID)) + .map(QrCodeSettings::getQrCodeConfig).map(QRCodeConfig::isShowOnHomePage) .orElse(false)); return systemParams; } diff --git a/application/src/main/java/org/thingsboard/server/service/entitiy/mobile/DefaultTbMobileAppBundleService.java b/application/src/main/java/org/thingsboard/server/service/entitiy/mobile/DefaultTbMobileAppBundleService.java new file mode 100644 index 0000000000..a8c20a772b --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/entitiy/mobile/DefaultTbMobileAppBundleService.java @@ -0,0 +1,83 @@ +/** + * 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.service.entitiy.mobile; + +import lombok.AllArgsConstructor; +import org.apache.commons.collections4.CollectionUtils; +import org.springframework.stereotype.Service; +import org.thingsboard.server.common.data.EntityType; +import org.thingsboard.server.common.data.User; +import org.thingsboard.server.common.data.audit.ActionType; +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.MobileAppBundle; +import org.thingsboard.server.dao.mobile.MobileAppBundleService; +import org.thingsboard.server.service.entitiy.AbstractTbEntityService; + +import java.util.List; + +@Service +@AllArgsConstructor +public class DefaultTbMobileAppBundleService extends AbstractTbEntityService implements TbMobileAppBundleService { + + private final MobileAppBundleService mobileAppBundleService; + + @Override + public MobileAppBundle save(MobileAppBundle mobileAppBundle, List oauth2Clients, User user) throws Exception { + ActionType actionType = mobileAppBundle.getId() == null ? ActionType.ADDED : ActionType.UPDATED; + TenantId tenantId = mobileAppBundle.getTenantId(); + try { + MobileAppBundle savedMobileAppBundle = checkNotNull(mobileAppBundleService.saveMobileAppBundle(tenantId, mobileAppBundle)); + if (CollectionUtils.isNotEmpty(oauth2Clients)) { + mobileAppBundleService.updateOauth2Clients(tenantId, savedMobileAppBundle.getId(), oauth2Clients); + } + logEntityActionService.logEntityAction(tenantId, savedMobileAppBundle.getId(), savedMobileAppBundle, actionType, user); + return savedMobileAppBundle; + } catch (Exception e) { + logEntityActionService.logEntityAction(tenantId, emptyId(EntityType.MOBILE_APP), mobileAppBundle, actionType, user, e); + throw e; + } + } + + @Override + public void updateOauth2Clients(MobileAppBundle mobileAppBundle, List oAuth2ClientIds, User user) { + ActionType actionType = ActionType.UPDATED; + TenantId tenantId = mobileAppBundle.getTenantId(); + MobileAppBundleId mobileAppBundleId = mobileAppBundle.getId(); + try { + mobileAppBundleService.updateOauth2Clients(tenantId, mobileAppBundleId, oAuth2ClientIds); + logEntityActionService.logEntityAction(tenantId, mobileAppBundleId, mobileAppBundle, actionType, user, oAuth2ClientIds); + } catch (Exception e) { + logEntityActionService.logEntityAction(tenantId, mobileAppBundleId, mobileAppBundle, actionType, user, e, oAuth2ClientIds); + throw e; + } + } + + @Override + public void delete(MobileAppBundle mobileAppBundle, User user) { + ActionType actionType = ActionType.DELETED; + TenantId tenantId = mobileAppBundle.getTenantId(); + MobileAppBundleId mobileAppBundleId = mobileAppBundle.getId(); + try { + mobileAppBundleService.deleteMobileAppBundleById(tenantId, mobileAppBundleId); + logEntityActionService.logEntityAction(tenantId, mobileAppBundleId, mobileAppBundle, actionType, user); + } catch (Exception e) { + logEntityActionService.logEntityAction(tenantId, mobileAppBundleId, mobileAppBundle, actionType, user, e); + throw e; + } + } +} diff --git a/application/src/main/java/org/thingsboard/server/service/entitiy/mobile/DefaultTbMobileAppService.java b/application/src/main/java/org/thingsboard/server/service/entitiy/mobile/DefaultTbMobileAppService.java index 8dbef1fc1e..be0d4640a0 100644 --- a/application/src/main/java/org/thingsboard/server/service/entitiy/mobile/DefaultTbMobileAppService.java +++ b/application/src/main/java/org/thingsboard/server/service/entitiy/mobile/DefaultTbMobileAppService.java @@ -37,14 +37,11 @@ public class DefaultTbMobileAppService extends AbstractTbEntityService implement private final MobileAppService mobileAppService; @Override - public MobileApp save(MobileApp mobileApp, List oauth2Clients, User user) throws Exception { + public MobileApp save(MobileApp mobileApp, User user) throws Exception { ActionType actionType = mobileApp.getId() == null ? ActionType.ADDED : ActionType.UPDATED; TenantId tenantId = mobileApp.getTenantId(); try { MobileApp savedMobileApp = checkNotNull(mobileAppService.saveMobileApp(tenantId, mobileApp)); - if (CollectionUtils.isNotEmpty(oauth2Clients)) { - mobileAppService.updateOauth2Clients(tenantId, savedMobileApp.getId(), oauth2Clients); - } logEntityActionService.logEntityAction(tenantId, savedMobileApp.getId(), savedMobileApp, actionType, user); return savedMobileApp; } catch (Exception e) { @@ -53,19 +50,6 @@ public class DefaultTbMobileAppService extends AbstractTbEntityService implement } } - @Override - public void updateOauth2Clients(MobileApp mobileApp, List oAuth2ClientIds, User user) { - ActionType actionType = ActionType.UPDATED; - TenantId tenantId = mobileApp.getTenantId(); - MobileAppId mobileAppId = mobileApp.getId(); - try { - mobileAppService.updateOauth2Clients(tenantId, mobileAppId, oAuth2ClientIds); - logEntityActionService.logEntityAction(tenantId, mobileAppId, mobileApp, actionType, user, oAuth2ClientIds); - } catch (Exception e) { - logEntityActionService.logEntityAction(tenantId, mobileAppId, mobileApp, actionType, user, e, oAuth2ClientIds); - throw e; - } - } @Override public void delete(MobileApp mobileApp, User user) { diff --git a/application/src/main/java/org/thingsboard/server/service/entitiy/mobile/TbMobileAppBundleService.java b/application/src/main/java/org/thingsboard/server/service/entitiy/mobile/TbMobileAppBundleService.java new file mode 100644 index 0000000000..d523f6a764 --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/entitiy/mobile/TbMobileAppBundleService.java @@ -0,0 +1,32 @@ +/** + * 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.service.entitiy.mobile; + +import org.thingsboard.server.common.data.User; +import org.thingsboard.server.common.data.id.OAuth2ClientId; +import org.thingsboard.server.common.data.mobile.MobileAppBundle; + +import java.util.List; + +public interface TbMobileAppBundleService { + + MobileAppBundle save(MobileAppBundle mobileAppBundle, List oauth2Clients, User user) throws Exception; + + void updateOauth2Clients(MobileAppBundle mobileAppBundle, List oAuth2ClientIds, User user); + + void delete(MobileAppBundle mobileAppBundle, User user); + +} diff --git a/application/src/main/java/org/thingsboard/server/service/entitiy/mobile/TbMobileAppService.java b/application/src/main/java/org/thingsboard/server/service/entitiy/mobile/TbMobileAppService.java index bcc9cf7004..97255e16cc 100644 --- a/application/src/main/java/org/thingsboard/server/service/entitiy/mobile/TbMobileAppService.java +++ b/application/src/main/java/org/thingsboard/server/service/entitiy/mobile/TbMobileAppService.java @@ -16,16 +16,11 @@ package org.thingsboard.server.service.entitiy.mobile; import org.thingsboard.server.common.data.User; -import org.thingsboard.server.common.data.id.OAuth2ClientId; import org.thingsboard.server.common.data.mobile.MobileApp; -import java.util.List; - public interface TbMobileAppService { - MobileApp save(MobileApp mobileApp, List oauth2Clients, User user) throws Exception; - - void updateOauth2Clients(MobileApp mobileApp, List oAuth2ClientIds, User user); + MobileApp save(MobileApp mobileApp, User user) throws Exception; void delete(MobileApp mobileApp, User user); diff --git a/application/src/main/java/org/thingsboard/server/service/security/permission/Resource.java b/application/src/main/java/org/thingsboard/server/service/security/permission/Resource.java index 16a3c4be59..8c6f4d00e0 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/permission/Resource.java +++ b/application/src/main/java/org/thingsboard/server/service/security/permission/Resource.java @@ -36,6 +36,7 @@ public enum Resource { OAUTH2_CLIENT(EntityType.OAUTH2_CLIENT), DOMAIN(EntityType.DOMAIN), MOBILE_APP(EntityType.MOBILE_APP), + MOBILE_APP_BUNDLE(EntityType.MOBILE_APP_BUNDLE), OAUTH2_CONFIGURATION_TEMPLATE(), TENANT_PROFILE(EntityType.TENANT_PROFILE), DEVICE_PROFILE(EntityType.DEVICE_PROFILE), diff --git a/application/src/main/java/org/thingsboard/server/service/security/permission/SysAdminPermissions.java b/application/src/main/java/org/thingsboard/server/service/security/permission/SysAdminPermissions.java index 4790a07965..8edbd9f6d5 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/permission/SysAdminPermissions.java +++ b/application/src/main/java/org/thingsboard/server/service/security/permission/SysAdminPermissions.java @@ -37,6 +37,7 @@ public class SysAdminPermissions extends AbstractPermissions { put(Resource.WIDGET_TYPE, systemEntityPermissionChecker); put(Resource.OAUTH2_CLIENT, PermissionChecker.allowAllPermissionChecker); put(Resource.MOBILE_APP, PermissionChecker.allowAllPermissionChecker); + put(Resource.MOBILE_APP_BUNDLE, PermissionChecker.allowAllPermissionChecker); put(Resource.DOMAIN, PermissionChecker.allowAllPermissionChecker); put(Resource.OAUTH2_CONFIGURATION_TEMPLATE, PermissionChecker.allowAllPermissionChecker); put(Resource.TENANT_PROFILE, PermissionChecker.allowAllPermissionChecker); diff --git a/application/src/main/resources/thingsboard.yml b/application/src/main/resources/thingsboard.yml index e9c8668958..4eca2e93ed 100644 --- a/application/src/main/resources/thingsboard.yml +++ b/application/src/main/resources/thingsboard.yml @@ -609,7 +609,7 @@ cache: alarmTypes: timeToLiveInMinutes: "${CACHE_SPECS_ALARM_TYPES_TTL:60}" # Alarm types cache TTL maxSize: "${CACHE_SPECS_ALARM_TYPES_MAX_SIZE:10000}" # 0 means the cache is disabled - mobileAppSettings: + qrCodeSettings: timeToLiveInMinutes: "${CACHE_SPECS_MOBILE_APP_SETTINGS_TTL:1440}" # Mobile application cache TTL maxSize: "${CACHE_SPECS_MOBILE_APP_SETTINGS_MAX_SIZE:10000}" # 0 means the cache is disabled mobileSecretKey: diff --git a/application/src/test/java/org/thingsboard/server/controller/MobileAppBundleControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/MobileAppBundleControllerTest.java new file mode 100644 index 0000000000..26c1a207e2 --- /dev/null +++ b/application/src/test/java/org/thingsboard/server/controller/MobileAppBundleControllerTest.java @@ -0,0 +1,149 @@ +/** + * 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.controller; + +import com.fasterxml.jackson.core.type.TypeReference; +import lombok.extern.slf4j.Slf4j; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.thingsboard.server.common.data.StringUtils; +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.oauth2.OAuth2Client; +import org.thingsboard.server.common.data.oauth2.OAuth2ClientInfo; +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.service.DaoSqlTest; + +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +@Slf4j +@DaoSqlTest +public class MobileAppBundleControllerTest extends AbstractControllerTest { + + static final TypeReference> PAGE_DATA_MOBILE_APP_BUNDLE_TYPE_REF = new TypeReference<>() { + }; + static final TypeReference> PAGE_DATA_MOBILE_APP_TYPE_REF = new TypeReference<>() { + }; + static final TypeReference> PAGE_DATA_OAUTH2_CLIENT_TYPE_REF = new TypeReference<>() { + }; + + private MobileApp androidApp; + private MobileApp iosApp; + + @Before + public void setUp() throws Exception { + loginSysAdmin(); + + androidApp = validMobileApp(TenantId.SYS_TENANT_ID, "my.android.package", PlatformType.ANDROID, true); + androidApp = doPost("/api/mobile/app", androidApp, MobileApp.class); + + iosApp = validMobileApp(TenantId.SYS_TENANT_ID, "my.ios.package", PlatformType.IOS, true); + iosApp = doPost("/api/mobile/app", iosApp, MobileApp.class); + } + + @After + public void tearDown() throws Exception { + PageData 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()); + } + + PageData 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()) + .andExpect(status().isOk()); + } + + PageData clients = doGetTypedWithPageLink("/api/oauth2/client/infos?", PAGE_DATA_OAUTH2_CLIENT_TYPE_REF, new PageLink(10, 0)); + for (OAuth2ClientInfo oAuth2ClientInfo : clients.getData()) { + doDelete("/api/oauth2/client/" + oAuth2ClientInfo.getId().getId().toString()) + .andExpect(status().isOk()); + } + } + + @Test + public void testSaveMobileApp() throws Exception { + MobileAppBundle mobileAppBundle = new MobileAppBundle(); + mobileAppBundle.setTitle("Test bundle"); + mobileAppBundle.setAndroidAppId(androidApp.getId()); + mobileAppBundle.setIosAppId(iosApp.getId()); + + MobileAppBundle createdMobileAppBundle = doPost("/api/mobile/bundle", mobileAppBundle, MobileAppBundle.class); + assertThat(createdMobileAppBundle.getAndroidAppId()).isEqualTo(androidApp.getId()); + assertThat(createdMobileAppBundle.getIosAppId()).isEqualTo(iosApp.getId()); + } + + + @Test + public void testUpdateMobileAppOauth2Clients() throws Exception { + MobileAppBundle mobileAppBundle = new MobileAppBundle(); + mobileAppBundle.setTitle("Test bundle"); + mobileAppBundle.setAndroidAppId(androidApp.getId()); + mobileAppBundle.setIosAppId(iosApp.getId()); + + MobileAppBundle savedAppBundle = doPost("/api/mobile/bundle", mobileAppBundle, MobileAppBundle.class); + + OAuth2Client oAuth2Client = createOauth2Client(TenantId.SYS_TENANT_ID, "test google client"); + OAuth2Client savedOAuth2Client = doPost("/api/oauth2/client", oAuth2Client, OAuth2Client.class); + + OAuth2Client oAuth2Client2 = createOauth2Client(TenantId.SYS_TENANT_ID, "test facebook client"); + OAuth2Client savedOAuth2Client2 = doPost("/api/oauth2/client", oAuth2Client2, OAuth2Client.class); + + 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)))); + + doPut("/api/mobile/bundle/" + savedAppBundle.getId() + "/oauth2Clients", List.of(savedOAuth2Client2.getId().getId())); + MobileAppBundleInfo retrievedMobileAppInfo2 = doGet("/api/mobileApp/info/{id}", MobileAppBundleInfo.class, savedOAuth2Client.getId().getId()); + assertThat(retrievedMobileAppInfo2).isEqualTo(new MobileAppBundleInfo(savedAppBundle, androidApp.getPkgName(), iosApp.getPkgName(), List.of(new OAuth2ClientInfo(savedOAuth2Client2)))); + } + + @Test + public void testCreateMobileAppBundleWithOauth2Clients() throws Exception { + OAuth2Client oAuth2Client = createOauth2Client(TenantId.SYS_TENANT_ID, "test google client"); + OAuth2Client savedOAuth2Client = doPost("/api/oauth2/client", oAuth2Client, OAuth2Client.class); + + MobileAppBundle mobileAppBundle = new MobileAppBundle(); + mobileAppBundle.setTitle("Test bundle"); + mobileAppBundle.setAndroidAppId(androidApp.getId()); + mobileAppBundle.setIosAppId(iosApp.getId()); + + 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()); + 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; + } + +} diff --git a/application/src/test/java/org/thingsboard/server/controller/MobileAppControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/MobileAppControllerTest.java index 6fd2d62d14..e4f9ab583a 100644 --- a/application/src/test/java/org/thingsboard/server/controller/MobileAppControllerTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/MobileAppControllerTest.java @@ -22,17 +22,13 @@ import org.junit.Before; import org.junit.Test; import org.thingsboard.server.common.data.StringUtils; 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.MobileAppInfo; -import org.thingsboard.server.common.data.oauth2.OAuth2Client; -import org.thingsboard.server.common.data.oauth2.OAuth2ClientInfo; 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.Collections; -import java.util.List; - import static org.assertj.core.api.Assertions.assertThat; import static org.hamcrest.Matchers.containsString; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; @@ -41,9 +37,7 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers. @DaoSqlTest public class MobileAppControllerTest extends AbstractControllerTest { - static final TypeReference> PAGE_DATA_MOBILE_APP_TYPE_REF = new TypeReference<>() { - }; - static final TypeReference> PAGE_DATA_OAUTH2_CLIENT_TYPE_REF = new TypeReference<>() { + static final TypeReference> PAGE_DATA_MOBILE_APP_TYPE_REF = new TypeReference<>() { }; @Before @@ -53,88 +47,91 @@ public class MobileAppControllerTest extends AbstractControllerTest { @After public void tearDown() throws Exception { - PageData pageData = doGetTypedWithPageLink("/api/mobileApp/infos?", PAGE_DATA_MOBILE_APP_TYPE_REF, new PageLink(10, 0)); + PageData pageData = doGetTypedWithPageLink("/api/mobile/app?", PAGE_DATA_MOBILE_APP_TYPE_REF, new PageLink(10, 0)); for (MobileApp mobileApp : pageData.getData()) { - doDelete("/api/mobileApp/" + mobileApp.getId().getId()) - .andExpect(status().isOk()); - } - - PageData clients = doGetTypedWithPageLink("/api/oauth2/client/infos?", PAGE_DATA_OAUTH2_CLIENT_TYPE_REF, new PageLink(10, 0)); - for (OAuth2ClientInfo oAuth2ClientInfo : clients.getData()) { - doDelete("/api/oauth2/client/" + oAuth2ClientInfo.getId().getId().toString()) + doDelete("/api/mobile/app/" + mobileApp.getId().getId()) .andExpect(status().isOk()); } } @Test public void testSaveMobileApp() throws Exception { - PageData pageData = doGetTypedWithPageLink("/api/mobileApp/infos?", PAGE_DATA_MOBILE_APP_TYPE_REF, new PageLink(10, 0)); + PageData pageData = doGetTypedWithPageLink("/api/mobile/app?", PAGE_DATA_MOBILE_APP_TYPE_REF, new PageLink(10, 0)); assertThat(pageData.getData()).isEmpty(); - MobileApp mobileApp = validMobileApp(TenantId.SYS_TENANT_ID, "my.test.package", true); - MobileApp savedMobileApp = doPost("/api/mobileApp", mobileApp, MobileApp.class); + MobileApp mobileApp = validMobileApp(TenantId.SYS_TENANT_ID, "my.test.package"); + MobileApp savedMobileApp = doPost("/api/mobile/app", mobileApp, MobileApp.class); - PageData pageData2 = doGetTypedWithPageLink("/api/mobileApp/infos?", PAGE_DATA_MOBILE_APP_TYPE_REF, new PageLink(10, 0)); + PageData pageData2 = doGetTypedWithPageLink("/api/mobile/app?", PAGE_DATA_MOBILE_APP_TYPE_REF, new PageLink(10, 0)); assertThat(pageData2.getData()).hasSize(1); - assertThat(pageData2.getData().get(0)).isEqualTo(new MobileAppInfo(savedMobileApp, Collections.emptyList())); + assertThat(pageData2.getData().get(0)).isEqualTo(savedMobileApp); - MobileAppInfo retrievedMobileAppInfo = doGet("/api/mobileApp/info/{id}", MobileAppInfo.class, savedMobileApp.getId().getId()); - assertThat(retrievedMobileAppInfo).isEqualTo(new MobileAppInfo(savedMobileApp, Collections.emptyList())); + MobileApp retrievedMobileAppInfo = doGet("/api/mobile/app/{id}", MobileApp.class, savedMobileApp.getId().getId()); + assertThat(retrievedMobileAppInfo).isEqualTo(savedMobileApp); - doDelete("/api/mobileApp/" + savedMobileApp.getId().getId()); - doGet("/api/mobileApp/info/{id}", savedMobileApp.getId().getId()) + doDelete("/api/mobile/app/" + savedMobileApp.getId().getId()); + doGet("/api/mobile/app/{id}", savedMobileApp.getId().getId()) .andExpect(status().isNotFound()); } @Test public void testSaveMobileAppWithShortAppSecret() throws Exception { - MobileApp mobileApp = validMobileApp(TenantId.SYS_TENANT_ID, "mobileApp.ce", true); + MobileApp mobileApp = validMobileApp(TenantId.SYS_TENANT_ID, "mobileApp.ce"); mobileApp.setAppSecret("short"); - doPost("/api/mobileApp", mobileApp) + doPost("/api/mobile/app", mobileApp) .andExpect(status().isBadRequest()) .andExpect(statusReason(containsString("appSecret must be at least 16 and max 2048 characters"))); } @Test - public void testUpdateMobileAppOauth2Clients() throws Exception { - MobileApp mobileApp = validMobileApp(TenantId.SYS_TENANT_ID, "my.test.package", true); - MobileApp savedMobileApp = doPost("/api/mobileApp", mobileApp, MobileApp.class); - - OAuth2Client oAuth2Client = createOauth2Client(TenantId.SYS_TENANT_ID, "test google client"); - OAuth2Client savedOAuth2Client = doPost("/api/oauth2/client", oAuth2Client, OAuth2Client.class); - - OAuth2Client oAuth2Client2 = createOauth2Client(TenantId.SYS_TENANT_ID, "test facebook client"); - OAuth2Client savedOAuth2Client2 = doPost("/api/oauth2/client", oAuth2Client2, OAuth2Client.class); - - doPut("/api/mobileApp/" + savedMobileApp.getId() + "/oauth2Clients", List.of(savedOAuth2Client.getId().getId(), savedOAuth2Client2.getId().getId())); + public void testShouldNotSaveMobileAppWithWrongQrCodeConf() throws Exception { + MobileApp mobileApp = validMobileApp(TenantId.SYS_TENANT_ID, "mobileApp.ce"); + AndroidQrCodeConfig androidQrCodeConfig = AndroidQrCodeConfig.builder() + .enabled(true) + .appPackage(null) + .sha256CertFingerprints(null) + .build(); + mobileApp.setQrCodeConfig(androidQrCodeConfig); + + doPost("/api/mobile/app", mobileApp) + .andExpect(status().isBadRequest()) + .andExpect(statusReason(containsString("Validation error: appPackage must not be blank, sha256CertFingerprints must not be blank, storeLink must not be blank"))); - MobileAppInfo retrievedMobileAppInfo = doGet("/api/mobileApp/info/{id}", MobileAppInfo.class, savedMobileApp.getId().getId()); - assertThat(retrievedMobileAppInfo).isEqualTo(new MobileAppInfo(savedMobileApp, List.of(new OAuth2ClientInfo(savedOAuth2Client2), - new OAuth2ClientInfo(savedOAuth2Client)))); + androidQrCodeConfig.setAppPackage("test_app_package"); + doPost("/api/mobile/app", mobileApp) + .andExpect(status().isBadRequest()) + .andExpect(statusReason(containsString("Validation error: sha256CertFingerprints must not be blank, storeLink must not be blank"))); - doPut("/api/mobileApp/" + savedMobileApp.getId() + "/oauth2Clients", List.of(savedOAuth2Client2.getId().getId())); - MobileAppInfo retrievedMobileAppInfo2 = doGet("/api/mobileApp/info/{id}", MobileAppInfo.class, savedMobileApp.getId().getId()); - assertThat(retrievedMobileAppInfo2).isEqualTo(new MobileAppInfo(savedMobileApp, List.of(new OAuth2ClientInfo(savedOAuth2Client2)))); + androidQrCodeConfig.setSha256CertFingerprints("test_sha_256"); + androidQrCodeConfig.setStoreLink("https://store.com"); + doPost("/api/mobile/app", mobileApp) + .andExpect(status().isOk()); } @Test - public void testCreateMobileAppWithOauth2Clients() throws Exception { - OAuth2Client oAuth2Client = createOauth2Client(TenantId.SYS_TENANT_ID, "test google client"); - OAuth2Client savedOAuth2Client = doPost("/api/oauth2/client", oAuth2Client, OAuth2Client.class); - - MobileApp mobileApp = validMobileApp(TenantId.SYS_TENANT_ID, "my.test.package", true); - MobileApp savedMobileApp = doPost("/api/mobileApp?oauth2ClientIds=" + savedOAuth2Client.getId().getId(), mobileApp, MobileApp.class); + public void testShouldNotSaveMobileAppWithWrongIosConf() throws Exception { + MobileApp mobileApp = validMobileApp(TenantId.SYS_TENANT_ID, "mobileApp.ce"); + IosQrCodeConfig iosQrCodeConfig = IosQrCodeConfig.builder() + .enabled(true) + .appId(null) + .build(); + mobileApp.setQrCodeConfig(iosQrCodeConfig); + + doPost("/api/mobile/app", mobileApp) + .andExpect(status().isBadRequest()) + .andExpect(statusReason(containsString("Validation error: appId must not be blank, storeLink must not be blank"))); - MobileAppInfo retrievedMobileAppInfo = doGet("/api/mobileApp/info/{id}", MobileAppInfo.class, savedMobileApp.getId().getId()); - assertThat(retrievedMobileAppInfo).isEqualTo(new MobileAppInfo(savedMobileApp, List.of(new OAuth2ClientInfo(savedOAuth2Client)))); + iosQrCodeConfig.setAppId("test_app_id"); + iosQrCodeConfig.setStoreLink("https://store.com"); + doPost("/api/mobile/app", mobileApp) + .andExpect(status().isOk()); } - private MobileApp validMobileApp(TenantId tenantId, String mobileAppName, boolean oauth2Enabled) { + private MobileApp validMobileApp(TenantId tenantId, String mobileAppName) { MobileApp MobileApp = new MobileApp(); MobileApp.setTenantId(tenantId); MobileApp.setPkgName(mobileAppName); MobileApp.setAppSecret(StringUtils.randomAlphanumeric(24)); - MobileApp.setOauth2Enabled(oauth2Enabled); return MobileApp; } diff --git a/application/src/test/java/org/thingsboard/server/controller/MobileApplicationControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/MobileApplicationControllerTest.java deleted file mode 100644 index 39e2ef6a40..0000000000 --- a/application/src/test/java/org/thingsboard/server/controller/MobileApplicationControllerTest.java +++ /dev/null @@ -1,253 +0,0 @@ -/** - * 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.controller; - -import com.fasterxml.jackson.databind.JsonNode; -import lombok.extern.slf4j.Slf4j; -import org.junit.Before; -import org.junit.Test; -import org.springframework.beans.factory.annotation.Value; -import org.thingsboard.server.common.data.mobile.AndroidConfig; -import org.thingsboard.server.common.data.mobile.IosConfig; -import org.thingsboard.server.common.data.mobile.MobileAppSettings; -import org.thingsboard.server.common.data.mobile.QRCodeConfig; -import org.thingsboard.server.common.data.security.model.JwtPair; -import org.thingsboard.server.dao.service.DaoSqlTest; - -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.hamcrest.Matchers.containsString; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; - -@Slf4j -@DaoSqlTest -public class MobileApplicationControllerTest extends AbstractControllerTest { - - @Value("${cache.specs.mobileSecretKey.timeToLiveInMinutes:2}") - private int mobileSecretKeyTtl; - private static final String ANDROID_PACKAGE_NAME = "testAppPackage"; - private static final String ANDROID_APP_SHA256 = "DF:28:32:66:8B:A7:D3:EC:7D:73:CF:CC"; - private static final String APPLE_APP_ID = "testId"; - private static final String TEST_LABEL = "Test label"; - - @Before - public void setUp() throws Exception { - loginSysAdmin(); - - MobileAppSettings mobileAppSettings = doGet("/api/mobile/app/settings", MobileAppSettings.class); - QRCodeConfig qrCodeConfig = new QRCodeConfig(); - qrCodeConfig.setQrCodeLabel(TEST_LABEL); - - mobileAppSettings.setUseDefaultApp(true); - AndroidConfig androidConfig = AndroidConfig.builder() - .appPackage(ANDROID_PACKAGE_NAME) - .sha256CertFingerprints(ANDROID_APP_SHA256) - .enabled(true) - .build(); - - IosConfig iosConfig = IosConfig.builder() - .appId(APPLE_APP_ID) - .enabled(true) - .build(); - mobileAppSettings.setAndroidConfig(androidConfig); - mobileAppSettings.setIosConfig(iosConfig); - mobileAppSettings.setQrCodeConfig(qrCodeConfig); - - doPost("/api/mobile/app/settings", mobileAppSettings) - .andExpect(status().isOk()); - } - - @Test - public void testSaveMobileAppSettings() throws Exception { - loginSysAdmin(); - MobileAppSettings mobileAppSettings = doGet("/api/mobile/app/settings", MobileAppSettings.class); - assertThat(mobileAppSettings.getQrCodeConfig().getQrCodeLabel()).isEqualTo(TEST_LABEL); - assertThat(mobileAppSettings.isUseDefaultApp()).isTrue(); - - mobileAppSettings.setUseDefaultApp(false); - - doPost("/api/mobile/app/settings", mobileAppSettings) - .andExpect(status().isOk()); - - MobileAppSettings updatedMobileAppSettings = doGet("/api/mobile/app/settings", MobileAppSettings.class); - assertThat(updatedMobileAppSettings.isUseDefaultApp()).isFalse(); - } - - @Test - public void testShouldNotSaveMobileAppSettingsWithoutRequiredConfig() throws Exception { - loginSysAdmin(); - MobileAppSettings mobileAppSettings = doGet("/api/mobile/app/settings", MobileAppSettings.class); - - mobileAppSettings.setUseDefaultApp(false); - mobileAppSettings.setAndroidConfig(null); - mobileAppSettings.setIosConfig(null); - mobileAppSettings.setQrCodeConfig(null); - - doPost("/api/mobile/app/settings", mobileAppSettings) - .andExpect(status().isBadRequest()) - .andExpect(statusReason(containsString("Android/ios settings are required to use custom application!"))); - - mobileAppSettings.setAndroidConfig(AndroidConfig.builder().enabled(false).build()); - doPost("/api/mobile/app/settings", mobileAppSettings) - .andExpect(status().isBadRequest()) - .andExpect(statusReason(containsString("Android/ios settings are required to use custom application!"))); - - mobileAppSettings.setIosConfig(IosConfig.builder().enabled(false).build()); - doPost("/api/mobile/app/settings", mobileAppSettings) - .andExpect(status().isBadRequest()) - .andExpect(statusReason(containsString("Qr code configuration is required!"))); - - mobileAppSettings.setQrCodeConfig(QRCodeConfig.builder().showOnHomePage(false).build()); - doPost("/api/mobile/app/settings", mobileAppSettings) - .andExpect(status().isOk()); - } - - @Test - public void testShouldNotSaveMobileAppSettingsWithoutRequiredAndroidConf() throws Exception { - loginSysAdmin(); - MobileAppSettings mobileAppSettings = doGet("/api/mobile/app/settings", MobileAppSettings.class); - mobileAppSettings.setUseDefaultApp(false); - AndroidConfig androidConfig = AndroidConfig.builder() - .enabled(true) - .appPackage(null) - .sha256CertFingerprints(null) - .build(); - mobileAppSettings.setAndroidConfig(androidConfig); - - doPost("/api/mobile/app/settings", mobileAppSettings) - .andExpect(status().isBadRequest()) - .andExpect(statusReason(containsString("Application package and sha256 cert fingerprints are required for custom android application!"))); - - androidConfig.setAppPackage("test_app_package"); - doPost("/api/mobile/app/settings", mobileAppSettings) - .andExpect(status().isBadRequest()) - .andExpect(statusReason(containsString("Application package and sha256 cert fingerprints are required for custom android application!"))); - - androidConfig.setSha256CertFingerprints("test_sha_256"); - doPost("/api/mobile/app/settings", mobileAppSettings) - .andExpect(status().isOk()); - } - - @Test - public void testShouldNotSaveMobileAppSettingsWithoutRequiredIosConf() throws Exception { - loginSysAdmin(); - MobileAppSettings mobileAppSettings = doGet("/api/mobile/app/settings", MobileAppSettings.class); - mobileAppSettings.setUseDefaultApp(false); - IosConfig iosConfig = IosConfig.builder() - .enabled(true) - .appId(null) - .build(); - mobileAppSettings.setIosConfig(iosConfig); - - doPost("/api/mobile/app/settings", mobileAppSettings) - .andExpect(status().isBadRequest()) - .andExpect(statusReason(containsString("Application id is required for custom ios application!"))); - - iosConfig.setAppId("test_app_id"); - doPost("/api/mobile/app/settings", mobileAppSettings) - .andExpect(status().isOk()); - } - - @Test - public void testShouldSaveMobileAppSettingsForDefaultApp() throws Exception { - loginSysAdmin(); - MobileAppSettings mobileAppSettings = doGet("/api/mobile/app/settings", MobileAppSettings.class); - mobileAppSettings.setUseDefaultApp(true); - mobileAppSettings.setIosConfig(null); - mobileAppSettings.setAndroidConfig(null); - - doPost("/api/mobile/app/settings", mobileAppSettings) - .andExpect(status().isOk()); - } - - @Test - public void testGetApplicationAssociations() throws Exception { - loginSysAdmin(); - MobileAppSettings mobileAppSettings = doGet("/api/mobile/app/settings", MobileAppSettings.class); - mobileAppSettings.setUseDefaultApp(false); - doPost("/api/mobile/app/settings", mobileAppSettings) - .andExpect(status().isOk()); - - JsonNode assetLinks = doGet("/.well-known/assetlinks.json", JsonNode.class); - assertThat(assetLinks.get(0).get("target").get("package_name").asText()).isEqualTo(ANDROID_PACKAGE_NAME); - assertThat(assetLinks.get(0).get("target").get("sha256_cert_fingerprints").get(0).asText()).isEqualTo(ANDROID_APP_SHA256); - - JsonNode appleAssociation = doGet("/.well-known/apple-app-site-association", JsonNode.class); - assertThat(appleAssociation.get("applinks").get("details").get(0).get("appID").asText()).isEqualTo(APPLE_APP_ID); - } - - @Test - public void testGetMobileDeepLink() throws Exception { - loginSysAdmin(); - String deepLink = doGet("/api/mobile/deepLink", String.class); - - Pattern expectedPattern = Pattern.compile("\"https://([^/]+)/api/noauth/qr\\?secret=([^&]+)&ttl=([^&]+)&host=([^&]+)\""); - Matcher parsedDeepLink = expectedPattern.matcher(deepLink); - assertThat(parsedDeepLink.matches()).isTrue(); - String appHost = parsedDeepLink.group(1); - String secret = parsedDeepLink.group(2); - String ttl = parsedDeepLink.group(3); - assertThat(appHost).isEqualTo("demo.thingsboard.io"); - assertThat(ttl).isEqualTo(String.valueOf(mobileSecretKeyTtl)); - - JwtPair jwtPair = doGet("/api/noauth/qr/" + secret, JwtPair.class); - assertThat(jwtPair).isNotNull(); - - loginTenantAdmin(); - String tenantDeepLink = doGet("/api/mobile/deepLink", String.class); - Matcher tenantParsedDeepLink = expectedPattern.matcher(tenantDeepLink); - assertThat(tenantParsedDeepLink.matches()).isTrue(); - String tenantSecret = tenantParsedDeepLink.group(2); - - JwtPair tenantJwtPair = doGet("/api/noauth/qr/" + tenantSecret, JwtPair.class); - assertThat(tenantJwtPair).isNotNull(); - - loginCustomerUser(); - String customerDeepLink = doGet("/api/mobile/deepLink", String.class); - Matcher customerParsedDeepLink = expectedPattern.matcher(customerDeepLink); - assertThat(customerParsedDeepLink.matches()).isTrue(); - String customerSecret = customerParsedDeepLink.group(2); - - JwtPair customerJwtPair = doGet("/api/noauth/qr/" + customerSecret, JwtPair.class); - assertThat(customerJwtPair).isNotNull(); - - // update mobile setting to use custom one - loginSysAdmin(); - MobileAppSettings mobileAppSettings = doGet("/api/mobile/app/settings", MobileAppSettings.class); - mobileAppSettings.setUseDefaultApp(false); - doPost("/api/mobile/app/settings", mobileAppSettings); - - String customAppDeepLink = doGet("/api/mobile/deepLink", String.class); - Pattern customAppExpectedPattern = Pattern.compile("\"https://([^/]+)/api/noauth/qr\\?secret=([^&]+)&ttl=([^&]+)\""); - Matcher customAppParsedDeepLink = customAppExpectedPattern.matcher(customAppDeepLink); - assertThat(customAppParsedDeepLink.matches()).isTrue(); - assertThat(customAppParsedDeepLink.group(1)).isEqualTo("localhost"); - - loginTenantAdmin(); - String tenantCustomAppDeepLink = doGet("/api/mobile/deepLink", String.class); - Matcher tenantCustomAppParsedDeepLink = customAppExpectedPattern.matcher(tenantCustomAppDeepLink); - assertThat(tenantCustomAppParsedDeepLink.matches()).isTrue(); - assertThat(tenantCustomAppParsedDeepLink.group(1)).isEqualTo("localhost"); - - loginCustomerUser(); - String customerCustomAppDeepLink = doGet("/api/mobile/deepLink", String.class); - Matcher customerCustomAppParsedDeepLink = customAppExpectedPattern.matcher(customerCustomAppDeepLink); - assertThat(customerCustomAppParsedDeepLink.matches()).isTrue(); - assertThat(customerCustomAppParsedDeepLink.group(1)).isEqualTo("localhost"); - } -} diff --git a/application/src/test/java/org/thingsboard/server/controller/QrCodeSettingsControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/QrCodeSettingsControllerTest.java new file mode 100644 index 0000000000..2ed4933d2b --- /dev/null +++ b/application/src/test/java/org/thingsboard/server/controller/QrCodeSettingsControllerTest.java @@ -0,0 +1,258 @@ +/** + * 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.controller; + +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.JsonNode; +import lombok.extern.slf4j.Slf4j; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.springframework.beans.factory.annotation.Value; +import org.thingsboard.server.common.data.StringUtils; +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.MobileAppBundle; +import org.thingsboard.server.common.data.mobile.MobileAppBundleInfo; +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.common.data.page.PageData; +import org.thingsboard.server.common.data.page.PageLink; +import org.thingsboard.server.common.data.security.model.JwtPair; +import org.thingsboard.server.dao.service.DaoSqlTest; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.hamcrest.Matchers.containsString; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +@Slf4j +@DaoSqlTest +public class QrCodeSettingsControllerTest extends AbstractControllerTest { + + @Value("${cache.specs.mobileSecretKey.timeToLiveInMinutes:2}") + private int mobileSecretKeyTtl; + + static final TypeReference> PAGE_DATA_MOBILE_APP_BUNDLE_TYPE_REF = new TypeReference<>() { + }; + static final TypeReference> PAGE_DATA_MOBILE_APP_TYPE_REF = new TypeReference<>() { + }; + private static final String ANDROID_PACKAGE_NAME = "testAppPackage"; + private static final String ANDROID_APP_SHA256 = "DF:28:32:66:8B:A7:D3:EC:7D:73:CF:CC"; + private static final String ANDROID_STORE_LINK = "https://store.link.com"; + private static final String APPLE_APP_ID = "testId"; + private static final String TEST_LABEL = "Test label"; + private static final String IOS_STORE_LINK = "https://store.link.com"; + + private MobileAppBundle mobileAppBundle; + + @Before + public void setUp() throws Exception { + loginSysAdmin(); + + MobileApp androidApp = validMobileApp(TenantId.SYS_TENANT_ID, "my.android.package", PlatformType.ANDROID, true); + AndroidQrCodeConfig androidQrCodeConfig = AndroidQrCodeConfig.builder() + .appPackage(ANDROID_PACKAGE_NAME) + .sha256CertFingerprints(ANDROID_APP_SHA256) + .storeLink(ANDROID_STORE_LINK) + .enabled(true) + .build(); + androidApp.setQrCodeConfig(androidQrCodeConfig); + MobileApp savedAndroidApp = doPost("/api/mobile/app", androidApp, MobileApp.class); + + MobileApp iosApp = validMobileApp(TenantId.SYS_TENANT_ID, "my.ios.package", PlatformType.IOS, true); + IosQrCodeConfig iosQrCodeConfig = IosQrCodeConfig.builder() + .appId(APPLE_APP_ID) + .enabled(true) + .storeLink(IOS_STORE_LINK) + .build(); + iosApp.setQrCodeConfig(iosQrCodeConfig); + MobileApp savedIosApp = doPost("/api/mobile/app", iosApp, MobileApp.class); + + mobileAppBundle = new MobileAppBundle(); + mobileAppBundle.setTitle("Test bundle"); + mobileAppBundle.setAndroidAppId(savedAndroidApp.getId()); + mobileAppBundle.setIosAppId(savedIosApp.getId()); + + mobileAppBundle = doPost("/api/mobile/bundle", mobileAppBundle, MobileAppBundle.class); + + QrCodeSettings qrCodeSettings = doGet("/api/qr/settings", QrCodeSettings.class); + QRCodeConfig qrCodeConfig = new QRCodeConfig(); + qrCodeConfig.setQrCodeLabel(TEST_LABEL); + qrCodeSettings.setUseDefaultApp(true); + qrCodeSettings.setMobileAppBundleId(null); + qrCodeSettings.setQrCodeConfig(qrCodeConfig); + + doPost("/api/qr/settings", qrCodeSettings) + .andExpect(status().isOk()); + } + + @After + public void tearDown() throws Exception { + PageData 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()); + } + + PageData 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()); + } + } + + @Test + public void testSaveQrCodeSettings() throws Exception { + loginSysAdmin(); + QrCodeSettings qrCodeSettings = doGet("/api/qr/settings", QrCodeSettings.class); + assertThat(qrCodeSettings.getQrCodeConfig().getQrCodeLabel()).isEqualTo(TEST_LABEL); + assertThat(qrCodeSettings.isUseDefaultApp()).isTrue(); + + qrCodeSettings.setUseDefaultApp(false); + qrCodeSettings.setMobileAppBundleId(mobileAppBundle.getId()); + + doPost("/api/qr/settings", qrCodeSettings) + .andExpect(status().isOk()); + + QrCodeSettings updatedQrCodeSettings = doGet("/api/qr/settings", QrCodeSettings.class); + assertThat(updatedQrCodeSettings.isUseDefaultApp()).isFalse(); + } + + @Test + public void testShouldNotSaveQrCodeSettingsWithoutRequiredConfig() throws Exception { + loginSysAdmin(); + QrCodeSettings qrCodeSettings = doGet("/api/qr/settings", QrCodeSettings.class); + + qrCodeSettings.setUseDefaultApp(false); + qrCodeSettings.setQrCodeConfig(null); + qrCodeSettings.setMobileAppBundleId(null); + + doPost("/api/qr/settings", qrCodeSettings) + .andExpect(status().isBadRequest()) + .andExpect(statusReason(containsString("Bundle required to use custom application!"))); + + qrCodeSettings.setMobileAppBundleId(mobileAppBundle.getId()); + doPost("/api/qr/settings", qrCodeSettings) + .andExpect(status().isBadRequest()) + .andExpect(statusReason(containsString("Qr code configuration is required!"))); + + qrCodeSettings.setQrCodeConfig(QRCodeConfig.builder().showOnHomePage(false).build()); + doPost("/api/qr/settings", qrCodeSettings) + .andExpect(status().isOk()); + } + + @Test + public void testShouldSaveQrCodeSettingsForDefaultApp() throws Exception { + loginSysAdmin(); + QrCodeSettings qrCodeSettings = doGet("/api/qr/settings", QrCodeSettings.class); + qrCodeSettings.setUseDefaultApp(true); + qrCodeSettings.setMobileAppBundleId(null); + + doPost("/api/qr/settings", qrCodeSettings) + .andExpect(status().isOk()); + } + + @Test + public void testGetApplicationAssociations() throws Exception { + loginSysAdmin(); + QrCodeSettings qrCodeSettings = doGet("/api/qr/settings", QrCodeSettings.class); + qrCodeSettings.setUseDefaultApp(false); + doPost("/api/qr/settings", qrCodeSettings) + .andExpect(status().isOk()); + + JsonNode assetLinks = doGet("/.well-known/assetlinks.json", JsonNode.class); + assertThat(assetLinks.get(0).get("target").get("package_name").asText()).isEqualTo(ANDROID_PACKAGE_NAME); + assertThat(assetLinks.get(0).get("target").get("sha256_cert_fingerprints").get(0).asText()).isEqualTo(ANDROID_APP_SHA256); + + JsonNode appleAssociation = doGet("/.well-known/apple-app-site-association", JsonNode.class); + assertThat(appleAssociation.get("applinks").get("details").get(0).get("appID").asText()).isEqualTo(APPLE_APP_ID); + } + + @Test + public void testGetMobileDeepLink() throws Exception { + loginSysAdmin(); + String deepLink = doGet("/api/qr/deepLink", String.class); + + Pattern expectedPattern = Pattern.compile("\"https://([^/]+)/api/noauth/qr\\?secret=([^&]+)&ttl=([^&]+)&host=([^&]+)\""); + Matcher parsedDeepLink = expectedPattern.matcher(deepLink); + assertThat(parsedDeepLink.matches()).isTrue(); + String appHost = parsedDeepLink.group(1); + String secret = parsedDeepLink.group(2); + String ttl = parsedDeepLink.group(3); + assertThat(appHost).isEqualTo("demo.thingsboard.io"); + assertThat(ttl).isEqualTo(String.valueOf(mobileSecretKeyTtl)); + + JwtPair jwtPair = doGet("/api/noauth/qr/" + secret, JwtPair.class); + assertThat(jwtPair).isNotNull(); + + loginTenantAdmin(); + String tenantDeepLink = doGet("/api/qr/deepLink", String.class); + Matcher tenantParsedDeepLink = expectedPattern.matcher(tenantDeepLink); + assertThat(tenantParsedDeepLink.matches()).isTrue(); + String tenantSecret = tenantParsedDeepLink.group(2); + + JwtPair tenantJwtPair = doGet("/api/noauth/qr/" + tenantSecret, JwtPair.class); + assertThat(tenantJwtPair).isNotNull(); + + loginCustomerUser(); + String customerDeepLink = doGet("/api/qr/deepLink", String.class); + Matcher customerParsedDeepLink = expectedPattern.matcher(customerDeepLink); + assertThat(customerParsedDeepLink.matches()).isTrue(); + String customerSecret = customerParsedDeepLink.group(2); + + JwtPair customerJwtPair = doGet("/api/noauth/qr/" + customerSecret, JwtPair.class); + assertThat(customerJwtPair).isNotNull(); + + // update mobile setting to use custom one + loginSysAdmin(); + QrCodeSettings qrCodeSettings = doGet("/api/qr/settings", QrCodeSettings.class); + qrCodeSettings.setUseDefaultApp(false); + doPost("/api/mobile/app/settings", qrCodeSettings); + + String customAppDeepLink = doGet("/api/qr/deepLink", String.class); + Pattern customAppExpectedPattern = Pattern.compile("\"https://([^/]+)/api/noauth/qr\\?secret=([^&]+)&ttl=([^&]+)\""); + Matcher customAppParsedDeepLink = customAppExpectedPattern.matcher(customAppDeepLink); + assertThat(customAppParsedDeepLink.matches()).isTrue(); + assertThat(customAppParsedDeepLink.group(1)).isEqualTo("localhost"); + + loginTenantAdmin(); + String tenantCustomAppDeepLink = doGet("/api/qr/deepLink", String.class); + Matcher tenantCustomAppParsedDeepLink = customAppExpectedPattern.matcher(tenantCustomAppDeepLink); + assertThat(tenantCustomAppParsedDeepLink.matches()).isTrue(); + assertThat(tenantCustomAppParsedDeepLink.group(1)).isEqualTo("localhost"); + + loginCustomerUser(); + String customerCustomAppDeepLink = doGet("/api/qr/deepLink", String.class); + Matcher customerCustomAppParsedDeepLink = customAppExpectedPattern.matcher(customerCustomAppDeepLink); + assertThat(customerCustomAppParsedDeepLink.matches()).isTrue(); + assertThat(customerCustomAppParsedDeepLink.group(1)).isEqualTo("localhost"); + } + + 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; + } +} diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/mobile/MobileAppBundleService.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/mobile/MobileAppBundleService.java new file mode 100644 index 0000000000..45e404d4af --- /dev/null +++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/mobile/MobileAppBundleService.java @@ -0,0 +1,47 @@ +/** + * 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.mobile; + +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.MobileAppBundle; +import org.thingsboard.server.common.data.mobile.MobileAppBundleInfo; +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; + +import java.util.List; + +public interface MobileAppBundleService extends EntityDaoService { + + MobileAppBundle saveMobileAppBundle(TenantId tenantId, MobileAppBundle mobileAppBundle); + + void deleteMobileAppBundleById(TenantId tenantId, MobileAppBundleId mobileAppBundleId); + + MobileAppBundle findMobileAppBundleById(TenantId tenantId, MobileAppBundleId mobileAppBundleId); + + PageData findMobileAppBundleInfosByTenantId(TenantId tenantId, PageLink pageLink); + + MobileAppBundleInfo findMobileAppBundleInfoById(TenantId tenantId, MobileAppBundleId mobileAppBundleId); + + void updateOauth2Clients(TenantId tenantId, MobileAppBundleId mobileAppBundleId, List oAuth2ClientIds); + + MobileAppBundle findMobileAppBundleByPkgNameAndPlatform(TenantId tenantId, String pkgName, PlatformType platform); + + void deleteMobileAppsByTenantId(TenantId tenantId); +} diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/mobile/MobileAppService.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/mobile/MobileAppService.java index 2976022116..57be235577 100644 --- a/common/dao-api/src/main/java/org/thingsboard/server/dao/mobile/MobileAppService.java +++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/mobile/MobileAppService.java @@ -15,17 +15,16 @@ */ 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.OAuth2ClientId; 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.MobileAppInfo; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.dao.entity.EntityDaoService; -import java.util.List; - public interface MobileAppService extends EntityDaoService { MobileApp saveMobileApp(TenantId tenantId, MobileApp mobileApp); @@ -34,11 +33,12 @@ public interface MobileAppService extends EntityDaoService { MobileApp findMobileAppById(TenantId tenantId, MobileAppId mobileAppId); - PageData findMobileAppInfosByTenantId(TenantId tenantId, PageLink pageLink); + PageData findMobileAppsByTenantId(TenantId tenantId, PageLink pageLink); - MobileAppInfo findMobileAppInfoById(TenantId tenantId, MobileAppId mobileAppId); + AndroidQrCodeConfig findAndroidQrCodeConfig(TenantId tenantId, MobileAppBundleId mobileAppBundleId); - void updateOauth2Clients(TenantId tenantId, MobileAppId mobileAppId, List oAuth2ClientIds); + IosQrCodeConfig findIosQrCodeConfig(TenantId tenantId, MobileAppBundleId mobileAppBundleId); void deleteMobileAppsByTenantId(TenantId tenantId); + } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/CacheConstants.java b/common/data/src/main/java/org/thingsboard/server/common/data/CacheConstants.java index 54a5bc755d..71c52d43c1 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/CacheConstants.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/CacheConstants.java @@ -51,6 +51,6 @@ public class CacheConstants { public static final String ENTITY_COUNT_CACHE = "entityCount"; public static final String RESOURCE_INFO_CACHE = "resourceInfo"; public static final String ALARM_TYPES_CACHE = "alarmTypes"; - public static final String MOBILE_APP_SETTINGS_CACHE = "mobileAppSettings"; + public static final String QR_CODE_SETTINGS_CACHE = "qrCodeSettings"; public static final String MOBILE_SECRET_KEY_CACHE = "mobileSecretKey"; } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/EntityType.java b/common/data/src/main/java/org/thingsboard/server/common/data/EntityType.java index b5ce79e20f..89b434047b 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/EntityType.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/EntityType.java @@ -60,7 +60,8 @@ public enum EntityType { QUEUE_STATS(34), OAUTH2_CLIENT(35), DOMAIN(36), - MOBILE_APP(37); + MOBILE_APP(37), + MOBILE_APP_BUNDLE(37); @Getter private final int protoNumber; // Corresponds to EntityTypeProto diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/id/EntityIdFactory.java b/common/data/src/main/java/org/thingsboard/server/common/data/id/EntityIdFactory.java index 5a85e6ce67..8e2688248e 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/id/EntityIdFactory.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/id/EntityIdFactory.java @@ -111,6 +111,8 @@ public class EntityIdFactory { return new MobileAppId(uuid); case DOMAIN: return new DomainId(uuid); + case MOBILE_APP_BUNDLE: + return new MobileAppBundleId(uuid); } throw new IllegalArgumentException("EntityType " + type + " is not supported!"); } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/id/MobileAppBundleId.java b/common/data/src/main/java/org/thingsboard/server/common/data/id/MobileAppBundleId.java new file mode 100644 index 0000000000..b3344dbcef --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/id/MobileAppBundleId.java @@ -0,0 +1,39 @@ +/** + * 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.id; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; +import org.thingsboard.server.common.data.EntityType; + +import java.util.UUID; + +public class MobileAppBundleId extends UUIDBased implements EntityId{ + + @JsonCreator + public MobileAppBundleId(@JsonProperty("id") UUID id) { + super(id); + } + + public static MobileAppBundleId fromString(String mobileAppId) { + return new MobileAppBundleId(UUID.fromString(mobileAppId)); + } + + @Override + public EntityType getEntityType() { + return EntityType.MOBILE_APP_BUNDLE; + } +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/id/MobileAppSettingsId.java b/common/data/src/main/java/org/thingsboard/server/common/data/id/QrCodeSettingsId.java similarity index 77% rename from common/data/src/main/java/org/thingsboard/server/common/data/id/MobileAppSettingsId.java rename to common/data/src/main/java/org/thingsboard/server/common/data/id/QrCodeSettingsId.java index d152701fc4..b58e1a98a3 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/id/MobileAppSettingsId.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/id/QrCodeSettingsId.java @@ -22,16 +22,16 @@ import io.swagger.v3.oas.annotations.media.Schema; import java.util.UUID; @Schema -public class MobileAppSettingsId extends UUIDBased { +public class QrCodeSettingsId extends UUIDBased { private static final long serialVersionUID = 1L; @JsonCreator - public MobileAppSettingsId(@JsonProperty("id") UUID id) { + public QrCodeSettingsId(@JsonProperty("id") UUID id) { super(id); } - public static MobileAppSettingsId fromString(String mobileAppSettingsId) { - return new MobileAppSettingsId(UUID.fromString(mobileAppSettingsId)); + public static QrCodeSettingsId fromString(String qrCodeSettingsId) { + return new QrCodeSettingsId(UUID.fromString(qrCodeSettingsId)); } } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/mobile/AndroidConfig.java b/common/data/src/main/java/org/thingsboard/server/common/data/mobile/AndroidQrCodeConfig.java similarity index 78% rename from common/data/src/main/java/org/thingsboard/server/common/data/mobile/AndroidConfig.java rename to common/data/src/main/java/org/thingsboard/server/common/data/mobile/AndroidQrCodeConfig.java index d670382462..01e4845702 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/mobile/AndroidConfig.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/mobile/AndroidQrCodeConfig.java @@ -15,11 +15,13 @@ */ package org.thingsboard.server.common.data.mobile; +import jakarta.validation.constraints.NotBlank; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; import lombok.EqualsAndHashCode; import lombok.NoArgsConstructor; +import org.thingsboard.server.common.data.oauth2.PlatformType; import org.thingsboard.server.common.data.validation.NoXss; @Data @@ -27,14 +29,21 @@ import org.thingsboard.server.common.data.validation.NoXss; @NoArgsConstructor @AllArgsConstructor @EqualsAndHashCode -public class AndroidConfig { +public class AndroidQrCodeConfig implements QrCodeConfig { private boolean enabled; @NoXss + @NotBlank private String appPackage; @NoXss + @NotBlank private String sha256CertFingerprints; @NoXss + @NotBlank private String storeLink; + @Override + public PlatformType getType() { + return PlatformType.ANDROID; + } } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/mobile/IosConfig.java b/common/data/src/main/java/org/thingsboard/server/common/data/mobile/IosQrCodeConfig.java similarity index 78% rename from common/data/src/main/java/org/thingsboard/server/common/data/mobile/IosConfig.java rename to common/data/src/main/java/org/thingsboard/server/common/data/mobile/IosQrCodeConfig.java index 7d40dfe805..84ed6c726e 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/mobile/IosConfig.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/mobile/IosQrCodeConfig.java @@ -15,11 +15,13 @@ */ package org.thingsboard.server.common.data.mobile; +import jakarta.validation.constraints.NotBlank; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; import lombok.EqualsAndHashCode; import lombok.NoArgsConstructor; +import org.thingsboard.server.common.data.oauth2.PlatformType; import org.thingsboard.server.common.data.validation.NoXss; @Data @@ -27,12 +29,18 @@ import org.thingsboard.server.common.data.validation.NoXss; @NoArgsConstructor @AllArgsConstructor @EqualsAndHashCode -public class IosConfig { +public class IosQrCodeConfig implements QrCodeConfig { private boolean enabled; @NoXss + @NotBlank private String appId; @NoXss + @NotBlank private String storeLink; + @Override + public PlatformType getType() { + return PlatformType.IOS; + } } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/mobile/MobileApp.java b/common/data/src/main/java/org/thingsboard/server/common/data/mobile/MobileApp.java index ad207e5635..018d79b965 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/mobile/MobileApp.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/mobile/MobileApp.java @@ -16,7 +16,9 @@ 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; import jakarta.validation.constraints.NotEmpty; import lombok.Data; @@ -27,7 +29,9 @@ import org.thingsboard.server.common.data.HasName; import org.thingsboard.server.common.data.HasTenantId; 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 @@ -44,8 +48,17 @@ public class MobileApp extends BaseData implements HasTenantId, Has @NotEmpty @Length(fieldName = "appSecret", min = 16, max = 2048, message = "must be at least 16 and max 2048 characters") private String appSecret; - @Schema(description = "Whether OAuth2 settings are enabled or not") - private boolean oauth2Enabled; + @Schema(description = "Application platform type: ANDROID or IOS", requiredMode = Schema.RequiredMode.REQUIRED) + private PlatformType platformType; + @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; + @Schema(description = "Application qr code configuration") + @Valid + private QrCodeConfig qrCodeConfig; public MobileApp() { super(); @@ -60,7 +73,10 @@ public class MobileApp extends BaseData implements HasTenantId, Has this.tenantId = mobile.tenantId; this.pkgName = mobile.pkgName; this.appSecret = mobile.appSecret; - this.oauth2Enabled = mobile.oauth2Enabled; + this.platformType = mobile.platformType; + this.status = mobile.status; + this.versionInfo = mobile.versionInfo; + this.qrCodeConfig = mobile.qrCodeConfig; } @Override diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/mobile/MobileAppBundle.java b/common/data/src/main/java/org/thingsboard/server/common/data/mobile/MobileAppBundle.java new file mode 100644 index 0000000000..10c8637f35 --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/mobile/MobileAppBundle.java @@ -0,0 +1,84 @@ +/** + * 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 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; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.ToString; +import org.thingsboard.server.common.data.BaseData; +import org.thingsboard.server.common.data.HasName; +import org.thingsboard.server.common.data.HasTenantId; +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.validation.Length; +import org.thingsboard.server.common.data.validation.NoXss; + +@EqualsAndHashCode(callSuper = true) +@Data +@ToString +public class MobileAppBundle extends BaseData implements HasTenantId, HasName { + + @Schema(description = "JSON object with Tenant Id") + private TenantId tenantId; + @Schema(description = "Application bundle title. Cannot be empty", requiredMode = Schema.RequiredMode.REQUIRED) + @NotBlank + @Length(fieldName = "title") + private String title; + @Schema(description = "Application bundle description.") + @Length(fieldName = "description") + private String description; + @Schema(description = "Android application id") + private MobileAppId androidAppId; + @Schema(description = "IOS application id") + private MobileAppId iosAppId; + @Schema(description = "Application layout configuration") + @Valid + private MobileLayoutConfig layoutConfig; + @Schema(description = "Whether OAuth2 settings are enabled or not") + private Boolean oauth2Enabled; + + public MobileAppBundle() { + super(); + } + + public MobileAppBundle(MobileAppBundleId id) { + super(id); + } + + public MobileAppBundle(MobileAppBundle mobile) { + super(mobile); + this.tenantId = mobile.tenantId; + this.title = mobile.title; + this.description = mobile.description; + this.androidAppId = mobile.androidAppId; + this.iosAppId = mobile.iosAppId; + this.layoutConfig = mobile.layoutConfig; + this.oauth2Enabled = mobile.oauth2Enabled; + } + + @Override + @JsonProperty(access = JsonProperty.Access.READ_ONLY) + @Schema(description = "Mobile app bundle title", example = "My main application", accessMode = Schema.AccessMode.READ_ONLY) + public String getName() { + return title; + } +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/mobile/MobileAppInfo.java b/common/data/src/main/java/org/thingsboard/server/common/data/mobile/MobileAppBundleInfo.java similarity index 63% rename from common/data/src/main/java/org/thingsboard/server/common/data/mobile/MobileAppInfo.java rename to common/data/src/main/java/org/thingsboard/server/common/data/mobile/MobileAppBundleInfo.java index 4d85028e36..64f3396d56 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/mobile/MobileAppInfo.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/mobile/MobileAppBundleInfo.java @@ -18,7 +18,7 @@ package org.thingsboard.server.common.data.mobile; import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; import lombok.EqualsAndHashCode; -import org.thingsboard.server.common.data.id.MobileAppId; +import org.thingsboard.server.common.data.id.MobileAppBundleId; import org.thingsboard.server.common.data.oauth2.OAuth2ClientInfo; import java.util.List; @@ -26,22 +26,28 @@ import java.util.List; @EqualsAndHashCode(callSuper = true) @Data @Schema -public class MobileAppInfo extends MobileApp { +public class MobileAppBundleInfo extends MobileAppBundle { + @Schema(description = "Android package name") + private String androidPkgName; + @Schema(description = "IOS package name") + private String iosPkgName; @Schema(description = "List of available oauth2 clients") private List oauth2ClientInfos; - public MobileAppInfo(MobileApp mobileApp, List oauth2ClientInfos) { + public MobileAppBundleInfo(MobileAppBundle mobileApp, String androidPkgName, String iosPkgName, List oauth2ClientInfos) { super(mobileApp); + this.androidPkgName = androidPkgName; + this.iosPkgName = iosPkgName; this.oauth2ClientInfos = oauth2ClientInfos; } - public MobileAppInfo() { + public MobileAppBundleInfo() { super(); } - public MobileAppInfo(MobileAppId mobileAppId) { - super(mobileAppId); + public MobileAppBundleInfo(MobileAppBundleId mobileAppBundleId) { + super(mobileAppBundleId); } } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/mobile/MobileAppOauth2Client.java b/common/data/src/main/java/org/thingsboard/server/common/data/mobile/MobileAppBundleOauth2Client.java similarity index 85% rename from common/data/src/main/java/org/thingsboard/server/common/data/mobile/MobileAppOauth2Client.java rename to common/data/src/main/java/org/thingsboard/server/common/data/mobile/MobileAppBundleOauth2Client.java index 2be75db92f..fcd261ead1 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/mobile/MobileAppOauth2Client.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/mobile/MobileAppBundleOauth2Client.java @@ -18,15 +18,15 @@ package org.thingsboard.server.common.data.mobile; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; -import org.thingsboard.server.common.data.id.MobileAppId; +import org.thingsboard.server.common.data.id.MobileAppBundleId; import org.thingsboard.server.common.data.id.OAuth2ClientId; @Data @NoArgsConstructor @AllArgsConstructor -public class MobileAppOauth2Client { +public class MobileAppBundleOauth2Client { - private MobileAppId mobileAppId; + private MobileAppBundleId mobileAppBundleId; private OAuth2ClientId oAuth2ClientId; } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/mobile/MobileAppStatus.java b/common/data/src/main/java/org/thingsboard/server/common/data/mobile/MobileAppStatus.java new file mode 100644 index 0000000000..f1043c732d --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/mobile/MobileAppStatus.java @@ -0,0 +1,24 @@ +/** + * 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; + +public enum MobileAppStatus { + + PUBLISHED, + DEPRECATED, + SUSPENDED + +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/mobile/MobileLayoutConfig.java b/common/data/src/main/java/org/thingsboard/server/common/data/mobile/MobileLayoutConfig.java new file mode 100644 index 0000000000..662ceee13f --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/mobile/MobileLayoutConfig.java @@ -0,0 +1,40 @@ +/** + * 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 jakarta.validation.Valid; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; + +import java.util.ArrayList; +import java.util.List; + +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +@EqualsAndHashCode +public class MobileLayoutConfig { + + @Schema(description = "List of custom menu items", requiredMode = Schema.RequiredMode.REQUIRED) + @Valid + private List items = new ArrayList<>(); + +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/mobile/MobileLoginInfo.java b/common/data/src/main/java/org/thingsboard/server/common/data/mobile/MobileLoginInfo.java new file mode 100644 index 0000000000..2d9717f279 --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/mobile/MobileLoginInfo.java @@ -0,0 +1,28 @@ +/** + * 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.AllArgsConstructor; +import lombok.Data; +import org.thingsboard.server.common.data.oauth2.OAuth2ClientLoginInfo; + +import java.util.List; + +@Data +@AllArgsConstructor +public class MobileLoginInfo { + List oAuth2ClientLoginInfos; +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/mobile/MobileMenuItem.java b/common/data/src/main/java/org/thingsboard/server/common/data/mobile/MobileMenuItem.java new file mode 100644 index 0000000000..ef876f88d9 --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/mobile/MobileMenuItem.java @@ -0,0 +1,41 @@ +/** + * 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; + +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +@EqualsAndHashCode +public class MobileMenuItem { + + @Schema(description = "Menu item label", example = "Ar quality", requiredMode = Schema.RequiredMode.REQUIRED) + private String label; + @Schema(description = "URL of the menu item icon", example = "home_icon") + private String icon; + @Schema(description = "Path to open, when user clicks the menu item", example = "/dashboard") + private MobileMenuPath path; + @Schema(description = "Id of the resource to open, when user clicks the menu item", example = "8a8d81b0-5975-11ef-83b1-d3209c242a36") + private String id; + +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/mobile/MobileMenuPath.java b/common/data/src/main/java/org/thingsboard/server/common/data/mobile/MobileMenuPath.java new file mode 100644 index 0000000000..68d81c146a --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/mobile/MobileMenuPath.java @@ -0,0 +1,32 @@ +/** + * 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; + +public enum MobileMenuPath { + + HOME, + ASSETS, + DEVICES, + DEVICE_LIST, + ALARMS, + DASHBOARDS, + DASHBOARD, + AUDIT_LOGS, + CUSTOMERS, + CUSTOMER, + NOTIFICATION, + CUSTOM +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/mobile/MobileUserInfo.java b/common/data/src/main/java/org/thingsboard/server/common/data/mobile/MobileUserInfo.java new file mode 100644 index 0000000000..1d078ab6d1 --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/mobile/MobileUserInfo.java @@ -0,0 +1,29 @@ +/** + * 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.AllArgsConstructor; +import lombok.Data; +import org.thingsboard.server.common.data.HomeDashboardInfo; +import org.thingsboard.server.common.data.User; + +@Data +@AllArgsConstructor +public class MobileUserInfo { + User user; + HomeDashboardInfo homeDashboardInfo; + MobileLayoutConfig layoutConfig; +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/mobile/QrCodeConfig.java b/common/data/src/main/java/org/thingsboard/server/common/data/mobile/QrCodeConfig.java new file mode 100644 index 0000000000..01f4eaa98a --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/mobile/QrCodeConfig.java @@ -0,0 +1,37 @@ +/** + * 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 com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonSubTypes; +import com.fasterxml.jackson.annotation.JsonTypeInfo; +import org.thingsboard.server.common.data.oauth2.PlatformType; + +@JsonIgnoreProperties(ignoreUnknown = true) +@JsonTypeInfo( + use = JsonTypeInfo.Id.NAME, + include = JsonTypeInfo.As.EXISTING_PROPERTY, + property = "type") +@JsonSubTypes({ + @JsonSubTypes.Type(value = AndroidQrCodeConfig.class, name = "ANDROID"), + @JsonSubTypes.Type(value = IosQrCodeConfig.class, name = "IOS") +}) +public interface QrCodeConfig { + + PlatformType getType(); + + +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/mobile/MobileAppSettings.java b/common/data/src/main/java/org/thingsboard/server/common/data/mobile/QrCodeSettings.java similarity index 76% rename from common/data/src/main/java/org/thingsboard/server/common/data/mobile/MobileAppSettings.java rename to common/data/src/main/java/org/thingsboard/server/common/data/mobile/QrCodeSettings.java index 31b2029bfc..ed2578cc88 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/mobile/MobileAppSettings.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/mobile/QrCodeSettings.java @@ -22,13 +22,15 @@ import lombok.Data; import lombok.EqualsAndHashCode; import org.thingsboard.server.common.data.BaseData; import org.thingsboard.server.common.data.HasTenantId; -import org.thingsboard.server.common.data.id.MobileAppSettingsId; +import org.thingsboard.server.common.data.id.MobileAppBundleId; +import org.thingsboard.server.common.data.id.MobileAppId; +import org.thingsboard.server.common.data.id.QrCodeSettingsId; import org.thingsboard.server.common.data.id.TenantId; @Schema @Data @EqualsAndHashCode(callSuper = true) -public class MobileAppSettings extends BaseData implements HasTenantId { +public class QrCodeSettings extends BaseData implements HasTenantId { private static final long serialVersionUID = 2628323657987010348L; @@ -36,12 +38,8 @@ public class MobileAppSettings extends BaseData implements private TenantId tenantId; @Schema(requiredMode = Schema.RequiredMode.REQUIRED, description = "Type of application: true means use default Thingsboard app", example = "true") private boolean useDefaultApp; - @Valid - @Schema(requiredMode = Schema.RequiredMode.REQUIRED, description = "Android mobile app configuration.") - private AndroidConfig androidConfig; - @Valid - @Schema(requiredMode = Schema.RequiredMode.REQUIRED, description = "Ios mobile app configuration.") - private IosConfig iosConfig; + @Schema(description = "Mobile app bundle.") + private MobileAppBundleId mobileAppBundleId; @Valid @Schema(requiredMode = Schema.RequiredMode.REQUIRED, description = "QR code config configuration.") private QRCodeConfig qrCodeConfig; @@ -52,10 +50,10 @@ public class MobileAppSettings extends BaseData implements @JsonProperty(access = JsonProperty.Access.READ_ONLY) private String defaultAppStoreLink; - public MobileAppSettings() { + public QrCodeSettings() { } - public MobileAppSettings(MobileAppSettingsId id) { + public QrCodeSettings(QrCodeSettingsId id) { super(id); } diff --git a/dao/src/main/java/org/thingsboard/server/dao/mobile/BaseMobileAppSettingsService.java b/dao/src/main/java/org/thingsboard/server/dao/mobile/BaseMobileAppSettingsService.java deleted file mode 100644 index 5ec717da09..0000000000 --- a/dao/src/main/java/org/thingsboard/server/dao/mobile/BaseMobileAppSettingsService.java +++ /dev/null @@ -1,120 +0,0 @@ -/** - * 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.mobile; - -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -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.AndroidConfig; -import org.thingsboard.server.common.data.mobile.BadgePosition; -import org.thingsboard.server.common.data.mobile.IosConfig; -import org.thingsboard.server.common.data.mobile.MobileAppSettings; -import org.thingsboard.server.common.data.mobile.QRCodeConfig; -import org.thingsboard.server.dao.entity.AbstractCachedEntityService; -import org.thingsboard.server.dao.service.DataValidator; - -import java.util.Map; - -import static org.thingsboard.server.dao.service.Validator.validateId; - -@Service -@Slf4j -@RequiredArgsConstructor -public class BaseMobileAppSettingsService extends AbstractCachedEntityService implements MobileAppSettingsService { - - public static final String INCORRECT_TENANT_ID = "Incorrect tenantId "; - private static final String DEFAULT_QR_CODE_LABEL = "Scan to connect or download mobile app"; - - @Value("${mobileApp.googlePlayLink:https://play.google.com/store/apps/details?id=org.thingsboard.demo.app}") - private String googlePlayLink; - @Value("${mobileApp.appStoreLink:https://apps.apple.com/us/app/thingsboard-live/id1594355695}") - private String appStoreLink; - - private final MobileAppSettingsDao mobileAppSettingsDao; - private final DataValidator mobileAppSettingsDataValidator; - - @Override - public MobileAppSettings saveMobileAppSettings(TenantId tenantId, MobileAppSettings mobileAppSettings) { - mobileAppSettingsDataValidator.validate(mobileAppSettings, s -> tenantId); - try { - MobileAppSettings savedMobileAppSettings = mobileAppSettingsDao.save(tenantId, mobileAppSettings); - publishEvictEvent(new MobileAppSettingsEvictEvent(tenantId)); - return constructMobileAppSettings(savedMobileAppSettings); - } catch (Exception e) { - handleEvictEvent(new MobileAppSettingsEvictEvent(tenantId)); - checkConstraintViolation(e, Map.of( - "mobile_app_settings_tenant_id_unq_key", "Mobile application for specified tenant already exists!" - )); - throw e; - } - } - - @Override - public MobileAppSettings getMobileAppSettings(TenantId tenantId) { - log.trace("Executing getMobileAppSettings for tenant [{}] ", tenantId); - MobileAppSettings mobileAppSettings = cache.getAndPutInTransaction(tenantId, - () -> mobileAppSettingsDao.findByTenantId(tenantId), true); - return constructMobileAppSettings(mobileAppSettings); - } - - @Override - public void deleteByTenantId(TenantId tenantId) { - log.trace("Executing deleteByTenantId, tenantId [{}]", tenantId); - validateId(tenantId, id -> INCORRECT_TENANT_ID + id); - mobileAppSettingsDao.removeByTenantId(tenantId); - } - - @TransactionalEventListener(classes = MobileAppSettingsEvictEvent.class) - @Override - public void handleEvictEvent(MobileAppSettingsEvictEvent event) { - cache.evict(event.getTenantId()); - } - - private MobileAppSettings constructMobileAppSettings(MobileAppSettings mobileAppSettings) { - if (mobileAppSettings == null) { - mobileAppSettings = new MobileAppSettings(); - mobileAppSettings.setUseDefaultApp(true); - - AndroidConfig androidConfig = AndroidConfig.builder() - .enabled(true) - .build(); - IosConfig iosConfig = IosConfig.builder() - .enabled(true) - .build(); - QRCodeConfig qrCodeConfig = QRCodeConfig.builder() - .showOnHomePage(true) - .qrCodeLabelEnabled(true) - .qrCodeLabel(DEFAULT_QR_CODE_LABEL) - .badgeEnabled(true) - .badgePosition(BadgePosition.RIGHT) - .badgeEnabled(true) - .build(); - - mobileAppSettings.setQrCodeConfig(qrCodeConfig); - mobileAppSettings.setAndroidConfig(androidConfig); - mobileAppSettings.setIosConfig(iosConfig); - } - if (mobileAppSettings.isUseDefaultApp()) { - mobileAppSettings.setDefaultGooglePlayLink(googlePlayLink); - mobileAppSettings.setDefaultAppStoreLink(appStoreLink); - } - return mobileAppSettings; - } - -} diff --git a/dao/src/main/java/org/thingsboard/server/dao/mobile/MobileAppBundleDao.java b/dao/src/main/java/org/thingsboard/server/dao/mobile/MobileAppBundleDao.java new file mode 100644 index 0000000000..b376a0096f --- /dev/null +++ b/dao/src/main/java/org/thingsboard/server/dao/mobile/MobileAppBundleDao.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.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.MobileAppBundleOauth2Client; +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.Dao; + +import java.util.List; + +public interface MobileAppBundleDao extends Dao { + + PageData findByTenantId(TenantId tenantId, PageLink pageLink); + + List findOauth2ClientsByMobileAppBundleId(TenantId tenantId, MobileAppBundleId mobileAppBundleId); + + void addOauth2Client(MobileAppBundleOauth2Client mobileAppBundleOauth2Client); + + void removeOauth2Client(MobileAppBundleOauth2Client mobileAppBundleOauth2Client); + + MobileAppBundle findByPkgNameAndPlatform(TenantId tenantId, String pkgName, PlatformType platform); + + void deleteByTenantId(TenantId tenantId); + +} diff --git a/dao/src/main/java/org/thingsboard/server/dao/mobile/MobileAppBundleServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/mobile/MobileAppBundleServiceImpl.java new file mode 100644 index 0000000000..b67ad2cac3 --- /dev/null +++ b/dao/src/main/java/org/thingsboard/server/dao/mobile/MobileAppBundleServiceImpl.java @@ -0,0 +1,170 @@ +/** + * 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.mobile; + +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.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.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; +import org.thingsboard.server.common.data.oauth2.OAuth2ClientInfo; +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.AbstractEntityService; +import org.thingsboard.server.dao.eventsourcing.DeleteEntityEvent; +import org.thingsboard.server.dao.eventsourcing.SaveEntityEvent; +import org.thingsboard.server.dao.oauth2.OAuth2ClientDao; + +import java.util.Comparator; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Set; +import java.util.stream.Collectors; + +@Slf4j +@Service +public class MobileAppBundleServiceImpl extends AbstractEntityService implements MobileAppBundleService { + + @Autowired + private OAuth2ClientDao oauth2ClientDao; + @Autowired + private MobileAppBundleDao mobileAppBundleDao; + @Autowired + private MobileAppService mobileAppService; + + @Override + public MobileAppBundle saveMobileAppBundle(TenantId tenantId, MobileAppBundle mobileApp) { + log.trace("Executing saveMobileApp [{}]", mobileApp); + try { + MobileAppBundle savedMobileApp = mobileAppBundleDao.save(tenantId, mobileApp); + eventPublisher.publishEvent(SaveEntityEvent.builder().tenantId(tenantId).entity(savedMobileApp).build()); + return savedMobileApp; + } catch (Exception e) { + checkConstraintViolation(e, + Map.of("android_app_id_unq_key", "Android mobile app already exists in another bundle!", + "ios_app_id_unq_key", "IOS mobile app already exists in another bundle!")); + throw e; + } + } + + @Override + public void deleteMobileAppBundleById(TenantId tenantId, MobileAppBundleId mobileAppBundleId) { + log.trace("Executing deleteMobileAppById [{}]", mobileAppBundleId.getId()); + mobileAppBundleDao.removeById(tenantId, mobileAppBundleId.getId()); + eventPublisher.publishEvent(DeleteEntityEvent.builder().tenantId(tenantId).entityId(mobileAppBundleId).build()); + } + + @Override + public MobileAppBundle findMobileAppBundleById(TenantId tenantId, MobileAppBundleId mobileAppBundleId) { + log.trace("Executing findMobileAppBundleById [{}] [{}]", tenantId, mobileAppBundleId); + return mobileAppBundleDao.findById(tenantId, mobileAppBundleId.getId()); + } + + @Override + public PageData findMobileAppBundleInfosByTenantId(TenantId tenantId, PageLink pageLink) { + log.trace("Executing findMobileAppInfosByTenantId [{}]", tenantId); + PageData mobileBundles = mobileAppBundleDao.findByTenantId(tenantId, pageLink); + return mobileBundles.mapData(this::getMobileAppBundleInfo); + } + + @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; + } + return getMobileAppBundleInfo(mobileAppBundle); + } + + @Override + public void updateOauth2Clients(TenantId tenantId, MobileAppBundleId mobileAppBundleId, List oAuth2ClientIds) { + log.trace("Executing updateOauth2Clients, mobileAppId [{}], oAuth2ClientIds [{}]", mobileAppBundleId, oAuth2ClientIds); + Set newClientList = oAuth2ClientIds.stream() + .map(clientId -> new MobileAppBundleOauth2Client(mobileAppBundleId, clientId)) + .collect(Collectors.toSet()); + + List existingClients = mobileAppBundleDao.findOauth2ClientsByMobileAppBundleId(tenantId, mobileAppBundleId); + List toRemoveList = existingClients.stream() + .filter(client -> !newClientList.contains(client)) + .toList(); + newClientList.removeIf(existingClients::contains); + + for (MobileAppBundleOauth2Client client : toRemoveList) { + mobileAppBundleDao.removeOauth2Client(client); + } + for (MobileAppBundleOauth2Client client : newClientList) { + mobileAppBundleDao.addOauth2Client(client); + } + eventPublisher.publishEvent(SaveEntityEvent.builder().tenantId(tenantId) + .entityId(mobileAppBundleId).created(false).build()); + } + + @Override + public MobileAppBundle findMobileAppBundleByPkgNameAndPlatform(TenantId tenantId, String pkgName, PlatformType platform) { + log.trace("Executing findMobileAppBundle, tenantId [{}], pkgName [{}], platform [{}]", tenantId, pkgName, platform); + return mobileAppBundleDao.findByPkgNameAndPlatform(tenantId, pkgName, platform); + } + + @Override + public Optional> findEntity(TenantId tenantId, EntityId entityId) { + return Optional.ofNullable(findMobileAppBundleById(tenantId, new MobileAppBundleId(entityId.getId()))); + } + + @Override + @Transactional + public void deleteEntity(TenantId tenantId, EntityId id, boolean force) { + deleteMobileAppBundleById(tenantId, (MobileAppBundleId) id); + } + + @Override + public void deleteMobileAppsByTenantId(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 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); + } + + @Override + public EntityType getEntityType() { + return EntityType.MOBILE_APP_BUNDLE; + } +} diff --git a/dao/src/main/java/org/thingsboard/server/dao/mobile/MobileAppDao.java b/dao/src/main/java/org/thingsboard/server/dao/mobile/MobileAppDao.java index 8da92e1eb3..091e61327c 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/mobile/MobileAppDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/mobile/MobileAppDao.java @@ -15,26 +15,19 @@ */ package org.thingsboard.server.dao.mobile; -import org.thingsboard.server.common.data.id.MobileAppId; -import org.thingsboard.server.common.data.id.OAuth2ClientId; +import org.thingsboard.server.common.data.id.MobileAppBundleId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.mobile.MobileApp; -import org.thingsboard.server.common.data.mobile.MobileAppOauth2Client; +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.Dao; -import java.util.List; - public interface MobileAppDao extends Dao { - PageData findByTenantId(TenantId tenantId, PageLink pageLink); - - List findOauth2ClientsByMobileAppId(TenantId tenantId, MobileAppId mobileAppId); + MobileApp findByBundleIdAndPlatformType(TenantId tenantId, MobileAppBundleId mobileAppBundleId, PlatformType platformType); - void addOauth2Client(MobileAppOauth2Client mobileAppOauth2Client); - - void removeOauth2Client(MobileAppOauth2Client mobileAppOauth2Client); + PageData findByTenantId(TenantId tenantId, PageLink pageLink); void deleteByTenantId(TenantId tenantId); } diff --git a/dao/src/main/java/org/thingsboard/server/dao/mobile/MobileAppServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/mobile/MobileAppServiceImpl.java index 66431a38dc..2b96d88497 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/mobile/MobileAppServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/mobile/MobileAppServiceImpl.java @@ -19,36 +19,30 @@ 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.OAuth2ClientId; 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.MobileAppInfo; -import org.thingsboard.server.common.data.mobile.MobileAppOauth2Client; -import org.thingsboard.server.common.data.oauth2.OAuth2ClientInfo; +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.AbstractEntityService; import org.thingsboard.server.dao.eventsourcing.DeleteEntityEvent; import org.thingsboard.server.dao.eventsourcing.SaveEntityEvent; -import org.thingsboard.server.dao.oauth2.OAuth2ClientDao; -import java.util.Comparator; -import java.util.List; import java.util.Map; import java.util.Optional; -import java.util.Set; -import java.util.stream.Collectors; @Slf4j @Service public class MobileAppServiceImpl extends AbstractEntityService implements MobileAppService { - @Autowired - private OAuth2ClientDao oauth2ClientDao; @Autowired private MobileAppDao mobileAppDao; @@ -80,43 +74,9 @@ public class MobileAppServiceImpl extends AbstractEntityService implements Mobil } @Override - public PageData findMobileAppInfosByTenantId(TenantId tenantId, PageLink pageLink) { + public PageData findMobileAppsByTenantId(TenantId tenantId, PageLink pageLink) { log.trace("Executing findMobileAppInfosByTenantId [{}]", tenantId); - PageData mobiles = mobileAppDao.findByTenantId(tenantId, pageLink); - return mobiles.mapData(this::getMobileAppInfo); - } - - @Override - public MobileAppInfo findMobileAppInfoById(TenantId tenantId, MobileAppId mobileAppId) { - log.trace("Executing findMobileAppInfoById [{}] [{}]", tenantId, mobileAppId); - MobileApp mobileApp = mobileAppDao.findById(tenantId, mobileAppId.getId()); - if (mobileApp == null) { - return null; - } - return getMobileAppInfo(mobileApp); - } - - @Override - public void updateOauth2Clients(TenantId tenantId, MobileAppId mobileAppId, List oAuth2ClientIds) { - log.trace("Executing updateOauth2Clients, mobileAppId [{}], oAuth2ClientIds [{}]", mobileAppId, oAuth2ClientIds); - Set newClientList = oAuth2ClientIds.stream() - .map(clientId -> new MobileAppOauth2Client(mobileAppId, clientId)) - .collect(Collectors.toSet()); - - List existingClients = mobileAppDao.findOauth2ClientsByMobileAppId(tenantId, mobileAppId); - List toRemoveList = existingClients.stream() - .filter(client -> !newClientList.contains(client)) - .toList(); - newClientList.removeIf(existingClients::contains); - - for (MobileAppOauth2Client client : toRemoveList) { - mobileAppDao.removeOauth2Client(client); - } - for (MobileAppOauth2Client client : newClientList) { - mobileAppDao.addOauth2Client(client); - } - eventPublisher.publishEvent(SaveEntityEvent.builder().tenantId(tenantId) - .entityId(mobileAppId).created(false).build()); + return mobileAppDao.findByTenantId(tenantId, pageLink); } @Override @@ -137,16 +97,22 @@ public class MobileAppServiceImpl extends AbstractEntityService implements Mobil } @Override - public void deleteByTenantId(TenantId tenantId) { - deleteMobileAppsByTenantId(tenantId); + public AndroidQrCodeConfig findAndroidQrCodeConfig(TenantId tenantId, MobileAppBundleId mobileAppBundleId) { + 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; + } + + @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; } - private MobileAppInfo getMobileAppInfo(MobileApp mobileApp) { - List clients = oauth2ClientDao.findByMobileAppId(mobileApp.getUuidId()).stream() - .map(OAuth2ClientInfo::new) - .sorted(Comparator.comparing(OAuth2ClientInfo::getTitle)) - .collect(Collectors.toList()); - return new MobileAppInfo(mobileApp, clients); + @Override + public void deleteByTenantId(TenantId tenantId) { + deleteMobileAppsByTenantId(tenantId); } @Override diff --git a/dao/src/main/java/org/thingsboard/server/dao/mobile/MobileAppSettingsService.java b/dao/src/main/java/org/thingsboard/server/dao/mobile/QrCodeSettingService.java similarity index 59% rename from dao/src/main/java/org/thingsboard/server/dao/mobile/MobileAppSettingsService.java rename to dao/src/main/java/org/thingsboard/server/dao/mobile/QrCodeSettingService.java index 4645947382..ae46e4039b 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/mobile/MobileAppSettingsService.java +++ b/dao/src/main/java/org/thingsboard/server/dao/mobile/QrCodeSettingService.java @@ -16,13 +16,19 @@ package org.thingsboard.server.dao.mobile; import org.thingsboard.server.common.data.id.TenantId; -import org.thingsboard.server.common.data.mobile.MobileAppSettings; +import org.thingsboard.server.common.data.mobile.AndroidQrCodeConfig; +import org.thingsboard.server.common.data.mobile.IosQrCodeConfig; +import org.thingsboard.server.common.data.mobile.QrCodeSettings; -public interface MobileAppSettingsService { +public interface QrCodeSettingService { - MobileAppSettings saveMobileAppSettings(TenantId tenantId, MobileAppSettings settings); + QrCodeSettings saveQrCodeSettings(TenantId tenantId, QrCodeSettings settings); - MobileAppSettings getMobileAppSettings(TenantId tenantId); + QrCodeSettings getQrCodeSettings(TenantId tenantId); + + AndroidQrCodeConfig getAndroidQrCodeConfig(TenantId sysTenantId); + + IosQrCodeConfig getIosQrCodeConfig(TenantId sysTenantId); void deleteByTenantId(TenantId tenantId); diff --git a/dao/src/main/java/org/thingsboard/server/dao/mobile/QrCodeSettingServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/mobile/QrCodeSettingServiceImpl.java new file mode 100644 index 0000000000..bda099bcc0 --- /dev/null +++ b/dao/src/main/java/org/thingsboard/server/dao/mobile/QrCodeSettingServiceImpl.java @@ -0,0 +1,128 @@ +/** + * 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.mobile; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +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.QrCodeSettings; +import org.thingsboard.server.common.data.mobile.QRCodeConfig; +import org.thingsboard.server.dao.entity.AbstractCachedEntityService; +import org.thingsboard.server.dao.service.DataValidator; + +import java.util.Map; + +import static org.thingsboard.server.dao.service.Validator.validateId; + +@Service +@Slf4j +@RequiredArgsConstructor +public class QrCodeSettingServiceImpl extends AbstractCachedEntityService implements QrCodeSettingService { + + public static final String INCORRECT_TENANT_ID = "Incorrect tenantId "; + private static final String DEFAULT_QR_CODE_LABEL = "Scan to connect or download mobile app"; + + @Value("${mobileApp.googlePlayLink:https://play.google.com/store/apps/details?id=org.thingsboard.demo.app}") + private String googlePlayLink; + @Value("${mobileApp.appStoreLink:https://apps.apple.com/us/app/thingsboard-live/id1594355695}") + private String appStoreLink; + + private final QrCodeSettingsDao qrCodeSettingsDao; + private final MobileAppService mobileAppService; + private final DataValidator mobileAppSettingsDataValidator; + + @Override + public QrCodeSettings saveQrCodeSettings(TenantId tenantId, QrCodeSettings qrCodeSettings) { + mobileAppSettingsDataValidator.validate(qrCodeSettings, s -> tenantId); + try { + QrCodeSettings savedQrCodeSettings = qrCodeSettingsDao.save(tenantId, qrCodeSettings); + publishEvictEvent(new QrCodeSettingsEvictEvent(tenantId)); + return constructMobileAppSettings(savedQrCodeSettings); + } catch (Exception e) { + handleEvictEvent(new QrCodeSettingsEvictEvent(tenantId)); + checkConstraintViolation(e, Map.of( + "mobile_app_settings_tenant_id_unq_key", "Mobile application for specified tenant already exists!" + )); + throw e; + } + } + + @Override + public QrCodeSettings getQrCodeSettings(TenantId tenantId) { + log.trace("Executing getMobileAppSettings for tenant [{}] ", tenantId); + QrCodeSettings qrCodeSettings = cache.getAndPutInTransaction(tenantId, + () -> qrCodeSettingsDao.findByTenantId(tenantId), true); + return constructMobileAppSettings(qrCodeSettings); + } + + @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; + } + + @Override + public void deleteByTenantId(TenantId tenantId) { + log.trace("Executing deleteByTenantId, tenantId [{}]", tenantId); + validateId(tenantId, id -> INCORRECT_TENANT_ID + id); + qrCodeSettingsDao.removeByTenantId(tenantId); + } + + @TransactionalEventListener(classes = QrCodeSettingsEvictEvent.class) + @Override + public void handleEvictEvent(QrCodeSettingsEvictEvent event) { + cache.evict(event.getTenantId()); + } + + private QrCodeSettings constructMobileAppSettings(QrCodeSettings qrCodeSettings) { + if (qrCodeSettings == null) { + qrCodeSettings = new QrCodeSettings(); + qrCodeSettings.setUseDefaultApp(true); + + QRCodeConfig qrCodeConfig = QRCodeConfig.builder() + .showOnHomePage(true) + .qrCodeLabelEnabled(true) + .qrCodeLabel(DEFAULT_QR_CODE_LABEL) + .badgeEnabled(true) + .badgePosition(BadgePosition.RIGHT) + .badgeEnabled(true) + .build(); + + qrCodeSettings.setQrCodeConfig(qrCodeConfig); + qrCodeSettings.setMobileAppBundleId(qrCodeSettings.getMobileAppBundleId()); + } + if (qrCodeSettings.isUseDefaultApp()) { + qrCodeSettings.setDefaultGooglePlayLink(googlePlayLink); + qrCodeSettings.setDefaultAppStoreLink(appStoreLink); + } + return qrCodeSettings; + } + +} diff --git a/dao/src/main/java/org/thingsboard/server/dao/mobile/MobileAppSettingsCaffeineCache.java b/dao/src/main/java/org/thingsboard/server/dao/mobile/QrCodeSettingsCaffeineCache.java similarity index 76% rename from dao/src/main/java/org/thingsboard/server/dao/mobile/MobileAppSettingsCaffeineCache.java rename to dao/src/main/java/org/thingsboard/server/dao/mobile/QrCodeSettingsCaffeineCache.java index 9e15ae8572..806d7af0a9 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/mobile/MobileAppSettingsCaffeineCache.java +++ b/dao/src/main/java/org/thingsboard/server/dao/mobile/QrCodeSettingsCaffeineCache.java @@ -21,14 +21,14 @@ import org.springframework.stereotype.Service; import org.thingsboard.server.cache.CaffeineTbTransactionalCache; import org.thingsboard.server.common.data.CacheConstants; import org.thingsboard.server.common.data.id.TenantId; -import org.thingsboard.server.common.data.mobile.MobileAppSettings; +import org.thingsboard.server.common.data.mobile.QrCodeSettings; @ConditionalOnProperty(prefix = "cache", value = "type", havingValue = "caffeine", matchIfMissing = true) -@Service("MobileAppCache") -public class MobileAppSettingsCaffeineCache extends CaffeineTbTransactionalCache { +@Service("QrCodeSettingsCache") +public class QrCodeSettingsCaffeineCache extends CaffeineTbTransactionalCache { - public MobileAppSettingsCaffeineCache(CacheManager cacheManager) { - super(cacheManager, CacheConstants.MOBILE_APP_SETTINGS_CACHE); + public QrCodeSettingsCaffeineCache(CacheManager cacheManager) { + super(cacheManager, CacheConstants.QR_CODE_SETTINGS_CACHE); } } diff --git a/dao/src/main/java/org/thingsboard/server/dao/mobile/MobileAppSettingsDao.java b/dao/src/main/java/org/thingsboard/server/dao/mobile/QrCodeSettingsDao.java similarity index 80% rename from dao/src/main/java/org/thingsboard/server/dao/mobile/MobileAppSettingsDao.java rename to dao/src/main/java/org/thingsboard/server/dao/mobile/QrCodeSettingsDao.java index 094ead1a5b..8b3600ceea 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/mobile/MobileAppSettingsDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/mobile/QrCodeSettingsDao.java @@ -16,13 +16,13 @@ package org.thingsboard.server.dao.mobile; import org.thingsboard.server.common.data.id.TenantId; -import org.thingsboard.server.common.data.mobile.MobileAppSettings; +import org.thingsboard.server.common.data.mobile.QrCodeSettings; import org.thingsboard.server.dao.Dao; -public interface MobileAppSettingsDao extends Dao { +public interface QrCodeSettingsDao extends Dao { - MobileAppSettings findByTenantId(TenantId tenantId); + QrCodeSettings findByTenantId(TenantId tenantId); void removeByTenantId(TenantId tenantId); } diff --git a/dao/src/main/java/org/thingsboard/server/dao/mobile/MobileAppSettingsEvictEvent.java b/dao/src/main/java/org/thingsboard/server/dao/mobile/QrCodeSettingsEvictEvent.java similarity index 94% rename from dao/src/main/java/org/thingsboard/server/dao/mobile/MobileAppSettingsEvictEvent.java rename to dao/src/main/java/org/thingsboard/server/dao/mobile/QrCodeSettingsEvictEvent.java index 459e61ef6d..b9b4f84576 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/mobile/MobileAppSettingsEvictEvent.java +++ b/dao/src/main/java/org/thingsboard/server/dao/mobile/QrCodeSettingsEvictEvent.java @@ -19,6 +19,6 @@ import lombok.Data; import org.thingsboard.server.common.data.id.TenantId; @Data -public class MobileAppSettingsEvictEvent { +public class QrCodeSettingsEvictEvent { private final TenantId tenantId; } diff --git a/dao/src/main/java/org/thingsboard/server/dao/mobile/MobileAppSettingsRedisCache.java b/dao/src/main/java/org/thingsboard/server/dao/mobile/QrCodeSettingsRedisCache.java similarity index 71% rename from dao/src/main/java/org/thingsboard/server/dao/mobile/MobileAppSettingsRedisCache.java rename to dao/src/main/java/org/thingsboard/server/dao/mobile/QrCodeSettingsRedisCache.java index aac802c1ec..063c79c2e7 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/mobile/MobileAppSettingsRedisCache.java +++ b/dao/src/main/java/org/thingsboard/server/dao/mobile/QrCodeSettingsRedisCache.java @@ -24,13 +24,13 @@ import org.thingsboard.server.cache.TBRedisCacheConfiguration; import org.thingsboard.server.cache.TbJsonRedisSerializer; import org.thingsboard.server.common.data.CacheConstants; import org.thingsboard.server.common.data.id.TenantId; -import org.thingsboard.server.common.data.mobile.MobileAppSettings; +import org.thingsboard.server.common.data.mobile.QrCodeSettings; @ConditionalOnProperty(prefix = "cache", value = "type", havingValue = "redis") -@Service("MobileAppCache") -public class MobileAppSettingsRedisCache extends RedisTbTransactionalCache { +@Service("QrCodeSettingsCache") +public class QrCodeSettingsRedisCache extends RedisTbTransactionalCache { - public MobileAppSettingsRedisCache(TBRedisCacheConfiguration configuration, CacheSpecsMap cacheSpecsMap, RedisConnectionFactory connectionFactory) { - super(CacheConstants.MOBILE_APP_SETTINGS_CACHE, cacheSpecsMap, connectionFactory, configuration, new TbJsonRedisSerializer<>(MobileAppSettings.class)); + public QrCodeSettingsRedisCache(TBRedisCacheConfiguration configuration, CacheSpecsMap cacheSpecsMap, RedisConnectionFactory connectionFactory) { + super(CacheConstants.QR_CODE_SETTINGS_CACHE, cacheSpecsMap, connectionFactory, configuration, new TbJsonRedisSerializer<>(QrCodeSettings.class)); } } diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java b/dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java index b920160cc0..20fdf88ebf 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java @@ -448,11 +448,25 @@ public class ModelConstants { public static final String MOBILE_APP_TABLE_NAME = "mobile_app"; public static final String MOBILE_APP_PKG_NAME_PROPERTY = "pkg_name"; public static final String MOBILE_APP_APP_SECRET_PROPERTY = "app_secret"; - public static final String MOBILE_APP_OAUTH2_ENABLED_PROPERTY = "oauth2_enabled"; + public static final String MOBILE_APP_PLATFORM_TYPE_PROPERTY = "platform_type"; + public static final String MOBILE_APP_STATUS_PROPERTY = "status"; + public static final String MOBILE_APP_VERSION_INFO_PROPERTY = "version_info"; + public static final String MOBILE_APP_QR_CODE_CONFIG_PROPERTY = "qr_code_config"; - public static final String MOBILE_APP_OAUTH2_CLIENT_TABLE_NAME = "mobile_app_oauth2_client"; - public static final String MOBILE_APP_OAUTH2_CLIENT_CLIENT_ID_PROPERTY = "oauth2_client_id"; - public static final String MOBILE_APP_OAUTH2_CLIENT_MOBILE_APP_ID_PROPERTY = "mobile_app_id"; + /** + * Mobile application bundle constants. + */ + public static final String MOBILE_APP_BUNDLE_TABLE_NAME = "mobile_app_bundle"; + public static final String MOBILE_APP_BUNDLE_TITLE_PROPERTY = "title"; + public static final String MOBILE_APP_BUNDLE_DESCRIPTION_PROPERTY = "description"; + public static final String MOBILE_APP_BUNDLE_ANDROID_APP_ID_PROPERTY = "android_app_id"; + public static final String MOBILE_APP_BUNDLE_IOS_APP_ID_PROPERTY = "ios_app_id"; + 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_CLIENT_ID_PROPERTY = "oauth2_client_id"; + public static final String MOBILE_APP_BUNDLE_OAUTH2_CLIENT_MOBILE_APP_BUNDLE_ID_PROPERTY = "mobile_app_bundle_id"; /** @@ -684,11 +698,10 @@ public class ModelConstants { /** * Mobile application settings constants. */ - public static final String MOBILE_APP_SETTINGS_TABLE_NAME = "mobile_app_settings"; - public static final String MOBILE_APP_SETTINGS_USE_DEFAULT_APP_PROPERTY = "use_default_app"; - public static final String MOBILE_APP_SETTINGS_ANDROID_CONFIG_PROPERTY = "android_config"; - public static final String MOBILE_APP_SETTINGS_IOS_CONFIG_PROPERTY = "ios_config"; - public static final String MOBILE_APP_SETTINGS_QR_CODE_CONFIG_PROPERTY = "qr_code_config"; + public static final String QR_CODE_SETTINGS_TABLE_NAME = "qr_code_settings"; + public static final String QR_CODE_SETTINGS_USE_DEFAULT_APP_PROPERTY = "use_default_app"; + public static final String QR_CODE_SETTINGS_BUNDLE_ID_PROPERTY = "mobile_app_bundle_id"; + public static final String QR_CODE_SETTINGS_CONFIG_PROPERTY = "qr_code_config"; protected static final String[] NONE_AGGREGATION_COLUMNS = new String[]{LONG_VALUE_COLUMN, DOUBLE_VALUE_COLUMN, BOOLEAN_VALUE_COLUMN, STRING_VALUE_COLUMN, JSON_VALUE_COLUMN, KEY_COLUMN, TS_COLUMN}; diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/MobileAppBundleEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/MobileAppBundleEntity.java new file mode 100644 index 0000000000..030d995203 --- /dev/null +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sql/MobileAppBundleEntity.java @@ -0,0 +1,107 @@ +/** + * 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.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; + +@Data +@EqualsAndHashCode(callSuper = true) +@Entity +@Table(name = ModelConstants.MOBILE_APP_BUNDLE_TABLE_NAME) +public class MobileAppBundleEntity extends BaseSqlEntity { + + @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 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(); + } + + @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; + } +} diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/MobileAppOauth2ClientEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/MobileAppBundleOauth2ClientEntity.java similarity index 57% rename from dao/src/main/java/org/thingsboard/server/dao/model/sql/MobileAppOauth2ClientEntity.java rename to dao/src/main/java/org/thingsboard/server/dao/model/sql/MobileAppBundleOauth2ClientEntity.java index b2c7a55855..58040d27d9 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/sql/MobileAppOauth2ClientEntity.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sql/MobileAppBundleOauth2ClientEntity.java @@ -21,44 +21,45 @@ import jakarta.persistence.Id; import jakarta.persistence.IdClass; import jakarta.persistence.Table; import lombok.Data; -import org.thingsboard.server.common.data.id.MobileAppId; +import org.thingsboard.server.common.data.id.MobileAppBundleId; import org.thingsboard.server.common.data.id.OAuth2ClientId; -import org.thingsboard.server.common.data.mobile.MobileAppOauth2Client; +import org.thingsboard.server.common.data.mobile.MobileAppBundleOauth2Client; import org.thingsboard.server.dao.model.ToData; import java.util.UUID; -import static org.thingsboard.server.dao.model.ModelConstants.MOBILE_APP_OAUTH2_CLIENT_MOBILE_APP_ID_PROPERTY; -import static org.thingsboard.server.dao.model.ModelConstants.MOBILE_APP_OAUTH2_CLIENT_CLIENT_ID_PROPERTY; -import static org.thingsboard.server.dao.model.ModelConstants.MOBILE_APP_OAUTH2_CLIENT_TABLE_NAME; +import static org.thingsboard.server.dao.model.ModelConstants.MOBILE_APP_BUNDLE_OAUTH2_CLIENT_CLIENT_ID_PROPERTY; +import static org.thingsboard.server.dao.model.ModelConstants.MOBILE_APP_BUNDLE_OAUTH2_CLIENT_MOBILE_APP_BUNDLE_ID_PROPERTY; +import static org.thingsboard.server.dao.model.ModelConstants.MOBILE_APP_BUNDLE_OAUTH2_CLIENT_TABLE_NAME; + @Data @Entity -@Table(name = MOBILE_APP_OAUTH2_CLIENT_TABLE_NAME) +@Table(name = MOBILE_APP_BUNDLE_OAUTH2_CLIENT_TABLE_NAME) @IdClass(MobileAppOauth2ClientCompositeKey.class) -public final class MobileAppOauth2ClientEntity implements ToData { +public final class MobileAppBundleOauth2ClientEntity implements ToData { @Id - @Column(name = MOBILE_APP_OAUTH2_CLIENT_MOBILE_APP_ID_PROPERTY, columnDefinition = "uuid") - private UUID mobileAppId; + @Column(name = MOBILE_APP_BUNDLE_OAUTH2_CLIENT_MOBILE_APP_BUNDLE_ID_PROPERTY, columnDefinition = "uuid") + private UUID mobileAppBundleId; @Id - @Column(name = MOBILE_APP_OAUTH2_CLIENT_CLIENT_ID_PROPERTY, columnDefinition = "uuid") + @Column(name = MOBILE_APP_BUNDLE_OAUTH2_CLIENT_CLIENT_ID_PROPERTY, columnDefinition = "uuid") private UUID oauth2ClientId; - public MobileAppOauth2ClientEntity() { + public MobileAppBundleOauth2ClientEntity() { super(); } - public MobileAppOauth2ClientEntity(MobileAppOauth2Client domainOauth2Provider) { - mobileAppId = domainOauth2Provider.getMobileAppId().getId(); + public MobileAppBundleOauth2ClientEntity(MobileAppBundleOauth2Client domainOauth2Provider) { + mobileAppBundleId = domainOauth2Provider.getMobileAppBundleId().getId(); oauth2ClientId = domainOauth2Provider.getOAuth2ClientId().getId(); } @Override - public MobileAppOauth2Client toData() { - MobileAppOauth2Client result = new MobileAppOauth2Client(); - result.setMobileAppId(new MobileAppId(mobileAppId)); + public MobileAppBundleOauth2Client toData() { + MobileAppBundleOauth2Client result = new MobileAppBundleOauth2Client(); + result.setMobileAppBundleId(new MobileAppBundleId(mobileAppBundleId)); result.setOAuth2ClientId(new OAuth2ClientId(oauth2ClientId)); return result; } diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/MobileAppEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/MobileAppEntity.java index 0313864d09..69d5fe4e6e 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/sql/MobileAppEntity.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sql/MobileAppEntity.java @@ -15,16 +15,24 @@ */ 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.EnumType; +import jakarta.persistence.Enumerated; import jakarta.persistence.Table; import lombok.Data; import lombok.EqualsAndHashCode; 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.QrCodeConfig; +import org.thingsboard.server.common.data.oauth2.PlatformType; 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; @@ -45,8 +53,21 @@ public class MobileAppEntity extends BaseSqlEntity { @Column(name = ModelConstants.MOBILE_APP_APP_SECRET_PROPERTY) private String appSecret; - @Column(name = ModelConstants.MOBILE_APP_OAUTH2_ENABLED_PROPERTY) - private Boolean oauth2Enabled; + @Enumerated(EnumType.STRING) + @Column(name = ModelConstants.MOBILE_APP_PLATFORM_TYPE_PROPERTY) + private PlatformType platformType; + + @Enumerated(EnumType.STRING) + @Column(name = ModelConstants.MOBILE_APP_STATUS_PROPERTY) + private MobileAppStatus status; + + @Convert(converter = JsonConverter.class) + @Column(name = ModelConstants.MOBILE_APP_VERSION_INFO_PROPERTY) + private JsonNode versionInfo; + + @Convert(converter = JsonConverter.class) + @Column(name = ModelConstants.MOBILE_APP_QR_CODE_CONFIG_PROPERTY) + private JsonNode qrCodeConfig; public MobileAppEntity() { super(); @@ -59,7 +80,10 @@ public class MobileAppEntity extends BaseSqlEntity { } this.pkgName = mobile.getPkgName(); this.appSecret = mobile.getAppSecret(); - this.oauth2Enabled = mobile.isOauth2Enabled(); + this.platformType = mobile.getPlatformType(); + this.status = mobile.getStatus(); + this.versionInfo = mobile.getVersionInfo(); + this.qrCodeConfig = toJson(mobile.getQrCodeConfig()); } @Override @@ -72,7 +96,10 @@ public class MobileAppEntity extends BaseSqlEntity { mobile.setCreatedTime(createdTime); mobile.setPkgName(pkgName); mobile.setAppSecret(appSecret); - mobile.setOauth2Enabled(oauth2Enabled); + mobile.setPlatformType(platformType); + mobile.setStatus(status); + mobile.setVersionInfo(versionInfo); + mobile.setQrCodeConfig(fromJson(qrCodeConfig, QrCodeConfig.class)); return mobile; } } diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/MobileAppOauth2ClientCompositeKey.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/MobileAppOauth2ClientCompositeKey.java index b372751549..843735f9eb 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/sql/MobileAppOauth2ClientCompositeKey.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sql/MobileAppOauth2ClientCompositeKey.java @@ -31,7 +31,7 @@ public class MobileAppOauth2ClientCompositeKey implements Serializable { @Transient private static final long serialVersionUID = -245388185894468455L; - private UUID mobileAppId; + private UUID mobileAppBundleId; private UUID oauth2ClientId; } diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/MobileAppSettingsEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/MobileAppSettingsEntity.java deleted file mode 100644 index cd38edd9cb..0000000000 --- a/dao/src/main/java/org/thingsboard/server/dao/model/sql/MobileAppSettingsEntity.java +++ /dev/null @@ -1,85 +0,0 @@ -/** - * 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.Entity; -import jakarta.persistence.Table; -import lombok.Data; -import lombok.EqualsAndHashCode; -import lombok.NoArgsConstructor; -import org.thingsboard.server.common.data.id.MobileAppSettingsId; -import org.thingsboard.server.common.data.id.TenantId; -import org.thingsboard.server.common.data.mobile.AndroidConfig; -import org.thingsboard.server.common.data.mobile.IosConfig; -import org.thingsboard.server.common.data.mobile.MobileAppSettings; -import org.thingsboard.server.common.data.mobile.QRCodeConfig; -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; - -@Data -@EqualsAndHashCode(callSuper = true) -@NoArgsConstructor -@Entity -@Table(name = ModelConstants.MOBILE_APP_SETTINGS_TABLE_NAME) -public class MobileAppSettingsEntity extends BaseSqlEntity { - - @Column(name = ModelConstants.TENANT_ID_COLUMN, columnDefinition = "uuid") - protected UUID tenantId; - - @Column(name = ModelConstants.MOBILE_APP_SETTINGS_USE_DEFAULT_APP_PROPERTY) - private boolean useDefaultApp; - - @Convert(converter = JsonConverter.class) - @Column(name = ModelConstants.MOBILE_APP_SETTINGS_ANDROID_CONFIG_PROPERTY) - private JsonNode androidConfig; - - @Convert(converter = JsonConverter.class) - @Column(name = ModelConstants.MOBILE_APP_SETTINGS_IOS_CONFIG_PROPERTY) - private JsonNode iosConfig; - - @Convert(converter = JsonConverter.class) - @Column(name = ModelConstants.MOBILE_APP_SETTINGS_QR_CODE_CONFIG_PROPERTY) - private JsonNode qrCodeConfig; - - public MobileAppSettingsEntity(MobileAppSettings mobileAppSettings) { - this.setId(mobileAppSettings.getUuidId()); - this.setCreatedTime(mobileAppSettings.getCreatedTime()); - this.tenantId = mobileAppSettings.getTenantId().getId(); - this.useDefaultApp = mobileAppSettings.isUseDefaultApp(); - this.androidConfig = toJson(mobileAppSettings.getAndroidConfig()); - this.iosConfig = toJson(mobileAppSettings.getIosConfig()); - this.qrCodeConfig = toJson(mobileAppSettings.getQrCodeConfig()); - } - - @Override - public MobileAppSettings toData() { - MobileAppSettings mobileAppSettings = new MobileAppSettings(new MobileAppSettingsId(getUuid())); - mobileAppSettings.setCreatedTime(createdTime); - mobileAppSettings.setTenantId(TenantId.fromUUID(tenantId)); - mobileAppSettings.setUseDefaultApp(useDefaultApp); - mobileAppSettings.setAndroidConfig(fromJson(androidConfig, AndroidConfig.class)); - mobileAppSettings.setIosConfig(fromJson(iosConfig, IosConfig.class)); - mobileAppSettings.setQrCodeConfig(fromJson(qrCodeConfig, QRCodeConfig.class)); - return mobileAppSettings; - } - -} diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/QrCodeSettingsEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/QrCodeSettingsEntity.java new file mode 100644 index 0000000000..55c301cbd0 --- /dev/null +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sql/QrCodeSettingsEntity.java @@ -0,0 +1,81 @@ +/** + * 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.Entity; +import jakarta.persistence.Table; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import org.thingsboard.server.common.data.id.MobileAppBundleId; +import org.thingsboard.server.common.data.id.QrCodeSettingsId; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.mobile.QrCodeSettings; +import org.thingsboard.server.common.data.mobile.QRCodeConfig; +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; + +@Data +@EqualsAndHashCode(callSuper = true) +@NoArgsConstructor +@Entity +@Table(name = ModelConstants.QR_CODE_SETTINGS_TABLE_NAME) +public class QrCodeSettingsEntity extends BaseSqlEntity { + + @Column(name = ModelConstants.TENANT_ID_COLUMN, columnDefinition = "uuid") + protected UUID tenantId; + + @Column(name = ModelConstants.QR_CODE_SETTINGS_USE_DEFAULT_APP_PROPERTY) + private boolean useDefaultApp; + + @Column(name = ModelConstants.QR_CODE_SETTINGS_BUNDLE_ID_PROPERTY) + private UUID mobileAppBundleId; + + @Convert(converter = JsonConverter.class) + @Column(name = ModelConstants.QR_CODE_SETTINGS_CONFIG_PROPERTY) + private JsonNode qrCodeConfig; + + public QrCodeSettingsEntity(QrCodeSettings qrCodeSettings) { + this.setId(qrCodeSettings.getUuidId()); + this.setCreatedTime(qrCodeSettings.getCreatedTime()); + this.tenantId = qrCodeSettings.getTenantId().getId(); + this.useDefaultApp = qrCodeSettings.isUseDefaultApp(); + if (qrCodeSettings.getMobileAppBundleId() != null) { + this.mobileAppBundleId = qrCodeSettings.getMobileAppBundleId().getId(); + } + this.qrCodeConfig = toJson(qrCodeSettings.getQrCodeConfig()); + } + + @Override + public QrCodeSettings toData() { + QrCodeSettings qrCodeSettings = new QrCodeSettings(new QrCodeSettingsId(getUuid())); + qrCodeSettings.setCreatedTime(createdTime); + qrCodeSettings.setTenantId(TenantId.fromUUID(tenantId)); + qrCodeSettings.setUseDefaultApp(useDefaultApp); + if (mobileAppBundleId != null) { + qrCodeSettings.setMobileAppBundleId(new MobileAppBundleId(mobileAppBundleId)); + } + qrCodeSettings.setQrCodeConfig(fromJson(qrCodeConfig, QRCodeConfig.class)); + return qrCodeSettings; + } + +} diff --git a/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ClientDao.java b/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ClientDao.java index cf40a331fb..01d56feb20 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ClientDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ClientDao.java @@ -36,7 +36,7 @@ public interface OAuth2ClientDao extends Dao { List findByDomainId(UUID domainId); - List findByMobileAppId(UUID mobileAppId); + List findByMobileAppBundleId(UUID mobileAppBundleId); String findAppSecret(UUID id, String pkgName); diff --git a/dao/src/main/java/org/thingsboard/server/dao/service/validator/MobileAppSettingsDataValidator.java b/dao/src/main/java/org/thingsboard/server/dao/service/validator/MobileAppSettingsDataValidator.java deleted file mode 100644 index c867b08cc4..0000000000 --- a/dao/src/main/java/org/thingsboard/server/dao/service/validator/MobileAppSettingsDataValidator.java +++ /dev/null @@ -1,51 +0,0 @@ -/** - * 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.service.validator; - -import lombok.AllArgsConstructor; -import org.springframework.stereotype.Component; -import org.thingsboard.server.common.data.id.TenantId; -import org.thingsboard.server.common.data.mobile.AndroidConfig; -import org.thingsboard.server.common.data.mobile.IosConfig; -import org.thingsboard.server.common.data.mobile.MobileAppSettings; -import org.thingsboard.server.common.data.mobile.QRCodeConfig; -import org.thingsboard.server.dao.exception.DataValidationException; -import org.thingsboard.server.dao.service.DataValidator; - -@Component -@AllArgsConstructor -public class MobileAppSettingsDataValidator extends DataValidator { - - @Override - protected void validateDataImpl(TenantId tenantId, MobileAppSettings mobileAppSettings) { - AndroidConfig androidConfig = mobileAppSettings.getAndroidConfig(); - IosConfig iosConfig = mobileAppSettings.getIosConfig(); - QRCodeConfig qrCodeConfig = mobileAppSettings.getQrCodeConfig(); - if (!mobileAppSettings.isUseDefaultApp() && (androidConfig == null || iosConfig == null)) { - throw new DataValidationException("Android/ios settings are required to use custom application!"); - } - if (qrCodeConfig == null) { - throw new DataValidationException("Qr code configuration is required!"); - } - if (androidConfig != null && androidConfig.isEnabled() && !mobileAppSettings.isUseDefaultApp() && - (androidConfig.getAppPackage() == null || androidConfig.getSha256CertFingerprints() == null)) { - throw new DataValidationException("Application package and sha256 cert fingerprints are required for custom android application!"); - } - if (iosConfig != null && iosConfig.isEnabled() && !mobileAppSettings.isUseDefaultApp() && iosConfig.getAppId() == null) { - throw new DataValidationException("Application id is required for custom ios application!"); - } - } -} diff --git a/dao/src/main/java/org/thingsboard/server/dao/service/validator/QrCodeSettingsDataValidator.java b/dao/src/main/java/org/thingsboard/server/dao/service/validator/QrCodeSettingsDataValidator.java new file mode 100644 index 0000000000..073b5eb53e --- /dev/null +++ b/dao/src/main/java/org/thingsboard/server/dao/service/validator/QrCodeSettingsDataValidator.java @@ -0,0 +1,42 @@ +/** + * 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.service.validator; + +import lombok.AllArgsConstructor; +import org.springframework.stereotype.Component; +import org.thingsboard.server.common.data.id.MobileAppBundleId; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.mobile.QRCodeConfig; +import org.thingsboard.server.common.data.mobile.QrCodeSettings; +import org.thingsboard.server.dao.exception.DataValidationException; +import org.thingsboard.server.dao.service.DataValidator; + +@Component +@AllArgsConstructor +public class QrCodeSettingsDataValidator extends DataValidator { + + @Override + protected void validateDataImpl(TenantId tenantId, QrCodeSettings qrCodeSettings) { + MobileAppBundleId mobileAppBundleId = qrCodeSettings.getMobileAppBundleId(); + QRCodeConfig qrCodeConfig = qrCodeSettings.getQrCodeConfig(); + if (!qrCodeSettings.isUseDefaultApp() && (mobileAppBundleId == null)) { + throw new DataValidationException("Mobile app bundle is required to use custom application!"); + } + if (qrCodeConfig == null) { + throw new DataValidationException("Qr code configuration is required!"); + } + } +} diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/mobile/JpaMobileAppBundleDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/mobile/JpaMobileAppBundleDao.java new file mode 100644 index 0000000000..01ac158863 --- /dev/null +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/mobile/JpaMobileAppBundleDao.java @@ -0,0 +1,95 @@ +/** + * 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.sql.mobile; + +import lombok.RequiredArgsConstructor; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Component; +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.MobileAppBundleOauth2Client; +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.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.sql.JpaAbstractDao; +import org.thingsboard.server.dao.util.SqlDao; + +import java.util.List; +import java.util.UUID; + +@Component +@RequiredArgsConstructor +@SqlDao +public class JpaMobileAppBundleDao extends JpaAbstractDao implements MobileAppBundleDao { + + private final MobileAppBundleRepository mobileAppBundleRepository; + private final MobileAppBundleOauth2ClientRepository mobileOauth2ProviderRepository; + + @Override + protected Class getEntityClass() { + return MobileAppBundleEntity.class; + } + + @Override + protected JpaRepository getRepository() { + return mobileAppBundleRepository; + } + + @Override + public PageData findByTenantId(TenantId tenantId, PageLink pageLink) { + return DaoUtil.toPageData(mobileAppBundleRepository.findByTenantId(tenantId.getId(), pageLink.getTextSearch(), DaoUtil.toPageable(pageLink))); + } + + @Override + public List findOauth2ClientsByMobileAppBundleId(TenantId tenantId, MobileAppBundleId mobileAppBundleId) { + return DaoUtil.convertDataList(mobileOauth2ProviderRepository.findAllByMobileAppBundleId(mobileAppBundleId.getId())); + } + + @Override + public void addOauth2Client(MobileAppBundleOauth2Client mobileAppBundleOauth2Client) { + mobileOauth2ProviderRepository.save(new MobileAppBundleOauth2ClientEntity(mobileAppBundleOauth2Client)); + } + + @Override + public void removeOauth2Client(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())); + } + + @Override + public void deleteByTenantId(TenantId tenantId) { + mobileAppBundleRepository.deleteByTenantId(tenantId.getId()); + } + + @Override + public EntityType getEntityType() { + return EntityType.MOBILE_APP_BUNDLE; + } + +} + diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/mobile/JpaMobileAppDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/mobile/JpaMobileAppDao.java index 0453481d08..61b34955e9 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/mobile/JpaMobileAppDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/mobile/JpaMobileAppDao.java @@ -19,21 +19,18 @@ import lombok.RequiredArgsConstructor; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Component; import org.thingsboard.server.common.data.EntityType; -import org.thingsboard.server.common.data.id.MobileAppId; +import org.thingsboard.server.common.data.id.MobileAppBundleId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.mobile.MobileApp; -import org.thingsboard.server.common.data.mobile.MobileAppOauth2Client; +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.DaoUtil; import org.thingsboard.server.dao.mobile.MobileAppDao; import org.thingsboard.server.dao.model.sql.MobileAppEntity; -import org.thingsboard.server.dao.model.sql.MobileAppOauth2ClientCompositeKey; -import org.thingsboard.server.dao.model.sql.MobileAppOauth2ClientEntity; import org.thingsboard.server.dao.sql.JpaAbstractDao; import org.thingsboard.server.dao.util.SqlDao; -import java.util.List; import java.util.UUID; @Component @@ -42,7 +39,6 @@ import java.util.UUID; public class JpaMobileAppDao extends JpaAbstractDao implements MobileAppDao { private final MobileAppRepository mobileAppRepository; - private final MobileAppOauth2ClientRepository mobileOauth2ProviderRepository; @Override protected Class getEntityClass() { @@ -55,24 +51,20 @@ public class JpaMobileAppDao extends JpaAbstractDao } @Override - public PageData findByTenantId(TenantId tenantId, PageLink pageLink) { - return DaoUtil.toPageData(mobileAppRepository.findByTenantId(tenantId.getId(), pageLink.getTextSearch(), DaoUtil.toPageable(pageLink))); - } - - @Override - public List findOauth2ClientsByMobileAppId(TenantId tenantId, MobileAppId mobileAppId) { - return DaoUtil.convertDataList(mobileOauth2ProviderRepository.findAllByMobileAppId(mobileAppId.getId())); + 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; + } } @Override - public void addOauth2Client(MobileAppOauth2Client mobileAppOauth2Client) { - mobileOauth2ProviderRepository.save(new MobileAppOauth2ClientEntity(mobileAppOauth2Client)); - } - - @Override - public void removeOauth2Client(MobileAppOauth2Client mobileAppOauth2Client) { - mobileOauth2ProviderRepository.deleteById(new MobileAppOauth2ClientCompositeKey(mobileAppOauth2Client.getMobileAppId().getId(), - mobileAppOauth2Client.getOAuth2ClientId().getId())); + public PageData findByTenantId(TenantId tenantId, PageLink pageLink) { + return DaoUtil.toPageData(mobileAppRepository.findByTenantId(tenantId.getId(), pageLink.getTextSearch(), DaoUtil.toPageable(pageLink))); } @Override diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/mobile/JpaMobileAppSettingsDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/mobile/JpaQrCodeSettingsDao.java similarity index 59% rename from dao/src/main/java/org/thingsboard/server/dao/sql/mobile/JpaMobileAppSettingsDao.java rename to dao/src/main/java/org/thingsboard/server/dao/sql/mobile/JpaQrCodeSettingsDao.java index e4286bb949..b415af445c 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/mobile/JpaMobileAppSettingsDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/mobile/JpaQrCodeSettingsDao.java @@ -20,10 +20,10 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Component; import org.thingsboard.server.common.data.id.TenantId; -import org.thingsboard.server.common.data.mobile.MobileAppSettings; +import org.thingsboard.server.common.data.mobile.QrCodeSettings; import org.thingsboard.server.dao.DaoUtil; -import org.thingsboard.server.dao.mobile.MobileAppSettingsDao; -import org.thingsboard.server.dao.model.sql.MobileAppSettingsEntity; +import org.thingsboard.server.dao.mobile.QrCodeSettingsDao; +import org.thingsboard.server.dao.model.sql.QrCodeSettingsEntity; import org.thingsboard.server.dao.sql.JpaAbstractDao; import org.thingsboard.server.dao.util.SqlDao; @@ -33,29 +33,29 @@ import java.util.UUID; @Component @Slf4j @SqlDao -public class JpaMobileAppSettingsDao extends JpaAbstractDao implements MobileAppSettingsDao { +public class JpaQrCodeSettingsDao extends JpaAbstractDao implements QrCodeSettingsDao { @Autowired - private MobileAppSettingsRepository mobileAppSettingsRepository; + private QrCodeSettingsRepository qrCodeSettingsRepository; @Override - public MobileAppSettings findByTenantId(TenantId tenantId) { - return DaoUtil.getData(mobileAppSettingsRepository.findByTenantId(tenantId.getId())); + public QrCodeSettings findByTenantId(TenantId tenantId) { + return DaoUtil.getData(qrCodeSettingsRepository.findByTenantId(tenantId.getId())); } @Override public void removeByTenantId(TenantId tenantId) { - mobileAppSettingsRepository.deleteByTenantId(tenantId.getId()); + qrCodeSettingsRepository.deleteByTenantId(tenantId.getId()); } @Override - protected Class getEntityClass() { - return MobileAppSettingsEntity.class; + protected Class getEntityClass() { + return QrCodeSettingsEntity.class; } @Override - protected JpaRepository getRepository() { - return mobileAppSettingsRepository; + protected JpaRepository getRepository() { + return qrCodeSettingsRepository; } } diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/mobile/MobileAppOauth2ClientRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/mobile/MobileAppBundleOauth2ClientRepository.java similarity index 72% rename from dao/src/main/java/org/thingsboard/server/dao/sql/mobile/MobileAppOauth2ClientRepository.java rename to dao/src/main/java/org/thingsboard/server/dao/sql/mobile/MobileAppBundleOauth2ClientRepository.java index 9abf9b3b61..bc865dea4c 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/mobile/MobileAppOauth2ClientRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/mobile/MobileAppBundleOauth2ClientRepository.java @@ -17,13 +17,13 @@ package org.thingsboard.server.dao.sql.mobile; import org.springframework.data.jpa.repository.JpaRepository; import org.thingsboard.server.dao.model.sql.MobileAppOauth2ClientCompositeKey; -import org.thingsboard.server.dao.model.sql.MobileAppOauth2ClientEntity; +import org.thingsboard.server.dao.model.sql.MobileAppBundleOauth2ClientEntity; import java.util.List; import java.util.UUID; -public interface MobileAppOauth2ClientRepository extends JpaRepository { +public interface MobileAppBundleOauth2ClientRepository extends JpaRepository { - List findAllByMobileAppId(UUID mobileAppId); + List findAllByMobileAppBundleId(UUID mobileAppId); } diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/mobile/MobileAppBundleRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/mobile/MobileAppBundleRepository.java new file mode 100644 index 0000000000..e935d9b246 --- /dev/null +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/mobile/MobileAppBundleRepository.java @@ -0,0 +1,51 @@ +/** + * 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.sql.mobile; + +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.dao.model.sql.MobileAppBundleEntity; +import org.thingsboard.server.dao.model.sql.OAuth2ClientEntity; + +import java.util.List; +import java.util.UUID; + +public interface MobileAppBundleRepository extends JpaRepository { + + @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); + + @Query("SELECT b FROM MobileAppBundleEntity b WHERE b.tenantId = :tenantId AND " + + "(:searchText is NULL OR ilike(b.title, concat('%', :searchText, '%')) = true)") + Page findByTenantId(@Param("tenantId") UUID tenantId, + @Param("searchText") String searchText, + Pageable pageable); + + @Transactional + @Modifying + @Query("DELETE FROM MobileAppBundleEntity r WHERE r.tenantId = :tenantId") + void deleteByTenantId(@Param("tenantId") UUID tenantId); + +} diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/mobile/MobileAppRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/mobile/MobileAppRepository.java index 185a277644..56aa8dfc9f 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/mobile/MobileAppRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/mobile/MobileAppRepository.java @@ -39,4 +39,10 @@ public interface MobileAppRepository extends JpaRepository { +public interface QrCodeSettingsRepository extends JpaRepository { - MobileAppSettingsEntity findByTenantId(@Param("tenantId") UUID tenantId); + QrCodeSettingsEntity findByTenantId(@Param("tenantId") UUID tenantId); @Transactional @Modifying - @Query("DELETE FROM MobileAppSettingsEntity r WHERE r.tenantId = :tenantId") + @Query("DELETE FROM QrCodeSettingsEntity r WHERE r.tenantId = :tenantId") void deleteByTenantId(@Param("tenantId") UUID tenantId); } diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/oauth2/JpaOAuth2ClientDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/oauth2/JpaOAuth2ClientDao.java index 4a585186d9..d694ca0f93 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/oauth2/JpaOAuth2ClientDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/oauth2/JpaOAuth2ClientDao.java @@ -75,8 +75,8 @@ public class JpaOAuth2ClientDao extends JpaAbstractDao findByMobileAppId(UUID mobileAppId) { - return DaoUtil.convertDataList(repository.findByMobileAppId(mobileAppId)); + public List findByMobileAppBundleId(UUID mobileAppBundleId) { + return DaoUtil.convertDataList(repository.findByMobileAppBundleId(mobileAppBundleId)); } @Override diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/oauth2/OAuth2ClientRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/oauth2/OAuth2ClientRepository.java index 6805ca06ee..e6a1485a87 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/oauth2/OAuth2ClientRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/oauth2/OAuth2ClientRepository.java @@ -46,9 +46,10 @@ public interface OAuth2ClientRepository extends JpaRepository findEnabledByPkgNameAndPlatformType(@Param("pkgName") String pkgName, @Param("platformFilter") String platformFilter); @@ -61,16 +62,17 @@ public interface OAuth2ClientRepository extends JpaRepository findByMobileAppId(@Param("mobileAppId") UUID mobileAppId); + "LEFT JOIN MobileAppBundleOauth2ClientEntity bc on bc.oauth2ClientId = c.id " + + "WHERE bc.mobileAppBundleId = :mobileAppBundleId ") + List findByMobileAppBundleId(@Param("mobileAppBundleId") UUID mobileAppBundleId); - @Query("SELECT m.appSecret " + - "FROM MobileAppEntity m " + - "LEFT JOIN MobileAppOauth2ClientEntity mp on m.id = mp.mobileAppId " + - "LEFT JOIN OAuth2ClientEntity p on mp.oauth2ClientId = p.id " + - "WHERE p.id = :clientId " + - "AND m.pkgName = :pkgName") + @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 " + + "WHERE c.id = :clientId " + + "AND a.pkgName = :pkgName") String findAppSecret(@Param("clientId") UUID id, @Param("pkgName") String pkgName); diff --git a/dao/src/main/java/org/thingsboard/server/dao/tenant/TenantServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/tenant/TenantServiceImpl.java index 8e6c38814a..9d550ff341 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/tenant/TenantServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/tenant/TenantServiceImpl.java @@ -38,7 +38,7 @@ import org.thingsboard.server.dao.device.DeviceProfileService; import org.thingsboard.server.dao.entity.AbstractCachedEntityService; import org.thingsboard.server.dao.eventsourcing.DeleteEntityEvent; import org.thingsboard.server.dao.eventsourcing.SaveEntityEvent; -import org.thingsboard.server.dao.mobile.MobileAppSettingsService; +import org.thingsboard.server.dao.mobile.QrCodeSettingService; import org.thingsboard.server.dao.notification.NotificationSettingsService; import org.thingsboard.server.dao.service.PaginatedRemover; import org.thingsboard.server.dao.service.Validator; @@ -79,7 +79,7 @@ public class TenantServiceImpl extends AbstractCachedEntityService MobileApps = new ArrayList<>(); + List mobileApps = new ArrayList<>(); for (int i = 0; i < 5; i++) { MobileApp oAuth2Client = validMobileApp(TenantId.SYS_TENANT_ID, StringUtils.randomAlphabetic(5), true); MobileApp savedOauth2Client = mobileAppService.saveMobileApp(SYSTEM_TENANT_ID, oAuth2Client); - MobileApps.add(savedOauth2Client); + mobileApps.add(savedOauth2Client); } - PageData retrieved = mobileAppService.findMobileAppInfosByTenantId(TenantId.SYS_TENANT_ID, new PageLink(10, 0)); - List MobileAppInfos = MobileApps.stream().map(MobileApp -> new MobileAppInfo(MobileApp, Collections.emptyList())).toList(); - assertThat(retrieved.getData()).containsOnlyOnceElementsOf(MobileAppInfos); + PageData retrieved = mobileAppService.findMobileAppsByTenantId(TenantId.SYS_TENANT_ID, new PageLink(10, 0)); + assertThat(retrieved.getData()).containsOnlyOnceElementsOf(mobileApps); } - @Test - public void tesGetMobileAppInfo() { - OAuth2Client oAuth2Client = validClientInfo(TenantId.SYS_TENANT_ID, "Test google client"); - OAuth2Client savedOauth2Client = oAuth2ClientService.saveOAuth2Client(SYSTEM_TENANT_ID, oAuth2Client); - List oAuth2ClientInfosByIds = oAuth2ClientService.findOAuth2ClientInfosByIds(TenantId.SYS_TENANT_ID, List.of(savedOauth2Client.getId())); - - MobileApp MobileApp = validMobileApp(TenantId.SYS_TENANT_ID, "my.app", true); - MobileApp savedMobileApp = mobileAppService.saveMobileApp(SYSTEM_TENANT_ID, MobileApp); - - mobileAppService.updateOauth2Clients(TenantId.SYS_TENANT_ID, savedMobileApp.getId(), List.of(savedOauth2Client.getId())); - - // check MobileApp info - MobileAppInfo retrievedInfo = mobileAppService.findMobileAppInfoById(SYSTEM_TENANT_ID, savedMobileApp.getId()); - assertThat(retrievedInfo).isEqualTo(new MobileAppInfo(savedMobileApp, oAuth2ClientInfosByIds)); - - //find clients by MobileApp name - List oauth2LoginInfo = oAuth2ClientService.findOAuth2ClientLoginInfosByMobilePkgNameAndPlatformType(savedMobileApp.getName(), null); - assertThat(oauth2LoginInfo).containsOnly(new OAuth2ClientLoginInfo(savedOauth2Client.getLoginButtonLabel(), savedOauth2Client.getLoginButtonIcon(), String.format(OAUTH2_AUTHORIZATION_PATH_TEMPLATE, savedOauth2Client.getUuidId().toString()))); - } private MobileApp validMobileApp(TenantId tenantId, String mobileAppName, boolean oauth2Enabled) { MobileApp MobileApp = new MobileApp(); MobileApp.setTenantId(tenantId); MobileApp.setPkgName(mobileAppName); MobileApp.setAppSecret(StringUtils.randomAlphanumeric(24)); - MobileApp.setOauth2Enabled(oauth2Enabled); return MobileApp; } } diff --git a/dao/src/test/resources/application-test.properties b/dao/src/test/resources/application-test.properties index e71c6b1294..1e11c77f9a 100644 --- a/dao/src/test/resources/application-test.properties +++ b/dao/src/test/resources/application-test.properties @@ -102,8 +102,8 @@ cache.specs.alarmTypes.maxSize=10000 cache.specs.userSettings.timeToLiveInMinutes=1440 cache.specs.userSettings.maxSize=10000 -cache.specs.mobileAppSettings.timeToLiveInMinutes=1440 -cache.specs.mobileAppSettings.maxSize=10000 +cache.specs.qrCodeSettings.timeToLiveInMinutes=1440 +cache.specs.qrCodeSettings.maxSize=10000 cache.specs.mobileSecretKey.timeToLiveInMinutes=1440 cache.specs.mobileSecretKey.maxSize=10000 diff --git a/rest-client/src/main/java/org/thingsboard/rest/client/RestClient.java b/rest-client/src/main/java/org/thingsboard/rest/client/RestClient.java index 818d714a3b..c1e6d3d7e0 100644 --- a/rest-client/src/main/java/org/thingsboard/rest/client/RestClient.java +++ b/rest-client/src/main/java/org/thingsboard/rest/client/RestClient.java @@ -107,6 +107,7 @@ import org.thingsboard.server.common.data.id.DomainId; import org.thingsboard.server.common.data.id.EdgeId; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.EntityViewId; +import org.thingsboard.server.common.data.id.MobileAppBundleId; import org.thingsboard.server.common.data.id.MobileAppId; import org.thingsboard.server.common.data.id.OAuth2ClientId; import org.thingsboard.server.common.data.id.OAuth2ClientRegistrationTemplateId; @@ -124,7 +125,7 @@ import org.thingsboard.server.common.data.kv.Aggregation; import org.thingsboard.server.common.data.kv.AttributeKvEntry; import org.thingsboard.server.common.data.kv.TsKvEntry; import org.thingsboard.server.common.data.mobile.MobileApp; -import org.thingsboard.server.common.data.mobile.MobileAppInfo; +import org.thingsboard.server.common.data.mobile.MobileAppBundle; import org.thingsboard.server.common.data.oauth2.OAuth2Client; import org.thingsboard.server.common.data.oauth2.OAuth2ClientInfo; import org.thingsboard.server.common.data.oauth2.OAuth2ClientLoginInfo; @@ -2171,19 +2172,19 @@ public class RestClient implements Closeable { restTemplate.postForLocation(baseURL + "/api/domain/{id}/oauth2Clients", oauth2ClientIds, domainId.getId()); } - public List getTenantMobileAppInfos() { + public List getTenantMobileApps() { return restTemplate.exchange( - baseURL + "/api/mobileApp/infos", + baseURL + "/api/mobile/app", HttpMethod.GET, HttpEntity.EMPTY, new ParameterizedTypeReference>() { }).getBody(); } - public Optional getMobileAppInfoById(MobileAppId mobileAppId) { + public Optional getMobileAppById(MobileAppId mobileAppId) { try { - ResponseEntity mobileAppInfo = restTemplate.getForEntity(baseURL + "/api/mobileApp/info/{id}", MobileAppInfo.class, mobileAppId.getId()); - return Optional.ofNullable(mobileAppInfo.getBody()); + ResponseEntity mobileApp = restTemplate.getForEntity(baseURL + "/api/mobile/app/{id}", MobileApp.class, mobileAppId.getId()); + return Optional.ofNullable(mobileApp.getBody()); } catch (HttpClientErrorException exception) { if (exception.getStatusCode() == HttpStatus.NOT_FOUND) { return Optional.empty(); @@ -2194,15 +2195,45 @@ public class RestClient implements Closeable { } public MobileApp saveMobileApp(MobileApp mobileApp) { - return restTemplate.postForEntity(baseURL + "/api/mobileApp", mobileApp, MobileApp.class).getBody(); + return restTemplate.postForEntity(baseURL + "/api/mobile/app", mobileApp, MobileApp.class).getBody(); } public void deleteMobileApp(MobileAppId mobileAppId) { - restTemplate.delete(baseURL + "/api/mobileApp/{id}", mobileAppId.getId()); + restTemplate.delete(baseURL + "/api/mobile/app/{id}", mobileAppId.getId()); } - public void updateMobileAppOauth2Clients(MobileAppId mobileAppId, UUID[] oauth2ClientIds) { - restTemplate.postForLocation(baseURL + "/api/mobileApp/{id}/oauth2Clients", oauth2ClientIds, mobileAppId.getId()); + public List getTenantMobileBundleInfos() { + return restTemplate.exchange( + baseURL + "/api/mobile/bundle/infos", + HttpMethod.GET, + HttpEntity.EMPTY, + new ParameterizedTypeReference>() { + }).getBody(); + } + + public Optional getMobileBundleById(MobileAppBundleId mobileAppBundleId) { + try { + ResponseEntity mobileApp = restTemplate.getForEntity(baseURL + "/api/mobile/app/{id}", MobileAppBundle.class, mobileAppBundleId.getId()); + return Optional.ofNullable(mobileApp.getBody()); + } catch (HttpClientErrorException exception) { + if (exception.getStatusCode() == HttpStatus.NOT_FOUND) { + return Optional.empty(); + } else { + throw exception; + } + } + } + + public MobileAppBundle saveMobileBundle(MobileAppBundle mobileAppBundle) { + return restTemplate.postForEntity(baseURL + "/api/mobile/bundle", mobileAppBundle, MobileAppBundle.class).getBody(); + } + + public void deleteMobileBundle(MobileAppBundleId mobileAppBundleId) { + restTemplate.delete(baseURL + "/api/mobile/bundle/{id}", mobileAppBundleId.getId()); + } + + public void updateMobileAppBundleOauth2Clients(MobileAppBundleId mobileAppBundleId, UUID[] oauth2ClientIds) { + restTemplate.postForLocation(baseURL + "/api/mobile/bundle/{id}/oauth2Clients", oauth2ClientIds, mobileAppBundleId.getId()); } public String getLoginProcessingUrl() {