Merge branch 'v10/dev' into v11/dev

# Conflicts:
#	src/Umbraco.Cms.ManagementApi/ManagementApiComposer.cs
This commit is contained in:
Sebastiaan Janssen
2022-09-20 10:56:01 +02:00
13 changed files with 2201 additions and 33 deletions

View File

@@ -0,0 +1,26 @@
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Options;
using Umbraco.Cms.Core;
using Umbraco.Cms.Core.Configuration.Models;
using Umbraco.New.Cms.Web.Common.Routing;
namespace Umbraco.Cms.ManagementApi.Configuration;
public class ConfigureMvcOptions : IConfigureOptions<MvcOptions>
{
private readonly IOptions<GlobalSettings> _globalSettings;
public ConfigureMvcOptions(IOptions<GlobalSettings> globalSettings)
{
_globalSettings = globalSettings;
}
public void Configure(MvcOptions options)
{
// Replace the BackOfficeToken in routes.
var backofficePath = _globalSettings.Value.UmbracoPath.TrimStart(Constants.CharArrays.TildeForwardSlash);
options.Conventions.Add(new UmbracoBackofficeToken(Constants.Web.AttributeRouting.BackOfficeToken, backofficePath));
}
}

View File

@@ -1,21 +1,22 @@
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Diagnostics;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Versioning;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.FileProviders;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Options;
using NSwag.AspNetCore;
using Umbraco.Cms.Core;
using Umbraco.Cms.Core.Composing;
using Umbraco.Cms.Core.Configuration.Models;
using Umbraco.Cms.Core.DependencyInjection;
using Umbraco.Cms.Core.Hosting;
using Umbraco.Cms.ManagementApi.Configuration;
using Umbraco.Cms.ManagementApi.DependencyInjection;
using Umbraco.Cms.Web.Common.ApplicationBuilder;
using Umbraco.Extensions;
using Umbraco.New.Cms.Web.Common.Routing;
using IHostingEnvironment = Umbraco.Cms.Core.Hosting.IHostingEnvironment;
namespace Umbraco.Cms.ManagementApi;
@@ -28,6 +29,8 @@ public class ManagementApiComposer : IComposer
public void Compose(IUmbracoBuilder builder)
{
// TODO Should just call a single extension method that can be called fromUmbracoTestServerTestBase too, instead of calling this method
IServiceCollection services = builder.Services;
builder
@@ -59,18 +62,8 @@ public class ManagementApiComposer : IComposer
options.AddApiVersionParametersWhenVersionNeutral = true;
options.AssumeDefaultVersionWhenUnspecified = true;
});
// Not super happy with this, but we need to know the UmbracoPath when registering the controller
// To be able to replace the route template token
// TODO this is fixed in Bjarkes PR for v10, and will need to be removed in v11 merge
GlobalSettings? globalSettings =
builder.Config.GetSection(Constants.Configuration.ConfigGlobal).Get<GlobalSettings>();
var backofficePath = (globalSettings?.UmbracoPath ?? new GlobalSettings().UmbracoPath).TrimStart(Constants.CharArrays.TildeForwardSlash);
services.AddControllers(options =>
{
options.Conventions.Add(new UmbracoBackofficeToken(Constants.Web.AttributeRouting.BackOfficeToken, backofficePath));
});
services.AddControllers();
builder.Services.ConfigureOptions<ConfigureMvcOptions>();
builder.Services.Configure<UmbracoPipelineOptions>(options =>
{
@@ -100,31 +93,46 @@ public class ManagementApiComposer : IComposer
applicationBuilder =>
{
IServiceProvider provider = applicationBuilder.ApplicationServices;
GlobalSettings? settings = provider.GetRequiredService<IOptions<GlobalSettings>>().Value;
IHostingEnvironment hostingEnvironment = provider.GetRequiredService<IHostingEnvironment>();
var officePath = settings.GetBackOfficePath(hostingEnvironment);
IWebHostEnvironment webHostEnvironment = provider.GetRequiredService<IWebHostEnvironment>();
// serve documents (same as app.UseSwagger())
applicationBuilder.UseOpenApi(config =>
if (!webHostEnvironment.IsProduction())
{
config.Path = $"{officePath}/swagger/{{documentName}}/swagger.json";
});
GlobalSettings? settings = provider.GetRequiredService<IOptions<GlobalSettings>>().Value;
IHostingEnvironment hostingEnvironment = provider.GetRequiredService<IHostingEnvironment>();
var officePath = settings.GetBackOfficePath(hostingEnvironment);
// serve documents (same as app.UseSwagger())
applicationBuilder.UseOpenApi(config =>
{
config.Path = $"{officePath}/swagger/{{documentName}}/swagger.json";
});
// Serve Swagger UI
applicationBuilder.UseSwaggerUi3(config =>
{
config.Path = officePath + "/swagger";
config.SwaggerRoutes.Clear();
var swaggerPath = $"{officePath}/swagger/{ApiAllName}/swagger.json";
config.SwaggerRoutes.Add(new SwaggerUi3Route(ApiAllName, swaggerPath));
});
// Serve Swagger UI
applicationBuilder.UseSwaggerUi3(config =>
{
config.Path = officePath + "/swagger";
config.SwaggerRoutes.Clear();
var swaggerPath = $"{officePath}/swagger/{ApiAllName}/swagger.json";
config.SwaggerRoutes.Add(new SwaggerUi3Route(ApiAllName, swaggerPath));
});
}
},
applicationBuilder =>
{
IServiceProvider provider = applicationBuilder.ApplicationServices;
applicationBuilder.UseEndpoints(endpoints =>
{
GlobalSettings? settings = provider.GetRequiredService<IOptions<GlobalSettings>>().Value;
IHostingEnvironment hostingEnvironment = provider.GetRequiredService<IHostingEnvironment>();
var officePath = settings.GetBackOfficePath(hostingEnvironment);
// Maps attribute routed controllers.
endpoints.MapControllers();
// Serve contract
endpoints.MapGet($"{officePath}/api/openapi.json",async context =>
{
await context.Response.SendFileAsync(new EmbeddedFileProvider(this.GetType().Assembly).GetFileInfo("OpenApi.json"));
});
});
}
));

