Skip to content

Commit

Permalink
Merge branch 'v14/dev' into release/14.0
Browse files Browse the repository at this point in the history
  • Loading branch information
iOvergaard committed Apr 25, 2024
2 parents ca0c90e + f4cc408 commit 4b922c6
Show file tree
Hide file tree
Showing 42 changed files with 334 additions and 195 deletions.
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
namespace Umbraco.Cms.Api.Management.OpenApi;
namespace Umbraco.Cms.Api.Common.OpenApi;

/// <summary>
/// Marker interface that ensure the type have a "$type" discriminator in the open api schema.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,10 @@ protected virtual void SwaggerUiConfiguration(
swaggerUiOptions.SwaggerEndpoint($"{name}/swagger.json", $"{apiInfo.Title}");
}

// Add custom configuration from https://swagger.io/docs/open-source-tools/swagger-ui/usage/configuration/
swaggerUiOptions.ConfigObject.PersistAuthorization = true; // persists authorization data so it would not be lost on browser close/refresh
swaggerUiOptions.ConfigObject.Filter = string.Empty; // Enable the filter with an empty string as default filter.

swaggerUiOptions.OAuthClientId(Constants.OAuthClientIds.Swagger);
swaggerUiOptions.OAuthUsePkce();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,6 @@ namespace Umbraco.Cms.Api.Common.Serialization;
public interface IUmbracoJsonTypeInfoResolver : IJsonTypeInfoResolver
{
IEnumerable<Type> FindSubTypes(Type type);

string? GetTypeDiscriminatorValue(Type type);
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
using System.Collections.Concurrent;
using System.Text.Json;
using System.Text.Json.Serialization;
using System.Text.Json.Serialization.Metadata;
using Umbraco.Cms.Api.Common.OpenApi;
using Umbraco.Cms.Core.Composing;
using Umbraco.Extensions;

namespace Umbraco.Cms.Api.Common.Serialization;

Expand All @@ -15,6 +18,14 @@ public UmbracoJsonTypeInfoResolver(ITypeFinder typeFinder)

public IEnumerable<Type> FindSubTypes(Type type)
{
JsonDerivedTypeAttribute[] explicitJsonDerivedTypes = type
.GetCustomAttributes<JsonDerivedTypeAttribute>(false)
.ToArray();
if (explicitJsonDerivedTypes.Any())
{
return explicitJsonDerivedTypes.Select(a => a.DerivedType);
}

if (type.IsInterface is false)
{
// IMPORTANT: do NOT return an empty enumerable here. it will cause nullability to fail on reference
Expand All @@ -33,6 +44,24 @@ public IEnumerable<Type> FindSubTypes(Type type)
return result;
}

public string? GetTypeDiscriminatorValue(Type type)
{
JsonDerivedTypeAttribute? jsonDerivedTypeAttribute = type
.GetBaseTypes(false)
.WhereNotNull()
.SelectMany(baseType => baseType.GetCustomAttributes<JsonDerivedTypeAttribute>(false))
.FirstOrDefault(attr => attr.DerivedType == type);

if (jsonDerivedTypeAttribute is not null)
{
// IMPORTANT: do NOT perform fallback to type.Name here - it will work for the schema generation,
// but not for the actual serialization, and then it's only going to cause confusion.
return jsonDerivedTypeAttribute.TypeDiscriminator?.ToString();
}

return typeof(IOpenApiDiscriminator).IsAssignableFrom(type) ? type.Name : null;
}

public override JsonTypeInfo GetTypeInfo(Type type, JsonSerializerOptions options)
{
JsonTypeInfo result = base.GetTypeInfo(type, options);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
using Microsoft.Extensions.Options;
using Microsoft.OpenApi.Models;
using Swashbuckle.AspNetCore.SwaggerGen;
using Umbraco.Cms.Api.Common.OpenApi;
using Umbraco.Cms.Api.Common.Serialization;
using Umbraco.Cms.Api.Management.DependencyInjection;
using Umbraco.Cms.Api.Management.OpenApi;
Expand Down Expand Up @@ -34,8 +35,8 @@ public void Configure(SwaggerGenOptions swaggerGenOptions)
swaggerGenOptions.UseOneOfForPolymorphism();

// Ensure all types that implements the IOpenApiDiscriminator have a $type property in the OpenApi schema with the default value (The class name) that is expected by the server
swaggerGenOptions.SelectDiscriminatorNameUsing(type => typeof(IOpenApiDiscriminator).IsAssignableFrom(type) ? "$type" : null);
swaggerGenOptions.SelectDiscriminatorValueUsing(type => typeof(IOpenApiDiscriminator).IsAssignableFrom(type) ? type.Name : null);
swaggerGenOptions.SelectDiscriminatorNameUsing(type => _umbracoJsonTypeInfoResolver.GetTypeDiscriminatorValue(type) is not null ? "$type" : null);
swaggerGenOptions.SelectDiscriminatorValueUsing(_umbracoJsonTypeInfoResolver.GetTypeDiscriminatorValue);


swaggerGenOptions.AddSecurityDefinition(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,11 @@ public async Task<ActionResult> Item(
CancellationToken cancellationToken,
[FromQuery(Name = "id")] HashSet<Guid> ids)
{
if (ids.Count is 0)
{
return Ok(Enumerable.Empty<DataTypeItemResponseModel>());
}

var dataTypes = new List<IDataType>();
foreach (Guid id in ids)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,11 @@ public async Task<ActionResult> Item(
CancellationToken cancellationToken,
[FromQuery(Name = "id")] HashSet<Guid> ids)
{
if (ids.Count is 0)
{
return Ok(Enumerable.Empty<DictionaryItemItemResponseModel>());
}

IEnumerable<IDictionaryItem> dictionaryItems = await _dictionaryItemService.GetManyAsync(ids.ToArray());

List<DictionaryItemItemResponseModel> responseModels = _mapper.MapEnumerable<IDictionaryItem, DictionaryItemItemResponseModel>(dictionaryItems);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,11 @@ public async Task<ActionResult> Item(
CancellationToken cancellationToken,
[FromQuery(Name = "id")] HashSet<Guid> ids)
{
if (ids.Count is 0)
{
return Ok(Enumerable.Empty<DocumentItemResponseModel>());
}

IEnumerable<IDocumentEntitySlim> documents = _entityService
.GetAll(UmbracoObjectTypes.Document, ids.ToArray())
.OfType<IDocumentEntitySlim>();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,11 @@ public async Task<IActionResult> Item(
CancellationToken cancellationToken,
[FromQuery(Name = "id")] HashSet<Guid> ids)
{
if (ids.Count is 0)
{
return Ok(Enumerable.Empty<DocumentTypeItemResponseModel>());
}

IEnumerable<IContentType> contentTypes = _contentTypeService.GetAll(ids);
List<DocumentTypeItemResponseModel> responseModels = _mapper.MapEnumerable<IContentType, DocumentTypeItemResponseModel>(contentTypes);
return await Task.FromResult(Ok(responseModels));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,12 @@
namespace Umbraco.Cms.Api.Management.Controllers.Language.Item;

[ApiVersion("1.0")]
public class ItemsLanguageEntityController : LanguageEntityControllerBase
public class ItemLanguageItemController : LanguageItemControllerBase
{
private readonly ILanguageService _languageService;
private readonly IUmbracoMapper _mapper;

public ItemsLanguageEntityController(ILanguageService languageService, IUmbracoMapper mapper)
public ItemLanguageItemController(ILanguageService languageService, IUmbracoMapper mapper)
{
_languageService = languageService;
_mapper = mapper;
Expand All @@ -23,10 +23,15 @@ public ItemsLanguageEntityController(ILanguageService languageService, IUmbracoM
[HttpGet]
[MapToApiVersion("1.0")]
[ProducesResponseType(typeof(IEnumerable<LanguageItemResponseModel>), StatusCodes.Status200OK)]
public async Task<ActionResult> Items(
public async Task<IActionResult> Item(
CancellationToken cancellationToken,
[FromQuery(Name = "isoCode")] HashSet<string> isoCodes)
{
if (isoCodes.Count is 0)
{
return Ok(Enumerable.Empty<LanguageItemResponseModel>());
}

IEnumerable<ILanguage> languages = await _languageService.GetMultipleAsync(isoCodes);
List<LanguageItemResponseModel> entityResponseModels = _mapper.MapEnumerable<ILanguage, LanguageItemResponseModel>(languages);
return Ok(entityResponseModels);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,6 @@ namespace Umbraco.Cms.Api.Management.Controllers.Language.Item;
[VersionedApiBackOfficeRoute($"{Constants.Web.RoutePath.Item}/{Constants.UdiEntityType.Language}")]
[ApiExplorerSettings(GroupName = nameof(Constants.UdiEntityType.Language))]
[Authorize(Policy = AuthorizationPolicies.TreeAccessLanguages)]
public class LanguageEntityControllerBase : ManagementApiControllerBase
public class LanguageItemControllerBase : ManagementApiControllerBase
{
}
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,11 @@ public async Task<ActionResult> Item(
CancellationToken cancellationToken,
[FromQuery(Name = "id")] HashSet<Guid> ids)
{
if (ids.Count is 0)
{
return Ok(Enumerable.Empty<MediaItemResponseModel>());
}

IEnumerable<IMediaEntitySlim> media = _entityService
.GetAll(UmbracoObjectTypes.Media, ids.ToArray())
.OfType<IMediaEntitySlim>();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,11 @@ public async Task<IActionResult> Item(
CancellationToken cancellationToken,
[FromQuery(Name = "id")] HashSet<Guid> ids)
{
if (ids.Count is 0)
{
return Ok(Enumerable.Empty<MediaTypeItemResponseModel>());
}

IEnumerable<IMediaType> mediaTypes = _mediaTypeService.GetAll(ids);
List<MediaTypeItemResponseModel> responseModels = _mapper.MapEnumerable<IMediaType, MediaTypeItemResponseModel>(mediaTypes);
return await Task.FromResult(Ok(responseModels));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,11 @@ public async Task<IActionResult> Item(
CancellationToken cancellationToken,
[FromQuery(Name = "id")] HashSet<Guid> ids)
{
if (ids.Count is 0)
{
return Ok(Enumerable.Empty<MemberItemResponseModel>());
}

IEnumerable<IMemberEntitySlim> members = _entityService
.GetAll(UmbracoObjectTypes.Member, ids.ToArray())
.OfType<IMemberEntitySlim>();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,9 @@ protected IActionResult MemberEditingOperationStatusResult(MemberEditingOperatio
.WithTitle("Duplicate email detected")
.WithDetail("The supplied email is already in use by another member.")
.Build()),
MemberEditingOperationStatus.CancelledByNotificationHandler => BadRequest(problemDetailsBuilder
.WithTitle("Cancelled")
.Build()),
MemberEditingOperationStatus.Unknown => StatusCode(
StatusCodes.Status500InternalServerError,
problemDetailsBuilder
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,14 +24,14 @@ public CreateMemberGroupController(IMemberGroupService memberGroupService, IUmbr

[HttpPost]
[MapToApiVersion("1.0")]
[ProducesResponseType(typeof(MemberGroupResponseModel), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status201Created)]
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status400BadRequest)]
public async Task<IActionResult> Create(CancellationToken cancellationToken, CreateMemberGroupRequestModel model)
{
IMemberGroup? memberGroup = _mapper.Map<IMemberGroup>(model);
Attempt<IMemberGroup?, MemberGroupOperationStatus> result = await _memberGroupService.CreateAsync(memberGroup!);
return result.Success
? Ok(_mapper.Map<MemberGroupResponseModel>(result.Result))
? CreatedAtId<ByKeyMemberGroupController>(controller => nameof(controller.ByKey), result.Result!.Key)
: MemberGroupOperationStatusResult(result.Status);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,11 @@ public async Task<IActionResult> Item(
CancellationToken cancellationToken,
[FromQuery(Name = "id")] HashSet<Guid> ids)
{
if (ids.Count is 0)
{
return Ok(Enumerable.Empty<MemberGroupItemResponseModel>());
}

IEnumerable<IEntitySlim> memberGroups = _entityService.GetAll(UmbracoObjectTypes.MemberGroup, ids.ToArray());
List<MemberGroupItemResponseModel> responseModel = _mapper.MapEnumerable<IEntitySlim, MemberGroupItemResponseModel>(memberGroups);
return Ok(responseModel);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ public UpdateMemberGroupController(IUmbracoMapper mapper, IMemberGroupService me

[HttpPut($"{{{nameof(id)}:guid}}")]
[MapToApiVersion("1.0")]
[ProducesResponseType(typeof(MemberGroupResponseModel), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status400BadRequest)]
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status404NotFound)]
public async Task<IActionResult> Update(
Expand All @@ -42,7 +42,7 @@ public async Task<IActionResult> Update(

Attempt<IMemberGroup?, MemberGroupOperationStatus> result = await _memberGroupService.UpdateAsync(updated);
return result.Success
? Ok(_mapper.Map<MemberGroupResponseModel>(result.Result))
? Ok()
: MemberGroupOperationStatusResult(result.Status);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,11 @@ public async Task<IActionResult> Item(
CancellationToken cancellationToken,
[FromQuery(Name = "id")] HashSet<Guid> ids)
{
if (ids.Count is 0)
{
return Ok(Enumerable.Empty<MemberTypeItemResponseModel>());
}

IEnumerable<IMemberType> memberTypes = _memberTypeService.GetAll(ids);
List<MemberTypeItemResponseModel> responseModels = _mapper.MapEnumerable<IMemberType, MemberTypeItemResponseModel>(memberTypes);
return Ok(responseModels);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,11 @@ public async Task<IActionResult> Item(
CancellationToken cancellationToken,
[FromQuery(Name = "path")] HashSet<string> paths)
{
if (paths.Count is 0)
{
return Ok(Enumerable.Empty<PartialViewItemResponseModel>());
}

paths = paths.Select(path => path.VirtualPathToSystemPath()).ToHashSet();
IEnumerable<PartialViewItemResponseModel> responseModels = _fileItemPresentationFactory.CreatePartialViewItemResponseModels(paths);
return await Task.FromResult(Ok(responseModels));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,11 @@ public async Task<IActionResult> Item(
CancellationToken cancellationToken,
[FromQuery(Name = "id")] HashSet<Guid> ids)
{
if (ids.Count is 0)
{
return Ok(Enumerable.Empty<RelationTypeItemResponseModel>());
}

// relation service does not allow fetching a collection of relation types by their ids; instead it relies
// heavily on caching, which means this is as fast as it gets - even if it looks less than performant
IRelationType[] relationTypes = _relationService
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,11 @@ public async Task<IActionResult> Item(
CancellationToken cancellationToken,
[FromQuery(Name = "path")] HashSet<string> paths)
{
if (paths.Count is 0)
{
return Ok(Enumerable.Empty<ScriptItemResponseModel>());
}

paths = paths.Select(path => path.VirtualPathToSystemPath()).ToHashSet();
IEnumerable<ScriptItemResponseModel> responseModels = _fileItemPresentationFactory.CreateScriptItemResponseModels(paths);
return await Task.FromResult(Ok(responseModels));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,11 @@ public async Task<IActionResult> Item(
CancellationToken cancellationToken,
[FromQuery(Name = "path")] HashSet<string> paths)
{
if (paths.Count is 0)
{
return Ok(Enumerable.Empty<StaticFileItemResponseModel>());
}

paths = paths.Select(path => path.VirtualPathToSystemPath()).ToHashSet();
IEnumerable<StaticFileItemResponseModel> responseModels = _fileItemPresentationFactory.CreateStaticFileItemResponseModels(paths);
return await Task.FromResult(Ok(responseModels));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,11 @@ public async Task<IActionResult> Item(
CancellationToken cancellationToken,
[FromQuery(Name = "path")] HashSet<string> paths)
{
if (paths.Count is 0)
{
return Ok(Enumerable.Empty<StylesheetItemResponseModel>());
}

paths = paths.Select(path => path.VirtualPathToSystemPath()).ToHashSet();
IEnumerable<StylesheetItemResponseModel> responseModels = _fileItemPresentationFactory.CreateStylesheetItemResponseModels(paths);
return await Task.FromResult(Ok(responseModels));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,11 @@ public async Task<IActionResult> Item(
CancellationToken cancellationToken,
[FromQuery(Name = "id")] HashSet<Guid> ids)
{
if (ids.Count is 0)
{
return Ok(Enumerable.Empty<TemplateItemResponseModel>());
}

// This is far from ideal, that we pick out the entire model, however, we must do this to get the alias.
// This is (for one) needed for when specifying master template, since alias + .cshtml
IEnumerable<ITemplate> templates = await _templateService.GetAllAsync(ids.ToArray());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,11 @@ public ItemUserItemController(IUserService userService, IUmbracoMapper mapper)
[ProducesResponseType(typeof(IEnumerable<UserItemResponseModel>), StatusCodes.Status200OK)]
public async Task<IActionResult> Item(CancellationToken cancellationToken, [FromQuery(Name = "id")] HashSet<Guid> ids)
{
if (ids.Count is 0)
{
return Ok(Enumerable.Empty<UserItemResponseModel>());
}

IEnumerable<IUser> users = await _userService.GetAsync(ids.ToArray());
List<UserItemResponseModel> responseModels = _mapper.MapEnumerable<IUser, UserItemResponseModel>(users);
return Ok(responseModels);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ public UpdateUserDataController(
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(typeof(UserDataOperationStatus), StatusCodes.Status400BadRequest)]
[ProducesResponseType(typeof(UserDataOperationStatus), StatusCodes.Status404NotFound)]
public async Task<IActionResult> Create(CancellationToken cancellationToken, UpdateUserDataRequestModel model)
public async Task<IActionResult> Update(CancellationToken cancellationToken, UpdateUserDataRequestModel model)
{
Guid currentUserKey = CurrentUserKey(_backOfficeSecurityAccessor);

Expand All @@ -43,7 +43,7 @@ public async Task<IActionResult> Create(CancellationToken cancellationToken, Upd
Attempt<IUserData, UserDataOperationStatus> attempt = await _userDataService.UpdateAsync(userData);

return attempt.Success
? Ok(attempt.Result)
? Ok()
: UserDataOperationStatusResult(attempt.Status);
}
}
Loading

0 comments on commit 4b922c6

Please sign in to comment.