V14: Member collection view/filter endpoint (#15727)

* Adding missing members policy

* Adding a member filter endpoint

* Adding response types to user filter endpoint

* Adding create multiple to factory

* Updating OpenApi.json
This commit is contained in:
Elitsa Marinovska
2024-02-18 16:06:41 +01:00
committed by GitHub
parent 638446bd43
commit 5b1fab1165
6 changed files with 307 additions and 4 deletions

View File

@@ -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<MemberResponseModel>), StatusCodes.Status200OK)]
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status400BadRequest)]
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status404NotFound)]
public async Task<IActionResult> 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<IMember> members = await Task.FromResult(_memberService.GetAll(
pageNumber,
pageSize,
out var totalRecords,
orderBy,
orderDirection,
memberTypeAlias,
filter ?? string.Empty));
var pageViewModel = new PagedViewModel<MemberResponseModel>
{
Items = await _memberPresentationFactory.CreateMultipleAsync(members),
Total = totalRecords,
};
return Ok(pageViewModel);
}
}

View File

@@ -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
/// <returns>A paged result of the users matching the query.</returns>
[HttpGet("filter")]
[MapToApiVersion("1.0")]
[ProducesResponseType(typeof(PagedViewModel<UserResponseModel>), StatusCodes.Status200OK)]
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status400BadRequest)]
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status404NotFound)]
public async Task<IActionResult> Filter(
int skip = 0,
int take = 100,
@@ -69,7 +73,7 @@ public class FilterUserController : UserControllerBase
var responseModel = new PagedViewModel<UserResponseModel>
{
Total = filterAttempt.Result.Total,
Items = filterAttempt.Result.Items.Select(_userPresentationFactory.CreateResponseModel).ToArray()
Items = filterAttempt.Result.Items.Select(_userPresentationFactory.CreateResponseModel).ToArray(),
};
return Ok(responseModel);

View File

@@ -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);

View File

@@ -9,7 +9,9 @@ namespace Umbraco.Cms.Api.Management.Factories;
public interface IMemberPresentationFactory
{
Task<MemberResponseModel> CreateResponseModelAsync(IMember Member);
Task<MemberResponseModel> CreateResponseModelAsync(IMember member);
Task<IEnumerable<MemberResponseModel>> CreateMultipleAsync(IEnumerable<IMember> members);
MemberItemResponseModel CreateItemResponseModel(IMemberEntitySlim entity);

View File

@@ -40,6 +40,17 @@ internal sealed class MemberPresentationFactory
return responseModel;
}
public async Task<IEnumerable<MemberResponseModel>> CreateMultipleAsync(IEnumerable<IMember> members)
{
var memberResponseModels = new List<MemberResponseModel>();
foreach (IMember member in members)
{
memberResponseModels.Add(await CreateResponseModelAsync(member));
}
return memberResponseModels;
}
public MemberItemResponseModel CreateItemResponseModel(IMemberEntitySlim entity)
{
var responseModel = new MemberItemResponseModel

View File

@@ -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",