View File

@@ -0,0 +1,552 @@
{
"openapi": "3.0.0",
"info": {
"title": "Umbraco Backoffice API",
"description": "This shows all APIs available in this version of Umbraco - Including all the legacy apis that is available for backward compatibility",
"version": "All"
},
"servers": [
{
"url": "https://localhost:44331"
}
],
"paths": {
"/umbraco/api/v1/install/settings": {
"get": {
"tags": [
"Install"
],
"operationId": "SettingsInstall_Settings",
"responses": {
"400": {
"description": "",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ProblemDetails"
}
}
}
},
"428": {
"description": "",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ProblemDetails"
}
}
}
},
"200": {
"description": "",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/InstallSettingsViewModel"
}
}
}
}
}
}
},
"/umbraco/api/v1/install/setup": {
"post": {
"tags": [
"Install"
],
"operationId": "SetupInstall_Setup",
"requestBody": {
"x-name": "installData",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/InstallViewModel"
}
}
},
"required": true,
"x-position": 1
},
"responses": {
"400": {
"description": "",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ProblemDetails"
}
}
}
},
"428": {
"description": "",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ProblemDetails"
}
}
}
},
"200": {
"description": ""
}
}
}
},
"/umbraco/api/v1/install/validateDatabase": {
"post": {
"tags": [
"Install"
],
"operationId": "ValidateDatabaseInstall_ValidateDatabase",
"requestBody": {
"x-name": "viewModel",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/DatabaseInstallViewModel"
}
}
},
"required": true,
"x-position": 1
},
"responses": {
"400": {
"description": "",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ProblemDetails"
}
}
}
},
"200": {
"description": ""
}
}
}
},
"/umbraco/api/v1/upgrade/authorize": {
"post": {
"tags": [
"Upgrade"
],
"operationId": "AuthorizeUpgrade_Authorize",
"responses": {
"200": {
"description": ""
},
"428": {
"description": "",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ProblemDetails"
}
}
}
},
"500": {
"description": "",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ProblemDetails"
}
}
}
}
}
}
},
"/umbraco/api/v1/upgrade/settings": {
"get": {
"tags": [
"Upgrade"
],
"operationId": "SettingsUpgrade_Settings",
"responses": {
"200": {
"description": "",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UpgradeSettingsViewModel"
}
}
}
},
"428": {
"description": "",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ProblemDetails"
}
}
}
}
}
}
},
"/umbraco/api/v1/server/status": {
"get": {
"tags": [
"Server"
],
"operationId": "StatusServer_Get",
"responses": {
"400": {
"description": "",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ProblemDetails"
}
}
}
},
"200": {
"description": "",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ServerStatusViewModel"
}
}
}
}
}
}
},
"/umbraco/api/v1/server/version": {
"get": {
"tags": [
"Server"
],
"operationId": "VersionServer_Get",
"responses": {
"400": {
"description": "",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ProblemDetails"
}
}
}
},
"200": {
"description": "",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/VersionViewModel"
}
}
}
}
}
}
}
},
"components": {
"schemas": {
"ProblemDetails": {
"type": "object",
"additionalProperties": {
"nullable": true
},
"properties": {
"type": {
"type": "string",
"nullable": true
},
"title": {
"type": "string",
"nullable": true
},
"status": {
"type": "integer",
"format": "int32",
"nullable": true
},
"detail": {
"type": "string",
"nullable": true
},
"instance": {
"type": "string",
"nullable": true
}
}
},
"InstallSettingsViewModel": {
"type": "object",
"additionalProperties": false,
"properties": {
"user": {
"$ref": "#/components/schemas/UserSettingsViewModel"
},
"databases": {
"type": "array",
"items": {
"$ref": "#/components/schemas/DatabaseSettingsViewModel"
}
}
}
},
"UserSettingsViewModel": {
"type": "object",
"additionalProperties": false,
"properties": {
"minCharLength": {
"type": "integer",
"format": "int32"
},
"minNonAlphaNumericLength": {
"type": "integer",
"format": "int32"
},
"consentLevels": {
"type": "array",
"items": {
"$ref": "#/components/schemas/ConsentLevelViewModel"
}
}
}
},
"ConsentLevelViewModel": {
"type": "object",
"additionalProperties": false,
"properties": {
"level": {
"$ref": "#/components/schemas/TelemetryLevel"
},
"description": {
"type": "string"
}
}
},
"TelemetryLevel": {
"type": "string",
"description": "",
"x-enumNames": [
"Minimal",
"Basic",
"Detailed"
],
"enum": [
"Minimal",
"Basic",
"Detailed"
]
},
"DatabaseSettingsViewModel": {
"type": "object",
"additionalProperties": false,
"properties": {
"id": {
"type": "string",
"format": "guid"
},
"sortOrder": {
"type": "integer",
"format": "int32"
},
"displayName": {
"type": "string"
},
"defaultDatabaseName": {
"type": "string"
},
"providerName": {
"type": "string"
},
"isConfigured": {
"type": "boolean"
},
"requiresServer": {
"type": "boolean"
},
"serverPlaceholder": {
"type": "string"
},
"requiresCredentials": {
"type": "boolean"
},
"supportsIntegratedAuthentication": {
"type": "boolean"
},
"requiresConnectionTest": {
"type": "boolean"
}
}
},
"InstallViewModel": {
"type": "object",
"additionalProperties": false,
"required": [
"user",
"database"
],
"properties": {
"user": {
"$ref": "#/components/schemas/UserInstallViewModel"
},
"database": {
"$ref": "#/components/schemas/DatabaseInstallViewModel"
},
"telemetryLevel": {
"$ref": "#/components/schemas/TelemetryLevel"
}
}
},
"UserInstallViewModel": {
"type": "object",
"additionalProperties": false,
"required": [
"name",
"email",
"password"
],
"properties": {
"name": {
"type": "string",
"maxLength": 255,
"minLength": 0
},
"email": {
"type": "string",
"format": "email",
"minLength": 1
},
"password": {
"type": "string",
"minLength": 1
},
"subscribeToNewsletter": {
"type": "boolean"
}
}
},
"DatabaseInstallViewModel": {
"type": "object",
"additionalProperties": false,
"required": [
"id",
"providerName"
],
"properties": {
"id": {
"type": "string",
"format": "guid",
"minLength": 1
},
"providerName": {
"type": "string",
"minLength": 1
},
"server": {
"type": "string",
"nullable": true
},
"name": {
"type": "string",
"nullable": true
},
"username": {
"type": "string",
"nullable": true
},
"password": {
"type": "string",
"nullable": true
},
"useIntegratedAuthentication": {
"type": "boolean"
},
"connectionString": {
"type": "string",
"nullable": true
}
}
},
"UpgradeSettingsViewModel": {
"type": "object",
"additionalProperties": false,
"properties": {
"currentState": {
"type": "string"
},
"newState": {
"type": "string"
},
"newVersion": {
"type": "string"
},
"oldVersion": {
"type": "string"
},
"reportUrl": {
"type": "string"
}
}
},
"ServerStatusViewModel": {
"type": "object",
"additionalProperties": false,
"properties": {
"serverStatus": {
"$ref": "#/components/schemas/RuntimeLevel"
}
}
},
"RuntimeLevel": {
"type": "string",
"description": "Describes the levels in which the runtime can run.\n ",
"x-enumNames": [
"Unknown",
"Boot",
"Install",
"Upgrade",
"Run",
"BootFailed"
],
"enum": [
"Unknown",
"Boot",
"Install",
"Upgrade",
"Run",
"BootFailed"
]
},
"VersionViewModel": {
"type": "object",
"additionalProperties": false,
"properties": {
"version": {
"type": "string"
}
}
}
}
},
"tags": [
{
"name": "Upgrade"
},
{
"name": "Server"
},
{
"name": "Install"
}
]
}

