[FA-11] I'm getting sick of fusion but I dont see better alternatives
Some checks failed
Release / build-and-push (map[dockerfile:FictionArchive.API/Dockerfile name:api]) (pull_request) Has been cancelled
Release / build-and-push (map[dockerfile:FictionArchive.Service.AuthenticationService/Dockerfile name:authentication-service]) (pull_request) Has been cancelled
Release / build-and-push (map[dockerfile:FictionArchive.Service.FileService/Dockerfile name:file-service]) (pull_request) Has been cancelled
Release / build-and-push (map[dockerfile:FictionArchive.Service.NovelService/Dockerfile name:novel-service]) (pull_request) Has been cancelled
Release / build-and-push (map[dockerfile:FictionArchive.Service.SchedulerService/Dockerfile name:scheduler-service]) (pull_request) Has been cancelled
Release / build-and-push (map[dockerfile:FictionArchive.Service.TranslationService/Dockerfile name:translation-service]) (pull_request) Has been cancelled
Release / build-and-push (map[dockerfile:FictionArchive.Service.UserService/Dockerfile name:user-service]) (pull_request) Has been cancelled
Release / build-frontend (pull_request) Has been cancelled
Build Gateway / build-subgraphs (map[name:novel-service project:FictionArchive.Service.NovelService subgraph:Novel]) (pull_request) Failing after 51s
Build Gateway / build-subgraphs (map[name:translation-service project:FictionArchive.Service.TranslationService subgraph:Translation]) (pull_request) Has been cancelled
Build Gateway / build-subgraphs (map[name:user-service project:FictionArchive.Service.UserService subgraph:User]) (pull_request) Has been cancelled
Build Gateway / build-gateway (pull_request) Has been cancelled
Build Gateway / build-subgraphs (map[name:scheduler-service project:FictionArchive.Service.SchedulerService subgraph:Scheduler]) (pull_request) Has been cancelled
CI / build-frontend (pull_request) Has been cancelled
CI / build-backend (pull_request) Has been cancelled

This commit is contained in:
gamer147
2025-11-26 12:40:22 -05:00
parent 7e94f06853
commit b9115d78a9
9 changed files with 163 additions and 68 deletions

View File

@@ -12,26 +12,15 @@ namespace FictionArchive.Service.NovelService.GraphQL;
public class Mutation public class Mutation
{ {
public async Task<NovelUpdateRequestedEvent> ImportNovel(string novelUrl, IEventBus eventBus) public async Task<NovelUpdateRequestedEvent> ImportNovel(string novelUrl, NovelUpdateService service)
{ {
var importNovelRequestEvent = new NovelUpdateRequestedEvent() return await service.QueueNovelImport(novelUrl);
{
NovelUrl = novelUrl
};
await eventBus.Publish(importNovelRequestEvent);
return importNovelRequestEvent;
} }
public async Task<ChapterPullRequestedEvent> FetchChapterContents(uint novelId, public async Task<ChapterPullRequestedEvent> FetchChapterContents(uint novelId,
uint chapterNumber, uint chapterNumber,
IEventBus eventBus) NovelUpdateService service)
{ {
var chapterPullEvent = new ChapterPullRequestedEvent() return await service.QueueChapterPull(novelId, chapterNumber);
{
NovelId = novelId,
ChapterNumber = chapterNumber
};
await eventBus.Publish(chapterPullEvent);
return chapterPullEvent;
} }
} }

View File

