From 96f2b17c5a74cbb7055ef979b250434eea91eaad Mon Sep 17 00:00:00 2001 From: Kenn Jacobsen Date: Tue, 2 Apr 2024 10:40:45 +0200 Subject: [PATCH] Entity search - take one (#15951) * Add entity search endpoints (to be replaced with Examine based ones for content, media and member) * Update OpenApi.json --- .../Item/SearchDataTypeItemController.cs | 46 ++ .../Item/SearchDocumentItemController.cs | 39 + .../Item/SearchDocumentTypeItemController.cs | 47 ++ .../Media/Item/SearchMediaItemController.cs | 39 + .../Item/SearchMediaTypeItemController.cs | 47 ++ .../Member/Item/SearchMemberItemController.cs | 39 + .../Item/SearchMemberTypeItemController.cs | 46 ++ .../Item/SearchTemplateItemController.cs | 46 ++ src/Umbraco.Cms.Api.Management/OpenApi.json | 714 +++++++++++++++++- .../Services/IEntitySearchService.cs | 9 + .../UmbracoBuilder.Services.cs | 1 + .../Services/Implement/EntitySearchService.cs | 46 ++ 12 files changed, 1118 insertions(+), 1 deletion(-) create mode 100644 src/Umbraco.Cms.Api.Management/Controllers/DataType/Item/SearchDataTypeItemController.cs create mode 100644 src/Umbraco.Cms.Api.Management/Controllers/Document/Item/SearchDocumentItemController.cs create mode 100644 src/Umbraco.Cms.Api.Management/Controllers/DocumentType/Item/SearchDocumentTypeItemController.cs create mode 100644 src/Umbraco.Cms.Api.Management/Controllers/Media/Item/SearchMediaItemController.cs create mode 100644 src/Umbraco.Cms.Api.Management/Controllers/MediaType/Item/SearchMediaTypeItemController.cs create mode 100644 src/Umbraco.Cms.Api.Management/Controllers/Member/Item/SearchMemberItemController.cs create mode 100644 src/Umbraco.Cms.Api.Management/Controllers/MemberType/Item/SearchMemberTypeItemController.cs create mode 100644 src/Umbraco.Cms.Api.Management/Controllers/Template/Item/SearchTemplateItemController.cs create mode 100644 src/Umbraco.Core/Services/IEntitySearchService.cs create mode 100644 src/Umbraco.Infrastructure/Services/Implement/EntitySearchService.cs diff --git a/src/Umbraco.Cms.Api.Management/Controllers/DataType/Item/SearchDataTypeItemController.cs b/src/Umbraco.Cms.Api.Management/Controllers/DataType/Item/SearchDataTypeItemController.cs new file mode 100644 index 0000000000..793f7d54c8 --- /dev/null +++ b/src/Umbraco.Cms.Api.Management/Controllers/DataType/Item/SearchDataTypeItemController.cs @@ -0,0 +1,46 @@ +using Asp.Versioning; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Umbraco.Cms.Api.Management.ViewModels.DataType.Item; +using Umbraco.Cms.Core.Mapping; +using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Models.Entities; +using Umbraco.Cms.Core.Services; + +namespace Umbraco.Cms.Api.Management.Controllers.DataType.Item; + +[ApiVersion("1.0")] +public class SearchDataTypeItemController : DatatypeItemControllerBase +{ + private readonly IEntitySearchService _entitySearchService; + private readonly IDataTypeService _dataTypeService; + private readonly IUmbracoMapper _mapper; + + public SearchDataTypeItemController(IEntitySearchService entitySearchService, IDataTypeService dataTypeService, IUmbracoMapper mapper) + { + _entitySearchService = entitySearchService; + _dataTypeService = dataTypeService; + _mapper = mapper; + } + + [HttpGet("search")] + [MapToApiVersion("1.0")] + [ProducesResponseType(typeof(PagedModel), StatusCodes.Status200OK)] + public async Task Search(string query, int skip = 0, int take = 100) + { + PagedModel searchResult = _entitySearchService.Search(UmbracoObjectTypes.DataType, query, skip, take); + if (searchResult.Items.Any() is false) + { + return Ok(new PagedModel { Total = searchResult.Total }); + } + + IEnumerable dataTypes = await _dataTypeService.GetAllAsync(searchResult.Items.Select(item => item.Key).ToArray()); + var result = new PagedModel + { + Items = _mapper.MapEnumerable(dataTypes), + Total = searchResult.Total + }; + + return Ok(result); + } +} diff --git a/src/Umbraco.Cms.Api.Management/Controllers/Document/Item/SearchDocumentItemController.cs b/src/Umbraco.Cms.Api.Management/Controllers/Document/Item/SearchDocumentItemController.cs new file mode 100644 index 0000000000..54d500691e --- /dev/null +++ b/src/Umbraco.Cms.Api.Management/Controllers/Document/Item/SearchDocumentItemController.cs @@ -0,0 +1,39 @@ +using Asp.Versioning; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Umbraco.Cms.Api.Management.Factories; +using Umbraco.Cms.Api.Management.ViewModels.Document.Item; +using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Models.Entities; +using Umbraco.Cms.Core.Services; + +namespace Umbraco.Cms.Api.Management.Controllers.Document.Item; + +[ApiVersion("1.0")] +public class SearchDocumentItemController : DocumentItemControllerBase +{ + private readonly IEntitySearchService _entitySearchService; + private readonly IDocumentPresentationFactory _documentPresentationFactory; + + public SearchDocumentItemController(IEntitySearchService entitySearchService, IDocumentPresentationFactory documentPresentationFactory) + { + _entitySearchService = entitySearchService; + _documentPresentationFactory = documentPresentationFactory; + } + + [HttpGet("search")] + [MapToApiVersion("1.0")] + [ProducesResponseType(typeof(PagedModel), StatusCodes.Status200OK)] + public async Task Search(string query, int skip = 0, int take = 100) + { + // FIXME: use Examine and handle user start nodes + PagedModel searchResult = _entitySearchService.Search(UmbracoObjectTypes.Document, query, skip, take); + var result = new PagedModel + { + Items = searchResult.Items.OfType().Select(_documentPresentationFactory.CreateItemResponseModel), + Total = searchResult.Total + }; + + return await Task.FromResult(Ok(result)); + } +} diff --git a/src/Umbraco.Cms.Api.Management/Controllers/DocumentType/Item/SearchDocumentTypeItemController.cs b/src/Umbraco.Cms.Api.Management/Controllers/DocumentType/Item/SearchDocumentTypeItemController.cs new file mode 100644 index 0000000000..a3878147e4 --- /dev/null +++ b/src/Umbraco.Cms.Api.Management/Controllers/DocumentType/Item/SearchDocumentTypeItemController.cs @@ -0,0 +1,47 @@ +using Asp.Versioning; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Umbraco.Cms.Api.Management.ViewModels.DocumentType.Item; +using Umbraco.Cms.Core.Mapping; +using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Models.Entities; +using Umbraco.Cms.Core.Services; +using Umbraco.Extensions; + +namespace Umbraco.Cms.Api.Management.Controllers.DocumentType.Item; + +[ApiVersion("1.0")] +public class SearchDocumentTypeItemController : DocumentTypeItemControllerBase +{ + private readonly IEntitySearchService _entitySearchService; + private readonly IContentTypeService _contentTypeService; + private readonly IUmbracoMapper _mapper; + + public SearchDocumentTypeItemController(IEntitySearchService entitySearchService, IContentTypeService contentTypeService, IUmbracoMapper mapper) + { + _entitySearchService = entitySearchService; + _contentTypeService = contentTypeService; + _mapper = mapper; + } + + [HttpGet("search")] + [MapToApiVersion("1.0")] + [ProducesResponseType(typeof(PagedModel), StatusCodes.Status200OK)] + public async Task Search(string query, int skip = 0, int take = 100) + { + PagedModel searchResult = _entitySearchService.Search(UmbracoObjectTypes.DocumentType, query, skip, take); + if (searchResult.Items.Any() is false) + { + return await Task.FromResult(Ok(new PagedModel { Total = searchResult.Total })); + } + + IEnumerable contentTypes = _contentTypeService.GetAll(searchResult.Items.Select(item => item.Key).ToArray().EmptyNull()); + var result = new PagedModel + { + Items = _mapper.MapEnumerable(contentTypes), + Total = searchResult.Total + }; + + return Ok(result); + } +} diff --git a/src/Umbraco.Cms.Api.Management/Controllers/Media/Item/SearchMediaItemController.cs b/src/Umbraco.Cms.Api.Management/Controllers/Media/Item/SearchMediaItemController.cs new file mode 100644 index 0000000000..7d506f1b87 --- /dev/null +++ b/src/Umbraco.Cms.Api.Management/Controllers/Media/Item/SearchMediaItemController.cs @@ -0,0 +1,39 @@ +using Asp.Versioning; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Umbraco.Cms.Api.Management.Factories; +using Umbraco.Cms.Api.Management.ViewModels.Media.Item; +using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Models.Entities; +using Umbraco.Cms.Core.Services; + +namespace Umbraco.Cms.Api.Management.Controllers.Media.Item; + +[ApiVersion("1.0")] +public class SearchMediaItemController : MediaItemControllerBase +{ + private readonly IEntitySearchService _entitySearchService; + private readonly IMediaPresentationFactory _mediaPresentationFactory; + + public SearchMediaItemController(IEntitySearchService entitySearchService, IMediaPresentationFactory mediaPresentationFactory) + { + _entitySearchService = entitySearchService; + _mediaPresentationFactory = mediaPresentationFactory; + } + + [HttpGet("search")] + [MapToApiVersion("1.0")] + [ProducesResponseType(typeof(PagedModel), StatusCodes.Status200OK)] + public async Task Search(string query, int skip = 0, int take = 100) + { + // FIXME: use Examine and handle user start nodes + PagedModel searchResult = _entitySearchService.Search(UmbracoObjectTypes.Media, query, skip, take); + var result = new PagedModel + { + Items = searchResult.Items.OfType().Select(_mediaPresentationFactory.CreateItemResponseModel), + Total = searchResult.Total + }; + + return await Task.FromResult(Ok(result)); + } +} diff --git a/src/Umbraco.Cms.Api.Management/Controllers/MediaType/Item/SearchMediaTypeItemController.cs b/src/Umbraco.Cms.Api.Management/Controllers/MediaType/Item/SearchMediaTypeItemController.cs new file mode 100644 index 0000000000..3b7628c51c --- /dev/null +++ b/src/Umbraco.Cms.Api.Management/Controllers/MediaType/Item/SearchMediaTypeItemController.cs @@ -0,0 +1,47 @@ +using Asp.Versioning; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Umbraco.Cms.Api.Management.ViewModels.MediaType.Item; +using Umbraco.Cms.Core.Mapping; +using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Models.Entities; +using Umbraco.Cms.Core.Services; +using Umbraco.Extensions; + +namespace Umbraco.Cms.Api.Management.Controllers.MediaType.Item; + +[ApiVersion("1.0")] +public class SearchMediaTypeItemController : MediaTypeItemControllerBase +{ + private readonly IEntitySearchService _entitySearchService; + private readonly IMediaTypeService _mediaTypeService; + private readonly IUmbracoMapper _mapper; + + public SearchMediaTypeItemController(IEntitySearchService entitySearchService, IMediaTypeService mediaTypeService, IUmbracoMapper mapper) + { + _entitySearchService = entitySearchService; + _mediaTypeService = mediaTypeService; + _mapper = mapper; + } + + [HttpGet("search")] + [MapToApiVersion("1.0")] + [ProducesResponseType(typeof(PagedModel), StatusCodes.Status200OK)] + public async Task Search(string query, int skip = 0, int take = 100) + { + PagedModel searchResult = _entitySearchService.Search(UmbracoObjectTypes.MediaType, query, skip, take); + if (searchResult.Items.Any() is false) + { + return await Task.FromResult(Ok(new PagedModel { Total = searchResult.Total })); + } + + IEnumerable mediaTypes = _mediaTypeService.GetAll(searchResult.Items.Select(item => item.Key).ToArray().EmptyNull()); + var result = new PagedModel + { + Items = _mapper.MapEnumerable(mediaTypes), + Total = searchResult.Total + }; + + return Ok(result); + } +} diff --git a/src/Umbraco.Cms.Api.Management/Controllers/Member/Item/SearchMemberItemController.cs b/src/Umbraco.Cms.Api.Management/Controllers/Member/Item/SearchMemberItemController.cs new file mode 100644 index 0000000000..fe58481816 --- /dev/null +++ b/src/Umbraco.Cms.Api.Management/Controllers/Member/Item/SearchMemberItemController.cs @@ -0,0 +1,39 @@ +using Asp.Versioning; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Umbraco.Cms.Api.Management.Factories; +using Umbraco.Cms.Api.Management.ViewModels.Member.Item; +using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Models.Entities; +using Umbraco.Cms.Core.Services; + +namespace Umbraco.Cms.Api.Management.Controllers.Member.Item; + +[ApiVersion("1.0")] +public class SearchMemberItemController : MemberItemControllerBase +{ + private readonly IEntitySearchService _entitySearchService; + private readonly IMemberPresentationFactory _memberPresentationFactory; + + public SearchMemberItemController(IEntitySearchService entitySearchService, IMemberPresentationFactory memberPresentationFactory) + { + _entitySearchService = entitySearchService; + _memberPresentationFactory = memberPresentationFactory; + } + + [HttpGet("search")] + [MapToApiVersion("1.0")] + [ProducesResponseType(typeof(PagedModel), StatusCodes.Status200OK)] + public async Task Search(string query, int skip = 0, int take = 100) + { + // FIXME: use Examine + PagedModel searchResult = _entitySearchService.Search(UmbracoObjectTypes.Member, query, skip, take); + var result = new PagedModel + { + Items = searchResult.Items.OfType().Select(_memberPresentationFactory.CreateItemResponseModel), + Total = searchResult.Total + }; + + return await Task.FromResult(Ok(result)); + } +} diff --git a/src/Umbraco.Cms.Api.Management/Controllers/MemberType/Item/SearchMemberTypeItemController.cs b/src/Umbraco.Cms.Api.Management/Controllers/MemberType/Item/SearchMemberTypeItemController.cs new file mode 100644 index 0000000000..abf9946b76 --- /dev/null +++ b/src/Umbraco.Cms.Api.Management/Controllers/MemberType/Item/SearchMemberTypeItemController.cs @@ -0,0 +1,46 @@ +using Asp.Versioning; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Umbraco.Cms.Api.Management.ViewModels.MemberType.Item; +using Umbraco.Cms.Core.Mapping; +using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Models.Entities; +using Umbraco.Cms.Core.Services; + +namespace Umbraco.Cms.Api.Management.Controllers.MemberType.Item; + +[ApiVersion("1.0")] +public class SearchMemberTypeItemController : MemberTypeItemControllerBase +{ + private readonly IEntitySearchService _entitySearchService; + private readonly IMemberTypeService _memberTypeService; + private readonly IUmbracoMapper _mapper; + + public SearchMemberTypeItemController(IEntitySearchService entitySearchService, IMemberTypeService memberTypeService, IUmbracoMapper mapper) + { + _entitySearchService = entitySearchService; + _memberTypeService = memberTypeService; + _mapper = mapper; + } + + [HttpGet("search")] + [MapToApiVersion("1.0")] + [ProducesResponseType(typeof(PagedModel), StatusCodes.Status200OK)] + public async Task Search(string query, int skip = 0, int take = 100) + { + PagedModel searchResult = _entitySearchService.Search(UmbracoObjectTypes.MemberType, query, skip, take); + if (searchResult.Items.Any() is false) + { + return await Task.FromResult(Ok(new PagedModel { Total = searchResult.Total })); + } + + IEnumerable memberTypes = _memberTypeService.GetAll(searchResult.Items.Select(item => item.Key).ToArray()); + var result = new PagedModel + { + Items = _mapper.MapEnumerable(memberTypes), + Total = searchResult.Total + }; + + return Ok(result); + } +} diff --git a/src/Umbraco.Cms.Api.Management/Controllers/Template/Item/SearchTemplateItemController.cs b/src/Umbraco.Cms.Api.Management/Controllers/Template/Item/SearchTemplateItemController.cs new file mode 100644 index 0000000000..c577cfc4da --- /dev/null +++ b/src/Umbraco.Cms.Api.Management/Controllers/Template/Item/SearchTemplateItemController.cs @@ -0,0 +1,46 @@ +using Asp.Versioning; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Umbraco.Cms.Api.Management.ViewModels.Template.Item; +using Umbraco.Cms.Core.Mapping; +using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Models.Entities; +using Umbraco.Cms.Core.Services; + +namespace Umbraco.Cms.Api.Management.Controllers.Template.Item; + +[ApiVersion("1.0")] +public class SearchTemplateItemController : TemplateItemControllerBase +{ + private readonly IEntitySearchService _entitySearchService; + private readonly ITemplateService _templateService; + private readonly IUmbracoMapper _mapper; + + public SearchTemplateItemController(IEntitySearchService entitySearchService, ITemplateService templateService, IUmbracoMapper mapper) + { + _entitySearchService = entitySearchService; + _templateService = templateService; + _mapper = mapper; + } + + [HttpGet("search")] + [MapToApiVersion("1.0")] + [ProducesResponseType(typeof(PagedModel), StatusCodes.Status200OK)] + public async Task Search(string query, int skip = 0, int take = 100) + { + PagedModel searchResult = _entitySearchService.Search(UmbracoObjectTypes.Template, query, skip, take); + if (searchResult.Items.Any() is false) + { + return Ok(new PagedModel { Total = searchResult.Total }); + } + + IEnumerable templates = await _templateService.GetAllAsync(searchResult.Items.Select(item => item.Key).ToArray()); + var result = new PagedModel + { + Items = _mapper.MapEnumerable(templates), + Total = searchResult.Total + }; + + return Ok(result); + } +} diff --git a/src/Umbraco.Cms.Api.Management/OpenApi.json b/src/Umbraco.Cms.Api.Management/OpenApi.json index e49e86cc1e..cda03adb95 100644 --- a/src/Umbraco.Cms.Api.Management/OpenApi.json +++ b/src/Umbraco.Cms.Api.Management/OpenApi.json @@ -1818,6 +1818,71 @@ ] } }, + "/umbraco/management/api/v1/item/data-type/search": { + "get": { + "tags": [ + "Data Type" + ], + "operationId": "GetItemDataTypeSearch", + "parameters": [ + { + "name": "query", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "skip", + "in": "query", + "schema": { + "type": "integer", + "format": "int32", + "default": 0 + } + }, + { + "name": "take", + "in": "query", + "schema": { + "type": "integer", + "format": "int32", + "default": 100 + } + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PagedModelDataTypeItemResponseModel" + } + }, + "text/json": { + "schema": { + "$ref": "#/components/schemas/PagedModelDataTypeItemResponseModel" + } + }, + "text/plain": { + "schema": { + "$ref": "#/components/schemas/PagedModelDataTypeItemResponseModel" + } + } + } + }, + "401": { + "description": "The resource is protected and requires an authentication token" + } + }, + "security": [ + { + "Backoffice User": [ ] + } + ] + } + }, "/umbraco/management/api/v1/tree/data-type/ancestors": { "get": { "tags": [ @@ -5107,6 +5172,71 @@ ] } }, + "/umbraco/management/api/v1/item/document-type/search": { + "get": { + "tags": [ + "Document Type" + ], + "operationId": "GetItemDocumentTypeSearch", + "parameters": [ + { + "name": "query", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "skip", + "in": "query", + "schema": { + "type": "integer", + "format": "int32", + "default": 0 + } + }, + { + "name": "take", + "in": "query", + "schema": { + "type": "integer", + "format": "int32", + "default": 100 + } + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PagedModelDocumentTypeItemResponseModel" + } + }, + "text/json": { + "schema": { + "$ref": "#/components/schemas/PagedModelDocumentTypeItemResponseModel" + } + }, + "text/plain": { + "schema": { + "$ref": "#/components/schemas/PagedModelDocumentTypeItemResponseModel" + } + } + } + }, + "401": { + "description": "The resource is protected and requires an authentication token" + } + }, + "security": [ + { + "Backoffice User": [ ] + } + ] + } + }, "/umbraco/management/api/v1/tree/document-type/ancestors": { "get": { "tags": [ @@ -8415,6 +8545,71 @@ ] } }, + "/umbraco/management/api/v1/item/document/search": { + "get": { + "tags": [ + "Document" + ], + "operationId": "GetItemDocumentSearch", + "parameters": [ + { + "name": "query", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "skip", + "in": "query", + "schema": { + "type": "integer", + "format": "int32", + "default": 0 + } + }, + { + "name": "take", + "in": "query", + "schema": { + "type": "integer", + "format": "int32", + "default": 100 + } + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PagedModelDocumentItemResponseModel" + } + }, + "text/json": { + "schema": { + "$ref": "#/components/schemas/PagedModelDocumentItemResponseModel" + } + }, + "text/plain": { + "schema": { + "$ref": "#/components/schemas/PagedModelDocumentItemResponseModel" + } + } + } + }, + "401": { + "description": "The resource is protected and requires an authentication token" + } + }, + "security": [ + { + "Backoffice User": [ ] + } + ] + } + }, "/umbraco/management/api/v1/recycle-bin/document": { "delete": { "tags": [ @@ -11730,6 +11925,71 @@ ] } }, + "/umbraco/management/api/v1/item/media-type/search": { + "get": { + "tags": [ + "Media Type" + ], + "operationId": "GetItemMediaTypeSearch", + "parameters": [ + { + "name": "query", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "skip", + "in": "query", + "schema": { + "type": "integer", + "format": "int32", + "default": 0 + } + }, + { + "name": "take", + "in": "query", + "schema": { + "type": "integer", + "format": "int32", + "default": 100 + } + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PagedModelMediaTypeItemResponseModel" + } + }, + "text/json": { + "schema": { + "$ref": "#/components/schemas/PagedModelMediaTypeItemResponseModel" + } + }, + "text/plain": { + "schema": { + "$ref": "#/components/schemas/PagedModelMediaTypeItemResponseModel" + } + } + } + }, + "401": { + "description": "The resource is protected and requires an authentication token" + } + }, + "security": [ + { + "Backoffice User": [ ] + } + ] + } + }, "/umbraco/management/api/v1/media-type": { "post": { "tags": [ @@ -13700,6 +13960,71 @@ ] } }, + "/umbraco/management/api/v1/item/media/search": { + "get": { + "tags": [ + "Media" + ], + "operationId": "GetItemMediaSearch", + "parameters": [ + { + "name": "query", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "skip", + "in": "query", + "schema": { + "type": "integer", + "format": "int32", + "default": 0 + } + }, + { + "name": "take", + "in": "query", + "schema": { + "type": "integer", + "format": "int32", + "default": 100 + } + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PagedModelMediaItemResponseModel" + } + }, + "text/json": { + "schema": { + "$ref": "#/components/schemas/PagedModelMediaItemResponseModel" + } + }, + "text/plain": { + "schema": { + "$ref": "#/components/schemas/PagedModelMediaItemResponseModel" + } + } + } + }, + "401": { + "description": "The resource is protected and requires an authentication token" + } + }, + "security": [ + { + "Backoffice User": [ ] + } + ] + } + }, "/umbraco/management/api/v1/media": { "post": { "tags": [ @@ -16669,6 +16994,71 @@ ] } }, + "/umbraco/management/api/v1/item/member-type/search": { + "get": { + "tags": [ + "Member Type" + ], + "operationId": "GetItemMemberTypeSearch", + "parameters": [ + { + "name": "query", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "skip", + "in": "query", + "schema": { + "type": "integer", + "format": "int32", + "default": 0 + } + }, + { + "name": "take", + "in": "query", + "schema": { + "type": "integer", + "format": "int32", + "default": 100 + } + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PagedModelMemberTypeItemResponseModel" + } + }, + "text/json": { + "schema": { + "$ref": "#/components/schemas/PagedModelMemberTypeItemResponseModel" + } + }, + "text/plain": { + "schema": { + "$ref": "#/components/schemas/PagedModelMemberTypeItemResponseModel" + } + } + } + }, + "401": { + "description": "The resource is protected and requires an authentication token" + } + }, + "security": [ + { + "Backoffice User": [ ] + } + ] + } + }, "/umbraco/management/api/v1/member-type": { "post": { "tags": [ @@ -17699,6 +18089,71 @@ ] } }, + "/umbraco/management/api/v1/item/member/search": { + "get": { + "tags": [ + "Member" + ], + "operationId": "GetItemMemberSearch", + "parameters": [ + { + "name": "query", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "skip", + "in": "query", + "schema": { + "type": "integer", + "format": "int32", + "default": 0 + } + }, + { + "name": "take", + "in": "query", + "schema": { + "type": "integer", + "format": "int32", + "default": 100 + } + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PagedModelMemberItemResponseModel" + } + }, + "text/json": { + "schema": { + "$ref": "#/components/schemas/PagedModelMemberItemResponseModel" + } + }, + "text/plain": { + "schema": { + "$ref": "#/components/schemas/PagedModelMemberItemResponseModel" + } + } + } + }, + "401": { + "description": "The resource is protected and requires an authentication token" + } + }, + "security": [ + { + "Backoffice User": [ ] + } + ] + } + }, "/umbraco/management/api/v1/member": { "post": { "tags": [ @@ -25726,6 +26181,71 @@ ] } }, + "/umbraco/management/api/v1/item/template/search": { + "get": { + "tags": [ + "Template" + ], + "operationId": "GetItemTemplateSearch", + "parameters": [ + { + "name": "query", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "skip", + "in": "query", + "schema": { + "type": "integer", + "format": "int32", + "default": 0 + } + }, + { + "name": "take", + "in": "query", + "schema": { + "type": "integer", + "format": "int32", + "default": 100 + } + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PagedModelTemplateItemResponseModel" + } + }, + "text/json": { + "schema": { + "$ref": "#/components/schemas/PagedModelTemplateItemResponseModel" + } + }, + "text/plain": { + "schema": { + "$ref": "#/components/schemas/PagedModelTemplateItemResponseModel" + } + } + } + }, + "401": { + "description": "The resource is protected and requires an authentication token" + } + }, + "security": [ + { + "Backoffice User": [ ] + } + ] + } + }, "/umbraco/management/api/v1/template": { "post": { "tags": [ @@ -38737,6 +39257,198 @@ }, "additionalProperties": false }, + "PagedModelDataTypeItemResponseModel": { + "required": [ + "items", + "total" + ], + "type": "object", + "properties": { + "items": { + "type": "array", + "items": { + "oneOf": [ + { + "$ref": "#/components/schemas/DataTypeItemResponseModel" + } + ] + } + }, + "total": { + "type": "integer", + "format": "int64" + } + }, + "additionalProperties": false + }, + "PagedModelDocumentItemResponseModel": { + "required": [ + "items", + "total" + ], + "type": "object", + "properties": { + "items": { + "type": "array", + "items": { + "oneOf": [ + { + "$ref": "#/components/schemas/DocumentItemResponseModel" + } + ] + } + }, + "total": { + "type": "integer", + "format": "int64" + } + }, + "additionalProperties": false + }, + "PagedModelDocumentTypeItemResponseModel": { + "required": [ + "items", + "total" + ], + "type": "object", + "properties": { + "items": { + "type": "array", + "items": { + "oneOf": [ + { + "$ref": "#/components/schemas/DocumentTypeItemResponseModel" + } + ] + } + }, + "total": { + "type": "integer", + "format": "int64" + } + }, + "additionalProperties": false + }, + "PagedModelMediaItemResponseModel": { + "required": [ + "items", + "total" + ], + "type": "object", + "properties": { + "items": { + "type": "array", + "items": { + "oneOf": [ + { + "$ref": "#/components/schemas/MediaItemResponseModel" + } + ] + } + }, + "total": { + "type": "integer", + "format": "int64" + } + }, + "additionalProperties": false + }, + "PagedModelMediaTypeItemResponseModel": { + "required": [ + "items", + "total" + ], + "type": "object", + "properties": { + "items": { + "type": "array", + "items": { + "oneOf": [ + { + "$ref": "#/components/schemas/MediaTypeItemResponseModel" + } + ] + } + }, + "total": { + "type": "integer", + "format": "int64" + } + }, + "additionalProperties": false + }, + "PagedModelMemberItemResponseModel": { + "required": [ + "items", + "total" + ], + "type": "object", + "properties": { + "items": { + "type": "array", + "items": { + "oneOf": [ + { + "$ref": "#/components/schemas/MemberItemResponseModel" + } + ] + } + }, + "total": { + "type": "integer", + "format": "int64" + } + }, + "additionalProperties": false + }, + "PagedModelMemberTypeItemResponseModel": { + "required": [ + "items", + "total" + ], + "type": "object", + "properties": { + "items": { + "type": "array", + "items": { + "oneOf": [ + { + "$ref": "#/components/schemas/MemberTypeItemResponseModel" + } + ] + } + }, + "total": { + "type": "integer", + "format": "int64" + } + }, + "additionalProperties": false + }, + "PagedModelTemplateItemResponseModel": { + "required": [ + "items", + "total" + ], + "type": "object", + "properties": { + "items": { + "type": "array", + "items": { + "oneOf": [ + { + "$ref": "#/components/schemas/TemplateItemResponseModel" + } + ] + } + }, + "total": { + "type": "integer", + "format": "int64" + } + }, + "additionalProperties": false + }, "PagedNamedEntityTreeItemResponseModel": { "required": [ "items", @@ -42318,4 +43030,4 @@ } } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Services/IEntitySearchService.cs b/src/Umbraco.Core/Services/IEntitySearchService.cs new file mode 100644 index 0000000000..b10dbbaa4d --- /dev/null +++ b/src/Umbraco.Core/Services/IEntitySearchService.cs @@ -0,0 +1,9 @@ +using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Models.Entities; + +namespace Umbraco.Cms.Core.Services; + +public interface IEntitySearchService +{ + PagedModel Search(UmbracoObjectTypes objectType, string query, int skip = 0, int take = 100); +} diff --git a/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.Services.cs b/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.Services.cs index 75249ce1db..a30fb5c3fd 100644 --- a/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.Services.cs +++ b/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.Services.cs @@ -62,6 +62,7 @@ public static partial class UmbracoBuilderExtensions builder.Services.AddTransient(); builder.Services.AddUnique(); builder.Services.AddUnique(); + builder.Services.AddUnique(); return builder; } diff --git a/src/Umbraco.Infrastructure/Services/Implement/EntitySearchService.cs b/src/Umbraco.Infrastructure/Services/Implement/EntitySearchService.cs new file mode 100644 index 0000000000..ff3a0c5341 --- /dev/null +++ b/src/Umbraco.Infrastructure/Services/Implement/EntitySearchService.cs @@ -0,0 +1,46 @@ +using Umbraco.Cms.Core; +using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Models.Entities; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Infrastructure.Persistence; +using Umbraco.Cms.Infrastructure.Persistence.Dtos; +using Umbraco.Extensions; + +namespace Umbraco.Cms.Infrastructure.Services.Implement; + +internal sealed class EntitySearchService : IEntitySearchService +{ + private readonly IEntityService _entityService; + private readonly ISqlContext _sqlContext; + + public EntitySearchService(IEntityService entityService, ISqlContext sqlContext) + { + _entityService = entityService; + _sqlContext = sqlContext; + } + + public PagedModel Search(UmbracoObjectTypes objectType, string query, int skip = 0, int take = 100) + { + PaginationHelper.ConvertSkipTakeToPaging(skip, take, out long pageNumber, out int pageSize); + + // if the query is a GUID, search for that explicitly + Guid.TryParse(query, out Guid guidQuery); + + IEntitySlim[] entities = _entityService + .GetPagedDescendants( + objectType, + pageNumber, + pageSize, + out long totalRecords, + _sqlContext.Query() + .Where(x => x.Name!.Contains(query) || x.Key == guidQuery), + Ordering.By(nameof(NodeDto.Text).ToFirstLowerInvariant())) + .ToArray(); + + return new PagedModel + { + Items = entities, + Total = totalRecords + }; + } +}