View File

@@ -26,4 +26,9 @@
<PrivateAssets>all</PrivateAssets>
</PackageReference>
</ItemGroup>
<ItemGroup>
<None Remove="OpenApi.json" />
<EmbeddedResource Include="OpenApi.json" />
</ItemGroup>
</Project>

View File

@@ -59,6 +59,8 @@ public class BackOfficeWebAssets
BundlingOptions.NotOptimizedAndComposite,
FormatPaths(
"assets/css/umbraco.min.css",
"lib/umbraco-ui/uui-css/dist/custom-properties.css",
"lib/umbraco-ui/uui-css/dist/uui-text.css",
"lib/bootstrap-social/bootstrap-social.css",
"lib/font-awesome/css/font-awesome.min.css"));

View File

@@ -35,6 +35,8 @@
'lib/umbraco/NamespaceManager.js',
'lib/umbraco/LegacySpeechBubble.js',
'lib/umbraco-ui/uui/dist/uui.min.js',
'js/utilities.min.js',
'js/app.min.js',

View File

@@ -279,6 +279,16 @@ function dependencies() {
"./node_modules/wicg-inert/dist/inert.min.js.map"
],
"base": "./node_modules/wicg-inert"
},
{
"name": "umbraco-ui",
"src": [
"./node_modules/@umbraco-ui/uui/dist/uui.min.js",
"./node_modules/@umbraco-ui/uui/dist/uui.min.js.map",
"./node_modules/@umbraco-ui/uui-css/dist/custom-properties.css",
"./node_modules/@umbraco-ui/uui-css/dist/uui-text.css"
],
"base": "./node_modules/@umbraco-ui"
}
];

