diff --git a/Directory.Packages.props b/Directory.Packages.props
index 416a91cec3..2c9fa306e4 100644
--- a/Directory.Packages.props
+++ b/Directory.Packages.props
@@ -33,7 +33,7 @@
-
+
@@ -74,7 +74,7 @@
-
+
diff --git a/src/Umbraco.Cms.Api.Common/Configuration/ConfigureUmbracoSwaggerGenOptions.cs b/src/Umbraco.Cms.Api.Common/Configuration/ConfigureUmbracoSwaggerGenOptions.cs
index a4cabf51bb..4b65bb12bf 100644
--- a/src/Umbraco.Cms.Api.Common/Configuration/ConfigureUmbracoSwaggerGenOptions.cs
+++ b/src/Umbraco.Cms.Api.Common/Configuration/ConfigureUmbracoSwaggerGenOptions.cs
@@ -4,7 +4,7 @@ using Microsoft.AspNetCore.Mvc.ApiExplorer;
using Microsoft.AspNetCore.Mvc.Controllers;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
-using Microsoft.OpenApi.Models;
+using Microsoft.OpenApi;
using Swashbuckle.AspNetCore.SwaggerGen;
using Umbraco.Cms.Api.Common.OpenApi;
using Umbraco.Extensions;
diff --git a/src/Umbraco.Cms.Api.Common/OpenApi/EnumSchemaFilter.cs b/src/Umbraco.Cms.Api.Common/OpenApi/EnumSchemaFilter.cs
index e2ac5ab870..845adb4990 100644
--- a/src/Umbraco.Cms.Api.Common/OpenApi/EnumSchemaFilter.cs
+++ b/src/Umbraco.Cms.Api.Common/OpenApi/EnumSchemaFilter.cs
@@ -1,25 +1,27 @@
using System.Reflection;
using System.Runtime.Serialization;
-using Microsoft.OpenApi.Any;
-using Microsoft.OpenApi.Models;
+using System.Text.Json.Nodes;
+using Microsoft.OpenApi;
using Swashbuckle.AspNetCore.SwaggerGen;
namespace Umbraco.Cms.Api.Common.OpenApi;
public class EnumSchemaFilter : ISchemaFilter
{
- public void Apply(OpenApiSchema model, SchemaFilterContext context)
+ public void Apply(IOpenApiSchema model, SchemaFilterContext context)
{
- if (context.Type.IsEnum)
+ if (model is not OpenApiSchema schema || context.Type.IsEnum is false)
{
- model.Type = "string";
- model.Format = null;
- model.Enum.Clear();
- foreach (var name in Enum.GetNames(context.Type))
- {
- var actualName = context.Type.GetField(name)?.GetCustomAttribute()?.Value ?? name;
- model.Enum.Add(new OpenApiString(actualName));
- }
+ return;
+ }
+
+ schema.Type = JsonSchemaType.String;
+ schema.Format = null;
+ schema.Enum = new List();
+ foreach (var name in Enum.GetNames(context.Type))
+ {
+ var actualName = context.Type.GetField(name)?.GetCustomAttribute()?.Value ?? name;
+ schema.Enum.Add(actualName);
}
}
}
diff --git a/src/Umbraco.Cms.Api.Common/OpenApi/MimeTypeDocumentFilter.cs b/src/Umbraco.Cms.Api.Common/OpenApi/MimeTypeDocumentFilter.cs
index a756c30f1f..2dfc6a88a4 100644
--- a/src/Umbraco.Cms.Api.Common/OpenApi/MimeTypeDocumentFilter.cs
+++ b/src/Umbraco.Cms.Api.Common/OpenApi/MimeTypeDocumentFilter.cs
@@ -1,4 +1,4 @@
-using Microsoft.OpenApi.Models;
+using Microsoft.OpenApi;
using Swashbuckle.AspNetCore.SwaggerGen;
using Umbraco.Extensions;
@@ -21,25 +21,32 @@ public class MimeTypeDocumentFilter : IDocumentFilter
}
OpenApiOperation[] operations = swaggerDoc.Paths
- .SelectMany(path => path.Value.Operations.Values)
+ .SelectMany(path => path.Value.Operations?.Values ?? Enumerable.Empty())
.ToArray();
- void RemoveUnwantedMimeTypes(IDictionary content)
+ void RemoveUnwantedMimeTypes(IDictionary? content)
{
- if (content.ContainsKey("application/json"))
+ if (content is null || content.ContainsKey("application/json") is false)
{
- content.RemoveAll(r => r.Key != "application/json");
+ return;
}
+ content.RemoveAll(r => r.Key != "application/json");
}
- OpenApiRequestBody[] requestBodies = operations.Select(operation => operation.RequestBody).WhereNotNull().ToArray();
+ OpenApiRequestBody[] requestBodies = operations
+ .Select(operation => operation.RequestBody)
+ .OfType()
+ .ToArray();
foreach (OpenApiRequestBody requestBody in requestBodies)
{
RemoveUnwantedMimeTypes(requestBody.Content);
}
- OpenApiResponse[] responses = operations.SelectMany(operation => operation.Responses.Values).WhereNotNull().ToArray();
+ OpenApiResponse[] responses = operations
+ .SelectMany(operation => operation.Responses?.Values ?? Enumerable.Empty())
+ .OfType()
+ .ToArray();
foreach (OpenApiResponse response in responses)
{
RemoveUnwantedMimeTypes(response.Content);
diff --git a/src/Umbraco.Cms.Api.Common/OpenApi/RemoveSecuritySchemesDocumentFilter.cs b/src/Umbraco.Cms.Api.Common/OpenApi/RemoveSecuritySchemesDocumentFilter.cs
index 8ab2041f7d..c90c9f9c73 100644
--- a/src/Umbraco.Cms.Api.Common/OpenApi/RemoveSecuritySchemesDocumentFilter.cs
+++ b/src/Umbraco.Cms.Api.Common/OpenApi/RemoveSecuritySchemesDocumentFilter.cs
@@ -1,4 +1,4 @@
-using Microsoft.OpenApi.Models;
+using Microsoft.OpenApi;
using Swashbuckle.AspNetCore.SwaggerGen;
namespace Umbraco.Cms.Api.Common.OpenApi;
@@ -20,6 +20,6 @@ public class RemoveSecuritySchemesDocumentFilter : IDocumentFilter
return;
}
- swaggerDoc.Components.SecuritySchemes.Clear();
+ swaggerDoc.Components?.SecuritySchemes?.Clear();
}
}
diff --git a/src/Umbraco.Cms.Api.Common/OpenApi/SwaggerRouteTemplatePipelineFilter.cs b/src/Umbraco.Cms.Api.Common/OpenApi/SwaggerRouteTemplatePipelineFilter.cs
index dba0e6f56d..2c45a36754 100644
--- a/src/Umbraco.Cms.Api.Common/OpenApi/SwaggerRouteTemplatePipelineFilter.cs
+++ b/src/Umbraco.Cms.Api.Common/OpenApi/SwaggerRouteTemplatePipelineFilter.cs
@@ -3,13 +3,12 @@ using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Options;
-using Microsoft.OpenApi.Models;
+using Microsoft.OpenApi;
using Swashbuckle.AspNetCore.SwaggerGen;
using Swashbuckle.AspNetCore.SwaggerUI;
using Umbraco.Cms.Core;
using Umbraco.Cms.Core.Hosting;
using Umbraco.Cms.Web.Common.ApplicationBuilder;
-using Umbraco.Extensions;
using IHostingEnvironment = Umbraco.Cms.Core.Hosting.IHostingEnvironment;
namespace Umbraco.Cms.Api.Common.OpenApi;
diff --git a/src/Umbraco.Cms.Api.Delivery/Configuration/ConfigureUmbracoDeliveryApiSwaggerGenOptions.cs b/src/Umbraco.Cms.Api.Delivery/Configuration/ConfigureUmbracoDeliveryApiSwaggerGenOptions.cs
index 9b87166300..8c7437e4bf 100644
--- a/src/Umbraco.Cms.Api.Delivery/Configuration/ConfigureUmbracoDeliveryApiSwaggerGenOptions.cs
+++ b/src/Umbraco.Cms.Api.Delivery/Configuration/ConfigureUmbracoDeliveryApiSwaggerGenOptions.cs
@@ -1,6 +1,6 @@
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
-using Microsoft.OpenApi.Models;
+using Microsoft.OpenApi;
using Swashbuckle.AspNetCore.SwaggerGen;
using Umbraco.Cms.Api.Common.OpenApi;
using Umbraco.Cms.Api.Delivery.Filters;
diff --git a/src/Umbraco.Cms.Api.Delivery/Configuration/ConfigureUmbracoMemberAuthenticationDeliveryApiSwaggerGenOptions.cs b/src/Umbraco.Cms.Api.Delivery/Configuration/ConfigureUmbracoMemberAuthenticationDeliveryApiSwaggerGenOptions.cs
index 3161105dca..5922768462 100644
--- a/src/Umbraco.Cms.Api.Delivery/Configuration/ConfigureUmbracoMemberAuthenticationDeliveryApiSwaggerGenOptions.cs
+++ b/src/Umbraco.Cms.Api.Delivery/Configuration/ConfigureUmbracoMemberAuthenticationDeliveryApiSwaggerGenOptions.cs
@@ -1,6 +1,6 @@
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
-using Microsoft.OpenApi.Models;
+using Microsoft.OpenApi;
using Swashbuckle.AspNetCore.SwaggerGen;
using Umbraco.Cms.Api.Common.Security;
using Umbraco.Cms.Api.Delivery.Controllers.Content;
@@ -35,23 +35,9 @@ public class ConfigureUmbracoMemberAuthenticationDeliveryApiSwaggerGenOptions :
return;
}
- operation.Security = new List
- {
- new OpenApiSecurityRequirement
- {
- {
- new OpenApiSecurityScheme
- {
- Reference = new OpenApiReference
- {
- Type = ReferenceType.SecurityScheme,
- Id = AuthSchemeName,
- }
- },
- []
- }
- }
- };
+ var schemaRef = new OpenApiSecuritySchemeReference(AuthSchemeName, context.Document);
+ operation.Security ??= new List();
+ operation.Security.Add(new OpenApiSecurityRequirement { [schemaRef] = [] });
}
public void Apply(OpenApiDocument swaggerDoc, DocumentFilterContext context)
@@ -61,6 +47,8 @@ public class ConfigureUmbracoMemberAuthenticationDeliveryApiSwaggerGenOptions :
return;
}
+ swaggerDoc.Components ??= new OpenApiComponents();
+ swaggerDoc.Components.SecuritySchemes ??= new Dictionary();
swaggerDoc.Components.SecuritySchemes.Add(
AuthSchemeName,
new OpenApiSecurityScheme
@@ -74,9 +62,9 @@ public class ConfigureUmbracoMemberAuthenticationDeliveryApiSwaggerGenOptions :
AuthorizationCode = new OpenApiOAuthFlow
{
AuthorizationUrl = new Uri(Paths.MemberApi.AuthorizationEndpoint, UriKind.Relative),
- TokenUrl = new Uri(Paths.MemberApi.TokenEndpoint, UriKind.Relative)
- }
- }
+ TokenUrl = new Uri(Paths.MemberApi.TokenEndpoint, UriKind.Relative),
+ },
+ },
});
}
}
diff --git a/src/Umbraco.Cms.Api.Delivery/Filters/SwaggerContentDocumentationFilter.cs b/src/Umbraco.Cms.Api.Delivery/Filters/SwaggerContentDocumentationFilter.cs
index 014aed28c8..290f6b66dc 100644
--- a/src/Umbraco.Cms.Api.Delivery/Filters/SwaggerContentDocumentationFilter.cs
+++ b/src/Umbraco.Cms.Api.Delivery/Filters/SwaggerContentDocumentationFilter.cs
@@ -1,9 +1,9 @@
-using Microsoft.OpenApi.Any;
-using Microsoft.OpenApi.Models;
+using System.Text.Json.Nodes;
+using Microsoft.OpenApi;
using Swashbuckle.AspNetCore.SwaggerGen;
using Umbraco.Cms.Api.Delivery.Configuration;
-using Umbraco.Cms.Api.Delivery.Controllers;
using Umbraco.Cms.Api.Delivery.Controllers.Content;
+using Umbraco.Cms.Core;
namespace Umbraco.Cms.Api.Delivery.Filters;
@@ -13,7 +13,7 @@ internal sealed class SwaggerContentDocumentationFilter : SwaggerDocumentationFi
protected override void ApplyOperation(OpenApiOperation operation, OperationFilterContext context)
{
- operation.Parameters ??= new List();
+ operation.Parameters ??= new List();
AddExpand(operation, context);
@@ -21,50 +21,50 @@ internal sealed class SwaggerContentDocumentationFilter : SwaggerDocumentationFi
operation.Parameters.Add(new OpenApiParameter
{
- Name = Core.Constants.DeliveryApi.HeaderNames.AcceptLanguage,
+ Name = Constants.DeliveryApi.HeaderNames.AcceptLanguage,
In = ParameterLocation.Header,
Required = false,
Description = "Defines the language to return. Use this when querying language variant content items.",
- Schema = new OpenApiSchema { Type = "string" },
- Examples = new Dictionary
+ Schema = new OpenApiSchema { Type = JsonSchemaType.String },
+ Examples = new Dictionary
{
- { "Default", new OpenApiExample { Value = new OpenApiString(string.Empty) } },
- { "English culture", new OpenApiExample { Value = new OpenApiString("en-us") } }
- }
+ { "Default", new OpenApiExample { Value = string.Empty } },
+ { "English culture", new OpenApiExample { Value = "en-us" } },
+ },
});
operation.Parameters.Add(new OpenApiParameter
{
- Name = Core.Constants.DeliveryApi.HeaderNames.AcceptSegment,
+ Name = Constants.DeliveryApi.HeaderNames.AcceptSegment,
In = ParameterLocation.Header,
Required = false,
Description = "Defines the segment to return. Use this when querying segment variant content items.",
- Schema = new OpenApiSchema { Type = "string" },
- Examples = new Dictionary
+ Schema = new OpenApiSchema { Type = JsonSchemaType.String },
+ Examples = new Dictionary
{
- { "Default", new OpenApiExample { Value = new OpenApiString(string.Empty) } },
- { "Segment One", new OpenApiExample { Value = new OpenApiString("segment-one") } }
- }
+ { "Default", new OpenApiExample { Value = string.Empty } },
+ { "Segment One", new OpenApiExample { Value = "segment-one" } },
+ },
});
AddApiKey(operation);
operation.Parameters.Add(new OpenApiParameter
{
- Name = Core.Constants.DeliveryApi.HeaderNames.Preview,
+ Name = Constants.DeliveryApi.HeaderNames.Preview,
In = ParameterLocation.Header,
Required = false,
Description = "Whether to request draft content.",
- Schema = new OpenApiSchema { Type = "boolean" }
+ Schema = new OpenApiSchema { Type = JsonSchemaType.Boolean },
});
operation.Parameters.Add(new OpenApiParameter
{
- Name = Core.Constants.DeliveryApi.HeaderNames.StartItem,
+ Name = Constants.DeliveryApi.HeaderNames.StartItem,
In = ParameterLocation.Header,
Required = false,
Description = "URL segment or GUID of a root content item.",
- Schema = new OpenApiSchema { Type = "string" }
+ Schema = new OpenApiSchema { Type = JsonSchemaType.String },
});
}
@@ -92,105 +92,36 @@ internal sealed class SwaggerContentDocumentationFilter : SwaggerDocumentationFi
}
}
- private Dictionary FetchQueryParameterExamples() =>
+ private Dictionary FetchQueryParameterExamples() =>
new()
{
- { "Select all", new OpenApiExample { Value = new OpenApiString(string.Empty) } },
- {
- "Select all ancestors of a node by id",
- new OpenApiExample { Value = new OpenApiString("ancestors:id") }
- },
- {
- "Select all ancestors of a node by path",
- new OpenApiExample { Value = new OpenApiString("ancestors:path") }
- },
- {
- "Select all children of a node by id",
- new OpenApiExample { Value = new OpenApiString("children:id") }
- },
- {
- "Select all children of a node by path",
- new OpenApiExample { Value = new OpenApiString("children:path") }
- },
- {
- "Select all descendants of a node by id",
- new OpenApiExample { Value = new OpenApiString("descendants:id") }
- },
- {
- "Select all descendants of a node by path",
- new OpenApiExample { Value = new OpenApiString("descendants:path") }
- }
+ { "Select all", new OpenApiExample { Value = string.Empty } },
+ { "Select all ancestors of a node by id", new OpenApiExample { Value = "ancestors:id" } },
+ { "Select all ancestors of a node by path", new OpenApiExample { Value = "ancestors:path" } },
+ { "Select all children of a node by id", new OpenApiExample { Value = "children:id" } },
+ { "Select all children of a node by path", new OpenApiExample { Value = "children:path" } },
+ { "Select all descendants of a node by id", new OpenApiExample { Value = "descendants:id" } },
+ { "Select all descendants of a node by path", new OpenApiExample { Value = "descendants:path" } },
};
- private Dictionary FilterQueryParameterExamples() =>
+ private Dictionary FilterQueryParameterExamples() =>
new()
{
- { "Default filter", new OpenApiExample { Value = new OpenApiString(string.Empty) } },
- {
- "Filter by content type (equals)",
- new OpenApiExample { Value = new OpenApiArray { new OpenApiString("contentType:alias1") } }
- },
- {
- "Filter by name (contains)",
- new OpenApiExample { Value = new OpenApiArray { new OpenApiString("name:nodeName") } }
- },
- {
- "Filter by creation date (less than)",
- new OpenApiExample { Value = new OpenApiArray { new OpenApiString("createDate<2024-01-01") } }
- },
- {
- "Filter by update date (greater than or equal)",
- new OpenApiExample { Value = new OpenApiArray { new OpenApiString("updateDate>:2023-01-01") } }
- }
+ { "Default filter", new OpenApiExample { Value = string.Empty } },
+ { "Filter by content type (equals)", new OpenApiExample { Value = new JsonArray { "contentType:alias1" } } },
+ { "Filter by name (contains)", new OpenApiExample { Value = new JsonArray { "name:nodeName" } } },
+ { "Filter by creation date (less than)", new OpenApiExample { Value = new JsonArray { "createDate<2024-01-01" } } },
+ { "Filter by update date (greater than or equal)", new OpenApiExample { Value = new JsonArray { "updateDate>:2023-01-01" } } },
};
- private Dictionary SortQueryParameterExamples() =>
+ private Dictionary SortQueryParameterExamples() =>
new()
{
- { "Default sort", new OpenApiExample { Value = new OpenApiString(string.Empty) } },
- {
- "Sort by create date",
- new OpenApiExample
- {
- Value = new OpenApiArray
- {
- new OpenApiString("createDate:asc"), new OpenApiString("createDate:desc")
- }
- }
- },
- {
- "Sort by level",
- new OpenApiExample
- {
- Value = new OpenApiArray { new OpenApiString("level:asc"), new OpenApiString("level:desc") }
- }
- },
- {
- "Sort by name",
- new OpenApiExample
- {
- Value = new OpenApiArray { new OpenApiString("name:asc"), new OpenApiString("name:desc") }
- }
- },
- {
- "Sort by sort order",
- new OpenApiExample
- {
- Value = new OpenApiArray
- {
- new OpenApiString("sortOrder:asc"), new OpenApiString("sortOrder:desc")
- }
- }
- },
- {
- "Sort by update date",
- new OpenApiExample
- {
- Value = new OpenApiArray
- {
- new OpenApiString("updateDate:asc"), new OpenApiString("updateDate:desc")
- }
- }
- }
+ { "Default sort", new OpenApiExample { Value = string.Empty } },
+ { "Sort by create date", new OpenApiExample { Value = new JsonArray { "createDate:asc", "createDate:desc" } } },
+ { "Sort by level", new OpenApiExample { Value = new JsonArray { "level:asc", "level:desc" } } },
+ { "Sort by name", new OpenApiExample { Value = new JsonArray { "name:asc", "name:desc" } } },
+ { "Sort by sort order", new OpenApiExample { Value = new JsonArray { "sortOrder:asc", "sortOrder:desc" } } },
+ { "Sort by update date", new OpenApiExample { Value = new JsonArray { "updateDate:asc", "updateDate:desc" } } },
};
}
diff --git a/src/Umbraco.Cms.Api.Delivery/Filters/SwaggerDocumentationFilterBase.cs b/src/Umbraco.Cms.Api.Delivery/Filters/SwaggerDocumentationFilterBase.cs
index 8a450a2f9b..2776f6a39e 100644
--- a/src/Umbraco.Cms.Api.Delivery/Filters/SwaggerDocumentationFilterBase.cs
+++ b/src/Umbraco.Cms.Api.Delivery/Filters/SwaggerDocumentationFilterBase.cs
@@ -1,7 +1,7 @@
using Microsoft.AspNetCore.Mvc;
-using Microsoft.OpenApi.Any;
-using Microsoft.OpenApi.Models;
+using Microsoft.OpenApi;
using Swashbuckle.AspNetCore.SwaggerGen;
+using Umbraco.Cms.Core;
namespace Umbraco.Cms.Api.Delivery.Filters;
@@ -9,6 +9,8 @@ internal abstract class SwaggerDocumentationFilterBase
: SwaggerFilterBase, IOperationFilter, IParameterFilter
where TBaseController : Controller
{
+ protected abstract string DocumentationLink { get; }
+
public void Apply(OpenApiOperation operation, OperationFilterContext context)
{
if (CanApply(context))
@@ -17,21 +19,19 @@ internal abstract class SwaggerDocumentationFilterBase
}
}
- public void Apply(OpenApiParameter parameter, ParameterFilterContext context)
+ public void Apply(IOpenApiParameter parameter, ParameterFilterContext context)
{
- if (CanApply(context))
+ if (CanApply(context) && parameter is OpenApiParameter openApiParameter)
{
- ApplyParameter(parameter, context);
+ ApplyParameter(openApiParameter, context);
}
}
- protected abstract string DocumentationLink { get; }
-
protected abstract void ApplyOperation(OpenApiOperation operation, OperationFilterContext context);
protected abstract void ApplyParameter(OpenApiParameter parameter, ParameterFilterContext context);
- protected void AddQueryParameterDocumentation(OpenApiParameter parameter, Dictionary examples, string description)
+ protected void AddQueryParameterDocumentation(OpenApiParameter parameter, Dictionary examples, string description)
{
parameter.Description = QueryParameterDescription(description);
parameter.Examples = examples;
@@ -60,15 +60,19 @@ internal abstract class SwaggerDocumentationFilterBase
AddFields(operation);
}
- protected void AddApiKey(OpenApiOperation operation) =>
- operation.Parameters.Add(new OpenApiParameter
- {
- Name = Core.Constants.DeliveryApi.HeaderNames.ApiKey,
- In = ParameterLocation.Header,
- Required = false,
- Description = "API key specified through configuration to authorize access to the API.",
- Schema = new OpenApiSchema { Type = "string" }
- });
+ protected void AddApiKey(OpenApiOperation operation)
+ {
+ operation.Parameters ??= new List();
+ operation.Parameters.Add(
+ new OpenApiParameter
+ {
+ Name = Constants.DeliveryApi.HeaderNames.ApiKey,
+ In = ParameterLocation.Header,
+ Required = false,
+ Description = "API key specified through configuration to authorize access to the API.",
+ Schema = new OpenApiSchema { Type = JsonSchemaType.String },
+ });
+ }
protected string PaginationDescription(bool skip, string itemType)
=> $"Specifies the number of found {itemType} items to {(skip ? "skip" : "take")}. Use this to control pagination of the response.";
@@ -82,78 +86,70 @@ internal abstract class SwaggerDocumentationFilterBase
// FIXME: remove this when Delivery API V1 has been removed (expectedly in V15)
private void AddExpandV1(OpenApiOperation operation)
- => operation.Parameters.Add(new OpenApiParameter
- {
- Name = "expand",
- In = ParameterLocation.Query,
- Required = false,
- Description = QueryParameterDescription("Defines the properties that should be expanded in the response"),
- Schema = new OpenApiSchema { Type = "string" },
- Examples = new Dictionary
+ {
+ operation.Parameters ??= new List();
+ operation.Parameters.Add(
+ new OpenApiParameter
{
- { "Expand none", new OpenApiExample { Value = new OpenApiString(string.Empty) } },
- { "Expand all", new OpenApiExample { Value = new OpenApiString("all") } },
+ Name = "expand",
+ In = ParameterLocation.Query,
+ Required = false,
+ Description =
+ QueryParameterDescription("Defines the properties that should be expanded in the response"),
+ Schema = new OpenApiSchema { Type = JsonSchemaType.String },
+ Examples = new Dictionary
{
- "Expand specific property",
- new OpenApiExample { Value = new OpenApiString("property:alias1") }
+ { "Expand none", new OpenApiExample { Value = string.Empty } },
+ { "Expand all", new OpenApiExample { Value = "all" } },
+ { "Expand specific property", new OpenApiExample { Value = "property:alias1" } },
+ { "Expand specific properties", new OpenApiExample { Value = "property:alias1,alias2" } },
},
- {
- "Expand specific properties",
- new OpenApiExample { Value = new OpenApiString("property:alias1,alias2") }
- }
- }
- });
+ });
+ }
private void AddExpand(OpenApiOperation operation)
- => operation.Parameters.Add(new OpenApiParameter
- {
- Name = "expand",
- In = ParameterLocation.Query,
- Required = false,
- Description = QueryParameterDescription("Defines the properties that should be expanded in the response"),
- Schema = new OpenApiSchema { Type = "string" },
- Examples = new Dictionary
+ {
+ operation.Parameters ??= new List();
+ operation.Parameters.Add(
+ new OpenApiParameter
{
- { "Expand none", new OpenApiExample { Value = new OpenApiString(string.Empty) } },
- { "Expand all properties", new OpenApiExample { Value = new OpenApiString("properties[$all]") } },
+ Name = "expand",
+ In = ParameterLocation.Query,
+ Required = false,
+ Description =
+ QueryParameterDescription("Defines the properties that should be expanded in the response"),
+ Schema = new OpenApiSchema { Type = JsonSchemaType.String },
+ Examples = new Dictionary
{
- "Expand specific property",
- new OpenApiExample { Value = new OpenApiString("properties[alias1]") }
+ { "Expand none", new OpenApiExample { Value = string.Empty } },
+ { "Expand all properties", new OpenApiExample { Value = "properties[$all]" } },
+ { "Expand specific property", new OpenApiExample { Value = "properties[alias1]" } },
+ { "Expand specific properties", new OpenApiExample { Value = "properties[alias1,alias2]" } },
+ { "Expand nested properties", new OpenApiExample { Value = "properties[alias1[properties[nestedAlias1,nestedAlias2]]]" } },
},
- {
- "Expand specific properties",
- new OpenApiExample { Value = new OpenApiString("properties[alias1,alias2]") }
- },
- {
- "Expand nested properties",
- new OpenApiExample { Value = new OpenApiString("properties[alias1[properties[nestedAlias1,nestedAlias2]]]") }
- }
- }
- });
+ });
+ }
private void AddFields(OpenApiOperation operation)
- => operation.Parameters.Add(new OpenApiParameter
- {
- Name = "fields",
- In = ParameterLocation.Query,
- Required = false,
- Description = QueryParameterDescription("Explicitly defines which properties should be included in the response (by default all properties are included)"),
- Schema = new OpenApiSchema { Type = "string" },
- Examples = new Dictionary
+ {
+ operation.Parameters ??= new List();
+ operation.Parameters.Add(
+ new OpenApiParameter
{
- { "Include all properties", new OpenApiExample { Value = new OpenApiString("properties[$all]") } },
+ Name = "fields",
+ In = ParameterLocation.Query,
+ Required = false,
+ Description =
+ QueryParameterDescription(
+ "Explicitly defines which properties should be included in the response (by default all properties are included)"),
+ Schema = new OpenApiSchema { Type = JsonSchemaType.String },
+ Examples = new Dictionary
{
- "Include only specific property",
- new OpenApiExample { Value = new OpenApiString("properties[alias1]") }
+ { "Include all properties", new OpenApiExample { Value = "properties[$all]" } },
+ { "Include only specific property", new OpenApiExample { Value = "properties[alias1]" } },
+ { "Include only specific properties", new OpenApiExample { Value = "properties[alias1,alias2]" } },
+ { "Include only specific nested properties", new OpenApiExample { Value = "properties[alias1[properties[nestedAlias1,nestedAlias2]]]" } },
},
- {
- "Include only specific properties",
- new OpenApiExample { Value = new OpenApiString("properties[alias1,alias2]") }
- },
- {
- "Include only specific nested properties",
- new OpenApiExample { Value = new OpenApiString("properties[alias1[properties[nestedAlias1,nestedAlias2]]]") }
- }
- }
- });
+ });
+ }
}
diff --git a/src/Umbraco.Cms.Api.Delivery/Filters/SwaggerMediaDocumentationFilter.cs b/src/Umbraco.Cms.Api.Delivery/Filters/SwaggerMediaDocumentationFilter.cs
index 85ba66e648..a8da861325 100644
--- a/src/Umbraco.Cms.Api.Delivery/Filters/SwaggerMediaDocumentationFilter.cs
+++ b/src/Umbraco.Cms.Api.Delivery/Filters/SwaggerMediaDocumentationFilter.cs
@@ -1,8 +1,7 @@
-using Microsoft.OpenApi.Any;
-using Microsoft.OpenApi.Models;
+using System.Text.Json.Nodes;
+using Microsoft.OpenApi;
using Swashbuckle.AspNetCore.SwaggerGen;
using Umbraco.Cms.Api.Delivery.Configuration;
-using Umbraco.Cms.Api.Delivery.Controllers;
using Umbraco.Cms.Api.Delivery.Controllers.Media;
namespace Umbraco.Cms.Api.Delivery.Filters;
@@ -13,7 +12,7 @@ internal sealed class SwaggerMediaDocumentationFilter : SwaggerDocumentationFilt
protected override void ApplyOperation(OpenApiOperation operation, OperationFilterContext context)
{
- operation.Parameters ??= new List();
+ operation.Parameters ??= new List();
AddExpand(operation, context);
@@ -46,77 +45,29 @@ internal sealed class SwaggerMediaDocumentationFilter : SwaggerDocumentationFilt
}
}
- private Dictionary FetchQueryParameterExamples() =>
+ private Dictionary FetchQueryParameterExamples() =>
new()
{
- {
- "Select all children at root level",
- new OpenApiExample { Value = new OpenApiString("children:/") }
- },
- {
- "Select all children of a media item by id",
- new OpenApiExample { Value = new OpenApiString("children:id") }
- },
- {
- "Select all children of a media item by path",
- new OpenApiExample { Value = new OpenApiString("children:path") }
- }
+ { "Select all children at root level", new OpenApiExample { Value = "children:/" } },
+ { "Select all children of a media item by id", new OpenApiExample { Value = "children:id" } },
+ { "Select all children of a media item by path", new OpenApiExample { Value = "children:path" } },
};
- private Dictionary FilterQueryParameterExamples() =>
+ private Dictionary FilterQueryParameterExamples() =>
new()
{
- { "Default filter", new OpenApiExample { Value = new OpenApiString(string.Empty) } },
- {
- "Filter by media type",
- new OpenApiExample { Value = new OpenApiArray { new OpenApiString("mediaType:alias1") } }
- },
- {
- "Filter by name",
- new OpenApiExample { Value = new OpenApiArray { new OpenApiString("name:nodeName") } }
- }
+ { "Default filter", new OpenApiExample { Value = string.Empty } },
+ { "Filter by media type", new OpenApiExample { Value = new JsonArray { "mediaType:alias1" } } },
+ { "Filter by name", new OpenApiExample { Value = new JsonArray { "name:nodeName" } } },
};
- private Dictionary SortQueryParameterExamples() =>
+ private Dictionary SortQueryParameterExamples() =>
new()
{
- { "Default sort", new OpenApiExample { Value = new OpenApiString(string.Empty) } },
- {
- "Sort by create date",
- new OpenApiExample
- {
- Value = new OpenApiArray
- {
- new OpenApiString("createDate:asc"), new OpenApiString("createDate:desc")
- }
- }
- },
- {
- "Sort by name",
- new OpenApiExample
- {
- Value = new OpenApiArray { new OpenApiString("name:asc"), new OpenApiString("name:desc") }
- }
- },
- {
- "Sort by sort order",
- new OpenApiExample
- {
- Value = new OpenApiArray
- {
- new OpenApiString("sortOrder:asc"), new OpenApiString("sortOrder:desc")
- }
- }
- },
- {
- "Sort by update date",
- new OpenApiExample
- {
- Value = new OpenApiArray
- {
- new OpenApiString("updateDate:asc"), new OpenApiString("updateDate:desc")
- }
- }
- }
+ { "Default sort", new OpenApiExample { Value = string.Empty } },
+ { "Sort by create date", new OpenApiExample { Value = new JsonArray { "createDate:asc", "createDate:desc" } } },
+ { "Sort by name", new OpenApiExample { Value = new JsonArray { "name:asc", "name:desc" } } },
+ { "Sort by sort order", new OpenApiExample { Value = new JsonArray { "sortOrder:asc", "sortOrder:desc" } } },
+ { "Sort by update date", new OpenApiExample { Value = new JsonArray { "updateDate:asc", "updateDate:desc" } } },
};
}
diff --git a/src/Umbraco.Cms.Api.Management/Configuration/ConfigureUmbracoManagementApiSwaggerGenOptions.cs b/src/Umbraco.Cms.Api.Management/Configuration/ConfigureUmbracoManagementApiSwaggerGenOptions.cs
index 74862e3bab..98ed51f42d 100644
--- a/src/Umbraco.Cms.Api.Management/Configuration/ConfigureUmbracoManagementApiSwaggerGenOptions.cs
+++ b/src/Umbraco.Cms.Api.Management/Configuration/ConfigureUmbracoManagementApiSwaggerGenOptions.cs
@@ -1,8 +1,8 @@
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
-using Microsoft.OpenApi.Models;
+using Microsoft.OpenApi;
using Swashbuckle.AspNetCore.SwaggerGen;
-using Umbraco.Cms.Api.Common.OpenApi;
+using Umbraco.Cms.Api.Common.Security;
using Umbraco.Cms.Api.Common.Serialization;
using Umbraco.Cms.Api.Management.DependencyInjection;
using Umbraco.Cms.Api.Management.OpenApi;
@@ -11,7 +11,7 @@ namespace Umbraco.Cms.Api.Management.Configuration;
public class ConfigureUmbracoManagementApiSwaggerGenOptions : IConfigureOptions
{
- private IUmbracoJsonTypeInfoResolver _umbracoJsonTypeInfoResolver;
+ private readonly IUmbracoJsonTypeInfoResolver _umbracoJsonTypeInfoResolver;
public ConfigureUmbracoManagementApiSwaggerGenOptions(IUmbracoJsonTypeInfoResolver umbracoJsonTypeInfoResolver)
{
@@ -20,7 +20,6 @@ public class ConfigureUmbracoManagementApiSwaggerGenOptions : IConfigureOptions<
public void Configure(SwaggerGenOptions swaggerGenOptions)
{
-
swaggerGenOptions.SwaggerDoc(
ManagementApiConfiguration.ApiName,
new OpenApiInfo
@@ -51,10 +50,10 @@ public class ConfigureUmbracoManagementApiSwaggerGenOptions : IConfigureOptions<
AuthorizationCode = new OpenApiOAuthFlow
{
AuthorizationUrl =
- new Uri(Common.Security.Paths.BackOfficeApi.AuthorizationEndpoint, UriKind.Relative),
- TokenUrl = new Uri(Common.Security.Paths.BackOfficeApi.TokenEndpoint, UriKind.Relative)
- }
- }
+ new Uri(Paths.BackOfficeApi.AuthorizationEndpoint, UriKind.Relative),
+ TokenUrl = new Uri(Paths.BackOfficeApi.TokenEndpoint, UriKind.Relative),
+ },
+ },
});
// Sets Security requirement on backoffice apis
diff --git a/src/Umbraco.Cms.Api.Management/OpenApi.json b/src/Umbraco.Cms.Api.Management/OpenApi.json
index 86ed089da5..4c495413c6 100644
--- a/src/Umbraco.Cms.Api.Management/OpenApi.json
+++ b/src/Umbraco.Cms.Api.Management/OpenApi.json
@@ -38521,9 +38521,6 @@
{
"$ref": "#/components/schemas/DocumentPropertyValuePermissionPresentationModel"
},
- {
- "$ref": "#/components/schemas/DocumentTypePermissionPresentationModel"
- },
{
"$ref": "#/components/schemas/UnknownTypePermissionPresentationModel"
}
@@ -38810,9 +38807,6 @@
{
"$ref": "#/components/schemas/DocumentPropertyValuePermissionPresentationModel"
},
- {
- "$ref": "#/components/schemas/DocumentTypePermissionPresentationModel"
- },
{
"$ref": "#/components/schemas/UnknownTypePermissionPresentationModel"
}
@@ -40211,36 +40205,6 @@
},
"additionalProperties": false
},
- "DocumentTypePermissionPresentationModel": {
- "required": [
- "$type",
- "documentTypeAlias",
- "verbs"
- ],
- "type": "object",
- "properties": {
- "$type": {
- "type": "string"
- },
- "verbs": {
- "uniqueItems": true,
- "type": "array",
- "items": {
- "type": "string"
- }
- },
- "documentTypeAlias": {
- "type": "string"
- }
- },
- "additionalProperties": false,
- "discriminator": {
- "propertyName": "$type",
- "mapping": {
- "DocumentTypePermissionPresentationModel": "#/components/schemas/DocumentTypePermissionPresentationModel"
- }
- }
- },
"DocumentTypePropertyTypeContainerResponseModel": {
"required": [
"id",
@@ -49034,9 +48998,6 @@
{
"$ref": "#/components/schemas/DocumentPropertyValuePermissionPresentationModel"
},
- {
- "$ref": "#/components/schemas/DocumentTypePermissionPresentationModel"
- },
{
"$ref": "#/components/schemas/UnknownTypePermissionPresentationModel"
}
@@ -49474,9 +49435,6 @@
{
"$ref": "#/components/schemas/DocumentPropertyValuePermissionPresentationModel"
},
- {
- "$ref": "#/components/schemas/DocumentTypePermissionPresentationModel"
- },
{
"$ref": "#/components/schemas/UnknownTypePermissionPresentationModel"
}
@@ -50157,5 +50115,160 @@
}
}
}
- }
-}
+ },
+ "tags": [
+ {
+ "name": "Culture"
+ },
+ {
+ "name": "Data Type"
+ },
+ {
+ "name": "Dictionary"
+ },
+ {
+ "name": "Document Blueprint"
+ },
+ {
+ "name": "Document Type"
+ },
+ {
+ "name": "Document Version"
+ },
+ {
+ "name": "Document"
+ },
+ {
+ "name": "Dynamic Root"
+ },
+ {
+ "name": "Health Check"
+ },
+ {
+ "name": "Help"
+ },
+ {
+ "name": "Imaging"
+ },
+ {
+ "name": "Import"
+ },
+ {
+ "name": "Indexer"
+ },
+ {
+ "name": "Install"
+ },
+ {
+ "name": "Language"
+ },
+ {
+ "name": "Log Viewer"
+ },
+ {
+ "name": "Manifest"
+ },
+ {
+ "name": "Media Type"
+ },
+ {
+ "name": "Media"
+ },
+ {
+ "name": "Member Group"
+ },
+ {
+ "name": "Member Type"
+ },
+ {
+ "name": "Member"
+ },
+ {
+ "name": "Models Builder"
+ },
+ {
+ "name": "News Dashboard"
+ },
+ {
+ "name": "Object Types"
+ },
+ {
+ "name": "oEmbed"
+ },
+ {
+ "name": "Package"
+ },
+ {
+ "name": "Partial View"
+ },
+ {
+ "name": "Preview"
+ },
+ {
+ "name": "Profiling"
+ },
+ {
+ "name": "Property Type"
+ },
+ {
+ "name": "Published Cache"
+ },
+ {
+ "name": "Redirect Management"
+ },
+ {
+ "name": "Relation Type"
+ },
+ {
+ "name": "Relation"
+ },
+ {
+ "name": "Script"
+ },
+ {
+ "name": "Searcher"
+ },
+ {
+ "name": "Security"
+ },
+ {
+ "name": "Segment"
+ },
+ {
+ "name": "Server"
+ },
+ {
+ "name": "Static File"
+ },
+ {
+ "name": "Stylesheet"
+ },
+ {
+ "name": "Tag"
+ },
+ {
+ "name": "Telemetry"
+ },
+ {
+ "name": "Template"
+ },
+ {
+ "name": "Temporary File"
+ },
+ {
+ "name": "Upgrade"
+ },
+ {
+ "name": "User Data"
+ },
+ {
+ "name": "User Group"
+ },
+ {
+ "name": "User"
+ },
+ {
+ "name": "Webhook"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/src/Umbraco.Cms.Api.Management/OpenApi/BackOfficeSecurityRequirementsOperationFilterBase.cs b/src/Umbraco.Cms.Api.Management/OpenApi/BackOfficeSecurityRequirementsOperationFilterBase.cs
index e2ff1e609a..bcc069f90b 100644
--- a/src/Umbraco.Cms.Api.Management/OpenApi/BackOfficeSecurityRequirementsOperationFilterBase.cs
+++ b/src/Umbraco.Cms.Api.Management/OpenApi/BackOfficeSecurityRequirementsOperationFilterBase.cs
@@ -1,6 +1,6 @@
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
-using Microsoft.OpenApi.Models;
+using Microsoft.OpenApi;
using Swashbuckle.AspNetCore.SwaggerGen;
using Umbraco.Cms.Api.Management.DependencyInjection;
using Umbraco.Extensions;
@@ -21,27 +21,17 @@ public abstract class BackOfficeSecurityRequirementsOperationFilterBase : IOpera
if (!context.MethodInfo.GetCustomAttributes(true).Any(x => x is AllowAnonymousAttribute) &&
!(context.MethodInfo.DeclaringType?.GetCustomAttributes(true).Any(x => x is AllowAnonymousAttribute) ?? false))
{
- operation.Responses.Add(StatusCodes.Status401Unauthorized.ToString(), new OpenApiResponse
- {
- Description = "The resource is protected and requires an authentication token"
- });
-
- operation.Security = new List
- {
- new OpenApiSecurityRequirement
+ operation.Responses ??= new OpenApiResponses();
+ operation.Responses.Add(
+ StatusCodes.Status401Unauthorized.ToString(),
+ new OpenApiResponse
{
- {
- new OpenApiSecurityScheme
- {
- Reference = new OpenApiReference
- {
- Type = ReferenceType.SecurityScheme,
- Id = ManagementApiConfiguration.ApiSecurityName
- }
- }, []
- }
- }
- };
+ Description = "The resource is protected and requires an authentication token",
+ });
+
+ var schemaRef = new OpenApiSecuritySchemeReference(ManagementApiConfiguration.ApiSecurityName, context.Document);
+ operation.Security ??= new List();
+ operation.Security.Add(new OpenApiSecurityRequirement { [schemaRef] = [] });
}
// Assuming if and endpoint have more then one AuthorizeAttribute, there is a risk the user do not have access while still being authorized.
@@ -57,10 +47,13 @@ public abstract class BackOfficeSecurityRequirementsOperationFilterBase : IOpera
if (numberOfAuthorizeAttributes > 2 || hasConstructorInjectingIAuthorizationService)
{
- operation.Responses.Add(StatusCodes.Status403Forbidden.ToString(), new OpenApiResponse()
- {
- Description = "The authenticated user does not have access to this resource"
- });
+ operation.Responses ??= new OpenApiResponses();
+ operation.Responses.Add(
+ StatusCodes.Status403Forbidden.ToString(),
+ new OpenApiResponse
+ {
+ Description = "The authenticated user does not have access to this resource",
+ });
}
}
}
diff --git a/src/Umbraco.Cms.Api.Management/OpenApi/NotificationHeaderFilter.cs b/src/Umbraco.Cms.Api.Management/OpenApi/NotificationHeaderFilter.cs
index f11c350aae..5872c36b45 100644
--- a/src/Umbraco.Cms.Api.Management/OpenApi/NotificationHeaderFilter.cs
+++ b/src/Umbraco.Cms.Api.Management/OpenApi/NotificationHeaderFilter.cs
@@ -1,5 +1,5 @@
using Microsoft.AspNetCore.Http;
-using Microsoft.OpenApi.Models;
+using Microsoft.OpenApi;
using Swashbuckle.AspNetCore.SwaggerGen;
using Umbraco.Cms.Api.Management.ViewModels;
using Umbraco.Cms.Core;
@@ -24,28 +24,25 @@ internal sealed class NotificationHeaderFilter : IOperationFilter
// filter out irrelevant responses (401 will never produce notifications)
IEnumerable relevantResponses = operation
- .Responses
+ .Responses?
.Where(pair => pair.Key != StatusCodes.Status401Unauthorized.ToString())
- .Select(pair => pair.Value);
+ .Select(pair => pair.Value)
+ .OfType()
+ ?? Enumerable.Empty();
foreach (OpenApiResponse response in relevantResponses)
{
- response.Headers.TryAdd(Constants.Headers.Notifications, new OpenApiHeader
- {
- Description = "The list of notifications produced during the request.",
- Schema = new OpenApiSchema
+ response.Headers ??= new Dictionary();
+ response.Headers.TryAdd(
+ Constants.Headers.Notifications,
+ new OpenApiHeader
{
- Type = "array",
- Nullable = true,
- Items = new OpenApiSchema()
+ Description = "The list of notifications produced during the request.",
+ Schema = new OpenApiSchema
{
- Reference = new OpenApiReference()
- {
- Type = ReferenceType.Schema,
- Id = notificationModelType.Name
- },
- }
- }
- });
+ Type = JsonSchemaType.Array | JsonSchemaType.Null,
+ Items = new OpenApiSchemaReference(notificationModelType.Name),
+ },
+ });
}
}
}
diff --git a/src/Umbraco.Cms.Api.Management/OpenApi/ReponseHeaderOperationFilter.cs b/src/Umbraco.Cms.Api.Management/OpenApi/ReponseHeaderOperationFilter.cs
index e639a64091..4bbc032398 100644
--- a/src/Umbraco.Cms.Api.Management/OpenApi/ReponseHeaderOperationFilter.cs
+++ b/src/Umbraco.Cms.Api.Management/OpenApi/ReponseHeaderOperationFilter.cs
@@ -1,5 +1,5 @@
using Microsoft.AspNetCore.Http;
-using Microsoft.OpenApi.Models;
+using Microsoft.OpenApi;
using Swashbuckle.AspNetCore.SwaggerGen;
using Umbraco.Cms.Api.Management.DependencyInjection;
using Umbraco.Cms.Core;
@@ -11,36 +11,36 @@ internal sealed class ResponseHeaderOperationFilter : IOperationFilter
{
public void Apply(OpenApiOperation operation, OperationFilterContext context)
{
- if (context.MethodInfo.HasMapToApiAttribute(ManagementApiConfiguration.ApiName) == false)
+ if (context.MethodInfo.HasMapToApiAttribute(ManagementApiConfiguration.ApiName) is false || operation.Responses is null)
{
return;
}
- foreach ((var key, OpenApiResponse? value) in operation.Responses)
+ foreach ((var key, IOpenApiResponse value) in operation.Responses)
{
+ if (value is not OpenApiResponse openApiResponse)
+ {
+ continue;
+ }
+
switch (int.Parse(key))
{
case StatusCodes.Status201Created:
// NOTE: The header order matters to the back-office client. Do not change.
- SetHeader(value, Constants.Headers.GeneratedResource, "Identifier of the newly created resource", "string");
- SetHeader(value, Constants.Headers.Location, "Location of the newly created resource", "string", "uri");
+ SetHeader(openApiResponse, Constants.Headers.GeneratedResource, "Identifier of the newly created resource", JsonSchemaType.String);
+ SetHeader(openApiResponse, Constants.Headers.Location, "Location of the newly created resource", JsonSchemaType.String, "uri");
break;
}
}
}
- private static void SetHeader(OpenApiResponse value, string headerName, string description, string type, string? format = null)
+ private static void SetHeader(OpenApiResponse value, string headerName, string description, JsonSchemaType type, string? format = null)
{
-
- if (value.Headers is null)
- {
- value.Headers = new Dictionary();
- }
-
- value.Headers[headerName] = new OpenApiHeader()
+ value.Headers ??= new Dictionary();
+ value.Headers[headerName] = new OpenApiHeader
{
Description = description,
- Schema = new OpenApiSchema { Description = description, Type = type, Format = format }
+ Schema = new OpenApiSchema { Description = description, Type = type, Format = format },
};
}
}
diff --git a/src/Umbraco.Cms.Api.Management/OpenApi/RequireNonNullablePropertiesSchemaFilter.cs b/src/Umbraco.Cms.Api.Management/OpenApi/RequireNonNullablePropertiesSchemaFilter.cs
index 0d06da8bf6..5c957725d3 100644
--- a/src/Umbraco.Cms.Api.Management/OpenApi/RequireNonNullablePropertiesSchemaFilter.cs
+++ b/src/Umbraco.Cms.Api.Management/OpenApi/RequireNonNullablePropertiesSchemaFilter.cs
@@ -1,4 +1,4 @@
-using Microsoft.OpenApi.Models;
+using Microsoft.OpenApi;
using Swashbuckle.AspNetCore.SwaggerGen;
namespace Umbraco.Cms.Api.Management.OpenApi;
@@ -8,14 +8,21 @@ public class RequireNonNullablePropertiesSchemaFilter : ISchemaFilter
///
/// Add to model.Required all properties where Nullable is false.
///
- public void Apply(OpenApiSchema model, SchemaFilterContext context)
+ public void Apply(IOpenApiSchema model, SchemaFilterContext context)
{
- var additionalRequiredProps = model.Properties
- .Where(x => !x.Value.Nullable && !model.Required.Contains(x.Key))
- .Select(x => x.Key);
+ if (model is not OpenApiSchema schema)
+ {
+ return;
+ }
+
+ IEnumerable additionalRequiredProps = schema.Properties
+ ?.Where(x => x.Value.Type?.HasFlag(JsonSchemaType.Null) is not true && model.Required?.Contains(x.Key) is not true)
+ .Select(x => x.Key)
+ ?? [];
+ schema.Required ??= new SortedSet();
foreach (var propKey in additionalRequiredProps)
{
- model.Required.Add(propKey);
+ schema.Required.Add(propKey);
}
}
}
diff --git a/src/Umbraco.Cms.Persistence.EFCore.SqlServer/Migrations/20251006140751_UpdateOpenIddictToV7.Designer.cs b/src/Umbraco.Cms.Persistence.EFCore.SqlServer/Migrations/20251006140751_UpdateOpenIddictToV7.Designer.cs
index 4883257986..430ff75841 100644
--- a/src/Umbraco.Cms.Persistence.EFCore.SqlServer/Migrations/20251006140751_UpdateOpenIddictToV7.Designer.cs
+++ b/src/Umbraco.Cms.Persistence.EFCore.SqlServer/Migrations/20251006140751_UpdateOpenIddictToV7.Designer.cs
@@ -20,7 +20,7 @@ namespace Umbraco.Cms.Persistence.EFCore.SqlServer.Migrations
{
#pragma warning disable 612, 618
modelBuilder
- .HasAnnotation("ProductVersion", "10.0.0-rc.2.25502.107")
+ .HasAnnotation("ProductVersion", "10.0.0")
.HasAnnotation("Relational:MaxIdentifierLength", 128);
SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder);
diff --git a/src/Umbraco.Cms.Persistence.EFCore.SqlServer/Migrations/UmbracoDbContextModelSnapshot.cs b/src/Umbraco.Cms.Persistence.EFCore.SqlServer/Migrations/UmbracoDbContextModelSnapshot.cs
index 66c495fe60..3ab5eabcb6 100644
--- a/src/Umbraco.Cms.Persistence.EFCore.SqlServer/Migrations/UmbracoDbContextModelSnapshot.cs
+++ b/src/Umbraco.Cms.Persistence.EFCore.SqlServer/Migrations/UmbracoDbContextModelSnapshot.cs
@@ -1,4 +1,4 @@
-//
+//
using System;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
@@ -17,7 +17,7 @@ namespace Umbraco.Cms.Persistence.EFCore.SqlServer.Migrations
{
#pragma warning disable 612, 618
modelBuilder
- .HasAnnotation("ProductVersion", "10.0.0-rc.2.25502.107")
+ .HasAnnotation("ProductVersion", "10.0.0")
.HasAnnotation("Relational:MaxIdentifierLength", 128);
SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder);
diff --git a/src/Umbraco.Cms.Persistence.EFCore.Sqlite/Migrations/20251006140958_UpdateOpenIddictToV7.Designer.cs b/src/Umbraco.Cms.Persistence.EFCore.Sqlite/Migrations/20251006140958_UpdateOpenIddictToV7.Designer.cs
index 5f44ea2dae..d77d3a0fb6 100644
--- a/src/Umbraco.Cms.Persistence.EFCore.Sqlite/Migrations/20251006140958_UpdateOpenIddictToV7.Designer.cs
+++ b/src/Umbraco.Cms.Persistence.EFCore.Sqlite/Migrations/20251006140958_UpdateOpenIddictToV7.Designer.cs
@@ -18,7 +18,7 @@ namespace Umbraco.Cms.Persistence.EFCore.Sqlite.Migrations
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
- modelBuilder.HasAnnotation("ProductVersion", "10.0.0-rc.2.25502.107");
+ modelBuilder.HasAnnotation("ProductVersion", "10.0.0");
modelBuilder.Entity("OpenIddict.EntityFrameworkCore.Models.OpenIddictEntityFrameworkCoreApplication", b =>
{
diff --git a/src/Umbraco.Cms.Persistence.EFCore.Sqlite/Migrations/UmbracoDbContextModelSnapshot.cs b/src/Umbraco.Cms.Persistence.EFCore.Sqlite/Migrations/UmbracoDbContextModelSnapshot.cs
index 7c3cf00576..392310d26a 100644
--- a/src/Umbraco.Cms.Persistence.EFCore.Sqlite/Migrations/UmbracoDbContextModelSnapshot.cs
+++ b/src/Umbraco.Cms.Persistence.EFCore.Sqlite/Migrations/UmbracoDbContextModelSnapshot.cs
@@ -1,4 +1,4 @@
-//
+//
using System;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
@@ -15,7 +15,7 @@ namespace Umbraco.Cms.Persistence.EFCore.Sqlite.Migrations
protected override void BuildModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
- modelBuilder.HasAnnotation("ProductVersion", "10.0.0-rc.2.25502.107");
+ modelBuilder.HasAnnotation("ProductVersion", "10.0.0");
modelBuilder.Entity("OpenIddict.EntityFrameworkCore.Models.OpenIddictEntityFrameworkCoreApplication", b =>
{
diff --git a/src/Umbraco.Web.UI.Client/package-lock.json b/src/Umbraco.Web.UI.Client/package-lock.json
index 5044853ffa..852218182e 100644
--- a/src/Umbraco.Web.UI.Client/package-lock.json
+++ b/src/Umbraco.Web.UI.Client/package-lock.json
@@ -1334,9 +1334,9 @@
}
},
"node_modules/@microsoft/signalr": {
- "version": "9.0.6",
- "resolved": "https://registry.npmjs.org/@microsoft/signalr/-/signalr-9.0.6.tgz",
- "integrity": "sha512-DrhgzFWI9JE4RPTsHYRxh4yr+OhnwKz8bnJe7eIi7mLLjqhJpEb62CiUy/YbFvLqLzcGzlzz1QWgVAW0zyipMQ==",
+ "version": "10.0.0",
+ "resolved": "https://registry.npmjs.org/@microsoft/signalr/-/signalr-10.0.0.tgz",
+ "integrity": "sha512-0BRqz/uCx3JdrOqiqgFhih/+hfTERaUfCZXFB52uMaZJrKaPRzHzMuqVsJC/V3pt7NozcNXGspjKiQEK+X7P2w==",
"license": "MIT",
"dependencies": {
"abort-controller": "^3.0.0",
@@ -10617,9 +10617,9 @@
}
},
"node_modules/marked": {
- "version": "16.3.0",
- "resolved": "https://registry.npmjs.org/marked/-/marked-16.3.0.tgz",
- "integrity": "sha512-K3UxuKu6l6bmA5FUwYho8CfJBlsUWAooKtdGgMcERSpF7gcBUrCGsLH7wDaaNOzwq18JzSUDyoEb/YsrqMac3w==",
+ "version": "17.0.1",
+ "resolved": "https://registry.npmjs.org/marked/-/marked-17.0.1.tgz",
+ "integrity": "sha512-boeBdiS0ghpWcSwoNm/jJBwdpFaMnZWRzjA6SkUMYb40SVaN1x7mmfGKp0jvexGcx+7y2La5zRZsYFZI6Qpypg==",
"license": "MIT",
"bin": {
"marked": "bin/marked.js"
@@ -16965,7 +16965,7 @@
"src/external/marked": {
"name": "@umbraco-backoffice/marked",
"dependencies": {
- "marked": "^16.3.0"
+ "marked": "^17.0.1"
}
},
"src/external/monaco-editor": {
@@ -16986,7 +16986,7 @@
"src/external/signalr": {
"name": "@umbraco-backoffice/signalr",
"dependencies": {
- "@microsoft/signalr": "9.0.6"
+ "@microsoft/signalr": "^10.0.0"
}
},
"src/external/uui": {
diff --git a/src/Umbraco.Web.UI.Client/src/external/marked/package.json b/src/Umbraco.Web.UI.Client/src/external/marked/package.json
index aba2839f75..30e912cca4 100644
--- a/src/Umbraco.Web.UI.Client/src/external/marked/package.json
+++ b/src/Umbraco.Web.UI.Client/src/external/marked/package.json
@@ -6,6 +6,6 @@
"build": "vite build"
},
"dependencies": {
- "marked": "^16.3.0"
+ "marked": "^17.0.1"
}
}
diff --git a/src/Umbraco.Web.UI.Client/src/external/signalr/package.json b/src/Umbraco.Web.UI.Client/src/external/signalr/package.json
index 08e7abbc27..6bbc565225 100644
--- a/src/Umbraco.Web.UI.Client/src/external/signalr/package.json
+++ b/src/Umbraco.Web.UI.Client/src/external/signalr/package.json
@@ -6,6 +6,6 @@
"build": "vite build"
},
"dependencies": {
- "@microsoft/signalr": "9.0.6"
+ "@microsoft/signalr": "^10.0.0"
}
}
diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/backend-api/types.gen.ts b/src/Umbraco.Web.UI.Client/src/packages/core/backend-api/types.gen.ts
index 9032ace017..07582d290f 100644
--- a/src/Umbraco.Web.UI.Client/src/packages/core/backend-api/types.gen.ts
+++ b/src/Umbraco.Web.UI.Client/src/packages/core/backend-api/types.gen.ts
@@ -417,7 +417,7 @@ export type CreateUserGroupRequestModel = {
mediaStartNode?: ReferenceByIdModel | null;
mediaRootAccess: boolean;
fallbackPermissions: Array;
- permissions: Array;
+ permissions: Array;
id?: string | null;
};
@@ -476,7 +476,7 @@ export type CurrentUserResponseModel = {
hasAccessToAllLanguages: boolean;
hasAccessToSensitiveData: boolean;
fallbackPermissions: Array;
- permissions: Array;
+ permissions: Array;
allowedSections: Array;
isAdmin: boolean;
};
@@ -778,12 +778,6 @@ export type DocumentTypeItemResponseModel = {
description?: string | null;
};
-export type DocumentTypePermissionPresentationModel = {
- $type: string;
- verbs: Array;
- documentTypeAlias: string;
-};
-
export type DocumentTypePropertyTypeContainerResponseModel = {
id: string;
parent?: ReferenceByIdModel | null;
@@ -2826,7 +2820,7 @@ export type UpdateUserGroupRequestModel = {
mediaStartNode?: ReferenceByIdModel | null;
mediaRootAccess: boolean;
fallbackPermissions: Array;
- permissions: Array;
+ permissions: Array;
};
export type UpdateUserGroupsOnUserRequestModel = {
@@ -2927,7 +2921,7 @@ export type UserGroupResponseModel = {
mediaStartNode?: ReferenceByIdModel | null;
mediaRootAccess: boolean;
fallbackPermissions: Array;
- permissions: Array;
+ permissions: Array;
id: string;
isDeletable: boolean;
aliasCanBeChanged: boolean;
diff --git a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj
index a9c363c8a0..287d607548 100644
--- a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj
+++ b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj
@@ -25,7 +25,7 @@
-
+
-
-
+
+
-
-
+
+
@@ -22,4 +22,4 @@
-
\ No newline at end of file
+
diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Core/DeliveryApi/OpenApiContractTest.cs b/tests/Umbraco.Tests.Integration/Umbraco.Core/DeliveryApi/OpenApiContractTest.cs
index 3514f32d2b..6d74f5ff5e 100644
--- a/tests/Umbraco.Tests.Integration/Umbraco.Core/DeliveryApi/OpenApiContractTest.cs
+++ b/tests/Umbraco.Tests.Integration/Umbraco.Core/DeliveryApi/OpenApiContractTest.cs
@@ -1596,7 +1596,15 @@ internal sealed class OpenApiContractTest : UmbracoTestServerTestBase
"additionalProperties": { }
}
}
- }
+ },
+ "tags": [
+ {
+ "name": "Content"
+ },
+ {
+ "name": "Media"
+ }
+ ]
}
""";
}