diff --git a/FictionArchive.Service.AuthenticationService/Controllers/AuthenticationWebhookController.cs b/FictionArchive.Service.AuthenticationService/Controllers/AuthenticationWebhookController.cs deleted file mode 100644 index 7ab6272..0000000 --- a/FictionArchive.Service.AuthenticationService/Controllers/AuthenticationWebhookController.cs +++ /dev/null @@ -1,36 +0,0 @@ -using FictionArchive.Service.AuthenticationService.Models.Requests; -using FictionArchive.Service.AuthenticationService.Models.IntegrationEvents; -using FictionArchive.Service.Shared.Services.EventBus; -using Microsoft.AspNetCore.Http; -using Microsoft.AspNetCore.Mvc; - -namespace FictionArchive.Service.AuthenticationService.Controllers -{ - [Route("api/[controller]")] - [ApiController] - public class AuthenticationWebhookController : ControllerBase - { - private readonly IEventBus _eventBus; - - public AuthenticationWebhookController(IEventBus eventBus) - { - _eventBus = eventBus; - } - - [HttpPost(nameof(UserRegistered))] - public async Task UserRegistered([FromBody] UserRegisteredWebhookPayload payload) - { - var authUserAddedEvent = new AuthUserAddedEvent - { - OAuthProviderId = payload.OAuthProviderId, - InviterOAuthProviderId = payload.InviterOAuthProviderId, - EventUserEmail = payload.EventUserEmail, - EventUserUsername = payload.EventUserUsername - }; - - await _eventBus.Publish(authUserAddedEvent); - - return Ok(); - } - } -} \ No newline at end of file diff --git a/FictionArchive.Service.AuthenticationService/Dockerfile b/FictionArchive.Service.AuthenticationService/Dockerfile deleted file mode 100644 index 5907b71..0000000 --- a/FictionArchive.Service.AuthenticationService/Dockerfile +++ /dev/null @@ -1,23 +0,0 @@ -FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS base -USER $APP_UID -WORKDIR /app -EXPOSE 8080 -EXPOSE 8081 - -FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build -ARG BUILD_CONFIGURATION=Release -WORKDIR /src -COPY ["FictionArchive.Service.AuthenticationService/FictionArchive.Service.AuthenticationService.csproj", "FictionArchive.Service.AuthenticationService/"] -RUN dotnet restore "FictionArchive.Service.AuthenticationService/FictionArchive.Service.AuthenticationService.csproj" -COPY . . -WORKDIR "/src/FictionArchive.Service.AuthenticationService" -RUN dotnet build "./FictionArchive.Service.AuthenticationService.csproj" -c $BUILD_CONFIGURATION -o /app/build - -FROM build AS publish -ARG BUILD_CONFIGURATION=Release -RUN dotnet publish "./FictionArchive.Service.AuthenticationService.csproj" -c $BUILD_CONFIGURATION -o /app/publish /p:UseAppHost=false - -FROM base AS final -WORKDIR /app -COPY --from=publish /app/publish . -ENTRYPOINT ["dotnet", "FictionArchive.Service.AuthenticationService.dll"] diff --git a/FictionArchive.Service.AuthenticationService/FictionArchive.Service.AuthenticationService.csproj b/FictionArchive.Service.AuthenticationService/FictionArchive.Service.AuthenticationService.csproj deleted file mode 100644 index 6ac3804..0000000 --- a/FictionArchive.Service.AuthenticationService/FictionArchive.Service.AuthenticationService.csproj +++ /dev/null @@ -1,29 +0,0 @@ - - - - net8.0 - enable - enable - Linux - - - - - - - - - - .dockerignore - - - - - - - - - - - - diff --git a/FictionArchive.Service.AuthenticationService/FictionArchive.Service.AuthenticationService.http b/FictionArchive.Service.AuthenticationService/FictionArchive.Service.AuthenticationService.http deleted file mode 100644 index 8668e05..0000000 --- a/FictionArchive.Service.AuthenticationService/FictionArchive.Service.AuthenticationService.http +++ /dev/null @@ -1,6 +0,0 @@ -@FictionArchive.Service.AuthenticationService_HostAddress = http://localhost:5091 - -GET {{FictionArchive.Service.AuthenticationService_HostAddress}}/weatherforecast/ -Accept: application/json - -### diff --git a/FictionArchive.Service.AuthenticationService/Models/IntegrationEvents/AuthUserAddedEvent.cs b/FictionArchive.Service.AuthenticationService/Models/IntegrationEvents/AuthUserAddedEvent.cs deleted file mode 100644 index 93ace36..0000000 --- a/FictionArchive.Service.AuthenticationService/Models/IntegrationEvents/AuthUserAddedEvent.cs +++ /dev/null @@ -1,16 +0,0 @@ -using FictionArchive.Service.Shared.Services.EventBus; - -namespace FictionArchive.Service.AuthenticationService.Models.IntegrationEvents; - -public class AuthUserAddedEvent : IIntegrationEvent -{ - public string OAuthProviderId { get; set; } - - public string InviterOAuthProviderId { get; set; } - - // The email of the user that created the event - public string EventUserEmail { get; set; } - - // The username of the user that created the event - public string EventUserUsername { get; set; } -} \ No newline at end of file diff --git a/FictionArchive.Service.AuthenticationService/Models/Requests/UserRegisteredWebhookPayload.cs b/FictionArchive.Service.AuthenticationService/Models/Requests/UserRegisteredWebhookPayload.cs deleted file mode 100644 index fffcb1c..0000000 --- a/FictionArchive.Service.AuthenticationService/Models/Requests/UserRegisteredWebhookPayload.cs +++ /dev/null @@ -1,17 +0,0 @@ -namespace FictionArchive.Service.AuthenticationService.Models.Requests; - -public class UserRegisteredWebhookPayload -{ - // The body of the notification message - public string Body { get; set; } - - public string OAuthProviderId { get; set; } - - public string InviterOAuthProviderId { get; set; } - - // The email of the user that created the event - public string EventUserEmail { get; set; } - - // The username of the user that created the event - public string EventUserUsername { get; set; } -} \ No newline at end of file diff --git a/FictionArchive.Service.AuthenticationService/Program.cs b/FictionArchive.Service.AuthenticationService/Program.cs deleted file mode 100644 index 897d20e..0000000 --- a/FictionArchive.Service.AuthenticationService/Program.cs +++ /dev/null @@ -1,49 +0,0 @@ -using FictionArchive.Service.Shared; -using FictionArchive.Service.Shared.Services.EventBus.Implementations; - -namespace FictionArchive.Service.AuthenticationService; - -public class Program -{ - public static void Main(string[] args) - { - var builder = WebApplication.CreateBuilder(args); - - // Add services to the container. - - builder.Services.AddControllers(); - // Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle - builder.Services.AddEndpointsApiExplorer(); - builder.Services.AddSwaggerGen(); - - #region Event Bus - - builder.Services.AddRabbitMQ(opt => - { - builder.Configuration.GetSection("RabbitMQ").Bind(opt); - }); - - #endregion - - builder.Services.AddHealthChecks(); - - var app = builder.Build(); - - // Configure the HTTP request pipeline. - if (app.Environment.IsDevelopment()) - { - app.UseSwagger(); - app.UseSwaggerUI(); - } - - app.UseHttpsRedirection(); - - app.MapHealthChecks("/healthz"); - - app.UseAuthorization(); - - app.MapControllers(); - - app.Run(); - } -} \ No newline at end of file diff --git a/FictionArchive.Service.AuthenticationService/Properties/launchSettings.json b/FictionArchive.Service.AuthenticationService/Properties/launchSettings.json deleted file mode 100644 index c6a404f..0000000 --- a/FictionArchive.Service.AuthenticationService/Properties/launchSettings.json +++ /dev/null @@ -1,41 +0,0 @@ -{ - "$schema": "http://json.schemastore.org/launchsettings.json", - "iisSettings": { - "windowsAuthentication": false, - "anonymousAuthentication": true, - "iisExpress": { - "applicationUrl": "http://localhost:23522", - "sslPort": 44397 - } - }, - "profiles": { - "http": { - "commandName": "Project", - "dotnetRunMessages": true, - "launchBrowser": true, - "launchUrl": "swagger", - "applicationUrl": "http://localhost:5091", - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development" - } - }, - "https": { - "commandName": "Project", - "dotnetRunMessages": true, - "launchBrowser": true, - "launchUrl": "swagger", - "applicationUrl": "https://localhost:7223;http://localhost:5091", - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development" - } - }, - "IIS Express": { - "commandName": "IISExpress", - "launchBrowser": true, - "launchUrl": "swagger", - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development" - } - } - } -} diff --git a/FictionArchive.Service.AuthenticationService/appsettings.Development.json b/FictionArchive.Service.AuthenticationService/appsettings.Development.json deleted file mode 100644 index 0c208ae..0000000 --- a/FictionArchive.Service.AuthenticationService/appsettings.Development.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "Logging": { - "LogLevel": { - "Default": "Information", - "Microsoft.AspNetCore": "Warning" - } - } -} diff --git a/FictionArchive.Service.AuthenticationService/appsettings.json b/FictionArchive.Service.AuthenticationService/appsettings.json deleted file mode 100644 index 9087a1b..0000000 --- a/FictionArchive.Service.AuthenticationService/appsettings.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "Logging": { - "LogLevel": { - "Default": "Information", - "Microsoft.AspNetCore": "Warning" - } - }, - "RabbitMQ": { - "ConnectionString": "amqp://localhost", - "ClientIdentifier": "AuthenticationService" - }, - "AllowedHosts": "*" -} diff --git a/FictionArchive.Service.FileService/Consumers/FileUploadRequestCreatedConsumer.cs b/FictionArchive.Service.FileService/Consumers/FileUploadRequestCreatedConsumer.cs new file mode 100644 index 0000000..0825358 --- /dev/null +++ b/FictionArchive.Service.FileService/Consumers/FileUploadRequestCreatedConsumer.cs @@ -0,0 +1,74 @@ +using Amazon.S3; +using Amazon.S3.Model; +using FictionArchive.Common.Enums; +using FictionArchive.Service.FileService.Contracts; +using FictionArchive.Service.FileService.Models; +using FictionArchive.Service.Shared.Contracts.Events; +using MassTransit; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; + +namespace FictionArchive.Service.FileService.Consumers; + +public class FileUploadRequestCreatedConsumer : IConsumer +{ + private readonly ILogger _logger; + private readonly AmazonS3Client _amazonS3Client; + private readonly IPublishEndpoint _publishEndpoint; + private readonly S3Configuration _s3Configuration; + private readonly ProxyConfiguration _proxyConfiguration; + + public FileUploadRequestCreatedConsumer( + ILogger logger, + AmazonS3Client amazonS3Client, + IPublishEndpoint publishEndpoint, + IOptions s3Configuration, + IOptions proxyConfiguration) + { + _logger = logger; + _amazonS3Client = amazonS3Client; + _publishEndpoint = publishEndpoint; + _s3Configuration = s3Configuration.Value; + _proxyConfiguration = proxyConfiguration.Value; + } + + public async Task Consume(ConsumeContext context) + { + var message = context.Message; + + var putObjectRequest = new PutObjectRequest + { + BucketName = _s3Configuration.Bucket, + Key = message.FilePath + }; + + using var memoryStream = new MemoryStream(message.FileData); + putObjectRequest.InputStream = memoryStream; + + var s3Response = await _amazonS3Client.PutObjectAsync(putObjectRequest); + + if (s3Response.HttpStatusCode != System.Net.HttpStatusCode.OK) + { + _logger.LogError("Failed to upload file {FilePath} to S3", message.FilePath); + + await _publishEndpoint.Publish( + new FileUploadRequestStatusUpdate( + RequestId: message.RequestId, + Status: RequestStatus.Failed, + FileAccessUrl: null, + ErrorMessage: "An error occurred while uploading file to S3.")); + return; + } + + var fileAccessUrl = _proxyConfiguration.BaseUrl + "/" + message.FilePath; + + _logger.LogInformation("Successfully uploaded file {FilePath} to S3", message.FilePath); + + await _publishEndpoint.Publish( + new FileUploadRequestStatusUpdate( + RequestId: message.RequestId, + Status: RequestStatus.Success, + FileAccessUrl: fileAccessUrl, + ErrorMessage: null)); + } +} diff --git a/FictionArchive.Service.FileService/Contracts/FileUploadRequestStatusUpdate.cs b/FictionArchive.Service.FileService/Contracts/FileUploadRequestStatusUpdate.cs new file mode 100644 index 0000000..dcb1eef --- /dev/null +++ b/FictionArchive.Service.FileService/Contracts/FileUploadRequestStatusUpdate.cs @@ -0,0 +1,10 @@ +using FictionArchive.Common.Enums; +using FictionArchive.Service.Shared.Contracts.Events; + +namespace FictionArchive.Service.FileService.Contracts; + +public record FileUploadRequestStatusUpdate( + Guid RequestId, + RequestStatus Status, + string? FileAccessUrl, + string? ErrorMessage) : IFileUploadRequestStatusUpdate; diff --git a/FictionArchive.Service.FileService/Models/IntegrationEvents/FileUploadRequestCreatedEvent.cs b/FictionArchive.Service.FileService/Models/IntegrationEvents/FileUploadRequestCreatedEvent.cs deleted file mode 100644 index bf03a56..0000000 --- a/FictionArchive.Service.FileService/Models/IntegrationEvents/FileUploadRequestCreatedEvent.cs +++ /dev/null @@ -1,10 +0,0 @@ -using FictionArchive.Service.Shared.Services.EventBus; - -namespace FictionArchive.Service.FileService.Models.IntegrationEvents; - -public class FileUploadRequestCreatedEvent : IIntegrationEvent -{ - public Guid RequestId { get; set; } - public string FilePath { get; set; } - public byte[] FileData { get; set; } -} \ No newline at end of file diff --git a/FictionArchive.Service.FileService/Models/IntegrationEvents/FileUploadRequestStatusUpdateEvent.cs b/FictionArchive.Service.FileService/Models/IntegrationEvents/FileUploadRequestStatusUpdateEvent.cs deleted file mode 100644 index b5936de..0000000 --- a/FictionArchive.Service.FileService/Models/IntegrationEvents/FileUploadRequestStatusUpdateEvent.cs +++ /dev/null @@ -1,22 +0,0 @@ -using FictionArchive.Common.Enums; -using FictionArchive.Service.Shared.Services.EventBus; - -namespace FictionArchive.Service.FileService.Models.IntegrationEvents; - -public class FileUploadRequestStatusUpdateEvent : IIntegrationEvent -{ - public Guid RequestId { get; set; } - public RequestStatus Status { get; set; } - - #region Success - - public string? FileAccessUrl { get; set; } - - #endregion - - #region Failure - - public string? ErrorMessage { get; set; } - - #endregion -} \ No newline at end of file diff --git a/FictionArchive.Service.FileService/Program.cs b/FictionArchive.Service.FileService/Program.cs index 7d8ac6d..da6a8b8 100644 --- a/FictionArchive.Service.FileService/Program.cs +++ b/FictionArchive.Service.FileService/Program.cs @@ -1,11 +1,9 @@ using Amazon.Runtime; using Amazon.S3; using FictionArchive.Common.Extensions; +using FictionArchive.Service.FileService.Consumers; using FictionArchive.Service.FileService.Models; -using FictionArchive.Service.FileService.Models.IntegrationEvents; -using FictionArchive.Service.FileService.Services.EventHandlers; using FictionArchive.Service.Shared.Extensions; -using FictionArchive.Service.Shared.Services.EventBus.Implementations; using Microsoft.Extensions.Options; namespace FictionArchive.Service.FileService; @@ -24,14 +22,15 @@ public class Program builder.Services.AddHealthChecks(); - #region Event Bus + #region MassTransit + + builder.Services.AddFictionArchiveMassTransit( + builder.Configuration, + x => + { + x.AddConsumer(); + }); - builder.Services.AddRabbitMQ(opt => - { - builder.Configuration.GetSection("RabbitMQ").Bind(opt); - }) - .Subscribe(); - #endregion // Add authentication with cookie support diff --git a/FictionArchive.Service.FileService/Services/EventHandlers/FileUploadRequestCreatedEventHandler.cs b/FictionArchive.Service.FileService/Services/EventHandlers/FileUploadRequestCreatedEventHandler.cs deleted file mode 100644 index 0383030..0000000 --- a/FictionArchive.Service.FileService/Services/EventHandlers/FileUploadRequestCreatedEventHandler.cs +++ /dev/null @@ -1,58 +0,0 @@ -using Amazon.S3; -using Amazon.S3.Model; -using FictionArchive.Common.Enums; -using FictionArchive.Service.FileService.Models; -using FictionArchive.Service.FileService.Models.IntegrationEvents; -using FictionArchive.Service.Shared.Services.EventBus; -using Microsoft.Extensions.Options; - -namespace FictionArchive.Service.FileService.Services.EventHandlers; - -public class FileUploadRequestCreatedEventHandler : IIntegrationEventHandler -{ - private readonly ILogger _logger; - private readonly AmazonS3Client _amazonS3Client; - private readonly IEventBus _eventBus; - private readonly S3Configuration _s3Configuration; - private readonly ProxyConfiguration _proxyConfiguration; - - public FileUploadRequestCreatedEventHandler(ILogger logger, AmazonS3Client amazonS3Client, IEventBus eventBus, IOptions s3Configuration, IOptions proxyConfiguration) - { - _logger = logger; - _amazonS3Client = amazonS3Client; - _eventBus = eventBus; - _proxyConfiguration = proxyConfiguration.Value; - _s3Configuration = s3Configuration.Value; - } - - public async Task Handle(FileUploadRequestCreatedEvent @event) - { - var putObjectRequest = new PutObjectRequest(); - putObjectRequest.BucketName = _s3Configuration.Bucket; - putObjectRequest.Key = @event.FilePath; - putObjectRequest.UseChunkEncoding = false; // Needed to avoid an error with Garage - - using MemoryStream memoryStream = new MemoryStream(@event.FileData); - putObjectRequest.InputStream = memoryStream; - - var s3Response = await _amazonS3Client.PutObjectAsync(putObjectRequest); - if (s3Response.HttpStatusCode != System.Net.HttpStatusCode.OK) - { - _logger.LogError("An error occurred while uploading file to S3. Response code: {responsecode}", s3Response.HttpStatusCode); - await _eventBus.Publish(new FileUploadRequestStatusUpdateEvent() - { - RequestId = @event.RequestId, - Status = RequestStatus.Failed, - ErrorMessage = "An error occurred while uploading file to S3." - }); - return; - } - - await _eventBus.Publish(new FileUploadRequestStatusUpdateEvent() - { - Status = RequestStatus.Success, - RequestId = @event.RequestId, - FileAccessUrl = _proxyConfiguration.BaseUrl + "/" + @event.FilePath - }); - } -} \ No newline at end of file diff --git a/FictionArchive.Service.FileService/appsettings.json b/FictionArchive.Service.FileService/appsettings.json index 0ec37b4..87fabdd 100644 --- a/FictionArchive.Service.FileService/appsettings.json +++ b/FictionArchive.Service.FileService/appsettings.json @@ -2,7 +2,8 @@ "Logging": { "LogLevel": { "Default": "Information", - "Microsoft.AspNetCore": "Warning" + "Microsoft.AspNetCore": "Warning", + "Microsoft.EntityFrameworkCore": "Warning" } }, "ProxyConfiguration": { diff --git a/FictionArchive.Service.NovelService.Tests/NovelUpdateServiceTests.cs b/FictionArchive.Service.NovelService.Tests/NovelUpdateServiceTests.cs index d5d2790..903d450 100644 --- a/FictionArchive.Service.NovelService.Tests/NovelUpdateServiceTests.cs +++ b/FictionArchive.Service.NovelService.Tests/NovelUpdateServiceTests.cs @@ -1,5 +1,4 @@ using FictionArchive.Common.Enums; -using FictionArchive.Service.FileService.IntegrationEvents; using FictionArchive.Service.NovelService.Models.Configuration; using FictionArchive.Service.NovelService.Models.Enums; using FictionArchive.Service.NovelService.Models.Images; @@ -8,9 +7,10 @@ using FictionArchive.Service.NovelService.Models.Novels; using FictionArchive.Service.NovelService.Models.SourceAdapters; using FictionArchive.Service.NovelService.Services; using FictionArchive.Service.NovelService.Services.SourceAdapters; -using FictionArchive.Service.Shared.Services.EventBus; +using FictionArchive.Service.Shared.Contracts.Events; using FluentAssertions; using HtmlAgilityPack; +using MassTransit; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Logging.Abstractions; using Microsoft.Extensions.Options; @@ -72,7 +72,7 @@ public class NovelUpdateServiceTests private static NovelUpdateService CreateService( NovelServiceDbContext dbContext, ISourceAdapter adapter, - IEventBus eventBus, + IPublishEndpoint publishEndpoint, string pendingImageUrl = "https://pending/placeholder.jpg") { var options = Options.Create(new NovelUpdateServiceConfiguration @@ -80,7 +80,7 @@ public class NovelUpdateServiceTests PendingImageUrl = pendingImageUrl }); - return new NovelUpdateService(dbContext, NullLogger.Instance, new[] { adapter }, eventBus, options); + return new NovelUpdateService(dbContext, NullLogger.Instance, new[] { adapter }, publishEndpoint, options); } [Fact] @@ -102,13 +102,13 @@ public class NovelUpdateServiceTests ImageData = new List { image1, image2 } })); - var publishedEvents = new List(); - var eventBus = Substitute.For(); - eventBus.Publish(Arg.Do(publishedEvents.Add)).Returns(Task.CompletedTask); - eventBus.Publish(Arg.Any(), Arg.Any()).Returns(Task.CompletedTask); + var publishedEvents = new List(); + var publishEndpoint = Substitute.For(); + publishEndpoint.Publish(Arg.Do(e => publishedEvents.Add(e)), Arg.Any()) + .Returns(Task.CompletedTask); var pendingImageUrl = "https://pending/placeholder.jpg"; - var service = CreateService(dbContext, adapter, eventBus, pendingImageUrl); + var service = CreateService(dbContext, adapter, publishEndpoint, pendingImageUrl); var updatedChapter = await service.PullChapterContents(novel.Id, volume.Id, chapter.Order); @@ -151,11 +151,9 @@ public class NovelUpdateServiceTests ImageData = new List { image } })); - var eventBus = Substitute.For(); - eventBus.Publish(Arg.Any()).Returns(Task.CompletedTask); - eventBus.Publish(Arg.Any(), Arg.Any()).Returns(Task.CompletedTask); + var publishEndpoint = Substitute.For(); - var service = CreateService(dbContext, adapter, eventBus); + var service = CreateService(dbContext, adapter, publishEndpoint); var updatedChapter = await service.PullChapterContents(novel.Id, volume.Id, chapter.Order); @@ -186,8 +184,8 @@ public class NovelUpdateServiceTests await dbContext.SaveChangesAsync(); var adapter = Substitute.For(); - var eventBus = Substitute.For(); - var service = CreateService(dbContext, adapter, eventBus); + var publishEndpoint = Substitute.For(); + var service = CreateService(dbContext, adapter, publishEndpoint); var newUrl = "https://cdn.example.com/uploaded/cover.jpg"; @@ -228,8 +226,8 @@ public class NovelUpdateServiceTests await dbContext.SaveChangesAsync(); var adapter = Substitute.For(); - var eventBus = Substitute.For(); - var service = CreateService(dbContext, adapter, eventBus, pendingUrl); + var publishEndpoint = Substitute.For(); + var service = CreateService(dbContext, adapter, publishEndpoint, pendingUrl); var newUrl = "https://cdn.example.com/uploaded/image.jpg"; @@ -277,8 +275,8 @@ public class NovelUpdateServiceTests await dbContext.SaveChangesAsync(); var adapter = Substitute.For(); - var eventBus = Substitute.For(); - var service = CreateService(dbContext, adapter, eventBus, pendingUrl); + var publishEndpoint = Substitute.For(); + var service = CreateService(dbContext, adapter, publishEndpoint, pendingUrl); var newUrl = "https://cdn.example.com/uploaded/img1.jpg"; diff --git a/FictionArchive.Service.NovelService/Consumers/ChapterPullRequestedConsumer.cs b/FictionArchive.Service.NovelService/Consumers/ChapterPullRequestedConsumer.cs new file mode 100644 index 0000000..117124d --- /dev/null +++ b/FictionArchive.Service.NovelService/Consumers/ChapterPullRequestedConsumer.cs @@ -0,0 +1,26 @@ +using FictionArchive.Service.NovelService.Services; +using FictionArchive.Service.Shared.Contracts.Events; +using MassTransit; +using Microsoft.Extensions.Logging; + +namespace FictionArchive.Service.NovelService.Consumers; + +public class ChapterPullRequestedConsumer : IConsumer +{ + private readonly ILogger _logger; + private readonly NovelUpdateService _novelUpdateService; + + public ChapterPullRequestedConsumer( + ILogger logger, + NovelUpdateService novelUpdateService) + { + _logger = logger; + _novelUpdateService = novelUpdateService; + } + + public async Task Consume(ConsumeContext context) + { + var message = context.Message; + await _novelUpdateService.PullChapterContents(message.NovelId, message.VolumeId, message.ChapterOrder); + } +} diff --git a/FictionArchive.Service.NovelService/Consumers/FileUploadRequestStatusUpdateConsumer.cs b/FictionArchive.Service.NovelService/Consumers/FileUploadRequestStatusUpdateConsumer.cs new file mode 100644 index 0000000..c25b3f5 --- /dev/null +++ b/FictionArchive.Service.NovelService/Consumers/FileUploadRequestStatusUpdateConsumer.cs @@ -0,0 +1,47 @@ +using FictionArchive.Common.Enums; +using FictionArchive.Service.NovelService.Services; +using FictionArchive.Service.Shared.Contracts.Events; +using MassTransit; +using Microsoft.Extensions.Logging; + +namespace FictionArchive.Service.NovelService.Consumers; + +public class FileUploadRequestStatusUpdateConsumer : IConsumer +{ + private readonly ILogger _logger; + private readonly NovelServiceDbContext _dbContext; + private readonly NovelUpdateService _novelUpdateService; + + public FileUploadRequestStatusUpdateConsumer( + ILogger logger, + NovelServiceDbContext dbContext, + NovelUpdateService novelUpdateService) + { + _logger = logger; + _dbContext = dbContext; + _novelUpdateService = novelUpdateService; + } + + public async Task Consume(ConsumeContext context) + { + var message = context.Message; + + var image = await _dbContext.Images.FindAsync(message.RequestId); + if (image == null) + { + // Not a request we care about. + return; + } + + if (message.Status == RequestStatus.Failed) + { + _logger.LogError("Image upload failed for image with id {imageId}", image.Id); + return; + } + else if (message.Status == RequestStatus.Success) + { + _logger.LogInformation("Image upload succeeded for image with id {imageId}", image.Id); + await _novelUpdateService.UpdateImage(image.Id, message.FileAccessUrl); + } + } +} diff --git a/FictionArchive.Service.NovelService/Consumers/NovelUpdateRequestedConsumer.cs b/FictionArchive.Service.NovelService/Consumers/NovelUpdateRequestedConsumer.cs new file mode 100644 index 0000000..d09ccec --- /dev/null +++ b/FictionArchive.Service.NovelService/Consumers/NovelUpdateRequestedConsumer.cs @@ -0,0 +1,26 @@ +using FictionArchive.Service.NovelService.Services; +using FictionArchive.Service.Shared.Contracts.Events; +using MassTransit; +using Microsoft.Extensions.Logging; + +namespace FictionArchive.Service.NovelService.Consumers; + +public class NovelUpdateRequestedConsumer : IConsumer +{ + private readonly ILogger _logger; + private readonly NovelUpdateService _novelUpdateService; + + public NovelUpdateRequestedConsumer( + ILogger logger, + NovelUpdateService novelUpdateService) + { + _logger = logger; + _novelUpdateService = novelUpdateService; + } + + public async Task Consume(ConsumeContext context) + { + var message = context.Message; + await _novelUpdateService.ImportNovel(message.NovelUrl); + } +} diff --git a/FictionArchive.Service.NovelService/Consumers/TranslationRequestCompletedConsumer.cs b/FictionArchive.Service.NovelService/Consumers/TranslationRequestCompletedConsumer.cs new file mode 100644 index 0000000..af58abf --- /dev/null +++ b/FictionArchive.Service.NovelService/Consumers/TranslationRequestCompletedConsumer.cs @@ -0,0 +1,48 @@ +using FictionArchive.Service.NovelService.Models.Localization; +using FictionArchive.Service.NovelService.Services; +using FictionArchive.Service.Shared.Contracts.Events; +using MassTransit; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; + +namespace FictionArchive.Service.NovelService.Consumers; + +public class TranslationRequestCompletedConsumer : IConsumer +{ + private readonly ILogger _logger; + private readonly NovelServiceDbContext _dbContext; + + public TranslationRequestCompletedConsumer( + ILogger logger, + NovelServiceDbContext dbContext) + { + _logger = logger; + _dbContext = dbContext; + } + + public async Task Consume(ConsumeContext context) + { + var message = context.Message; + + var localizationRequest = await _dbContext.LocalizationRequests + .Include(r => r.KeyRequestedForTranslation) + .ThenInclude(lk => lk.Texts) + .FirstOrDefaultAsync(lk => lk.Id == message.TranslationRequestId); + + if (localizationRequest == null) + { + // Not one of our requests, discard it + return; + } + + localizationRequest.KeyRequestedForTranslation.Texts.Add(new LocalizationText + { + Language = localizationRequest.TranslateTo, + Text = message.TranslatedText, + TranslationEngine = localizationRequest.Engine + }); + + _dbContext.LocalizationRequests.Remove(localizationRequest); + await _dbContext.SaveChangesAsync(); + } +} diff --git a/FictionArchive.Service.NovelService/Contracts/ChapterCreated.cs b/FictionArchive.Service.NovelService/Contracts/ChapterCreated.cs new file mode 100644 index 0000000..6b413cb --- /dev/null +++ b/FictionArchive.Service.NovelService/Contracts/ChapterCreated.cs @@ -0,0 +1,11 @@ +using FictionArchive.Service.Shared.Contracts.Events; + +namespace FictionArchive.Service.NovelService.Contracts; + +public record ChapterCreated( + uint ChapterId, + uint NovelId, + uint VolumeId, + uint VolumeOrder, + uint ChapterOrder, + string ChapterTitle) : IChapterCreated; diff --git a/FictionArchive.Service.NovelService/Contracts/ChapterPullRequested.cs b/FictionArchive.Service.NovelService/Contracts/ChapterPullRequested.cs new file mode 100644 index 0000000..994975e --- /dev/null +++ b/FictionArchive.Service.NovelService/Contracts/ChapterPullRequested.cs @@ -0,0 +1,8 @@ +using FictionArchive.Service.Shared.Contracts.Events; + +namespace FictionArchive.Service.NovelService.Contracts; + +public record ChapterPullRequested( + uint NovelId, + uint VolumeId, + uint ChapterOrder) : IChapterPullRequested; diff --git a/FictionArchive.Service.NovelService/Contracts/FileUploadRequestCreated.cs b/FictionArchive.Service.NovelService/Contracts/FileUploadRequestCreated.cs new file mode 100644 index 0000000..b1ecdb2 --- /dev/null +++ b/FictionArchive.Service.NovelService/Contracts/FileUploadRequestCreated.cs @@ -0,0 +1,8 @@ +using FictionArchive.Service.Shared.Contracts.Events; + +namespace FictionArchive.Service.NovelService.Contracts; + +public record FileUploadRequestCreated( + Guid RequestId, + string FilePath, + byte[] FileData) : IFileUploadRequestCreated; diff --git a/FictionArchive.Service.NovelService/Contracts/NovelCreated.cs b/FictionArchive.Service.NovelService/Contracts/NovelCreated.cs new file mode 100644 index 0000000..bf35be2 --- /dev/null +++ b/FictionArchive.Service.NovelService/Contracts/NovelCreated.cs @@ -0,0 +1,11 @@ +using FictionArchive.Common.Enums; +using FictionArchive.Service.Shared.Contracts.Events; + +namespace FictionArchive.Service.NovelService.Contracts; + +public record NovelCreated( + uint NovelId, + string Title, + Language OriginalLanguage, + string Source, + string AuthorName) : INovelCreated; diff --git a/FictionArchive.Service.NovelService/Contracts/NovelUpdateRequested.cs b/FictionArchive.Service.NovelService/Contracts/NovelUpdateRequested.cs new file mode 100644 index 0000000..2fe8887 --- /dev/null +++ b/FictionArchive.Service.NovelService/Contracts/NovelUpdateRequested.cs @@ -0,0 +1,6 @@ +using FictionArchive.Service.Shared.Contracts.Events; + +namespace FictionArchive.Service.NovelService.Contracts; + +public record NovelUpdateRequested( + string NovelUrl) : INovelUpdateRequested; diff --git a/FictionArchive.Service.NovelService/Contracts/TranslationRequestCreated.cs b/FictionArchive.Service.NovelService/Contracts/TranslationRequestCreated.cs new file mode 100644 index 0000000..ce29172 --- /dev/null +++ b/FictionArchive.Service.NovelService/Contracts/TranslationRequestCreated.cs @@ -0,0 +1,11 @@ +using FictionArchive.Common.Enums; +using FictionArchive.Service.Shared.Contracts.Events; + +namespace FictionArchive.Service.NovelService.Contracts; + +public record TranslationRequestCreated( + Guid TranslationRequestId, + Language From, + Language To, + string Body, + string TranslationEngineKey) : ITranslationRequestCreated; diff --git a/FictionArchive.Service.NovelService/GraphQL/Mutation.cs b/FictionArchive.Service.NovelService/GraphQL/Mutation.cs index 47c3e40..d5163b4 100644 --- a/FictionArchive.Service.NovelService/GraphQL/Mutation.cs +++ b/FictionArchive.Service.NovelService/GraphQL/Mutation.cs @@ -1,11 +1,10 @@ +using FictionArchive.Service.NovelService.Contracts; using FictionArchive.Service.NovelService.Models.Enums; -using FictionArchive.Service.NovelService.Models.IntegrationEvents; using FictionArchive.Service.NovelService.Models.Localization; using FictionArchive.Service.NovelService.Models.Novels; using FictionArchive.Service.NovelService.Models.SourceAdapters; using FictionArchive.Service.NovelService.Services; using FictionArchive.Service.NovelService.Services.SourceAdapters; -using FictionArchive.Service.Shared.Services.EventBus; using HotChocolate.Authorization; using HotChocolate.Types; using Microsoft.EntityFrameworkCore; @@ -15,13 +14,13 @@ namespace FictionArchive.Service.NovelService.GraphQL; public class Mutation { [Authorize] - public async Task ImportNovel(string novelUrl, NovelUpdateService service) + public async Task ImportNovel(string novelUrl, NovelUpdateService service) { return await service.QueueNovelImport(novelUrl); } [Authorize] - public async Task FetchChapterContents( + public async Task FetchChapterContents( uint novelId, uint volumeId, uint chapterOrder, diff --git a/FictionArchive.Service.NovelService/Models/IntegrationEvents/ChapterCreatedEvent.cs b/FictionArchive.Service.NovelService/Models/IntegrationEvents/ChapterCreatedEvent.cs deleted file mode 100644 index 3608c2f..0000000 --- a/FictionArchive.Service.NovelService/Models/IntegrationEvents/ChapterCreatedEvent.cs +++ /dev/null @@ -1,13 +0,0 @@ -using FictionArchive.Service.Shared.Services.EventBus; - -namespace FictionArchive.Service.NovelService.Models.IntegrationEvents; - -public class ChapterCreatedEvent : IIntegrationEvent -{ - public required uint ChapterId { get; init; } - public required uint NovelId { get; init; } - public required uint VolumeId { get; init; } - public required int VolumeOrder { get; init; } - public required uint ChapterOrder { get; init; } - public required string ChapterTitle { get; init; } -} diff --git a/FictionArchive.Service.NovelService/Models/IntegrationEvents/ChapterPullRequestedEvent.cs b/FictionArchive.Service.NovelService/Models/IntegrationEvents/ChapterPullRequestedEvent.cs deleted file mode 100644 index cd70549..0000000 --- a/FictionArchive.Service.NovelService/Models/IntegrationEvents/ChapterPullRequestedEvent.cs +++ /dev/null @@ -1,10 +0,0 @@ -using FictionArchive.Service.Shared.Services.EventBus; - -namespace FictionArchive.Service.NovelService.Models.IntegrationEvents; - -public class ChapterPullRequestedEvent : IIntegrationEvent -{ - public uint NovelId { get; set; } - public uint VolumeId { get; set; } - public uint ChapterOrder { get; set; } -} \ No newline at end of file diff --git a/FictionArchive.Service.NovelService/Models/IntegrationEvents/FileUploadRequestCreatedEvent.cs b/FictionArchive.Service.NovelService/Models/IntegrationEvents/FileUploadRequestCreatedEvent.cs deleted file mode 100644 index 44e3f46..0000000 --- a/FictionArchive.Service.NovelService/Models/IntegrationEvents/FileUploadRequestCreatedEvent.cs +++ /dev/null @@ -1,10 +0,0 @@ -using FictionArchive.Service.Shared.Services.EventBus; - -namespace FictionArchive.Service.FileService.IntegrationEvents; - -public class FileUploadRequestCreatedEvent : IIntegrationEvent -{ - public Guid RequestId { get; set; } - public string FilePath { get; set; } - public byte[] FileData { get; set; } -} \ No newline at end of file diff --git a/FictionArchive.Service.NovelService/Models/IntegrationEvents/FileUploadRequestStatusUpdateEvent.cs b/FictionArchive.Service.NovelService/Models/IntegrationEvents/FileUploadRequestStatusUpdateEvent.cs deleted file mode 100644 index 4f61613..0000000 --- a/FictionArchive.Service.NovelService/Models/IntegrationEvents/FileUploadRequestStatusUpdateEvent.cs +++ /dev/null @@ -1,22 +0,0 @@ -using FictionArchive.Common.Enums; -using FictionArchive.Service.Shared.Services.EventBus; - -namespace FictionArchive.Service.NovelService.Models.IntegrationEvents; - -public class FileUploadRequestStatusUpdateEvent : IIntegrationEvent -{ - public Guid RequestId { get; set; } - public RequestStatus Status { get; set; } - - #region Success - - public string? FileAccessUrl { get; set; } - - #endregion - - #region Failure - - public string? ErrorMessage { get; set; } - - #endregion -} \ No newline at end of file diff --git a/FictionArchive.Service.NovelService/Models/IntegrationEvents/NovelCreatedEvent.cs b/FictionArchive.Service.NovelService/Models/IntegrationEvents/NovelCreatedEvent.cs deleted file mode 100644 index 50ede95..0000000 --- a/FictionArchive.Service.NovelService/Models/IntegrationEvents/NovelCreatedEvent.cs +++ /dev/null @@ -1,13 +0,0 @@ -using FictionArchive.Common.Enums; -using FictionArchive.Service.Shared.Services.EventBus; - -namespace FictionArchive.Service.NovelService.Models.IntegrationEvents; - -public class NovelCreatedEvent : IIntegrationEvent -{ - public required uint NovelId { get; init; } - public required string Title { get; init; } - public required Language OriginalLanguage { get; init; } - public required string Source { get; init; } - public required string AuthorName { get; init; } -} diff --git a/FictionArchive.Service.NovelService/Models/IntegrationEvents/NovelUpdateRequestedEvent.cs b/FictionArchive.Service.NovelService/Models/IntegrationEvents/NovelUpdateRequestedEvent.cs deleted file mode 100644 index 5243a3f..0000000 --- a/FictionArchive.Service.NovelService/Models/IntegrationEvents/NovelUpdateRequestedEvent.cs +++ /dev/null @@ -1,8 +0,0 @@ -using FictionArchive.Service.Shared.Services.EventBus; - -namespace FictionArchive.Service.NovelService.Models.IntegrationEvents; - -public class NovelUpdateRequestedEvent : IIntegrationEvent -{ - public string NovelUrl { get; set; } -} \ No newline at end of file diff --git a/FictionArchive.Service.NovelService/Models/IntegrationEvents/TranslationRequestCompletedEvent.cs b/FictionArchive.Service.NovelService/Models/IntegrationEvents/TranslationRequestCompletedEvent.cs deleted file mode 100644 index 98d5ee6..0000000 --- a/FictionArchive.Service.NovelService/Models/IntegrationEvents/TranslationRequestCompletedEvent.cs +++ /dev/null @@ -1,17 +0,0 @@ -using FictionArchive.Common.Enums; -using FictionArchive.Service.Shared.Services.EventBus; - -namespace FictionArchive.Service.NovelService.Models.IntegrationEvents; - -public class TranslationRequestCompletedEvent : IIntegrationEvent -{ - /// - /// Maps this event back to a triggering request. - /// - public Guid? TranslationRequestId { get; set; } - - /// - /// The resulting text. - /// - public string? TranslatedText { get; set; } -} \ No newline at end of file diff --git a/FictionArchive.Service.NovelService/Models/IntegrationEvents/TranslationRequestCreatedEvent.cs b/FictionArchive.Service.NovelService/Models/IntegrationEvents/TranslationRequestCreatedEvent.cs deleted file mode 100644 index e6623f0..0000000 --- a/FictionArchive.Service.NovelService/Models/IntegrationEvents/TranslationRequestCreatedEvent.cs +++ /dev/null @@ -1,13 +0,0 @@ -using FictionArchive.Common.Enums; -using FictionArchive.Service.Shared.Services.EventBus; - -namespace FictionArchive.Service.NovelService.Models.IntegrationEvents; - -public class TranslationRequestCreatedEvent : IIntegrationEvent -{ - public Guid TranslationRequestId { get; set; } - public Language From { get; set; } - public Language To { get; set; } - public string Body { get; set; } - public string TranslationEngineKey { get; set; } -} \ No newline at end of file diff --git a/FictionArchive.Service.NovelService/Program.cs b/FictionArchive.Service.NovelService/Program.cs index e06f9b9..95af51f 100644 --- a/FictionArchive.Service.NovelService/Program.cs +++ b/FictionArchive.Service.NovelService/Program.cs @@ -1,14 +1,12 @@ using FictionArchive.Common.Extensions; +using FictionArchive.Service.NovelService.Consumers; using FictionArchive.Service.NovelService.GraphQL; using FictionArchive.Service.NovelService.Models.Configuration; -using FictionArchive.Service.NovelService.Models.IntegrationEvents; using FictionArchive.Service.NovelService.Services; -using FictionArchive.Service.NovelService.Services.EventHandlers; using FictionArchive.Service.NovelService.Services.SourceAdapters; using FictionArchive.Service.NovelService.Services.SourceAdapters.Novelpia; using FictionArchive.Service.Shared; using FictionArchive.Service.Shared.Extensions; -using FictionArchive.Service.Shared.Services.EventBus.Implementations; using FictionArchive.Service.Shared.Services.GraphQL; using Microsoft.EntityFrameworkCore; @@ -25,18 +23,19 @@ public class Program builder.Services.AddMemoryCache(); - #region Event Bus + #region MassTransit if (!isSchemaExport) { - builder.Services.AddRabbitMQ(opt => - { - builder.Configuration.GetSection("RabbitMQ").Bind(opt); - }) - .Subscribe() - .Subscribe() - .Subscribe() - .Subscribe(); + builder.Services.AddFictionArchiveMassTransit( + builder.Configuration, + x => + { + x.AddConsumer(); + x.AddConsumer(); + x.AddConsumer(); + x.AddConsumer(); + }); } #endregion diff --git a/FictionArchive.Service.NovelService/Services/EventHandlers/ChapterPullRequestedEventHandler.cs b/FictionArchive.Service.NovelService/Services/EventHandlers/ChapterPullRequestedEventHandler.cs deleted file mode 100644 index 6acd8ee..0000000 --- a/FictionArchive.Service.NovelService/Services/EventHandlers/ChapterPullRequestedEventHandler.cs +++ /dev/null @@ -1,19 +0,0 @@ -using FictionArchive.Service.NovelService.Models.IntegrationEvents; -using FictionArchive.Service.Shared.Services.EventBus; - -namespace FictionArchive.Service.NovelService.Services.EventHandlers; - -public class ChapterPullRequestedEventHandler : IIntegrationEventHandler -{ - private readonly NovelUpdateService _novelUpdateService; - - public ChapterPullRequestedEventHandler(NovelUpdateService novelUpdateService) - { - _novelUpdateService = novelUpdateService; - } - - public async Task Handle(ChapterPullRequestedEvent @event) - { - await _novelUpdateService.PullChapterContents(@event.NovelId, @event.VolumeId, @event.ChapterOrder); - } -} \ No newline at end of file diff --git a/FictionArchive.Service.NovelService/Services/EventHandlers/FileUploadRequestStatusUpdateEventHandler.cs b/FictionArchive.Service.NovelService/Services/EventHandlers/FileUploadRequestStatusUpdateEventHandler.cs deleted file mode 100644 index f6b3dc3..0000000 --- a/FictionArchive.Service.NovelService/Services/EventHandlers/FileUploadRequestStatusUpdateEventHandler.cs +++ /dev/null @@ -1,39 +0,0 @@ -using FictionArchive.Common.Enums; -using FictionArchive.Service.NovelService.Models.IntegrationEvents; -using FictionArchive.Service.Shared.Services.EventBus; - -namespace FictionArchive.Service.NovelService.Services.EventHandlers; - -public class FileUploadRequestStatusUpdateEventHandler : IIntegrationEventHandler -{ - private readonly ILogger _logger; - private readonly NovelServiceDbContext _context; - private readonly NovelUpdateService _novelUpdateService; - - public FileUploadRequestStatusUpdateEventHandler(ILogger logger, NovelServiceDbContext context, NovelUpdateService novelUpdateService) - { - _logger = logger; - _context = context; - _novelUpdateService = novelUpdateService; - } - - public async Task Handle(FileUploadRequestStatusUpdateEvent @event) - { - var image = await _context.Images.FindAsync(@event.RequestId); - if (image == null) - { - // Not a request we care about. - return; - } - if (@event.Status == RequestStatus.Failed) - { - _logger.LogError("Image upload failed for image with id {imageId}", image.Id); - return; - } - else if (@event.Status == RequestStatus.Success) - { - _logger.LogInformation("Image upload succeeded for image with id {imageId}", image.Id); - await _novelUpdateService.UpdateImage(image.Id, @event.FileAccessUrl); - } - } -} \ No newline at end of file diff --git a/FictionArchive.Service.NovelService/Services/EventHandlers/NovelUpdateRequestedEventHandler.cs b/FictionArchive.Service.NovelService/Services/EventHandlers/NovelUpdateRequestedEventHandler.cs deleted file mode 100644 index 5fad1e0..0000000 --- a/FictionArchive.Service.NovelService/Services/EventHandlers/NovelUpdateRequestedEventHandler.cs +++ /dev/null @@ -1,23 +0,0 @@ -using FictionArchive.Service.NovelService.Models.IntegrationEvents; -using FictionArchive.Service.Shared.Services.EventBus; - -namespace FictionArchive.Service.NovelService.Services.EventHandlers; - -public class NovelUpdateRequestedEventHandler : IIntegrationEventHandler -{ - private readonly ILogger _logger; - private readonly IEventBus _eventBus; - private readonly NovelUpdateService _novelUpdateService; - - public NovelUpdateRequestedEventHandler(ILogger logger, IEventBus eventBus, NovelUpdateService novelUpdateService) - { - _logger = logger; - _eventBus = eventBus; - _novelUpdateService = novelUpdateService; - } - - public async Task Handle(NovelUpdateRequestedEvent @event) - { - await _novelUpdateService.ImportNovel(@event.NovelUrl); - } -} \ No newline at end of file diff --git a/FictionArchive.Service.NovelService/Services/EventHandlers/TranslationRequestCompletedEventHandler.cs b/FictionArchive.Service.NovelService/Services/EventHandlers/TranslationRequestCompletedEventHandler.cs deleted file mode 100644 index 632d26c..0000000 --- a/FictionArchive.Service.NovelService/Services/EventHandlers/TranslationRequestCompletedEventHandler.cs +++ /dev/null @@ -1,39 +0,0 @@ -using FictionArchive.Service.NovelService.Models.IntegrationEvents; -using FictionArchive.Service.NovelService.Models.Localization; -using FictionArchive.Service.Shared.Services.EventBus; -using Microsoft.EntityFrameworkCore; - -namespace FictionArchive.Service.NovelService.Services.EventHandlers; - -public class TranslationRequestCompletedEventHandler : IIntegrationEventHandler -{ - private readonly ILogger _logger; - private readonly NovelServiceDbContext _dbContext; - - public TranslationRequestCompletedEventHandler(ILogger logger, NovelServiceDbContext dbContext) - { - _logger = logger; - _dbContext = dbContext; - } - - public async Task Handle(TranslationRequestCompletedEvent @event) - { - var localizationRequest = await _dbContext.LocalizationRequests.Include(r => r.KeyRequestedForTranslation) - .ThenInclude(lk => lk.Texts) - .FirstOrDefaultAsync(lk => lk.Id == @event.TranslationRequestId); - if (localizationRequest == null) - { - // Not one of our requests, discard it - return; - } - - localizationRequest.KeyRequestedForTranslation.Texts.Add(new LocalizationText() - { - Language = localizationRequest.TranslateTo, - Text = @event.TranslatedText, - TranslationEngine = localizationRequest.Engine - }); - _dbContext.LocalizationRequests.Remove(localizationRequest); - await _dbContext.SaveChangesAsync(); - } -} \ No newline at end of file diff --git a/FictionArchive.Service.NovelService/Services/NovelUpdateService.cs b/FictionArchive.Service.NovelService/Services/NovelUpdateService.cs index d95e865..4905a89 100644 --- a/FictionArchive.Service.NovelService/Services/NovelUpdateService.cs +++ b/FictionArchive.Service.NovelService/Services/NovelUpdateService.cs @@ -1,15 +1,15 @@ using FictionArchive.Common.Enums; -using FictionArchive.Service.FileService.IntegrationEvents; +using FictionArchive.Service.NovelService.Contracts; using FictionArchive.Service.NovelService.Models.Configuration; using FictionArchive.Service.NovelService.Models.Enums; using FictionArchive.Service.NovelService.Models.Images; -using FictionArchive.Service.NovelService.Models.IntegrationEvents; using FictionArchive.Service.NovelService.Models.Localization; using FictionArchive.Service.NovelService.Models.Novels; using FictionArchive.Service.NovelService.Models.SourceAdapters; using FictionArchive.Service.NovelService.Services.SourceAdapters; -using FictionArchive.Service.Shared.Services.EventBus; +using FictionArchive.Service.Shared.Contracts.Events; using HtmlAgilityPack; +using MassTransit; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Options; @@ -20,15 +20,15 @@ public class NovelUpdateService private readonly NovelServiceDbContext _dbContext; private readonly ILogger _logger; private readonly IEnumerable _sourceAdapters; - private readonly IEventBus _eventBus; + private readonly IPublishEndpoint _publishEndpoint; private readonly NovelUpdateServiceConfiguration _novelUpdateServiceConfiguration; - public NovelUpdateService(NovelServiceDbContext dbContext, ILogger logger, IEnumerable sourceAdapters, IEventBus eventBus, IOptions novelUpdateServiceConfiguration) + public NovelUpdateService(NovelServiceDbContext dbContext, ILogger logger, IEnumerable sourceAdapters, IPublishEndpoint publishEndpoint, IOptions novelUpdateServiceConfiguration) { _dbContext = dbContext; _logger = logger; _sourceAdapters = sourceAdapters; - _eventBus = eventBus; + _publishEndpoint = publishEndpoint; _novelUpdateServiceConfiguration = novelUpdateServiceConfiguration.Value; } @@ -335,7 +335,8 @@ public class NovelUpdateService .ThenInclude(volume => volume.Chapters) .ThenInclude(chapter => chapter.Body) .ThenInclude(localizationKey => localizationKey.Texts) - .Include(n => n.CoverImage) + .Include(n => n.CoverImage).Include(novel => novel.Volumes).ThenInclude(volume => volume.Chapters) + .ThenInclude(chapter => chapter.Name) .FirstOrDefaultAsync(n => n.ExternalId == metadata.ExternalId && n.Source.Key == metadata.SourceDescriptor.Key); @@ -393,14 +394,12 @@ public class NovelUpdateService // Publish novel created event for new novels if (existingNovel == null) { - await _eventBus.Publish(new NovelCreatedEvent - { - NovelId = novel.Id, - Title = novel.Name.Texts.First(t => t.Language == novel.RawLanguage).Text, - OriginalLanguage = novel.RawLanguage, - Source = novel.Source.Key, - AuthorName = novel.Author.Name.Texts.First(t => t.Language == novel.RawLanguage).Text - }); + await _publishEndpoint.Publish(new NovelCreated( + novel.Id, + novel.Name.Texts.First(t => t.Language == novel.RawLanguage).Text, + novel.RawLanguage, + novel.Source.Key, + novel.Author.Name.Texts.First(t => t.Language == novel.RawLanguage).Text)); } // Publish chapter created events for new chapters @@ -408,27 +407,23 @@ public class NovelUpdateService { foreach (var chapter in volume.Chapters.Where(c => !existingChapterIds.Contains(c.Id))) { - await _eventBus.Publish(new ChapterCreatedEvent - { - ChapterId = chapter.Id, - NovelId = novel.Id, - VolumeId = volume.Id, - VolumeOrder = volume.Order, - ChapterOrder = chapter.Order, - ChapterTitle = chapter.Name.Texts.First(t => t.Language == novel.RawLanguage).Text - }); + await _publishEndpoint.Publish(new ChapterCreated( + chapter.Id, + novel.Id, + volume.Id, + (uint)volume.Order, + chapter.Order, + chapter.Name.Texts.First(t => t.Language == novel.RawLanguage).Text)); } } // Publish cover image event if needed if (shouldPublishCoverEvent && novel.CoverImage != null && metadata.CoverImage != null) { - await _eventBus.Publish(new FileUploadRequestCreatedEvent - { - RequestId = novel.CoverImage.Id, - FileData = metadata.CoverImage.Data, - FilePath = $"Novels/{novel.Id}/Images/cover.jpg" - }); + await _publishEndpoint.Publish(new FileUploadRequestCreated( + novel.CoverImage.Id, + $"Novels/{novel.Id}/Images/cover.jpg", + metadata.CoverImage.Data)); } // Publish chapter pull events for chapters without body content @@ -440,12 +435,10 @@ public class NovelUpdateService foreach (var chapter in chaptersNeedingPull) { - await _eventBus.Publish(new ChapterPullRequestedEvent - { - NovelId = novel.Id, - VolumeId = volume.Id, - ChapterOrder = chapter.Order - }); + await _publishEndpoint.Publish(new ChapterPullRequested( + novel.Id, + volume.Id, + chapter.Order)); } } @@ -518,12 +511,10 @@ public class NovelUpdateService foreach (var image in chapter.Images) { var data = rawChapter.ImageData.FirstOrDefault(img => img.Url == image.OriginalPath); - await _eventBus.Publish(new FileUploadRequestCreatedEvent() - { - FileData = data.Data, - FilePath = $"{novel.Id}/Images/Chapter-{chapter.Id}/{imgCount++}.jpg", - RequestId = image.Id - }); + await _publishEndpoint.Publish(new FileUploadRequestCreated( + image.Id, + $"Novels/{novel.Id}/Images/Chapter-{chapter.Id}/{imgCount++}.jpg", + data.Data)); } return chapter; @@ -557,25 +548,17 @@ public class NovelUpdateService await _dbContext.SaveChangesAsync(); } - public async Task QueueNovelImport(string novelUrl) + public async Task QueueNovelImport(string novelUrl) { - var importNovelRequestEvent = new NovelUpdateRequestedEvent() - { - NovelUrl = novelUrl - }; - await _eventBus.Publish(importNovelRequestEvent); + var importNovelRequestEvent = new NovelUpdateRequested(novelUrl); + await _publishEndpoint.Publish(importNovelRequestEvent); return importNovelRequestEvent; } - public async Task QueueChapterPull(uint novelId, uint volumeId, uint chapterOrder) + public async Task QueueChapterPull(uint novelId, uint volumeId, uint chapterOrder) { - var chapterPullEvent = new ChapterPullRequestedEvent() - { - NovelId = novelId, - VolumeId = volumeId, - ChapterOrder = chapterOrder - }; - await _eventBus.Publish(chapterPullEvent); + var chapterPullEvent = new ChapterPullRequested(novelId, volumeId, chapterOrder); + await _publishEndpoint.Publish(chapterPullEvent); return chapterPullEvent; } diff --git a/FictionArchive.Service.NovelService/appsettings.json b/FictionArchive.Service.NovelService/appsettings.json index f287976..bdc841b 100644 --- a/FictionArchive.Service.NovelService/appsettings.json +++ b/FictionArchive.Service.NovelService/appsettings.json @@ -2,7 +2,8 @@ "Logging": { "LogLevel": { "Default": "Information", - "Microsoft.AspNetCore": "Warning" + "Microsoft.AspNetCore": "Warning", + "Microsoft.EntityFrameworkCore": "Warning" } }, "Novelpia": { diff --git a/FictionArchive.Service.SchedulerService/Models/JobTemplates/EventJobTemplate.cs b/FictionArchive.Service.SchedulerService/Models/JobTemplates/EventJobTemplate.cs index fa4e8fd..f3d0db4 100644 --- a/FictionArchive.Service.SchedulerService/Models/JobTemplates/EventJobTemplate.cs +++ b/FictionArchive.Service.SchedulerService/Models/JobTemplates/EventJobTemplate.cs @@ -1,4 +1,4 @@ -using FictionArchive.Service.Shared.Services.EventBus; +using MassTransit; using Newtonsoft.Json; using Quartz; @@ -6,18 +6,18 @@ namespace FictionArchive.Service.SchedulerService.Models.JobTemplates; public class EventJobTemplate : IJob { - private readonly IEventBus _eventBus; + private readonly IBus _bus; private readonly ILogger _logger; public const string EventTypeParameter = "RoutingKey"; public const string EventDataParameter = "MessageData"; - - public EventJobTemplate(IEventBus eventBus, ILogger logger) + + public EventJobTemplate(IBus bus, ILogger logger) { - _eventBus = eventBus; + _bus = bus; _logger = logger; } - + public async Task Execute(IJobExecutionContext context) { try @@ -25,7 +25,7 @@ public class EventJobTemplate : IJob var eventData = context.MergedJobDataMap.GetString(EventDataParameter); var eventType = context.MergedJobDataMap.GetString(EventTypeParameter); var eventObject = JsonConvert.DeserializeObject(eventData); - await _eventBus.Publish(eventObject, eventType); + await _bus.Publish(eventObject); } catch (Exception ex) { diff --git a/FictionArchive.Service.SchedulerService/Program.cs b/FictionArchive.Service.SchedulerService/Program.cs index b1a9c92..e20ed4d 100644 --- a/FictionArchive.Service.SchedulerService/Program.cs +++ b/FictionArchive.Service.SchedulerService/Program.cs @@ -2,7 +2,6 @@ using FictionArchive.Service.SchedulerService.GraphQL; using FictionArchive.Service.SchedulerService.Services; using FictionArchive.Service.Shared; using FictionArchive.Service.Shared.Extensions; -using FictionArchive.Service.Shared.Services.EventBus.Implementations; using Quartz; using Quartz.Impl.AdoJobStore; @@ -38,10 +37,7 @@ public class Program if (!isSchemaExport) { - builder.Services.AddRabbitMQ(opt => - { - builder.Configuration.GetSection("RabbitMQ").Bind(opt); - }); + builder.Services.AddFictionArchiveMassTransit(builder.Configuration); } #endregion diff --git a/FictionArchive.Service.SchedulerService/Services/JobManagerService.cs b/FictionArchive.Service.SchedulerService/Services/JobManagerService.cs index 1e37f75..fa9404b 100644 --- a/FictionArchive.Service.SchedulerService/Services/JobManagerService.cs +++ b/FictionArchive.Service.SchedulerService/Services/JobManagerService.cs @@ -1,7 +1,6 @@ using System.Data; using FictionArchive.Service.SchedulerService.Models; using FictionArchive.Service.SchedulerService.Models.JobTemplates; -using FictionArchive.Service.Shared.Services.EventBus; using Quartz; using Quartz.Impl.Matchers; diff --git a/FictionArchive.Service.SchedulerService/appsettings.json b/FictionArchive.Service.SchedulerService/appsettings.json index 4b087a3..7a3f4bf 100644 --- a/FictionArchive.Service.SchedulerService/appsettings.json +++ b/FictionArchive.Service.SchedulerService/appsettings.json @@ -2,7 +2,8 @@ "Logging": { "LogLevel": { "Default": "Information", - "Microsoft.AspNetCore": "Warning" + "Microsoft.AspNetCore": "Warning", + "Microsoft.EntityFrameworkCore": "Warning" } }, "RabbitMQ": { diff --git a/FictionArchive.Service.Shared/Contracts/Events/IChapterCreated.cs b/FictionArchive.Service.Shared/Contracts/Events/IChapterCreated.cs new file mode 100644 index 0000000..9edc77b --- /dev/null +++ b/FictionArchive.Service.Shared/Contracts/Events/IChapterCreated.cs @@ -0,0 +1,11 @@ +namespace FictionArchive.Service.Shared.Contracts.Events; + +public interface IChapterCreated +{ + uint ChapterId { get; } + uint NovelId { get; } + uint VolumeId { get; } + uint VolumeOrder { get; } + uint ChapterOrder { get; } + string ChapterTitle { get; } +} diff --git a/FictionArchive.Service.Shared/Contracts/Events/IChapterPullRequested.cs b/FictionArchive.Service.Shared/Contracts/Events/IChapterPullRequested.cs new file mode 100644 index 0000000..a0f3377 --- /dev/null +++ b/FictionArchive.Service.Shared/Contracts/Events/IChapterPullRequested.cs @@ -0,0 +1,8 @@ +namespace FictionArchive.Service.Shared.Contracts.Events; + +public interface IChapterPullRequested +{ + uint NovelId { get; } + uint VolumeId { get; } + uint ChapterOrder { get; } +} diff --git a/FictionArchive.Service.Shared/Contracts/Events/IFileUploadRequestCreated.cs b/FictionArchive.Service.Shared/Contracts/Events/IFileUploadRequestCreated.cs new file mode 100644 index 0000000..8624ce5 --- /dev/null +++ b/FictionArchive.Service.Shared/Contracts/Events/IFileUploadRequestCreated.cs @@ -0,0 +1,8 @@ +namespace FictionArchive.Service.Shared.Contracts.Events; + +public interface IFileUploadRequestCreated +{ + Guid RequestId { get; } + string FilePath { get; } + byte[] FileData { get; } +} diff --git a/FictionArchive.Service.Shared/Contracts/Events/IFileUploadRequestStatusUpdate.cs b/FictionArchive.Service.Shared/Contracts/Events/IFileUploadRequestStatusUpdate.cs new file mode 100644 index 0000000..bd7380c --- /dev/null +++ b/FictionArchive.Service.Shared/Contracts/Events/IFileUploadRequestStatusUpdate.cs @@ -0,0 +1,11 @@ +using FictionArchive.Common.Enums; + +namespace FictionArchive.Service.Shared.Contracts.Events; + +public interface IFileUploadRequestStatusUpdate +{ + Guid RequestId { get; } + RequestStatus Status { get; } + string? FileAccessUrl { get; } + string? ErrorMessage { get; } +} diff --git a/FictionArchive.Service.Shared/Contracts/Events/INovelCreated.cs b/FictionArchive.Service.Shared/Contracts/Events/INovelCreated.cs new file mode 100644 index 0000000..071048b --- /dev/null +++ b/FictionArchive.Service.Shared/Contracts/Events/INovelCreated.cs @@ -0,0 +1,12 @@ +using FictionArchive.Common.Enums; + +namespace FictionArchive.Service.Shared.Contracts.Events; + +public interface INovelCreated +{ + uint NovelId { get; } + string Title { get; } + Language OriginalLanguage { get; } + string Source { get; } + string AuthorName { get; } +} diff --git a/FictionArchive.Service.Shared/Contracts/Events/INovelUpdateRequested.cs b/FictionArchive.Service.Shared/Contracts/Events/INovelUpdateRequested.cs new file mode 100644 index 0000000..d64ac3f --- /dev/null +++ b/FictionArchive.Service.Shared/Contracts/Events/INovelUpdateRequested.cs @@ -0,0 +1,6 @@ +namespace FictionArchive.Service.Shared.Contracts.Events; + +public interface INovelUpdateRequested +{ + string NovelUrl { get; } +} diff --git a/FictionArchive.Service.Shared/Contracts/Events/ITranslationRequestCompleted.cs b/FictionArchive.Service.Shared/Contracts/Events/ITranslationRequestCompleted.cs new file mode 100644 index 0000000..f16eef3 --- /dev/null +++ b/FictionArchive.Service.Shared/Contracts/Events/ITranslationRequestCompleted.cs @@ -0,0 +1,7 @@ +namespace FictionArchive.Service.Shared.Contracts.Events; + +public interface ITranslationRequestCompleted +{ + Guid? TranslationRequestId { get; } + string? TranslatedText { get; } +} diff --git a/FictionArchive.Service.Shared/Contracts/Events/ITranslationRequestCreated.cs b/FictionArchive.Service.Shared/Contracts/Events/ITranslationRequestCreated.cs new file mode 100644 index 0000000..1fbe7a0 --- /dev/null +++ b/FictionArchive.Service.Shared/Contracts/Events/ITranslationRequestCreated.cs @@ -0,0 +1,12 @@ +using FictionArchive.Common.Enums; + +namespace FictionArchive.Service.Shared.Contracts.Events; + +public interface ITranslationRequestCreated +{ + Guid TranslationRequestId { get; } + Language From { get; } + Language To { get; } + string Body { get; } + string TranslationEngineKey { get; } +} diff --git a/FictionArchive.Service.Shared/Contracts/Events/IUserInvited.cs b/FictionArchive.Service.Shared/Contracts/Events/IUserInvited.cs new file mode 100644 index 0000000..44ecd64 --- /dev/null +++ b/FictionArchive.Service.Shared/Contracts/Events/IUserInvited.cs @@ -0,0 +1,12 @@ +namespace FictionArchive.Service.Shared.Contracts.Events; + +public interface IUserInvited +{ + string InvitedUserId { get; } + string InvitedUsername { get; } + string InvitedEmail { get; } + string InvitedOAuthProviderId { get; } + string InviterId { get; } + string InviterUsername { get; } + string InviterOAuthProviderId { get; } +} diff --git a/FictionArchive.Service.Shared/Extensions/MassTransitExtensions.cs b/FictionArchive.Service.Shared/Extensions/MassTransitExtensions.cs new file mode 100644 index 0000000..0879e72 --- /dev/null +++ b/FictionArchive.Service.Shared/Extensions/MassTransitExtensions.cs @@ -0,0 +1,118 @@ +using System.Text.RegularExpressions; +using FictionArchive.Service.Shared.Services.Filters; +using MassTransit; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; + +namespace FictionArchive.Service.Shared.Extensions; + +public static class MassTransitExtensions +{ + public static IServiceCollection AddFictionArchiveMassTransit( + this IServiceCollection services, + IConfiguration configuration, + Action? configureConsumers = null) + { + services.AddMassTransit(x => + { + configureConsumers?.Invoke(x); + + x.UsingRabbitMq((context, cfg) => + { + var (host, username, password) = ParseRabbitMqConfiguration(configuration); + + cfg.Host(host, h => + { + h.Username(username); + h.Password(password); + }); + + cfg.UseMessageRetry(r => r.Exponential( + retryLimit: 5, + minInterval: TimeSpan.FromSeconds(1), + maxInterval: TimeSpan.FromMinutes(1), + intervalDelta: TimeSpan.FromSeconds(2))); + + cfg.UseConsumeFilter(typeof(LoggingConsumeFilter<>), context); + + // Process one message at a time per consumer (matches old EventBus behavior) + cfg.PrefetchCount = 1; + + cfg.ConfigureEndpoints(context); + }); + }); + + return services; + } + + /// + /// Parses RabbitMQ configuration from either ConnectionString format or separate Host/Username/Password keys. + /// ConnectionString format: amqp://[username:password@]host[:port] + /// + private static (string Host, string Username, string Password) ParseRabbitMqConfiguration(IConfiguration configuration) + { + var connectionString = configuration["RabbitMQ:ConnectionString"]; + + if (!string.IsNullOrEmpty(connectionString)) + { + return ParseConnectionString(connectionString); + } + + // Fallback to separate configuration keys + var host = configuration["RabbitMQ:Host"] ?? "localhost"; + var username = configuration["RabbitMQ:Username"] ?? "guest"; + var password = configuration["RabbitMQ:Password"] ?? "guest"; + + return (host, username, password); + } + + /// + /// Parses an AMQP connection string into host, username, and password components. + /// Supports formats: + /// - amqp://host + /// - amqp://host:port + /// - amqp://username:password@host + /// - amqp://username:password@host:port + /// + private static (string Host, string Username, string Password) ParseConnectionString(string connectionString) + { + var username = "guest"; + var password = "guest"; + var host = "localhost"; + + // Try to parse as URI first + if (Uri.TryCreate(connectionString, UriKind.Absolute, out var uri)) + { + host = uri.Host; + + if (!string.IsNullOrEmpty(uri.UserInfo)) + { + var userInfoParts = uri.UserInfo.Split(':', 2); + username = Uri.UnescapeDataString(userInfoParts[0]); + if (userInfoParts.Length > 1) + { + password = Uri.UnescapeDataString(userInfoParts[1]); + } + } + } + else + { + // Fallback regex parsing for edge cases + var match = Regex.Match(connectionString, @"amqp://(?:([^:]+):([^@]+)@)?([^:/]+)"); + if (match.Success) + { + if (match.Groups[1].Success && match.Groups[2].Success) + { + username = match.Groups[1].Value; + password = match.Groups[2].Value; + } + if (match.Groups[3].Success) + { + host = match.Groups[3].Value; + } + } + } + + return (host, username, password); + } +} diff --git a/FictionArchive.Service.Shared/FictionArchive.Service.Shared.csproj b/FictionArchive.Service.Shared/FictionArchive.Service.Shared.csproj index 323b166..9cbe796 100644 --- a/FictionArchive.Service.Shared/FictionArchive.Service.Shared.csproj +++ b/FictionArchive.Service.Shared/FictionArchive.Service.Shared.csproj @@ -31,7 +31,7 @@ - + diff --git a/FictionArchive.Service.Shared/Services/EventBus/EventBusBuilder.cs b/FictionArchive.Service.Shared/Services/EventBus/EventBusBuilder.cs deleted file mode 100644 index 1331fba..0000000 --- a/FictionArchive.Service.Shared/Services/EventBus/EventBusBuilder.cs +++ /dev/null @@ -1,25 +0,0 @@ -using Microsoft.Extensions.DependencyInjection; - -namespace FictionArchive.Service.Shared.Services.EventBus; - -public class EventBusBuilder where TEventBus : class, IEventBus -{ - private readonly IServiceCollection _services; - private readonly SubscriptionManager _subscriptionManager; - - public EventBusBuilder(IServiceCollection services) - { - _services = services; - _services.AddSingleton(); - - _subscriptionManager = new SubscriptionManager(); - _services.AddSingleton(_subscriptionManager); - } - - public EventBusBuilder Subscribe() where TEvent : IIntegrationEvent where TEventHandler : class, IIntegrationEventHandler - { - _services.AddKeyedTransient(typeof(TEvent).Name); - _subscriptionManager.RegisterSubscription(); - return this; - } -} \ No newline at end of file diff --git a/FictionArchive.Service.Shared/Services/EventBus/EventBusExtensions.cs b/FictionArchive.Service.Shared/Services/EventBus/EventBusExtensions.cs deleted file mode 100644 index 417bfa8..0000000 --- a/FictionArchive.Service.Shared/Services/EventBus/EventBusExtensions.cs +++ /dev/null @@ -1,12 +0,0 @@ -using Microsoft.Extensions.DependencyInjection; - -namespace FictionArchive.Service.Shared.Services.EventBus; - -public static class EventBusExtensions -{ - public static EventBusBuilder AddEventBus(this IServiceCollection services) - where TEventBus : class, IEventBus - { - return new EventBusBuilder(services); - } -} \ No newline at end of file diff --git a/FictionArchive.Service.Shared/Services/EventBus/IEventBus.cs b/FictionArchive.Service.Shared/Services/EventBus/IEventBus.cs deleted file mode 100644 index 46b28eb..0000000 --- a/FictionArchive.Service.Shared/Services/EventBus/IEventBus.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace FictionArchive.Service.Shared.Services.EventBus; - -public interface IEventBus -{ - Task Publish(TEvent integrationEvent) where TEvent : IIntegrationEvent; - Task Publish(object integrationEvent, string eventType); -} \ No newline at end of file diff --git a/FictionArchive.Service.Shared/Services/EventBus/IIntegrationEvent.cs b/FictionArchive.Service.Shared/Services/EventBus/IIntegrationEvent.cs deleted file mode 100644 index 499f92c..0000000 --- a/FictionArchive.Service.Shared/Services/EventBus/IIntegrationEvent.cs +++ /dev/null @@ -1,7 +0,0 @@ -using NodaTime; - -namespace FictionArchive.Service.Shared.Services.EventBus; - -public interface IIntegrationEvent -{ -} \ No newline at end of file diff --git a/FictionArchive.Service.Shared/Services/EventBus/IIntegrationEventHandler.cs b/FictionArchive.Service.Shared/Services/EventBus/IIntegrationEventHandler.cs deleted file mode 100644 index 0541637..0000000 --- a/FictionArchive.Service.Shared/Services/EventBus/IIntegrationEventHandler.cs +++ /dev/null @@ -1,12 +0,0 @@ -namespace FictionArchive.Service.Shared.Services.EventBus; - -public interface IIntegrationEventHandler : IIntegrationEventHandler where TEvent : IIntegrationEvent -{ - Task Handle(TEvent @event); - Task IIntegrationEventHandler.Handle(IIntegrationEvent @event) => Handle((TEvent)@event); -} - -public interface IIntegrationEventHandler -{ - Task Handle(IIntegrationEvent @event); -} \ No newline at end of file diff --git a/FictionArchive.Service.Shared/Services/EventBus/Implementations/RabbitMQConnectionProvider.cs b/FictionArchive.Service.Shared/Services/EventBus/Implementations/RabbitMQConnectionProvider.cs deleted file mode 100644 index ad93539..0000000 --- a/FictionArchive.Service.Shared/Services/EventBus/Implementations/RabbitMQConnectionProvider.cs +++ /dev/null @@ -1,35 +0,0 @@ -using RabbitMQ.Client; - -namespace FictionArchive.Service.Shared.Services.EventBus.Implementations; - -public class RabbitMQConnectionProvider -{ - private readonly IConnectionFactory _connectionFactory; - - private IConnection Connection { get; set; } - private IChannel DefaultChannel { get; set; } - - public RabbitMQConnectionProvider(IConnectionFactory connectionFactory) - { - _connectionFactory = connectionFactory; - } - - public async Task GetConnectionAsync() - { - if (Connection == null) - { - Connection = await _connectionFactory.CreateConnectionAsync(); - } - - return Connection; - } - - public async Task GetDefaultChannelAsync() - { - if (DefaultChannel == null) - { - DefaultChannel = await (await GetConnectionAsync()).CreateChannelAsync(); - } - return DefaultChannel; - } -} \ No newline at end of file diff --git a/FictionArchive.Service.Shared/Services/EventBus/Implementations/RabbitMQEventBus.cs b/FictionArchive.Service.Shared/Services/EventBus/Implementations/RabbitMQEventBus.cs deleted file mode 100644 index 758f2d8..0000000 --- a/FictionArchive.Service.Shared/Services/EventBus/Implementations/RabbitMQEventBus.cs +++ /dev/null @@ -1,137 +0,0 @@ -using System.Text; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Hosting; -using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Options; -using Newtonsoft.Json; -using NodaTime; -using NodaTime.Serialization.JsonNet; -using RabbitMQ.Client; -using RabbitMQ.Client.Events; - -namespace FictionArchive.Service.Shared.Services.EventBus.Implementations; - -public class RabbitMQEventBus : IEventBus, IHostedService -{ - private readonly IServiceScopeFactory _serviceScopeFactory; - private readonly RabbitMQConnectionProvider _connectionProvider; - private readonly RabbitMQOptions _options; - private readonly SubscriptionManager _subscriptionManager; - private readonly ILogger _logger; - - private readonly JsonSerializerSettings _jsonSerializerSettings; - - private const string ExchangeName = "fiction-archive-event-bus"; - private const string CreatedAtHeader = "X-Created-At"; - private const string EventIdHeader = "X-Event-Id"; - - public RabbitMQEventBus(IServiceScopeFactory serviceScopeFactory, RabbitMQConnectionProvider connectionProvider, IOptions options, SubscriptionManager subscriptionManager, ILogger logger) - { - _serviceScopeFactory = serviceScopeFactory; - _connectionProvider = connectionProvider; - _subscriptionManager = subscriptionManager; - _logger = logger; - _options = options.Value; - _jsonSerializerSettings = new JsonSerializerSettings().ConfigureForNodaTime(DateTimeZoneProviders.Tzdb); - } - - public async Task Publish(TEvent integrationEvent) where TEvent : IIntegrationEvent - { - var routingKey = typeof(TEvent).Name; - - await Publish(integrationEvent, routingKey); - } - - public async Task Publish(object integrationEvent, string eventType) - { - var channel = await _connectionProvider.GetDefaultChannelAsync(); - var body = Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(integrationEvent)); - - // headers - var props = new BasicProperties(); - props.Headers = new Dictionary() - { - { CreatedAtHeader, Instant.FromDateTimeUtc(DateTime.UtcNow).ToString() }, - { EventIdHeader, Guid.NewGuid().ToString() } - }; - - await channel.BasicPublishAsync(ExchangeName, eventType, true, props, body); - _logger.LogInformation("Published event {EventName}", eventType); - } - - public async Task StartAsync(CancellationToken cancellationToken) - { - _ = Task.Factory.StartNew(async () => - { - try - { - var channel = await _connectionProvider.GetDefaultChannelAsync(); - await channel.ExchangeDeclareAsync(ExchangeName, ExchangeType.Direct, - cancellationToken: cancellationToken); - - await channel.BasicQosAsync(prefetchSize: 0, prefetchCount: 1, global: false, cancellationToken: cancellationToken); - await channel.QueueDeclareAsync(_options.ClientIdentifier, true, false, false, - cancellationToken: cancellationToken); - var consumer = new AsyncEventingBasicConsumer(channel); - consumer.ReceivedAsync += (sender, @event) => - { - return OnReceivedEvent(sender, @event, channel); - }; - - await channel.BasicConsumeAsync(_options.ClientIdentifier, false, consumer, cancellationToken: cancellationToken); - - foreach (var subscription in _subscriptionManager.Subscriptions) - { - await channel.QueueBindAsync(_options.ClientIdentifier, ExchangeName, subscription.Key, - cancellationToken: cancellationToken); - _logger.LogInformation("Subscribed to {SubscriptionKey}", subscription.Key); - } - - _logger.LogInformation("RabbitMQ EventBus started."); - } - catch (Exception e) - { - _logger.LogError(e, "An error occurred while starting the RabbitMQ EventBus"); - } - }, cancellationToken); - } - - public Task StopAsync(CancellationToken cancellationToken) - { - return Task.CompletedTask; - } - - private async Task OnReceivedEvent(object sender, BasicDeliverEventArgs @event, IChannel channel) - { - var eventName = @event.RoutingKey; - _logger.LogInformation("Received event {EventName}", eventName); - try - { - if (!_subscriptionManager.Subscriptions.ContainsKey(eventName)) - { - _logger.LogWarning("Received event without subscription entry."); - return; - } - - var eventBody = Encoding.UTF8.GetString(@event.Body.Span); - var eventObject = JsonConvert.DeserializeObject(eventBody, _subscriptionManager.Subscriptions[eventName], _jsonSerializerSettings) as IIntegrationEvent; - - using var scope = _serviceScopeFactory.CreateScope(); - - foreach (var service in scope.ServiceProvider.GetKeyedServices(eventName)) - { - await service.Handle(eventObject); - } - _logger.LogInformation("Finished handling event with name {EventName}", eventName); - } - catch (Exception e) - { - _logger.LogError(e, "An error occurred while handling an event."); - } - finally - { - await channel.BasicAckAsync(@event.DeliveryTag, false); - } - - } -} \ No newline at end of file diff --git a/FictionArchive.Service.Shared/Services/EventBus/Implementations/RabbitMQExtensions.cs b/FictionArchive.Service.Shared/Services/EventBus/Implementations/RabbitMQExtensions.cs deleted file mode 100644 index 14546d4..0000000 --- a/FictionArchive.Service.Shared/Services/EventBus/Implementations/RabbitMQExtensions.cs +++ /dev/null @@ -1,24 +0,0 @@ -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Options; -using RabbitMQ.Client; - -namespace FictionArchive.Service.Shared.Services.EventBus.Implementations; - -public static class RabbitMQExtensions -{ - public static EventBusBuilder AddRabbitMQ(this IServiceCollection services, Action configure) - { - services.Configure(configure); - services.AddSingleton(provider => - { - var options = provider.GetService>(); - ConnectionFactory factory = new ConnectionFactory(); - factory.Uri = new Uri(options.Value.ConnectionString); - return factory; - }); - services.AddSingleton(); - services.AddHostedService(); - return services.AddEventBus(); - } -} \ No newline at end of file diff --git a/FictionArchive.Service.Shared/Services/EventBus/Implementations/RabbitMQOptions.cs b/FictionArchive.Service.Shared/Services/EventBus/Implementations/RabbitMQOptions.cs deleted file mode 100644 index f385a35..0000000 --- a/FictionArchive.Service.Shared/Services/EventBus/Implementations/RabbitMQOptions.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace FictionArchive.Service.Shared.Services.EventBus.Implementations; - -public class RabbitMQOptions -{ - public string ConnectionString { get; set; } - public string ClientIdentifier { get; set; } -} \ No newline at end of file diff --git a/FictionArchive.Service.Shared/Services/EventBus/SubscriptionManager.cs b/FictionArchive.Service.Shared/Services/EventBus/SubscriptionManager.cs deleted file mode 100644 index 73b8c37..0000000 --- a/FictionArchive.Service.Shared/Services/EventBus/SubscriptionManager.cs +++ /dev/null @@ -1,11 +0,0 @@ -namespace FictionArchive.Service.Shared.Services.EventBus; - -public class SubscriptionManager -{ - public Dictionary Subscriptions { get; } = new Dictionary(); - - public void RegisterSubscription() - { - Subscriptions.Add(typeof(TEvent).Name, typeof(TEvent)); - } -} \ No newline at end of file diff --git a/FictionArchive.Service.Shared/Services/Filters/LoggingConsumeFilter.cs b/FictionArchive.Service.Shared/Services/Filters/LoggingConsumeFilter.cs new file mode 100644 index 0000000..2b3a539 --- /dev/null +++ b/FictionArchive.Service.Shared/Services/Filters/LoggingConsumeFilter.cs @@ -0,0 +1,33 @@ +using MassTransit; +using Microsoft.Extensions.Logging; + +namespace FictionArchive.Service.Shared.Services.Filters; + +public class LoggingConsumeFilter : IFilter> where T : class +{ + private readonly ILogger> _logger; + + public LoggingConsumeFilter(ILogger> logger) + { + _logger = logger; + } + + public async Task Send(ConsumeContext context, IPipe> next) + { + try + { + await next.Send(context); + } + catch (Exception ex) + { + _logger.LogError(ex, + "Message {MessageType} failed after all retries. MessageId: {MessageId}, ConversationId: {ConversationId}", + typeof(T).Name, + context.MessageId, + context.ConversationId); + throw; + } + } + + public void Probe(ProbeContext context) => context.CreateFilterScope("logging"); +} diff --git a/FictionArchive.Service.TranslationService/Consumers/TranslationRequestCreatedConsumer.cs b/FictionArchive.Service.TranslationService/Consumers/TranslationRequestCreatedConsumer.cs new file mode 100644 index 0000000..e911c47 --- /dev/null +++ b/FictionArchive.Service.TranslationService/Consumers/TranslationRequestCreatedConsumer.cs @@ -0,0 +1,53 @@ +using FictionArchive.Service.Shared.Contracts.Events; +using FictionArchive.Service.TranslationService.Contracts; +using FictionArchive.Service.TranslationService.Models.Enums; +using FictionArchive.Service.TranslationService.Services; +using MassTransit; +using Microsoft.Extensions.Logging; + +namespace FictionArchive.Service.TranslationService.Consumers; + +public class TranslationRequestCreatedConsumer : IConsumer +{ + private readonly ILogger _logger; + private readonly TranslationEngineService _translationEngineService; + private readonly IPublishEndpoint _publishEndpoint; + + public TranslationRequestCreatedConsumer( + ILogger logger, + TranslationEngineService translationEngineService, + IPublishEndpoint publishEndpoint) + { + _logger = logger; + _translationEngineService = translationEngineService; + _publishEndpoint = publishEndpoint; + } + + public async Task Consume(ConsumeContext context) + { + var message = context.Message; + + _logger.LogInformation("Processing translation request {TranslationRequestId}", message.TranslationRequestId); + + var result = await _translationEngineService.Translate( + message.From, + message.To, + message.Body, + message.TranslationEngineKey); + + if (result.Status == TranslationRequestStatus.Success) + { + await _publishEndpoint.Publish( + new TranslationRequestCompleted( + TranslationRequestId: message.TranslationRequestId, + TranslatedText: result.TranslatedText)); + + _logger.LogInformation("Translation completed for request {TranslationRequestId}", message.TranslationRequestId); + } + else + { + _logger.LogError("Translation failed for request {TranslationRequestId}", message.TranslationRequestId); + throw new InvalidOperationException($"Translation failed for request {message.TranslationRequestId}"); + } + } +} diff --git a/FictionArchive.Service.TranslationService/Contracts/TranslationRequestCompleted.cs b/FictionArchive.Service.TranslationService/Contracts/TranslationRequestCompleted.cs new file mode 100644 index 0000000..4281c30 --- /dev/null +++ b/FictionArchive.Service.TranslationService/Contracts/TranslationRequestCompleted.cs @@ -0,0 +1,7 @@ +using FictionArchive.Service.Shared.Contracts.Events; + +namespace FictionArchive.Service.TranslationService.Contracts; + +public record TranslationRequestCompleted( + Guid? TranslationRequestId, + string? TranslatedText) : ITranslationRequestCompleted; diff --git a/FictionArchive.Service.TranslationService/Models/IntegrationEvents/TranslationRequestCompletedEvent.cs b/FictionArchive.Service.TranslationService/Models/IntegrationEvents/TranslationRequestCompletedEvent.cs deleted file mode 100644 index e2ead5f..0000000 --- a/FictionArchive.Service.TranslationService/Models/IntegrationEvents/TranslationRequestCompletedEvent.cs +++ /dev/null @@ -1,18 +0,0 @@ -using FictionArchive.Common.Enums; -using FictionArchive.Service.Shared.Services.EventBus; -using FictionArchive.Service.TranslationService.Models.Enums; - -namespace FictionArchive.Service.TranslationService.Models.IntegrationEvents; - -public class TranslationRequestCompletedEvent : IIntegrationEvent -{ - /// - /// Maps this event back to a triggering request. - /// - public Guid? TranslationRequestId { get; set; } - - /// - /// The resulting text. - /// - public string? TranslatedText { get; set; } -} \ No newline at end of file diff --git a/FictionArchive.Service.TranslationService/Models/IntegrationEvents/TranslationRequestCreatedEvent.cs b/FictionArchive.Service.TranslationService/Models/IntegrationEvents/TranslationRequestCreatedEvent.cs deleted file mode 100644 index 4d627cb..0000000 --- a/FictionArchive.Service.TranslationService/Models/IntegrationEvents/TranslationRequestCreatedEvent.cs +++ /dev/null @@ -1,13 +0,0 @@ -using FictionArchive.Common.Enums; -using FictionArchive.Service.Shared.Services.EventBus; - -namespace FictionArchive.Service.TranslationService.Models.IntegrationEvents; - -public class TranslationRequestCreatedEvent : IIntegrationEvent -{ - public Guid TranslationRequestId { get; set; } - public Language From { get; set; } - public Language To { get; set; } - public string Body { get; set; } - public string TranslationEngineKey { get; set; } -} \ No newline at end of file diff --git a/FictionArchive.Service.TranslationService/Program.cs b/FictionArchive.Service.TranslationService/Program.cs index 9f4972c..bcd9fd1 100644 --- a/FictionArchive.Service.TranslationService/Program.cs +++ b/FictionArchive.Service.TranslationService/Program.cs @@ -2,16 +2,13 @@ using DeepL; using FictionArchive.Common.Extensions; using FictionArchive.Service.Shared; using FictionArchive.Service.Shared.Extensions; -using FictionArchive.Service.Shared.Services.EventBus.Implementations; using FictionArchive.Service.Shared.Services.GraphQL; +using FictionArchive.Service.TranslationService.Consumers; using FictionArchive.Service.TranslationService.GraphQL; -using FictionArchive.Service.TranslationService.Models.IntegrationEvents; using FictionArchive.Service.TranslationService.Services; using FictionArchive.Service.TranslationService.Services.Database; -using FictionArchive.Service.TranslationService.Services.EventHandlers; using FictionArchive.Service.TranslationService.Services.TranslationEngines; using FictionArchive.Service.TranslationService.Services.TranslationEngines.DeepLTranslate; -using RabbitMQ.Client; namespace FictionArchive.Service.TranslationService; @@ -26,15 +23,16 @@ public class Program builder.Services.AddHealthChecks(); - #region Event Bus + #region MassTransit if (!isSchemaExport) { - builder.Services.AddRabbitMQ(opt => - { - builder.Configuration.GetSection("RabbitMQ").Bind(opt); - }) - .Subscribe(); + builder.Services.AddFictionArchiveMassTransit( + builder.Configuration, + x => + { + x.AddConsumer(); + }); } #endregion diff --git a/FictionArchive.Service.TranslationService/Services/EventHandlers/TranslationRequestCreatedEventHandler.cs b/FictionArchive.Service.TranslationService/Services/EventHandlers/TranslationRequestCreatedEventHandler.cs deleted file mode 100644 index a824d34..0000000 --- a/FictionArchive.Service.TranslationService/Services/EventHandlers/TranslationRequestCreatedEventHandler.cs +++ /dev/null @@ -1,31 +0,0 @@ -using FictionArchive.Service.Shared.Services.EventBus; -using FictionArchive.Service.TranslationService.Models.Enums; -using FictionArchive.Service.TranslationService.Models.IntegrationEvents; - -namespace FictionArchive.Service.TranslationService.Services.EventHandlers; - -public class TranslationRequestCreatedEventHandler : IIntegrationEventHandler -{ - private readonly ILogger _logger; - private readonly TranslationEngineService _translationEngineService; - private readonly IEventBus _eventBus; - - public TranslationRequestCreatedEventHandler(ILogger logger, TranslationEngineService translationEngineService) - { - _logger = logger; - _translationEngineService = translationEngineService; - } - - public async Task Handle(TranslationRequestCreatedEvent @event) - { - var result = await _translationEngineService.Translate(@event.From, @event.To, @event.Body, @event.TranslationEngineKey); - if (result.Status == TranslationRequestStatus.Success) - { - await _eventBus.Publish(new TranslationRequestCompletedEvent() - { - TranslatedText = result.TranslatedText, - TranslationRequestId = @event.TranslationRequestId, - }); - } - } -} \ No newline at end of file diff --git a/FictionArchive.Service.TranslationService/Services/TranslationEngineService.cs b/FictionArchive.Service.TranslationService/Services/TranslationEngineService.cs index 3768704..7db19a3 100644 --- a/FictionArchive.Service.TranslationService/Services/TranslationEngineService.cs +++ b/FictionArchive.Service.TranslationService/Services/TranslationEngineService.cs @@ -1,28 +1,21 @@ -using System.Text; using FictionArchive.Common.Enums; -using FictionArchive.Service.Shared.Services.EventBus; -using FictionArchive.Service.Shared.Services.EventBus.Implementations; using FictionArchive.Service.TranslationService.Models; using FictionArchive.Service.TranslationService.Models.Database; using FictionArchive.Service.TranslationService.Models.Enums; -using FictionArchive.Service.TranslationService.Models.IntegrationEvents; using FictionArchive.Service.TranslationService.Services.Database; using FictionArchive.Service.TranslationService.Services.TranslationEngines; -using RabbitMQ.Client; namespace FictionArchive.Service.TranslationService.Services; public class TranslationEngineService { private readonly IEnumerable _translationEngines; - private readonly IEventBus _eventBus; private readonly TranslationServiceDbContext _dbContext; - public TranslationEngineService(IEnumerable translationEngines, TranslationServiceDbContext dbContext, IEventBus eventBus) + public TranslationEngineService(IEnumerable translationEngines, TranslationServiceDbContext dbContext) { _translationEngines = translationEngines; _dbContext = dbContext; - _eventBus = eventBus; } public async Task Translate(Language from, Language to, string text, string translationEngineKey) diff --git a/FictionArchive.Service.TranslationService/appsettings.json b/FictionArchive.Service.TranslationService/appsettings.json index 7f2534b..9d1b67d 100644 --- a/FictionArchive.Service.TranslationService/appsettings.json +++ b/FictionArchive.Service.TranslationService/appsettings.json @@ -2,7 +2,8 @@ "Logging": { "LogLevel": { "Default": "Information", - "Microsoft.AspNetCore": "Warning" + "Microsoft.AspNetCore": "Warning", + "Microsoft.EntityFrameworkCore": "Warning" } }, "DeepL": { diff --git a/FictionArchive.Service.UserNovelDataService/Services/EventHandlers/ChapterCreatedEventHandler.cs b/FictionArchive.Service.UserNovelDataService/Consumers/ChapterCreatedConsumer.cs similarity index 52% rename from FictionArchive.Service.UserNovelDataService/Services/EventHandlers/ChapterCreatedEventHandler.cs rename to FictionArchive.Service.UserNovelDataService/Consumers/ChapterCreatedConsumer.cs index 74f46f7..293bb4a 100644 --- a/FictionArchive.Service.UserNovelDataService/Services/EventHandlers/ChapterCreatedEventHandler.cs +++ b/FictionArchive.Service.UserNovelDataService/Consumers/ChapterCreatedConsumer.cs @@ -1,53 +1,56 @@ -using FictionArchive.Service.Shared.Services.EventBus; +using FictionArchive.Service.Shared.Contracts.Events; using FictionArchive.Service.UserNovelDataService.Models.Database; -using FictionArchive.Service.UserNovelDataService.Models.IntegrationEvents; +using FictionArchive.Service.UserNovelDataService.Services; +using MassTransit; using Microsoft.EntityFrameworkCore; -namespace FictionArchive.Service.UserNovelDataService.Services.EventHandlers; +namespace FictionArchive.Service.UserNovelDataService.Consumers; -public class ChapterCreatedEventHandler : IIntegrationEventHandler +public class ChapterCreatedConsumer : IConsumer { + private readonly ILogger _logger; private readonly UserNovelDataServiceDbContext _dbContext; - private readonly ILogger _logger; - public ChapterCreatedEventHandler( - UserNovelDataServiceDbContext dbContext, - ILogger logger) + public ChapterCreatedConsumer( + ILogger logger, + UserNovelDataServiceDbContext dbContext) { - _dbContext = dbContext; _logger = logger; + _dbContext = dbContext; } - public async Task Handle(ChapterCreatedEvent @event) + public async Task Consume(ConsumeContext context) { + var message = context.Message; + // Ensure novel exists - var novelExists = await _dbContext.Novels.AnyAsync(n => n.Id == @event.NovelId); + var novelExists = await _dbContext.Novels.AnyAsync(n => n.Id == message.NovelId); if (!novelExists) { - var novel = new Novel { Id = @event.NovelId }; + var novel = new Novel { Id = message.NovelId }; _dbContext.Novels.Add(novel); } // Ensure volume exists - var volumeExists = await _dbContext.Volumes.AnyAsync(v => v.Id == @event.VolumeId); + var volumeExists = await _dbContext.Volumes.AnyAsync(v => v.Id == message.VolumeId); if (!volumeExists) { - var volume = new Volume { Id = @event.VolumeId }; + var volume = new Volume { Id = message.VolumeId }; _dbContext.Volumes.Add(volume); } // Create chapter if not exists - var chapterExists = await _dbContext.Chapters.AnyAsync(c => c.Id == @event.ChapterId); + var chapterExists = await _dbContext.Chapters.AnyAsync(c => c.Id == message.ChapterId); if (chapterExists) { - _logger.LogDebug("Chapter {ChapterId} already exists, skipping", @event.ChapterId); + _logger.LogDebug("Chapter {ChapterId} already exists, skipping", message.ChapterId); return; } - var chapter = new Chapter { Id = @event.ChapterId }; + var chapter = new Chapter { Id = message.ChapterId }; _dbContext.Chapters.Add(chapter); await _dbContext.SaveChangesAsync(); - _logger.LogInformation("Created chapter stub for {ChapterId} in novel {NovelId}", @event.ChapterId, @event.NovelId); + _logger.LogInformation("Created chapter stub for {ChapterId} in novel {NovelId}", message.ChapterId, message.NovelId); } } diff --git a/FictionArchive.Service.UserNovelDataService/Consumers/NovelCreatedConsumer.cs b/FictionArchive.Service.UserNovelDataService/Consumers/NovelCreatedConsumer.cs new file mode 100644 index 0000000..97d3603 --- /dev/null +++ b/FictionArchive.Service.UserNovelDataService/Consumers/NovelCreatedConsumer.cs @@ -0,0 +1,39 @@ +using FictionArchive.Service.Shared.Contracts.Events; +using FictionArchive.Service.UserNovelDataService.Models.Database; +using FictionArchive.Service.UserNovelDataService.Services; +using MassTransit; +using Microsoft.EntityFrameworkCore; + +namespace FictionArchive.Service.UserNovelDataService.Consumers; + +public class NovelCreatedConsumer : IConsumer +{ + private readonly ILogger _logger; + private readonly UserNovelDataServiceDbContext _dbContext; + + public NovelCreatedConsumer( + ILogger logger, + UserNovelDataServiceDbContext dbContext) + { + _logger = logger; + _dbContext = dbContext; + } + + public async Task Consume(ConsumeContext context) + { + var message = context.Message; + + var exists = await _dbContext.Novels.AnyAsync(n => n.Id == message.NovelId); + if (exists) + { + _logger.LogDebug("Novel {NovelId} already exists, skipping", message.NovelId); + return; + } + + var novel = new Novel { Id = message.NovelId }; + _dbContext.Novels.Add(novel); + await _dbContext.SaveChangesAsync(); + + _logger.LogInformation("Created novel stub for {NovelId}", message.NovelId); + } +} diff --git a/FictionArchive.Service.UserNovelDataService/Consumers/UserInvitedConsumer.cs b/FictionArchive.Service.UserNovelDataService/Consumers/UserInvitedConsumer.cs new file mode 100644 index 0000000..5c7adf0 --- /dev/null +++ b/FictionArchive.Service.UserNovelDataService/Consumers/UserInvitedConsumer.cs @@ -0,0 +1,44 @@ +using FictionArchive.Service.Shared.Contracts.Events; +using FictionArchive.Service.UserNovelDataService.Models.Database; +using FictionArchive.Service.UserNovelDataService.Services; +using MassTransit; +using Microsoft.EntityFrameworkCore; + +namespace FictionArchive.Service.UserNovelDataService.Consumers; + +public class UserInvitedConsumer : IConsumer +{ + private readonly ILogger _logger; + private readonly UserNovelDataServiceDbContext _dbContext; + + public UserInvitedConsumer( + ILogger logger, + UserNovelDataServiceDbContext dbContext) + { + _logger = logger; + _dbContext = dbContext; + } + + public async Task Consume(ConsumeContext context) + { + var message = context.Message; + + var userId = Guid.Parse(message.InvitedUserId); + var exists = await _dbContext.Users.AnyAsync(u => u.Id == userId); + if (exists) + { + _logger.LogDebug("User {UserId} already exists, skipping", message.InvitedUserId); + return; + } + + var user = new User + { + Id = userId, + OAuthProviderId = message.InvitedOAuthProviderId + }; + _dbContext.Users.Add(user); + await _dbContext.SaveChangesAsync(); + + _logger.LogInformation("Created user stub for {UserId}", message.InvitedUserId); + } +} diff --git a/FictionArchive.Service.UserNovelDataService/Models/IntegrationEvents/ChapterCreatedEvent.cs b/FictionArchive.Service.UserNovelDataService/Models/IntegrationEvents/ChapterCreatedEvent.cs deleted file mode 100644 index 2591f68..0000000 --- a/FictionArchive.Service.UserNovelDataService/Models/IntegrationEvents/ChapterCreatedEvent.cs +++ /dev/null @@ -1,13 +0,0 @@ -using FictionArchive.Service.Shared.Services.EventBus; - -namespace FictionArchive.Service.UserNovelDataService.Models.IntegrationEvents; - -public class ChapterCreatedEvent : IIntegrationEvent -{ - public required uint ChapterId { get; init; } - public required uint NovelId { get; init; } - public required uint VolumeId { get; init; } - public required int VolumeOrder { get; init; } - public required uint ChapterOrder { get; init; } - public required string ChapterTitle { get; init; } -} diff --git a/FictionArchive.Service.UserNovelDataService/Models/IntegrationEvents/NovelCreatedEvent.cs b/FictionArchive.Service.UserNovelDataService/Models/IntegrationEvents/NovelCreatedEvent.cs deleted file mode 100644 index f55c349..0000000 --- a/FictionArchive.Service.UserNovelDataService/Models/IntegrationEvents/NovelCreatedEvent.cs +++ /dev/null @@ -1,13 +0,0 @@ -using FictionArchive.Common.Enums; -using FictionArchive.Service.Shared.Services.EventBus; - -namespace FictionArchive.Service.UserNovelDataService.Models.IntegrationEvents; - -public class NovelCreatedEvent : IIntegrationEvent -{ - public required uint NovelId { get; init; } - public required string Title { get; init; } - public required Language OriginalLanguage { get; init; } - public required string Source { get; init; } - public required string AuthorName { get; init; } -} diff --git a/FictionArchive.Service.UserNovelDataService/Models/IntegrationEvents/UserInvitedEvent.cs b/FictionArchive.Service.UserNovelDataService/Models/IntegrationEvents/UserInvitedEvent.cs deleted file mode 100644 index 609f029..0000000 --- a/FictionArchive.Service.UserNovelDataService/Models/IntegrationEvents/UserInvitedEvent.cs +++ /dev/null @@ -1,15 +0,0 @@ -using FictionArchive.Service.Shared.Services.EventBus; - -namespace FictionArchive.Service.UserNovelDataService.Models.IntegrationEvents; - -public class UserInvitedEvent : IIntegrationEvent -{ - public Guid InvitedUserId { get; set; } - public required string InvitedUsername { get; set; } - public required string InvitedEmail { get; set; } - public required string InvitedOAuthProviderId { get; set; } - - public Guid InviterId { get; set; } - public required string InviterUsername { get; set; } - public required string InviterOAuthProviderId { get; set; } -} diff --git a/FictionArchive.Service.UserNovelDataService/Program.cs b/FictionArchive.Service.UserNovelDataService/Program.cs index d14cc0e..c06317f 100644 --- a/FictionArchive.Service.UserNovelDataService/Program.cs +++ b/FictionArchive.Service.UserNovelDataService/Program.cs @@ -1,11 +1,9 @@ using FictionArchive.Common.Extensions; using FictionArchive.Service.Shared; using FictionArchive.Service.Shared.Extensions; -using FictionArchive.Service.Shared.Services.EventBus.Implementations; +using FictionArchive.Service.UserNovelDataService.Consumers; using FictionArchive.Service.UserNovelDataService.GraphQL; -using FictionArchive.Service.UserNovelDataService.Models.IntegrationEvents; using FictionArchive.Service.UserNovelDataService.Services; -using FictionArchive.Service.UserNovelDataService.Services.EventHandlers; namespace FictionArchive.Service.UserNovelDataService; @@ -22,17 +20,18 @@ public class Program builder.Services.AddMemoryCache(); builder.Services.AddHealthChecks(); - #region Event Bus + #region MassTransit if (!isSchemaExport) { - builder.Services.AddRabbitMQ(opt => - { - builder.Configuration.GetSection("RabbitMQ").Bind(opt); - }) - .Subscribe() - .Subscribe() - .Subscribe(); + builder.Services.AddFictionArchiveMassTransit( + builder.Configuration, + x => + { + x.AddConsumer(); + x.AddConsumer(); + x.AddConsumer(); + }); } #endregion diff --git a/FictionArchive.Service.UserNovelDataService/Services/EventHandlers/NovelCreatedEventHandler.cs b/FictionArchive.Service.UserNovelDataService/Services/EventHandlers/NovelCreatedEventHandler.cs deleted file mode 100644 index 1e47531..0000000 --- a/FictionArchive.Service.UserNovelDataService/Services/EventHandlers/NovelCreatedEventHandler.cs +++ /dev/null @@ -1,36 +0,0 @@ -using FictionArchive.Service.Shared.Services.EventBus; -using FictionArchive.Service.UserNovelDataService.Models.Database; -using FictionArchive.Service.UserNovelDataService.Models.IntegrationEvents; -using Microsoft.EntityFrameworkCore; - -namespace FictionArchive.Service.UserNovelDataService.Services.EventHandlers; - -public class NovelCreatedEventHandler : IIntegrationEventHandler -{ - private readonly UserNovelDataServiceDbContext _dbContext; - private readonly ILogger _logger; - - public NovelCreatedEventHandler( - UserNovelDataServiceDbContext dbContext, - ILogger logger) - { - _dbContext = dbContext; - _logger = logger; - } - - public async Task Handle(NovelCreatedEvent @event) - { - var exists = await _dbContext.Novels.AnyAsync(n => n.Id == @event.NovelId); - if (exists) - { - _logger.LogDebug("Novel {NovelId} already exists, skipping", @event.NovelId); - return; - } - - var novel = new Novel { Id = @event.NovelId }; - _dbContext.Novels.Add(novel); - await _dbContext.SaveChangesAsync(); - - _logger.LogInformation("Created novel stub for {NovelId}", @event.NovelId); - } -} diff --git a/FictionArchive.Service.UserNovelDataService/Services/EventHandlers/UserInvitedEventHandler.cs b/FictionArchive.Service.UserNovelDataService/Services/EventHandlers/UserInvitedEventHandler.cs deleted file mode 100644 index a48a2c8..0000000 --- a/FictionArchive.Service.UserNovelDataService/Services/EventHandlers/UserInvitedEventHandler.cs +++ /dev/null @@ -1,40 +0,0 @@ -using FictionArchive.Service.Shared.Services.EventBus; -using FictionArchive.Service.UserNovelDataService.Models.Database; -using FictionArchive.Service.UserNovelDataService.Models.IntegrationEvents; -using Microsoft.EntityFrameworkCore; - -namespace FictionArchive.Service.UserNovelDataService.Services.EventHandlers; - -public class UserInvitedEventHandler : IIntegrationEventHandler -{ - private readonly UserNovelDataServiceDbContext _dbContext; - private readonly ILogger _logger; - - public UserInvitedEventHandler( - UserNovelDataServiceDbContext dbContext, - ILogger logger) - { - _dbContext = dbContext; - _logger = logger; - } - - public async Task Handle(UserInvitedEvent @event) - { - var exists = await _dbContext.Users.AnyAsync(u => u.Id == @event.InvitedUserId); - if (exists) - { - _logger.LogDebug("User {UserId} already exists, skipping", @event.InvitedUserId); - return; - } - - var user = new User - { - Id = @event.InvitedUserId, - OAuthProviderId = @event.InvitedOAuthProviderId - }; - _dbContext.Users.Add(user); - await _dbContext.SaveChangesAsync(); - - _logger.LogInformation("Created user stub for {UserId}", @event.InvitedUserId); - } -} diff --git a/FictionArchive.Service.UserNovelDataService/appsettings.json b/FictionArchive.Service.UserNovelDataService/appsettings.json index 425535a..7a9a338 100644 --- a/FictionArchive.Service.UserNovelDataService/appsettings.json +++ b/FictionArchive.Service.UserNovelDataService/appsettings.json @@ -2,7 +2,8 @@ "Logging": { "LogLevel": { "Default": "Information", - "Microsoft.AspNetCore": "Warning" + "Microsoft.AspNetCore": "Warning", + "Microsoft.EntityFrameworkCore": "Warning" } }, "ConnectionStrings": { diff --git a/FictionArchive.Service.UserService.Tests/UserManagementServiceTests.cs b/FictionArchive.Service.UserService.Tests/UserManagementServiceTests.cs index 9050d79..9233141 100644 --- a/FictionArchive.Service.UserService.Tests/UserManagementServiceTests.cs +++ b/FictionArchive.Service.UserService.Tests/UserManagementServiceTests.cs @@ -1,9 +1,9 @@ -using FictionArchive.Service.Shared.Services.EventBus; using FictionArchive.Service.UserService.Models.Database; using FictionArchive.Service.UserService.Services; using FictionArchive.Service.UserService.Services.AuthenticationClient; using FictionArchive.Service.UserService.Services.AuthenticationClient.Authentik; using FluentAssertions; +using MassTransit; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Logging.Abstractions; using NSubstitute; @@ -27,13 +27,13 @@ public class UserManagementServiceTests private static UserManagementService CreateService( UserServiceDbContext dbContext, IAuthenticationServiceClient authClient, - IEventBus? eventBus = null) + IPublishEndpoint? publishEndpoint = null) { return new UserManagementService( dbContext, NullLogger.Instance, authClient, - eventBus ?? Substitute.For()); + publishEndpoint ?? Substitute.For()); } private static User CreateTestUser(string username, string email, int availableInvites = 5) diff --git a/FictionArchive.Service.UserService/Contracts/UserInvited.cs b/FictionArchive.Service.UserService/Contracts/UserInvited.cs new file mode 100644 index 0000000..3cf108b --- /dev/null +++ b/FictionArchive.Service.UserService/Contracts/UserInvited.cs @@ -0,0 +1,12 @@ +using FictionArchive.Service.Shared.Contracts.Events; + +namespace FictionArchive.Service.UserService.Contracts; + +public record UserInvited( + string InvitedUserId, + string InvitedUsername, + string InvitedEmail, + string InvitedOAuthProviderId, + string InviterId, + string InviterUsername, + string InviterOAuthProviderId) : IUserInvited; diff --git a/FictionArchive.Service.UserService/Models/IntegrationEvents/UserInvitedEvent.cs b/FictionArchive.Service.UserService/Models/IntegrationEvents/UserInvitedEvent.cs deleted file mode 100644 index 5039e75..0000000 --- a/FictionArchive.Service.UserService/Models/IntegrationEvents/UserInvitedEvent.cs +++ /dev/null @@ -1,17 +0,0 @@ -using FictionArchive.Service.Shared.Services.EventBus; - -namespace FictionArchive.Service.UserService.Models.IntegrationEvents; - -public class UserInvitedEvent : IIntegrationEvent -{ - // Invited user info - public Guid InvitedUserId { get; set; } - public required string InvitedUsername { get; set; } - public required string InvitedEmail { get; set; } - public required string InvitedOAuthProviderId { get; set; } - - // Inviter info - public Guid InviterId { get; set; } - public required string InviterUsername { get; set; } - public required string InviterOAuthProviderId { get; set; } -} diff --git a/FictionArchive.Service.UserService/Program.cs b/FictionArchive.Service.UserService/Program.cs index 419c199..cdbfb84 100644 --- a/FictionArchive.Service.UserService/Program.cs +++ b/FictionArchive.Service.UserService/Program.cs @@ -2,7 +2,6 @@ using System.Net.Http.Headers; using FictionArchive.Common.Extensions; using FictionArchive.Service.Shared; using FictionArchive.Service.Shared.Extensions; -using FictionArchive.Service.Shared.Services.EventBus.Implementations; using FictionArchive.Service.UserService.GraphQL; using FictionArchive.Service.UserService.Services; using FictionArchive.Service.UserService.Services.AuthenticationClient; @@ -19,15 +18,9 @@ public class Program var builder = WebApplication.CreateBuilder(args); builder.AddLocalAppsettings(); - #region Event Bus + #region MassTransit - if (!isSchemaExport) - { - builder.Services.AddRabbitMQ(opt => - { - builder.Configuration.GetSection("RabbitMQ").Bind(opt); - }); - } + builder.Services.AddFictionArchiveMassTransit(builder.Configuration); #endregion diff --git a/FictionArchive.Service.UserService/Services/UserManagementService.cs b/FictionArchive.Service.UserService/Services/UserManagementService.cs index 6a5a496..b690689 100644 --- a/FictionArchive.Service.UserService/Services/UserManagementService.cs +++ b/FictionArchive.Service.UserService/Services/UserManagementService.cs @@ -1,7 +1,8 @@ -using FictionArchive.Service.Shared.Services.EventBus; +using FictionArchive.Service.Shared.Contracts.Events; +using FictionArchive.Service.UserService.Contracts; using FictionArchive.Service.UserService.Models.Database; -using FictionArchive.Service.UserService.Models.IntegrationEvents; using FictionArchive.Service.UserService.Services.AuthenticationClient; +using MassTransit; using Microsoft.EntityFrameworkCore; namespace FictionArchive.Service.UserService.Services; @@ -11,18 +12,18 @@ public class UserManagementService private readonly ILogger _logger; private readonly UserServiceDbContext _dbContext; private readonly IAuthenticationServiceClient _authClient; - private readonly IEventBus _eventBus; + private readonly IPublishEndpoint _publishEndpoint; public UserManagementService( UserServiceDbContext dbContext, ILogger logger, IAuthenticationServiceClient authClient, - IEventBus eventBus) + IPublishEndpoint publishEndpoint) { _dbContext = dbContext; _logger = logger; _authClient = authClient; - _eventBus = eventBus; + _publishEndpoint = publishEndpoint; } /// @@ -99,16 +100,14 @@ public class UserManagementService await _dbContext.SaveChangesAsync(); - await _eventBus.Publish(new UserInvitedEvent - { - InvitedUserId = newUser.Id, - InvitedUsername = newUser.Username, - InvitedEmail = newUser.Email, - InvitedOAuthProviderId = newUser.OAuthProviderId, - InviterId = inviter.Id, - InviterUsername = inviter.Username, - InviterOAuthProviderId = inviter.OAuthProviderId - }); + await _publishEndpoint.Publish(new UserInvited( + InvitedUserId: newUser.Id.ToString(), + InvitedUsername: newUser.Username, + InvitedEmail: newUser.Email, + InvitedOAuthProviderId: newUser.OAuthProviderId, + InviterId: inviter.Id.ToString(), + InviterUsername: inviter.Username, + InviterOAuthProviderId: inviter.OAuthProviderId)); _logger.LogInformation( "User {Username} was successfully invited by {InviterId}. New user id: {NewUserId}", diff --git a/FictionArchive.Service.UserService/appsettings.json b/FictionArchive.Service.UserService/appsettings.json index 2182f2f..b5fa583 100644 --- a/FictionArchive.Service.UserService/appsettings.json +++ b/FictionArchive.Service.UserService/appsettings.json @@ -2,7 +2,8 @@ "Logging": { "LogLevel": { "Default": "Information", - "Microsoft.AspNetCore": "Warning" + "Microsoft.AspNetCore": "Warning", + "Microsoft.EntityFrameworkCore": "Warning" } }, "ConnectionStrings": { diff --git a/fictionarchive-web-astro/src/lib/graphql/__generated__/graphql.ts b/fictionarchive-web-astro/src/lib/graphql/__generated__/graphql.ts index 1a5ffd9..1f5a4da 100644 --- a/fictionarchive-web-astro/src/lib/graphql/__generated__/graphql.ts +++ b/fictionarchive-web-astro/src/lib/graphql/__generated__/graphql.ts @@ -80,7 +80,7 @@ export type ChapterDtoFilterInput = { url?: InputMaybe; }; -export type ChapterPullRequestedEvent = { +export type ChapterPullRequested = { chapterOrder: Scalars['UnsignedInt']['output']; novelId: Scalars['UnsignedInt']['output']; volumeId: Scalars['UnsignedInt']['output']; @@ -168,7 +168,7 @@ export type FetchChapterContentsInput = { }; export type FetchChapterContentsPayload = { - chapterPullRequestedEvent: Maybe; + chapterPullRequested: Maybe; }; export type FormatError = Error & { @@ -203,7 +203,7 @@ export type ImportNovelInput = { }; export type ImportNovelPayload = { - novelUpdateRequestedEvent: Maybe; + novelUpdateRequested: Maybe; }; export type InstantFilterInput = { @@ -499,7 +499,7 @@ export type NovelTagDtoFilterInput = { tagType?: InputMaybe; }; -export type NovelUpdateRequestedEvent = { +export type NovelUpdateRequested = { novelUrl: Scalars['String']['output']; }; @@ -1010,7 +1010,7 @@ export type ImportNovelMutationVariables = Exact<{ }>; -export type ImportNovelMutation = { importNovel: { novelUpdateRequestedEvent: { novelUrl: string } | null } }; +export type ImportNovelMutation = { importNovel: { novelUpdateRequested: { novelUrl: string } | null } }; export type InviteUserMutationVariables = Exact<{ input: InviteUserInput; @@ -1114,7 +1114,7 @@ export const AddToReadingListDocument = {"kind":"Document","definitions":[{"kind export const CreateReadingListDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"CreateReadingList"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"input"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"CreateReadingListInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"createReadingList"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"input"},"value":{"kind":"Variable","name":{"kind":"Name","value":"input"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"readingListPayload"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"success"}},{"kind":"Field","name":{"kind":"Name","value":"readingList"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"description"}},{"kind":"Field","name":{"kind":"Name","value":"itemCount"}},{"kind":"Field","name":{"kind":"Name","value":"createdTime"}}]}}]}},{"kind":"Field","name":{"kind":"Name","value":"errors"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"InlineFragment","typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Error"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"message"}}]}}]}}]}}]}}]} as unknown as DocumentNode; export const DeleteNovelDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"DeleteNovel"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"input"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"DeleteNovelInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"deleteNovel"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"input"},"value":{"kind":"Variable","name":{"kind":"Name","value":"input"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"boolean"}},{"kind":"Field","name":{"kind":"Name","value":"errors"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"InlineFragment","typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Error"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"message"}}]}}]}}]}}]}}]} as unknown as DocumentNode; export const DeleteReadingListDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"DeleteReadingList"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"input"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"DeleteReadingListInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"deleteReadingList"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"input"},"value":{"kind":"Variable","name":{"kind":"Name","value":"input"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"success"}},{"kind":"Field","name":{"kind":"Name","value":"errors"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"InlineFragment","typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Error"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"message"}}]}}]}}]}}]}}]} as unknown as DocumentNode; -export const ImportNovelDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"ImportNovel"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"input"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"ImportNovelInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"importNovel"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"input"},"value":{"kind":"Variable","name":{"kind":"Name","value":"input"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"novelUpdateRequestedEvent"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"novelUrl"}}]}}]}}]}}]} as unknown as DocumentNode; +export const ImportNovelDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"ImportNovel"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"input"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"ImportNovelInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"importNovel"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"input"},"value":{"kind":"Variable","name":{"kind":"Name","value":"input"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"novelUpdateRequested"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"novelUrl"}}]}}]}}]}}]} as unknown as DocumentNode; export const InviteUserDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"InviteUser"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"input"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"InviteUserInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"inviteUser"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"input"},"value":{"kind":"Variable","name":{"kind":"Name","value":"input"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"userDto"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"username"}},{"kind":"Field","name":{"kind":"Name","value":"email"}}]}},{"kind":"Field","name":{"kind":"Name","value":"errors"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"InlineFragment","typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"InvalidOperationError"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"message"}}]}}]}}]}}]}}]} as unknown as DocumentNode; export const RemoveBookmarkDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"RemoveBookmark"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"input"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"RemoveBookmarkInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"removeBookmark"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"input"},"value":{"kind":"Variable","name":{"kind":"Name","value":"input"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"bookmarkPayload"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"success"}}]}},{"kind":"Field","name":{"kind":"Name","value":"errors"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"InlineFragment","typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Error"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"message"}}]}}]}}]}}]}}]} as unknown as DocumentNode; export const RemoveFromReadingListDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"RemoveFromReadingList"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"input"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"RemoveFromReadingListInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"removeFromReadingList"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"input"},"value":{"kind":"Variable","name":{"kind":"Name","value":"input"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"readingListPayload"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"success"}},{"kind":"Field","name":{"kind":"Name","value":"readingList"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"itemCount"}}]}}]}},{"kind":"Field","name":{"kind":"Name","value":"errors"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"InlineFragment","typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Error"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"message"}}]}}]}}]}}]}}]} as unknown as DocumentNode; diff --git a/fictionarchive-web-astro/src/lib/graphql/mutations/importNovel.graphql b/fictionarchive-web-astro/src/lib/graphql/mutations/importNovel.graphql index 887afa3..83ea3b2 100644 --- a/fictionarchive-web-astro/src/lib/graphql/mutations/importNovel.graphql +++ b/fictionarchive-web-astro/src/lib/graphql/mutations/importNovel.graphql @@ -1,6 +1,6 @@ mutation ImportNovel($input: ImportNovelInput!) { importNovel(input: $input) { - novelUpdateRequestedEvent { + novelUpdateRequested { novelUrl } }