diff --git a/README.md b/README.md index 958a3fd..3dfd132 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Umbraco Vercel Analytics -`Umbraco.VercelAnalytics` displays Vercel Web Analytics in the Umbraco 17 backoffice. +`Umbraco.VercelAnalytics` displays Vercel Web Analytics in the Umbraco 17 and 18 backoffice. It provides: @@ -13,7 +13,7 @@ The package reads analytics already collected by Vercel. It does not add Vercel ## Requirements -- Umbraco CMS 17.1 or later. +- Umbraco CMS 17.1 or later, up to (but not including) Umbraco 19. - A Vercel project with [Web Analytics enabled and installed](https://vercel.com/docs/analytics/quickstart). - A [Vercel access token](https://vercel.com/kb/guide/how-do-i-use-a-vercel-api-access-token) scoped to the personal account or team that owns the project. - The Vercel project ID (`prj_...`). @@ -209,8 +209,34 @@ pnpm test pnpm build ``` -Run the example host before regenerating the OpenAPI client, then pass its Swagger URL to: +The generated API client is checked in with the package source. The example host registers the package's OpenAPI document for development without adding version-specific OpenAPI dependencies to the distributed package. + +Run the example host against the Umbraco version whose document you want to use: + +```sh +# Umbraco 17 +dotnet run \ + --project samples/Umbraco.VercelAnalytics.Example \ + -p:UmbracoVersion=17.1.0 + +# Umbraco 18 +dotnet run \ + --project samples/Umbraco.VercelAnalytics.Example \ + -p:UmbracoVersion=18.0.0 +``` + +Use a separate database for each major when switching the example host between versions. Umbraco upgrades its database schema and does not support downgrading that database to an earlier major. + +Then regenerate the client from the matching development endpoint: ```sh -pnpm generate-client -- +cd src/Umbraco.VercelAnalytics/Client + +# Umbraco 17 +corepack pnpm generate-client -- \ + https://localhost:44389/umbraco/swagger/umbracovercelanalytics/swagger.json + +# Umbraco 18 +corepack pnpm generate-client -- \ + https://localhost:44389/umbraco/openapi/umbracovercelanalytics.json ``` diff --git a/samples/Umbraco.VercelAnalytics.Example/Umbraco.VercelAnalytics.Example.csproj b/samples/Umbraco.VercelAnalytics.Example/Umbraco.VercelAnalytics.Example.csproj index 10a3221..c112aab 100644 --- a/samples/Umbraco.VercelAnalytics.Example/Umbraco.VercelAnalytics.Example.csproj +++ b/samples/Umbraco.VercelAnalytics.Example/Umbraco.VercelAnalytics.Example.csproj @@ -2,13 +2,15 @@ net10.0 enable - enable - false + enable + false + 17.1.0 + $(DefineConstants);UMBRACO_18_OR_LATER - - + + diff --git a/samples/Umbraco.VercelAnalytics.Example/VercelAnalyticsOpenApiComposer.cs b/samples/Umbraco.VercelAnalytics.Example/VercelAnalyticsOpenApiComposer.cs new file mode 100644 index 0000000..c08b38c --- /dev/null +++ b/samples/Umbraco.VercelAnalytics.Example/VercelAnalyticsOpenApiComposer.cs @@ -0,0 +1,80 @@ +#if UMBRACO_18_OR_LATER +using Microsoft.AspNetCore.Mvc.Controllers; +using Umbraco.Cms.Api.Common.OpenApi; +using Umbraco.Cms.Api.Management.OpenApi; +#else +using Asp.Versioning; +using Microsoft.AspNetCore.Mvc.ApiExplorer; +using Microsoft.AspNetCore.Mvc.Controllers; +using Microsoft.Extensions.Options; +using Microsoft.OpenApi; +using Swashbuckle.AspNetCore.SwaggerGen; +using Umbraco.Cms.Api.Common.OpenApi; +using Umbraco.Cms.Api.Management.OpenApi; +#endif +using Umbraco.Cms.Core.Composing; +using Umbraco.Cms.Core.DependencyInjection; +using Umbraco.VercelAnalytics; + +namespace Umbraco.VercelAnalytics.Example; + +public sealed class VercelAnalyticsOpenApiComposer : IComposer +{ + public void Compose(IUmbracoBuilder builder) + { +#if UMBRACO_18_OR_LATER + builder.AddBackOfficeOpenApiDocument( + Constants.ApiName, + document => document + .WithTitle("Umbraco Vercel Analytics Backoffice API") + .WithBackOfficeAuthentication() + .ConfigureOpenApiOptions(options => options.AddOperationTransformer( + (operation, context, _) => + { + if (context.Description.ActionDescriptor is ControllerActionDescriptor controller && + controller.ControllerTypeInfo.Namespace?.StartsWith( + "Umbraco.VercelAnalytics.Controllers", + StringComparison.InvariantCultureIgnoreCase) is true) + { + operation.OperationId = context.Description.ActionDescriptor.RouteValues["action"]; + } + + return Task.CompletedTask; + }))); +#else + builder.Services.AddSingleton(); + builder.Services.Configure(options => + { + options.SwaggerDoc( + Constants.ApiName, + new OpenApiInfo + { + Title = "Umbraco Vercel Analytics Backoffice API", + Version = "1.0", + }); + options.OperationFilter(); + }); +#endif + } + +#if !UMBRACO_18_OR_LATER + private sealed class VercelAnalyticsOperationSecurityFilter : BackOfficeSecurityRequirementsOperationFilterBase + { + protected override string ApiName => Constants.ApiName; + } + + private sealed class VercelAnalyticsOperationIdHandler(IOptions apiVersioningOptions) + : OperationIdHandler(apiVersioningOptions) + { + protected override bool CanHandle( + ApiDescription apiDescription, + ControllerActionDescriptor controllerActionDescriptor) + => controllerActionDescriptor.ControllerTypeInfo.Namespace?.StartsWith( + "Umbraco.VercelAnalytics.Controllers", + StringComparison.InvariantCultureIgnoreCase) is true; + + public override string Handle(ApiDescription apiDescription) + => $"{apiDescription.ActionDescriptor.RouteValues["action"]}"; + } +#endif +} diff --git a/src/Umbraco.VercelAnalytics/Client/scripts/generate-openapi.js b/src/Umbraco.VercelAnalytics/Client/scripts/generate-openapi.js index f55e75d..73d6cd0 100644 --- a/src/Umbraco.VercelAnalytics/Client/scripts/generate-openapi.js +++ b/src/Umbraco.VercelAnalytics/Client/scripts/generate-openapi.js @@ -9,7 +9,8 @@ const swaggerUrl = process.argv.slice(2).find((argument) => argument !== '--'); if (swaggerUrl === undefined) { console.error(chalk.red(`ERROR: Missing URL to OpenAPI spec`)); console.error(`Please provide the URL to the OpenAPI spec as the first argument found in ${chalk.yellow('package.json')}`); - console.error(`Example: node generate-openapi.js ${chalk.yellow('https://localhost:44331/umbraco/swagger/REPLACE_ME/swagger.json')}`); + console.error(`Umbraco 17 example: node generate-openapi.js ${chalk.yellow('https://localhost:44389/umbraco/swagger/umbracovercelanalytics/swagger.json')}`); + console.error(`Umbraco 18 example: node generate-openapi.js ${chalk.yellow('https://localhost:44389/umbraco/openapi/umbracovercelanalytics.json')}`); process.exit(); } diff --git a/src/Umbraco.VercelAnalytics/Composers/UmbracoVercelAnalyticsApiComposer.cs b/src/Umbraco.VercelAnalytics/Composers/UmbracoVercelAnalyticsApiComposer.cs index 8cb7dd0..101ea61 100644 --- a/src/Umbraco.VercelAnalytics/Composers/UmbracoVercelAnalyticsApiComposer.cs +++ b/src/Umbraco.VercelAnalytics/Composers/UmbracoVercelAnalyticsApiComposer.cs @@ -1,15 +1,8 @@ -using Asp.Versioning; -using Microsoft.AspNetCore.Mvc.ApiExplorer; -using Microsoft.AspNetCore.Mvc.Controllers; -using Microsoft.Extensions.Options; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.OpenApi; -using Swashbuckle.AspNetCore.SwaggerGen; -using Umbraco.Cms.Core.Composing; +using Microsoft.Extensions.Options; +using Microsoft.Extensions.DependencyInjection; +using Umbraco.Cms.Core.Composing; using Umbraco.Cms.Core.DependencyInjection; using Umbraco.Cms.Core.Notifications; -using Umbraco.Cms.Api.Management.OpenApi; -using Umbraco.Cms.Api.Common.OpenApi; using Umbraco.VercelAnalytics.Configuration; using Umbraco.VercelAnalytics.Services; @@ -40,57 +33,6 @@ public void Compose(IUmbracoBuilder builder) builder.Services.AddTransient(); builder.Services.AddTransient(); builder.AddNotificationAsyncHandler(); - builder.Services.AddSingleton(); - - builder.Services.Configure(opt => - { - // Related documentation: - // https://docs.umbraco.com/umbraco-cms/tutorials/creating-a-backoffice-api - // https://docs.umbraco.com/umbraco-cms/tutorials/creating-a-backoffice-api/adding-a-custom-swagger-document - // https://docs.umbraco.com/umbraco-cms/tutorials/creating-a-backoffice-api/versioning-your-api - // https://docs.umbraco.com/umbraco-cms/tutorials/creating-a-backoffice-api/access-policies - - // Configure the Swagger generation options - // Add in a new Swagger API document solely for our own package that can be browsed via Swagger UI - // Along with having a generated swagger JSON file that we can use to auto generate a TypeScript client - opt.SwaggerDoc(Constants.ApiName, new OpenApiInfo - { - Title = "Umbraco Vercel Analytics Backoffice API", - Version = "1.0", - // Contact = new OpenApiContact - // { - // Name = "Some Developer", - // Email = "you@company.com", - // Url = new Uri("https://company.com") - // } - }); - - // Enable Umbraco authentication for the "Example" Swagger document - // PR: https://github.com/umbraco/Umbraco-CMS/pull/15699 - opt.OperationFilter(); - }); - } - - public class UmbracoVercelAnalyticsOperationSecurityFilter : BackOfficeSecurityRequirementsOperationFilterBase - { - protected override string ApiName => Constants.ApiName; - } - - // This is used to generate nice operation IDs in our swagger json file - // So that the gnerated TypeScript client has nice method names and not too verbose - // https://docs.umbraco.com/umbraco-cms/tutorials/creating-a-backoffice-api/umbraco-schema-and-operation-ids#operation-ids - public class CustomOperationHandler : OperationIdHandler - { - public CustomOperationHandler(IOptions apiVersioningOptions) : base(apiVersioningOptions) - { - } - - protected override bool CanHandle(ApiDescription apiDescription, ControllerActionDescriptor controllerActionDescriptor) - { - return controllerActionDescriptor.ControllerTypeInfo.Namespace?.StartsWith("Umbraco.VercelAnalytics.Controllers", comparisonType: StringComparison.InvariantCultureIgnoreCase) is true; - } - - public override string Handle(ApiDescription apiDescription) => $"{apiDescription.ActionDescriptor.RouteValues["action"]}"; - } - } -} + } + } +} diff --git a/src/Umbraco.VercelAnalytics/Umbraco.VercelAnalytics.csproj b/src/Umbraco.VercelAnalytics/Umbraco.VercelAnalytics.csproj index b4bb0f5..ff6aad2 100644 --- a/src/Umbraco.VercelAnalytics/Umbraco.VercelAnalytics.csproj +++ b/src/Umbraco.VercelAnalytics/Umbraco.VercelAnalytics.csproj @@ -3,7 +3,8 @@ net10.0 enable enable - / + / + [17.1.0,19.0.0) @@ -11,7 +12,7 @@ Umbraco.VercelAnalytics Umbraco.VercelAnalytics 0.1.0 - Vercel Web Analytics reports inside the Umbraco 17 backoffice. + Vercel Web Analytics reports inside the Umbraco 17 and 18 backoffice. umbraco;vercel;analytics;backoffice MIT README.md @@ -24,10 +25,10 @@ - - - - + + + +