diff --git a/src/protagonist/API.Tests/Integration/CustomerQueueTests.cs b/src/protagonist/API.Tests/Integration/CustomerQueueTests.cs index b88d59b25..df2ab803c 100644 --- a/src/protagonist/API.Tests/Integration/CustomerQueueTests.cs +++ b/src/protagonist/API.Tests/Integration/CustomerQueueTests.cs @@ -8,6 +8,7 @@ using System.Threading; using API.Client; using API.Tests.Integration.Infrastructure; +using DLCS.AWS.SNS.Messaging; using DLCS.Core.Types; using DLCS.Model.Assets; using DLCS.Model.Policies; @@ -35,6 +36,7 @@ public class CustomerQueueTests : IClassFixture> private readonly DlcsContext dbContext; private readonly HttpClient httpClient; private static readonly IEngineClient EngineClient = A.Fake(); + private static readonly IBatchCompletedNotificationSender NotificationSender = A.Fake(); public CustomerQueueTests(DlcsDatabaseFixture dbFixture, ProtagonistAppFactory factory) { @@ -43,6 +45,7 @@ public CustomerQueueTests(DlcsDatabaseFixture dbFixture, ProtagonistAppFactory { + services.AddSingleton(NotificationSender); services.AddScoped(_ => EngineClient); services.AddAuthentication("API-Test") .AddScheme( @@ -1172,6 +1175,12 @@ public async Task Post_TestBatch_MarksBatchAsSuperseded_IfNoImagesFound() var dbBatch = await dbContext.Batches.SingleAsync(b => b.Id == 201); dbBatch.Superseded.Should().BeTrue(); + + A.CallTo(() => + NotificationSender.SendBatchCompletedMessage( + A.That.Matches(b => b.Id == dbBatch.Id), + A._)) + .MustHaveHappened(); } [Fact] @@ -1202,6 +1211,12 @@ await dbContext.Images.AddTestAsset(AssetId.FromString("2/1/fake"), batch: batch dbBatch.Count.Should().Be(4); dbBatch.Errors.Should().Be(1); dbBatch.Completed.Should().Be(3); + + A.CallTo(() => + NotificationSender.SendBatchCompletedMessage( + A.That.Matches(b => b.Id == dbBatch.Id), + A._)) + .MustHaveHappened(); } [Fact] @@ -1228,6 +1243,12 @@ public async Task Post_TestBatch_ReturnsFalse_IfNotSupersededOrFinished() dbBatch.Superseded.Should().BeFalse(); dbBatch.Finished.Should().BeNull(); dbBatch.Count.Should().Be(100); + + A.CallTo(() => + NotificationSender.SendBatchCompletedMessage( + A._, + A._)) + .MustNotHaveHappened(); } [Fact] @@ -1255,6 +1276,12 @@ public async Task Post_TestBatch_DoesNotChangeBatchFinished_IfImagesFoundAndAllF dbBatch.Superseded.Should().BeFalse(); dbBatch.Finished.Should().BeCloseTo(finished, TimeSpan.FromMinutes((1))); dbBatch.Count.Should().Be(3); + + A.CallTo(() => + NotificationSender.SendBatchCompletedMessage( + A.That.Matches(b => b.Id == dbBatch.Id), + A._)) + .MustNotHaveHappened(); } [Fact] diff --git a/src/protagonist/API/Features/Queues/Requests/TestBatch.cs b/src/protagonist/API/Features/Queues/Requests/TestBatch.cs index 1344d967e..9a1c6b7cb 100644 --- a/src/protagonist/API/Features/Queues/Requests/TestBatch.cs +++ b/src/protagonist/API/Features/Queues/Requests/TestBatch.cs @@ -1,5 +1,7 @@ using System.Collections; using System.Collections.Generic; +using DLCS.AWS.SNS; +using DLCS.AWS.SNS.Messaging; using DLCS.Model.Assets; using DLCS.Repository; using MediatR; @@ -27,12 +29,15 @@ public class TestBatchHandler : IRequestHandler { private readonly DlcsContext dlcsContext; private readonly ILogger logger; + private readonly IBatchCompletedNotificationSender batchCompletedNotificationSender; public TestBatchHandler( DlcsContext dlcsContext, + IBatchCompletedNotificationSender batchCompletedNotificationSender, ILogger logger) { this.dlcsContext = dlcsContext; + this.batchCompletedNotificationSender = batchCompletedNotificationSender; this.logger = logger; } @@ -63,6 +68,7 @@ public TestBatchHandler( logger.LogInformation("Batch {BatchId} complete but not finished. Setting Finished.", request.BatchId); changesMade = true; batch.Finished = DateTime.UtcNow; + await batchCompletedNotificationSender.SendBatchCompletedMessage(batch, cancellationToken); } if (batch.Count != batchImages.Count) diff --git a/src/protagonist/API/Infrastructure/ServiceCollectionX.cs b/src/protagonist/API/Infrastructure/ServiceCollectionX.cs index 224096ade..5419d98c0 100644 --- a/src/protagonist/API/Infrastructure/ServiceCollectionX.cs +++ b/src/protagonist/API/Infrastructure/ServiceCollectionX.cs @@ -2,13 +2,14 @@ using System.Reflection; using API.Features.Assets; using API.Features.Customer; -using API.Features.DeliveryChannels; using API.Features.DeliveryChannels.DataAccess; +using API.Infrastructure.Messaging; using API.Infrastructure.Requests.Pipelines; using DLCS.AWS.Configuration; using DLCS.AWS.ElasticTranscoder; using DLCS.AWS.S3; using DLCS.AWS.SNS; +using DLCS.AWS.SNS.Messaging; using DLCS.AWS.SQS; using DLCS.Core.Caching; using DLCS.Mediatr.Behaviours; @@ -154,4 +155,16 @@ public static IServiceCollection ConfigureSwagger(this IServiceCollection servic var xmlPath = Path.Combine(AppContext.BaseDirectory, xmlFile); c.IncludeXmlComments(xmlPath); }); + + /// + /// Add topic notifiers + /// + public static IServiceCollection AddTopicNotifiers(this IServiceCollection services) + { + services + .AddScoped() + .AddScoped(); + + return services; + } } \ No newline at end of file diff --git a/src/protagonist/API/Startup.cs b/src/protagonist/API/Startup.cs index 975a50e0b..bc709f40a 100644 --- a/src/protagonist/API/Startup.cs +++ b/src/protagonist/API/Startup.cs @@ -1,6 +1,5 @@ using System.Security.Claims; using API.Auth; -using API.Features.DeliveryChannels.Converters; using API.Features.DeliveryChannels.Validation; using API.Features.Image.Ingest; using API.Features.OriginStrategies.Credentials; @@ -75,7 +74,6 @@ public void ConfigureServices(IServiceCollection services) .AddDataAccess(configuration) .AddScoped() .AddScoped() - .AddScoped() .AddScoped() .AddScoped() .AddTransient() @@ -85,6 +83,7 @@ public void ConfigureServices(IServiceCollection services) .AddNamedQueriesCore() .AddAws(configuration, webHostEnvironment) .AddCorrelationIdHeaderPropagation() + .AddTopicNotifiers() .ConfigureSwagger(); services.AddHttpClient(client => diff --git a/src/protagonist/API/appsettings-Development-Example.json b/src/protagonist/API/appsettings-Development-Example.json index dc895d6e2..b22c810f7 100644 --- a/src/protagonist/API/appsettings-Development-Example.json +++ b/src/protagonist/API/appsettings-Development-Example.json @@ -22,7 +22,9 @@ "FileQueueName": "dlcs-file" }, "SNS": { - "AssetModifiedNotificationTopicArn": "arn:aws:sns:eu-west-1:{AWS account}:dlcsspinup-asset-modified-notifications" + "AssetModifiedNotificationTopicArn": "arn:aws:sns:eu-west-1:{AWS account}:dlcsspinup-asset-modified-notifications", + "CustomerCreatedTopicArn": "arn:aws:sns:{region}:{account}:{prefix}-customer-created", + "BatchCompletedTopicArn": "arn:aws:sns:{region}:{account}:{prefix}-batch-completion" } }, "DLCS": { diff --git a/src/protagonist/DLCS.AWS/SNS/BatchCompletedNotification.cs b/src/protagonist/DLCS.AWS/SNS/BatchCompletedNotification.cs new file mode 100644 index 000000000..671a280bf --- /dev/null +++ b/src/protagonist/DLCS.AWS/SNS/BatchCompletedNotification.cs @@ -0,0 +1,34 @@ +using DLCS.Model.Assets; + +namespace DLCS.AWS.SNS; + +public class BatchCompletedNotification +{ + public int Id { get; private set; } + + public int CustomerId { get; private set; } + + public int Total { get; private set; } + + public int Success { get; private set; } + + public int Errors { get; private set; } + + public bool Superseded { get; private set; } + + public DateTime Started { get; private set; } + + public DateTime? Finished { get; private set; } + + public BatchCompletedNotification(Batch completedBatch) + { + Id = completedBatch.Id; + CustomerId = completedBatch.Customer; + Total = completedBatch.Count; + Success = completedBatch.Completed; + Errors = completedBatch.Errors; + Superseded = completedBatch.Superseded; + Started = completedBatch.Submitted; + Finished = completedBatch.Finished; + } +} \ No newline at end of file diff --git a/src/protagonist/DLCS.AWS/SNS/ITopicPublisher.cs b/src/protagonist/DLCS.AWS/SNS/ITopicPublisher.cs index 707c55911..de08eebae 100644 --- a/src/protagonist/DLCS.AWS/SNS/ITopicPublisher.cs +++ b/src/protagonist/DLCS.AWS/SNS/ITopicPublisher.cs @@ -19,6 +19,13 @@ public Task PublishToAssetModifiedTopic(IReadOnlyListBoolean representing the overall success/failure status of request public Task PublishToCustomerCreatedTopic(CustomerCreatedNotification message, CancellationToken cancellationToken); + + /// + /// Asynchronously publishes a message to the Batch completed topic + /// + /// Boolean representing the overall success/failure status of request + public Task PublishToBatchCompletedTopic(BatchCompletedNotification message, + CancellationToken cancellationToken); } /// diff --git a/src/protagonist/DLCS.AWS/SNS/Messaging/BatchCompletedNotificationSender.cs b/src/protagonist/DLCS.AWS/SNS/Messaging/BatchCompletedNotificationSender.cs new file mode 100644 index 000000000..4de2fc036 --- /dev/null +++ b/src/protagonist/DLCS.AWS/SNS/Messaging/BatchCompletedNotificationSender.cs @@ -0,0 +1,25 @@ +using DLCS.Model.Assets; +using Microsoft.Extensions.Logging; + +namespace DLCS.AWS.SNS.Messaging; + +public class BatchCompletedNotificationSender : IBatchCompletedNotificationSender +{ + private readonly ITopicPublisher topicPublisher; + private readonly ILogger logger; + + public BatchCompletedNotificationSender(ITopicPublisher topicPublisher, + ILogger logger) + { + this.topicPublisher = topicPublisher; + this.logger = logger; + } + + public async Task SendBatchCompletedMessage(Batch batch, CancellationToken cancellationToken = default) + { + logger.LogDebug("Sending notification of creation of batch {Batch}", batch.Id); + + var batchCompletedNotification = new BatchCompletedNotification(batch); + await topicPublisher.PublishToBatchCompletedTopic(batchCompletedNotification, cancellationToken); + } +} \ No newline at end of file diff --git a/src/protagonist/DLCS.AWS/SNS/Messaging/IBatchCompletedNotificationSender.cs b/src/protagonist/DLCS.AWS/SNS/Messaging/IBatchCompletedNotificationSender.cs new file mode 100644 index 000000000..f9fc0a34a --- /dev/null +++ b/src/protagonist/DLCS.AWS/SNS/Messaging/IBatchCompletedNotificationSender.cs @@ -0,0 +1,11 @@ +using DLCS.Model.Assets; + +namespace DLCS.AWS.SNS.Messaging; + +public interface IBatchCompletedNotificationSender +{ + /// + /// Broadcast batch completed notification + /// + Task SendBatchCompletedMessage(Batch completedBatch, CancellationToken cancellationToken = default); +} \ No newline at end of file diff --git a/src/protagonist/DLCS.AWS/SNS/TopicPublisher.cs b/src/protagonist/DLCS.AWS/SNS/TopicPublisher.cs index 1312af0f1..3e99b3c42 100644 --- a/src/protagonist/DLCS.AWS/SNS/TopicPublisher.cs +++ b/src/protagonist/DLCS.AWS/SNS/TopicPublisher.cs @@ -69,6 +69,32 @@ public async Task PublishToCustomerCreatedTopic(CustomerCreatedNotificatio return await TryPublishRequest(request, cancellationToken); } + /// + public async Task PublishToBatchCompletedTopic(BatchCompletedNotification message, CancellationToken cancellationToken) + { + if (string.IsNullOrEmpty(snsSettings.BatchCompletedTopicArn)) + { + logger.LogWarning("Customer Created Topic Arn is not set - cannot send CustomerCreatedNotification"); + return false; + } + + var request = new PublishRequest + { + TopicArn = snsSettings.BatchCompletedTopicArn, + Message = JsonSerializer.Serialize(message, settings), + MessageAttributes = new Dictionary() + { + {"CustomerId", new MessageAttributeValue + { + StringValue = message.CustomerId.ToString(), + DataType = "String" + }} + } + }; + + return await TryPublishRequest(request, cancellationToken); + } + private Task PublishToAssetModifiedTopic(AssetModifiedNotification message, CancellationToken cancellationToken = default) { diff --git a/src/protagonist/DLCS.AWS/Settings/SNSSettings.cs b/src/protagonist/DLCS.AWS/Settings/SNSSettings.cs index 68549b037..276f1deaf 100644 --- a/src/protagonist/DLCS.AWS/Settings/SNSSettings.cs +++ b/src/protagonist/DLCS.AWS/Settings/SNSSettings.cs @@ -12,6 +12,11 @@ public class SNSSettings /// public string? CustomerCreatedTopicArn { get; set; } + /// + /// Name of the SNS topic for notifying that + /// + public string? BatchCompletedTopicArn { get; set; } + /// /// Service root for SNS. Only used if running LocalStack /// diff --git a/src/protagonist/Engine.Tests/Data/EngineAssetRepositoryTests.cs b/src/protagonist/Engine.Tests/Data/EngineAssetRepositoryTests.cs index bdccf7971..6ea2fe7bf 100644 --- a/src/protagonist/Engine.Tests/Data/EngineAssetRepositoryTests.cs +++ b/src/protagonist/Engine.Tests/Data/EngineAssetRepositoryTests.cs @@ -1,8 +1,11 @@ +using AngleSharp.Common; +using DLCS.AWS.SNS.Messaging; using DLCS.Core.Types; using DLCS.Model.Assets; using DLCS.Repository; using Engine.Data; using Engine.Tests.Integration.Infrastructure; +using FakeItEasy; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Logging.Abstractions; using Test.Helpers.Integration; @@ -16,6 +19,7 @@ public class EngineAssetRepositoryTests private readonly DlcsContext dbContext; private readonly DlcsContext contextForTests; private readonly EngineAssetRepository sut; + private readonly IBatchCompletedNotificationSender batchCompletedNotificationSender; public EngineAssetRepositoryTests(DlcsDatabaseFixture dbFixture) { @@ -23,8 +27,12 @@ public EngineAssetRepositoryTests(DlcsDatabaseFixture dbFixture) var optionsBuilder = new DbContextOptionsBuilder(); optionsBuilder.UseNpgsql(dbFixture.ConnectionString); + + batchCompletedNotificationSender = A.Fake(); + contextForTests = new DlcsContext(optionsBuilder.Options); - sut = new EngineAssetRepository(contextForTests, new NullLogger()); + sut = new EngineAssetRepository(contextForTests, batchCompletedNotificationSender, + new NullLogger()); } [Fact] @@ -81,6 +89,11 @@ public async Task UpdateIngestedAsset_ModifiedExistingAsset_NoBatch_Location_OrS updatedItem.Error.Should().Be("broken state"); updatedItem.Ingesting.Should().BeFalse(); updatedItem.Finished.Should().BeCloseTo(DateTime.UtcNow, TimeSpan.FromMinutes(1)); + A.CallTo(() => + batchCompletedNotificationSender.SendBatchCompletedMessage( + A._, + A._)) + .MustNotHaveHappened(); } [Fact] @@ -114,6 +127,11 @@ await dbContext.Images.AddTestAsset(assetId, width: 0, height: 0, duration: 0, updatedItem.Error.Should().Be(trackedAsset.Error); updatedItem.Ingesting.Should().BeFalse(); updatedItem.Finished.Should().BeCloseTo(DateTime.UtcNow, TimeSpan.FromMinutes(1)); + A.CallTo(() => + batchCompletedNotificationSender.SendBatchCompletedMessage( + A._, + A._)) + .MustNotHaveHappened(); } [Fact] @@ -148,6 +166,11 @@ public async Task UpdateIngestedAsset_ModifiedExistingAsset_IncludingMediaType_N updatedItem.Error.Should().Be("broken state"); updatedItem.Ingesting.Should().BeFalse(); updatedItem.Finished.Should().BeCloseTo(DateTime.UtcNow, TimeSpan.FromMinutes(1)); + A.CallTo(() => + batchCompletedNotificationSender.SendBatchCompletedMessage( + A._, + A._)) + .MustNotHaveHappened(); } [Fact] @@ -179,6 +202,11 @@ public async Task UpdateIngestedAsset_ModifiedExistingAsset_NoBatch_WithLocation var dbImageStorage = await dbContext.ImageStorages.SingleAsync(a => a.Id == assetId); dbImageStorage.Should().BeEquivalentTo(imageStorage, opts => opts.Excluding(s => s.LastChecked)); dbImageStorage.LastChecked.Should().BeCloseTo(imageStorage.LastChecked, TimeSpan.FromMinutes(1)); + A.CallTo(() => + batchCompletedNotificationSender.SendBatchCompletedMessage( + A._, + A._)) + .MustNotHaveHappened(); } [Fact] @@ -218,6 +246,11 @@ public async Task UpdateIngestedAsset_ModifiedExistingAsset_NoBatch_WithLocation var dbCustomerStorage = await dbContext.CustomerStorages.SingleAsync(cs => cs.Customer == 99 && cs.Space == 0); dbCustomerStorage.TotalSizeOfStoredImages.Should().Be(1510); dbCustomerStorage.TotalSizeOfThumbnails.Should().Be(2820); + A.CallTo(() => + batchCompletedNotificationSender.SendBatchCompletedMessage( + A._, + A._)) + .MustNotHaveHappened(); } [Fact] @@ -249,6 +282,11 @@ public async Task UpdateIngestedAsset_UpdatesBatch_IfError() updatedItem.Errors.Should().Be(2); updatedItem.Completed.Should().Be(1); updatedItem.Finished.Should().BeNull(); + A.CallTo(() => + batchCompletedNotificationSender.SendBatchCompletedMessage( + A._, + A._)) + .MustNotHaveHappened(); } [Trait("Category", "Manual")] @@ -343,6 +381,11 @@ public async Task UpdateIngestedAsset_UpdatesBatch_IfComplete() updatedItem.Errors.Should().Be(1); updatedItem.Completed.Should().Be(2); updatedItem.Finished.Should().BeNull(); + A.CallTo(() => + batchCompletedNotificationSender.SendBatchCompletedMessage( + A._, + A._)) + .MustNotHaveHappened(); } [Fact] @@ -377,6 +420,11 @@ public async Task UpdateIngestedAsset_DoesNotUpdateBatch_IfIngestNotFinished() var updatedImage = await dbContext.Images.SingleAsync(i => i.Id == assetId); updatedImage.Finished.Should().BeNull(); updatedImage.Ingesting.Should().BeTrue(); + A.CallTo(() => + batchCompletedNotificationSender.SendBatchCompletedMessage( + A._, + A._)) + .MustNotHaveHappened(); } [Theory] @@ -408,6 +456,11 @@ public async Task UpdateIngestedAsset_MarksBatchAsComplete_IfCompletedAndError_E var updatedItem = await dbContext.Batches.SingleAsync(b => b.Id == batchId); updatedItem.Finished.Should().NotBeNull(); + A.CallTo(() => + batchCompletedNotificationSender.SendBatchCompletedMessage( + A.That.Matches(b => b.Id == batchId), + A._)) + .MustHaveHappened(1, Times.Exactly); } [Fact] @@ -434,5 +487,10 @@ public async Task UpdateIngestedAsset_SavesError_IfBatchNotFound() var updatedItem = await dbContext.Images.AsNoTracking().SingleAsync(a => a.Id == assetId); updatedItem.Error.Should().Be("Unable to update batch associated with image"); + A.CallTo(() => + batchCompletedNotificationSender.SendBatchCompletedMessage( + A._, + A._)) + .MustNotHaveHappened(); } } diff --git a/src/protagonist/Engine/Data/EngineAssetRepository.cs b/src/protagonist/Engine/Data/EngineAssetRepository.cs index f74dd208c..652a15c5d 100644 --- a/src/protagonist/Engine/Data/EngineAssetRepository.cs +++ b/src/protagonist/Engine/Data/EngineAssetRepository.cs @@ -1,5 +1,5 @@ using System.Data; -using DLCS.Core.Strings; +using DLCS.AWS.SNS.Messaging; using DLCS.Core.Types; using DLCS.Model.Assets; using DLCS.Model.Storage; @@ -14,11 +14,16 @@ public class EngineAssetRepository : IEngineAssetRepository { private readonly DlcsContext dlcsContext; private readonly ILogger logger; + private readonly IBatchCompletedNotificationSender batchCompletedNotificationSender; - public EngineAssetRepository(DlcsContext dlcsContext, ILogger logger) + public EngineAssetRepository( + DlcsContext dlcsContext, + IBatchCompletedNotificationSender batchCompletedNotificationSender, + ILogger logger) { this.dlcsContext = dlcsContext; this.logger = logger; + this.batchCompletedNotificationSender = batchCompletedNotificationSender; } public async Task UpdateIngestedAsset(Asset asset, ImageLocation? imageLocation, ImageStorage? imageStorage, @@ -115,13 +120,20 @@ private async Task BatchSave(int batchId, CancellationToken cancellationTo try { var updatedRows = await dlcsContext.SaveChangesAsync(cancellationToken); - updatedRows += await TryFinishBatch(batchId, cancellationToken); + var finishedBatch = await TryFinishBatch(batchId, cancellationToken); if (transaction != null) { await transaction.CommitAsync(cancellationToken); } - + + if (finishedBatch != null) + { + updatedRows++; + await batchCompletedNotificationSender.SendBatchCompletedMessage(finishedBatch, + cancellationToken); + } + return updatedRows > 0; } finally @@ -163,11 +175,18 @@ private void UpdateAsset(Asset asset, bool ingestFinished) } } - private Task TryFinishBatch(int batchId, CancellationToken cancellationToken) - => dlcsContext.Batches + private async Task TryFinishBatch(int batchId, + CancellationToken cancellationToken) + { + var rowsAffected = await dlcsContext.Batches .Where(b => b.Id == batchId && b.Count == b.Completed + b.Errors) .UpdateFromQueryAsync(b => new Batch { Finished = DateTime.UtcNow }, cancellationToken); + return rowsAffected == 0 + ? null + : await dlcsContext.Batches.FindAsync(new object[] { batchId }, cancellationToken); + } + private async Task IncreaseCustomerStorage(ImageStorage imageStorage, CancellationToken cancellationToken) { try diff --git a/src/protagonist/Engine/Infrastructure/ServiceCollectionX.cs b/src/protagonist/Engine/Infrastructure/ServiceCollectionX.cs index a8413f4ab..dcadb2fc3 100644 --- a/src/protagonist/Engine/Infrastructure/ServiceCollectionX.cs +++ b/src/protagonist/Engine/Infrastructure/ServiceCollectionX.cs @@ -1,6 +1,8 @@ using DLCS.AWS.Configuration; using DLCS.AWS.ElasticTranscoder; using DLCS.AWS.S3; +using DLCS.AWS.SNS; +using DLCS.AWS.SNS.Messaging; using DLCS.AWS.SQS; using DLCS.Core.Caching; using DLCS.Core.FileSystem; @@ -49,9 +51,11 @@ public static IServiceCollection AddAws(this IServiceCollection services, .AddSingleton() .AddSingleton() .AddSingleton() + .AddScoped() .SetupAWS(configuration, webHostEnvironment) .WithAmazonS3() .WithAmazonSQS() + .WithAmazonSNS() .WithAmazonElasticTranscoder(); return services; @@ -164,4 +168,15 @@ public static IServiceCollection ConfigureHealthChecks(this IServiceCollection s return services; } + + /// + /// Add topic notifiers + /// + public static IServiceCollection AddTopicNotifiers(this IServiceCollection services) + { + services + .AddScoped(); + + return services; + } } \ No newline at end of file diff --git a/src/protagonist/Engine/Startup.cs b/src/protagonist/Engine/Startup.cs index 402c6af31..2f5cc5f6f 100644 --- a/src/protagonist/Engine/Startup.cs +++ b/src/protagonist/Engine/Startup.cs @@ -30,6 +30,7 @@ public void ConfigureServices(IServiceCollection services) .AddAssetIngestion(configuration.Get()) .AddDataAccess(configuration) .AddCaching(cachingSection.Get()) + .AddTopicNotifiers() .AddCorrelationIdHeaderPropagation() .ConfigureHealthChecks(); diff --git a/src/protagonist/Engine/appsettings-Development-Example.json b/src/protagonist/Engine/appsettings-Development-Example.json index 82c302344..625dc7524 100644 --- a/src/protagonist/Engine/appsettings-Development-Example.json +++ b/src/protagonist/Engine/appsettings-Development-Example.json @@ -46,6 +46,9 @@ "TimebasedQueueName": "dlcs-timebased", "TranscodeCompleteQueueName": "dlcs-transcode-complete", "FileQueueName": "dlcs-file" + }, + "SNS": { + "BatchCompletedTopicArn": "arn:aws:sns:{region}:{account}:{prefix}-batch-completion" } } }