diff --git a/FictionArchive.Service.NovelService/GraphQL/Mutation.cs b/FictionArchive.Service.NovelService/GraphQL/Mutation.cs index 7b014f5..15f92a7 100644 --- a/FictionArchive.Service.NovelService/GraphQL/Mutation.cs +++ b/FictionArchive.Service.NovelService/GraphQL/Mutation.cs @@ -12,26 +12,15 @@ namespace FictionArchive.Service.NovelService.GraphQL; public class Mutation { - public async Task ImportNovel(string novelUrl, IEventBus eventBus) + public async Task ImportNovel(string novelUrl, NovelUpdateService service) { - var importNovelRequestEvent = new NovelUpdateRequestedEvent() - { - NovelUrl = novelUrl - }; - await eventBus.Publish(importNovelRequestEvent); - return importNovelRequestEvent; + return await service.QueueNovelImport(novelUrl); } public async Task FetchChapterContents(uint novelId, uint chapterNumber, - IEventBus eventBus) + NovelUpdateService service) { - var chapterPullEvent = new ChapterPullRequestedEvent() - { - NovelId = novelId, - ChapterNumber = chapterNumber - }; - await eventBus.Publish(chapterPullEvent); - return chapterPullEvent; + return await service.QueueChapterPull(novelId, chapterNumber); } } \ No newline at end of file diff --git a/FictionArchive.Service.NovelService/Program.cs b/FictionArchive.Service.NovelService/Program.cs index 68fe593..ff9fd26 100644 --- a/FictionArchive.Service.NovelService/Program.cs +++ b/FictionArchive.Service.NovelService/Program.cs @@ -6,6 +6,7 @@ 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; @@ -17,6 +18,8 @@ public class Program { public static void Main(string[] args) { + var isSchemaExport = SchemaExportDetector.IsSchemaExportMode(args); + var builder = WebApplication.CreateBuilder(args); builder.AddLocalAppsettings(); @@ -24,15 +27,18 @@ public class Program #region Event Bus - builder.Services.AddRabbitMQ(opt => + if (!isSchemaExport) { - builder.Configuration.GetSection("RabbitMQ").Bind(opt); - }) - .Subscribe() - .Subscribe() - .Subscribe() - .Subscribe(); - + builder.Services.AddRabbitMQ(opt => + { + builder.Configuration.GetSection("RabbitMQ").Bind(opt); + }) + .Subscribe() + .Subscribe() + .Subscribe() + .Subscribe(); + } + #endregion #region GraphQL @@ -43,7 +49,9 @@ public class Program #region Database - builder.Services.RegisterDbContext(builder.Configuration.GetConnectionString("DefaultConnection")); + builder.Services.RegisterDbContext( + builder.Configuration.GetConnectionString("DefaultConnection"), + skipInfrastructure: isSchemaExport); #endregion @@ -69,9 +77,10 @@ public class Program var app = builder.Build(); - // Update database - using (var scope = app.Services.CreateScope()) + // Update database (skip in schema export mode) + if (!isSchemaExport) { + using var scope = app.Services.CreateScope(); var dbContext = scope.ServiceProvider.GetRequiredService(); dbContext.UpdateDatabase(); } diff --git a/FictionArchive.Service.NovelService/Services/NovelUpdateService.cs b/FictionArchive.Service.NovelService/Services/NovelUpdateService.cs index de3438b..862dcbe 100644 --- a/FictionArchive.Service.NovelService/Services/NovelUpdateService.cs +++ b/FictionArchive.Service.NovelService/Services/NovelUpdateService.cs @@ -2,6 +2,7 @@ using FictionArchive.Service.FileService.IntegrationEvents; 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; @@ -201,4 +202,25 @@ public class NovelUpdateService await _dbContext.SaveChangesAsync(); } + + public async Task QueueNovelImport(string novelUrl) + { + var importNovelRequestEvent = new NovelUpdateRequestedEvent() + { + NovelUrl = novelUrl + }; + await _eventBus.Publish(importNovelRequestEvent); + return importNovelRequestEvent; + } + + public async Task QueueChapterPull(uint novelId, uint chapterNumber) + { + var chapterPullEvent = new ChapterPullRequestedEvent() + { + NovelId = novelId, + ChapterNumber = chapterNumber + }; + await _eventBus.Publish(chapterPullEvent); + return chapterPullEvent; + } } diff --git a/FictionArchive.Service.SchedulerService/Program.cs b/FictionArchive.Service.SchedulerService/Program.cs index 9d40de1..01e47d9 100644 --- a/FictionArchive.Service.SchedulerService/Program.cs +++ b/FictionArchive.Service.SchedulerService/Program.cs @@ -1,5 +1,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; @@ -11,6 +12,8 @@ public class Program { public static void Main(string[] args) { + var isSchemaExport = SchemaExportDetector.IsSchemaExportMode(args); + var builder = WebApplication.CreateBuilder(args); // Services @@ -20,45 +23,63 @@ public class Program #region Database - builder.Services.RegisterDbContext(builder.Configuration.GetConnectionString("DefaultConnection")); + builder.Services.RegisterDbContext( + builder.Configuration.GetConnectionString("DefaultConnection"), + skipInfrastructure: isSchemaExport); #endregion #region Event Bus - builder.Services.AddRabbitMQ(opt => + if (!isSchemaExport) { - builder.Configuration.GetSection("RabbitMQ").Bind(opt); - }); - + builder.Services.AddRabbitMQ(opt => + { + builder.Configuration.GetSection("RabbitMQ").Bind(opt); + }); + } + #endregion #region Quartz - builder.Services.AddQuartz(opt => + if (isSchemaExport) { - opt.UsePersistentStore(pso => + // Schema export mode: use in-memory store (no DB connection needed) + builder.Services.AddQuartz(opt => { - pso.UsePostgres(pgsql => - { - pgsql.ConnectionString = builder.Configuration.GetConnectionString("DefaultConnection"); - pgsql.UseDriverDelegate(); - pgsql.TablePrefix = "quartz.qrtz_"; // Needed for Postgres due to the differing schema used - }); - pso.UseNewtonsoftJsonSerializer(); + opt.UseInMemoryStore(); }); - }); - builder.Services.AddQuartzHostedService(opt => + } + else { - opt.WaitForJobsToComplete = true; - }); - + builder.Services.AddQuartz(opt => + { + opt.UsePersistentStore(pso => + { + pso.UsePostgres(pgsql => + { + pgsql.ConnectionString = builder.Configuration.GetConnectionString("DefaultConnection"); + pgsql.UseDriverDelegate(); + pgsql.TablePrefix = "quartz.qrtz_"; // Needed for Postgres due to the differing schema used + }); + pso.UseNewtonsoftJsonSerializer(); + }); + }); + builder.Services.AddQuartzHostedService(opt => + { + opt.WaitForJobsToComplete = true; + }); + } + #endregion var app = builder.Build(); - using (var scope = app.Services.CreateScope()) + // Update database (skip in schema export mode) + if (!isSchemaExport) { + using var scope = app.Services.CreateScope(); var dbContext = scope.ServiceProvider.GetRequiredService(); dbContext.UpdateDatabase(); } diff --git a/FictionArchive.Service.Shared/Extensions/DatabaseExtensions.cs b/FictionArchive.Service.Shared/Extensions/DatabaseExtensions.cs index c373f51..03b2a01 100644 --- a/FictionArchive.Service.Shared/Extensions/DatabaseExtensions.cs +++ b/FictionArchive.Service.Shared/Extensions/DatabaseExtensions.cs @@ -6,16 +6,29 @@ namespace FictionArchive.Service.Shared.Extensions; public static class DatabaseExtensions { - public static IServiceCollection RegisterDbContext(this IServiceCollection services, - string connectionString) where TContext : FictionArchiveDbContext + public static IServiceCollection RegisterDbContext( + this IServiceCollection services, + string connectionString, + bool skipInfrastructure = false) where TContext : FictionArchiveDbContext { - services.AddDbContext(options => + if (skipInfrastructure) { - options.UseNpgsql(connectionString, o => + // For schema export: use in-memory provider to allow EF Core entity discovery + services.AddDbContext(options => { - o.UseNodaTime(); + options.UseInMemoryDatabase($"SchemaExport_{typeof(TContext).Name}"); }); - }); + } + else + { + services.AddDbContext(options => + { + options.UseNpgsql(connectionString, o => + { + o.UseNodaTime(); + }); + }); + } return services; } } \ No newline at end of file diff --git a/FictionArchive.Service.Shared/FictionArchive.Service.Shared.csproj b/FictionArchive.Service.Shared/FictionArchive.Service.Shared.csproj index da7c156..ee7c426 100644 --- a/FictionArchive.Service.Shared/FictionArchive.Service.Shared.csproj +++ b/FictionArchive.Service.Shared/FictionArchive.Service.Shared.csproj @@ -18,6 +18,7 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive + all diff --git a/FictionArchive.Service.Shared/SchemaExportDetector.cs b/FictionArchive.Service.Shared/SchemaExportDetector.cs new file mode 100644 index 0000000..024860d --- /dev/null +++ b/FictionArchive.Service.Shared/SchemaExportDetector.cs @@ -0,0 +1,22 @@ +namespace FictionArchive.Service.Shared; + +/// +/// Detects if the application is running in schema export mode (for HotChocolate CLI commands). +/// In this mode, infrastructure like RabbitMQ and databases should not be initialized. +/// +public static class SchemaExportDetector +{ + /// + /// Checks if the current run is a schema export command. + /// + /// Command line arguments passed to Main() + /// True if running schema export, false otherwise + public static bool IsSchemaExportMode(string[] args) + { + // HotChocolate CLI pattern: "schema export" after "--" delimiter + // Handles: dotnet run -- schema export --output schema.graphql + var normalizedArgs = args.SkipWhile(a => a == "--").ToArray(); + return normalizedArgs.Length > 0 && + normalizedArgs[0].Equals("schema", StringComparison.OrdinalIgnoreCase); + } +} diff --git a/FictionArchive.Service.TranslationService/Program.cs b/FictionArchive.Service.TranslationService/Program.cs index 3fb305c..c110f78 100644 --- a/FictionArchive.Service.TranslationService/Program.cs +++ b/FictionArchive.Service.TranslationService/Program.cs @@ -1,5 +1,6 @@ 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; @@ -18,6 +19,8 @@ public class Program { public static void Main(string[] args) { + var isSchemaExport = SchemaExportDetector.IsSchemaExportMode(args); + var builder = WebApplication.CreateBuilder(args); builder.AddLocalAppsettings(); @@ -25,18 +28,23 @@ public class Program #region Event Bus - builder.Services.AddRabbitMQ(opt => + if (!isSchemaExport) { - builder.Configuration.GetSection("RabbitMQ").Bind(opt); - }) - .Subscribe(); - + builder.Services.AddRabbitMQ(opt => + { + builder.Configuration.GetSection("RabbitMQ").Bind(opt); + }) + .Subscribe(); + } + #endregion #region Database - builder.Services.RegisterDbContext(builder.Configuration.GetConnectionString("DefaultConnection")); + builder.Services.RegisterDbContext( + builder.Configuration.GetConnectionString("DefaultConnection"), + skipInfrastructure: isSchemaExport); #endregion @@ -60,9 +68,10 @@ public class Program var app = builder.Build(); - // Update database - using (var scope = app.Services.CreateScope()) + // Update database (skip in schema export mode) + if (!isSchemaExport) { + using var scope = app.Services.CreateScope(); var dbContext = scope.ServiceProvider.GetRequiredService(); dbContext.UpdateDatabase(); } diff --git a/FictionArchive.Service.UserService/Program.cs b/FictionArchive.Service.UserService/Program.cs index 13c397d..efc54e9 100644 --- a/FictionArchive.Service.UserService/Program.cs +++ b/FictionArchive.Service.UserService/Program.cs @@ -1,3 +1,4 @@ +using FictionArchive.Service.Shared; using FictionArchive.Service.Shared.Extensions; using FictionArchive.Service.Shared.Services.EventBus.Implementations; using FictionArchive.Service.UserService.GraphQL; @@ -11,16 +12,21 @@ public class Program { public static void Main(string[] args) { + var isSchemaExport = SchemaExportDetector.IsSchemaExportMode(args); + var builder = WebApplication.CreateBuilder(args); #region Event Bus - builder.Services.AddRabbitMQ(opt => + if (!isSchemaExport) { - builder.Configuration.GetSection("RabbitMQ").Bind(opt); - }) - .Subscribe(); - + builder.Services.AddRabbitMQ(opt => + { + builder.Configuration.GetSection("RabbitMQ").Bind(opt); + }) + .Subscribe(); + } + #endregion #region GraphQL @@ -29,16 +35,19 @@ public class Program #endregion - builder.Services.RegisterDbContext(builder.Configuration.GetConnectionString("DefaultConnection")); + builder.Services.RegisterDbContext( + builder.Configuration.GetConnectionString("DefaultConnection"), + skipInfrastructure: isSchemaExport); builder.Services.AddTransient(); builder.Services.AddHealthChecks(); var app = builder.Build(); - // Update database - using (var scope = app.Services.CreateScope()) + // Update database (skip in schema export mode) + if (!isSchemaExport) { + using var scope = app.Services.CreateScope(); var dbContext = scope.ServiceProvider.GetRequiredService(); dbContext.UpdateDatabase(); }