-
Notifications
You must be signed in to change notification settings - Fork 33
Target .NET 8, rewrite webhook controller #113
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,64 +1,151 @@ | ||
| using System; | ||
| using System.Collections.Generic; | ||
| using System.Linq; | ||
| using Microsoft.AspNetCore.Mvc; | ||
| using Kontent.Ai.Delivery.Abstractions; | ||
| using Kontent.Ai.Delivery.Caching; | ||
| using Kontent.Ai.AspNetCore.Webhooks.Models; | ||
| using System.Threading.Tasks; | ||
| using System.Collections.Generic; | ||
|
|
||
| namespace Kontent.Ai.Boilerplate.Areas.WebHooks.Controllers | ||
| { | ||
| [Area("WebHooks")] | ||
| public class WebhooksController : Controller | ||
| { | ||
| private readonly IDeliveryCacheManager _cacheManager; | ||
|
|
||
| public WebhooksController(IDeliveryCacheManager cacheManager) | ||
| public WebhooksController(IDeliveryCacheManager cacheManager, IDeliveryClient deliveryClient) | ||
| { | ||
| _cacheManager = cacheManager ?? throw new ArgumentNullException(nameof(cacheManager)); | ||
| _deliveryClient = deliveryClient ?? throw new ArgumentNullException(nameof(deliveryClient)); | ||
| } | ||
|
|
||
| private readonly IDeliveryCacheManager _cacheManager; | ||
| private readonly IDeliveryClient _deliveryClient; | ||
|
|
||
| [HttpPost] | ||
| public async Task<IActionResult> Index([FromBody] DeliveryWebhookModel model) | ||
| public async Task<IActionResult> Index([FromBody] WebhookNotification? webhook) | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. shouldn't we support both until the 1.11.? or is it ok to cause this breaking now? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. the underlying package is https://github.com/kontent-ai/aspnetcore-extensions and it still supports legacy webhooks as well. the boilerplate should ideally use the most up-to-date approach and since we'll be removing legacy webhooks soon, I don't see a problem with adopting the new webhooks (wouldn't want users to scaffold a new project with webhooks that won't be a thing in a month). |
||
| { | ||
| if (model != null) | ||
| if (webhook is null) | ||
| { | ||
| var dependencies = new HashSet<string>(); | ||
| if (model.Data.Items?.Any() == true) | ||
| { | ||
| foreach (var item in model.Data.Items ?? Enumerable.Empty<DeliveryWebhookItem>()) | ||
| { | ||
| dependencies.Add(CacheHelpers.GetItemDependencyKey(item.Codename)); | ||
| } | ||
| return Ok(); | ||
| } | ||
|
|
||
| dependencies.Add(CacheHelpers.GetItemsDependencyKey()); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What about items dependencyKey? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. yes, this was way too simple. I added more complex logic with pattern matching to wipe the keys of (potentially) dependent entities |
||
| } | ||
| var dependencies = new HashSet<string>(StringComparer.Ordinal); | ||
| var usedInFetchTasks = new List<Task<IEnumerable<string>>>(); | ||
|
|
||
| if (model.Data.Taxonomies?.Any() == true) | ||
| { | ||
| foreach (var taxonomy in model.Data.Taxonomies ?? Enumerable.Empty<Taxonomy>()) | ||
| { | ||
| dependencies.Add(CacheHelpers.GetTaxonomyDependencyKey(taxonomy.Codename)); | ||
| } | ||
|
|
||
| dependencies.Add(CacheHelpers.GetTaxonomiesDependencyKey()); | ||
| dependencies.Add(CacheHelpers.GetItemsDependencyKey()); | ||
| dependencies.Add(CacheHelpers.GetTypesDependencyKey()); | ||
| } | ||
| foreach (var notification in webhook.Notifications ?? Enumerable.Empty<WebhookModel>()) | ||
| { | ||
| ProcessNotification(notification, dependencies, usedInFetchTasks); | ||
| } | ||
|
|
||
| if (model.Message.Type == "content_type") | ||
| { | ||
| dependencies.Add(CacheHelpers.GetTypesDependencyKey()); | ||
| } | ||
| await CollectUsedInDependencies(usedInFetchTasks, dependencies); | ||
| await InvalidateCacheDependencies(dependencies); | ||
|
|
||
| return Ok(); | ||
| } | ||
|
|
||
| private void ProcessNotification(WebhookModel notification, HashSet<string> dependencies, List<Task<IEnumerable<string>>> usedInFetchTasks) | ||
| { | ||
| switch (notification) | ||
| { | ||
| // Content item variant: invalidate the item and listing; on publish/unpublish also invalidate items that reference it | ||
| case { Message.ObjectType: "content_item", Data.System.Codename: var codename, Message.Action: var action }: | ||
| ProcessContentItemNotification(codename, action, dependencies, usedInFetchTasks); | ||
| break; | ||
|
|
||
| // Taxonomy change: for created, invalidate the specific taxonomy and listing, otherwise also invalidate items and type listings | ||
| case { Message.ObjectType: "taxonomy", Data.System.Codename: var codename, Message.Action: var action }: | ||
| ProcessTaxonomyNotification(codename, action, dependencies); | ||
| break; | ||
|
|
||
| // Content type change: invalidate types listing and items listing | ||
| case { Message.ObjectType: "content_type" }: | ||
| ProcessContentTypeNotification(dependencies); | ||
| break; | ||
|
|
||
| // Language change: invalidate languages listing | ||
| case { Message.ObjectType: "language" }: | ||
| ProcessLanguageNotification(dependencies); | ||
| break; | ||
|
|
||
| // Asset and unknown types: no Delivery SDK dependency helpers; skip | ||
| } | ||
| } | ||
|
|
||
| private void ProcessContentItemNotification(string codename, string action, HashSet<string> dependencies, List<Task<IEnumerable<string>>> usedInFetchTasks) | ||
| { | ||
| dependencies.Add(CacheHelpers.GetItemDependencyKey(codename)); | ||
| dependencies.Add(CacheHelpers.GetItemsDependencyKey()); | ||
|
|
||
| if (IsPublishOrUnpublishAction(action)) | ||
| { | ||
| usedInFetchTasks.Add(GetItemUsedInDependencyKeys(codename)); | ||
| } | ||
| } | ||
|
|
||
| private static void ProcessTaxonomyNotification(string codename, string action, HashSet<string> dependencies) | ||
| { | ||
| dependencies.Add(CacheHelpers.GetTaxonomyDependencyKey(codename)); | ||
| dependencies.Add(CacheHelpers.GetTaxonomiesDependencyKey()); | ||
|
|
||
| if (!IsCreatedAction(action)) | ||
| { | ||
| dependencies.Add(CacheHelpers.GetItemsDependencyKey()); | ||
| dependencies.Add(CacheHelpers.GetTypesDependencyKey()); | ||
| } | ||
| } | ||
|
|
||
| private static void ProcessContentTypeNotification(HashSet<string> dependencies) | ||
| { | ||
| dependencies.Add(CacheHelpers.GetTypesDependencyKey()); | ||
| dependencies.Add(CacheHelpers.GetItemsDependencyKey()); | ||
| } | ||
|
|
||
| private static void ProcessLanguageNotification(HashSet<string> dependencies) | ||
| { | ||
| dependencies.Add(CacheHelpers.GetLanguagesDependencyKey()); | ||
| } | ||
|
|
||
| private static bool IsPublishOrUnpublishAction(string action) => | ||
| string.Equals(action, "published", StringComparison.OrdinalIgnoreCase) || | ||
| string.Equals(action, "unpublished", StringComparison.OrdinalIgnoreCase); | ||
|
|
||
| private static bool IsCreatedAction(string action) => | ||
| string.Equals(action, "created", StringComparison.OrdinalIgnoreCase); | ||
|
|
||
| foreach (var dependency in dependencies) | ||
| private static async Task CollectUsedInDependencies(List<Task<IEnumerable<string>>> usedInFetchTasks, HashSet<string> dependencies) | ||
| { | ||
| if (usedInFetchTasks.Count == 0) return; | ||
|
|
||
| var usedInResults = await Task.WhenAll(usedInFetchTasks); | ||
| foreach (var key in usedInResults.SelectMany(result => result)) | ||
| { | ||
| dependencies.Add(key); | ||
| } | ||
| } | ||
|
|
||
| private async Task InvalidateCacheDependencies(HashSet<string> dependencies) | ||
| { | ||
| if (dependencies.Count == 0) return; | ||
|
|
||
| var invalidations = dependencies.Select(key => _cacheManager.InvalidateDependencyAsync(key)); | ||
| await Task.WhenAll(invalidations); | ||
| } | ||
|
|
||
| private async Task<IEnumerable<string>> GetItemUsedInDependencyKeys(string codename) | ||
| { | ||
| List<string> dependencyKeys = []; | ||
| var feed = _deliveryClient.GetItemUsedIn(codename); | ||
|
|
||
| while (feed.HasMoreResults) | ||
| { | ||
| IDeliveryItemsFeedResponse<IUsedInItem> response = await feed.FetchNextBatchAsync(); | ||
|
|
||
| foreach (IUsedInItem item in response.Items) | ||
| { | ||
| await _cacheManager.InvalidateDependencyAsync(dependency); | ||
| dependencyKeys.Add(CacheHelpers.GetItemDependencyKey(item.System.Codename)); | ||
| } | ||
| } | ||
|
|
||
| return Ok(); | ||
| return dependencyKeys; | ||
| } | ||
| } | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would keep the original constructor, rather then using this hybrid solution for field validation. but it is up to your decision.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
you might be right, especially since rest of the project doesn't use primary constructors either. reverting.