From 8f4cd7c4a481eddf1482f5a992d9960a7e7c1cd1 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 2 Jul 2026 00:38:07 +0000 Subject: [PATCH 01/23] build(deps): bump actions/checkout from 4 to 7 Bumps [actions/checkout](https://github.com/actions/checkout) from 4 to 7. - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/v4...v7) --- updated-dependencies: - dependency-name: actions/checkout dependency-version: '7' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/ci.yml | 2 +- .github/workflows/release.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c427d32..30f031d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -13,7 +13,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v7 - name: Setup .NET uses: actions/setup-dotnet@v4 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 72cabeb..1e2ce50 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -14,7 +14,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v7 - name: Setup .NET uses: actions/setup-dotnet@v4 From 274e3361c78cff273aa12de25a8c803f7a2d6681 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 2 Jul 2026 00:45:03 +0000 Subject: [PATCH 02/23] Bump coverlet.collector from 6.0.0 to 10.0.1 --- updated-dependencies: - dependency-name: coverlet.collector dependency-version: 10.0.1 dependency-type: direct:production update-type: version-update:semver-major - dependency-name: coverlet.collector dependency-version: 10.0.1 dependency-type: direct:production update-type: version-update:semver-major - dependency-name: coverlet.collector dependency-version: 10.0.1 dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .../CleanArchitecture.Tests.Shared.csproj | 2 +- .../CleanArchitecture.WebApi.IntegrationTests.csproj | 2 +- .../CleanArchitecture.WebApi.UnitTests.csproj | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/CleanArchitecture.Tests.Shared/CleanArchitecture.Tests.Shared.csproj b/tests/CleanArchitecture.Tests.Shared/CleanArchitecture.Tests.Shared.csproj index 4b0710c..7602a82 100644 --- a/tests/CleanArchitecture.Tests.Shared/CleanArchitecture.Tests.Shared.csproj +++ b/tests/CleanArchitecture.Tests.Shared/CleanArchitecture.Tests.Shared.csproj @@ -6,7 +6,7 @@ - + diff --git a/tests/CleanArchitecture.WebApi.IntegrationTests/CleanArchitecture.WebApi.IntegrationTests.csproj b/tests/CleanArchitecture.WebApi.IntegrationTests/CleanArchitecture.WebApi.IntegrationTests.csproj index 7a2dffb..133320b 100644 --- a/tests/CleanArchitecture.WebApi.IntegrationTests/CleanArchitecture.WebApi.IntegrationTests.csproj +++ b/tests/CleanArchitecture.WebApi.IntegrationTests/CleanArchitecture.WebApi.IntegrationTests.csproj @@ -6,7 +6,7 @@ - + diff --git a/tests/CleanArchitecture.WebApi.UnitTests/CleanArchitecture.WebApi.UnitTests.csproj b/tests/CleanArchitecture.WebApi.UnitTests/CleanArchitecture.WebApi.UnitTests.csproj index 53ff909..6bc9e53 100644 --- a/tests/CleanArchitecture.WebApi.UnitTests/CleanArchitecture.WebApi.UnitTests.csproj +++ b/tests/CleanArchitecture.WebApi.UnitTests/CleanArchitecture.WebApi.UnitTests.csproj @@ -6,7 +6,7 @@ - + From 1d0b92042508c773ae5f845bd9c1346090b1ece2 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 2 Jul 2026 00:45:50 +0000 Subject: [PATCH 03/23] Bump FluentValidation and FluentValidation.DependencyInjectionExtensions Bumps FluentValidation from 11.9.2 to 12.1.1 Bumps FluentValidation.DependencyInjectionExtensions from 11.9.2 to 12.1.1 --- updated-dependencies: - dependency-name: FluentValidation dependency-version: 12.1.1 dependency-type: direct:production update-type: version-update:semver-major - dependency-name: FluentValidation.DependencyInjectionExtensions dependency-version: 12.1.1 dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .../CleanArchitecture.Application.csproj | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/CleanArchitecture.Application/CleanArchitecture.Application.csproj b/src/CleanArchitecture.Application/CleanArchitecture.Application.csproj index 600ad26..0e4716e 100644 --- a/src/CleanArchitecture.Application/CleanArchitecture.Application.csproj +++ b/src/CleanArchitecture.Application/CleanArchitecture.Application.csproj @@ -8,8 +8,8 @@ - - + + From 571439b0283bdf5d20f52f2df81e2b582e39555e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 2 Jul 2026 00:46:27 +0000 Subject: [PATCH 04/23] Bump MediatR and Microsoft.Extensions.Logging.Abstractions Bumps MediatR from 12.3.0 to 14.1.0 Bumps Microsoft.Extensions.Logging.Abstractions from 8.0.3 to 10.0.0 --- updated-dependencies: - dependency-name: MediatR dependency-version: 14.1.0 dependency-type: direct:production update-type: version-update:semver-major - dependency-name: Microsoft.Extensions.Logging.Abstractions dependency-version: 10.0.0 dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .../CleanArchitecture.Presentation.csproj | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/CleanArchitecture.Presentation/CleanArchitecture.Presentation.csproj b/src/CleanArchitecture.Presentation/CleanArchitecture.Presentation.csproj index c8c4e5d..bcdd977 100644 --- a/src/CleanArchitecture.Presentation/CleanArchitecture.Presentation.csproj +++ b/src/CleanArchitecture.Presentation/CleanArchitecture.Presentation.csproj @@ -13,8 +13,8 @@ - - + + From e24eddbf8c27db6fbd36304ba78095b1053315a0 Mon Sep 17 00:00:00 2001 From: Mario Alberto Arce Date: Wed, 1 Jul 2026 19:26:51 -0600 Subject: [PATCH 05/23] infra: wire Axlis and Axlis.GraphQL services - Add dev-only ProjectReferences to Axlis.Core, Axlis, Axlis.GraphQL (fixme) - Add Axlis and AxlisGraphQL config sections to appsettings.json - Register AddAxlis and AddAxlisGraphQL in Program.cs - Wire UseAxlis after app.Build() for ambient lazy-loader - Set user-secrets for Endpoint and ApiKey (fixme) - Enable Samples feature flag for showcase endpoint access --- .../CleanArchitecture.Application.csproj | 5 +++++ .../CleanArchitecture.WebApi.csproj | 9 ++++++++- src/CleanArchitecture.WebApi/Program.cs | 19 +++++++++++++++++++ src/CleanArchitecture.WebApi/appsettings.json | 11 ++++++++++- 4 files changed, 42 insertions(+), 2 deletions(-) diff --git a/src/CleanArchitecture.Application/CleanArchitecture.Application.csproj b/src/CleanArchitecture.Application/CleanArchitecture.Application.csproj index 600ad26..933ebfb 100644 --- a/src/CleanArchitecture.Application/CleanArchitecture.Application.csproj +++ b/src/CleanArchitecture.Application/CleanArchitecture.Application.csproj @@ -5,6 +5,11 @@ + + + + + diff --git a/src/CleanArchitecture.WebApi/CleanArchitecture.WebApi.csproj b/src/CleanArchitecture.WebApi/CleanArchitecture.WebApi.csproj index e8b0a4d..cfa911c 100644 --- a/src/CleanArchitecture.WebApi/CleanArchitecture.WebApi.csproj +++ b/src/CleanArchitecture.WebApi/CleanArchitecture.WebApi.csproj @@ -1,4 +1,5 @@ - + d7619c51-7f22-471a-b328-7e4198f851b0 + @@ -22,4 +23,10 @@ + + + + + + diff --git a/src/CleanArchitecture.WebApi/Program.cs b/src/CleanArchitecture.WebApi/Program.cs index cee6c66..7a87d23 100644 --- a/src/CleanArchitecture.WebApi/Program.cs +++ b/src/CleanArchitecture.WebApi/Program.cs @@ -1,3 +1,4 @@ +using Axlis.Extensions; using CleanArchitecture.Application; using CleanArchitecture.Infrastructure; using CleanArchitecture.Presentation; @@ -27,6 +28,21 @@ .AddCacheDisk(builder.Configuration) // Disk cache provider (flag-gated) ; +// --- Axlis: Sitecore Headless GraphQL ORM --- +// fixme: Endpoint and ApiKey must be set in user-secrets for dev; swap to NuGet PackageReference before release. +builder.Services + .AddAxlis(o => + { + builder.Configuration.GetSection("Axlis").Bind(o); + }) + .AddAxlisGraphQL(o => + { + o.Endpoint = builder.Configuration["AxlisGraphQL:Endpoint"] ?? string.Empty; + o.ApiKey = builder.Configuration["AxlisGraphQL:ApiKey"]; + o.BatchSize = int.TryParse(builder.Configuration["AxlisGraphQL:BatchSize"], out var b) ? b : 10; + o.TimeoutSeconds = int.TryParse(builder.Configuration["AxlisGraphQL:TimeoutSeconds"], out var t) ? t : 30; + }); + // --- Composition root: wire the layers (clean-correct dependency direction) --- builder.Services .AddApplication() @@ -44,6 +60,9 @@ var app = builder.Build(); +// Wire the ambient lazy-loader so ExtendedItem.Axes traversal works at runtime. +app.Services.UseAxlis(); + if (app.Environment.IsDevelopment()) { app.UseSwagger(); diff --git a/src/CleanArchitecture.WebApi/appsettings.json b/src/CleanArchitecture.WebApi/appsettings.json index fa9ee88..14ebbb1 100644 --- a/src/CleanArchitecture.WebApi/appsettings.json +++ b/src/CleanArchitecture.WebApi/appsettings.json @@ -27,7 +27,16 @@ "EnableCrossProcessLocking": false }, "Samples": { - "Enabled": false + "Enabled": true } + }, + "Axlis": { + "CacheTtl": "00:30:00", + "EnableDiagnostics": true + }, + "AxlisGraphQL": { + "Endpoint": "", + "BatchSize": 10, + "TimeoutSeconds": 30 } } From 54a8d1958a7c724fcbed0a46109a7e555978327d Mon Sep 17 00:00:00 2001 From: Mario Alberto Arce Date: Wed, 1 Jul 2026 19:26:54 -0600 Subject: [PATCH 06/23] wiring: add sample Sitecore template POCOs - Add Disclaimer template with TextField Heading/Description - Add HomePage template with ImageField MetaThumbnail + MultilistField HeadCssLinks - Add PresentationAssetLink template with TextField Link - Add DictionaryRoot template with ItemReferenceField FallbackDomain - Add Style template for typed Axes traversal predicates - All templates use placeholder GUIDs (fixme for real IDs) --- .../Sitecore/Templates/DictionaryRoot.cs | 18 +++++++++++++++ .../Sitecore/Templates/Disclaimer.cs | 21 ++++++++++++++++++ .../Sitecore/Templates/HomePage.cs | 22 +++++++++++++++++++ .../Templates/PresentationAssetLink.cs | 17 ++++++++++++++ .../Sitecore/Templates/Style.cs | 14 ++++++++++++ 5 files changed, 92 insertions(+) create mode 100644 src/CleanArchitecture.Application/Sitecore/Templates/DictionaryRoot.cs create mode 100644 src/CleanArchitecture.Application/Sitecore/Templates/Disclaimer.cs create mode 100644 src/CleanArchitecture.Application/Sitecore/Templates/HomePage.cs create mode 100644 src/CleanArchitecture.Application/Sitecore/Templates/PresentationAssetLink.cs create mode 100644 src/CleanArchitecture.Application/Sitecore/Templates/Style.cs diff --git a/src/CleanArchitecture.Application/Sitecore/Templates/DictionaryRoot.cs b/src/CleanArchitecture.Application/Sitecore/Templates/DictionaryRoot.cs new file mode 100644 index 0000000..b951812 --- /dev/null +++ b/src/CleanArchitecture.Application/Sitecore/Templates/DictionaryRoot.cs @@ -0,0 +1,18 @@ +using Axlis.Attributes; +using Axlis.Core; +using Axlis.Core.FieldTypes; + +namespace CleanArchitecture.Application.Sitecore.Templates; + +/// +/// Represents the Sitecore Dictionary root item template. +/// Demonstrates field access. +/// +// fixme: replace the placeholder GUID with the real template ID from your Sitecore instance +[SitecoreTemplate("{00000000-0000-0000-0000-000000000004}")] +public class DictionaryRoot : ExtendedItem +{ + /// The fallback domain for dictionary lookups. + [SitecoreField("FallbackDomain")] + public ItemReferenceField FallbackDomain => GetField("FallbackDomain"); +} diff --git a/src/CleanArchitecture.Application/Sitecore/Templates/Disclaimer.cs b/src/CleanArchitecture.Application/Sitecore/Templates/Disclaimer.cs new file mode 100644 index 0000000..276a668 --- /dev/null +++ b/src/CleanArchitecture.Application/Sitecore/Templates/Disclaimer.cs @@ -0,0 +1,21 @@ +using Axlis.Attributes; +using Axlis.Core; +using Axlis.Core.FieldTypes; + +namespace CleanArchitecture.Application.Sitecore.Templates; + +/// +/// Represents the Sitecore Disclaimer template. +/// +// fixme: replace the placeholder GUID with the real template ID from your Sitecore instance +[SitecoreTemplate("{00000000-0000-0000-0000-000000000001}")] +public class Disclaimer : ExtendedItem +{ + /// The disclaimer heading text. + [SitecoreField("Heading")] + public TextField Heading => GetField("Heading"); + + /// The disclaimer body text. + [SitecoreField("Description")] + public TextField Description => GetField("Description"); +} diff --git a/src/CleanArchitecture.Application/Sitecore/Templates/HomePage.cs b/src/CleanArchitecture.Application/Sitecore/Templates/HomePage.cs new file mode 100644 index 0000000..0161e13 --- /dev/null +++ b/src/CleanArchitecture.Application/Sitecore/Templates/HomePage.cs @@ -0,0 +1,22 @@ +using Axlis.Attributes; +using Axlis.Core; +using Axlis.Core.FieldTypes; + +namespace CleanArchitecture.Application.Sitecore.Templates; + +/// +/// Represents the Sitecore Home page template. +/// Demonstrates and field access. +/// +// fixme: replace the placeholder GUID with the real template ID from your Sitecore instance +[SitecoreTemplate("{00000000-0000-0000-0000-000000000002}")] +public class HomePage : ExtendedItem +{ + /// The meta thumbnail image for the page. + [SitecoreField("MetaThumbnail")] + public ImageField MetaThumbnail => GetField("MetaThumbnail"); + + /// CSS asset links referenced in the page head. + [SitecoreField("HeadCSSLinks")] + public MultilistField HeadCssLinks => GetField("HeadCSSLinks"); +} diff --git a/src/CleanArchitecture.Application/Sitecore/Templates/PresentationAssetLink.cs b/src/CleanArchitecture.Application/Sitecore/Templates/PresentationAssetLink.cs new file mode 100644 index 0000000..750906e --- /dev/null +++ b/src/CleanArchitecture.Application/Sitecore/Templates/PresentationAssetLink.cs @@ -0,0 +1,17 @@ +using Axlis.Attributes; +using Axlis.Core; +using Axlis.Core.FieldTypes; + +namespace CleanArchitecture.Application.Sitecore.Templates; + +/// +/// Represents the Sitecore PresentationAssetLink template (e.g. a CSS or JS asset link item). +/// +// fixme: replace the placeholder GUID with the real template ID from your Sitecore instance +[SitecoreTemplate("{00000000-0000-0000-0000-000000000003}")] +public class PresentationAssetLink : ExtendedItem +{ + /// The URL of the asset. + [SitecoreField("Link")] + public TextField Link => GetField("Link"); +} diff --git a/src/CleanArchitecture.Application/Sitecore/Templates/Style.cs b/src/CleanArchitecture.Application/Sitecore/Templates/Style.cs new file mode 100644 index 0000000..18bad82 --- /dev/null +++ b/src/CleanArchitecture.Application/Sitecore/Templates/Style.cs @@ -0,0 +1,14 @@ +using Axlis.Attributes; +using Axlis.Core; + +namespace CleanArchitecture.Application.Sitecore.Templates; + +/// +/// Represents the Sitecore Style template. +/// Used as a typed predicate target for Axes traversal (GetChildren, GetDescendants). +/// +// fixme: replace the placeholder GUID with the real template ID from your Sitecore instance +[SitecoreTemplate("{00000000-0000-0000-0000-000000000005}")] +public class Style : ExtendedItem +{ +} From b2b4d0c01cb2225143395ab74161e867ade13a8f Mon Sep 17 00:00:00 2001 From: Mario Alberto Arce Date: Wed, 1 Jul 2026 19:26:58 -0600 Subject: [PATCH 07/23] showcase: add CQRS Sitecore showcase endpoint - Add GetSitecoreShowcaseQuery with RootPath parameter - Add GetSitecoreShowcaseResponse with six pivot DTOs - Add GetSitecoreShowcaseQueryHandler exercising Axlis facade - Pivot 1: TextField (Disclaimer Heading/Description) - Pivot 2: ImageField + MultilistField (HomePage MetaThumbnail + HeadCssLinks) - Pivot 3: ItemReferenceField (Dictionary FallbackDomain) - Pivot 4: Axes traversal (Parent, Children, Grandparent, Siblings, typed GetChildren) - Pivot 5: GetDescendants typed traversal - Pivot 6: WithResult rich API (value, metadata, diagnostics) - Add SitecoreController with /v1/sitecore/showcase endpoint - Gated behind Samples feature flag --- .../GetSitecoreShowcaseQueryHandler.cs | 167 ++++++++++++++++++ .../Queries/GetSitecoreShowcaseQuery.cs | 26 +++ .../Responses/GetSitecoreShowcaseResponse.cs | 79 +++++++++ .../Controllers/v1/SitecoreController.cs | 61 +++++++ 4 files changed, 333 insertions(+) create mode 100644 src/CleanArchitecture.Application/Api/Sitecore/Handlers/GetSitecoreShowcaseQueryHandler.cs create mode 100644 src/CleanArchitecture.Application/Api/Sitecore/Queries/GetSitecoreShowcaseQuery.cs create mode 100644 src/CleanArchitecture.Application/Api/Sitecore/Responses/GetSitecoreShowcaseResponse.cs create mode 100644 src/CleanArchitecture.Presentation/Controllers/v1/SitecoreController.cs diff --git a/src/CleanArchitecture.Application/Api/Sitecore/Handlers/GetSitecoreShowcaseQueryHandler.cs b/src/CleanArchitecture.Application/Api/Sitecore/Handlers/GetSitecoreShowcaseQueryHandler.cs new file mode 100644 index 0000000..da4df4e --- /dev/null +++ b/src/CleanArchitecture.Application/Api/Sitecore/Handlers/GetSitecoreShowcaseQueryHandler.cs @@ -0,0 +1,167 @@ +using Ardalis.Result; +using Axlis.Services; +using CleanArchitecture.Application.Api.Sitecore.Queries; +using CleanArchitecture.Application.Api.Sitecore.Responses; +using CleanArchitecture.Application.Common.Handlers; +using CleanArchitecture.Application.Sitecore.Templates; +using Microsoft.Extensions.Logging; + +namespace CleanArchitecture.Application.Api.Sitecore.Handlers; + +/// +/// Handles . +/// Exercises six Axlis API pivots: +/// TextField, ImageField/MultilistField, ItemReferenceField, Axes traversal, +/// GetDescendants, and the WithResult rich-API. +/// +public sealed class GetSitecoreShowcaseQueryHandler + : BaseRequestHandler> +{ + private readonly ISitecoreFacade _facade; + + /// + /// Initializes a new instance of . + /// + public GetSitecoreShowcaseQueryHandler( + ILogger logger, + ISitecoreFacade facade) + : base(logger) + { + _facade = facade; + ArgumentNullException.ThrowIfNull(_facade); + } + + /// + public override async Task> Handle( + GetSitecoreShowcaseQuery request, + CancellationToken cancellationToken) + { + var rootPath = request.RootPath; + // fixme: adjust relative paths to match the connected Sitecore content tree + var disclaimerPath = $"{rootPath}/disclaimer"; + var dictionaryPath = $"{GetParentPath(rootPath)}/dictionary"; + + // ── Pivot 1: TextField (Disclaimer) ─────────────────────────────────── + var disclaimer = await _facade + .GetItemByPathAsync(disclaimerPath, cancellationToken) + .ConfigureAwait(false); + + DisclaimerPivot? disclaimerPivot = null; + if (disclaimer is not null) + { + disclaimerPivot = new DisclaimerPivot + { + Heading = disclaimer.Heading.Value, + Description = disclaimer.Description.Value, + }; + } + + // ── Pivot 2: ImageField + MultilistField (HomePage) ─────────────────── + var home = await _facade + .GetItemByPathAsync(rootPath, cancellationToken) + .ConfigureAwait(false); + + HomePivot? homePivot = null; + if (home is not null) + { + var cssLinks = home.HeadCssLinks.As(); + var firstParent = cssLinks.Count > 0 + ? cssLinks[0].Axes.Parent?.Path + : null; + + homePivot = new HomePivot + { + MetaThumbnailSrc = home.MetaThumbnail.Value?.Sources.Small, + MetaThumbnailAlt = home.MetaThumbnail.Value?.AlternativeText, + HeadCssLinksCount = cssLinks.Count, + HeadCssLinkValues = cssLinks.Select(l => l.Link.Value).ToList(), + FirstLinkParentPath = firstParent, + }; + } + + // ── Pivot 3: ItemReferenceField (DictionaryRoot) ────────────────────── + var dictRoot = await _facade + .GetItemByPathAsync(dictionaryPath, cancellationToken) + .ConfigureAwait(false); + + DictionaryPivot? dictionaryPivot = null; + if (dictRoot is not null) + { + var fallback = dictRoot.FallbackDomain.Value; + dictionaryPivot = new DictionaryPivot + { + FallbackDomainId = fallback?.Id, + FallbackDomainPath = fallback?.Path, + }; + } + + // ── Pivot 4: Axes traversal (on the home item) ──────────────────────── + AxesPivot? axesPivot = null; + if (home is not null) + { + var parent = home.Axes.Parent; + var children = home.Axes.Children; + var grandparent = parent?.Axes.Parent; + var siblings = home.Axes.Siblings; + var typedKids = home.Axes.GetChildren(); + + axesPivot = new AxesPivot + { + ItemPath = home.Path, + ParentPath = parent?.Path, + ChildrenCount = children?.Count ?? 0, + GrandparentPath = grandparent?.Path, + SiblingsCount = siblings?.Count ?? 0, + TypedChildrenCount = typedKids.Count, + }; + } + + // ── Pivot 5: GetDescendants (on the home item) ──────────────────────── + DescendantsPivot? descendantsPivot = null; + if (home is not null) + { + var descendants = home.Axes.GetDescendants