From f77d688150671348cbfa3775afa0ee78dfacea56 Mon Sep 17 00:00:00 2001 From: Flaminel Date: Mon, 19 Oct 2026 14:12:31 +0300 Subject: [PATCH] added content kind to seeker units and events --- .../Entities/Arr/SearchableSeries.cs | 2 + .../Enums/SeekerContentKind.cs | 30 + ...019111201_AddSeekerContentKind.Designer.cs | 850 +++++++++++++++++ .../20261019111201_AddSeekerContentKind.cs | 48 + .../Events/EventsContextModelSnapshot.cs | 9 + ...019111156_AddSeekerContentKind.Designer.cs | 853 ++++++++++++++++++ .../20261019111156_AddSeekerContentKind.cs | 44 + .../Events/EventsContextModelSnapshot.cs | 9 + .../Models/Events/AppEvent.cs | 5 + .../Models/State/SeekerHistory.cs | 5 + 10 files changed, 1855 insertions(+) create mode 100644 code/backend/Cleanuparr.Domain/Enums/SeekerContentKind.cs create mode 100644 code/backend/Cleanuparr.Persistence.Postgres/Migrations/Events/20261019111201_AddSeekerContentKind.Designer.cs create mode 100644 code/backend/Cleanuparr.Persistence.Postgres/Migrations/Events/20261019111201_AddSeekerContentKind.cs create mode 100644 code/backend/Cleanuparr.Persistence.Sqlite/Migrations/Events/20261019111156_AddSeekerContentKind.Designer.cs create mode 100644 code/backend/Cleanuparr.Persistence.Sqlite/Migrations/Events/20261019111156_AddSeekerContentKind.cs diff --git a/code/backend/Cleanuparr.Domain/Entities/Arr/SearchableSeries.cs b/code/backend/Cleanuparr.Domain/Entities/Arr/SearchableSeries.cs index 4bc0b7fd..cc2c8532 100644 --- a/code/backend/Cleanuparr.Domain/Entities/Arr/SearchableSeries.cs +++ b/code/backend/Cleanuparr.Domain/Entities/Arr/SearchableSeries.cs @@ -16,5 +16,7 @@ public sealed record SearchableSeries public string Status { get; init; } = string.Empty; + public string SeriesType { get; init; } = string.Empty; + public SeriesStatistics? Statistics { get; init; } } diff --git a/code/backend/Cleanuparr.Domain/Enums/SeekerContentKind.cs b/code/backend/Cleanuparr.Domain/Enums/SeekerContentKind.cs new file mode 100644 index 00000000..65a5b15e --- /dev/null +++ b/code/backend/Cleanuparr.Domain/Enums/SeekerContentKind.cs @@ -0,0 +1,30 @@ +using System.Text.Json.Serialization; + +namespace Cleanuparr.Domain.Enums; + +/// +/// What a Seeker search unit holds, which decides how Sonarr searches it +/// +[JsonConverter(typeof(JsonStringEnumConverter))] +public enum SeekerContentKind +{ + /// + /// A movie or a regular series season + /// + Standard, + + /// + /// A season of a daily series + /// + Daily, + + /// + /// A season or episode of an anime series + /// + Anime, + + /// + /// An episode from season 0 + /// + Special, +} diff --git a/code/backend/Cleanuparr.Persistence.Postgres/Migrations/Events/20261019111201_AddSeekerContentKind.Designer.cs b/code/backend/Cleanuparr.Persistence.Postgres/Migrations/Events/20261019111201_AddSeekerContentKind.Designer.cs new file mode 100644 index 00000000..f2689a10 --- /dev/null +++ b/code/backend/Cleanuparr.Persistence.Postgres/Migrations/Events/20261019111201_AddSeekerContentKind.Designer.cs @@ -0,0 +1,850 @@ +// +using System; +using System.Collections.Generic; +using Cleanuparr.Persistence; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace Cleanuparr.Persistence.Postgres.Migrations.Events +{ + [DbContext(typeof(EventsContext))] + [Migration("20261019111201_AddSeekerContentKind")] + partial class AddSeekerContentKind + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasDefaultSchema("events") + .HasAnnotation("ProductVersion", "10.0.1") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("Cleanuparr.Persistence.Models.Events.AppEvent", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("ArrInstanceId") + .HasColumnType("uuid") + .HasColumnName("arr_instance_id"); + + b.Property("CleanReason") + .HasColumnType("text") + .HasColumnName("clean_reason"); + + b.Property("CleanedCategory") + .HasMaxLength(200) + .HasColumnType("character varying(200)") + .HasColumnName("cleaned_category"); + + b.Property("CompletedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("completed_at"); + + b.Property("ContentKind") + .HasColumnType("text") + .HasColumnName("content_kind"); + + b.Property("CycleId") + .HasColumnType("uuid") + .HasColumnName("cycle_id"); + + b.Property("DeleteReason") + .HasColumnType("text") + .HasColumnName("delete_reason"); + + b.Property("DownloadClientId") + .HasColumnType("uuid") + .HasColumnName("download_client_id"); + + b.Property("EventType") + .IsRequired() + .HasColumnType("text") + .HasColumnName("event_type"); + + b.PrimitiveCollection>("FailedImportReasons") + .IsRequired() + .HasColumnType("text[]") + .HasColumnName("failed_import_reasons"); + + b.PrimitiveCollection>("GrabbedItems") + .IsRequired() + .HasColumnType("text[]") + .HasColumnName("grabbed_items"); + + b.Property("IsCategoryTag") + .HasColumnType("boolean") + .HasColumnName("is_category_tag"); + + b.Property("IsDryRun") + .HasColumnType("boolean") + .HasColumnName("is_dry_run"); + + b.Property("ItemHash") + .HasMaxLength(100) + .HasColumnType("character varying(100)") + .HasColumnName("item_hash"); + + b.Property("ItemTitle") + .HasMaxLength(500) + .HasColumnType("character varying(500)") + .HasColumnName("item_title"); + + b.Property("JobRunId") + .HasColumnType("uuid") + .HasColumnName("job_run_id"); + + b.Property("Message") + .IsRequired() + .HasMaxLength(1000) + .HasColumnType("character varying(1000)") + .HasColumnName("message"); + + b.Property("NewCategory") + .HasMaxLength(200) + .HasColumnType("character varying(200)") + .HasColumnName("new_category"); + + b.Property("OldCategory") + .HasMaxLength(200) + .HasColumnType("character varying(200)") + .HasColumnName("old_category"); + + b.Property("RemoveFromClient") + .HasColumnType("boolean") + .HasColumnName("remove_from_client"); + + b.Property("SearchReason") + .HasColumnType("text") + .HasColumnName("search_reason"); + + b.Property("SearchStatus") + .HasColumnType("text") + .HasColumnName("search_status"); + + b.Property("SearchType") + .HasColumnType("text") + .HasColumnName("search_type"); + + b.Property("SeedRatio") + .HasColumnType("double precision") + .HasColumnName("seed_ratio"); + + b.Property("SeedingTimeHours") + .HasColumnType("double precision") + .HasColumnName("seeding_time_hours"); + + b.Property("Severity") + .IsRequired() + .HasColumnType("text") + .HasColumnName("severity"); + + b.Property("StrikeCount") + .HasColumnType("integer") + .HasColumnName("strike_count"); + + b.Property("StrikeId") + .HasColumnType("uuid") + .HasColumnName("strike_id"); + + b.Property("Timestamp") + .HasColumnType("timestamp with time zone") + .HasColumnName("timestamp"); + + b.Property("TrackingId") + .HasColumnType("uuid") + .HasColumnName("tracking_id"); + + b.HasKey("Id") + .HasName("pk_events"); + + b.HasIndex("ArrInstanceId") + .HasDatabaseName("ix_events_arr_instance_id"); + + b.HasIndex("CycleId") + .HasDatabaseName("ix_events_cycle_id"); + + b.HasIndex("DeleteReason") + .HasDatabaseName("ix_events_delete_reason"); + + b.HasIndex("EventType") + .HasDatabaseName("ix_events_event_type"); + + b.HasIndex("JobRunId") + .HasDatabaseName("ix_events_job_run_id"); + + b.HasIndex("Severity") + .HasDatabaseName("ix_events_severity"); + + b.HasIndex("StrikeId") + .HasDatabaseName("ix_events_strike_id"); + + b.HasIndex("Timestamp") + .IsDescending() + .HasDatabaseName("ix_events_timestamp"); + + b.ToTable("events", "events"); + }); + + modelBuilder.Entity("Cleanuparr.Persistence.Models.Events.ManualEvent", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("DownloadClientName") + .HasMaxLength(200) + .HasColumnType("character varying(200)") + .HasColumnName("download_client_name"); + + b.Property("DownloadClientType") + .HasColumnType("text") + .HasColumnName("download_client_type"); + + b.Property("InstanceType") + .HasColumnType("text") + .HasColumnName("instance_type"); + + b.Property("InstanceUrl") + .HasMaxLength(500) + .HasColumnType("character varying(500)") + .HasColumnName("instance_url"); + + b.Property("IsDryRun") + .HasColumnType("boolean") + .HasColumnName("is_dry_run"); + + b.Property("IsResolved") + .HasColumnType("boolean") + .HasColumnName("is_resolved"); + + b.Property("ItemHash") + .HasMaxLength(100) + .HasColumnType("character varying(100)") + .HasColumnName("item_hash"); + + b.Property("ItemTitle") + .HasMaxLength(500) + .HasColumnType("character varying(500)") + .HasColumnName("item_title"); + + b.Property("JobRunId") + .HasColumnType("uuid") + .HasColumnName("job_run_id"); + + b.Property("Message") + .IsRequired() + .HasMaxLength(1000) + .HasColumnType("character varying(1000)") + .HasColumnName("message"); + + b.Property("ResolvedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("resolved_at"); + + b.Property("Severity") + .IsRequired() + .HasColumnType("text") + .HasColumnName("severity"); + + b.Property("StrikeCount") + .HasColumnType("integer") + .HasColumnName("strike_count"); + + b.Property("Timestamp") + .HasColumnType("timestamp with time zone") + .HasColumnName("timestamp"); + + b.Property("Type") + .IsRequired() + .HasColumnType("text") + .HasColumnName("type"); + + b.HasKey("Id") + .HasName("pk_manual_events"); + + b.HasIndex("InstanceType") + .HasDatabaseName("ix_manual_events_instance_type"); + + b.HasIndex("IsResolved") + .HasDatabaseName("ix_manual_events_is_resolved"); + + b.HasIndex("JobRunId") + .HasDatabaseName("ix_manual_events_job_run_id"); + + b.HasIndex("Message") + .HasDatabaseName("ix_manual_events_message"); + + b.HasIndex("Severity") + .HasDatabaseName("ix_manual_events_severity"); + + b.HasIndex("Timestamp") + .IsDescending() + .HasDatabaseName("ix_manual_events_timestamp"); + + b.HasIndex("Type", "ItemHash") + .IsUnique() + .HasDatabaseName("ix_manual_events_type_item_hash") + .HasFilter("is_resolved = false"); + + b.ToTable("manual_events", "events"); + }); + + modelBuilder.Entity("Cleanuparr.Persistence.Models.State.CustomFormatScoreEntry", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("AirDateUtc") + .HasColumnType("timestamp with time zone") + .HasColumnName("air_date_utc"); + + b.Property("ArrInstanceId") + .HasColumnType("uuid") + .HasColumnName("arr_instance_id"); + + b.Property("CurrentScore") + .HasColumnType("integer") + .HasColumnName("current_score"); + + b.Property("CutoffScore") + .HasColumnType("integer") + .HasColumnName("cutoff_score"); + + b.Property("EpisodeId") + .HasColumnType("bigint") + .HasColumnName("episode_id"); + + b.Property("ExternalItemId") + .HasColumnType("bigint") + .HasColumnName("external_item_id"); + + b.Property("FileId") + .HasColumnType("bigint") + .HasColumnName("file_id"); + + b.Property("IsMonitored") + .HasColumnType("boolean") + .HasColumnName("is_monitored"); + + b.Property("ItemType") + .IsRequired() + .HasColumnType("text") + .HasColumnName("item_type"); + + b.Property("LastSyncedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("last_synced_at"); + + b.Property("LastUpgradedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("last_upgraded_at"); + + b.Property("QualityProfileName") + .IsRequired() + .HasColumnType("text") + .HasColumnName("quality_profile_name"); + + b.Property("SeasonNumber") + .HasColumnType("integer") + .HasColumnName("season_number"); + + b.Property("Title") + .IsRequired() + .HasColumnType("text") + .HasColumnName("title"); + + b.HasKey("Id") + .HasName("pk_custom_format_score_entries"); + + b.HasIndex("LastUpgradedAt") + .HasDatabaseName("ix_custom_format_score_entries_last_upgraded_at"); + + b.HasIndex("ArrInstanceId", "ExternalItemId", "EpisodeId") + .IsUnique() + .HasDatabaseName("ix_custom_format_score_entries_arr_instance_id_external_item_i"); + + b.ToTable("custom_format_score_entries", "events"); + }); + + modelBuilder.Entity("Cleanuparr.Persistence.Models.State.CustomFormatScoreHistory", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("ArrInstanceId") + .HasColumnType("uuid") + .HasColumnName("arr_instance_id"); + + b.Property("CutoffScore") + .HasColumnType("integer") + .HasColumnName("cutoff_score"); + + b.Property("EpisodeId") + .HasColumnType("bigint") + .HasColumnName("episode_id"); + + b.Property("ExternalItemId") + .HasColumnType("bigint") + .HasColumnName("external_item_id"); + + b.Property("ItemType") + .IsRequired() + .HasColumnType("text") + .HasColumnName("item_type"); + + b.Property("RecordedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("recorded_at"); + + b.Property("Score") + .HasColumnType("integer") + .HasColumnName("score"); + + b.Property("Title") + .IsRequired() + .HasColumnType("text") + .HasColumnName("title"); + + b.HasKey("Id") + .HasName("pk_custom_format_score_history"); + + b.HasIndex("RecordedAt") + .HasDatabaseName("ix_custom_format_score_history_recorded_at"); + + b.HasIndex("ArrInstanceId", "ExternalItemId", "EpisodeId") + .HasDatabaseName("ix_custom_format_score_history_arr_instance_id_external_item_i"); + + b.ToTable("custom_format_score_history", "events"); + }); + + modelBuilder.Entity("Cleanuparr.Persistence.Models.State.DownloadItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("DownloadId") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)") + .HasColumnName("download_id"); + + b.Property("IsMarkedForRemoval") + .HasColumnType("boolean") + .HasColumnName("is_marked_for_removal"); + + b.Property("IsRemoved") + .HasColumnType("boolean") + .HasColumnName("is_removed"); + + b.Property("IsReturning") + .HasColumnType("boolean") + .HasColumnName("is_returning"); + + b.Property("Title") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("character varying(500)") + .HasColumnName("title"); + + b.HasKey("Id") + .HasName("pk_download_items"); + + b.HasIndex("DownloadId") + .IsUnique() + .HasDatabaseName("ix_download_items_download_id"); + + b.ToTable("download_items", "events"); + }); + + modelBuilder.Entity("Cleanuparr.Persistence.Models.State.JobRun", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("CompletedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("completed_at"); + + b.Property("StartedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("started_at"); + + b.Property("Status") + .HasColumnType("text") + .HasColumnName("status"); + + b.Property("Type") + .IsRequired() + .HasColumnType("text") + .HasColumnName("type"); + + b.HasKey("Id") + .HasName("pk_job_runs"); + + b.HasIndex("StartedAt") + .IsDescending() + .HasDatabaseName("ix_job_runs_started_at"); + + b.HasIndex("Type") + .HasDatabaseName("ix_job_runs_type"); + + b.ToTable("job_runs", "events"); + }); + + modelBuilder.Entity("Cleanuparr.Persistence.Models.State.SearchQueueItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("ArrInstanceId") + .HasColumnType("uuid") + .HasColumnName("arr_instance_id"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("ItemId") + .HasColumnType("bigint") + .HasColumnName("item_id"); + + b.Property("SearchType") + .HasColumnType("text") + .HasColumnName("search_type"); + + b.Property("SeriesId") + .HasColumnType("bigint") + .HasColumnName("series_id"); + + b.Property("Title") + .IsRequired() + .HasColumnType("text") + .HasColumnName("title"); + + b.HasKey("Id") + .HasName("pk_search_queue"); + + b.HasIndex("ArrInstanceId") + .HasDatabaseName("ix_search_queue_arr_instance_id"); + + b.ToTable("search_queue", "events"); + }); + + modelBuilder.Entity("Cleanuparr.Persistence.Models.State.SeekerCommandTracker", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("ArrInstanceId") + .HasColumnType("uuid") + .HasColumnName("arr_instance_id"); + + b.Property("CommandId") + .HasColumnType("bigint") + .HasColumnName("command_id"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("EpisodeId") + .HasColumnType("bigint") + .HasColumnName("episode_id"); + + b.Property("EventId") + .HasColumnType("uuid") + .HasColumnName("event_id"); + + b.Property("ExternalItemId") + .HasColumnType("bigint") + .HasColumnName("external_item_id"); + + b.Property("ItemTitle") + .IsRequired() + .HasColumnType("text") + .HasColumnName("item_title"); + + b.Property("SeasonNumber") + .HasColumnType("integer") + .HasColumnName("season_number"); + + b.Property("Status") + .IsRequired() + .HasColumnType("text") + .HasColumnName("status"); + + b.HasKey("Id") + .HasName("pk_seeker_command_trackers"); + + b.HasIndex("ArrInstanceId") + .HasDatabaseName("ix_seeker_command_trackers_arr_instance_id"); + + b.ToTable("seeker_command_trackers", "events"); + }); + + modelBuilder.Entity("Cleanuparr.Persistence.Models.State.SeekerCycle", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("ArrInstanceId") + .HasColumnType("uuid") + .HasColumnName("arr_instance_id"); + + b.Property("CompletedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("completed_at"); + + b.Property("CompletionReason") + .HasColumnType("text") + .HasColumnName("completion_reason"); + + b.Property("EligibleAtStart") + .HasColumnType("integer") + .HasColumnName("eligible_at_start"); + + b.Property("EligibleNow") + .HasColumnType("integer") + .HasColumnName("eligible_now"); + + b.Property("GrabbedUnits") + .HasColumnType("integer") + .HasColumnName("grabbed_units"); + + b.Property("IsDryRun") + .HasColumnType("boolean") + .HasColumnName("is_dry_run"); + + b.Property("RemainingUnits") + .HasColumnType("integer") + .HasColumnName("remaining_units"); + + b.Property("SearchedUnits") + .HasColumnType("integer") + .HasColumnName("searched_units"); + + b.Property("StartedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("started_at"); + + b.HasKey("Id") + .HasName("pk_seeker_cycles"); + + b.HasIndex("ArrInstanceId") + .HasDatabaseName("ix_seeker_cycles_arr_instance_id"); + + b.ToTable("seeker_cycles", "events"); + }); + + modelBuilder.Entity("Cleanuparr.Persistence.Models.State.SeekerHistory", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("ArrInstanceId") + .HasColumnType("uuid") + .HasColumnName("arr_instance_id"); + + b.Property("ContentKind") + .IsRequired() + .HasColumnType("text") + .HasColumnName("content_kind"); + + b.Property("CycleId") + .HasColumnType("uuid") + .HasColumnName("cycle_id"); + + b.Property("EpisodeId") + .HasColumnType("bigint") + .HasColumnName("episode_id"); + + b.Property("ExternalItemId") + .HasColumnType("bigint") + .HasColumnName("external_item_id"); + + b.Property("Grabs") + .HasColumnType("integer") + .HasColumnName("grabs"); + + b.Property("IsDryRun") + .HasColumnType("boolean") + .HasColumnName("is_dry_run"); + + b.Property("ItemTitle") + .IsRequired() + .HasColumnType("text") + .HasColumnName("item_title"); + + b.Property("ItemType") + .IsRequired() + .HasColumnType("text") + .HasColumnName("item_type"); + + b.Property("LastOutcome") + .HasColumnType("text") + .HasColumnName("last_outcome"); + + b.Property("LastSearchedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("last_searched_at"); + + b.Property("SearchCount") + .HasColumnType("integer") + .HasColumnName("search_count"); + + b.Property("SeasonNumber") + .HasColumnType("integer") + .HasColumnName("season_number"); + + b.HasKey("Id") + .HasName("pk_seeker_history"); + + b.HasIndex("ArrInstanceId", "ExternalItemId", "ItemType", "SeasonNumber", "EpisodeId", "IsDryRun") + .IsUnique() + .HasDatabaseName("ix_seeker_history_arr_instance_id_external_item_id_item_type_s"); + + b.ToTable("seeker_history", "events"); + }); + + modelBuilder.Entity("Cleanuparr.Persistence.Models.State.Strike", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("DownloadItemId") + .HasColumnType("uuid") + .HasColumnName("download_item_id"); + + b.Property("IsDryRun") + .HasColumnType("boolean") + .HasColumnName("is_dry_run"); + + b.Property("JobRunId") + .HasColumnType("uuid") + .HasColumnName("job_run_id"); + + b.Property("LastDownloadedBytes") + .HasColumnType("bigint") + .HasColumnName("last_downloaded_bytes"); + + b.Property("Type") + .IsRequired() + .HasColumnType("text") + .HasColumnName("type"); + + b.HasKey("Id") + .HasName("pk_strikes"); + + b.HasIndex("CreatedAt") + .HasDatabaseName("ix_strikes_created_at"); + + b.HasIndex("JobRunId") + .HasDatabaseName("ix_strikes_job_run_id"); + + b.HasIndex("DownloadItemId", "Type") + .HasDatabaseName("ix_strikes_download_item_id_type"); + + b.ToTable("strikes", "events"); + }); + + modelBuilder.Entity("Cleanuparr.Persistence.Models.Events.AppEvent", b => + { + b.HasOne("Cleanuparr.Persistence.Models.State.JobRun", "JobRun") + .WithMany("Events") + .HasForeignKey("JobRunId") + .OnDelete(DeleteBehavior.SetNull) + .HasConstraintName("fk_events_job_runs_job_run_id"); + + b.HasOne("Cleanuparr.Persistence.Models.State.Strike", "Strike") + .WithMany() + .HasForeignKey("StrikeId") + .OnDelete(DeleteBehavior.SetNull) + .HasConstraintName("fk_events_strikes_strike_id"); + + b.Navigation("JobRun"); + + b.Navigation("Strike"); + }); + + modelBuilder.Entity("Cleanuparr.Persistence.Models.Events.ManualEvent", b => + { + b.HasOne("Cleanuparr.Persistence.Models.State.JobRun", "JobRun") + .WithMany("ManualEvents") + .HasForeignKey("JobRunId") + .HasConstraintName("fk_manual_events_job_runs_job_run_id"); + + b.Navigation("JobRun"); + }); + + modelBuilder.Entity("Cleanuparr.Persistence.Models.State.Strike", b => + { + b.HasOne("Cleanuparr.Persistence.Models.State.DownloadItem", "DownloadItem") + .WithMany("Strikes") + .HasForeignKey("DownloadItemId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_strikes_download_items_download_item_id"); + + b.HasOne("Cleanuparr.Persistence.Models.State.JobRun", "JobRun") + .WithMany("Strikes") + .HasForeignKey("JobRunId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_strikes_job_runs_job_run_id"); + + b.Navigation("DownloadItem"); + + b.Navigation("JobRun"); + }); + + modelBuilder.Entity("Cleanuparr.Persistence.Models.State.DownloadItem", b => + { + b.Navigation("Strikes"); + }); + + modelBuilder.Entity("Cleanuparr.Persistence.Models.State.JobRun", b => + { + b.Navigation("Events"); + + b.Navigation("ManualEvents"); + + b.Navigation("Strikes"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/code/backend/Cleanuparr.Persistence.Postgres/Migrations/Events/20261019111201_AddSeekerContentKind.cs b/code/backend/Cleanuparr.Persistence.Postgres/Migrations/Events/20261019111201_AddSeekerContentKind.cs new file mode 100644 index 00000000..dcd9349c --- /dev/null +++ b/code/backend/Cleanuparr.Persistence.Postgres/Migrations/Events/20261019111201_AddSeekerContentKind.cs @@ -0,0 +1,48 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Cleanuparr.Persistence.Postgres.Migrations.Events +{ + /// + public partial class AddSeekerContentKind : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "content_kind", + schema: "events", + table: "seeker_history", + type: "text", + nullable: false, + defaultValue: "standard"); + + migrationBuilder.AddColumn( + name: "content_kind", + schema: "events", + table: "events", + type: "text", + nullable: true); + + migrationBuilder.Sql(@" +UPDATE events.seeker_history +SET content_kind = 'special' +WHERE item_type = 'sonarr' AND season_number = 0;"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "content_kind", + schema: "events", + table: "seeker_history"); + + migrationBuilder.DropColumn( + name: "content_kind", + schema: "events", + table: "events"); + } + } +} diff --git a/code/backend/Cleanuparr.Persistence.Postgres/Migrations/Events/EventsContextModelSnapshot.cs b/code/backend/Cleanuparr.Persistence.Postgres/Migrations/Events/EventsContextModelSnapshot.cs index 533cad69..58c213a1 100644 --- a/code/backend/Cleanuparr.Persistence.Postgres/Migrations/Events/EventsContextModelSnapshot.cs +++ b/code/backend/Cleanuparr.Persistence.Postgres/Migrations/Events/EventsContextModelSnapshot.cs @@ -48,6 +48,10 @@ namespace Cleanuparr.Persistence.Postgres.Migrations.Events .HasColumnType("timestamp with time zone") .HasColumnName("completed_at"); + b.Property("ContentKind") + .HasColumnType("text") + .HasColumnName("content_kind"); + b.Property("CycleId") .HasColumnType("uuid") .HasColumnName("cycle_id"); @@ -666,6 +670,11 @@ namespace Cleanuparr.Persistence.Postgres.Migrations.Events .HasColumnType("uuid") .HasColumnName("arr_instance_id"); + b.Property("ContentKind") + .IsRequired() + .HasColumnType("text") + .HasColumnName("content_kind"); + b.Property("CycleId") .HasColumnType("uuid") .HasColumnName("cycle_id"); diff --git a/code/backend/Cleanuparr.Persistence.Sqlite/Migrations/Events/20261019111156_AddSeekerContentKind.Designer.cs b/code/backend/Cleanuparr.Persistence.Sqlite/Migrations/Events/20261019111156_AddSeekerContentKind.Designer.cs new file mode 100644 index 00000000..3ce4f40d --- /dev/null +++ b/code/backend/Cleanuparr.Persistence.Sqlite/Migrations/Events/20261019111156_AddSeekerContentKind.Designer.cs @@ -0,0 +1,853 @@ +// +using System; +using Cleanuparr.Persistence; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace Cleanuparr.Persistence.Migrations.Events +{ + [DbContext(typeof(EventsContext))] + [Migration("20261019111156_AddSeekerContentKind")] + partial class AddSeekerContentKind + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder.HasAnnotation("ProductVersion", "10.0.1"); + + modelBuilder.Entity("Cleanuparr.Persistence.Models.Events.AppEvent", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT") + .HasColumnName("id"); + + b.Property("ArrInstanceId") + .HasColumnType("TEXT") + .HasColumnName("arr_instance_id"); + + b.Property("CleanReason") + .HasColumnType("TEXT") + .HasColumnName("clean_reason"); + + b.Property("CleanedCategory") + .HasMaxLength(200) + .HasColumnType("TEXT") + .HasColumnName("cleaned_category"); + + b.Property("CompletedAt") + .HasColumnType("TEXT") + .HasColumnName("completed_at"); + + b.Property("ContentKind") + .HasColumnType("TEXT") + .HasColumnName("content_kind"); + + b.Property("CycleId") + .HasColumnType("TEXT") + .HasColumnName("cycle_id"); + + b.Property("DeleteReason") + .HasColumnType("TEXT") + .HasColumnName("delete_reason"); + + b.Property("DownloadClientId") + .HasColumnType("TEXT") + .HasColumnName("download_client_id"); + + b.Property("EventType") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("event_type"); + + b.PrimitiveCollection("FailedImportReasons") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("failed_import_reasons"); + + b.PrimitiveCollection("GrabbedItems") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("grabbed_items"); + + b.Property("IsCategoryTag") + .HasColumnType("INTEGER") + .HasColumnName("is_category_tag"); + + b.Property("IsDryRun") + .HasColumnType("INTEGER") + .HasColumnName("is_dry_run"); + + b.Property("ItemHash") + .HasMaxLength(100) + .HasColumnType("TEXT") + .HasColumnName("item_hash"); + + b.Property("ItemTitle") + .HasMaxLength(500) + .HasColumnType("TEXT") + .HasColumnName("item_title"); + + b.Property("JobRunId") + .HasColumnType("TEXT") + .HasColumnName("job_run_id"); + + b.Property("Message") + .IsRequired() + .HasMaxLength(1000) + .HasColumnType("TEXT") + .HasColumnName("message"); + + b.Property("NewCategory") + .HasMaxLength(200) + .HasColumnType("TEXT") + .HasColumnName("new_category"); + + b.Property("OldCategory") + .HasMaxLength(200) + .HasColumnType("TEXT") + .HasColumnName("old_category"); + + b.Property("RemoveFromClient") + .HasColumnType("INTEGER") + .HasColumnName("remove_from_client"); + + b.Property("SearchReason") + .HasColumnType("TEXT") + .HasColumnName("search_reason"); + + b.Property("SearchStatus") + .HasColumnType("TEXT") + .HasColumnName("search_status"); + + b.Property("SearchType") + .HasColumnType("TEXT") + .HasColumnName("search_type"); + + b.Property("SeedRatio") + .HasColumnType("REAL") + .HasColumnName("seed_ratio"); + + b.Property("SeedingTimeHours") + .HasColumnType("REAL") + .HasColumnName("seeding_time_hours"); + + b.Property("Severity") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("severity"); + + b.Property("StrikeCount") + .HasColumnType("INTEGER") + .HasColumnName("strike_count"); + + b.Property("StrikeId") + .HasColumnType("TEXT") + .HasColumnName("strike_id"); + + b.Property("Timestamp") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("timestamp"); + + b.Property("TrackingId") + .HasColumnType("TEXT") + .HasColumnName("tracking_id"); + + b.HasKey("Id") + .HasName("pk_events"); + + b.HasIndex("ArrInstanceId") + .HasDatabaseName("ix_events_arr_instance_id"); + + b.HasIndex("CycleId") + .HasDatabaseName("ix_events_cycle_id"); + + b.HasIndex("DeleteReason") + .HasDatabaseName("ix_events_delete_reason"); + + b.HasIndex("EventType") + .HasDatabaseName("ix_events_event_type"); + + b.HasIndex("JobRunId") + .HasDatabaseName("ix_events_job_run_id"); + + b.HasIndex("Severity") + .HasDatabaseName("ix_events_severity"); + + b.HasIndex("StrikeId") + .HasDatabaseName("ix_events_strike_id"); + + b.HasIndex("Timestamp") + .IsDescending() + .HasDatabaseName("ix_events_timestamp"); + + b.ToTable("events", (string)null); + }); + + modelBuilder.Entity("Cleanuparr.Persistence.Models.Events.ManualEvent", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT") + .HasColumnName("id"); + + b.Property("DownloadClientName") + .HasMaxLength(200) + .HasColumnType("TEXT") + .HasColumnName("download_client_name"); + + b.Property("DownloadClientType") + .HasColumnType("TEXT") + .HasColumnName("download_client_type"); + + b.Property("InstanceType") + .HasColumnType("TEXT") + .HasColumnName("instance_type"); + + b.Property("InstanceUrl") + .HasMaxLength(500) + .HasColumnType("TEXT") + .HasColumnName("instance_url"); + + b.Property("IsDryRun") + .HasColumnType("INTEGER") + .HasColumnName("is_dry_run"); + + b.Property("IsResolved") + .HasColumnType("INTEGER") + .HasColumnName("is_resolved"); + + b.Property("ItemHash") + .HasMaxLength(100) + .HasColumnType("TEXT") + .HasColumnName("item_hash"); + + b.Property("ItemTitle") + .HasMaxLength(500) + .HasColumnType("TEXT") + .HasColumnName("item_title"); + + b.Property("JobRunId") + .HasColumnType("TEXT") + .HasColumnName("job_run_id"); + + b.Property("Message") + .IsRequired() + .HasMaxLength(1000) + .HasColumnType("TEXT") + .HasColumnName("message"); + + b.Property("ResolvedAt") + .HasColumnType("TEXT") + .HasColumnName("resolved_at"); + + b.Property("Severity") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("severity"); + + b.Property("StrikeCount") + .HasColumnType("INTEGER") + .HasColumnName("strike_count"); + + b.Property("Timestamp") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("timestamp"); + + b.Property("Type") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("type"); + + b.HasKey("Id") + .HasName("pk_manual_events"); + + b.HasIndex("InstanceType") + .HasDatabaseName("ix_manual_events_instance_type"); + + b.HasIndex("IsResolved") + .HasDatabaseName("ix_manual_events_is_resolved"); + + b.HasIndex("JobRunId") + .HasDatabaseName("ix_manual_events_job_run_id"); + + b.HasIndex("Message") + .HasDatabaseName("ix_manual_events_message"); + + b.HasIndex("Severity") + .HasDatabaseName("ix_manual_events_severity"); + + b.HasIndex("Timestamp") + .IsDescending() + .HasDatabaseName("ix_manual_events_timestamp"); + + b.HasIndex("Type", "ItemHash") + .IsUnique() + .HasDatabaseName("ix_manual_events_type_item_hash") + .HasFilter("\"is_resolved\" = 0"); + + b.ToTable("manual_events", (string)null); + }); + + modelBuilder.Entity("Cleanuparr.Persistence.Models.State.CustomFormatScoreEntry", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT") + .HasColumnName("id"); + + b.Property("AirDateUtc") + .HasColumnType("TEXT") + .HasColumnName("air_date_utc"); + + b.Property("ArrInstanceId") + .HasColumnType("TEXT") + .HasColumnName("arr_instance_id"); + + b.Property("CurrentScore") + .HasColumnType("INTEGER") + .HasColumnName("current_score"); + + b.Property("CutoffScore") + .HasColumnType("INTEGER") + .HasColumnName("cutoff_score"); + + b.Property("EpisodeId") + .HasColumnType("INTEGER") + .HasColumnName("episode_id"); + + b.Property("ExternalItemId") + .HasColumnType("INTEGER") + .HasColumnName("external_item_id"); + + b.Property("FileId") + .HasColumnType("INTEGER") + .HasColumnName("file_id"); + + b.Property("IsMonitored") + .HasColumnType("INTEGER") + .HasColumnName("is_monitored"); + + b.Property("ItemType") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("item_type"); + + b.Property("LastSyncedAt") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("last_synced_at"); + + b.Property("LastUpgradedAt") + .HasColumnType("TEXT") + .HasColumnName("last_upgraded_at"); + + b.Property("QualityProfileName") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("quality_profile_name"); + + b.Property("SeasonNumber") + .HasColumnType("INTEGER") + .HasColumnName("season_number"); + + b.Property("Title") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("title"); + + b.HasKey("Id") + .HasName("pk_custom_format_score_entries"); + + b.HasIndex("LastUpgradedAt") + .HasDatabaseName("ix_custom_format_score_entries_last_upgraded_at"); + + b.HasIndex("ArrInstanceId", "ExternalItemId", "EpisodeId") + .IsUnique() + .HasDatabaseName("ix_custom_format_score_entries_arr_instance_id_external_item_id_episode_id"); + + b.ToTable("custom_format_score_entries", (string)null); + }); + + modelBuilder.Entity("Cleanuparr.Persistence.Models.State.CustomFormatScoreHistory", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT") + .HasColumnName("id"); + + b.Property("ArrInstanceId") + .HasColumnType("TEXT") + .HasColumnName("arr_instance_id"); + + b.Property("CutoffScore") + .HasColumnType("INTEGER") + .HasColumnName("cutoff_score"); + + b.Property("EpisodeId") + .HasColumnType("INTEGER") + .HasColumnName("episode_id"); + + b.Property("ExternalItemId") + .HasColumnType("INTEGER") + .HasColumnName("external_item_id"); + + b.Property("ItemType") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("item_type"); + + b.Property("RecordedAt") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("recorded_at"); + + b.Property("Score") + .HasColumnType("INTEGER") + .HasColumnName("score"); + + b.Property("Title") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("title"); + + b.HasKey("Id") + .HasName("pk_custom_format_score_history"); + + b.HasIndex("RecordedAt") + .HasDatabaseName("ix_custom_format_score_history_recorded_at"); + + b.HasIndex("ArrInstanceId", "ExternalItemId", "EpisodeId") + .HasDatabaseName("ix_custom_format_score_history_arr_instance_id_external_item_id_episode_id"); + + b.ToTable("custom_format_score_history", (string)null); + }); + + modelBuilder.Entity("Cleanuparr.Persistence.Models.State.DownloadItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT") + .HasColumnName("id"); + + b.Property("DownloadId") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT") + .HasColumnName("download_id"); + + b.Property("IsMarkedForRemoval") + .HasColumnType("INTEGER") + .HasColumnName("is_marked_for_removal"); + + b.Property("IsRemoved") + .HasColumnType("INTEGER") + .HasColumnName("is_removed"); + + b.Property("IsReturning") + .HasColumnType("INTEGER") + .HasColumnName("is_returning"); + + b.Property("Title") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("TEXT") + .HasColumnName("title"); + + b.HasKey("Id") + .HasName("pk_download_items"); + + b.HasIndex("DownloadId") + .IsUnique() + .HasDatabaseName("ix_download_items_download_id"); + + b.ToTable("download_items", (string)null); + }); + + modelBuilder.Entity("Cleanuparr.Persistence.Models.State.JobRun", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT") + .HasColumnName("id"); + + b.Property("CompletedAt") + .HasColumnType("TEXT") + .HasColumnName("completed_at"); + + b.Property("StartedAt") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("started_at"); + + b.Property("Status") + .HasColumnType("TEXT") + .HasColumnName("status"); + + b.Property("Type") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("type"); + + b.HasKey("Id") + .HasName("pk_job_runs"); + + b.HasIndex("StartedAt") + .IsDescending() + .HasDatabaseName("ix_job_runs_started_at"); + + b.HasIndex("Type") + .HasDatabaseName("ix_job_runs_type"); + + b.ToTable("job_runs", (string)null); + }); + + modelBuilder.Entity("Cleanuparr.Persistence.Models.State.SearchQueueItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT") + .HasColumnName("id"); + + b.Property("ArrInstanceId") + .HasColumnType("TEXT") + .HasColumnName("arr_instance_id"); + + b.Property("CreatedAt") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("created_at"); + + b.Property("ItemId") + .HasColumnType("INTEGER") + .HasColumnName("item_id"); + + b.Property("SearchType") + .HasColumnType("TEXT") + .HasColumnName("search_type"); + + b.Property("SeriesId") + .HasColumnType("INTEGER") + .HasColumnName("series_id"); + + b.Property("Title") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("title"); + + b.HasKey("Id") + .HasName("pk_search_queue"); + + b.HasIndex("ArrInstanceId") + .HasDatabaseName("ix_search_queue_arr_instance_id"); + + b.ToTable("search_queue", (string)null); + }); + + modelBuilder.Entity("Cleanuparr.Persistence.Models.State.SeekerCommandTracker", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT") + .HasColumnName("id"); + + b.Property("ArrInstanceId") + .HasColumnType("TEXT") + .HasColumnName("arr_instance_id"); + + b.Property("CommandId") + .HasColumnType("INTEGER") + .HasColumnName("command_id"); + + b.Property("CreatedAt") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("created_at"); + + b.Property("EpisodeId") + .HasColumnType("INTEGER") + .HasColumnName("episode_id"); + + b.Property("EventId") + .HasColumnType("TEXT") + .HasColumnName("event_id"); + + b.Property("ExternalItemId") + .HasColumnType("INTEGER") + .HasColumnName("external_item_id"); + + b.Property("ItemTitle") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("item_title"); + + b.Property("SeasonNumber") + .HasColumnType("INTEGER") + .HasColumnName("season_number"); + + b.Property("Status") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("status"); + + b.HasKey("Id") + .HasName("pk_seeker_command_trackers"); + + b.HasIndex("ArrInstanceId") + .HasDatabaseName("ix_seeker_command_trackers_arr_instance_id"); + + b.ToTable("seeker_command_trackers", (string)null); + }); + + modelBuilder.Entity("Cleanuparr.Persistence.Models.State.SeekerCycle", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT") + .HasColumnName("id"); + + b.Property("ArrInstanceId") + .HasColumnType("TEXT") + .HasColumnName("arr_instance_id"); + + b.Property("CompletedAt") + .HasColumnType("TEXT") + .HasColumnName("completed_at"); + + b.Property("CompletionReason") + .HasColumnType("TEXT") + .HasColumnName("completion_reason"); + + b.Property("EligibleAtStart") + .HasColumnType("INTEGER") + .HasColumnName("eligible_at_start"); + + b.Property("EligibleNow") + .HasColumnType("INTEGER") + .HasColumnName("eligible_now"); + + b.Property("GrabbedUnits") + .HasColumnType("INTEGER") + .HasColumnName("grabbed_units"); + + b.Property("IsDryRun") + .HasColumnType("INTEGER") + .HasColumnName("is_dry_run"); + + b.Property("RemainingUnits") + .HasColumnType("INTEGER") + .HasColumnName("remaining_units"); + + b.Property("SearchedUnits") + .HasColumnType("INTEGER") + .HasColumnName("searched_units"); + + b.Property("StartedAt") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("started_at"); + + b.HasKey("Id") + .HasName("pk_seeker_cycles"); + + b.HasIndex("ArrInstanceId") + .HasDatabaseName("ix_seeker_cycles_arr_instance_id"); + + b.ToTable("seeker_cycles", (string)null); + }); + + modelBuilder.Entity("Cleanuparr.Persistence.Models.State.SeekerHistory", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT") + .HasColumnName("id"); + + b.Property("ArrInstanceId") + .HasColumnType("TEXT") + .HasColumnName("arr_instance_id"); + + b.Property("ContentKind") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("content_kind"); + + b.Property("CycleId") + .HasColumnType("TEXT") + .HasColumnName("cycle_id"); + + b.Property("EpisodeId") + .HasColumnType("INTEGER") + .HasColumnName("episode_id"); + + b.Property("ExternalItemId") + .HasColumnType("INTEGER") + .HasColumnName("external_item_id"); + + b.Property("Grabs") + .HasColumnType("INTEGER") + .HasColumnName("grabs"); + + b.Property("IsDryRun") + .HasColumnType("INTEGER") + .HasColumnName("is_dry_run"); + + b.Property("ItemTitle") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("item_title"); + + b.Property("ItemType") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("item_type"); + + b.Property("LastOutcome") + .HasColumnType("TEXT") + .HasColumnName("last_outcome"); + + b.Property("LastSearchedAt") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("last_searched_at"); + + b.Property("SearchCount") + .HasColumnType("INTEGER") + .HasColumnName("search_count"); + + b.Property("SeasonNumber") + .HasColumnType("INTEGER") + .HasColumnName("season_number"); + + b.HasKey("Id") + .HasName("pk_seeker_history"); + + b.HasIndex("ArrInstanceId", "ExternalItemId", "ItemType", "SeasonNumber", "EpisodeId", "IsDryRun") + .IsUnique() + .HasDatabaseName("ix_seeker_history_arr_instance_id_external_item_id_item_type_season_number_episode_id_is_dry_run"); + + b.ToTable("seeker_history", (string)null); + }); + + modelBuilder.Entity("Cleanuparr.Persistence.Models.State.Strike", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT") + .HasColumnName("id"); + + b.Property("CreatedAt") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("created_at"); + + b.Property("DownloadItemId") + .HasColumnType("TEXT") + .HasColumnName("download_item_id"); + + b.Property("IsDryRun") + .HasColumnType("INTEGER") + .HasColumnName("is_dry_run"); + + b.Property("JobRunId") + .HasColumnType("TEXT") + .HasColumnName("job_run_id"); + + b.Property("LastDownloadedBytes") + .HasColumnType("INTEGER") + .HasColumnName("last_downloaded_bytes"); + + b.Property("Type") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("type"); + + b.HasKey("Id") + .HasName("pk_strikes"); + + b.HasIndex("CreatedAt") + .HasDatabaseName("ix_strikes_created_at"); + + b.HasIndex("JobRunId") + .HasDatabaseName("ix_strikes_job_run_id"); + + b.HasIndex("DownloadItemId", "Type") + .HasDatabaseName("ix_strikes_download_item_id_type"); + + b.ToTable("strikes", (string)null); + }); + + modelBuilder.Entity("Cleanuparr.Persistence.Models.Events.AppEvent", b => + { + b.HasOne("Cleanuparr.Persistence.Models.State.JobRun", "JobRun") + .WithMany("Events") + .HasForeignKey("JobRunId") + .OnDelete(DeleteBehavior.SetNull) + .HasConstraintName("fk_events_job_runs_job_run_id"); + + b.HasOne("Cleanuparr.Persistence.Models.State.Strike", "Strike") + .WithMany() + .HasForeignKey("StrikeId") + .OnDelete(DeleteBehavior.SetNull) + .HasConstraintName("fk_events_strikes_strike_id"); + + b.Navigation("JobRun"); + + b.Navigation("Strike"); + }); + + modelBuilder.Entity("Cleanuparr.Persistence.Models.Events.ManualEvent", b => + { + b.HasOne("Cleanuparr.Persistence.Models.State.JobRun", "JobRun") + .WithMany("ManualEvents") + .HasForeignKey("JobRunId") + .HasConstraintName("fk_manual_events_job_runs_job_run_id"); + + b.Navigation("JobRun"); + }); + + modelBuilder.Entity("Cleanuparr.Persistence.Models.State.Strike", b => + { + b.HasOne("Cleanuparr.Persistence.Models.State.DownloadItem", "DownloadItem") + .WithMany("Strikes") + .HasForeignKey("DownloadItemId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_strikes_download_items_download_item_id"); + + b.HasOne("Cleanuparr.Persistence.Models.State.JobRun", "JobRun") + .WithMany("Strikes") + .HasForeignKey("JobRunId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_strikes_job_runs_job_run_id"); + + b.Navigation("DownloadItem"); + + b.Navigation("JobRun"); + }); + + modelBuilder.Entity("Cleanuparr.Persistence.Models.State.DownloadItem", b => + { + b.Navigation("Strikes"); + }); + + modelBuilder.Entity("Cleanuparr.Persistence.Models.State.JobRun", b => + { + b.Navigation("Events"); + + b.Navigation("ManualEvents"); + + b.Navigation("Strikes"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/code/backend/Cleanuparr.Persistence.Sqlite/Migrations/Events/20261019111156_AddSeekerContentKind.cs b/code/backend/Cleanuparr.Persistence.Sqlite/Migrations/Events/20261019111156_AddSeekerContentKind.cs new file mode 100644 index 00000000..9cef2e31 --- /dev/null +++ b/code/backend/Cleanuparr.Persistence.Sqlite/Migrations/Events/20261019111156_AddSeekerContentKind.cs @@ -0,0 +1,44 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Cleanuparr.Persistence.Migrations.Events +{ + /// + public partial class AddSeekerContentKind : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "content_kind", + table: "seeker_history", + type: "TEXT", + nullable: false, + defaultValue: "standard"); + + migrationBuilder.AddColumn( + name: "content_kind", + table: "events", + type: "TEXT", + nullable: true); + + migrationBuilder.Sql(@" +UPDATE seeker_history +SET content_kind = 'special' +WHERE item_type = 'sonarr' AND season_number = 0;"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "content_kind", + table: "seeker_history"); + + migrationBuilder.DropColumn( + name: "content_kind", + table: "events"); + } + } +} diff --git a/code/backend/Cleanuparr.Persistence.Sqlite/Migrations/Events/EventsContextModelSnapshot.cs b/code/backend/Cleanuparr.Persistence.Sqlite/Migrations/Events/EventsContextModelSnapshot.cs index aa39baa9..f594e375 100644 --- a/code/backend/Cleanuparr.Persistence.Sqlite/Migrations/Events/EventsContextModelSnapshot.cs +++ b/code/backend/Cleanuparr.Persistence.Sqlite/Migrations/Events/EventsContextModelSnapshot.cs @@ -41,6 +41,10 @@ namespace Cleanuparr.Persistence.Migrations.Events .HasColumnType("TEXT") .HasColumnName("completed_at"); + b.Property("ContentKind") + .HasColumnType("TEXT") + .HasColumnName("content_kind"); + b.Property("CycleId") .HasColumnType("TEXT") .HasColumnName("cycle_id"); @@ -667,6 +671,11 @@ namespace Cleanuparr.Persistence.Migrations.Events .HasColumnType("TEXT") .HasColumnName("arr_instance_id"); + b.Property("ContentKind") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("content_kind"); + b.Property("CycleId") .HasColumnType("TEXT") .HasColumnName("cycle_id"); diff --git a/code/backend/Cleanuparr.Persistence/Models/Events/AppEvent.cs b/code/backend/Cleanuparr.Persistence/Models/Events/AppEvent.cs index 05f081b0..6c327986 100644 --- a/code/backend/Cleanuparr.Persistence/Models/Events/AppEvent.cs +++ b/code/backend/Cleanuparr.Persistence/Models/Events/AppEvent.cs @@ -158,6 +158,11 @@ public class AppEvent /// public SeekerSearchReason? SearchReason { get; set; } + /// + /// What the searched Sonarr unit holds (SearchTriggered events) + /// + public SeekerContentKind? ContentKind { get; set; } + /// /// Titles of items grabbed after search completion, populated by SeekerCommandMonitor (SearchTriggered events) /// diff --git a/code/backend/Cleanuparr.Persistence/Models/State/SeekerHistory.cs b/code/backend/Cleanuparr.Persistence/Models/State/SeekerHistory.cs index 6e7e4de7..a0b430ee 100644 --- a/code/backend/Cleanuparr.Persistence/Models/State/SeekerHistory.cs +++ b/code/backend/Cleanuparr.Persistence/Models/State/SeekerHistory.cs @@ -40,6 +40,11 @@ public sealed record SeekerHistory /// public long EpisodeId { get; set; } + /// + /// What the unit holds + /// + public SeekerContentKind ContentKind { get; set; } + /// /// The last cycle this unit was searched in ///