File diff suppressed because it is too large Load Diff

View File

@@ -18,6 +18,8 @@
},
"dependencies": {
"@microsoft/signalr": "6.0.9",
"@umbraco-ui/uui": "1.0.1",
"@umbraco-ui/uui-css": "1.0.0",
"ace-builds": "1.10.1",
"angular": "1.8.3",
"angular-animate": "1.8.3",

View File

@@ -0,0 +1,57 @@
using System;
using System.Threading.Tasks;
using Microsoft.Extensions.Options;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using NUnit.Framework;
using Umbraco.Cms.Core.Configuration.Models;
using Umbraco.Cms.Core.DependencyInjection;
using Umbraco.Cms.Core.Hosting;
using Umbraco.Cms.Tests.Integration.TestServerTest;
using Umbraco.Extensions;
namespace Umbraco.Cms.Tests.Integration.NewBackoffice;
// We only run this test in release because the schema looks different depending if it's built against release or debug.
// XML summaries is included in the description of a response model in release, but not debug mode.
#if DEBUG
[Ignore("This test runs only in release")]
#endif
[TestFixture]
public class OpenAPIContractTest : UmbracoTestServerTestBase
{
private GlobalSettings GlobalSettings => GetRequiredService<IOptions<GlobalSettings>>().Value;
private IHostingEnvironment HostingEnvironment => GetRequiredService<IHostingEnvironment>();
[Test]
public async Task Validate_OpenApi_Contract_is_implemented()
{
string[] keysToIgnore = { "servers" };
var officePath = GlobalSettings.GetBackOfficePath(HostingEnvironment);
var urlToContract = $"{officePath}/api/openapi.json";
var swaggerPath = $"{officePath}/swagger/All/swagger.json";
var apiContract = JObject.Parse(await Client.GetStringAsync(urlToContract));
var generatedJsonString = await Client.GetStringAsync(swaggerPath);
var mergedContract = JObject.Parse(generatedJsonString);
var originalGeneratedContract = JObject.Parse(generatedJsonString);
mergedContract.Merge(apiContract, new JsonMergeSettings
{
MergeArrayHandling = MergeArrayHandling.Merge
});
foreach (var key in keysToIgnore)
{
originalGeneratedContract.Remove(key);
mergedContract.Remove(key);
}
Assert.AreEqual(originalGeneratedContract, mergedContract, $"Generated API do not respect the contract:{Environment.NewLine}Expected:{Environment.NewLine}{originalGeneratedContract.ToString(Formatting.Indented)}{Environment.NewLine}{Environment.NewLine}Actual:{Environment.NewLine}{mergedContract.ToString(Formatting.Indented)}");
}
}

View File

@@ -19,6 +19,9 @@ using Umbraco.Cms.Core.Composing;
using Umbraco.Cms.Core.DependencyInjection;
using Umbraco.Cms.Core.Services;
using Umbraco.Cms.Core.Web;
using Umbraco.Cms.ManagementApi;
using Umbraco.Cms.ManagementApi.Configuration;
using Umbraco.Cms.ManagementApi.Controllers.Install;
using Umbraco.Cms.Persistence.Sqlite;
using Umbraco.Cms.Persistence.SqlServer;
using Umbraco.Cms.Tests.Common.Testing;
@@ -26,7 +29,6 @@ using Umbraco.Cms.Tests.Integration.DependencyInjection;
using Umbraco.Cms.Tests.Integration.Testing;
using Umbraco.Cms.Web.BackOffice.Controllers;
using Umbraco.Cms.Web.Common.Controllers;
using Umbraco.Cms.Web.Common.Hosting;
using Umbraco.Cms.Web.Website.Controllers;
using Umbraco.Extensions;
@@ -238,6 +240,9 @@ namespace Umbraco.Cms.Tests.Integration.TestServerTest
// Adds Umbraco.Tests.Integration
mvcBuilder.AddApplicationPart(typeof(UmbracoTestServerTestBase).Assembly);
// Adds Umbraco.Tests.Integration
mvcBuilder.AddApplicationPart(typeof(InstallControllerBase).Assembly);
})
.AddWebServer()
.AddWebsite()
@@ -245,6 +250,8 @@ namespace Umbraco.Cms.Tests.Integration.TestServerTest
.AddUmbracoSqliteSupport()
.AddTestServices(TestHelper); // This is the important one!
new ManagementApiComposer().Compose(builder);
CustomTestSetup(builder);
builder.Build();
}
@@ -254,6 +261,7 @@ namespace Umbraco.Cms.Tests.Integration.TestServerTest
/// </summary>
protected virtual void ConfigureTestServices(IServiceCollection services)
{
}
protected void Configure(IApplicationBuilder app)