@@ -6,6 +6,7 @@ using FictionArchive.Service.NovelService.Services;
using FictionArchive.Service.NovelService.Services.EventHandlers; using FictionArchive.Service.NovelService.Services.EventHandlers;
using FictionArchive.Service.NovelService.Services.SourceAdapters; using FictionArchive.Service.NovelService.Services.SourceAdapters;
using FictionArchive.Service.NovelService.Services.SourceAdapters.Novelpia; using FictionArchive.Service.NovelService.Services.SourceAdapters.Novelpia;
using FictionArchive.Service.Shared;
using FictionArchive.Service.Shared.Extensions; using FictionArchive.Service.Shared.Extensions;
using FictionArchive.Service.Shared.Services.EventBus.Implementations; using FictionArchive.Service.Shared.Services.EventBus.Implementations;
using FictionArchive.Service.Shared.Services.GraphQL; using FictionArchive.Service.Shared.Services.GraphQL;
@@ -17,6 +18,8 @@ public class Program
{ {
public static void Main(string[] args) public static void Main(string[] args)
{ {
var isSchemaExport = SchemaExportDetector.IsSchemaExportMode(args);
var builder = WebApplication.CreateBuilder(args); var builder = WebApplication.CreateBuilder(args);
builder.AddLocalAppsettings(); builder.AddLocalAppsettings();
@@ -24,15 +27,18 @@ public class Program
#region Event Bus #region Event Bus
builder.Services.AddRabbitMQ(opt => if (!isSchemaExport)
{ {
builder.Configuration.GetSection("RabbitMQ").Bind(opt); builder.Services.AddRabbitMQ(opt =>
}) {
.Subscribe<TranslationRequestCompletedEvent, TranslationRequestCompletedEventHandler>() builder.Configuration.GetSection("RabbitMQ").Bind(opt);
.Subscribe<NovelUpdateRequestedEvent, NovelUpdateRequestedEventHandler>() })
.Subscribe<ChapterPullRequestedEvent, ChapterPullRequestedEventHandler>() .Subscribe<TranslationRequestCompletedEvent, TranslationRequestCompletedEventHandler>()
.Subscribe<FileUploadRequestStatusUpdateEvent, FileUploadRequestStatusUpdateEventHandler>(); .Subscribe<NovelUpdateRequestedEvent, NovelUpdateRequestedEventHandler>()
.Subscribe<ChapterPullRequestedEvent, ChapterPullRequestedEventHandler>()
.Subscribe<FileUploadRequestStatusUpdateEvent, FileUploadRequestStatusUpdateEventHandler>();
}
#endregion #endregion
#region GraphQL #region GraphQL
@@ -43,7 +49,9 @@ public class Program
#region Database #region Database
builder.Services.RegisterDbContext<NovelServiceDbContext>(builder.Configuration.GetConnectionString("DefaultConnection")); builder.Services.RegisterDbContext<NovelServiceDbContext>(
builder.Configuration.GetConnectionString("DefaultConnection"),
skipInfrastructure: isSchemaExport);
#endregion #endregion
@@ -69,9 +77,10 @@ public class Program
var app = builder.Build(); var app = builder.Build();
// Update database // Update database (skip in schema export mode)
using (var scope = app.Services.CreateScope()) if (!isSchemaExport)
{ {
using var scope = app.Services.CreateScope();
var dbContext = scope.ServiceProvider.GetRequiredService<NovelServiceDbContext>(); var dbContext = scope.ServiceProvider.GetRequiredService<NovelServiceDbContext>();
dbContext.UpdateDatabase(); dbContext.UpdateDatabase();
} }

View File

@@ -2,6 +2,7 @@ using FictionArchive.Service.FileService.IntegrationEvents;
using FictionArchive.Service.NovelService.Models.Configuration; using FictionArchive.Service.NovelService.Models.Configuration;
using FictionArchive.Service.NovelService.Models.Enums; using FictionArchive.Service.NovelService.Models.Enums;
using FictionArchive.Service.NovelService.Models.Images; using FictionArchive.Service.NovelService.Models.Images;
using FictionArchive.Service.NovelService.Models.IntegrationEvents;
using FictionArchive.Service.NovelService.Models.Localization; using FictionArchive.Service.NovelService.Models.Localization;
using FictionArchive.Service.NovelService.Models.Novels; using FictionArchive.Service.NovelService.Models.Novels;
using FictionArchive.Service.NovelService.Models.SourceAdapters; using FictionArchive.Service.NovelService.Models.SourceAdapters;
@@ -201,4 +202,25 @@ public class NovelUpdateService
await _dbContext.SaveChangesAsync(); await _dbContext.SaveChangesAsync();
} }
public async Task<NovelUpdateRequestedEvent> QueueNovelImport(string novelUrl)
{
var importNovelRequestEvent = new NovelUpdateRequestedEvent()
{
NovelUrl = novelUrl
};
await _eventBus.Publish(importNovelRequestEvent);
return importNovelRequestEvent;
}
public async Task<ChapterPullRequestedEvent> QueueChapterPull(uint novelId, uint chapterNumber)
{
var chapterPullEvent = new ChapterPullRequestedEvent()
{
NovelId = novelId,
ChapterNumber = chapterNumber
};
await _eventBus.Publish(chapterPullEvent);
return chapterPullEvent;
}
} }

View File

