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..055a4f1
--- /dev/null
+++ b/FictionArchive.Service.SchedulerService/FictionArchive.Service.SchedulerService.csproj
@@ -0,0 +1,32 @@
+
+
+
+ net8.0
+ enable
+ enable
+ Linux
+
+
+
+
+ .dockerignore
+
+
+
+
+
+
+
+
+
+
+ all
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+
+
+
+
+
+
+
+
diff --git a/FictionArchive.Service.SchedulerService/GraphQL/Mutation.cs b/FictionArchive.Service.SchedulerService/GraphQL/Mutation.cs
new file mode 100644
index 0000000..f1a6b65
--- /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/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/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..1710637
--- /dev/null
+++ b/FictionArchive.Service.SchedulerService/Program.cs
@@ -0,0 +1,74 @@
+using FictionArchive.Service.SchedulerService.GraphQL;
+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;
+
+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 Database
+
+ builder.Services.RegisterDbContext