diff --git a/application/src/main/data/upgrade/3.7.0/schema_update.sql b/application/src/main/data/upgrade/3.7.0/schema_update.sql new file mode 100644 index 0000000000..6ef2074ffe --- /dev/null +++ b/application/src/main/data/upgrade/3.7.0/schema_update.sql @@ -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. +-- + +-- UPDATE RESOURCE SUB TYPE START + +DO +$$ + BEGIN + IF NOT EXISTS ( + SELECT FROM information_schema.columns + WHERE table_name = 'resource' AND column_name = 'resource_sub_type' + ) THEN + ALTER TABLE resource ADD COLUMN resource_sub_type varchar(32); + UPDATE resource SET resource_sub_type = 'IMAGE' WHERE resource_type = 'IMAGE'; + END IF; + END; +$$; + +-- UPDATE RESOURCE SUB TYPE END diff --git a/application/src/main/java/org/thingsboard/server/controller/ControllerConstants.java b/application/src/main/java/org/thingsboard/server/controller/ControllerConstants.java index d9232cda78..3a39eb5177 100644 --- a/application/src/main/java/org/thingsboard/server/controller/ControllerConstants.java +++ b/application/src/main/java/org/thingsboard/server/controller/ControllerConstants.java @@ -115,6 +115,8 @@ public class ControllerConstants { protected static final String RESOURCE_INFO_DESCRIPTION = "Resource Info is a lightweight object that includes main information about the Resource excluding the heavyweight data. "; protected static final String RESOURCE_DESCRIPTION = "Resource is a heavyweight object that includes main information about the Resource and also data. "; + protected static final String RESOURCE_IMAGE_SUB_TYPE_DESCRIPTION = "A string value representing resource sub-type."; + protected static final String RESOURCE_INCLUDE_SYSTEM_IMAGES_DESCRIPTION = "Use 'true' to include system images. Disabled by default. Ignored for requests by users with system administrator authority."; protected static final String RESOURCE_TEXT_SEARCH_DESCRIPTION = "The case insensitive 'substring' filter based on the resource title."; diff --git a/application/src/main/java/org/thingsboard/server/controller/ImageController.java b/application/src/main/java/org/thingsboard/server/controller/ImageController.java index c8d24d6081..b81dcd326d 100644 --- a/application/src/main/java/org/thingsboard/server/controller/ImageController.java +++ b/application/src/main/java/org/thingsboard/server/controller/ImageController.java @@ -40,6 +40,7 @@ import org.springframework.web.bind.annotation.RestController; import org.springframework.web.multipart.MultipartFile; import org.thingsboard.server.common.data.ImageDescriptor; import org.thingsboard.server.common.data.ImageExportData; +import org.thingsboard.server.common.data.ResourceSubType; import org.thingsboard.server.common.data.ResourceType; import org.thingsboard.server.common.data.TbImageDeleteResult; import org.thingsboard.server.common.data.TbResource; @@ -64,6 +65,7 @@ import java.util.concurrent.TimeUnit; 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.RESOURCE_IMAGE_SUB_TYPE_DESCRIPTION; import static org.thingsboard.server.controller.ControllerConstants.RESOURCE_INCLUDE_SYSTEM_IMAGES_DESCRIPTION; import static org.thingsboard.server.controller.ControllerConstants.RESOURCE_TEXT_SEARCH_DESCRIPTION; import static org.thingsboard.server.controller.ControllerConstants.SORT_ORDER_DESCRIPTION; @@ -95,7 +97,8 @@ public class ImageController extends BaseController { @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')") @PostMapping("/api/image") public TbResourceInfo uploadImage(@RequestPart MultipartFile file, - @RequestPart(required = false) String title) throws Exception { + @RequestPart(required = false) String title, + @RequestPart(required = false) String imageSubType) throws Exception { SecurityUser user = getCurrentUser(); TbResource image = new TbResource(); image.setTenantId(user.getTenantId()); @@ -108,7 +111,14 @@ public class ImageController extends BaseController { } else { image.setTitle(file.getOriginalFilename()); } + + ResourceSubType subType = ResourceSubType.IMAGE; + if (StringUtils.isNotEmpty(imageSubType)) { + subType = ResourceSubType.valueOf(imageSubType); + } + image.setResourceType(ResourceType.IMAGE); + image.setResourceSubType(subType); ImageDescriptor descriptor = new ImageDescriptor(); descriptor.setMediaType(file.getContentType()); image.setDescriptorValue(descriptor); @@ -193,6 +203,7 @@ public class ImageController extends BaseController { .mediaType(descriptor.getMediaType()) .fileName(imageInfo.getFileName()) .title(imageInfo.getTitle()) + .subType(imageInfo.getResourceSubType().name()) .resourceKey(imageInfo.getResourceKey()) .isPublic(imageInfo.isPublic()) .publicResourceKey(imageInfo.getPublicResourceKey()) @@ -214,6 +225,11 @@ public class ImageController extends BaseController { } else { image.setTitle(imageData.getFileName()); } + if (StringUtils.isNotEmpty(imageData.getSubType())) { + image.setResourceSubType(ResourceSubType.valueOf(imageData.getSubType())); + } else { + image.setResourceSubType(ResourceSubType.IMAGE); + } image.setResourceType(ResourceType.IMAGE); image.setResourceKey(imageData.getResourceKey()); image.setPublic(imageData.isPublic()); @@ -250,6 +266,8 @@ public class ImageController extends BaseController { @RequestParam int pageSize, @Parameter(description = PAGE_NUMBER_DESCRIPTION, required = true) @RequestParam int page, + @Parameter(description = RESOURCE_IMAGE_SUB_TYPE_DESCRIPTION, schema = @Schema(allowableValues = {"IMAGE", "IOT_SVG"})) + @RequestParam(required = false) String imageSubType, @Parameter(description = RESOURCE_INCLUDE_SYSTEM_IMAGES_DESCRIPTION) @RequestParam(required = false) boolean includeSystemImages, @Parameter(description = RESOURCE_TEXT_SEARCH_DESCRIPTION) @@ -261,10 +279,14 @@ public class ImageController extends BaseController { // PE: generic permission PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder); TenantId tenantId = getTenantId(); + ResourceSubType subType = ResourceSubType.IMAGE; + if (StringUtils.isNotEmpty(imageSubType)) { + subType = ResourceSubType.valueOf(imageSubType); + } if (getCurrentUser().getAuthority() == Authority.SYS_ADMIN || !includeSystemImages) { - return checkNotNull(imageService.getImagesByTenantId(tenantId, pageLink)); + return checkNotNull(imageService.getImagesByTenantId(tenantId, subType, pageLink)); } else { - return checkNotNull(imageService.getAllImagesByTenantId(tenantId, pageLink)); + return checkNotNull(imageService.getAllImagesByTenantId(tenantId, subType, pageLink)); } } diff --git a/application/src/main/java/org/thingsboard/server/install/ThingsboardInstallService.java b/application/src/main/java/org/thingsboard/server/install/ThingsboardInstallService.java index 33e2ddee01..eba35157c9 100644 --- a/application/src/main/java/org/thingsboard/server/install/ThingsboardInstallService.java +++ b/application/src/main/java/org/thingsboard/server/install/ThingsboardInstallService.java @@ -137,6 +137,10 @@ public class ThingsboardInstallService { entityDatabaseSchemaService.createCustomerTitleUniqueConstraintIfNotExists(); systemDataLoaderService.updateDefaultNotificationConfigs(false); systemDataLoaderService.updateJwtSettings(); + break; + case "3.7.0": + log.info("Upgrading ThingsBoard from version 3.7.0 to 3.7.1 ..."); + databaseEntitiesUpgradeService.upgradeDatabase("3.7.0"); //TODO DON'T FORGET to update switch statement in the CacheCleanupService if you need to clear the cache break; default: diff --git a/application/src/main/java/org/thingsboard/server/service/install/SqlDatabaseUpgradeService.java b/application/src/main/java/org/thingsboard/server/service/install/SqlDatabaseUpgradeService.java index 86d922c305..000c0536cc 100644 --- a/application/src/main/java/org/thingsboard/server/service/install/SqlDatabaseUpgradeService.java +++ b/application/src/main/java/org/thingsboard/server/service/install/SqlDatabaseUpgradeService.java @@ -121,6 +121,9 @@ public class SqlDatabaseUpgradeService implements DatabaseEntitiesUpgradeService case "3.6.4": updateSchema("3.6.4", 3006004, "3.7.0", 3007000, null); break; + case "3.7.0": + updateSchema("3.7.0", 3007000, "3.7.1", 3007001, null); + break; default: throw new RuntimeException("Unable to upgrade SQL database, unsupported fromVersion: " + fromVersion); } diff --git a/application/src/test/java/org/thingsboard/server/controller/ImageControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/ImageControllerTest.java index e272a40857..53b36c955b 100644 --- a/application/src/test/java/org/thingsboard/server/controller/ImageControllerTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/ImageControllerTest.java @@ -23,11 +23,14 @@ import org.junit.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpMethod; import org.springframework.mock.web.MockMultipartFile; +import org.springframework.mock.web.MockPart; import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.server.common.data.ImageDescriptor; import org.thingsboard.server.common.data.ImageExportData; +import org.thingsboard.server.common.data.ResourceSubType; import org.thingsboard.server.common.data.ResourceType; +import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.SystemParams; import org.thingsboard.server.common.data.TbResourceInfo; import org.thingsboard.server.common.data.page.PageData; @@ -35,6 +38,7 @@ import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.dao.service.DaoSqlTest; import org.thingsboard.server.dao.sql.resource.TbResourceRepository; +import java.nio.charset.StandardCharsets; import java.util.Base64; import java.util.List; @@ -53,6 +57,7 @@ public class ImageControllerTest extends AbstractControllerTest { @Before public void beforeEach() throws Exception { + resourceRepository.deleteAll(); loginTenantAdmin(); } @@ -66,6 +71,8 @@ public class ImageControllerTest extends AbstractControllerTest { String filename = "my_png_image.png"; TbResourceInfo imageInfo = uploadImage(HttpMethod.POST, "/api/image", filename, "image/png", PNG_IMAGE); + assertThat(imageInfo.getResourceSubType()).isEqualTo(ResourceSubType.IMAGE); + assertThat(imageInfo.getTitle()).isEqualTo(filename); assertThat(imageInfo.getResourceType()).isEqualTo(ResourceType.IMAGE); assertThat(imageInfo.getResourceKey()).isEqualTo(filename); @@ -85,6 +92,8 @@ public class ImageControllerTest extends AbstractControllerTest { String filename = "my_jpeg_image.jpg"; TbResourceInfo imageInfo = uploadImage(HttpMethod.POST, "/api/image", filename, "image/jpeg", JPEG_IMAGE); + assertThat(imageInfo.getResourceSubType()).isEqualTo(ResourceSubType.IMAGE); + ImageDescriptor imageDescriptor = imageInfo.getDescriptor(ImageDescriptor.class); checkJpegImageDescriptor(imageDescriptor); @@ -97,6 +106,22 @@ public class ImageControllerTest extends AbstractControllerTest { String filename = "my_svg_image.svg"; TbResourceInfo imageInfo = uploadImage(HttpMethod.POST, "/api/image", filename, "image/svg+xml", SVG_IMAGE); + assertThat(imageInfo.getResourceSubType()).isEqualTo(ResourceSubType.IMAGE); + + ImageDescriptor imageDescriptor = imageInfo.getDescriptor(ImageDescriptor.class); + checkSvgImageDescriptor(imageDescriptor); + + assertThat(downloadImage("tenant", filename)).containsExactly(SVG_IMAGE); + assertThat(downloadImagePreview("tenant", filename)).hasSize((int) imageDescriptor.getPreviewDescriptor().getSize()); + } + + @Test + public void testUploadIoTSvgImage() throws Exception { + String filename = "my_iot_svg_image.svg"; + TbResourceInfo imageInfo = uploadImage(HttpMethod.POST, "/api/image", ResourceSubType.IOT_SVG.name(), filename, "image/svg+xml", SVG_IMAGE); + + assertThat(imageInfo.getResourceSubType()).isEqualTo(ResourceSubType.IOT_SVG); + ImageDescriptor imageDescriptor = imageInfo.getDescriptor(ImageDescriptor.class); checkSvgImageDescriptor(imageDescriptor); @@ -175,6 +200,7 @@ public class ImageControllerTest extends AbstractControllerTest { assertThat(exportData.getMediaType()).isEqualTo("image/png"); assertThat(exportData.getFileName()).isEqualTo(filename); assertThat(exportData.getTitle()).isEqualTo(filename); + assertThat(exportData.getSubType()).isEqualTo(ResourceSubType.IMAGE.name()); assertThat(exportData.getResourceKey()).isEqualTo(filename); assertThat(exportData.getData()).isEqualTo(Base64.getEncoder().encodeToString(PNG_IMAGE)); assertThat(exportData.isPublic()).isTrue(); @@ -184,6 +210,7 @@ public class ImageControllerTest extends AbstractControllerTest { TbResourceInfo importedImageInfo = doPut("/api/image/import", exportData, TbResourceInfo.class); assertThat(importedImageInfo.getTitle()).isEqualTo(filename); + assertThat(exportData.getSubType()).isEqualTo(ResourceSubType.IMAGE.name()); assertThat(importedImageInfo.getResourceKey()).isEqualTo(filename); assertThat(importedImageInfo.getFileName()).isEqualTo(filename); assertThat(importedImageInfo.isPublic()).isTrue(); @@ -198,20 +225,37 @@ public class ImageControllerTest extends AbstractControllerTest { String systemImageName = "my_system_png_image.png"; TbResourceInfo systemImage = uploadImage(HttpMethod.POST, "/api/image", systemImageName, "image/png", PNG_IMAGE); + String systemIotSvgName = "my_system_iot_svg_image.svg"; + TbResourceInfo systemIotSvg = uploadImage(HttpMethod.POST, "/api/image", ResourceSubType.IOT_SVG.name(), systemIotSvgName, "image/svg+xml", SVG_IMAGE); + loginTenantAdmin(); String tenantImageName = "my_jpeg_image.jpg"; TbResourceInfo tenantImage = uploadImage(HttpMethod.POST, "/api/image", tenantImageName, "image/jpeg", JPEG_IMAGE); + String tenantIotSvgName = "my_iot_svg_image.svg"; + TbResourceInfo tenantIotSvg = uploadImage(HttpMethod.POST, "/api/image", ResourceSubType.IOT_SVG.name(), tenantIotSvgName, "image/svg+xml", SVG_IMAGE); + List tenantImages = getImages(null, false, 10); assertThat(tenantImages).containsOnly(tenantImage); + List tenantIotSvgs = getImages(null, ResourceSubType.IOT_SVG.name(), false, 10); + assertThat(tenantIotSvgs).containsOnly(tenantIotSvg); + List allImages = getImages(null, true, 10); assertThat(allImages).containsOnly(tenantImage, systemImage); + List allIotSvgs = getImages(null, ResourceSubType.IOT_SVG.name(), true, 10); + assertThat(allIotSvgs).containsOnly(tenantIotSvg, systemIotSvg); + assertThat(getImages("png", true, 10)) .containsOnly(systemImage); assertThat(getImages("jpg", true, 10)) .containsOnly(tenantImage); + + assertThat(getImages("my_system_iot", ResourceSubType.IOT_SVG.name(), true, 10)) + .containsOnly(systemIotSvg); + assertThat(getImages("my_iot_svg", ResourceSubType.IOT_SVG.name(),true, 10)) + .containsOnly(tenantIotSvg); } @Test @@ -312,7 +356,15 @@ public class ImageControllerTest extends AbstractControllerTest { } private List getImages(String searchText, boolean includeSystemImages, int limit) throws Exception { - PageData images = doGetTypedWithPageLink("/api/images?includeSystemImages=" + includeSystemImages + "&", new TypeReference<>() {}, new PageLink(limit, 0, searchText)); + return this.getImages(searchText, null, includeSystemImages, limit); + } + + private List getImages(String searchText, String imageSubType, boolean includeSystemImages, int limit) throws Exception { + var url = "/api/images?includeSystemImages=" + includeSystemImages + "&"; + if (StringUtils.isNotEmpty(imageSubType)) { + url += "imageSubType=" + imageSubType+ "&"; + } + PageData images = doGetTypedWithPageLink(url, new TypeReference<>() {}, new PageLink(limit, 0, searchText)); return images.getData(); } @@ -332,8 +384,16 @@ public class ImageControllerTest extends AbstractControllerTest { } private TbResourceInfo uploadImage(HttpMethod httpMethod, String url, String filename, String mediaType, byte[] content) throws Exception { + return this.uploadImage(httpMethod, url, null, filename, mediaType, content); + } + + private TbResourceInfo uploadImage(HttpMethod httpMethod, String url, String subType, String filename, String mediaType, byte[] content) throws Exception { MockMultipartFile file = new MockMultipartFile("file", filename, mediaType, content); var request = MockMvcRequestBuilders.multipart(httpMethod, url).file(file); + if (StringUtils.isNotEmpty(subType)) { + var imageSubTypePart = new MockPart("imageSubType", subType.getBytes(StandardCharsets.UTF_8)); + request.part(imageSubTypePart); + } setJwtToken(request); return readResponse(mockMvc.perform(request).andExpect(status().isOk()), TbResourceInfo.class); } diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/resource/ImageService.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/resource/ImageService.java index 9a65fcfdae..e1253ae568 100644 --- a/common/dao-api/src/main/java/org/thingsboard/server/dao/resource/ImageService.java +++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/resource/ImageService.java @@ -17,6 +17,7 @@ package org.thingsboard.server.dao.resource; import org.thingsboard.server.common.data.Dashboard; import org.thingsboard.server.common.data.HasImage; +import org.thingsboard.server.common.data.ResourceSubType; import org.thingsboard.server.common.data.TbImageDeleteResult; import org.thingsboard.server.common.data.TbResource; import org.thingsboard.server.common.data.TbResourceInfo; @@ -36,9 +37,9 @@ public interface ImageService { TbResourceInfo getPublicImageInfoByKey(String publicResourceKey); - PageData getImagesByTenantId(TenantId tenantId, PageLink pageLink); + PageData getImagesByTenantId(TenantId tenantId, ResourceSubType imageSubType, PageLink pageLink); - PageData getAllImagesByTenantId(TenantId tenantId, PageLink pageLink); + PageData getAllImagesByTenantId(TenantId tenantId, ResourceSubType imageSubType, PageLink pageLink); byte[] getImageData(TenantId tenantId, TbResourceId imageId); diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/ImageExportData.java b/common/data/src/main/java/org/thingsboard/server/common/data/ImageExportData.java index b407563d24..3be7b6f92d 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/ImageExportData.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/ImageExportData.java @@ -33,6 +33,7 @@ public class ImageExportData { private String mediaType; private String fileName; private String title; + private String subType; private String resourceKey; private boolean isPublic; private String publicResourceKey; diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/ResourceSubType.java b/common/data/src/main/java/org/thingsboard/server/common/data/ResourceSubType.java new file mode 100644 index 0000000000..e7e8b0747a --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/ResourceSubType.java @@ -0,0 +1,6 @@ +package org.thingsboard.server.common.data; + +public enum ResourceSubType { + IMAGE, + IOT_SVG +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/TbResourceInfo.java b/common/data/src/main/java/org/thingsboard/server/common/data/TbResourceInfo.java index f1318c2257..8537fe68ed 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/TbResourceInfo.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/TbResourceInfo.java @@ -46,6 +46,8 @@ public class TbResourceInfo extends BaseData implements HasName, H private String title; @Schema(description = "Resource type.", example = "LWM2M_MODEL", accessMode = Schema.AccessMode.READ_ONLY) private ResourceType resourceType; + @Schema(description = "Resource sub type.", example = "IOT_SVG", accessMode = Schema.AccessMode.READ_ONLY) + private ResourceSubType resourceSubType; @NoXss @Length(fieldName = "resourceKey") @Schema(description = "Resource key.", example = "19_1.0", accessMode = Schema.AccessMode.READ_ONLY) @@ -78,6 +80,7 @@ public class TbResourceInfo extends BaseData implements HasName, H this.tenantId = resourceInfo.tenantId; this.title = resourceInfo.title; this.resourceType = resourceInfo.resourceType; + this.resourceSubType = resourceInfo.resourceSubType; this.resourceKey = resourceInfo.resourceKey; this.searchText = resourceInfo.searchText; this.isPublic = resourceInfo.isPublic; diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/TbResourceInfoFilter.java b/common/data/src/main/java/org/thingsboard/server/common/data/TbResourceInfoFilter.java index 87d9b69749..60583dbb96 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/TbResourceInfoFilter.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/TbResourceInfoFilter.java @@ -27,5 +27,6 @@ public class TbResourceInfoFilter { private TenantId tenantId; private Set resourceTypes; + private Set resourceSubTypes; } diff --git a/common/proto/src/main/java/org/thingsboard/server/common/util/ProtoUtils.java b/common/proto/src/main/java/org/thingsboard/server/common/util/ProtoUtils.java index e89ce5634c..0ddc309b46 100644 --- a/common/proto/src/main/java/org/thingsboard/server/common/util/ProtoUtils.java +++ b/common/proto/src/main/java/org/thingsboard/server/common/util/ProtoUtils.java @@ -27,6 +27,7 @@ import org.thingsboard.server.common.data.DeviceProfileProvisionType; import org.thingsboard.server.common.data.DeviceProfileType; import org.thingsboard.server.common.data.DeviceTransportType; import org.thingsboard.server.common.data.EntityType; +import org.thingsboard.server.common.data.ResourceSubType; import org.thingsboard.server.common.data.ResourceType; import org.thingsboard.server.common.data.TbResource; import org.thingsboard.server.common.data.Tenant; @@ -827,6 +828,9 @@ public class ProtoUtils { if (isNotNull(resource.getPreview())) { builder.setPreview(ByteString.copyFrom(resource.getPreview())); } + if (isNotNull(resource.getResourceSubType())) { + builder.setResourceSubType(resource.getResourceSubType().name()); + } return builder.build(); } @@ -858,6 +862,9 @@ public class ProtoUtils { if (proto.hasPreview()) { resource.setPreview(proto.getPreview().toByteArray()); } + if (proto.hasResourceSubType()) { + resource.setResourceSubType(ResourceSubType.valueOf(proto.getResourceSubType())); + } return resource; } diff --git a/common/proto/src/main/proto/queue.proto b/common/proto/src/main/proto/queue.proto index 7b2aa2fe1b..afc157e476 100644 --- a/common/proto/src/main/proto/queue.proto +++ b/common/proto/src/main/proto/queue.proto @@ -286,6 +286,7 @@ message TbResourceProto { optional int64 externalIdLSB = 16; optional bytes data = 17; optional bytes preview = 18; + optional string resourceSubType = 19; } message ApiUsageStateProto { diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java b/dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java index 8f0502efbf..fdaf7fa96c 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java @@ -498,6 +498,7 @@ public class ModelConstants { public static final String RESOURCE_TABLE_NAME = "resource"; public static final String RESOURCE_TENANT_ID_COLUMN = TENANT_ID_COLUMN; public static final String RESOURCE_TYPE_COLUMN = "resource_type"; + public static final String RESOURCE_SUB_TYPE_COLUMN = "resource_sub_type"; public static final String RESOURCE_KEY_COLUMN = "resource_key"; public static final String RESOURCE_TITLE_COLUMN = TITLE_PROPERTY; public static final String RESOURCE_FILE_NAME_COLUMN = "file_name"; diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/TbResourceEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/TbResourceEntity.java index f507a8661f..ef12d656b6 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/sql/TbResourceEntity.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sql/TbResourceEntity.java @@ -20,6 +20,7 @@ import jakarta.persistence.Convert; import lombok.Data; import lombok.EqualsAndHashCode; import org.hibernate.annotations.Type; +import org.thingsboard.server.common.data.ResourceSubType; import org.thingsboard.server.common.data.ResourceType; import org.thingsboard.server.common.data.TbResource; import org.thingsboard.server.common.data.id.TbResourceId; @@ -42,6 +43,7 @@ import static org.thingsboard.server.dao.model.ModelConstants.RESOURCE_IS_PUBLIC import static org.thingsboard.server.dao.model.ModelConstants.RESOURCE_KEY_COLUMN; import static org.thingsboard.server.dao.model.ModelConstants.RESOURCE_PREVIEW_COLUMN; import static org.thingsboard.server.dao.model.ModelConstants.PUBLIC_RESOURCE_KEY_COLUMN; +import static org.thingsboard.server.dao.model.ModelConstants.RESOURCE_SUB_TYPE_COLUMN; import static org.thingsboard.server.dao.model.ModelConstants.RESOURCE_TABLE_NAME; import static org.thingsboard.server.dao.model.ModelConstants.RESOURCE_TENANT_ID_COLUMN; import static org.thingsboard.server.dao.model.ModelConstants.RESOURCE_TITLE_COLUMN; @@ -63,6 +65,9 @@ public class TbResourceEntity extends BaseSqlEntity { @Column(name = RESOURCE_TYPE_COLUMN) private String resourceType; + @Column(name = RESOURCE_SUB_TYPE_COLUMN) + private String resourceSubType; + @Column(name = RESOURCE_KEY_COLUMN) private String resourceKey; @@ -107,6 +112,9 @@ public class TbResourceEntity extends BaseSqlEntity { } this.title = resource.getTitle(); this.resourceType = resource.getResourceType().name(); + if (resource.getResourceSubType() != null) { + this.resourceSubType = resource.getResourceSubType().name(); + } this.resourceKey = resource.getResourceKey(); this.searchText = resource.getSearchText(); this.fileName = resource.getFileName(); @@ -126,6 +134,7 @@ public class TbResourceEntity extends BaseSqlEntity { resource.setTenantId(TenantId.fromUUID(tenantId)); resource.setTitle(title); resource.setResourceType(ResourceType.valueOf(resourceType)); + resource.setResourceSubType(resourceSubType != null ? ResourceSubType.valueOf(resourceSubType) : null); resource.setResourceKey(resourceKey); resource.setSearchText(searchText); resource.setFileName(fileName); diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/TbResourceInfoEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/TbResourceInfoEntity.java index 8f525916bf..35394d5baa 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/sql/TbResourceInfoEntity.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sql/TbResourceInfoEntity.java @@ -19,6 +19,7 @@ import com.fasterxml.jackson.databind.JsonNode; import jakarta.persistence.Convert; import lombok.Data; import lombok.EqualsAndHashCode; +import org.thingsboard.server.common.data.ResourceSubType; import org.thingsboard.server.common.data.ResourceType; import org.thingsboard.server.common.data.TbResourceInfo; import org.thingsboard.server.common.data.id.TbResourceId; @@ -39,6 +40,7 @@ import static org.thingsboard.server.dao.model.ModelConstants.RESOURCE_FILE_NAME import static org.thingsboard.server.dao.model.ModelConstants.RESOURCE_IS_PUBLIC_COLUMN; import static org.thingsboard.server.dao.model.ModelConstants.RESOURCE_KEY_COLUMN; import static org.thingsboard.server.dao.model.ModelConstants.PUBLIC_RESOURCE_KEY_COLUMN; +import static org.thingsboard.server.dao.model.ModelConstants.RESOURCE_SUB_TYPE_COLUMN; import static org.thingsboard.server.dao.model.ModelConstants.RESOURCE_TABLE_NAME; import static org.thingsboard.server.dao.model.ModelConstants.RESOURCE_TENANT_ID_COLUMN; import static org.thingsboard.server.dao.model.ModelConstants.RESOURCE_TITLE_COLUMN; @@ -60,6 +62,9 @@ public class TbResourceInfoEntity extends BaseSqlEntity implemen @Column(name = RESOURCE_TYPE_COLUMN) private String resourceType; + @Column(name = RESOURCE_SUB_TYPE_COLUMN) + private String resourceSubType; + @Column(name = RESOURCE_KEY_COLUMN) private String resourceKey; @@ -96,6 +101,9 @@ public class TbResourceInfoEntity extends BaseSqlEntity implemen this.tenantId = resource.getTenantId().getId(); this.title = resource.getTitle(); this.resourceType = resource.getResourceType().name(); + if (resource.getResourceSubType() != null) { + this.resourceSubType = resource.getResourceSubType().name(); + } this.resourceKey = resource.getResourceKey(); this.searchText = resource.getSearchText(); this.etag = resource.getEtag(); @@ -113,6 +121,7 @@ public class TbResourceInfoEntity extends BaseSqlEntity implemen resource.setTenantId(TenantId.fromUUID(tenantId)); resource.setTitle(title); resource.setResourceType(ResourceType.valueOf(resourceType)); + resource.setResourceSubType(resourceSubType != null ? ResourceSubType.valueOf(resourceSubType) : null); resource.setResourceKey(resourceKey); resource.setSearchText(searchText); resource.setEtag(etag); diff --git a/dao/src/main/java/org/thingsboard/server/dao/resource/BaseImageService.java b/dao/src/main/java/org/thingsboard/server/dao/resource/BaseImageService.java index 953037a864..28d00f8b5d 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/resource/BaseImageService.java +++ b/dao/src/main/java/org/thingsboard/server/dao/resource/BaseImageService.java @@ -36,6 +36,7 @@ import org.thingsboard.server.common.data.DataConstants; import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.HasImage; import org.thingsboard.server.common.data.ImageDescriptor; +import org.thingsboard.server.common.data.ResourceSubType; import org.thingsboard.server.common.data.ResourceType; import org.thingsboard.server.common.data.TbImageDeleteResult; import org.thingsboard.server.common.data.TbResource; @@ -140,6 +141,9 @@ public class BaseImageService extends BaseResourceService implements ImageServic if (image.getId() == null) { image.setResourceKey(getUniqueKey(image.getTenantId(), StringUtils.defaultIfEmpty(image.getResourceKey(), image.getFileName()))); } + if (image.getResourceSubType() == null) { + image.setResourceSubType(ResourceSubType.IMAGE); + } resourceValidator.validate(image, TbResourceInfo::getTenantId); ImageDescriptor descriptor = image.getDescriptor(ImageDescriptor.class); @@ -220,21 +224,23 @@ public class BaseImageService extends BaseResourceService implements ImageServic } @Override - public PageData getImagesByTenantId(TenantId tenantId, PageLink pageLink) { + public PageData getImagesByTenantId(TenantId tenantId, ResourceSubType imageSubType, PageLink pageLink) { log.trace("Executing getImagesByTenantId [{}]", tenantId); TbResourceInfoFilter filter = TbResourceInfoFilter.builder() .tenantId(tenantId) .resourceTypes(Set.of(ResourceType.IMAGE)) + .resourceSubTypes(Set.of(imageSubType)) .build(); return findTenantResourcesByTenantId(filter, pageLink); } @Override - public PageData getAllImagesByTenantId(TenantId tenantId, PageLink pageLink) { + public PageData getAllImagesByTenantId(TenantId tenantId, ResourceSubType imageSubType, PageLink pageLink) { log.trace("Executing getAllImagesByTenantId [{}]", tenantId); TbResourceInfoFilter filter = TbResourceInfoFilter.builder() .tenantId(tenantId) .resourceTypes(Set.of(ResourceType.IMAGE)) + .resourceSubTypes(Set.of(imageSubType)) .build(); return findAllTenantResourcesByTenantId(filter, pageLink); } diff --git a/dao/src/main/java/org/thingsboard/server/dao/resource/BaseResourceService.java b/dao/src/main/java/org/thingsboard/server/dao/resource/BaseResourceService.java index 723d0e8861..93cc662472 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/resource/BaseResourceService.java +++ b/dao/src/main/java/org/thingsboard/server/dao/resource/BaseResourceService.java @@ -184,7 +184,7 @@ public class BaseResourceService extends AbstractCachedEntityService findTenantResourcesByResourceTypeAndObjectIds(TenantId tenantId, ResourceType resourceType, String[] objectIds) { log.trace("Executing findTenantResourcesByResourceTypeAndObjectIds [{}][{}][{}]", tenantId, resourceType, objectIds); validateId(tenantId, id -> INCORRECT_TENANT_ID + id); - return resourceDao.findResourcesByTenantIdAndResourceType(tenantId, resourceType, objectIds, null); + return resourceDao.findResourcesByTenantIdAndResourceType(tenantId, resourceType, null, objectIds, null); } @Override @@ -198,7 +198,7 @@ public class BaseResourceService extends AbstractCachedEntityService findTenantResourcesByResourceTypeAndPageLink(TenantId tenantId, ResourceType resourceType, PageLink pageLink) { log.trace("Executing findTenantResourcesByResourceTypeAndPageLink [{}][{}][{}]", tenantId, resourceType, pageLink); validateId(tenantId, id -> INCORRECT_TENANT_ID + id); - return resourceDao.findResourcesByTenantIdAndResourceType(tenantId, resourceType, pageLink); + return resourceDao.findResourcesByTenantIdAndResourceType(tenantId, resourceType, null, pageLink); } @Override diff --git a/dao/src/main/java/org/thingsboard/server/dao/resource/TbResourceDao.java b/dao/src/main/java/org/thingsboard/server/dao/resource/TbResourceDao.java index b776b49cc9..c236a4a5d5 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/resource/TbResourceDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/resource/TbResourceDao.java @@ -15,6 +15,7 @@ */ package org.thingsboard.server.dao.resource; +import org.thingsboard.server.common.data.ResourceSubType; import org.thingsboard.server.common.data.ResourceType; import org.thingsboard.server.common.data.TbResource; import org.thingsboard.server.common.data.id.TbResourceId; @@ -35,10 +36,12 @@ public interface TbResourceDao extends Dao, TenantEntityWithDataDao, PageData findResourcesByTenantIdAndResourceType(TenantId tenantId, ResourceType resourceType, + ResourceSubType resourceSubType, PageLink pageLink); List findResourcesByTenantIdAndResourceType(TenantId tenantId, ResourceType resourceType, + ResourceSubType resourceSubType, String[] objectIds, String searchText); diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/resource/JpaTbResourceDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/resource/JpaTbResourceDao.java index cb2ab5aa42..f8927aaa81 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/resource/JpaTbResourceDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/resource/JpaTbResourceDao.java @@ -19,6 +19,7 @@ import lombok.extern.slf4j.Slf4j; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Component; import org.thingsboard.server.common.data.EntityType; +import org.thingsboard.server.common.data.ResourceSubType; import org.thingsboard.server.common.data.ResourceType; import org.thingsboard.server.common.data.TbResource; import org.thingsboard.server.common.data.id.TbResourceId; @@ -68,11 +69,13 @@ public class JpaTbResourceDao extends JpaAbstractDao findResourcesByTenantIdAndResourceType(TenantId tenantId, ResourceType resourceType, + ResourceSubType resourceSubType, PageLink pageLink) { return DaoUtil.toPageData(resourceRepository.findResourcesPage( tenantId.getId(), TenantId.SYS_TENANT_ID.getId(), resourceType.name(), + resourceSubType != null ? resourceSubType.name() : null, pageLink.getTextSearch(), DaoUtil.toPageable(pageLink) )); @@ -80,6 +83,7 @@ public class JpaTbResourceDao extends JpaAbstractDao findResourcesByTenantIdAndResourceType(TenantId tenantId, ResourceType resourceType, + ResourceSubType resourceSubType, String[] objectIds, String searchText) { return objectIds == null ? @@ -87,6 +91,7 @@ public class JpaTbResourceDao extends JpaAbstractDao resourceSubTypes = filter.getResourceSubTypes(); return DaoUtil.toPageData(resourceInfoRepository .findAllTenantResourcesByTenantId( filter.getTenantId().getId(), TenantId.NULL_UUID, resourceTypes.stream().map(Enum::name).collect(Collectors.toList()), + CollectionsUtil.isEmpty(resourceSubTypes) ? null : + resourceSubTypes.stream().map(Enum::name).collect(Collectors.toList()), Objects.toString(pageLink.getTextSearch(), ""), DaoUtil.toPageable(pageLink))); } @@ -77,10 +81,13 @@ public class JpaTbResourceInfoDao extends JpaAbstractDao resourceSubTypes = filter.getResourceSubTypes(); return DaoUtil.toPageData(resourceInfoRepository .findTenantResourcesByTenantId( filter.getTenantId().getId(), resourceTypes.stream().map(Enum::name).collect(Collectors.toList()), + CollectionsUtil.isEmpty(resourceSubTypes) ? null : + resourceSubTypes.stream().map(Enum::name).collect(Collectors.toList()), pageLink.getTextSearch(), DaoUtil.toPageable(pageLink))); } diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/resource/TbResourceInfoRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/resource/TbResourceInfoRepository.java index c092ea3292..00c51b50a2 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/resource/TbResourceInfoRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/resource/TbResourceInfoRepository.java @@ -37,19 +37,23 @@ public interface TbResourceInfoRepository extends JpaRepository findAllTenantResourcesByTenantId(@Param("tenantId") UUID tenantId, @Param("systemTenantId") UUID systemTenantId, @Param("resourceTypes") List resourceTypes, + @Param("resourceSubTypes") List resourceSubTypes, @Param("searchText") String searchText, Pageable pageable); @Query("SELECT ri FROM TbResourceInfoEntity ri WHERE " + "ri.tenantId = :tenantId " + "AND ri.resourceType IN :resourceTypes " + + "AND (:resourceSubTypes IS NULL OR ri.resourceSubType IN :resourceSubTypes) " + "AND (:searchText IS NULL OR ilike(ri.title, CONCAT('%', :searchText, '%')) = true)") Page findTenantResourcesByTenantId(@Param("tenantId") UUID tenantId, @Param("resourceTypes") List resourceTypes, + @Param("resourceSubTypes") List resourceSubTypes, @Param("searchText") String searchText, Pageable pageable); diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/resource/TbResourceRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/resource/TbResourceRepository.java index f5a831ff64..ce2e6b890b 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/resource/TbResourceRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/resource/TbResourceRepository.java @@ -34,6 +34,7 @@ public interface TbResourceRepository extends JpaRepository findResources(@Param("tenantId") UUID tenantId, @Param("systemAdminId") UUID sysAdminId, @Param("resourceType") String resourceType, + @Param("resourceSubType") String resourceSubType, @Param("searchText") String searchText); @Query("SELECT tr FROM TbResourceEntity tr " + diff --git a/dao/src/main/resources/sql/schema-entities.sql b/dao/src/main/resources/sql/schema-entities.sql index 9c1841240b..375cd9f689 100644 --- a/dao/src/main/resources/sql/schema-entities.sql +++ b/dao/src/main/resources/sql/schema-entities.sql @@ -715,6 +715,7 @@ CREATE TABLE IF NOT EXISTS resource ( tenant_id uuid NOT NULL, title varchar(255) NOT NULL, resource_type varchar(32) NOT NULL, + resource_sub_type varchar(32), resource_key varchar(255) NOT NULL, search_text varchar(255), file_name varchar(255) NOT NULL, diff --git a/rest-client/src/main/java/org/thingsboard/rest/client/RestClient.java b/rest-client/src/main/java/org/thingsboard/rest/client/RestClient.java index 8783770be3..39d408464f 100644 --- a/rest-client/src/main/java/org/thingsboard/rest/client/RestClient.java +++ b/rest-client/src/main/java/org/thingsboard/rest/client/RestClient.java @@ -58,6 +58,7 @@ import org.thingsboard.server.common.data.EventInfo; import org.thingsboard.server.common.data.ImageExportData; import org.thingsboard.server.common.data.OtaPackage; import org.thingsboard.server.common.data.OtaPackageInfo; +import org.thingsboard.server.common.data.ResourceSubType; import org.thingsboard.server.common.data.SaveDeviceWithCredentialsRequest; import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.SystemInfo; @@ -3603,10 +3604,19 @@ public class RestClient implements Closeable { } public PageData getImages(PageLink pageLink, boolean includeSystemImages) { + return this.getImages(pageLink, null, includeSystemImages); + } + + public PageData getImages(PageLink pageLink, ResourceSubType imageSubType, boolean includeSystemImages) { Map params = new HashMap<>(); + var url = baseURL + "/api/images?includeSystemImages={includeSystemImages}&"; addPageLinkToParam(params, pageLink); params.put("includeSystemImages", String.valueOf(includeSystemImages)); - return restTemplate.exchange(baseURL + "/api/images?includeSystemImages={includeSystemImages}&" + getUrlParams(pageLink), + if (imageSubType != null) { + url += "imageSubType={imageSubType}&"; + params.put("imageSubType", imageSubType.name()); + } + return restTemplate.exchange(url + getUrlParams(pageLink), HttpMethod.GET, HttpEntity.EMPTY, new ParameterizedTypeReference>() {},