@@ -1,5 +1,6 @@
using FictionArchive.Service.SchedulerService.GraphQL; using FictionArchive.Service.SchedulerService.GraphQL;
using FictionArchive.Service.SchedulerService.Services; using FictionArchive.Service.SchedulerService.Services;
using FictionArchive.Service.Shared;
using FictionArchive.Service.Shared.Extensions; using FictionArchive.Service.Shared.Extensions;
using FictionArchive.Service.Shared.Services.EventBus.Implementations; using FictionArchive.Service.Shared.Services.EventBus.Implementations;
using Quartz; using Quartz;
@@ -11,6 +12,8 @@ public class Program
{ {
public static void Main(string[] args) public static void Main(string[] args)
{ {
var isSchemaExport = SchemaExportDetector.IsSchemaExportMode(args);
var builder = WebApplication.CreateBuilder(args); var builder = WebApplication.CreateBuilder(args);
// Services // Services
@@ -20,45 +23,63 @@ public class Program
#region Database #region Database
builder.Services.RegisterDbContext<SchedulerServiceDbContext>(builder.Configuration.GetConnectionString("DefaultConnection")); builder.Services.RegisterDbContext<SchedulerServiceDbContext>(
builder.Configuration.GetConnectionString("DefaultConnection"),
skipInfrastructure: isSchemaExport);
#endregion #endregion
#region Event Bus #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 #endregion
#region Quartz #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 => opt.UseInMemoryStore();
{
pgsql.ConnectionString = builder.Configuration.GetConnectionString("DefaultConnection");
pgsql.UseDriverDelegate<PostgreSQLDelegate>();
pgsql.TablePrefix = "quartz.qrtz_"; // Needed for Postgres due to the differing schema used
});
pso.UseNewtonsoftJsonSerializer();
}); });
}); }
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<PostgreSQLDelegate>();
pgsql.TablePrefix = "quartz.qrtz_"; // Needed for Postgres due to the differing schema used
});
pso.UseNewtonsoftJsonSerializer();
});
});
builder.Services.AddQuartzHostedService(opt =>
{
opt.WaitForJobsToComplete = true;
});
}
#endregion #endregion
var app = builder.Build(); 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<SchedulerServiceDbContext>(); var dbContext = scope.ServiceProvider.GetRequiredService<SchedulerServiceDbContext>();
dbContext.UpdateDatabase(); dbContext.UpdateDatabase();
} }

View File

@@ -6,16 +6,29 @@ namespace FictionArchive.Service.Shared.Extensions;
public static class DatabaseExtensions public static class DatabaseExtensions
{ {
public static IServiceCollection RegisterDbContext<TContext>(this IServiceCollection services, public static IServiceCollection RegisterDbContext<TContext>(
string connectionString) where TContext : FictionArchiveDbContext this IServiceCollection services,
string connectionString,
bool skipInfrastructure = false) where TContext : FictionArchiveDbContext
{ {
services.AddDbContext<TContext>(options => if (skipInfrastructure)
{ {
options.UseNpgsql(connectionString, o => // For schema export: use in-memory provider to allow EF Core entity discovery
services.AddDbContext<TContext>(options =>
{ {
o.UseNodaTime(); options.UseInMemoryDatabase($"SchemaExport_{typeof(TContext).Name}");
}); });
}); }
else
{
services.AddDbContext<TContext>(options =>
{
options.UseNpgsql(connectionString, o =>
{
o.UseNodaTime();
});
});
}
return services; return services;
} }
} }

View File

@@ -18,6 +18,7 @@
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference> </PackageReference>
<PackageReference Include="Microsoft.EntityFrameworkCore.InMemory" Version="9.0.11" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="9.0.11" /> <PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="9.0.11" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="9.0.11"> <PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="9.0.11">
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>

View File

@@ -0,0 +1,22 @@
namespace FictionArchive.Service.Shared;
/// <summary>
/// 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.
/// </summary>
public static class SchemaExportDetector
{
/// <summary>
/// Checks if the current run is a schema export command.
/// </summary>
/// <param name="args">Command line arguments passed to Main()</param>
/// <returns>True if running schema export, false otherwise</returns>
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);
}
}

View File

