diff --git a/src/Umbraco.Cms.Api.Management/Controllers/Member/FilterMemberController.cs b/src/Umbraco.Cms.Api.Management/Controllers/Member/FilterMemberController.cs new file mode 100644 index 0000000000..b4936dee5e --- /dev/null +++ b/src/Umbraco.Cms.Api.Management/Controllers/Member/FilterMemberController.cs @@ -0,0 +1,76 @@ +using Asp.Versioning; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Umbraco.Cms.Api.Common.ViewModels.Pagination; +using Umbraco.Cms.Api.Management.Factories; +using Umbraco.Cms.Api.Management.ViewModels.Member; +using Umbraco.Cms.Core; +using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Core.Services.OperationStatus; + +namespace Umbraco.Cms.Api.Management.Controllers.Member; + +[ApiVersion("1.0")] +public class FilterMemberController : MemberControllerBase +{ + private readonly IMemberTypeService _memberTypeService; + private readonly IMemberService _memberService; + private readonly IMemberPresentationFactory _memberPresentationFactory; + + public FilterMemberController( + IMemberTypeService memberTypeService, + IMemberService memberService, + IMemberPresentationFactory memberPresentationFactory) + { + _memberTypeService = memberTypeService; + _memberService = memberService; + _memberPresentationFactory = memberPresentationFactory; + } + + [HttpGet("filter")] + [MapToApiVersion("1.0")] + [ProducesResponseType(typeof(PagedViewModel), StatusCodes.Status200OK)] + [ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status400BadRequest)] + [ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status404NotFound)] + public async Task Filter( + Guid? memberTypeId = null, + string orderBy = "username", + Direction orderDirection = Direction.Ascending, + string? filter = null, + int skip = 0, + int take = 100) + { + // TODO: Move to service once we have FilterAsync method for members + string? memberTypeAlias = null; + if (memberTypeId.HasValue) + { + IMemberType? memberType = await _memberTypeService.GetAsync(memberTypeId.Value); + if (memberType == null) + { + return MemberEditingOperationStatusResult(MemberEditingOperationStatus.MemberTypeNotFound); + } + + memberTypeAlias = memberType.Alias; + } + + PaginationHelper.ConvertSkipTakeToPaging(skip, take, out var pageNumber, out var pageSize); + + IEnumerable members = await Task.FromResult(_memberService.GetAll( + pageNumber, + pageSize, + out var totalRecords, + orderBy, + orderDirection, + memberTypeAlias, + filter ?? string.Empty)); + + var pageViewModel = new PagedViewModel + { + Items = await _memberPresentationFactory.CreateMultipleAsync(members), + Total = totalRecords, + }; + + return Ok(pageViewModel); + } +} diff --git a/src/Umbraco.Cms.Api.Management/Controllers/User/FilterUserController.cs b/src/Umbraco.Cms.Api.Management/Controllers/User/FilterUserController.cs index 953db161cb..1a81dfdf93 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/User/FilterUserController.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/User/FilterUserController.cs @@ -1,14 +1,15 @@ using Asp.Versioning; +using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Umbraco.Cms.Api.Common.ViewModels.Pagination; using Umbraco.Cms.Api.Management.Factories; using Umbraco.Cms.Api.Management.ViewModels.User; using Umbraco.Cms.Core; +using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Models.Membership; using Umbraco.Cms.Core.Security; using Umbraco.Cms.Core.Services; using Umbraco.Cms.Core.Services.OperationStatus; -using Umbraco.Cms.Core.Models; namespace Umbraco.Cms.Api.Management.Controllers.User; @@ -42,6 +43,9 @@ public class FilterUserController : UserControllerBase /// A paged result of the users matching the query. [HttpGet("filter")] [MapToApiVersion("1.0")] + [ProducesResponseType(typeof(PagedViewModel), StatusCodes.Status200OK)] + [ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status400BadRequest)] + [ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status404NotFound)] public async Task Filter( int skip = 0, int take = 100, @@ -69,7 +73,7 @@ public class FilterUserController : UserControllerBase var responseModel = new PagedViewModel { Total = filterAttempt.Result.Total, - Items = filterAttempt.Result.Items.Select(_userPresentationFactory.CreateResponseModel).ToArray() + Items = filterAttempt.Result.Items.Select(_userPresentationFactory.CreateResponseModel).ToArray(), }; return Ok(responseModel); diff --git a/src/Umbraco.Cms.Api.Management/DependencyInjection/BackOfficeAuthPolicyBuilderExtensions.cs b/src/Umbraco.Cms.Api.Management/DependencyInjection/BackOfficeAuthPolicyBuilderExtensions.cs index 9b6653f11b..b996a426ab 100644 --- a/src/Umbraco.Cms.Api.Management/DependencyInjection/BackOfficeAuthPolicyBuilderExtensions.cs +++ b/src/Umbraco.Cms.Api.Management/DependencyInjection/BackOfficeAuthPolicyBuilderExtensions.cs @@ -77,6 +77,7 @@ internal static class BackOfficeAuthPolicyBuilderExtensions AddPolicy(AuthorizationPolicies.SectionAccessForMemberTree, Constants.Security.AllowedApplicationsClaimType, Constants.Applications.Content, Constants.Applications.Media, Constants.Applications.Members); AddPolicy(AuthorizationPolicies.SectionAccessMedia, Constants.Security.AllowedApplicationsClaimType, Constants.Applications.Media); + AddPolicy(AuthorizationPolicies.SectionAccessMembers, Constants.Security.AllowedApplicationsClaimType, Constants.Applications.Members); AddPolicy(AuthorizationPolicies.SectionAccessPackages, Constants.Security.AllowedApplicationsClaimType, Constants.Applications.Packages); AddPolicy(AuthorizationPolicies.SectionAccessSettings, Constants.Security.AllowedApplicationsClaimType, Constants.Applications.Settings); AddPolicy(AuthorizationPolicies.SectionAccessUsers, Constants.Security.AllowedApplicationsClaimType, Constants.Applications.Users); diff --git a/src/Umbraco.Cms.Api.Management/Factories/IMemberPresentationFactory.cs b/src/Umbraco.Cms.Api.Management/Factories/IMemberPresentationFactory.cs index 68d2132984..90f0e22c55 100644 --- a/src/Umbraco.Cms.Api.Management/Factories/IMemberPresentationFactory.cs +++ b/src/Umbraco.Cms.Api.Management/Factories/IMemberPresentationFactory.cs @@ -9,7 +9,9 @@ namespace Umbraco.Cms.Api.Management.Factories; public interface IMemberPresentationFactory { - Task CreateResponseModelAsync(IMember Member); + Task CreateResponseModelAsync(IMember member); + + Task> CreateMultipleAsync(IEnumerable members); MemberItemResponseModel CreateItemResponseModel(IMemberEntitySlim entity); diff --git a/src/Umbraco.Cms.Api.Management/Factories/MemberPresentationFactory.cs b/src/Umbraco.Cms.Api.Management/Factories/MemberPresentationFactory.cs index 0d40e2bd24..e0d83f333b 100644 --- a/src/Umbraco.Cms.Api.Management/Factories/MemberPresentationFactory.cs +++ b/src/Umbraco.Cms.Api.Management/Factories/MemberPresentationFactory.cs @@ -40,6 +40,17 @@ internal sealed class MemberPresentationFactory return responseModel; } + public async Task> CreateMultipleAsync(IEnumerable members) + { + var memberResponseModels = new List(); + foreach (IMember member in members) + { + memberResponseModels.Add(await CreateResponseModelAsync(member)); + } + + return memberResponseModels; + } + public MemberItemResponseModel CreateItemResponseModel(IMemberEntitySlim entity) { var responseModel = new MemberItemResponseModel diff --git a/src/Umbraco.Cms.Api.Management/OpenApi.json b/src/Umbraco.Cms.Api.Management/OpenApi.json index 611b6d68b3..bbc4110e56 100644 --- a/src/Umbraco.Cms.Api.Management/OpenApi.json +++ b/src/Umbraco.Cms.Api.Management/OpenApi.json @@ -13316,6 +13316,134 @@ ] } }, + "/umbraco/management/api/v1/member/filter": { + "get": { + "tags": [ + "Member" + ], + "operationId": "GetMemberFilter", + "parameters": [ + { + "name": "memberTypeId", + "in": "query", + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "orderBy", + "in": "query", + "schema": { + "type": "string", + "default": "username" + } + }, + { + "name": "orderDirection", + "in": "query", + "schema": { + "$ref": "#/components/schemas/DirectionModel" + } + }, + { + "name": "filter", + "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/PagedMemberResponseModel" + } + }, + "text/json": { + "schema": { + "$ref": "#/components/schemas/PagedMemberResponseModel" + } + }, + "text/plain": { + "schema": { + "$ref": "#/components/schemas/PagedMemberResponseModel" + } + } + } + }, + "400": { + "description": "Bad Request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "text/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "text/plain": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + }, + "404": { + "description": "Not Found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "text/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "text/plain": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + }, + "401": { + "description": "The resource is protected and requires an authentication token" + } + }, + "security": [ + { + "Backoffice User": [ ] + } + ] + } + }, "/umbraco/management/api/v1/member/validate": { "post": { "tags": [ @@ -23729,7 +23857,64 @@ ], "responses": { "200": { - "description": "Success" + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PagedUserResponseModel" + } + }, + "text/json": { + "schema": { + "$ref": "#/components/schemas/PagedUserResponseModel" + } + }, + "text/plain": { + "schema": { + "$ref": "#/components/schemas/PagedUserResponseModel" + } + } + } + }, + "400": { + "description": "Bad Request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "text/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "text/plain": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + }, + "404": { + "description": "Not Found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "text/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "text/plain": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } }, "401": { "description": "The resource is protected and requires an authentication token" @@ -29769,6 +29954,30 @@ }, "additionalProperties": false }, + "PagedMemberResponseModel": { + "required": [ + "items", + "total" + ], + "type": "object", + "properties": { + "total": { + "type": "integer", + "format": "int64" + }, + "items": { + "type": "array", + "items": { + "oneOf": [ + { + "$ref": "#/components/schemas/MemberResponseModel" + } + ] + } + } + }, + "additionalProperties": false + }, "PagedNamedEntityTreeItemResponseModel": { "required": [ "items",