|
After Width: | Height: | Size: 2.2 KiB |
|
After Width: | Height: | Size: 2.7 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 2.9 KiB |
|
After Width: | Height: | Size: 30 KiB |
|
After Width: | Height: | Size: 30 KiB |
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 25 KiB |
|
After Width: | Height: | Size: 5.2 KiB |
|
After Width: | Height: | Size: 49 KiB |
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 2.2 KiB |
|
After Width: | Height: | Size: 2.7 KiB |
|
After Width: | Height: | Size: 2.2 KiB |
|
After Width: | Height: | Size: 5.2 KiB |
|
After Width: | Height: | Size: 6.0 KiB |
|
After Width: | Height: | Size: 48 KiB |
|
After Width: | Height: | Size: 16 KiB |
|
After Width: | Height: | Size: 2.7 KiB |
|
After Width: | Height: | Size: 31 KiB |
|
After Width: | Height: | Size: 50 KiB |
|
After Width: | Height: | Size: 28 KiB |
|
After Width: | Height: | Size: 2.2 KiB |
|
After Width: | Height: | Size: 2.7 KiB |
|
After Width: | Height: | Size: 5.2 KiB |
|
After Width: | Height: | Size: 27 KiB |
|
After Width: | Height: | Size: 50 KiB |
|
After Width: | Height: | Size: 12 KiB |
@ -0,0 +1,31 @@ |
|||||
|
{ |
||||
|
"widgetsBundle": { |
||||
|
"alias": "general_high_performance_scada_symbol", |
||||
|
"title": "General high-performance SCADA symbols", |
||||
|
"image": "tb-image:aHBfc2NhZGFfZ2VuZXJhbF9zeW1ib2xzX3N5c3RlbV9idW5kbGVfaW1hZ2UucG5n:IkdlbmVyYWwgaGlnaC1wZXJmb3JtYW5jZSBTQ0FEQSBzdW1ib2xzIiBzeXN0ZW0gYnVuZGxlIGltYWdl:SU1BR0U=;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAMgAAACgCAMAAAB+IdObAAAA8FBMVEXe3t7f39/f39/X19cAAADf39/e3t7d3d3e3t6VlZXI3/eZmZlmZmbGxsbr6+vc3Nytra3Y2Nikrri3xtZ9fX0aGho1NTW5ubnS0tKUlJS/v7+urq6zs7Pa2tqJiYm0tLR5eXnPz8/b29t8fHyhoaJxcXImJia+vr5WVlZjY2NNTU0wMDCpqamzx9zCx9GxsbGEhIRZWVlubm7Q09jJycmLmbhvg6tBQUG3ydvR0dEbPoW90+qzu8spSosNM3+nsMWZpb6TlpkAKHhhZWpfX19UVFRGRkbU1NR9jrKmpqZTbJ43VZFdZm5qampFYZh4foRgw9f8AAAACHRSTlPvvyAgAK+wr43PeosAAAVgSURBVHja7d1pV9pAGIZhuj9TMw1t02RiZawkYYlKRSm21qWb3Zf//286Y8RCgBgIlBecWwkh+sHrvIF45hyldPfOrRKWvXt37pbuLD9DpRgPsBKtwGmVVFoVCAyEWjcWsreftAdiTQp5YSU5IJaBgFgGAmIZCIhlICCWgWDSBJ9hYnEQyWbboiD8OWZayNFruSFlAzEQopCd9aSdohAh9NHFQbYeJW0UhVgMgIv+IrHMEMFCACHjCGtSS8pltS9CIBTcC0OhH+odshDJOXfBY2ZJRFHoiucJxI3kof4KXM4OWVRzpAu9E1OFxJZluXAc7ikOLx8+h8uRTEnKS4iFqx24gigkObWstuNIRBZz+yDMGQGJOWmIjACOGvBvIgIWEy5Q64O01Q7VUyuBIIpjCcc9jEPImOtj8WF8cbh/Iq7lSqKQsbkYjll0X35XH7JsF0QDoQKxbegPfX+5kxzVx5cI8uXbWkbfHkMlvPWMNr9QgNgv1zLzbAAeMvNmA3nyOunJNBDb3smGbEBJ1pHZ5mwgTx8mTQXBtRDbtpcAYg9ORH7+LFOQl2mI/8tH53zTiShB7BRkW0rX3/8kMyAbEZwd/1f9fJMYpNoPqalPue1sZ0BkiB3Hj+rn9XlAdn++nQqCFGRbQz4rTx+kqiB9P7TvY0duSHzCHCC778XHt7OYSCOOG2mIPQgRn/zzzpwgu+8BJZkKUlWQvl44a75cczImgo66/okO6nOA/IGK/yx4aqVLQ9IRm0gOSHX0dYTSc8SGgpSvn4jt/a8L4m70duqX33o2ZN1WdbaQUaez8OsIFKTCvfHVOxUNwdambuNis6Fv+rNXx6YBqTzOqFK1dehlA7Yq2VxFAZIlqVQqVQUBbPTXUxCDXEiuMMlepefoDcQecCRbOpAQl5JqtaI0A1Wr6qbSCsDGUIQmAnaVPz6WO4lFQZLs60LeFgyBrS1pD3r3SwS5tOhN8uDKhjyRggxVCEEKck0GMj4DKZSBjM9ACmUg41tRyMFZCxe1sPjyQR4jaQDSPf1+1AXw9fTkAAsvH8QPkdQHaZ220PpwgFdHFAaSE7JnRS+h6occvAOgID9eteYh4Zik/BDL2tsahOi634HTk5OjLmZesO0jf/khOn8rBXmnz6oP73BweoxZFzx7lp8yIcSKvAHI8UkLCtKCOr1QrDfldM1nzzSlPBRHYcjeOvohrZNjQCHOgKOzYoyoIVmqtobUApZONpuiGGT/ZerJfvS72+2eqfPr6/cTFKrpY6hAM95gRKxZBLIfPkYK8kp3DBx3vx6gSLyJERDFGOfm00PkFpCCzK6QYThvHANgDKlyQxRjjhDGCn8/jV8aDURlIAZiIAYyPwhjBmIgBmIgBmIgU0I4Y80mY2LpIaJ2sSzCMZwMMFFNb5EQBBoSYETbNYEJ8hpYKETUxgzEV778611h0OazgBzvJoWYuGDcQNTCj8i93sXKwCwg6yLpG6YYybiBjBGi6SNnRf4QZvKCaMxAkpFkr3dRgjCG4XgQNBpB4F273kUdknXcQAzEQAzEQAzEQAzkhkG8etIarlpOyIj/r2UgNwMytHy0rJCh5SNCEC/KXKfKXj6iBEHDG+lr5Fk+IgXhjWbWOlX28hElCFBmQ5VzLx9RgkxUECFvtCFkryOXGUhGBmIgKgPJ6CZDGkkBpo4GhGw3FSJCDsBjZVBrQojF2oBz6FiuAK0mhLTDNrhGtCVoNSGEMw7Z1jsctCph4hxyb9kxLSQCwaaAhDEASW0sU0DgSlF2V+A5Am7F7ecg1k29stPNQKhlINRamTfWLd3CSnS/dHslRqIYd2/fw7JXunX77l/CoXsjeoRJ3gAAAABJRU5ErkJggg==", |
||||
|
"scada": true, |
||||
|
"description": "Bundle with high-performance SCADA symbols", |
||||
|
"order": 9500, |
||||
|
"name": "General high-performance SCADA symbols" |
||||
|
}, |
||||
|
"widgetTypeFqns": [ |
||||
|
"hp_control_panel", |
||||
|
"hp_simple_horizontal_scale", |
||||
|
"hp_dynamic_horizontal_scale", |
||||
|
"hp_simple_vertical_scale", |
||||
|
"hp_dynamic_vertical_scale", |
||||
|
"hp_long_horizontal_connector", |
||||
|
"hp_long_vertical_connector", |
||||
|
"hp_horizontal_connector", |
||||
|
"hp_vertical_connector", |
||||
|
"hp_top_right_elbow_connector", |
||||
|
"hp_left_bottom_elbow_connector", |
||||
|
"hp_bottom_right_elbow_connector", |
||||
|
"hp_left_top_elbow_connector", |
||||
|
"hp_cross_connector", |
||||
|
"hp_bottom_tee_connector", |
||||
|
"hp_right_tee_connector", |
||||
|
"hp_left_tee_connector", |
||||
|
"hp_top_tee_connector" |
||||
|
] |
||||
|
} |
||||
@ -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; |
||||
|
} |
||||
|
} |
||||