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