74 changed files with 2316 additions and 949 deletions
@ -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<OAuth2ClientId> 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<MobileAppBundleInfo> 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()); |
||||
|
} |
||||
|
|
||||
|
} |
||||
@ -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<OAuth2ClientLoginInfo> 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); |
||||
|
} |
||||
|
|
||||
|
} |
||||
@ -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<OAuth2ClientId> 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<OAuth2ClientId> 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; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -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<OAuth2ClientId> oauth2Clients, User user) throws Exception; |
||||
|
|
||||
|
void updateOauth2Clients(MobileAppBundle mobileAppBundle, List<OAuth2ClientId> oAuth2ClientIds, User user); |
||||
|
|
||||
|
void delete(MobileAppBundle mobileAppBundle, User user); |
||||
|
|
||||
|
} |
||||
@ -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<PageData<MobileAppBundleInfo>> PAGE_DATA_MOBILE_APP_BUNDLE_TYPE_REF = new TypeReference<>() { |
||||
|
}; |
||||
|
static final TypeReference<PageData<MobileApp>> PAGE_DATA_MOBILE_APP_TYPE_REF = new TypeReference<>() { |
||||
|
}; |
||||
|
static final TypeReference<PageData<OAuth2ClientInfo>> 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<MobileApp> pageData = doGetTypedWithPageLink("/api/mobile/app?", PAGE_DATA_MOBILE_APP_TYPE_REF, new PageLink(10, 0)); |
||||
|
for (MobileApp mobileApp : pageData.getData()) { |
||||
|
doDelete("/api/mobile/app/" + mobileApp.getId().getId()) |
||||
|
.andExpect(status().isOk()); |
||||
|
} |
||||
|
|
||||
|
PageData<MobileAppBundleInfo> pageData2 = doGetTypedWithPageLink("/api/mobile/bundle?", PAGE_DATA_MOBILE_APP_BUNDLE_TYPE_REF, new PageLink(10, 0)); |
||||
|
for (MobileAppBundleInfo appBundleInfo : pageData2.getData()) { |
||||
|
doDelete("/api/mobile/bundle/" + appBundleInfo.getId().getId()) |
||||
|
.andExpect(status().isOk()); |
||||
|
} |
||||
|
|
||||
|
PageData<OAuth2ClientInfo> 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; |
||||
|
} |
||||
|
|
||||
|
} |
||||
@ -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"); |
|
||||
} |
|
||||
} |
|
||||
@ -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<PageData<MobileAppBundleInfo>> PAGE_DATA_MOBILE_APP_BUNDLE_TYPE_REF = new TypeReference<>() { |
||||
|
}; |
||||
|
static final TypeReference<PageData<MobileApp>> 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<MobileApp> pageData = doGetTypedWithPageLink("/api/mobile/app?", PAGE_DATA_MOBILE_APP_TYPE_REF, new PageLink(10, 0)); |
||||
|
for (MobileApp mobileApp : pageData.getData()) { |
||||
|
doDelete("/api/mobile/app/" + mobileApp.getId().getId()) |
||||
|
.andExpect(status().isOk()); |
||||
|
} |
||||
|
|
||||
|
PageData<MobileAppBundleInfo> pageData2 = doGetTypedWithPageLink("/api/mobile/bundle/infos?", PAGE_DATA_MOBILE_APP_BUNDLE_TYPE_REF, new PageLink(10, 0)); |
||||
|
for (MobileAppBundleInfo appBundleInfo : pageData2.getData()) { |
||||
|
doDelete("/api/mobile/bundle/" + appBundleInfo.getId().getId()) |
||||
|
.andExpect(status().isOk()); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
@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; |
||||
|
} |
||||
|
} |
||||
@ -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<MobileAppBundleInfo> findMobileAppBundleInfosByTenantId(TenantId tenantId, PageLink pageLink); |
||||
|
|
||||
|
MobileAppBundleInfo findMobileAppBundleInfoById(TenantId tenantId, MobileAppBundleId mobileAppBundleId); |
||||
|
|
||||
|
void updateOauth2Clients(TenantId tenantId, MobileAppBundleId mobileAppBundleId, List<OAuth2ClientId> oAuth2ClientIds); |
||||
|
|
||||
|
MobileAppBundle findMobileAppBundleByPkgNameAndPlatform(TenantId tenantId, String pkgName, PlatformType platform); |
||||
|
|
||||
|
void deleteMobileAppsByTenantId(TenantId tenantId); |
||||
|
} |
||||
@ -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; |
||||
|
} |
||||
|
} |
||||
@ -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<MobileAppBundleId> 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; |
||||
|
} |
||||
|
} |
||||
@ -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 |
||||
|
|
||||
|
} |
||||
@ -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<MobileMenuItem> items = new ArrayList<>(); |
||||
|
|
||||
|
} |
||||
@ -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<OAuth2ClientLoginInfo> oAuth2ClientLoginInfos; |
||||
|
} |
||||
@ -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; |
||||
|
|
||||
|
} |
||||
@ -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 |
||||
|
} |
||||
@ -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; |
||||
|
} |
||||
@ -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(); |
||||
|
|
||||
|
|
||||
|
} |
||||
@ -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<TenantId, MobileAppSettings, MobileAppSettingsEvictEvent> 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<MobileAppSettings> 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; |
|
||||
} |
|
||||
|
|
||||
} |
|
||||
@ -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<MobileAppBundle> { |
||||
|
|
||||
|
PageData<MobileAppBundle> findByTenantId(TenantId tenantId, PageLink pageLink); |
||||
|
|
||||
|
List<MobileAppBundleOauth2Client> 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); |
||||
|
|
||||
|
} |
||||
@ -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<MobileAppBundleInfo> findMobileAppBundleInfosByTenantId(TenantId tenantId, PageLink pageLink) { |
||||
|
log.trace("Executing findMobileAppInfosByTenantId [{}]", tenantId); |
||||
|
PageData<MobileAppBundle> 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<OAuth2ClientId> oAuth2ClientIds) { |
||||
|
log.trace("Executing updateOauth2Clients, mobileAppId [{}], oAuth2ClientIds [{}]", mobileAppBundleId, oAuth2ClientIds); |
||||
|
Set<MobileAppBundleOauth2Client> newClientList = oAuth2ClientIds.stream() |
||||
|
.map(clientId -> new MobileAppBundleOauth2Client(mobileAppBundleId, clientId)) |
||||
|
.collect(Collectors.toSet()); |
||||
|
|
||||
|
List<MobileAppBundleOauth2Client> existingClients = mobileAppBundleDao.findOauth2ClientsByMobileAppBundleId(tenantId, mobileAppBundleId); |
||||
|
List<MobileAppBundleOauth2Client> 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<HasId<?>> 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<OAuth2ClientInfo> clients = oauth2ClientDao.findByMobileAppBundleId(mobileAppBundle.getUuidId()).stream() |
||||
|
.map(OAuth2ClientInfo::new) |
||||
|
.sorted(Comparator.comparing(OAuth2ClientInfo::getTitle)) |
||||
|
.collect(Collectors.toList()); |
||||
|
MobileApp androidApp = mobileAppService.findMobileAppById(mobileAppBundle.getTenantId(), mobileAppBundle.getAndroidAppId()); |
||||
|
MobileApp iosApp = mobileAppService.findMobileAppById(mobileAppBundle.getTenantId(), mobileAppBundle.getIosAppId()); |
||||
|
return new MobileAppBundleInfo(mobileAppBundle, androidApp != null ? androidApp.getPkgName() : null, |
||||
|
iosApp != null ? iosApp.getPkgName() : null, clients); |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public EntityType getEntityType() { |
||||
|
return EntityType.MOBILE_APP_BUNDLE; |
||||
|
} |
||||
|
} |
||||
@ -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<TenantId, QrCodeSettings, QrCodeSettingsEvictEvent> 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<QrCodeSettings> 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; |
||||
|
} |
||||
|
|
||||
|
} |
||||
@ -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<MobileAppBundle> { |
||||
|
|
||||
|
@Column(name = TENANT_ID_COLUMN) |
||||
|
private UUID tenantId; |
||||
|
|
||||
|
@Column(name = ModelConstants.MOBILE_APP_BUNDLE_TITLE_PROPERTY) |
||||
|
private String title; |
||||
|
|
||||
|
@Column(name = ModelConstants.MOBILE_APP_BUNDLE_DESCRIPTION_PROPERTY) |
||||
|
private String description; |
||||
|
|
||||
|
@Column(name = ModelConstants.MOBILE_APP_BUNDLE_ANDROID_APP_ID_PROPERTY) |
||||
|
private UUID androidAppId; |
||||
|
|
||||
|
@Column(name = ModelConstants.MOBILE_APP_BUNDLE_IOS_APP_ID_PROPERTY) |
||||
|
private UUID iosAppID; |
||||
|
|
||||
|
@Convert(converter = JsonConverter.class) |
||||
|
@Column(name = ModelConstants.MOBILE_APP_BUNDLE_LAYOUT_CONFIG_PROPERTY) |
||||
|
private JsonNode layoutConfig; |
||||
|
|
||||
|
@Column(name = ModelConstants.MOBILE_APP_BUNDLE_OAUTH2_ENABLED_PROPERTY) |
||||
|
private Boolean oauth2Enabled; |
||||
|
|
||||
|
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; |
||||
|
} |
||||
|
} |
||||
@ -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<MobileAppSettings> { |
|
||||
|
|
||||
@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; |
|
||||
} |
|
||||
|
|
||||
} |
|
||||
@ -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<QrCodeSettings> { |
||||
|
|
||||
|
@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; |
||||
|
} |
||||
|
|
||||
|
} |
||||
@ -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<MobileAppSettings> { |
|
||||
|
|
||||
@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!"); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -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<QrCodeSettings> { |
||||
|
|
||||
|
@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!"); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -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<MobileAppBundleEntity, MobileAppBundle> implements MobileAppBundleDao { |
||||
|
|
||||
|
private final MobileAppBundleRepository mobileAppBundleRepository; |
||||
|
private final MobileAppBundleOauth2ClientRepository mobileOauth2ProviderRepository; |
||||
|
|
||||
|
@Override |
||||
|
protected Class<MobileAppBundleEntity> getEntityClass() { |
||||
|
return MobileAppBundleEntity.class; |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
protected JpaRepository<MobileAppBundleEntity, UUID> getRepository() { |
||||
|
return mobileAppBundleRepository; |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public PageData<MobileAppBundle> findByTenantId(TenantId tenantId, PageLink pageLink) { |
||||
|
return DaoUtil.toPageData(mobileAppBundleRepository.findByTenantId(tenantId.getId(), pageLink.getTextSearch(), DaoUtil.toPageable(pageLink))); |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public List<MobileAppBundleOauth2Client> 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; |
||||
|
} |
||||
|
|
||||
|
} |
||||
|
|
||||
@ -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<MobileAppBundleEntity, UUID> { |
||||
|
|
||||
|
@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<MobileAppBundleEntity> 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); |
||||
|
|
||||
|
} |
||||
Loading…
Reference in new issue