@@ -1,5 +1,6 @@
using DeepL; using DeepL;
using FictionArchive.Common.Extensions; using FictionArchive.Common.Extensions;
using FictionArchive.Service.Shared;
using FictionArchive.Service.Shared.Extensions; using FictionArchive.Service.Shared.Extensions;
using FictionArchive.Service.Shared.Services.EventBus.Implementations; using FictionArchive.Service.Shared.Services.EventBus.Implementations;
using FictionArchive.Service.Shared.Services.GraphQL; using FictionArchive.Service.Shared.Services.GraphQL;
@@ -18,6 +19,8 @@ public class Program
{ {
public static void Main(string[] args) public static void Main(string[] args)
{ {
var isSchemaExport = SchemaExportDetector.IsSchemaExportMode(args);
var builder = WebApplication.CreateBuilder(args); var builder = WebApplication.CreateBuilder(args);
builder.AddLocalAppsettings(); builder.AddLocalAppsettings();
@@ -25,18 +28,23 @@ public class Program
#region Event Bus #region Event Bus
builder.Services.AddRabbitMQ(opt => if (!isSchemaExport)
{ {
builder.Configuration.GetSection("RabbitMQ").Bind(opt); builder.Services.AddRabbitMQ(opt =>
}) {
.Subscribe<TranslationRequestCreatedEvent, TranslationRequestCreatedEventHandler>(); builder.Configuration.GetSection("RabbitMQ").Bind(opt);
})
.Subscribe<TranslationRequestCreatedEvent, TranslationRequestCreatedEventHandler>();
}
#endregion #endregion
#region Database #region Database
builder.Services.RegisterDbContext<TranslationServiceDbContext>(builder.Configuration.GetConnectionString("DefaultConnection")); builder.Services.RegisterDbContext<TranslationServiceDbContext>(
builder.Configuration.GetConnectionString("DefaultConnection"),
skipInfrastructure: isSchemaExport);
#endregion #endregion
@@ -60,9 +68,10 @@ public class Program
var app = builder.Build(); var app = builder.Build();
// Update database // Update database (skip in schema export mode)
using (var scope = app.Services.CreateScope()) if (!isSchemaExport)
{ {
using var scope = app.Services.CreateScope();
var dbContext = scope.ServiceProvider.GetRequiredService<TranslationServiceDbContext>(); var dbContext = scope.ServiceProvider.GetRequiredService<TranslationServiceDbContext>();
dbContext.UpdateDatabase(); dbContext.UpdateDatabase();
} }

View File

@@ -1,3 +1,4 @@
using FictionArchive.Service.Shared;
using FictionArchive.Service.Shared.Extensions; using FictionArchive.Service.Shared.Extensions;
using FictionArchive.Service.Shared.Services.EventBus.Implementations; using FictionArchive.Service.Shared.Services.EventBus.Implementations;
using FictionArchive.Service.UserService.GraphQL; using FictionArchive.Service.UserService.GraphQL;
@@ -11,16 +12,21 @@ public class Program
{ {
public static void Main(string[] args) public static void Main(string[] args)
{ {
var isSchemaExport = SchemaExportDetector.IsSchemaExportMode(args);
var builder = WebApplication.CreateBuilder(args); var builder = WebApplication.CreateBuilder(args);
#region Event Bus #region Event Bus
builder.Services.AddRabbitMQ(opt => if (!isSchemaExport)
{ {
builder.Configuration.GetSection("RabbitMQ").Bind(opt); builder.Services.AddRabbitMQ(opt =>
}) {
.Subscribe<AuthUserAddedEvent, AuthUserAddedEventHandler>(); builder.Configuration.GetSection("RabbitMQ").Bind(opt);
})
.Subscribe<AuthUserAddedEvent, AuthUserAddedEventHandler>();
}
#endregion #endregion
#region GraphQL #region GraphQL
@@ -29,16 +35,19 @@ public class Program
#endregion #endregion
builder.Services.RegisterDbContext<UserServiceDbContext>(builder.Configuration.GetConnectionString("DefaultConnection")); builder.Services.RegisterDbContext<UserServiceDbContext>(
builder.Configuration.GetConnectionString("DefaultConnection"),
skipInfrastructure: isSchemaExport);
builder.Services.AddTransient<UserManagementService>(); builder.Services.AddTransient<UserManagementService>();
builder.Services.AddHealthChecks(); builder.Services.AddHealthChecks();
var app = builder.Build(); var app = builder.Build();
// Update database // Update database (skip in schema export mode)
using (var scope = app.Services.CreateScope()) if (!isSchemaExport)
{ {
using var scope = app.Services.CreateScope();
var dbContext = scope.ServiceProvider.GetRequiredService<UserServiceDbContext>(); var dbContext = scope.ServiceProvider.GetRequiredService<UserServiceDbContext>();
dbContext.UpdateDatabase(); dbContext.UpdateDatabase();
} }