From 0abb10bb00f743b296895aab90a82adf992d7665 Mon Sep 17 00:00:00 2001 From: gamer147 Date: Thu, 20 Nov 2025 09:04:45 -0500 Subject: [PATCH 1/2] [FA-9] Need to add persistence layer --- .../Dockerfile | 23 +++++ ...ionArchive.Service.SchedulerService.csproj | 26 +++++ .../GraphQL/Mutation.cs | 35 +++++++ .../GraphQL/Query.cs | 15 +++ .../Models/JobTemplates/EventJobTemplate.cs | 35 +++++++ .../Models/SchedulerJob.cs | 12 +++ .../Program.cs | 52 ++++++++++ .../Properties/launchSettings.json | 39 ++++++++ .../Services/JobManagerService.cs | 99 +++++++++++++++++++ .../appsettings.Development.json | 8 ++ .../appsettings.json | 13 +++ .../Extensions/GraphQLExtensions.cs | 1 + .../Services/EventBus/IEventBus.cs | 1 + .../Implementations/RabbitMQEventBus.cs | 11 ++- .../Services/GraphQL/LoggingErrorFilter.cs | 23 +++++ FictionArchive.sln | 6 ++ 16 files changed, 396 insertions(+), 3 deletions(-) create mode 100644 FictionArchive.Service.SchedulerService/Dockerfile create mode 100644 FictionArchive.Service.SchedulerService/FictionArchive.Service.SchedulerService.csproj create mode 100644 FictionArchive.Service.SchedulerService/GraphQL/Mutation.cs create mode 100644 FictionArchive.Service.SchedulerService/GraphQL/Query.cs create mode 100644 FictionArchive.Service.SchedulerService/Models/JobTemplates/EventJobTemplate.cs create mode 100644 FictionArchive.Service.SchedulerService/Models/SchedulerJob.cs create mode 100644 FictionArchive.Service.SchedulerService/Program.cs create mode 100644 FictionArchive.Service.SchedulerService/Properties/launchSettings.json create mode 100644 FictionArchive.Service.SchedulerService/Services/JobManagerService.cs create mode 100644 FictionArchive.Service.SchedulerService/appsettings.Development.json create mode 100644 FictionArchive.Service.SchedulerService/appsettings.json create mode 100644 FictionArchive.Service.Shared/Services/GraphQL/LoggingErrorFilter.cs diff --git a/FictionArchive.Service.SchedulerService/Dockerfile b/FictionArchive.Service.SchedulerService/Dockerfile new file mode 100644 index 0000000..fd21bd5 --- /dev/null +++ b/FictionArchive.Service.SchedulerService/Dockerfile @@ -0,0 +1,23 @@ +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.SchedulerService/FictionArchive.Service.SchedulerService.csproj", "FictionArchive.Service.SchedulerService/"] +RUN dotnet restore "FictionArchive.Service.SchedulerService/FictionArchive.Service.SchedulerService.csproj" +COPY . . +WORKDIR "/src/FictionArchive.Service.SchedulerService" +RUN dotnet build "./FictionArchive.Service.SchedulerService.csproj" -c $BUILD_CONFIGURATION -o /app/build + +FROM build AS publish +ARG BUILD_CONFIGURATION=Release +RUN dotnet publish "./FictionArchive.Service.SchedulerService.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.SchedulerService.dll"] diff --git a/FictionArchive.Service.SchedulerService/FictionArchive.Service.SchedulerService.csproj b/FictionArchive.Service.SchedulerService/FictionArchive.Service.SchedulerService.csproj new file mode 100644 index 0000000..ccdea73 --- /dev/null +++ b/FictionArchive.Service.SchedulerService/FictionArchive.Service.SchedulerService.csproj @@ -0,0 +1,26 @@ + + + + net8.0 + enable + enable + Linux + + + + + .dockerignore + + + + + + + + + + + + + + diff --git a/FictionArchive.Service.SchedulerService/GraphQL/Mutation.cs b/FictionArchive.Service.SchedulerService/GraphQL/Mutation.cs new file mode 100644 index 0000000..e5118b5 --- /dev/null +++ b/FictionArchive.Service.SchedulerService/GraphQL/Mutation.cs @@ -0,0 +1,35 @@ +using System.Data; +using FictionArchive.Service.SchedulerService.Models; +using FictionArchive.Service.SchedulerService.Services; +using HotChocolate.Types; +using Quartz; + +namespace FictionArchive.Service.SchedulerService.GraphQL; + +public class Mutation +{ + [Error] + [Error] + public async Task ScheduleEventJob(string key, string description, string eventType, string eventData, string cronSchedule, JobManagerService jobManager) + { + return await jobManager.ScheduleEventJob(key, description, eventType, eventData, cronSchedule); + } + + [Error] + public async Task RunJob(string jobKey, JobManagerService jobManager) + { + return await jobManager.TriggerJob(jobKey); + } + + [Error] + public async Task DeleteJob(string jobKey, JobManagerService jobManager) + { + bool deleted = await jobManager.DeleteJob(jobKey); + if (!deleted) + { + throw new KeyNotFoundException($"Job with key {jobKey} was not found"); + } + + return true; + } +} \ No newline at end of file diff --git a/FictionArchive.Service.SchedulerService/GraphQL/Query.cs b/FictionArchive.Service.SchedulerService/GraphQL/Query.cs new file mode 100644 index 0000000..2594569 --- /dev/null +++ b/FictionArchive.Service.SchedulerService/GraphQL/Query.cs @@ -0,0 +1,15 @@ +using FictionArchive.Service.SchedulerService.Models; +using FictionArchive.Service.SchedulerService.Services; +using HotChocolate; +using Quartz; +using Quartz.Impl.Matchers; + +namespace FictionArchive.Service.SchedulerService.GraphQL; + +public class Query +{ + public async Task> GetJobs(JobManagerService jobManager) + { + return await jobManager.GetScheduledJobs(); + } +} \ No newline at end of file diff --git a/FictionArchive.Service.SchedulerService/Models/JobTemplates/EventJobTemplate.cs b/FictionArchive.Service.SchedulerService/Models/JobTemplates/EventJobTemplate.cs new file mode 100644 index 0000000..fa4e8fd --- /dev/null +++ b/FictionArchive.Service.SchedulerService/Models/JobTemplates/EventJobTemplate.cs @@ -0,0 +1,35 @@ +using FictionArchive.Service.Shared.Services.EventBus; +using Newtonsoft.Json; +using Quartz; + +namespace FictionArchive.Service.SchedulerService.Models.JobTemplates; + +public class EventJobTemplate : IJob +{ + private readonly IEventBus _eventBus; + private readonly ILogger _logger; + + public const string EventTypeParameter = "RoutingKey"; + public const string EventDataParameter = "MessageData"; + + public EventJobTemplate(IEventBus eventBus, ILogger logger) + { + _eventBus = eventBus; + _logger = logger; + } + + public async Task Execute(IJobExecutionContext context) + { + try + { + var eventData = context.MergedJobDataMap.GetString(EventDataParameter); + var eventType = context.MergedJobDataMap.GetString(EventTypeParameter); + var eventObject = JsonConvert.DeserializeObject(eventData); + await _eventBus.Publish(eventObject, eventType); + } + catch (Exception ex) + { + _logger.LogError(ex, "An error occurred while running an event job."); + } + } +} \ No newline at end of file diff --git a/FictionArchive.Service.SchedulerService/Models/SchedulerJob.cs b/FictionArchive.Service.SchedulerService/Models/SchedulerJob.cs new file mode 100644 index 0000000..2921ee4 --- /dev/null +++ b/FictionArchive.Service.SchedulerService/Models/SchedulerJob.cs @@ -0,0 +1,12 @@ +using Quartz; + +namespace FictionArchive.Service.SchedulerService.Models; + +public class SchedulerJob +{ + public JobKey JobKey { get; set; } + public string Description { get; set; } + public string JobTypeName { get; set; } + public List CronSchedule { get; set; } + public Dictionary JobData { get; set; } +} \ No newline at end of file diff --git a/FictionArchive.Service.SchedulerService/Program.cs b/FictionArchive.Service.SchedulerService/Program.cs new file mode 100644 index 0000000..69406f4 --- /dev/null +++ b/FictionArchive.Service.SchedulerService/Program.cs @@ -0,0 +1,52 @@ +using FictionArchive.Service.SchedulerService.GraphQL; +using FictionArchive.Service.SchedulerService.Services; +using FictionArchive.Service.Shared.Extensions; +using FictionArchive.Service.Shared.Services.EventBus.Implementations; +using Quartz; + +namespace FictionArchive.Service.SchedulerService; + +public class Program +{ + public static void Main(string[] args) + { + var builder = WebApplication.CreateBuilder(args); + + // Services + builder.Services.AddDefaultGraphQl(); + builder.Services.AddHealthChecks(); + builder.Services.AddTransient(); + + #region Event Bus + + builder.Services.AddRabbitMQ(opt => + { + builder.Configuration.GetSection("RabbitMQ").Bind(opt); + }); + + #endregion + + #region Quartz + + builder.Services.AddQuartz(opt => + { + opt.UseMicrosoftDependencyInjectionJobFactory(); + }); + builder.Services.AddQuartzHostedService(opt => + { + opt.WaitForJobsToComplete = true; + }); + + #endregion + + var app = builder.Build(); + + app.UseHttpsRedirection(); + + app.MapHealthChecks("/healthz"); + + app.MapGraphQL(); + + app.Run(); + } +} \ No newline at end of file diff --git a/FictionArchive.Service.SchedulerService/Properties/launchSettings.json b/FictionArchive.Service.SchedulerService/Properties/launchSettings.json new file mode 100644 index 0000000..b430b15 --- /dev/null +++ b/FictionArchive.Service.SchedulerService/Properties/launchSettings.json @@ -0,0 +1,39 @@ +{ + "$schema": "http://json.schemastore.org/launchsettings.json", + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "http://localhost:61312", + "sslPort": 44365 + } + }, + "profiles": { + "http": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "applicationUrl": "http://localhost:5213", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "https": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "launchUrl": "graphql", + "applicationUrl": "https://localhost:7145;http://localhost:5213", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "IIS Express": { + "commandName": "IISExpress", + "launchBrowser": true, + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} diff --git a/FictionArchive.Service.SchedulerService/Services/JobManagerService.cs b/FictionArchive.Service.SchedulerService/Services/JobManagerService.cs new file mode 100644 index 0000000..1e37f75 --- /dev/null +++ b/FictionArchive.Service.SchedulerService/Services/JobManagerService.cs @@ -0,0 +1,99 @@ +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; + +namespace FictionArchive.Service.SchedulerService.Services; + +public class JobManagerService +{ + private readonly ILogger _logger; + private readonly ISchedulerFactory _schedulerFactory; + + public JobManagerService(ILogger logger, ISchedulerFactory schedulerFactory) + { + _logger = logger; + _schedulerFactory = schedulerFactory; + } + + public async Task> GetScheduledJobs() + { + var scheduler = await _schedulerFactory.GetScheduler(); + var groups = await scheduler.GetJobGroupNames(); + var result = new List<(IJobDetail Job, IReadOnlyCollection Triggers)>(); + + foreach (var group in groups) + { + var jobKeys = await scheduler.GetJobKeys(GroupMatcher.GroupEquals(group)); + foreach (var jobKey in jobKeys) + { + var jobDetail = await scheduler.GetJobDetail(jobKey); + var triggers = await scheduler.GetTriggersOfJob(jobKey); + + result.Add((jobDetail, triggers)); + } + } + + return result.Select(tuple => new SchedulerJob() + { + JobKey = tuple.Job.Key, + Description = tuple.Job.Description, + JobTypeName = tuple.Job.JobType.FullName, + JobData = tuple.Job.JobDataMap.ToDictionary(kv => kv.Key, kv => kv.Value.ToString()), + CronSchedule = tuple.Triggers.Where(trigger => trigger is ICronTrigger).Select(trigger => (trigger as ICronTrigger).CronExpressionString).ToList() + }).ToList(); + } + + public async Task ScheduleEventJob(string? jobKey, string? description, string eventType, string eventData, string cronSchedule) + { + var scheduler = await _schedulerFactory.GetScheduler(); + + if (await scheduler.GetJobDetail(new JobKey(jobKey)) != null) + { + throw new DuplicateNameException("A job with the same key already exists."); + } + + jobKey ??= Guid.NewGuid().ToString(); + var jobData = new JobDataMap + { + { EventJobTemplate.EventTypeParameter, eventType }, + { EventJobTemplate.EventDataParameter, eventData } + }; + var job = JobBuilder.Create() + .WithIdentity(jobKey) + .WithDescription(description ?? $"Fires off an event on a set schedule") + .SetJobData(jobData) + .Build(); + var trigger = TriggerBuilder.Create() + .WithIdentity(jobKey) + .WithCronSchedule(cronSchedule) + .StartNow() + .Build(); + + await scheduler.ScheduleJob(job, trigger); + + return new SchedulerJob() + { + CronSchedule = new List { cronSchedule }, + Description = description, + JobKey = new JobKey(jobKey), + JobTypeName = typeof(EventJobTemplate).FullName, + JobData = jobData.ToDictionary(kv => kv.Key, kv => kv.Value.ToString()) + }; + } + + public async Task TriggerJob(string jobKey) + { + var scheduler = await _schedulerFactory.GetScheduler(); + await scheduler.TriggerJob(new JobKey(jobKey)); + return true; + } + + public async Task DeleteJob(string jobKey) + { + var scheduler = await _schedulerFactory.GetScheduler(); + return await scheduler.DeleteJob(new JobKey(jobKey)); + } +} \ No newline at end of file diff --git a/FictionArchive.Service.SchedulerService/appsettings.Development.json b/FictionArchive.Service.SchedulerService/appsettings.Development.json new file mode 100644 index 0000000..0c208ae --- /dev/null +++ b/FictionArchive.Service.SchedulerService/appsettings.Development.json @@ -0,0 +1,8 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + } +} diff --git a/FictionArchive.Service.SchedulerService/appsettings.json b/FictionArchive.Service.SchedulerService/appsettings.json new file mode 100644 index 0000000..250543c --- /dev/null +++ b/FictionArchive.Service.SchedulerService/appsettings.json @@ -0,0 +1,13 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "RabbitMQ": { + "ConnectionString": "amqp://localhost", + "ClientIdentifier": "SchedulerService" + }, + "AllowedHosts": "*" +} diff --git a/FictionArchive.Service.Shared/Extensions/GraphQLExtensions.cs b/FictionArchive.Service.Shared/Extensions/GraphQLExtensions.cs index 3ab030c..b51551a 100644 --- a/FictionArchive.Service.Shared/Extensions/GraphQLExtensions.cs +++ b/FictionArchive.Service.Shared/Extensions/GraphQLExtensions.cs @@ -13,6 +13,7 @@ public static class GraphQLExtensions .AddQueryType() .AddMutationType() .AddDiagnosticEventListener() + .AddErrorFilter() .AddType() .AddType() .AddMutationConventions(applyToAllMutations: true) diff --git a/FictionArchive.Service.Shared/Services/EventBus/IEventBus.cs b/FictionArchive.Service.Shared/Services/EventBus/IEventBus.cs index d3433c1..46c7e75 100644 --- a/FictionArchive.Service.Shared/Services/EventBus/IEventBus.cs +++ b/FictionArchive.Service.Shared/Services/EventBus/IEventBus.cs @@ -3,4 +3,5 @@ namespace FictionArchive.Service.Shared.Services.EventBus; public interface IEventBus { Task Publish(TEvent integrationEvent) where TEvent : IntegrationEvent; + Task Publish(object integrationEvent, string eventType); } \ 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 index cdf83d8..6650e18 100644 --- a/FictionArchive.Service.Shared/Services/EventBus/Implementations/RabbitMQEventBus.cs +++ b/FictionArchive.Service.Shared/Services/EventBus/Implementations/RabbitMQEventBus.cs @@ -36,15 +36,20 @@ public class RabbitMQEventBus : IEventBus, IHostedService public async Task Publish(TEvent integrationEvent) where TEvent : IntegrationEvent { var routingKey = typeof(TEvent).Name; - var channel = await _connectionProvider.GetDefaultChannelAsync(); // Set integration event values integrationEvent.CreatedAt = Instant.FromDateTimeUtc(DateTime.UtcNow); integrationEvent.EventId = Guid.NewGuid(); + 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)); - await channel.BasicPublishAsync(ExchangeName, routingKey, true, body); - _logger.LogInformation("Published event {EventName}", routingKey); + await channel.BasicPublishAsync(ExchangeName, eventType, true, body); + _logger.LogInformation("Published event {EventName}", eventType); } public async Task StartAsync(CancellationToken cancellationToken) diff --git a/FictionArchive.Service.Shared/Services/GraphQL/LoggingErrorFilter.cs b/FictionArchive.Service.Shared/Services/GraphQL/LoggingErrorFilter.cs new file mode 100644 index 0000000..aa03776 --- /dev/null +++ b/FictionArchive.Service.Shared/Services/GraphQL/LoggingErrorFilter.cs @@ -0,0 +1,23 @@ +using Microsoft.Extensions.Logging; + +namespace FictionArchive.Service.Shared.Services.GraphQL; + +public class LoggingErrorFilter : IErrorFilter +{ + private readonly ILogger _logger; + + public LoggingErrorFilter(ILogger logger) + { + _logger = logger; + } + + public IError OnError(IError error) + { + if (error.Exception != null) + { + _logger.LogError(error.Exception, "Unexpected GraphQL error occurred"); + } + + return error; + } +} \ No newline at end of file diff --git a/FictionArchive.sln b/FictionArchive.sln index a22fd12..aa199ef 100644 --- a/FictionArchive.sln +++ b/FictionArchive.sln @@ -10,6 +10,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FictionArchive.Service.Tran EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FictionArchive.Service.Shared", "FictionArchive.Service.Shared\FictionArchive.Service.Shared.csproj", "{82638874-304C-43E6-8EFA-8AD4C41C4435}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FictionArchive.Service.SchedulerService", "FictionArchive.Service.SchedulerService\FictionArchive.Service.SchedulerService.csproj", "{6813A8AD-A071-4F86-B227-BC4A5BCD7F3C}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -36,5 +38,9 @@ Global {82638874-304C-43E6-8EFA-8AD4C41C4435}.Debug|Any CPU.Build.0 = Debug|Any CPU {82638874-304C-43E6-8EFA-8AD4C41C4435}.Release|Any CPU.ActiveCfg = Release|Any CPU {82638874-304C-43E6-8EFA-8AD4C41C4435}.Release|Any CPU.Build.0 = Release|Any CPU + {6813A8AD-A071-4F86-B227-BC4A5BCD7F3C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {6813A8AD-A071-4F86-B227-BC4A5BCD7F3C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {6813A8AD-A071-4F86-B227-BC4A5BCD7F3C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {6813A8AD-A071-4F86-B227-BC4A5BCD7F3C}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection EndGlobal -- 2.49.1 From 3250cf14674f0b44c0a5b744cef5b201078defc6 Mon Sep 17 00:00:00 2001 From: gamer147 Date: Thu, 20 Nov 2025 13:53:58 -0500 Subject: [PATCH 2/2] [FA-9] Postgres backing works --- ...ionArchive.Service.SchedulerService.csproj | 6 + .../GraphQL/Mutation.cs | 2 +- .../20251120151130_Initial.Designer.cs | 543 ++++++++++++++++++ .../Migrations/20251120151130_Initial.cs | 373 ++++++++++++ .../SchedulerServiceDbContextModelSnapshot.cs | 540 +++++++++++++++++ .../Program.cs | 24 +- .../Services/SchedulerServiceDbContext.cs | 19 + .../appsettings.json | 3 + 8 files changed, 1508 insertions(+), 2 deletions(-) create mode 100644 FictionArchive.Service.SchedulerService/Migrations/20251120151130_Initial.Designer.cs create mode 100644 FictionArchive.Service.SchedulerService/Migrations/20251120151130_Initial.cs create mode 100644 FictionArchive.Service.SchedulerService/Migrations/SchedulerServiceDbContextModelSnapshot.cs create mode 100644 FictionArchive.Service.SchedulerService/Services/SchedulerServiceDbContext.cs diff --git a/FictionArchive.Service.SchedulerService/FictionArchive.Service.SchedulerService.csproj b/FictionArchive.Service.SchedulerService/FictionArchive.Service.SchedulerService.csproj index ccdea73..055a4f1 100644 --- a/FictionArchive.Service.SchedulerService/FictionArchive.Service.SchedulerService.csproj +++ b/FictionArchive.Service.SchedulerService/FictionArchive.Service.SchedulerService.csproj @@ -18,9 +18,15 @@ + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + diff --git a/FictionArchive.Service.SchedulerService/GraphQL/Mutation.cs b/FictionArchive.Service.SchedulerService/GraphQL/Mutation.cs index e5118b5..f1a6b65 100644 --- a/FictionArchive.Service.SchedulerService/GraphQL/Mutation.cs +++ b/FictionArchive.Service.SchedulerService/GraphQL/Mutation.cs @@ -27,7 +27,7 @@ public class Mutation bool deleted = await jobManager.DeleteJob(jobKey); if (!deleted) { - throw new KeyNotFoundException($"Job with key {jobKey} was not found"); + throw new KeyNotFoundException($"Job with key '{jobKey}' was not found"); } return true; diff --git a/FictionArchive.Service.SchedulerService/Migrations/20251120151130_Initial.Designer.cs b/FictionArchive.Service.SchedulerService/Migrations/20251120151130_Initial.Designer.cs new file mode 100644 index 0000000..1026246 --- /dev/null +++ b/FictionArchive.Service.SchedulerService/Migrations/20251120151130_Initial.Designer.cs @@ -0,0 +1,543 @@ +// +using FictionArchive.Service.SchedulerService.Services; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace FictionArchive.Service.SchedulerService.Migrations +{ + [DbContext(typeof(SchedulerServiceDbContext))] + [Migration("20251120151130_Initial")] + partial class Initial + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "9.0.11") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("AppAny.Quartz.EntityFrameworkCore.Migrations.QuartzBlobTrigger", b => + { + b.Property("SchedulerName") + .HasColumnType("text") + .HasColumnName("sched_name"); + + b.Property("TriggerName") + .HasColumnType("text") + .HasColumnName("trigger_name"); + + b.Property("TriggerGroup") + .HasColumnType("text") + .HasColumnName("trigger_group"); + + b.Property("BlobData") + .HasColumnType("bytea") + .HasColumnName("blob_data"); + + b.HasKey("SchedulerName", "TriggerName", "TriggerGroup"); + + b.ToTable("qrtz_blob_triggers", "quartz"); + }); + + modelBuilder.Entity("AppAny.Quartz.EntityFrameworkCore.Migrations.QuartzCalendar", b => + { + b.Property("SchedulerName") + .HasColumnType("text") + .HasColumnName("sched_name"); + + b.Property("CalendarName") + .HasColumnType("text") + .HasColumnName("calendar_name"); + + b.Property("Calendar") + .IsRequired() + .HasColumnType("bytea") + .HasColumnName("calendar"); + + b.HasKey("SchedulerName", "CalendarName"); + + b.ToTable("qrtz_calendars", "quartz"); + }); + + modelBuilder.Entity("AppAny.Quartz.EntityFrameworkCore.Migrations.QuartzCronTrigger", b => + { + b.Property("SchedulerName") + .HasColumnType("text") + .HasColumnName("sched_name"); + + b.Property("TriggerName") + .HasColumnType("text") + .HasColumnName("trigger_name"); + + b.Property("TriggerGroup") + .HasColumnType("text") + .HasColumnName("trigger_group"); + + b.Property("CronExpression") + .IsRequired() + .HasColumnType("text") + .HasColumnName("cron_expression"); + + b.Property("TimeZoneId") + .HasColumnType("text") + .HasColumnName("time_zone_id"); + + b.HasKey("SchedulerName", "TriggerName", "TriggerGroup"); + + b.ToTable("qrtz_cron_triggers", "quartz"); + }); + + modelBuilder.Entity("AppAny.Quartz.EntityFrameworkCore.Migrations.QuartzFiredTrigger", b => + { + b.Property("SchedulerName") + .HasColumnType("text") + .HasColumnName("sched_name"); + + b.Property("EntryId") + .HasColumnType("text") + .HasColumnName("entry_id"); + + b.Property("FiredTime") + .HasColumnType("bigint") + .HasColumnName("fired_time"); + + b.Property("InstanceName") + .IsRequired() + .HasColumnType("text") + .HasColumnName("instance_name"); + + b.Property("IsNonConcurrent") + .HasColumnType("bool") + .HasColumnName("is_nonconcurrent"); + + b.Property("JobGroup") + .HasColumnType("text") + .HasColumnName("job_group"); + + b.Property("JobName") + .HasColumnType("text") + .HasColumnName("job_name"); + + b.Property("Priority") + .HasColumnType("integer") + .HasColumnName("priority"); + + b.Property("RequestsRecovery") + .HasColumnType("bool") + .HasColumnName("requests_recovery"); + + b.Property("ScheduledTime") + .HasColumnType("bigint") + .HasColumnName("sched_time"); + + b.Property("State") + .IsRequired() + .HasColumnType("text") + .HasColumnName("state"); + + b.Property("TriggerGroup") + .IsRequired() + .HasColumnType("text") + .HasColumnName("trigger_group"); + + b.Property("TriggerName") + .IsRequired() + .HasColumnType("text") + .HasColumnName("trigger_name"); + + b.HasKey("SchedulerName", "EntryId"); + + b.HasIndex("InstanceName") + .HasDatabaseName("idx_qrtz_ft_trig_inst_name"); + + b.HasIndex("JobGroup") + .HasDatabaseName("idx_qrtz_ft_job_group"); + + b.HasIndex("JobName") + .HasDatabaseName("idx_qrtz_ft_job_name"); + + b.HasIndex("RequestsRecovery") + .HasDatabaseName("idx_qrtz_ft_job_req_recovery"); + + b.HasIndex("TriggerGroup") + .HasDatabaseName("idx_qrtz_ft_trig_group"); + + b.HasIndex("TriggerName") + .HasDatabaseName("idx_qrtz_ft_trig_name"); + + b.HasIndex("SchedulerName", "TriggerName", "TriggerGroup") + .HasDatabaseName("idx_qrtz_ft_trig_nm_gp"); + + b.ToTable("qrtz_fired_triggers", "quartz"); + }); + + modelBuilder.Entity("AppAny.Quartz.EntityFrameworkCore.Migrations.QuartzJobDetail", b => + { + b.Property("SchedulerName") + .HasColumnType("text") + .HasColumnName("sched_name"); + + b.Property("JobName") + .HasColumnType("text") + .HasColumnName("job_name"); + + b.Property("JobGroup") + .HasColumnType("text") + .HasColumnName("job_group"); + + b.Property("Description") + .HasColumnType("text") + .HasColumnName("description"); + + b.Property("IsDurable") + .HasColumnType("bool") + .HasColumnName("is_durable"); + + b.Property("IsNonConcurrent") + .HasColumnType("bool") + .HasColumnName("is_nonconcurrent"); + + b.Property("IsUpdateData") + .HasColumnType("bool") + .HasColumnName("is_update_data"); + + b.Property("JobClassName") + .IsRequired() + .HasColumnType("text") + .HasColumnName("job_class_name"); + + b.Property("JobData") + .HasColumnType("bytea") + .HasColumnName("job_data"); + + b.Property("RequestsRecovery") + .HasColumnType("bool") + .HasColumnName("requests_recovery"); + + b.HasKey("SchedulerName", "JobName", "JobGroup"); + + b.HasIndex("RequestsRecovery") + .HasDatabaseName("idx_qrtz_j_req_recovery"); + + b.ToTable("qrtz_job_details", "quartz"); + }); + + modelBuilder.Entity("AppAny.Quartz.EntityFrameworkCore.Migrations.QuartzLock", b => + { + b.Property("SchedulerName") + .HasColumnType("text") + .HasColumnName("sched_name"); + + b.Property("LockName") + .HasColumnType("text") + .HasColumnName("lock_name"); + + b.HasKey("SchedulerName", "LockName"); + + b.ToTable("qrtz_locks", "quartz"); + }); + + modelBuilder.Entity("AppAny.Quartz.EntityFrameworkCore.Migrations.QuartzPausedTriggerGroup", b => + { + b.Property("SchedulerName") + .HasColumnType("text") + .HasColumnName("sched_name"); + + b.Property("TriggerGroup") + .HasColumnType("text") + .HasColumnName("trigger_group"); + + b.HasKey("SchedulerName", "TriggerGroup"); + + b.ToTable("qrtz_paused_trigger_grps", "quartz"); + }); + + modelBuilder.Entity("AppAny.Quartz.EntityFrameworkCore.Migrations.QuartzSchedulerState", b => + { + b.Property("SchedulerName") + .HasColumnType("text") + .HasColumnName("sched_name"); + + b.Property("InstanceName") + .HasColumnType("text") + .HasColumnName("instance_name"); + + b.Property("CheckInInterval") + .HasColumnType("bigint") + .HasColumnName("checkin_interval"); + + b.Property("LastCheckInTime") + .HasColumnType("bigint") + .HasColumnName("last_checkin_time"); + + b.HasKey("SchedulerName", "InstanceName"); + + b.ToTable("qrtz_scheduler_state", "quartz"); + }); + + modelBuilder.Entity("AppAny.Quartz.EntityFrameworkCore.Migrations.QuartzSimplePropertyTrigger", b => + { + b.Property("SchedulerName") + .HasColumnType("text") + .HasColumnName("sched_name"); + + b.Property("TriggerName") + .HasColumnType("text") + .HasColumnName("trigger_name"); + + b.Property("TriggerGroup") + .HasColumnType("text") + .HasColumnName("trigger_group"); + + b.Property("BooleanProperty1") + .HasColumnType("bool") + .HasColumnName("bool_prop_1"); + + b.Property("BooleanProperty2") + .HasColumnType("bool") + .HasColumnName("bool_prop_2"); + + b.Property("DecimalProperty1") + .HasColumnType("numeric") + .HasColumnName("dec_prop_1"); + + b.Property("DecimalProperty2") + .HasColumnType("numeric") + .HasColumnName("dec_prop_2"); + + b.Property("IntegerProperty1") + .HasColumnType("integer") + .HasColumnName("int_prop_1"); + + b.Property("IntegerProperty2") + .HasColumnType("integer") + .HasColumnName("int_prop_2"); + + b.Property("LongProperty1") + .HasColumnType("bigint") + .HasColumnName("long_prop_1"); + + b.Property("LongProperty2") + .HasColumnType("bigint") + .HasColumnName("long_prop_2"); + + b.Property("StringProperty1") + .HasColumnType("text") + .HasColumnName("str_prop_1"); + + b.Property("StringProperty2") + .HasColumnType("text") + .HasColumnName("str_prop_2"); + + b.Property("StringProperty3") + .HasColumnType("text") + .HasColumnName("str_prop_3"); + + b.Property("TimeZoneId") + .HasColumnType("text") + .HasColumnName("time_zone_id"); + + b.HasKey("SchedulerName", "TriggerName", "TriggerGroup"); + + b.ToTable("qrtz_simprop_triggers", "quartz"); + }); + + modelBuilder.Entity("AppAny.Quartz.EntityFrameworkCore.Migrations.QuartzSimpleTrigger", b => + { + b.Property("SchedulerName") + .HasColumnType("text") + .HasColumnName("sched_name"); + + b.Property("TriggerName") + .HasColumnType("text") + .HasColumnName("trigger_name"); + + b.Property("TriggerGroup") + .HasColumnType("text") + .HasColumnName("trigger_group"); + + b.Property("RepeatCount") + .HasColumnType("bigint") + .HasColumnName("repeat_count"); + + b.Property("RepeatInterval") + .HasColumnType("bigint") + .HasColumnName("repeat_interval"); + + b.Property("TimesTriggered") + .HasColumnType("bigint") + .HasColumnName("times_triggered"); + + b.HasKey("SchedulerName", "TriggerName", "TriggerGroup"); + + b.ToTable("qrtz_simple_triggers", "quartz"); + }); + + modelBuilder.Entity("AppAny.Quartz.EntityFrameworkCore.Migrations.QuartzTrigger", b => + { + b.Property("SchedulerName") + .HasColumnType("text") + .HasColumnName("sched_name"); + + b.Property("TriggerName") + .HasColumnType("text") + .HasColumnName("trigger_name"); + + b.Property("TriggerGroup") + .HasColumnType("text") + .HasColumnName("trigger_group"); + + b.Property("CalendarName") + .HasColumnType("text") + .HasColumnName("calendar_name"); + + b.Property("Description") + .HasColumnType("text") + .HasColumnName("description"); + + b.Property("EndTime") + .HasColumnType("bigint") + .HasColumnName("end_time"); + + b.Property("JobData") + .HasColumnType("bytea") + .HasColumnName("job_data"); + + b.Property("JobGroup") + .IsRequired() + .HasColumnType("text") + .HasColumnName("job_group"); + + b.Property("JobName") + .IsRequired() + .HasColumnType("text") + .HasColumnName("job_name"); + + b.Property("MisfireInstruction") + .HasColumnType("smallint") + .HasColumnName("misfire_instr"); + + b.Property("NextFireTime") + .HasColumnType("bigint") + .HasColumnName("next_fire_time"); + + b.Property("PreviousFireTime") + .HasColumnType("bigint") + .HasColumnName("prev_fire_time"); + + b.Property("Priority") + .HasColumnType("integer") + .HasColumnName("priority"); + + b.Property("StartTime") + .HasColumnType("bigint") + .HasColumnName("start_time"); + + b.Property("TriggerState") + .IsRequired() + .HasColumnType("text") + .HasColumnName("trigger_state"); + + b.Property("TriggerType") + .IsRequired() + .HasColumnType("text") + .HasColumnName("trigger_type"); + + b.HasKey("SchedulerName", "TriggerName", "TriggerGroup"); + + b.HasIndex("NextFireTime") + .HasDatabaseName("idx_qrtz_t_next_fire_time"); + + b.HasIndex("TriggerState") + .HasDatabaseName("idx_qrtz_t_state"); + + b.HasIndex("NextFireTime", "TriggerState") + .HasDatabaseName("idx_qrtz_t_nft_st"); + + b.HasIndex("SchedulerName", "JobName", "JobGroup"); + + b.ToTable("qrtz_triggers", "quartz"); + }); + + modelBuilder.Entity("AppAny.Quartz.EntityFrameworkCore.Migrations.QuartzBlobTrigger", b => + { + b.HasOne("AppAny.Quartz.EntityFrameworkCore.Migrations.QuartzTrigger", "Trigger") + .WithMany("BlobTriggers") + .HasForeignKey("SchedulerName", "TriggerName", "TriggerGroup") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Trigger"); + }); + + modelBuilder.Entity("AppAny.Quartz.EntityFrameworkCore.Migrations.QuartzCronTrigger", b => + { + b.HasOne("AppAny.Quartz.EntityFrameworkCore.Migrations.QuartzTrigger", "Trigger") + .WithMany("CronTriggers") + .HasForeignKey("SchedulerName", "TriggerName", "TriggerGroup") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Trigger"); + }); + + modelBuilder.Entity("AppAny.Quartz.EntityFrameworkCore.Migrations.QuartzSimplePropertyTrigger", b => + { + b.HasOne("AppAny.Quartz.EntityFrameworkCore.Migrations.QuartzTrigger", "Trigger") + .WithMany("SimplePropertyTriggers") + .HasForeignKey("SchedulerName", "TriggerName", "TriggerGroup") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Trigger"); + }); + + modelBuilder.Entity("AppAny.Quartz.EntityFrameworkCore.Migrations.QuartzSimpleTrigger", b => + { + b.HasOne("AppAny.Quartz.EntityFrameworkCore.Migrations.QuartzTrigger", "Trigger") + .WithMany("SimpleTriggers") + .HasForeignKey("SchedulerName", "TriggerName", "TriggerGroup") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Trigger"); + }); + + modelBuilder.Entity("AppAny.Quartz.EntityFrameworkCore.Migrations.QuartzTrigger", b => + { + b.HasOne("AppAny.Quartz.EntityFrameworkCore.Migrations.QuartzJobDetail", "JobDetail") + .WithMany("Triggers") + .HasForeignKey("SchedulerName", "JobName", "JobGroup") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("JobDetail"); + }); + + modelBuilder.Entity("AppAny.Quartz.EntityFrameworkCore.Migrations.QuartzJobDetail", b => + { + b.Navigation("Triggers"); + }); + + modelBuilder.Entity("AppAny.Quartz.EntityFrameworkCore.Migrations.QuartzTrigger", b => + { + b.Navigation("BlobTriggers"); + + b.Navigation("CronTriggers"); + + b.Navigation("SimplePropertyTriggers"); + + b.Navigation("SimpleTriggers"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/FictionArchive.Service.SchedulerService/Migrations/20251120151130_Initial.cs b/FictionArchive.Service.SchedulerService/Migrations/20251120151130_Initial.cs new file mode 100644 index 0000000..6210aa1 --- /dev/null +++ b/FictionArchive.Service.SchedulerService/Migrations/20251120151130_Initial.cs @@ -0,0 +1,373 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace FictionArchive.Service.SchedulerService.Migrations +{ + /// + public partial class Initial : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.EnsureSchema( + name: "quartz"); + + migrationBuilder.CreateTable( + name: "qrtz_calendars", + schema: "quartz", + columns: table => new + { + sched_name = table.Column(type: "text", nullable: false), + calendar_name = table.Column(type: "text", nullable: false), + calendar = table.Column(type: "bytea", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_qrtz_calendars", x => new { x.sched_name, x.calendar_name }); + }); + + migrationBuilder.CreateTable( + name: "qrtz_fired_triggers", + schema: "quartz", + columns: table => new + { + sched_name = table.Column(type: "text", nullable: false), + entry_id = table.Column(type: "text", nullable: false), + trigger_name = table.Column(type: "text", nullable: false), + trigger_group = table.Column(type: "text", nullable: false), + instance_name = table.Column(type: "text", nullable: false), + fired_time = table.Column(type: "bigint", nullable: false), + sched_time = table.Column(type: "bigint", nullable: false), + priority = table.Column(type: "integer", nullable: false), + state = table.Column(type: "text", nullable: false), + job_name = table.Column(type: "text", nullable: true), + job_group = table.Column(type: "text", nullable: true), + is_nonconcurrent = table.Column(type: "bool", nullable: false), + requests_recovery = table.Column(type: "bool", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_qrtz_fired_triggers", x => new { x.sched_name, x.entry_id }); + }); + + migrationBuilder.CreateTable( + name: "qrtz_job_details", + schema: "quartz", + columns: table => new + { + sched_name = table.Column(type: "text", nullable: false), + job_name = table.Column(type: "text", nullable: false), + job_group = table.Column(type: "text", nullable: false), + description = table.Column(type: "text", nullable: true), + job_class_name = table.Column(type: "text", nullable: false), + is_durable = table.Column(type: "bool", nullable: false), + is_nonconcurrent = table.Column(type: "bool", nullable: false), + is_update_data = table.Column(type: "bool", nullable: false), + requests_recovery = table.Column(type: "bool", nullable: false), + job_data = table.Column(type: "bytea", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_qrtz_job_details", x => new { x.sched_name, x.job_name, x.job_group }); + }); + + migrationBuilder.CreateTable( + name: "qrtz_locks", + schema: "quartz", + columns: table => new + { + sched_name = table.Column(type: "text", nullable: false), + lock_name = table.Column(type: "text", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_qrtz_locks", x => new { x.sched_name, x.lock_name }); + }); + + migrationBuilder.CreateTable( + name: "qrtz_paused_trigger_grps", + schema: "quartz", + columns: table => new + { + sched_name = table.Column(type: "text", nullable: false), + trigger_group = table.Column(type: "text", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_qrtz_paused_trigger_grps", x => new { x.sched_name, x.trigger_group }); + }); + + migrationBuilder.CreateTable( + name: "qrtz_scheduler_state", + schema: "quartz", + columns: table => new + { + sched_name = table.Column(type: "text", nullable: false), + instance_name = table.Column(type: "text", nullable: false), + last_checkin_time = table.Column(type: "bigint", nullable: false), + checkin_interval = table.Column(type: "bigint", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_qrtz_scheduler_state", x => new { x.sched_name, x.instance_name }); + }); + + migrationBuilder.CreateTable( + name: "qrtz_triggers", + schema: "quartz", + columns: table => new + { + sched_name = table.Column(type: "text", nullable: false), + trigger_name = table.Column(type: "text", nullable: false), + trigger_group = table.Column(type: "text", nullable: false), + job_name = table.Column(type: "text", nullable: false), + job_group = table.Column(type: "text", nullable: false), + description = table.Column(type: "text", nullable: true), + next_fire_time = table.Column(type: "bigint", nullable: true), + prev_fire_time = table.Column(type: "bigint", nullable: true), + priority = table.Column(type: "integer", nullable: true), + trigger_state = table.Column(type: "text", nullable: false), + trigger_type = table.Column(type: "text", nullable: false), + start_time = table.Column(type: "bigint", nullable: false), + end_time = table.Column(type: "bigint", nullable: true), + calendar_name = table.Column(type: "text", nullable: true), + misfire_instr = table.Column(type: "smallint", nullable: true), + job_data = table.Column(type: "bytea", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_qrtz_triggers", x => new { x.sched_name, x.trigger_name, x.trigger_group }); + table.ForeignKey( + name: "FK_qrtz_triggers_qrtz_job_details_sched_name_job_name_job_group", + columns: x => new { x.sched_name, x.job_name, x.job_group }, + principalSchema: "quartz", + principalTable: "qrtz_job_details", + principalColumns: new[] { "sched_name", "job_name", "job_group" }, + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "qrtz_blob_triggers", + schema: "quartz", + columns: table => new + { + sched_name = table.Column(type: "text", nullable: false), + trigger_name = table.Column(type: "text", nullable: false), + trigger_group = table.Column(type: "text", nullable: false), + blob_data = table.Column(type: "bytea", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_qrtz_blob_triggers", x => new { x.sched_name, x.trigger_name, x.trigger_group }); + table.ForeignKey( + name: "FK_qrtz_blob_triggers_qrtz_triggers_sched_name_trigger_name_tr~", + columns: x => new { x.sched_name, x.trigger_name, x.trigger_group }, + principalSchema: "quartz", + principalTable: "qrtz_triggers", + principalColumns: new[] { "sched_name", "trigger_name", "trigger_group" }, + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "qrtz_cron_triggers", + schema: "quartz", + columns: table => new + { + sched_name = table.Column(type: "text", nullable: false), + trigger_name = table.Column(type: "text", nullable: false), + trigger_group = table.Column(type: "text", nullable: false), + cron_expression = table.Column(type: "text", nullable: false), + time_zone_id = table.Column(type: "text", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_qrtz_cron_triggers", x => new { x.sched_name, x.trigger_name, x.trigger_group }); + table.ForeignKey( + name: "FK_qrtz_cron_triggers_qrtz_triggers_sched_name_trigger_name_tr~", + columns: x => new { x.sched_name, x.trigger_name, x.trigger_group }, + principalSchema: "quartz", + principalTable: "qrtz_triggers", + principalColumns: new[] { "sched_name", "trigger_name", "trigger_group" }, + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "qrtz_simple_triggers", + schema: "quartz", + columns: table => new + { + sched_name = table.Column(type: "text", nullable: false), + trigger_name = table.Column(type: "text", nullable: false), + trigger_group = table.Column(type: "text", nullable: false), + repeat_count = table.Column(type: "bigint", nullable: false), + repeat_interval = table.Column(type: "bigint", nullable: false), + times_triggered = table.Column(type: "bigint", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_qrtz_simple_triggers", x => new { x.sched_name, x.trigger_name, x.trigger_group }); + table.ForeignKey( + name: "FK_qrtz_simple_triggers_qrtz_triggers_sched_name_trigger_name_~", + columns: x => new { x.sched_name, x.trigger_name, x.trigger_group }, + principalSchema: "quartz", + principalTable: "qrtz_triggers", + principalColumns: new[] { "sched_name", "trigger_name", "trigger_group" }, + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "qrtz_simprop_triggers", + schema: "quartz", + columns: table => new + { + sched_name = table.Column(type: "text", nullable: false), + trigger_name = table.Column(type: "text", nullable: false), + trigger_group = table.Column(type: "text", nullable: false), + str_prop_1 = table.Column(type: "text", nullable: true), + str_prop_2 = table.Column(type: "text", nullable: true), + str_prop_3 = table.Column(type: "text", nullable: true), + int_prop_1 = table.Column(type: "integer", nullable: true), + int_prop_2 = table.Column(type: "integer", nullable: true), + long_prop_1 = table.Column(type: "bigint", nullable: true), + long_prop_2 = table.Column(type: "bigint", nullable: true), + dec_prop_1 = table.Column(type: "numeric", nullable: true), + dec_prop_2 = table.Column(type: "numeric", nullable: true), + bool_prop_1 = table.Column(type: "bool", nullable: true), + bool_prop_2 = table.Column(type: "bool", nullable: true), + time_zone_id = table.Column(type: "text", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_qrtz_simprop_triggers", x => new { x.sched_name, x.trigger_name, x.trigger_group }); + table.ForeignKey( + name: "FK_qrtz_simprop_triggers_qrtz_triggers_sched_name_trigger_name~", + columns: x => new { x.sched_name, x.trigger_name, x.trigger_group }, + principalSchema: "quartz", + principalTable: "qrtz_triggers", + principalColumns: new[] { "sched_name", "trigger_name", "trigger_group" }, + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex( + name: "idx_qrtz_ft_job_group", + schema: "quartz", + table: "qrtz_fired_triggers", + column: "job_group"); + + migrationBuilder.CreateIndex( + name: "idx_qrtz_ft_job_name", + schema: "quartz", + table: "qrtz_fired_triggers", + column: "job_name"); + + migrationBuilder.CreateIndex( + name: "idx_qrtz_ft_job_req_recovery", + schema: "quartz", + table: "qrtz_fired_triggers", + column: "requests_recovery"); + + migrationBuilder.CreateIndex( + name: "idx_qrtz_ft_trig_group", + schema: "quartz", + table: "qrtz_fired_triggers", + column: "trigger_group"); + + migrationBuilder.CreateIndex( + name: "idx_qrtz_ft_trig_inst_name", + schema: "quartz", + table: "qrtz_fired_triggers", + column: "instance_name"); + + migrationBuilder.CreateIndex( + name: "idx_qrtz_ft_trig_name", + schema: "quartz", + table: "qrtz_fired_triggers", + column: "trigger_name"); + + migrationBuilder.CreateIndex( + name: "idx_qrtz_ft_trig_nm_gp", + schema: "quartz", + table: "qrtz_fired_triggers", + columns: new[] { "sched_name", "trigger_name", "trigger_group" }); + + migrationBuilder.CreateIndex( + name: "idx_qrtz_j_req_recovery", + schema: "quartz", + table: "qrtz_job_details", + column: "requests_recovery"); + + migrationBuilder.CreateIndex( + name: "idx_qrtz_t_next_fire_time", + schema: "quartz", + table: "qrtz_triggers", + column: "next_fire_time"); + + migrationBuilder.CreateIndex( + name: "idx_qrtz_t_nft_st", + schema: "quartz", + table: "qrtz_triggers", + columns: new[] { "next_fire_time", "trigger_state" }); + + migrationBuilder.CreateIndex( + name: "idx_qrtz_t_state", + schema: "quartz", + table: "qrtz_triggers", + column: "trigger_state"); + + migrationBuilder.CreateIndex( + name: "IX_qrtz_triggers_sched_name_job_name_job_group", + schema: "quartz", + table: "qrtz_triggers", + columns: new[] { "sched_name", "job_name", "job_group" }); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "qrtz_blob_triggers", + schema: "quartz"); + + migrationBuilder.DropTable( + name: "qrtz_calendars", + schema: "quartz"); + + migrationBuilder.DropTable( + name: "qrtz_cron_triggers", + schema: "quartz"); + + migrationBuilder.DropTable( + name: "qrtz_fired_triggers", + schema: "quartz"); + + migrationBuilder.DropTable( + name: "qrtz_locks", + schema: "quartz"); + + migrationBuilder.DropTable( + name: "qrtz_paused_trigger_grps", + schema: "quartz"); + + migrationBuilder.DropTable( + name: "qrtz_scheduler_state", + schema: "quartz"); + + migrationBuilder.DropTable( + name: "qrtz_simple_triggers", + schema: "quartz"); + + migrationBuilder.DropTable( + name: "qrtz_simprop_triggers", + schema: "quartz"); + + migrationBuilder.DropTable( + name: "qrtz_triggers", + schema: "quartz"); + + migrationBuilder.DropTable( + name: "qrtz_job_details", + schema: "quartz"); + } + } +} diff --git a/FictionArchive.Service.SchedulerService/Migrations/SchedulerServiceDbContextModelSnapshot.cs b/FictionArchive.Service.SchedulerService/Migrations/SchedulerServiceDbContextModelSnapshot.cs new file mode 100644 index 0000000..1a4a2b3 --- /dev/null +++ b/FictionArchive.Service.SchedulerService/Migrations/SchedulerServiceDbContextModelSnapshot.cs @@ -0,0 +1,540 @@ +// +using FictionArchive.Service.SchedulerService.Services; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace FictionArchive.Service.SchedulerService.Migrations +{ + [DbContext(typeof(SchedulerServiceDbContext))] + partial class SchedulerServiceDbContextModelSnapshot : ModelSnapshot + { + protected override void BuildModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "9.0.11") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("AppAny.Quartz.EntityFrameworkCore.Migrations.QuartzBlobTrigger", b => + { + b.Property("SchedulerName") + .HasColumnType("text") + .HasColumnName("sched_name"); + + b.Property("TriggerName") + .HasColumnType("text") + .HasColumnName("trigger_name"); + + b.Property("TriggerGroup") + .HasColumnType("text") + .HasColumnName("trigger_group"); + + b.Property("BlobData") + .HasColumnType("bytea") + .HasColumnName("blob_data"); + + b.HasKey("SchedulerName", "TriggerName", "TriggerGroup"); + + b.ToTable("qrtz_blob_triggers", "quartz"); + }); + + modelBuilder.Entity("AppAny.Quartz.EntityFrameworkCore.Migrations.QuartzCalendar", b => + { + b.Property("SchedulerName") + .HasColumnType("text") + .HasColumnName("sched_name"); + + b.Property("CalendarName") + .HasColumnType("text") + .HasColumnName("calendar_name"); + + b.Property("Calendar") + .IsRequired() + .HasColumnType("bytea") + .HasColumnName("calendar"); + + b.HasKey("SchedulerName", "CalendarName"); + + b.ToTable("qrtz_calendars", "quartz"); + }); + + modelBuilder.Entity("AppAny.Quartz.EntityFrameworkCore.Migrations.QuartzCronTrigger", b => + { + b.Property("SchedulerName") + .HasColumnType("text") + .HasColumnName("sched_name"); + + b.Property("TriggerName") + .HasColumnType("text") + .HasColumnName("trigger_name"); + + b.Property("TriggerGroup") + .HasColumnType("text") + .HasColumnName("trigger_group"); + + b.Property("CronExpression") + .IsRequired() + .HasColumnType("text") + .HasColumnName("cron_expression"); + + b.Property("TimeZoneId") + .HasColumnType("text") + .HasColumnName("time_zone_id"); + + b.HasKey("SchedulerName", "TriggerName", "TriggerGroup"); + + b.ToTable("qrtz_cron_triggers", "quartz"); + }); + + modelBuilder.Entity("AppAny.Quartz.EntityFrameworkCore.Migrations.QuartzFiredTrigger", b => + { + b.Property("SchedulerName") + .HasColumnType("text") + .HasColumnName("sched_name"); + + b.Property("EntryId") + .HasColumnType("text") + .HasColumnName("entry_id"); + + b.Property("FiredTime") + .HasColumnType("bigint") + .HasColumnName("fired_time"); + + b.Property("InstanceName") + .IsRequired() + .HasColumnType("text") + .HasColumnName("instance_name"); + + b.Property("IsNonConcurrent") + .HasColumnType("bool") + .HasColumnName("is_nonconcurrent"); + + b.Property("JobGroup") + .HasColumnType("text") + .HasColumnName("job_group"); + + b.Property("JobName") + .HasColumnType("text") + .HasColumnName("job_name"); + + b.Property("Priority") + .HasColumnType("integer") + .HasColumnName("priority"); + + b.Property("RequestsRecovery") + .HasColumnType("bool") + .HasColumnName("requests_recovery"); + + b.Property("ScheduledTime") + .HasColumnType("bigint") + .HasColumnName("sched_time"); + + b.Property("State") + .IsRequired() + .HasColumnType("text") + .HasColumnName("state"); + + b.Property("TriggerGroup") + .IsRequired() + .HasColumnType("text") + .HasColumnName("trigger_group"); + + b.Property("TriggerName") + .IsRequired() + .HasColumnType("text") + .HasColumnName("trigger_name"); + + b.HasKey("SchedulerName", "EntryId"); + + b.HasIndex("InstanceName") + .HasDatabaseName("idx_qrtz_ft_trig_inst_name"); + + b.HasIndex("JobGroup") + .HasDatabaseName("idx_qrtz_ft_job_group"); + + b.HasIndex("JobName") + .HasDatabaseName("idx_qrtz_ft_job_name"); + + b.HasIndex("RequestsRecovery") + .HasDatabaseName("idx_qrtz_ft_job_req_recovery"); + + b.HasIndex("TriggerGroup") + .HasDatabaseName("idx_qrtz_ft_trig_group"); + + b.HasIndex("TriggerName") + .HasDatabaseName("idx_qrtz_ft_trig_name"); + + b.HasIndex("SchedulerName", "TriggerName", "TriggerGroup") + .HasDatabaseName("idx_qrtz_ft_trig_nm_gp"); + + b.ToTable("qrtz_fired_triggers", "quartz"); + }); + + modelBuilder.Entity("AppAny.Quartz.EntityFrameworkCore.Migrations.QuartzJobDetail", b => + { + b.Property("SchedulerName") + .HasColumnType("text") + .HasColumnName("sched_name"); + + b.Property("JobName") + .HasColumnType("text") + .HasColumnName("job_name"); + + b.Property("JobGroup") + .HasColumnType("text") + .HasColumnName("job_group"); + + b.Property("Description") + .HasColumnType("text") + .HasColumnName("description"); + + b.Property("IsDurable") + .HasColumnType("bool") + .HasColumnName("is_durable"); + + b.Property("IsNonConcurrent") + .HasColumnType("bool") + .HasColumnName("is_nonconcurrent"); + + b.Property("IsUpdateData") + .HasColumnType("bool") + .HasColumnName("is_update_data"); + + b.Property("JobClassName") + .IsRequired() + .HasColumnType("text") + .HasColumnName("job_class_name"); + + b.Property("JobData") + .HasColumnType("bytea") + .HasColumnName("job_data"); + + b.Property("RequestsRecovery") + .HasColumnType("bool") + .HasColumnName("requests_recovery"); + + b.HasKey("SchedulerName", "JobName", "JobGroup"); + + b.HasIndex("RequestsRecovery") + .HasDatabaseName("idx_qrtz_j_req_recovery"); + + b.ToTable("qrtz_job_details", "quartz"); + }); + + modelBuilder.Entity("AppAny.Quartz.EntityFrameworkCore.Migrations.QuartzLock", b => + { + b.Property("SchedulerName") + .HasColumnType("text") + .HasColumnName("sched_name"); + + b.Property("LockName") + .HasColumnType("text") + .HasColumnName("lock_name"); + + b.HasKey("SchedulerName", "LockName"); + + b.ToTable("qrtz_locks", "quartz"); + }); + + modelBuilder.Entity("AppAny.Quartz.EntityFrameworkCore.Migrations.QuartzPausedTriggerGroup", b => + { + b.Property("SchedulerName") + .HasColumnType("text") + .HasColumnName("sched_name"); + + b.Property("TriggerGroup") + .HasColumnType("text") + .HasColumnName("trigger_group"); + + b.HasKey("SchedulerName", "TriggerGroup"); + + b.ToTable("qrtz_paused_trigger_grps", "quartz"); + }); + + modelBuilder.Entity("AppAny.Quartz.EntityFrameworkCore.Migrations.QuartzSchedulerState", b => + { + b.Property("SchedulerName") + .HasColumnType("text") + .HasColumnName("sched_name"); + + b.Property("InstanceName") + .HasColumnType("text") + .HasColumnName("instance_name"); + + b.Property("CheckInInterval") + .HasColumnType("bigint") + .HasColumnName("checkin_interval"); + + b.Property("LastCheckInTime") + .HasColumnType("bigint") + .HasColumnName("last_checkin_time"); + + b.HasKey("SchedulerName", "InstanceName"); + + b.ToTable("qrtz_scheduler_state", "quartz"); + }); + + modelBuilder.Entity("AppAny.Quartz.EntityFrameworkCore.Migrations.QuartzSimplePropertyTrigger", b => + { + b.Property("SchedulerName") + .HasColumnType("text") + .HasColumnName("sched_name"); + + b.Property("TriggerName") + .HasColumnType("text") + .HasColumnName("trigger_name"); + + b.Property("TriggerGroup") + .HasColumnType("text") + .HasColumnName("trigger_group"); + + b.Property("BooleanProperty1") + .HasColumnType("bool") + .HasColumnName("bool_prop_1"); + + b.Property("BooleanProperty2") + .HasColumnType("bool") + .HasColumnName("bool_prop_2"); + + b.Property("DecimalProperty1") + .HasColumnType("numeric") + .HasColumnName("dec_prop_1"); + + b.Property("DecimalProperty2") + .HasColumnType("numeric") + .HasColumnName("dec_prop_2"); + + b.Property("IntegerProperty1") + .HasColumnType("integer") + .HasColumnName("int_prop_1"); + + b.Property("IntegerProperty2") + .HasColumnType("integer") + .HasColumnName("int_prop_2"); + + b.Property("LongProperty1") + .HasColumnType("bigint") + .HasColumnName("long_prop_1"); + + b.Property("LongProperty2") + .HasColumnType("bigint") + .HasColumnName("long_prop_2"); + + b.Property("StringProperty1") + .HasColumnType("text") + .HasColumnName("str_prop_1"); + + b.Property("StringProperty2") + .HasColumnType("text") + .HasColumnName("str_prop_2"); + + b.Property("StringProperty3") + .HasColumnType("text") + .HasColumnName("str_prop_3"); + + b.Property("TimeZoneId") + .HasColumnType("text") + .HasColumnName("time_zone_id"); + + b.HasKey("SchedulerName", "TriggerName", "TriggerGroup"); + + b.ToTable("qrtz_simprop_triggers", "quartz"); + }); + + modelBuilder.Entity("AppAny.Quartz.EntityFrameworkCore.Migrations.QuartzSimpleTrigger", b => + { + b.Property("SchedulerName") + .HasColumnType("text") + .HasColumnName("sched_name"); + + b.Property("TriggerName") + .HasColumnType("text") + .HasColumnName("trigger_name"); + + b.Property("TriggerGroup") + .HasColumnType("text") + .HasColumnName("trigger_group"); + + b.Property("RepeatCount") + .HasColumnType("bigint") + .HasColumnName("repeat_count"); + + b.Property("RepeatInterval") + .HasColumnType("bigint") + .HasColumnName("repeat_interval"); + + b.Property("TimesTriggered") + .HasColumnType("bigint") + .HasColumnName("times_triggered"); + + b.HasKey("SchedulerName", "TriggerName", "TriggerGroup"); + + b.ToTable("qrtz_simple_triggers", "quartz"); + }); + + modelBuilder.Entity("AppAny.Quartz.EntityFrameworkCore.Migrations.QuartzTrigger", b => + { + b.Property("SchedulerName") + .HasColumnType("text") + .HasColumnName("sched_name"); + + b.Property("TriggerName") + .HasColumnType("text") + .HasColumnName("trigger_name"); + + b.Property("TriggerGroup") + .HasColumnType("text") + .HasColumnName("trigger_group"); + + b.Property("CalendarName") + .HasColumnType("text") + .HasColumnName("calendar_name"); + + b.Property("Description") + .HasColumnType("text") + .HasColumnName("description"); + + b.Property("EndTime") + .HasColumnType("bigint") + .HasColumnName("end_time"); + + b.Property("JobData") + .HasColumnType("bytea") + .HasColumnName("job_data"); + + b.Property("JobGroup") + .IsRequired() + .HasColumnType("text") + .HasColumnName("job_group"); + + b.Property("JobName") + .IsRequired() + .HasColumnType("text") + .HasColumnName("job_name"); + + b.Property("MisfireInstruction") + .HasColumnType("smallint") + .HasColumnName("misfire_instr"); + + b.Property("NextFireTime") + .HasColumnType("bigint") + .HasColumnName("next_fire_time"); + + b.Property("PreviousFireTime") + .HasColumnType("bigint") + .HasColumnName("prev_fire_time"); + + b.Property("Priority") + .HasColumnType("integer") + .HasColumnName("priority"); + + b.Property("StartTime") + .HasColumnType("bigint") + .HasColumnName("start_time"); + + b.Property("TriggerState") + .IsRequired() + .HasColumnType("text") + .HasColumnName("trigger_state"); + + b.Property("TriggerType") + .IsRequired() + .HasColumnType("text") + .HasColumnName("trigger_type"); + + b.HasKey("SchedulerName", "TriggerName", "TriggerGroup"); + + b.HasIndex("NextFireTime") + .HasDatabaseName("idx_qrtz_t_next_fire_time"); + + b.HasIndex("TriggerState") + .HasDatabaseName("idx_qrtz_t_state"); + + b.HasIndex("NextFireTime", "TriggerState") + .HasDatabaseName("idx_qrtz_t_nft_st"); + + b.HasIndex("SchedulerName", "JobName", "JobGroup"); + + b.ToTable("qrtz_triggers", "quartz"); + }); + + modelBuilder.Entity("AppAny.Quartz.EntityFrameworkCore.Migrations.QuartzBlobTrigger", b => + { + b.HasOne("AppAny.Quartz.EntityFrameworkCore.Migrations.QuartzTrigger", "Trigger") + .WithMany("BlobTriggers") + .HasForeignKey("SchedulerName", "TriggerName", "TriggerGroup") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Trigger"); + }); + + modelBuilder.Entity("AppAny.Quartz.EntityFrameworkCore.Migrations.QuartzCronTrigger", b => + { + b.HasOne("AppAny.Quartz.EntityFrameworkCore.Migrations.QuartzTrigger", "Trigger") + .WithMany("CronTriggers") + .HasForeignKey("SchedulerName", "TriggerName", "TriggerGroup") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Trigger"); + }); + + modelBuilder.Entity("AppAny.Quartz.EntityFrameworkCore.Migrations.QuartzSimplePropertyTrigger", b => + { + b.HasOne("AppAny.Quartz.EntityFrameworkCore.Migrations.QuartzTrigger", "Trigger") + .WithMany("SimplePropertyTriggers") + .HasForeignKey("SchedulerName", "TriggerName", "TriggerGroup") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Trigger"); + }); + + modelBuilder.Entity("AppAny.Quartz.EntityFrameworkCore.Migrations.QuartzSimpleTrigger", b => + { + b.HasOne("AppAny.Quartz.EntityFrameworkCore.Migrations.QuartzTrigger", "Trigger") + .WithMany("SimpleTriggers") + .HasForeignKey("SchedulerName", "TriggerName", "TriggerGroup") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Trigger"); + }); + + modelBuilder.Entity("AppAny.Quartz.EntityFrameworkCore.Migrations.QuartzTrigger", b => + { + b.HasOne("AppAny.Quartz.EntityFrameworkCore.Migrations.QuartzJobDetail", "JobDetail") + .WithMany("Triggers") + .HasForeignKey("SchedulerName", "JobName", "JobGroup") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("JobDetail"); + }); + + modelBuilder.Entity("AppAny.Quartz.EntityFrameworkCore.Migrations.QuartzJobDetail", b => + { + b.Navigation("Triggers"); + }); + + modelBuilder.Entity("AppAny.Quartz.EntityFrameworkCore.Migrations.QuartzTrigger", b => + { + b.Navigation("BlobTriggers"); + + b.Navigation("CronTriggers"); + + b.Navigation("SimplePropertyTriggers"); + + b.Navigation("SimpleTriggers"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/FictionArchive.Service.SchedulerService/Program.cs b/FictionArchive.Service.SchedulerService/Program.cs index 69406f4..1710637 100644 --- a/FictionArchive.Service.SchedulerService/Program.cs +++ b/FictionArchive.Service.SchedulerService/Program.cs @@ -3,6 +3,7 @@ using FictionArchive.Service.SchedulerService.Services; using FictionArchive.Service.Shared.Extensions; using FictionArchive.Service.Shared.Services.EventBus.Implementations; using Quartz; +using Quartz.Impl.AdoJobStore; namespace FictionArchive.Service.SchedulerService; @@ -17,6 +18,12 @@ public class Program builder.Services.AddHealthChecks(); builder.Services.AddTransient(); + #region Database + + builder.Services.RegisterDbContext(builder.Configuration.GetConnectionString("DefaultConnection")); + + #endregion + #region Event Bus builder.Services.AddRabbitMQ(opt => @@ -30,7 +37,16 @@ public class Program builder.Services.AddQuartz(opt => { - opt.UseMicrosoftDependencyInjectionJobFactory(); + 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 => { @@ -40,6 +56,12 @@ public class Program #endregion var app = builder.Build(); + + using (var scope = app.Services.CreateScope()) + { + var dbContext = scope.ServiceProvider.GetRequiredService(); + dbContext.UpdateDatabase(); + } app.UseHttpsRedirection(); diff --git a/FictionArchive.Service.SchedulerService/Services/SchedulerServiceDbContext.cs b/FictionArchive.Service.SchedulerService/Services/SchedulerServiceDbContext.cs new file mode 100644 index 0000000..cd4844e --- /dev/null +++ b/FictionArchive.Service.SchedulerService/Services/SchedulerServiceDbContext.cs @@ -0,0 +1,19 @@ +using AppAny.Quartz.EntityFrameworkCore.Migrations; +using AppAny.Quartz.EntityFrameworkCore.Migrations.PostgreSQL; +using FictionArchive.Service.Shared.Services.Database; +using Microsoft.EntityFrameworkCore; + +namespace FictionArchive.Service.SchedulerService.Services; + +public class SchedulerServiceDbContext : FictionArchiveDbContext +{ + public SchedulerServiceDbContext(DbContextOptions options, ILogger logger) : base(options, logger) + { + } + + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + modelBuilder.AddQuartz(builder => builder.UsePostgreSql()); + base.OnModelCreating(modelBuilder); + } +} \ No newline at end of file diff --git a/FictionArchive.Service.SchedulerService/appsettings.json b/FictionArchive.Service.SchedulerService/appsettings.json index 250543c..6d027f1 100644 --- a/FictionArchive.Service.SchedulerService/appsettings.json +++ b/FictionArchive.Service.SchedulerService/appsettings.json @@ -9,5 +9,8 @@ "ConnectionString": "amqp://localhost", "ClientIdentifier": "SchedulerService" }, + "ConnectionStrings": { + "DefaultConnection": "Host=localhost;Database=FictionArchive_SchedulerService;Username=postgres;password=postgres" + }, "AllowedHosts": "*" } -- 2.49.1