diff --git a/src/Umbraco.Cms.Api.Management/Controllers/Segment/AllSegmentController.cs b/src/Umbraco.Cms.Api.Management/Controllers/Segment/AllSegmentController.cs new file mode 100644 index 0000000000..48542c6b31 --- /dev/null +++ b/src/Umbraco.Cms.Api.Management/Controllers/Segment/AllSegmentController.cs @@ -0,0 +1,50 @@ +using Asp.Versioning; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Umbraco.Cms.Api.Common.ViewModels.Pagination; +using Umbraco.Cms.Api.Management.ViewModels.Segment; +using Umbraco.Cms.Core; +using Umbraco.Cms.Core.Mapping; +using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Core.Services.OperationStatus; +using Umbraco.Cms.Web.Common.Authorization; + +namespace Umbraco.Cms.Api.Management.Controllers.Segment; + +[ApiVersion("1.0")] +[Authorize(Policy = AuthorizationPolicies.SectionAccessSettings)] +public class AllSegmentController : SegmentControllerBase +{ + private readonly ISegmentService _segmentService; + private readonly IUmbracoMapper _umbracoMapper; + + public AllSegmentController(ISegmentService segmentService, IUmbracoMapper umbracoMapper) + { + _segmentService = segmentService; + _umbracoMapper = umbracoMapper; + } + + [HttpGet] + [MapToApiVersion("1.0")] + [ProducesResponseType(typeof(PagedViewModel), StatusCodes.Status200OK)] + [ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status400BadRequest)] + public async Task GetAll(CancellationToken cancellationToken, int skip = 0, int take = 100) + { + Attempt?, SegmentOperationStatus> pagedAttempt = await _segmentService.GetPagedSegmentsAsync(skip, take); + + if (pagedAttempt.Success is false) + { + return MapFailure(pagedAttempt.Status); + } + + var viewModel = new PagedViewModel + { + Items = _umbracoMapper.MapEnumerable(pagedAttempt.Result!.Items), + Total = pagedAttempt.Result!.Total, + }; + + return Ok(viewModel); + } +} diff --git a/src/Umbraco.Cms.Api.Management/Controllers/Segment/SegmentControllerBase.cs b/src/Umbraco.Cms.Api.Management/Controllers/Segment/SegmentControllerBase.cs new file mode 100644 index 0000000000..9149392ccd --- /dev/null +++ b/src/Umbraco.Cms.Api.Management/Controllers/Segment/SegmentControllerBase.cs @@ -0,0 +1,19 @@ +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Umbraco.Cms.Api.Management.Routing; +using Umbraco.Cms.Core.Services.OperationStatus; + +namespace Umbraco.Cms.Api.Management.Controllers.Segment; + +[VersionedApiBackOfficeRoute("segment")] +[ApiExplorerSettings(GroupName = "Segment")] +public abstract class SegmentControllerBase : ManagementApiControllerBase +{ + protected IActionResult MapFailure(SegmentOperationStatus status) + => OperationStatusResult(status, problemDetailsBuilder => status switch + { + _ => StatusCode(StatusCodes.Status500InternalServerError, problemDetailsBuilder + .WithTitle("Unknown segment operation status.") + .Build()), + }); +} diff --git a/src/Umbraco.Cms.Api.Management/DependencyInjection/SegmentBuilderExtensions.cs b/src/Umbraco.Cms.Api.Management/DependencyInjection/SegmentBuilderExtensions.cs new file mode 100644 index 0000000000..17b9f2ae99 --- /dev/null +++ b/src/Umbraco.Cms.Api.Management/DependencyInjection/SegmentBuilderExtensions.cs @@ -0,0 +1,15 @@ +using Umbraco.Cms.Api.Management.Mapping.Segment; +using Umbraco.Cms.Core.DependencyInjection; +using Umbraco.Cms.Core.Mapping; + +namespace Umbraco.Cms.Api.Management.DependencyInjection; + +internal static class SegmentBuilderExtensions +{ + internal static IUmbracoBuilder AddSegment(this IUmbracoBuilder builder) + { + builder.WithCollectionBuilder().Add(); + + return builder; + } +} diff --git a/src/Umbraco.Cms.Api.Management/DependencyInjection/UmbracoBuilderExtensions.cs b/src/Umbraco.Cms.Api.Management/DependencyInjection/UmbracoBuilderExtensions.cs index d6dabeb0c3..58a3434e6c 100644 --- a/src/Umbraco.Cms.Api.Management/DependencyInjection/UmbracoBuilderExtensions.cs +++ b/src/Umbraco.Cms.Api.Management/DependencyInjection/UmbracoBuilderExtensions.cs @@ -67,7 +67,8 @@ public static partial class UmbracoBuilderExtensions .AddPreview() .AddPasswordConfiguration() .AddSupplemenataryLocalizedTextFileSources() - .AddUserData(); + .AddUserData() + .AddSegment(); services .ConfigureOptions() diff --git a/src/Umbraco.Cms.Api.Management/Mapping/Segment/SegmentMapDefinition.cs b/src/Umbraco.Cms.Api.Management/Mapping/Segment/SegmentMapDefinition.cs new file mode 100644 index 0000000000..903d460119 --- /dev/null +++ b/src/Umbraco.Cms.Api.Management/Mapping/Segment/SegmentMapDefinition.cs @@ -0,0 +1,16 @@ +using Umbraco.Cms.Api.Management.ViewModels.Segment; +using Umbraco.Cms.Core.Mapping; + +namespace Umbraco.Cms.Api.Management.Mapping.Segment; + +public class SegmentMapDefinition : IMapDefinition +{ + public void DefineMaps(IUmbracoMapper mapper) => mapper.Define((_, _) => new SegmentResponseModel { Name = string.Empty, Alias = string.Empty }, Map); + + // Umbraco.Code.MapAll + private static void Map(Core.Models.Segment source, SegmentResponseModel target, MapperContext context) + { + target.Name = source.Name; + target.Alias = source.Alias; + } +} diff --git a/src/Umbraco.Cms.Api.Management/OpenApi.json b/src/Umbraco.Cms.Api.Management/OpenApi.json index 676bc29b5d..c16cf7240c 100644 --- a/src/Umbraco.Cms.Api.Management/OpenApi.json +++ b/src/Umbraco.Cms.Api.Management/OpenApi.json @@ -23974,6 +23974,75 @@ } } }, + "/umbraco/management/api/v1/segment": { + "get": { + "tags": [ + "Segment" + ], + "operationId": "GetSegment", + "parameters": [ + { + "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": { + "oneOf": [ + { + "$ref": "#/components/schemas/PagedSegmentResponseModel" + } + ] + } + } + } + }, + "400": { + "description": "Bad Request", + "content": { + "application/json": { + "schema": { + "oneOf": [ + { + "$ref": "#/components/schemas/ProblemDetails" + } + ] + } + } + } + }, + "401": { + "description": "The resource is protected and requires an authentication token" + }, + "403": { + "description": "The authenticated user do not have access to this resource" + } + }, + "security": [ + { + "Backoffice User": [ ] + } + ] + } + }, "/umbraco/management/api/v1/server/configuration": { "get": { "tags": [ @@ -39861,6 +39930,30 @@ }, "additionalProperties": false }, + "PagedSegmentResponseModel": { + "required": [ + "items", + "total" + ], + "type": "object", + "properties": { + "total": { + "type": "integer", + "format": "int64" + }, + "items": { + "type": "array", + "items": { + "oneOf": [ + { + "$ref": "#/components/schemas/SegmentResponseModel" + } + ] + } + } + }, + "additionalProperties": false + }, "PagedTagResponseModel": { "required": [ "items", @@ -40836,6 +40929,24 @@ }, "additionalProperties": false }, + "SegmentResponseModel": { + "required": [ + "alias", + "name" + ], + "type": "object", + "properties": { + "name": { + "minLength": 1, + "type": "string" + }, + "alias": { + "minLength": 1, + "type": "string" + } + }, + "additionalProperties": false + }, "ServerConfigurationItemResponseModel": { "required": [ "data", @@ -43568,4 +43679,4 @@ } } } -} +} \ No newline at end of file diff --git a/src/Umbraco.Cms.Api.Management/ViewModels/Segment/SegmentResponseModel.cs b/src/Umbraco.Cms.Api.Management/ViewModels/Segment/SegmentResponseModel.cs new file mode 100644 index 0000000000..4932104206 --- /dev/null +++ b/src/Umbraco.Cms.Api.Management/ViewModels/Segment/SegmentResponseModel.cs @@ -0,0 +1,10 @@ +using System.ComponentModel.DataAnnotations; + +namespace Umbraco.Cms.Api.Management.ViewModels.Segment; + +public class SegmentResponseModel +{ + public required string Name { get; set; } = string.Empty; + + public required string Alias { get; set; } = string.Empty; +} diff --git a/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.cs b/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.cs index 5191f03837..1d2c08e790 100644 --- a/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.cs +++ b/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.cs @@ -390,6 +390,9 @@ namespace Umbraco.Cms.Core.DependencyInjection Services.AddSingleton(); Services.AddSingleton(); Services.AddSingleton(); + + // Segments + Services.AddUnique(); } } } diff --git a/src/Umbraco.Core/Models/Segment.cs b/src/Umbraco.Core/Models/Segment.cs new file mode 100644 index 0000000000..117b886d64 --- /dev/null +++ b/src/Umbraco.Core/Models/Segment.cs @@ -0,0 +1,8 @@ +namespace Umbraco.Cms.Core.Models; + +public class Segment +{ + public required string Name { get; set; } + + public required string Alias { get; set; } +} diff --git a/src/Umbraco.Core/Services/ISegmentService.cs b/src/Umbraco.Core/Services/ISegmentService.cs new file mode 100644 index 0000000000..f5af487a73 --- /dev/null +++ b/src/Umbraco.Core/Services/ISegmentService.cs @@ -0,0 +1,9 @@ +using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Services.OperationStatus; + +namespace Umbraco.Cms.Core.Services; + +public interface ISegmentService +{ + Task?, SegmentOperationStatus>> GetPagedSegmentsAsync(int skip = 0, int take = 100); +} diff --git a/src/Umbraco.Core/Services/NoopSegmentService.cs b/src/Umbraco.Core/Services/NoopSegmentService.cs new file mode 100644 index 0000000000..90a3076db4 --- /dev/null +++ b/src/Umbraco.Core/Services/NoopSegmentService.cs @@ -0,0 +1,14 @@ +using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Services.OperationStatus; + +namespace Umbraco.Cms.Core.Services; + +public class NoopSegmentService : ISegmentService +{ + public async Task?, SegmentOperationStatus>> GetPagedSegmentsAsync(int skip = 0, int take = 100) + { + return await Task.FromResult(Attempt.SucceedWithStatus?, SegmentOperationStatus>( + SegmentOperationStatus.Success, + new PagedModel { Total = 0, Items = Enumerable.Empty() })); + } +} diff --git a/src/Umbraco.Core/Services/OperationStatus/SegmentOperationStatus.cs b/src/Umbraco.Core/Services/OperationStatus/SegmentOperationStatus.cs new file mode 100644 index 0000000000..e911fef7e7 --- /dev/null +++ b/src/Umbraco.Core/Services/OperationStatus/SegmentOperationStatus.cs @@ -0,0 +1,6 @@ +namespace Umbraco.Cms.Core.Services.OperationStatus; + +public enum SegmentOperationStatus +{ + Success, +}