View File

@@ -107,6 +107,7 @@
<ProjectReference Include="..\..\src\Umbraco.Cms.Persistence.SqlServer\Umbraco.Cms.Persistence.SqlServer.csproj" />
<ProjectReference Include="..\..\src\Umbraco.Core\Umbraco.Core.csproj" />
<ProjectReference Include="..\..\src\Umbraco.Infrastructure\Umbraco.Infrastructure.csproj" />
<ProjectReference Include="..\..\src\Umbraco.Cms.ManagementApi\Umbraco.Cms.ManagementApi.csproj" />
<ProjectReference Include="..\..\src\Umbraco.PublishedCache.NuCache\Umbraco.PublishedCache.NuCache.csproj" />
<ProjectReference Include="..\Umbraco.Tests.Common\Umbraco.Tests.Common.csproj" />
<ProjectReference Include="..\..\src\Umbraco.Web.BackOffice\Umbraco.Web.BackOffice.csproj" />

View File

@@ -81,7 +81,8 @@ public class InstallAreaRoutesTests
var route = endpoints.DataSources.First();
Assert.AreEqual(1, route.Endpoints.Count);
Assert.AreEqual("install/{controller?}/{action?} HTTP: GET", route.Endpoints[0].ToString());
var routeEndpoint = (RouteEndpoint)route.Endpoints[0];
Assert.AreEqual("install/{controller?}/{action?}", routeEndpoint.RoutePattern.RawText);
}
private InstallAreaRoutes GetInstallAreaRoutes(RuntimeLevel level) =>