committed by
GitHub
189 changed files with 8404 additions and 2030 deletions
@ -0,0 +1,131 @@ |
|||
/** |
|||
* 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.bundle.MobileAppBundle; |
|||
import org.thingsboard.server.common.data.mobile.bundle.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_OR_TENANT_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 ANDROID and IOS app and " + |
|||
"mobile settings like oauth2 clients, self-registration and layout configuration." + |
|||
"When creating mobile app bundle, platform generates Mobile App Bundle Id as " + UUID_WIKI_LINK + |
|||
"The newly created Mobile App Bundle Id will be present in the response. " + |
|||
"Referencing non-existing Mobile App Bundle Id will cause 'Not Found' error." + SYSTEM_OR_TENANT_AUTHORITY_PARAGRAPH) |
|||
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_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." + SYSTEM_OR_TENANT_AUTHORITY_PARAGRAPH) |
|||
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_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_OR_TENANT_AUTHORITY_PARAGRAPH) |
|||
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_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 { |
|||
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_OR_TENANT_AUTHORITY_PARAGRAPH) |
|||
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_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_OR_TENANT_AUTHORITY_PARAGRAPH) |
|||
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_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,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.bundle.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.bundle.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,169 @@ |
|||
/** |
|||
* 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.app.MobileApp; |
|||
import org.thingsboard.server.common.data.mobile.app.MobileAppStatus; |
|||
import org.thingsboard.server.common.data.mobile.bundle.MobileAppBundle; |
|||
import org.thingsboard.server.common.data.mobile.bundle.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.Collections; |
|||
import java.util.Comparator; |
|||
import java.util.List; |
|||
import java.util.stream.Collectors; |
|||
import java.util.stream.Stream; |
|||
|
|||
import static org.assertj.core.api.Assertions.assertThat; |
|||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; |
|||
|
|||
@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); |
|||
androidApp = doPost("/api/mobile/app", androidApp, MobileApp.class); |
|||
|
|||
iosApp = validMobileApp(TenantId.SYS_TENANT_ID, "my.ios.package", PlatformType.IOS); |
|||
iosApp = doPost("/api/mobile/app", iosApp, MobileApp.class); |
|||
} |
|||
|
|||
@After |
|||
public void tearDown() throws Exception { |
|||
PageData<MobileAppBundleInfo> pageData2 = doGetTypedWithPageLink("/api/mobile/bundle/infos?", PAGE_DATA_MOBILE_APP_BUNDLE_TYPE_REF, new PageLink(10, 0)); |
|||
for (MobileAppBundleInfo appBundleInfo : pageData2.getData()) { |
|||
doDelete("/api/mobile/bundle/" + appBundleInfo.getId().getId()) |
|||
.andExpect(status().isOk()); |
|||
} |
|||
|
|||
PageData<MobileApp> pageData = doGetTypedWithPageLink("/api/mobile/app?", PAGE_DATA_MOBILE_APP_TYPE_REF, new PageLink(10, 0)); |
|||
for (MobileApp mobileApp : pageData.getData()) { |
|||
doDelete("/api/mobile/app/" + mobileApp.getId().getId()) |
|||
.andExpect(status().isOk()); |
|||
} |
|||
|
|||
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 testSaveMobileAppBundle() { |
|||
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 testSaveMobileAppBundleWithoutApps() throws Exception { |
|||
MobileAppBundle mobileAppBundle = new MobileAppBundle(); |
|||
mobileAppBundle.setTitle("Test bundle"); |
|||
|
|||
MobileAppBundle savedAppBundle = doPost("/api/mobile/bundle", mobileAppBundle, MobileAppBundle.class); |
|||
MobileAppBundleInfo retrievedMobileAppBundleInfo = doGet("/api/mobile/bundle/info/{id}", MobileAppBundleInfo.class, savedAppBundle.getId().getId()); |
|||
assertThat(retrievedMobileAppBundleInfo).isEqualTo(new MobileAppBundleInfo(savedAppBundle, null, null, false, |
|||
Collections.emptyList())); |
|||
} |
|||
|
|||
|
|||
@Test |
|||
public void testUpdateMobileAppBundleOauth2Clients() 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 retrievedMobileAppBundleInfo = doGet("/api/mobile/bundle/info/{id}", MobileAppBundleInfo.class, savedAppBundle.getId().getId()); |
|||
assertThat(retrievedMobileAppBundleInfo).isEqualTo(new MobileAppBundleInfo(savedAppBundle, androidApp.getPkgName(), iosApp.getPkgName(), false, |
|||
Stream.of(new OAuth2ClientInfo(savedOAuth2Client), new OAuth2ClientInfo(savedOAuth2Client2)) |
|||
.sorted(Comparator.comparing(OAuth2ClientInfo::getTitle)).collect(Collectors.toList()) |
|||
)); |
|||
|
|||
doPut("/api/mobile/bundle/" + savedAppBundle.getId() + "/oauth2Clients", List.of(savedOAuth2Client2.getId().getId())); |
|||
MobileAppBundleInfo retrievedMobileAppInfo2 = doGet("/api/mobile/bundle/info/{id}", MobileAppBundleInfo.class, savedAppBundle.getId().getId()); |
|||
assertThat(retrievedMobileAppInfo2).isEqualTo(new MobileAppBundleInfo(savedAppBundle, androidApp.getPkgName(), iosApp.getPkgName(), false, 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/mobile/bundle/info/{id}", MobileAppBundleInfo.class, savedMobileAppBundle.getId().getId()); |
|||
assertThat(retrievedMobileAppInfo).isEqualTo(new MobileAppBundleInfo(savedMobileAppBundle, androidApp.getPkgName(), iosApp.getPkgName(), false, List.of(new OAuth2ClientInfo(savedOAuth2Client)))); |
|||
} |
|||
|
|||
private MobileApp validMobileApp(TenantId tenantId, String mobileAppName, PlatformType platformType) { |
|||
MobileApp mobileApp = new MobileApp(); |
|||
mobileApp.setTenantId(tenantId); |
|||
mobileApp.setStatus(MobileAppStatus.DRAFT); |
|||
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,262 @@ |
|||
/** |
|||
* 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.mobile.app.MobileApp; |
|||
import org.thingsboard.server.common.data.mobile.app.MobileAppStatus; |
|||
import org.thingsboard.server.common.data.mobile.bundle.MobileAppBundle; |
|||
import org.thingsboard.server.common.data.mobile.bundle.MobileAppBundleInfo; |
|||
import org.thingsboard.server.common.data.mobile.qrCodeSettings.QRCodeConfig; |
|||
import org.thingsboard.server.common.data.mobile.qrCodeSettings.QrCodeSettings; |
|||
import org.thingsboard.server.common.data.mobile.app.StoreInfo; |
|||
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( "my.android.package", PlatformType.ANDROID); |
|||
StoreInfo androidStoreInfo = StoreInfo.builder() |
|||
.sha256CertFingerprints(ANDROID_APP_SHA256) |
|||
.storeLink(ANDROID_STORE_LINK) |
|||
.build(); |
|||
androidApp.setStoreInfo(androidStoreInfo); |
|||
MobileApp savedAndroidApp = doPost("/api/mobile/app", androidApp, MobileApp.class); |
|||
|
|||
MobileApp iosApp = validMobileApp( "my.ios.package", PlatformType.IOS); |
|||
StoreInfo iosQrCodeConfig = StoreInfo.builder() |
|||
.appId(APPLE_APP_ID) |
|||
.storeLink(IOS_STORE_LINK) |
|||
.build(); |
|||
iosApp.setStoreInfo(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/mobile/qr/settings", QrCodeSettings.class); |
|||
QRCodeConfig qrCodeConfig = new QRCodeConfig(); |
|||
qrCodeConfig.setQrCodeLabel(TEST_LABEL); |
|||
qrCodeSettings.setUseDefaultApp(true); |
|||
qrCodeSettings.setMobileAppBundleId(null); |
|||
qrCodeSettings.setQrCodeConfig(qrCodeConfig); |
|||
|
|||
doPost("/api/mobile/qr/settings", qrCodeSettings) |
|||
.andExpect(status().isOk()); |
|||
} |
|||
|
|||
@After |
|||
public void tearDown() throws Exception { |
|||
loginSysAdmin(); |
|||
PageData<MobileAppBundleInfo> pageData2 = doGetTypedWithPageLink("/api/mobile/bundle/infos?", PAGE_DATA_MOBILE_APP_BUNDLE_TYPE_REF, new PageLink(10, 0)); |
|||
for (MobileAppBundleInfo appBundleInfo : pageData2.getData()) { |
|||
doDelete("/api/mobile/bundle/" + appBundleInfo.getId().getId()) |
|||
.andExpect(status().isOk()); |
|||
} |
|||
PageData<MobileApp> pageData = doGetTypedWithPageLink("/api/mobile/app?", PAGE_DATA_MOBILE_APP_TYPE_REF, new PageLink(10, 0)); |
|||
for (MobileApp mobileApp : pageData.getData()) { |
|||
doDelete("/api/mobile/app/" + mobileApp.getId().getId()) |
|||
.andExpect(status().isOk()); |
|||
} |
|||
} |
|||
|
|||
@Test |
|||
public void testSaveQrCodeSettings() throws Exception { |
|||
loginSysAdmin(); |
|||
QrCodeSettings qrCodeSettings = doGet("/api/mobile/qr/settings", QrCodeSettings.class); |
|||
assertThat(qrCodeSettings.getQrCodeConfig().getQrCodeLabel()).isEqualTo(TEST_LABEL); |
|||
assertThat(qrCodeSettings.isUseDefaultApp()).isTrue(); |
|||
|
|||
qrCodeSettings.setUseDefaultApp(false); |
|||
qrCodeSettings.setMobileAppBundleId(mobileAppBundle.getId()); |
|||
|
|||
doPost("/api/mobile/qr/settings", qrCodeSettings) |
|||
.andExpect(status().isOk()); |
|||
|
|||
QrCodeSettings updatedQrCodeSettings = doGet("/api/mobile/qr/settings", QrCodeSettings.class); |
|||
assertThat(updatedQrCodeSettings.isUseDefaultApp()).isFalse(); |
|||
} |
|||
|
|||
@Test |
|||
public void testShouldNotSaveQrCodeSettingsWithoutRequiredConfig() throws Exception { |
|||
loginSysAdmin(); |
|||
QrCodeSettings qrCodeSettings = doGet("/api/mobile/qr/settings", QrCodeSettings.class); |
|||
|
|||
qrCodeSettings.setUseDefaultApp(false); |
|||
qrCodeSettings.setQrCodeConfig(null); |
|||
qrCodeSettings.setMobileAppBundleId(null); |
|||
|
|||
doPost("/api/mobile/qr/settings", qrCodeSettings) |
|||
.andExpect(status().isBadRequest()) |
|||
.andExpect(statusReason(containsString("Validation error: qrCodeConfig must not be null"))); |
|||
|
|||
qrCodeSettings.setQrCodeConfig(QRCodeConfig.builder().showOnHomePage(false).build()); |
|||
doPost("/api/mobile/qr/settings", qrCodeSettings) |
|||
.andExpect(status().isBadRequest()) |
|||
.andExpect(statusReason(containsString("Mobile app bundle is required to use custom application!"))); |
|||
|
|||
qrCodeSettings.setMobileAppBundleId(mobileAppBundle.getId()); |
|||
doPost("/api/mobile/qr/settings", qrCodeSettings) |
|||
.andExpect(status().isOk()); |
|||
} |
|||
|
|||
@Test |
|||
public void testShouldSaveQrCodeSettingsForDefaultApp() throws Exception { |
|||
loginSysAdmin(); |
|||
QrCodeSettings qrCodeSettings = doGet("/api/mobile/qr/settings", QrCodeSettings.class); |
|||
qrCodeSettings.setUseDefaultApp(true); |
|||
qrCodeSettings.setMobileAppBundleId(null); |
|||
|
|||
doPost("/api/mobile/qr/settings", qrCodeSettings) |
|||
.andExpect(status().isOk()); |
|||
} |
|||
|
|||
@Test |
|||
public void testGetApplicationAssociations() throws Exception { |
|||
loginSysAdmin(); |
|||
QrCodeSettings qrCodeSettings = doGet("/api/mobile/qr/settings", QrCodeSettings.class); |
|||
qrCodeSettings.setUseDefaultApp(true); |
|||
qrCodeSettings.setMobileAppBundleId(mobileAppBundle.getId()); |
|||
doPost("/api/mobile/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("my.android.package"); |
|||
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/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/mobile/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/mobile/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/mobile/qr/settings", QrCodeSettings.class); |
|||
qrCodeSettings.setUseDefaultApp(false); |
|||
qrCodeSettings.setMobileAppBundleId(mobileAppBundle.getId()); |
|||
doPost("/api/mobile/qr/settings", qrCodeSettings); |
|||
|
|||
String customAppDeepLink = doGet("/api/mobile/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/mobile/qr/deepLink", String.class); |
|||
Matcher tenantCustomAppParsedDeepLink = customAppExpectedPattern.matcher(tenantCustomAppDeepLink); |
|||
assertThat(tenantCustomAppParsedDeepLink.matches()).isTrue(); |
|||
assertThat(tenantCustomAppParsedDeepLink.group(1)).isEqualTo("localhost"); |
|||
|
|||
loginCustomerUser(); |
|||
String customerCustomAppDeepLink = doGet("/api/mobile/qr/deepLink", String.class); |
|||
Matcher customerCustomAppParsedDeepLink = customAppExpectedPattern.matcher(customerCustomAppDeepLink); |
|||
assertThat(customerCustomAppParsedDeepLink.matches()).isTrue(); |
|||
assertThat(customerCustomAppParsedDeepLink.group(1)).isEqualTo("localhost"); |
|||
} |
|||
|
|||
private MobileApp validMobileApp(String mobileAppName, PlatformType platformType) { |
|||
MobileApp mobileApp = new MobileApp(); |
|||
mobileApp.setTenantId(tenantId); |
|||
mobileApp.setStatus(MobileAppStatus.PUBLISHED); |
|||
mobileApp.setPkgName(mobileAppName); |
|||
mobileApp.setPlatformType(platformType); |
|||
mobileApp.setAppSecret(StringUtils.randomAlphanumeric(24)); |
|||
StoreInfo storeInfo = StoreInfo.builder() |
|||
.storeLink("https://play.google/test") |
|||
.sha256CertFingerprints(ANDROID_APP_SHA256) |
|||
.appId("test.app.id").build(); |
|||
mobileApp.setStoreInfo(storeInfo); |
|||
return mobileApp; |
|||
} |
|||
} |
|||
@ -0,0 +1,46 @@ |
|||
/** |
|||
* 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.bundle.MobileAppBundle; |
|||
import org.thingsboard.server.common.data.mobile.bundle.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 updateOauth2Clients(TenantId tenantId, MobileAppBundleId mobileAppBundleId, List<OAuth2ClientId> oAuth2ClientIds); |
|||
|
|||
MobileAppBundle findMobileAppBundleById(TenantId tenantId, MobileAppBundleId mobileAppBundleId); |
|||
|
|||
PageData<MobileAppBundleInfo> findMobileAppBundleInfosByTenantId(TenantId tenantId, PageLink pageLink); |
|||
|
|||
MobileAppBundleInfo findMobileAppBundleInfoById(TenantId tenantId, MobileAppBundleId mobileAppBundleId); |
|||
|
|||
MobileAppBundle findMobileAppBundleByPkgNameAndPlatform(TenantId tenantId, String pkgName, PlatformType platform); |
|||
|
|||
void deleteMobileAppBundleById(TenantId tenantId, MobileAppBundleId mobileAppBundleId); |
|||
|
|||
} |
|||
@ -0,0 +1,21 @@ |
|||
/** |
|||
* 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; |
|||
|
|||
public class Views { |
|||
public static class Public {} |
|||
public static class Private extends Public {} |
|||
} |
|||
@ -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,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; |
|||
|
|||
import org.thingsboard.server.common.data.mobile.app.MobileAppVersionInfo; |
|||
import org.thingsboard.server.common.data.oauth2.OAuth2ClientLoginInfo; |
|||
|
|||
import java.util.List; |
|||
|
|||
public record LoginMobileInfo(List<OAuth2ClientLoginInfo> oAuth2ClientLoginInfos, MobileAppVersionInfo versionInfo) { |
|||
} |
|||
@ -0,0 +1,27 @@ |
|||
/** |
|||
* Copyright © 2016-2024 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
package org.thingsboard.server.common.data.mobile; |
|||
|
|||
import lombok.Data; |
|||
|
|||
import java.util.Map; |
|||
|
|||
@Data |
|||
public class UserMobileSessionInfo { |
|||
|
|||
private Map<String, MobileSessionInfo> sessions; |
|||
|
|||
} |
|||
@ -0,0 +1,25 @@ |
|||
/** |
|||
* 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.app; |
|||
|
|||
public enum MobileAppStatus { |
|||
|
|||
DRAFT, |
|||
PUBLISHED, |
|||
DEPRECATED, |
|||
SUSPENDED |
|||
|
|||
} |
|||
@ -0,0 +1,49 @@ |
|||
/** |
|||
* Copyright © 2016-2024 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
package org.thingsboard.server.common.data.mobile.app; |
|||
|
|||
import io.swagger.v3.oas.annotations.media.Schema; |
|||
import lombok.AllArgsConstructor; |
|||
import lombok.Builder; |
|||
import lombok.Data; |
|||
import lombok.EqualsAndHashCode; |
|||
import lombok.NoArgsConstructor; |
|||
import org.thingsboard.server.common.data.validation.Length; |
|||
|
|||
@Data |
|||
@Builder |
|||
@NoArgsConstructor |
|||
@AllArgsConstructor |
|||
@EqualsAndHashCode |
|||
public class MobileAppVersionInfo { |
|||
|
|||
@Schema(description = "Minimum supported version") |
|||
@Length(fieldName = "minVersion", max = 20) |
|||
private String minVersion; |
|||
|
|||
@Schema(description = "Release notes of minimum supported version") |
|||
@Length(fieldName = "minVersionReleaseNotes", max = 40000) |
|||
private String minVersionReleaseNotes; |
|||
|
|||
@Schema(description = "Latest supported version") |
|||
@Length(fieldName = "latestVersion", max = 20) |
|||
private String latestVersion; |
|||
|
|||
@Schema(description = "Release notes of latest supported version") |
|||
@Length(fieldName = "latestVersionReleaseNotes", max = 40000) |
|||
private String latestVersionReleaseNotes; |
|||
|
|||
} |
|||
@ -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.common.data.mobile.bundle; |
|||
|
|||
import com.fasterxml.jackson.annotation.JsonProperty; |
|||
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.mobile.layout.MobileLayoutConfig; |
|||
import org.thingsboard.server.common.data.validation.Length; |
|||
|
|||
@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,63 @@ |
|||
/** |
|||
* 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.bundle; |
|||
|
|||
import io.swagger.v3.oas.annotations.media.Schema; |
|||
import lombok.Data; |
|||
import lombok.EqualsAndHashCode; |
|||
import org.thingsboard.server.common.data.id.MobileAppBundleId; |
|||
import org.thingsboard.server.common.data.oauth2.OAuth2ClientInfo; |
|||
|
|||
import java.util.List; |
|||
|
|||
@EqualsAndHashCode(callSuper = true) |
|||
@Data |
|||
@Schema |
|||
public class MobileAppBundleInfo extends MobileAppBundle { |
|||
|
|||
@Schema(description = "Android package name") |
|||
private String androidPkgName; |
|||
@Schema(description = "IOS package name") |
|||
private String iosPkgName; |
|||
@Schema(description = "List of available oauth2 clients") |
|||
private List<OAuth2ClientInfo> oauth2ClientInfos; |
|||
@Schema(description = "Indicates if qr code is available for bundle") |
|||
private boolean qrCodeEnabled; |
|||
|
|||
public MobileAppBundleInfo(MobileAppBundle mobileApp, String androidPkgName, String iosPkgName, boolean qrCodeEnabled) { |
|||
super(mobileApp); |
|||
this.androidPkgName = androidPkgName; |
|||
this.iosPkgName = iosPkgName; |
|||
this.qrCodeEnabled = qrCodeEnabled; |
|||
} |
|||
|
|||
public MobileAppBundleInfo(MobileAppBundle mobileApp, String androidPkgName, String iosPkgName, boolean qrCodeEnabled, List<OAuth2ClientInfo> oauth2ClientInfos) { |
|||
super(mobileApp); |
|||
this.androidPkgName = androidPkgName; |
|||
this.iosPkgName = iosPkgName; |
|||
this.qrCodeEnabled = qrCodeEnabled; |
|||
this.oauth2ClientInfos = oauth2ClientInfos; |
|||
} |
|||
|
|||
public MobileAppBundleInfo() { |
|||
super(); |
|||
} |
|||
|
|||
public MobileAppBundleInfo(MobileAppBundleId mobileAppBundleId) { |
|||
super(mobileAppBundleId); |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,35 @@ |
|||
/** |
|||
* 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.layout; |
|||
|
|||
import com.fasterxml.jackson.annotation.JsonView; |
|||
import io.swagger.v3.oas.annotations.media.Schema; |
|||
import lombok.Data; |
|||
import org.thingsboard.server.common.data.Views; |
|||
|
|||
@Data |
|||
public abstract class AbstractMobilePage implements MobilePage { |
|||
|
|||
@Schema(description = "Page label", example = "Air quality") |
|||
@JsonView(Views.Public.class) |
|||
protected String label; |
|||
@Schema(description = "Indicates if page is visible", example = "true", requiredMode = Schema.RequiredMode.REQUIRED) |
|||
@JsonView(Views.Private.class) |
|||
protected boolean visible; |
|||
@Schema(description = "URL of the page icon", example = "home_icon") |
|||
@JsonView(Views.Public.class) |
|||
protected String icon; |
|||
} |
|||
@ -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.common.data.mobile.layout; |
|||
|
|||
import com.fasterxml.jackson.annotation.JsonView; |
|||
import io.swagger.v3.oas.annotations.media.Schema; |
|||
import lombok.AllArgsConstructor; |
|||
import lombok.Builder; |
|||
import lombok.Data; |
|||
import lombok.EqualsAndHashCode; |
|||
import lombok.NoArgsConstructor; |
|||
import org.thingsboard.server.common.data.Views; |
|||
|
|||
@Data |
|||
@Builder |
|||
@NoArgsConstructor |
|||
@AllArgsConstructor |
|||
@EqualsAndHashCode(callSuper = true) |
|||
public class CustomMobilePage extends AbstractMobilePage { |
|||
|
|||
@Schema(description = "Path to custom page", example = "/alarmDetails/868c7083-032d-4f52-b8b4-7859aebb6a4e") |
|||
@JsonView(Views.Public.class) |
|||
private String path; |
|||
|
|||
@Override |
|||
public MobilePageType getType() { |
|||
return MobilePageType.CUSTOM; |
|||
} |
|||
} |
|||
@ -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.common.data.mobile.layout; |
|||
|
|||
import com.fasterxml.jackson.annotation.JsonView; |
|||
import io.swagger.v3.oas.annotations.media.Schema; |
|||
import lombok.AllArgsConstructor; |
|||
import lombok.Builder; |
|||
import lombok.Data; |
|||
import lombok.EqualsAndHashCode; |
|||
import lombok.NoArgsConstructor; |
|||
import org.thingsboard.server.common.data.Views; |
|||
|
|||
@Data |
|||
@Builder |
|||
@NoArgsConstructor |
|||
@AllArgsConstructor |
|||
@EqualsAndHashCode(callSuper = true) |
|||
public class DashboardPage extends AbstractMobilePage { |
|||
|
|||
@Schema(description = "Dashboard id", example = "784f394c-42b6-435a-983c-b7beff2784f9") |
|||
@JsonView(Views.Public.class) |
|||
private String dashboardId; |
|||
|
|||
@Override |
|||
public MobilePageType getType() { |
|||
return MobilePageType.DASHBOARD; |
|||
} |
|||
} |
|||
@ -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.common.data.mobile.layout; |
|||
|
|||
import com.fasterxml.jackson.annotation.JsonView; |
|||
import io.swagger.v3.oas.annotations.media.Schema; |
|||
import lombok.AllArgsConstructor; |
|||
import lombok.Builder; |
|||
import lombok.Data; |
|||
import lombok.EqualsAndHashCode; |
|||
import lombok.NoArgsConstructor; |
|||
import org.thingsboard.server.common.data.Views; |
|||
|
|||
@Data |
|||
@Builder |
|||
@NoArgsConstructor |
|||
@AllArgsConstructor |
|||
@EqualsAndHashCode(callSuper = true) |
|||
public class DefaultMobilePage extends AbstractMobilePage { |
|||
|
|||
@Schema(description = "Identifier for default page", example = "HOME") |
|||
@JsonView(Views.Public.class) |
|||
private DefaultPageId id; |
|||
|
|||
@Override |
|||
public MobilePageType getType() { |
|||
return MobilePageType.DEFAULT; |
|||
} |
|||
|
|||
} |
|||
@ -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.layout; |
|||
|
|||
public enum DefaultPageId { |
|||
|
|||
HOME, |
|||
ALARMS, |
|||
DEVICES, |
|||
CUSTOMERS, |
|||
ASSETS, |
|||
AUDIT_LOGS, |
|||
NOTIFICATIONS, |
|||
DEVICE_LIST, |
|||
DASHBOARDS |
|||
} |
|||
@ -0,0 +1,45 @@ |
|||
/** |
|||
* 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.layout; |
|||
|
|||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties; |
|||
import com.fasterxml.jackson.annotation.JsonSubTypes; |
|||
import com.fasterxml.jackson.annotation.JsonTypeInfo; |
|||
import com.fasterxml.jackson.annotation.JsonView; |
|||
import org.thingsboard.server.common.data.Views; |
|||
|
|||
import java.io.Serializable; |
|||
|
|||
@JsonIgnoreProperties(ignoreUnknown = true) |
|||
@JsonTypeInfo( |
|||
use = JsonTypeInfo.Id.NAME, |
|||
include = JsonTypeInfo.As.EXISTING_PROPERTY, |
|||
property = "type") |
|||
@JsonSubTypes({ |
|||
@JsonSubTypes.Type(value = DefaultMobilePage.class, name = "DEFAULT"), |
|||
@JsonSubTypes.Type(value = DashboardPage.class, name = "DASHBOARD"), |
|||
@JsonSubTypes.Type(value = WebViewPage.class, name = "WEB_VIEW"), |
|||
@JsonSubTypes.Type(value = CustomMobilePage.class, name = "CUSTOM") |
|||
}) |
|||
public interface MobilePage extends Serializable { |
|||
|
|||
@JsonView(Views.Private.class) |
|||
MobilePageType getType(); |
|||
|
|||
@JsonView(Views.Private.class) |
|||
boolean isVisible(); |
|||
|
|||
} |
|||
@ -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.layout; |
|||
|
|||
public enum MobilePageType { |
|||
|
|||
DEFAULT, |
|||
DASHBOARD, |
|||
WEB_VIEW, |
|||
CUSTOM |
|||
} |
|||
@ -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.common.data.mobile.layout; |
|||
|
|||
import com.fasterxml.jackson.annotation.JsonView; |
|||
import io.swagger.v3.oas.annotations.media.Schema; |
|||
import lombok.AllArgsConstructor; |
|||
import lombok.Builder; |
|||
import lombok.Data; |
|||
import lombok.EqualsAndHashCode; |
|||
import lombok.NoArgsConstructor; |
|||
import org.thingsboard.server.common.data.Views; |
|||
|
|||
@Data |
|||
@Builder |
|||
@NoArgsConstructor |
|||
@AllArgsConstructor |
|||
@EqualsAndHashCode(callSuper = true) |
|||
public class WebViewPage extends AbstractMobilePage { |
|||
|
|||
@Schema(description = "Url", example = "/url") |
|||
@JsonView(Views.Public.class) |
|||
private String url; |
|||
|
|||
@Override |
|||
public MobilePageType getType() { |
|||
return MobilePageType.WEB_VIEW; |
|||
} |
|||
} |
|||
@ -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,46 @@ |
|||
/** |
|||
* 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.bundle.MobileAppBundle; |
|||
import org.thingsboard.server.common.data.mobile.bundle.MobileAppBundleInfo; |
|||
import org.thingsboard.server.common.data.mobile.bundle.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<MobileAppBundleInfo> findInfosByTenantId(TenantId tenantId, PageLink pageLink); |
|||
|
|||
MobileAppBundleInfo findInfoById(TenantId tenantId, MobileAppBundleId mobileAppBundleId); |
|||
|
|||
List<MobileAppBundleOauth2Client> findOauth2ClientsByMobileAppBundleId(TenantId tenantId, MobileAppBundleId mobileAppBundleId); |
|||
|
|||
void addOauth2Client(TenantId tenantId, MobileAppBundleOauth2Client mobileAppBundleOauth2Client); |
|||
|
|||
void removeOauth2Client(TenantId tenantId, 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.bundle.MobileAppBundle; |
|||
import org.thingsboard.server.common.data.mobile.bundle.MobileAppBundleInfo; |
|||
import org.thingsboard.server.common.data.mobile.bundle.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 org.thingsboard.server.dao.service.DataValidator; |
|||
|
|||
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; |
|||
|
|||
import static org.thingsboard.server.dao.service.Validator.checkNotNull; |
|||
|
|||
@Slf4j |
|||
@Service |
|||
public class MobileAppBundleServiceImpl extends AbstractEntityService implements MobileAppBundleService { |
|||
|
|||
private static final String PLATFORM_TYPE_IS_REQUIRED = "Platform type is required if package name is specified"; |
|||
|
|||
@Autowired |
|||
private OAuth2ClientDao oauth2ClientDao; |
|||
@Autowired |
|||
private MobileAppBundleDao mobileAppBundleDao; |
|||
@Autowired |
|||
private DataValidator<MobileAppBundle> mobileAppBundleDataValidator; |
|||
|
|||
|
|||
@Override |
|||
public MobileAppBundle saveMobileAppBundle(TenantId tenantId, MobileAppBundle mobileAppBundle) { |
|||
log.trace("Executing saveMobileAppBundle [{}]", mobileAppBundle); |
|||
mobileAppBundleDataValidator.validate(mobileAppBundle, b -> tenantId); |
|||
try { |
|||
MobileAppBundle savedMobileApp = mobileAppBundleDao.save(tenantId, mobileAppBundle); |
|||
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 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(tenantId, client); |
|||
} |
|||
for (MobileAppBundleOauth2Client client : newClientList) { |
|||
mobileAppBundleDao.addOauth2Client(tenantId, client); |
|||
} |
|||
eventPublisher.publishEvent(SaveEntityEvent.builder().tenantId(tenantId) |
|||
.entityId(mobileAppBundleId).created(false).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 findMobileAppBundleInfosByTenantId [{}]", tenantId); |
|||
PageData<MobileAppBundleInfo> mobileBundles = mobileAppBundleDao.findInfosByTenantId(tenantId, pageLink); |
|||
mobileBundles.getData().forEach(this::fetchOauth2Clients); |
|||
return mobileBundles; |
|||
} |
|||
|
|||
@Override |
|||
public MobileAppBundleInfo findMobileAppBundleInfoById(TenantId tenantId, MobileAppBundleId mobileAppIdBundle) { |
|||
log.trace("Executing findMobileAppBundleInfoById [{}] [{}]", tenantId, mobileAppIdBundle); |
|||
MobileAppBundleInfo mobileAppBundleInfo = mobileAppBundleDao.findInfoById(tenantId, mobileAppIdBundle); |
|||
if (mobileAppBundleInfo != null) { |
|||
fetchOauth2Clients(mobileAppBundleInfo); |
|||
} |
|||
return mobileAppBundleInfo; |
|||
} |
|||
|
|||
@Override |
|||
public MobileAppBundle findMobileAppBundleByPkgNameAndPlatform(TenantId tenantId, String pkgName, PlatformType platform) { |
|||
log.trace("Executing findMobileAppBundleByPkgNameAndPlatform, tenantId [{}], pkgName [{}], platform [{}]", tenantId, pkgName, platform); |
|||
checkNotNull(platform, PLATFORM_TYPE_IS_REQUIRED); |
|||
return mobileAppBundleDao.findByPkgNameAndPlatform(tenantId, pkgName, platform); |
|||
} |
|||
|
|||
@Override |
|||
public void deleteMobileAppBundleById(TenantId tenantId, MobileAppBundleId mobileAppBundleId) { |
|||
log.trace("Executing deleteMobileAppBundleById [{}]", mobileAppBundleId.getId()); |
|||
mobileAppBundleDao.removeById(tenantId, mobileAppBundleId.getId()); |
|||
eventPublisher.publishEvent(DeleteEntityEvent.builder().tenantId(tenantId).entityId(mobileAppBundleId).build()); |
|||
} |
|||
|
|||
@Override |
|||
public void deleteByTenantId(TenantId tenantId) { |
|||
log.trace("Executing deleteMobileAppsByTenantId, tenantId [{}]", tenantId); |
|||
mobileAppBundleDao.deleteByTenantId(tenantId); |
|||
} |
|||
|
|||
@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 EntityType getEntityType() { |
|||
return EntityType.MOBILE_APP_BUNDLE; |
|||
} |
|||
|
|||
private void fetchOauth2Clients(MobileAppBundleInfo mobileAppBundleInfo) { |
|||
List<OAuth2ClientInfo> clients = oauth2ClientDao.findByMobileAppBundleId(mobileAppBundleInfo.getUuidId()).stream() |
|||
.map(OAuth2ClientInfo::new) |
|||
.sorted(Comparator.comparing(OAuth2ClientInfo::getTitle)) |
|||
.collect(Collectors.toList()); |
|||
mobileAppBundleInfo.setOauth2ClientInfos(clients); |
|||
} |
|||
} |
|||
@ -0,0 +1,134 @@ |
|||
/** |
|||
* 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.qrCodeSettings.BadgePosition; |
|||
import org.thingsboard.server.common.data.mobile.app.MobileApp; |
|||
import org.thingsboard.server.common.data.mobile.qrCodeSettings.QrCodeSettings; |
|||
import org.thingsboard.server.common.data.mobile.qrCodeSettings.QRCodeConfig; |
|||
import org.thingsboard.server.common.data.oauth2.PlatformType; |
|||
import org.thingsboard.server.dao.entity.AbstractCachedEntityService; |
|||
import org.thingsboard.server.dao.service.DataValidator; |
|||
|
|||
import java.util.Map; |
|||
|
|||
import static org.thingsboard.server.common.data.oauth2.PlatformType.ANDROID; |
|||
import static org.thingsboard.server.common.data.oauth2.PlatformType.IOS; |
|||
import static org.thingsboard.server.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( |
|||
"qr_code_settings_tenant_id_unq_key", "Mobile application for specified tenant already exists!" |
|||
)); |
|||
throw e; |
|||
} |
|||
} |
|||
|
|||
@Override |
|||
public QrCodeSettings findQrCodeSettings(TenantId tenantId) { |
|||
log.trace("Executing getMobileAppSettings for tenant [{}] ", tenantId); |
|||
QrCodeSettings qrCodeSettings = cache.getAndPutInTransaction(tenantId, |
|||
() -> qrCodeSettingsDao.findByTenantId(tenantId), true); |
|||
return constructMobileAppSettings(qrCodeSettings); |
|||
} |
|||
|
|||
@Override |
|||
public MobileApp findAppFromQrCodeSettings(TenantId tenantId, PlatformType platformType) { |
|||
log.trace("Executing findAppQrCodeConfig for tenant [{}] ", tenantId); |
|||
QrCodeSettings qrCodeSettings = findQrCodeSettings(tenantId); |
|||
return qrCodeSettings.getMobileAppBundleId() != null ? mobileAppService.findByBundleIdAndPlatformType(tenantId, qrCodeSettings.getMobileAppBundleId(), platformType) : 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); |
|||
qrCodeSettings.setAndroidEnabled(true); |
|||
qrCodeSettings.setIosEnabled(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.setGooglePlayLink(googlePlayLink); |
|||
qrCodeSettings.setAppStoreLink(appStoreLink); |
|||
} else { |
|||
MobileApp androidApp = mobileAppService.findByBundleIdAndPlatformType(qrCodeSettings.getTenantId(), qrCodeSettings.getMobileAppBundleId(), ANDROID); |
|||
MobileApp iosApp = mobileAppService.findByBundleIdAndPlatformType(qrCodeSettings.getTenantId(), qrCodeSettings.getMobileAppBundleId(), IOS); |
|||
if (androidApp != null && androidApp.getStoreInfo() != null) { |
|||
qrCodeSettings.setGooglePlayLink(androidApp.getStoreInfo().getStoreLink()); |
|||
} |
|||
if (iosApp != null && iosApp.getStoreInfo() != null) { |
|||
qrCodeSettings.setAppStoreLink(iosApp.getStoreInfo().getStoreLink()); |
|||
} |
|||
} |
|||
return qrCodeSettings; |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,114 @@ |
|||
/** |
|||
* Copyright © 2016-2024 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
package org.thingsboard.server.dao.model.sql; |
|||
|
|||
import com.fasterxml.jackson.databind.JsonNode; |
|||
import jakarta.persistence.Column; |
|||
import jakarta.persistence.Convert; |
|||
import jakarta.persistence.MappedSuperclass; |
|||
import lombok.Data; |
|||
import lombok.EqualsAndHashCode; |
|||
import org.thingsboard.server.common.data.id.MobileAppBundleId; |
|||
import org.thingsboard.server.common.data.id.MobileAppId; |
|||
import org.thingsboard.server.common.data.id.TenantId; |
|||
import org.thingsboard.server.common.data.mobile.bundle.MobileAppBundle; |
|||
import org.thingsboard.server.common.data.mobile.layout.MobileLayoutConfig; |
|||
import org.thingsboard.server.dao.model.BaseSqlEntity; |
|||
import org.thingsboard.server.dao.model.ModelConstants; |
|||
import org.thingsboard.server.dao.util.mapping.JsonConverter; |
|||
|
|||
import java.util.UUID; |
|||
|
|||
import static org.thingsboard.server.dao.model.ModelConstants.TENANT_ID_COLUMN; |
|||
|
|||
@Data |
|||
@EqualsAndHashCode(callSuper = true) |
|||
@MappedSuperclass |
|||
public abstract class AbstractMobileAppBundleEntity<T extends MobileAppBundle> extends BaseSqlEntity<T> { |
|||
|
|||
@Column(name = TENANT_ID_COLUMN) |
|||
private UUID tenantId; |
|||
|
|||
@Column(name = ModelConstants.MOBILE_APP_BUNDLE_TITLE_PROPERTY) |
|||
private String title; |
|||
|
|||
@Column(name = ModelConstants.MOBILE_APP_BUNDLE_DESCRIPTION_PROPERTY) |
|||
private String description; |
|||
|
|||
@Column(name = ModelConstants.MOBILE_APP_BUNDLE_ANDROID_APP_ID_PROPERTY) |
|||
private UUID androidAppId; |
|||
|
|||
@Column(name = ModelConstants.MOBILE_APP_BUNDLE_IOS_APP_ID_PROPERTY) |
|||
private UUID iosAppID; |
|||
|
|||
@Convert(converter = JsonConverter.class) |
|||
@Column(name = ModelConstants.MOBILE_APP_BUNDLE_LAYOUT_CONFIG_PROPERTY) |
|||
private JsonNode layoutConfig; |
|||
|
|||
@Column(name = ModelConstants.MOBILE_APP_BUNDLE_OAUTH2_ENABLED_PROPERTY) |
|||
private Boolean oauth2Enabled; |
|||
|
|||
public AbstractMobileAppBundleEntity() { |
|||
super(); |
|||
} |
|||
|
|||
public AbstractMobileAppBundleEntity(MobileAppBundleEntity mobileAppBundleEntity) { |
|||
super(mobileAppBundleEntity); |
|||
this.tenantId = mobileAppBundleEntity.getTenantId(); |
|||
this.title = mobileAppBundleEntity.getTitle(); |
|||
this.description = mobileAppBundleEntity.getDescription(); |
|||
this.androidAppId = mobileAppBundleEntity.getAndroidAppId(); |
|||
this.iosAppID = mobileAppBundleEntity.getIosAppID(); |
|||
this.layoutConfig = mobileAppBundleEntity.getLayoutConfig(); |
|||
this.oauth2Enabled = mobileAppBundleEntity.getOauth2Enabled(); |
|||
} |
|||
|
|||
public AbstractMobileAppBundleEntity(T mobileAppBundle) { |
|||
super(mobileAppBundle); |
|||
if (mobileAppBundle.getTenantId() != null) { |
|||
this.tenantId = mobileAppBundle.getTenantId().getId(); |
|||
} |
|||
this.title = mobileAppBundle.getTitle(); |
|||
this.description = mobileAppBundle.getDescription(); |
|||
if (mobileAppBundle.getAndroidAppId() != null) { |
|||
this.androidAppId = mobileAppBundle.getAndroidAppId().getId(); |
|||
} |
|||
if (mobileAppBundle.getIosAppId() != null) { |
|||
this.iosAppID = mobileAppBundle.getIosAppId().getId(); |
|||
} |
|||
this.layoutConfig = toJson(mobileAppBundle.getLayoutConfig()); |
|||
this.oauth2Enabled = mobileAppBundle.getOauth2Enabled(); |
|||
} |
|||
|
|||
protected MobileAppBundle toMobileAppBundle() { |
|||
MobileAppBundle mobileAppBundle = new MobileAppBundle(new MobileAppBundleId(id)); |
|||
mobileAppBundle.setCreatedTime(createdTime); |
|||
mobileAppBundle.setTitle(title); |
|||
mobileAppBundle.setDescription(description); |
|||
if (tenantId != null) { |
|||
mobileAppBundle.setTenantId(TenantId.fromUUID(tenantId)); |
|||
} |
|||
if (androidAppId != null) { |
|||
mobileAppBundle.setAndroidAppId(new MobileAppId(androidAppId)); |
|||
} |
|||
if (iosAppID != null) { |
|||
mobileAppBundle.setIosAppId(new MobileAppId(iosAppID)); |
|||
} |
|||
mobileAppBundle.setLayoutConfig(fromJson(layoutConfig, MobileLayoutConfig.class)); |
|||
mobileAppBundle.setOauth2Enabled(oauth2Enabled); |
|||
return mobileAppBundle; |
|||
} |
|||
} |
|||
@ -0,0 +1,45 @@ |
|||
/** |
|||
* Copyright © 2016-2024 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
package org.thingsboard.server.dao.model.sql; |
|||
|
|||
import lombok.Data; |
|||
import lombok.EqualsAndHashCode; |
|||
import org.thingsboard.server.common.data.mobile.bundle.MobileAppBundleInfo; |
|||
|
|||
@Data |
|||
@EqualsAndHashCode(callSuper = true) |
|||
public class MobileAppBundleInfoEntity extends AbstractMobileAppBundleEntity<MobileAppBundleInfo> { |
|||
|
|||
private String androidPkgName; |
|||
private String iosPkgName; |
|||
private boolean qrCodeEnabled; |
|||
|
|||
public MobileAppBundleInfoEntity() { |
|||
super(); |
|||
} |
|||
|
|||
public MobileAppBundleInfoEntity(MobileAppBundleEntity mobileAppBundleEntity, String androidPkgName, String iosPkgName, boolean qrCodeEnabled) { |
|||
super(mobileAppBundleEntity); |
|||
this.androidPkgName = androidPkgName; |
|||
this.iosPkgName = iosPkgName; |
|||
this.qrCodeEnabled = qrCodeEnabled; |
|||
} |
|||
|
|||
@Override |
|||
public MobileAppBundleInfo toData() { |
|||
return new MobileAppBundleInfo(super.toMobileAppBundle(), androidPkgName, iosPkgName, qrCodeEnabled); |
|||
} |
|||
} |
|||
@ -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,91 @@ |
|||
/** |
|||
* 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.QrCodeSettings; |
|||
import org.thingsboard.server.common.data.mobile.qrCodeSettings.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_ANDROID_ENABLED_PROPERTY) |
|||
private boolean androidEnabled; |
|||
|
|||
@Column(name = ModelConstants.QR_CODE_SETTINGS_IOS_ENABLED_PROPERTY) |
|||
private boolean iosEnabled; |
|||
|
|||
@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(); |
|||
this.androidEnabled = qrCodeSettings.isAndroidEnabled(); |
|||
this.iosEnabled = qrCodeSettings.isIosEnabled(); |
|||
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); |
|||
qrCodeSettings.setAndroidEnabled(androidEnabled); |
|||
qrCodeSettings.setIosEnabled(iosEnabled); |
|||
if (mobileAppBundleId != null) { |
|||
qrCodeSettings.setMobileAppBundleId(new MobileAppBundleId(mobileAppBundleId)); |
|||
} |
|||
qrCodeSettings.setQrCodeConfig(fromJson(qrCodeConfig, QRCodeConfig.class)); |
|||
return qrCodeSettings; |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,60 @@ |
|||
/** |
|||
* 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.beans.factory.annotation.Autowired; |
|||
import org.springframework.stereotype.Component; |
|||
import org.thingsboard.server.common.data.id.MobileAppId; |
|||
import org.thingsboard.server.common.data.id.TenantId; |
|||
import org.thingsboard.server.common.data.mobile.app.MobileApp; |
|||
import org.thingsboard.server.common.data.mobile.bundle.MobileAppBundle; |
|||
import org.thingsboard.server.common.data.oauth2.PlatformType; |
|||
import org.thingsboard.server.dao.exception.DataValidationException; |
|||
import org.thingsboard.server.dao.mobile.MobileAppDao; |
|||
import org.thingsboard.server.dao.service.DataValidator; |
|||
|
|||
@Component |
|||
@AllArgsConstructor |
|||
public class MobileAppBundleDataValidator extends DataValidator<MobileAppBundle> { |
|||
|
|||
@Autowired |
|||
private MobileAppDao mobileAppDao; |
|||
|
|||
@Override |
|||
protected void validateDataImpl(TenantId tenantId, MobileAppBundle mobileAppBundle) { |
|||
MobileAppId androidAppId = mobileAppBundle.getAndroidAppId(); |
|||
if (androidAppId != null) { |
|||
MobileApp androidApp = mobileAppDao.findById(tenantId, androidAppId.getId()); |
|||
if (androidApp == null) { |
|||
throw new DataValidationException("Mobile app bundle refers to non-existing android app!"); |
|||
} |
|||
if (androidApp.getPlatformType() != PlatformType.ANDROID) { |
|||
throw new DataValidationException("Mobile app bundle refers to wrong android app! Platform type of specified app is " + androidApp.getPlatformType()); |
|||
} |
|||
} |
|||
MobileAppId iosAppId = mobileAppBundle.getIosAppId(); |
|||
if (iosAppId != null) { |
|||
MobileApp iosApp = mobileAppDao.findById(tenantId, iosAppId.getId()); |
|||
if (iosApp == null) { |
|||
throw new DataValidationException("Mobile app bundle refers to non-existing ios app!"); |
|||
} |
|||
if (iosApp.getPlatformType() != PlatformType.IOS) { |
|||
throw new DataValidationException("Mobile app bundle refers to wrong ios app! Platform type of specified app is " + iosApp.getPlatformType()); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,50 @@ |
|||
/** |
|||
* 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.app.MobileApp; |
|||
import org.thingsboard.server.common.data.mobile.app.MobileAppStatus; |
|||
import org.thingsboard.server.common.data.oauth2.PlatformType; |
|||
import org.thingsboard.server.dao.exception.DataValidationException; |
|||
import org.thingsboard.server.dao.service.DataValidator; |
|||
|
|||
@Component |
|||
@AllArgsConstructor |
|||
public class MobileAppDataValidator extends DataValidator<MobileApp> { |
|||
|
|||
@Override |
|||
protected void validateDataImpl(TenantId tenantId, MobileApp mobileApp) { |
|||
if (mobileApp.getPlatformType() == PlatformType.ANDROID) { |
|||
if (mobileApp.getStoreInfo() != null && |
|||
(mobileApp.getStoreInfo().getSha256CertFingerprints() == null || mobileApp.getStoreInfo().getStoreLink() == null)) { |
|||
throw new DataValidationException("Sha256CertFingerprints and store link are required"); |
|||
} |
|||
} else if (mobileApp.getPlatformType() == PlatformType.IOS) { |
|||
if (mobileApp.getStoreInfo() != null && |
|||
(mobileApp.getStoreInfo().getAppId() == null || mobileApp.getStoreInfo().getStoreLink() == null)) { |
|||
throw new DataValidationException("AppId and store link are required"); |
|||
} |
|||
} else { |
|||
throw new DataValidationException("Wrong application platform type"); |
|||
} |
|||
if (mobileApp.getStatus() == MobileAppStatus.PUBLISHED && mobileApp.getStoreInfo() == null) { |
|||
throw new DataValidationException("Store info is required for published apps"); |
|||
} |
|||
} |
|||
} |
|||
@ -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,59 @@ |
|||
/** |
|||
* 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.beans.factory.annotation.Autowired; |
|||
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.app.MobileApp; |
|||
import org.thingsboard.server.common.data.mobile.app.MobileAppStatus; |
|||
import org.thingsboard.server.common.data.mobile.qrCodeSettings.QrCodeSettings; |
|||
import org.thingsboard.server.common.data.oauth2.PlatformType; |
|||
import org.thingsboard.server.dao.exception.DataValidationException; |
|||
import org.thingsboard.server.dao.mobile.MobileAppDao; |
|||
import org.thingsboard.server.dao.service.DataValidator; |
|||
|
|||
@Component |
|||
@AllArgsConstructor |
|||
public class QrCodeSettingsDataValidator extends DataValidator<QrCodeSettings> { |
|||
|
|||
@Autowired |
|||
MobileAppDao mobileAppDao; |
|||
|
|||
@Override |
|||
protected void validateDataImpl(TenantId tenantId, QrCodeSettings qrCodeSettings) { |
|||
MobileAppBundleId mobileAppBundleId = qrCodeSettings.getMobileAppBundleId(); |
|||
if (!qrCodeSettings.isUseDefaultApp() && (mobileAppBundleId == null)) { |
|||
throw new DataValidationException("Mobile app bundle is required to use custom application!"); |
|||
} |
|||
if (!qrCodeSettings.isUseDefaultApp()) { |
|||
if (qrCodeSettings.isAndroidEnabled()) { |
|||
MobileApp androidApp = mobileAppDao.findByBundleIdAndPlatformType(tenantId, mobileAppBundleId, PlatformType.ANDROID); |
|||
if (androidApp != null && androidApp.getStatus() != MobileAppStatus.PUBLISHED) { |
|||
throw new DataValidationException("The mobile app bundle references an Android app that has not been published!"); |
|||
} |
|||
} |
|||
if (qrCodeSettings.isIosEnabled()) { |
|||
MobileApp iosApp = mobileAppDao.findByBundleIdAndPlatformType(tenantId, mobileAppBundleId, PlatformType.IOS); |
|||
if (iosApp != null && iosApp.getStatus() != MobileAppStatus.PUBLISHED) { |
|||
throw new DataValidationException("The mobile app bundle references an iOS app that has not been published!"); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,101 @@ |
|||
/** |
|||
* 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.bundle.MobileAppBundle; |
|||
import org.thingsboard.server.common.data.mobile.bundle.MobileAppBundleInfo; |
|||
import org.thingsboard.server.common.data.mobile.bundle.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.MobileAppBundleOauth2ClientEntity; |
|||
import org.thingsboard.server.dao.model.sql.MobileAppOauth2ClientCompositeKey; |
|||
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<MobileAppBundleInfo> findInfosByTenantId(TenantId tenantId, PageLink pageLink) { |
|||
return DaoUtil.toPageData(mobileAppBundleRepository.findInfoByTenantId(tenantId.getId(), pageLink.getTextSearch(), DaoUtil.toPageable(pageLink))); |
|||
} |
|||
|
|||
@Override |
|||
public MobileAppBundleInfo findInfoById(TenantId tenantId, MobileAppBundleId mobileAppBundleId) { |
|||
return DaoUtil.getData(mobileAppBundleRepository.findInfoById(mobileAppBundleId.getId())); |
|||
} |
|||
|
|||
@Override |
|||
public List<MobileAppBundleOauth2Client> findOauth2ClientsByMobileAppBundleId(TenantId tenantId, MobileAppBundleId mobileAppBundleId) { |
|||
return DaoUtil.convertDataList(mobileOauth2ProviderRepository.findAllByMobileAppBundleId(mobileAppBundleId.getId())); |
|||
} |
|||
|
|||
@Override |
|||
public void addOauth2Client(TenantId tenantId, MobileAppBundleOauth2Client mobileAppBundleOauth2Client) { |
|||
mobileOauth2ProviderRepository.save(new MobileAppBundleOauth2ClientEntity(mobileAppBundleOauth2Client)); |
|||
} |
|||
|
|||
@Override |
|||
public void removeOauth2Client(TenantId tenantId, MobileAppBundleOauth2Client mobileAppBundleOauth2Client) { |
|||
mobileOauth2ProviderRepository.deleteById(new MobileAppOauth2ClientCompositeKey(mobileAppBundleOauth2Client.getMobileAppBundleId().getId(), |
|||
mobileAppBundleOauth2Client.getOAuth2ClientId().getId())); |
|||
} |
|||
|
|||
@Override |
|||
public MobileAppBundle findByPkgNameAndPlatform(TenantId tenantId, String pkgName, PlatformType platform) { |
|||
return DaoUtil.getData(mobileAppBundleRepository.findByPkgNameAndPlatformType(pkgName, platform)); |
|||
} |
|||
|
|||
@Override |
|||
public void deleteByTenantId(TenantId tenantId) { |
|||
mobileAppBundleRepository.deleteByTenantId(tenantId.getId()); |
|||
} |
|||
|
|||
@Override |
|||
public EntityType getEntityType() { |
|||
return EntityType.MOBILE_APP_BUNDLE; |
|||
} |
|||
|
|||
} |
|||
|
|||
@ -0,0 +1,70 @@ |
|||
/** |
|||
* 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.common.data.oauth2.PlatformType; |
|||
import org.thingsboard.server.dao.model.sql.MobileAppBundleEntity; |
|||
import org.thingsboard.server.dao.model.sql.MobileAppBundleInfoEntity; |
|||
|
|||
import java.util.UUID; |
|||
|
|||
public interface MobileAppBundleRepository extends JpaRepository<MobileAppBundleEntity, UUID> { |
|||
|
|||
@Query("SELECT new org.thingsboard.server.dao.model.sql.MobileAppBundleInfoEntity(b, andApp.pkgName, iosApp.pkgName, " + |
|||
"((andApp.status IS NOT NULL AND andApp.status = 'PUBLISHED') OR (iosApp.status IS NOT NULL AND iosApp.status = 'PUBLISHED'))) " + |
|||
"FROM MobileAppBundleEntity b " + |
|||
"LEFT JOIN MobileAppEntity andApp ON b.androidAppId = andApp.id " + |
|||
"LEFT JOIN MobileAppEntity iosApp ON b.iosAppID = iosApp.id " + |
|||
"WHERE b.tenantId = :tenantId AND " + |
|||
"(:searchText is NULL OR ilike(b.title, concat('%', :searchText, '%')) = true)") |
|||
Page<MobileAppBundleInfoEntity> findInfoByTenantId(@Param("tenantId") UUID tenantId, |
|||
@Param("searchText") String searchText, |
|||
Pageable pageable); |
|||
|
|||
@Query("SELECT new org.thingsboard.server.dao.model.sql.MobileAppBundleInfoEntity(b, andApp.pkgName, iosApp.pkgName, " + |
|||
"((andApp.status IS NOT NULL AND andApp.status = 'PUBLISHED') OR (iosApp.status IS NOT NULL AND iosApp.status = 'PUBLISHED'))) " + |
|||
"FROM MobileAppBundleEntity b " + |
|||
"LEFT JOIN MobileAppEntity andApp on b.androidAppId = andApp.id " + |
|||
"LEFT JOIN MobileAppEntity iosApp on b.iosAppID = iosApp.id " + |
|||
"WHERE b.id = :bundleId ") |
|||
MobileAppBundleInfoEntity findInfoById(UUID bundleId); |
|||
|
|||
@Query("SELECT b " + |
|||
"FROM MobileAppBundleEntity b " + |
|||
"LEFT JOIN MobileAppEntity a ON b.androidAppId = a.id OR b.iosAppID = a.id " + |
|||
"WHERE a.pkgName = :pkgName AND a.platformType = :platformType") |
|||
MobileAppBundleEntity findByPkgNameAndPlatformType(@Param("pkgName") String pkgName, |
|||
@Param("platformType") PlatformType platformType); |
|||
|
|||
@Query("SELECT b FROM MobileAppBundleEntity b WHERE b.tenantId = :tenantId AND " + |
|||
"(:searchText is NULL OR ilike(b.title, concat('%', :searchText, '%')) = true)") |
|||
Page<MobileAppBundleEntity> findByTenantId(@Param("tenantId") UUID tenantId, |
|||
@Param("searchText") String searchText, |
|||
Pageable pageable); |
|||
|
|||
@Transactional |
|||
@Modifying |
|||
@Query("DELETE FROM MobileAppBundleEntity r WHERE r.tenantId = :tenantId") |
|||
void deleteByTenantId(@Param("tenantId") UUID tenantId); |
|||
|
|||
} |
|||
Some files were not shown because too many files changed in this diff
Loading…
Reference in new issue