diff --git a/API/Migrations/Library/20250703191925_Initial.Designer.cs b/API/Migrations/Library/20250703191925_Initial.Designer.cs new file mode 100644 index 0000000..85754b7 --- /dev/null +++ b/API/Migrations/Library/20250703191925_Initial.Designer.cs @@ -0,0 +1,70 @@ +// +using API.Schema.LibraryContext; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace API.Migrations.Library +{ + [DbContext(typeof(LibraryContext))] + [Migration("20250703191925_Initial")] + partial class Initial + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "9.0.5") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("API.Schema.LibraryContext.LibraryConnectors.LibraryConnector", b => + { + b.Property("Key") + .HasColumnType("text"); + + b.Property("Auth") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("BaseUrl") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("LibraryType") + .HasColumnType("smallint"); + + b.HasKey("Key"); + + b.ToTable("LibraryConnectors"); + + b.HasDiscriminator("LibraryType"); + + b.UseTphMappingStrategy(); + }); + + modelBuilder.Entity("API.Schema.LibraryContext.LibraryConnectors.Kavita", b => + { + b.HasBaseType("API.Schema.LibraryContext.LibraryConnectors.LibraryConnector"); + + b.HasDiscriminator().HasValue((byte)1); + }); + + modelBuilder.Entity("API.Schema.LibraryContext.LibraryConnectors.Komga", b => + { + b.HasBaseType("API.Schema.LibraryContext.LibraryConnectors.LibraryConnector"); + + b.HasDiscriminator().HasValue((byte)0); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/API/Migrations/Library/20250703191925_Initial.cs b/API/Migrations/Library/20250703191925_Initial.cs new file mode 100644 index 0000000..6de0f48 --- /dev/null +++ b/API/Migrations/Library/20250703191925_Initial.cs @@ -0,0 +1,35 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace API.Migrations.Library +{ + /// + public partial class Initial : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "LibraryConnectors", + columns: table => new + { + Key = table.Column(type: "text", nullable: false), + LibraryType = table.Column(type: "smallint", nullable: false), + BaseUrl = table.Column(type: "character varying(256)", maxLength: 256, nullable: false), + Auth = table.Column(type: "character varying(256)", maxLength: 256, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_LibraryConnectors", x => x.Key); + }); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "LibraryConnectors"); + } + } +} diff --git a/API/Migrations/Library/LibraryContextModelSnapshot.cs b/API/Migrations/Library/LibraryContextModelSnapshot.cs new file mode 100644 index 0000000..8f2d077 --- /dev/null +++ b/API/Migrations/Library/LibraryContextModelSnapshot.cs @@ -0,0 +1,67 @@ +// +using API.Schema.LibraryContext; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace API.Migrations.Library +{ + [DbContext(typeof(LibraryContext))] + partial class LibraryContextModelSnapshot : ModelSnapshot + { + protected override void BuildModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "9.0.5") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("API.Schema.LibraryContext.LibraryConnectors.LibraryConnector", b => + { + b.Property("Key") + .HasColumnType("text"); + + b.Property("Auth") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("BaseUrl") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("LibraryType") + .HasColumnType("smallint"); + + b.HasKey("Key"); + + b.ToTable("LibraryConnectors"); + + b.HasDiscriminator("LibraryType"); + + b.UseTphMappingStrategy(); + }); + + modelBuilder.Entity("API.Schema.LibraryContext.LibraryConnectors.Kavita", b => + { + b.HasBaseType("API.Schema.LibraryContext.LibraryConnectors.LibraryConnector"); + + b.HasDiscriminator().HasValue((byte)1); + }); + + modelBuilder.Entity("API.Schema.LibraryContext.LibraryConnectors.Komga", b => + { + b.HasBaseType("API.Schema.LibraryContext.LibraryConnectors.LibraryConnector"); + + b.HasDiscriminator().HasValue((byte)0); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/API/Migrations/Manga/20250703192023_Initial.Designer.cs b/API/Migrations/Manga/20250703192023_Initial.Designer.cs new file mode 100644 index 0000000..b728b26 --- /dev/null +++ b/API/Migrations/Manga/20250703192023_Initial.Designer.cs @@ -0,0 +1,547 @@ +// +using API.Schema.MangaContext; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace API.Migrations.Manga +{ + [DbContext(typeof(MangaContext))] + [Migration("20250703192023_Initial")] + partial class Initial + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "9.0.5") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("API.Schema.MangaContext.Author", b => + { + b.Property("Key") + .HasColumnType("text"); + + b.Property("AuthorName") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("character varying(128)"); + + b.HasKey("Key"); + + b.ToTable("Authors"); + }); + + modelBuilder.Entity("API.Schema.MangaContext.Chapter", b => + { + b.Property("Key") + .HasColumnType("text"); + + b.Property("ChapterNumber") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("character varying(10)"); + + b.Property("Downloaded") + .HasColumnType("boolean"); + + b.Property("FileName") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("ParentMangaId") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("Title") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("VolumeNumber") + .HasColumnType("integer"); + + b.HasKey("Key"); + + b.HasIndex("ParentMangaId"); + + b.ToTable("Chapters"); + }); + + modelBuilder.Entity("API.Schema.MangaContext.FileLibrary", b => + { + b.Property("Key") + .HasColumnType("text"); + + b.Property("BasePath") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("LibraryName") + .IsRequired() + .HasMaxLength(512) + .HasColumnType("character varying(512)"); + + b.HasKey("Key"); + + b.ToTable("FileLibraries"); + }); + + modelBuilder.Entity("API.Schema.MangaContext.Manga", b => + { + b.Property("Key") + .HasColumnType("text"); + + b.Property("CoverFileNameInCache") + .HasMaxLength(512) + .HasColumnType("character varying(512)"); + + b.Property("CoverUrl") + .IsRequired() + .HasMaxLength(512) + .HasColumnType("character varying(512)"); + + b.Property("Description") + .IsRequired() + .HasColumnType("text"); + + b.Property("DirectoryName") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("character varying(1024)"); + + b.Property("IgnoreChaptersBefore") + .HasColumnType("real"); + + b.Property("LibraryId") + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(512) + .HasColumnType("character varying(512)"); + + b.Property("OriginalLanguage") + .HasMaxLength(8) + .HasColumnType("character varying(8)"); + + b.Property("ReleaseStatus") + .HasColumnType("smallint"); + + b.Property("Year") + .HasColumnType("bigint"); + + b.HasKey("Key"); + + b.HasIndex("LibraryId"); + + b.ToTable("Mangas"); + }); + + modelBuilder.Entity("API.Schema.MangaContext.MangaConnectorId", b => + { + b.Property("Key") + .HasColumnType("text"); + + b.Property("IdOnConnectorSite") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("MangaConnectorName") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("character varying(32)"); + + b.Property("ObjId") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("UseForDownload") + .HasColumnType("boolean"); + + b.Property("WebsiteUrl") + .HasMaxLength(512) + .HasColumnType("character varying(512)"); + + b.HasKey("Key"); + + b.HasIndex("MangaConnectorName"); + + b.HasIndex("ObjId"); + + b.ToTable("MangaConnectorToChapter"); + }); + + modelBuilder.Entity("API.Schema.MangaContext.MangaConnectorId", b => + { + b.Property("Key") + .HasColumnType("text"); + + b.Property("IdOnConnectorSite") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("MangaConnectorName") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("character varying(32)"); + + b.Property("ObjId") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("UseForDownload") + .HasColumnType("boolean"); + + b.Property("WebsiteUrl") + .HasMaxLength(512) + .HasColumnType("character varying(512)"); + + b.HasKey("Key"); + + b.HasIndex("MangaConnectorName"); + + b.HasIndex("ObjId"); + + b.ToTable("MangaConnectorToManga"); + }); + + modelBuilder.Entity("API.Schema.MangaContext.MangaConnectors.MangaConnector", b => + { + b.Property("Name") + .HasMaxLength(32) + .HasColumnType("character varying(32)"); + + b.PrimitiveCollection("BaseUris") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("text[]"); + + b.Property("Enabled") + .HasColumnType("boolean"); + + b.Property("IconUrl") + .IsRequired() + .HasMaxLength(2048) + .HasColumnType("character varying(2048)"); + + b.PrimitiveCollection("SupportedLanguages") + .IsRequired() + .HasMaxLength(8) + .HasColumnType("text[]"); + + b.HasKey("Name"); + + b.ToTable("MangaConnectors"); + + b.HasDiscriminator("Name").HasValue("MangaConnector"); + + b.UseTphMappingStrategy(); + }); + + modelBuilder.Entity("API.Schema.MangaContext.MangaTag", b => + { + b.Property("Tag") + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.HasKey("Tag"); + + b.ToTable("Tags"); + }); + + modelBuilder.Entity("API.Schema.MangaContext.MetadataFetchers.MetadataEntry", b => + { + b.Property("MetadataFetcherName") + .HasColumnType("text"); + + b.Property("Identifier") + .HasColumnType("text"); + + b.Property("MangaId") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("MetadataFetcherName", "Identifier"); + + b.HasIndex("MangaId"); + + b.ToTable("MetadataEntries"); + }); + + modelBuilder.Entity("API.Schema.MangaContext.MetadataFetchers.MetadataFetcher", b => + { + b.Property("Name") + .HasColumnType("text"); + + b.Property("MetadataEntry") + .IsRequired() + .HasMaxLength(21) + .HasColumnType("character varying(21)"); + + b.HasKey("Name"); + + b.ToTable("MetadataFetcher"); + + b.HasDiscriminator("MetadataEntry").HasValue("MetadataFetcher"); + + b.UseTphMappingStrategy(); + }); + + modelBuilder.Entity("AuthorToManga", b => + { + b.Property("AuthorIds") + .HasColumnType("text"); + + b.Property("MangaIds") + .HasColumnType("text"); + + b.HasKey("AuthorIds", "MangaIds"); + + b.HasIndex("MangaIds"); + + b.ToTable("AuthorToManga"); + }); + + modelBuilder.Entity("MangaTagToManga", b => + { + b.Property("MangaTagIds") + .HasColumnType("character varying(64)"); + + b.Property("MangaIds") + .HasColumnType("text"); + + b.HasKey("MangaTagIds", "MangaIds"); + + b.HasIndex("MangaIds"); + + b.ToTable("MangaTagToManga"); + }); + + modelBuilder.Entity("API.Schema.MangaContext.MangaConnectors.ComickIo", b => + { + b.HasBaseType("API.Schema.MangaContext.MangaConnectors.MangaConnector"); + + b.HasDiscriminator().HasValue("ComickIo"); + }); + + modelBuilder.Entity("API.Schema.MangaContext.MangaConnectors.Global", b => + { + b.HasBaseType("API.Schema.MangaContext.MangaConnectors.MangaConnector"); + + b.HasDiscriminator().HasValue("Global"); + }); + + modelBuilder.Entity("API.Schema.MangaContext.MangaConnectors.MangaDex", b => + { + b.HasBaseType("API.Schema.MangaContext.MangaConnectors.MangaConnector"); + + b.HasDiscriminator().HasValue("MangaDex"); + }); + + modelBuilder.Entity("API.Schema.MangaContext.MetadataFetchers.MyAnimeList", b => + { + b.HasBaseType("API.Schema.MangaContext.MetadataFetchers.MetadataFetcher"); + + b.HasDiscriminator().HasValue("MyAnimeList"); + }); + + modelBuilder.Entity("API.Schema.MangaContext.Chapter", b => + { + b.HasOne("API.Schema.MangaContext.Manga", "ParentManga") + .WithMany("Chapters") + .HasForeignKey("ParentMangaId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ParentManga"); + }); + + modelBuilder.Entity("API.Schema.MangaContext.Manga", b => + { + b.HasOne("API.Schema.MangaContext.FileLibrary", "Library") + .WithMany() + .HasForeignKey("LibraryId") + .OnDelete(DeleteBehavior.SetNull); + + b.OwnsMany("API.Schema.MangaContext.AltTitle", "AltTitles", b1 => + { + b1.Property("Key") + .HasColumnType("text"); + + b1.Property("Language") + .IsRequired() + .HasMaxLength(8) + .HasColumnType("character varying(8)"); + + b1.Property("MangaKey") + .IsRequired() + .HasColumnType("text"); + + b1.Property("Title") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b1.HasKey("Key"); + + b1.HasIndex("MangaKey"); + + b1.ToTable("AltTitle"); + + b1.WithOwner() + .HasForeignKey("MangaKey"); + }); + + b.OwnsMany("API.Schema.MangaContext.Link", "Links", b1 => + { + b1.Property("Key") + .HasColumnType("text"); + + b1.Property("LinkProvider") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b1.Property("LinkUrl") + .IsRequired() + .HasMaxLength(2048) + .HasColumnType("character varying(2048)"); + + b1.Property("MangaKey") + .IsRequired() + .HasColumnType("text"); + + b1.HasKey("Key"); + + b1.HasIndex("MangaKey"); + + b1.ToTable("Link"); + + b1.WithOwner() + .HasForeignKey("MangaKey"); + }); + + b.Navigation("AltTitles"); + + b.Navigation("Library"); + + b.Navigation("Links"); + }); + + modelBuilder.Entity("API.Schema.MangaContext.MangaConnectorId", b => + { + b.HasOne("API.Schema.MangaContext.MangaConnectors.MangaConnector", "MangaConnector") + .WithMany() + .HasForeignKey("MangaConnectorName") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("API.Schema.MangaContext.Chapter", "Obj") + .WithMany("MangaConnectorIds") + .HasForeignKey("ObjId") + .OnDelete(DeleteBehavior.NoAction) + .IsRequired(); + + b.Navigation("MangaConnector"); + + b.Navigation("Obj"); + }); + + modelBuilder.Entity("API.Schema.MangaContext.MangaConnectorId", b => + { + b.HasOne("API.Schema.MangaContext.MangaConnectors.MangaConnector", "MangaConnector") + .WithMany() + .HasForeignKey("MangaConnectorName") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("API.Schema.MangaContext.Manga", "Obj") + .WithMany("MangaConnectorIds") + .HasForeignKey("ObjId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("MangaConnector"); + + b.Navigation("Obj"); + }); + + modelBuilder.Entity("API.Schema.MangaContext.MetadataFetchers.MetadataEntry", b => + { + b.HasOne("API.Schema.MangaContext.Manga", "Manga") + .WithMany() + .HasForeignKey("MangaId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("API.Schema.MangaContext.MetadataFetchers.MetadataFetcher", "MetadataFetcher") + .WithMany() + .HasForeignKey("MetadataFetcherName") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Manga"); + + b.Navigation("MetadataFetcher"); + }); + + modelBuilder.Entity("AuthorToManga", b => + { + b.HasOne("API.Schema.MangaContext.Author", null) + .WithMany() + .HasForeignKey("AuthorIds") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("API.Schema.MangaContext.Manga", null) + .WithMany() + .HasForeignKey("MangaIds") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("MangaTagToManga", b => + { + b.HasOne("API.Schema.MangaContext.Manga", null) + .WithMany() + .HasForeignKey("MangaIds") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("API.Schema.MangaContext.MangaTag", null) + .WithMany() + .HasForeignKey("MangaTagIds") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("API.Schema.MangaContext.Chapter", b => + { + b.Navigation("MangaConnectorIds"); + }); + + modelBuilder.Entity("API.Schema.MangaContext.Manga", b => + { + b.Navigation("Chapters"); + + b.Navigation("MangaConnectorIds"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/API/Migrations/Manga/20250703192023_Initial.cs b/API/Migrations/Manga/20250703192023_Initial.cs new file mode 100644 index 0000000..cb0a7e7 --- /dev/null +++ b/API/Migrations/Manga/20250703192023_Initial.cs @@ -0,0 +1,396 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace API.Migrations.Manga +{ + /// + public partial class Initial : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "Authors", + columns: table => new + { + Key = table.Column(type: "text", nullable: false), + AuthorName = table.Column(type: "character varying(128)", maxLength: 128, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Authors", x => x.Key); + }); + + migrationBuilder.CreateTable( + name: "FileLibraries", + columns: table => new + { + Key = table.Column(type: "text", nullable: false), + BasePath = table.Column(type: "character varying(256)", maxLength: 256, nullable: false), + LibraryName = table.Column(type: "character varying(512)", maxLength: 512, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_FileLibraries", x => x.Key); + }); + + migrationBuilder.CreateTable( + name: "MangaConnectors", + columns: table => new + { + Name = table.Column(type: "character varying(32)", maxLength: 32, nullable: false), + SupportedLanguages = table.Column(type: "text[]", maxLength: 8, nullable: false), + IconUrl = table.Column(type: "character varying(2048)", maxLength: 2048, nullable: false), + BaseUris = table.Column(type: "text[]", maxLength: 256, nullable: false), + Enabled = table.Column(type: "boolean", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_MangaConnectors", x => x.Name); + }); + + migrationBuilder.CreateTable( + name: "MetadataFetcher", + columns: table => new + { + Name = table.Column(type: "text", nullable: false), + MetadataEntry = table.Column(type: "character varying(21)", maxLength: 21, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_MetadataFetcher", x => x.Name); + }); + + migrationBuilder.CreateTable( + name: "Tags", + columns: table => new + { + Tag = table.Column(type: "character varying(64)", maxLength: 64, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Tags", x => x.Tag); + }); + + migrationBuilder.CreateTable( + name: "Mangas", + columns: table => new + { + Key = table.Column(type: "text", nullable: false), + Name = table.Column(type: "character varying(512)", maxLength: 512, nullable: false), + Description = table.Column(type: "text", nullable: false), + CoverUrl = table.Column(type: "character varying(512)", maxLength: 512, nullable: false), + ReleaseStatus = table.Column(type: "smallint", nullable: false), + LibraryId = table.Column(type: "character varying(64)", maxLength: 64, nullable: true), + IgnoreChaptersBefore = table.Column(type: "real", nullable: false), + DirectoryName = table.Column(type: "character varying(1024)", maxLength: 1024, nullable: false), + CoverFileNameInCache = table.Column(type: "character varying(512)", maxLength: 512, nullable: true), + Year = table.Column(type: "bigint", nullable: true), + OriginalLanguage = table.Column(type: "character varying(8)", maxLength: 8, nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_Mangas", x => x.Key); + table.ForeignKey( + name: "FK_Mangas_FileLibraries_LibraryId", + column: x => x.LibraryId, + principalTable: "FileLibraries", + principalColumn: "Key", + onDelete: ReferentialAction.SetNull); + }); + + migrationBuilder.CreateTable( + name: "AltTitle", + columns: table => new + { + Key = table.Column(type: "text", nullable: false), + Language = table.Column(type: "character varying(8)", maxLength: 8, nullable: false), + Title = table.Column(type: "character varying(256)", maxLength: 256, nullable: false), + MangaKey = table.Column(type: "text", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_AltTitle", x => x.Key); + table.ForeignKey( + name: "FK_AltTitle_Mangas_MangaKey", + column: x => x.MangaKey, + principalTable: "Mangas", + principalColumn: "Key", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "AuthorToManga", + columns: table => new + { + AuthorIds = table.Column(type: "text", nullable: false), + MangaIds = table.Column(type: "text", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_AuthorToManga", x => new { x.AuthorIds, x.MangaIds }); + table.ForeignKey( + name: "FK_AuthorToManga_Authors_AuthorIds", + column: x => x.AuthorIds, + principalTable: "Authors", + principalColumn: "Key", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_AuthorToManga_Mangas_MangaIds", + column: x => x.MangaIds, + principalTable: "Mangas", + principalColumn: "Key", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "Chapters", + columns: table => new + { + Key = table.Column(type: "text", nullable: false), + ParentMangaId = table.Column(type: "character varying(64)", maxLength: 64, nullable: false), + VolumeNumber = table.Column(type: "integer", nullable: true), + ChapterNumber = table.Column(type: "character varying(10)", maxLength: 10, nullable: false), + Title = table.Column(type: "character varying(256)", maxLength: 256, nullable: true), + FileName = table.Column(type: "character varying(256)", maxLength: 256, nullable: false), + Downloaded = table.Column(type: "boolean", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Chapters", x => x.Key); + table.ForeignKey( + name: "FK_Chapters_Mangas_ParentMangaId", + column: x => x.ParentMangaId, + principalTable: "Mangas", + principalColumn: "Key", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "Link", + columns: table => new + { + Key = table.Column(type: "text", nullable: false), + LinkProvider = table.Column(type: "character varying(64)", maxLength: 64, nullable: false), + LinkUrl = table.Column(type: "character varying(2048)", maxLength: 2048, nullable: false), + MangaKey = table.Column(type: "text", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Link", x => x.Key); + table.ForeignKey( + name: "FK_Link_Mangas_MangaKey", + column: x => x.MangaKey, + principalTable: "Mangas", + principalColumn: "Key", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "MangaConnectorToManga", + columns: table => new + { + Key = table.Column(type: "text", nullable: false), + ObjId = table.Column(type: "character varying(64)", maxLength: 64, nullable: false), + MangaConnectorName = table.Column(type: "character varying(32)", maxLength: 32, nullable: false), + IdOnConnectorSite = table.Column(type: "character varying(256)", maxLength: 256, nullable: false), + WebsiteUrl = table.Column(type: "character varying(512)", maxLength: 512, nullable: true), + UseForDownload = table.Column(type: "boolean", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_MangaConnectorToManga", x => x.Key); + table.ForeignKey( + name: "FK_MangaConnectorToManga_MangaConnectors_MangaConnectorName", + column: x => x.MangaConnectorName, + principalTable: "MangaConnectors", + principalColumn: "Name", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_MangaConnectorToManga_Mangas_ObjId", + column: x => x.ObjId, + principalTable: "Mangas", + principalColumn: "Key", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "MangaTagToManga", + columns: table => new + { + MangaTagIds = table.Column(type: "character varying(64)", nullable: false), + MangaIds = table.Column(type: "text", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_MangaTagToManga", x => new { x.MangaTagIds, x.MangaIds }); + table.ForeignKey( + name: "FK_MangaTagToManga_Mangas_MangaIds", + column: x => x.MangaIds, + principalTable: "Mangas", + principalColumn: "Key", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_MangaTagToManga_Tags_MangaTagIds", + column: x => x.MangaTagIds, + principalTable: "Tags", + principalColumn: "Tag", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "MetadataEntries", + columns: table => new + { + MetadataFetcherName = table.Column(type: "text", nullable: false), + Identifier = table.Column(type: "text", nullable: false), + MangaId = table.Column(type: "text", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_MetadataEntries", x => new { x.MetadataFetcherName, x.Identifier }); + table.ForeignKey( + name: "FK_MetadataEntries_Mangas_MangaId", + column: x => x.MangaId, + principalTable: "Mangas", + principalColumn: "Key", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_MetadataEntries_MetadataFetcher_MetadataFetcherName", + column: x => x.MetadataFetcherName, + principalTable: "MetadataFetcher", + principalColumn: "Name", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "MangaConnectorToChapter", + columns: table => new + { + Key = table.Column(type: "text", nullable: false), + ObjId = table.Column(type: "character varying(64)", maxLength: 64, nullable: false), + MangaConnectorName = table.Column(type: "character varying(32)", maxLength: 32, nullable: false), + IdOnConnectorSite = table.Column(type: "character varying(256)", maxLength: 256, nullable: false), + WebsiteUrl = table.Column(type: "character varying(512)", maxLength: 512, nullable: true), + UseForDownload = table.Column(type: "boolean", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_MangaConnectorToChapter", x => x.Key); + table.ForeignKey( + name: "FK_MangaConnectorToChapter_Chapters_ObjId", + column: x => x.ObjId, + principalTable: "Chapters", + principalColumn: "Key"); + table.ForeignKey( + name: "FK_MangaConnectorToChapter_MangaConnectors_MangaConnectorName", + column: x => x.MangaConnectorName, + principalTable: "MangaConnectors", + principalColumn: "Name", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex( + name: "IX_AltTitle_MangaKey", + table: "AltTitle", + column: "MangaKey"); + + migrationBuilder.CreateIndex( + name: "IX_AuthorToManga_MangaIds", + table: "AuthorToManga", + column: "MangaIds"); + + migrationBuilder.CreateIndex( + name: "IX_Chapters_ParentMangaId", + table: "Chapters", + column: "ParentMangaId"); + + migrationBuilder.CreateIndex( + name: "IX_Link_MangaKey", + table: "Link", + column: "MangaKey"); + + migrationBuilder.CreateIndex( + name: "IX_MangaConnectorToChapter_MangaConnectorName", + table: "MangaConnectorToChapter", + column: "MangaConnectorName"); + + migrationBuilder.CreateIndex( + name: "IX_MangaConnectorToChapter_ObjId", + table: "MangaConnectorToChapter", + column: "ObjId"); + + migrationBuilder.CreateIndex( + name: "IX_MangaConnectorToManga_MangaConnectorName", + table: "MangaConnectorToManga", + column: "MangaConnectorName"); + + migrationBuilder.CreateIndex( + name: "IX_MangaConnectorToManga_ObjId", + table: "MangaConnectorToManga", + column: "ObjId"); + + migrationBuilder.CreateIndex( + name: "IX_Mangas_LibraryId", + table: "Mangas", + column: "LibraryId"); + + migrationBuilder.CreateIndex( + name: "IX_MangaTagToManga_MangaIds", + table: "MangaTagToManga", + column: "MangaIds"); + + migrationBuilder.CreateIndex( + name: "IX_MetadataEntries_MangaId", + table: "MetadataEntries", + column: "MangaId"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "AltTitle"); + + migrationBuilder.DropTable( + name: "AuthorToManga"); + + migrationBuilder.DropTable( + name: "Link"); + + migrationBuilder.DropTable( + name: "MangaConnectorToChapter"); + + migrationBuilder.DropTable( + name: "MangaConnectorToManga"); + + migrationBuilder.DropTable( + name: "MangaTagToManga"); + + migrationBuilder.DropTable( + name: "MetadataEntries"); + + migrationBuilder.DropTable( + name: "Authors"); + + migrationBuilder.DropTable( + name: "Chapters"); + + migrationBuilder.DropTable( + name: "MangaConnectors"); + + migrationBuilder.DropTable( + name: "Tags"); + + migrationBuilder.DropTable( + name: "MetadataFetcher"); + + migrationBuilder.DropTable( + name: "Mangas"); + + migrationBuilder.DropTable( + name: "FileLibraries"); + } + } +} diff --git a/API/Migrations/Manga/MangaContextModelSnapshot.cs b/API/Migrations/Manga/MangaContextModelSnapshot.cs new file mode 100644 index 0000000..ddaad3b --- /dev/null +++ b/API/Migrations/Manga/MangaContextModelSnapshot.cs @@ -0,0 +1,544 @@ +// +using API.Schema.MangaContext; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace API.Migrations.Manga +{ + [DbContext(typeof(MangaContext))] + partial class MangaContextModelSnapshot : ModelSnapshot + { + protected override void BuildModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "9.0.5") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("API.Schema.MangaContext.Author", b => + { + b.Property("Key") + .HasColumnType("text"); + + b.Property("AuthorName") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("character varying(128)"); + + b.HasKey("Key"); + + b.ToTable("Authors"); + }); + + modelBuilder.Entity("API.Schema.MangaContext.Chapter", b => + { + b.Property("Key") + .HasColumnType("text"); + + b.Property("ChapterNumber") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("character varying(10)"); + + b.Property("Downloaded") + .HasColumnType("boolean"); + + b.Property("FileName") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("ParentMangaId") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("Title") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("VolumeNumber") + .HasColumnType("integer"); + + b.HasKey("Key"); + + b.HasIndex("ParentMangaId"); + + b.ToTable("Chapters"); + }); + + modelBuilder.Entity("API.Schema.MangaContext.FileLibrary", b => + { + b.Property("Key") + .HasColumnType("text"); + + b.Property("BasePath") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("LibraryName") + .IsRequired() + .HasMaxLength(512) + .HasColumnType("character varying(512)"); + + b.HasKey("Key"); + + b.ToTable("FileLibraries"); + }); + + modelBuilder.Entity("API.Schema.MangaContext.Manga", b => + { + b.Property("Key") + .HasColumnType("text"); + + b.Property("CoverFileNameInCache") + .HasMaxLength(512) + .HasColumnType("character varying(512)"); + + b.Property("CoverUrl") + .IsRequired() + .HasMaxLength(512) + .HasColumnType("character varying(512)"); + + b.Property("Description") + .IsRequired() + .HasColumnType("text"); + + b.Property("DirectoryName") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("character varying(1024)"); + + b.Property("IgnoreChaptersBefore") + .HasColumnType("real"); + + b.Property("LibraryId") + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(512) + .HasColumnType("character varying(512)"); + + b.Property("OriginalLanguage") + .HasMaxLength(8) + .HasColumnType("character varying(8)"); + + b.Property("ReleaseStatus") + .HasColumnType("smallint"); + + b.Property("Year") + .HasColumnType("bigint"); + + b.HasKey("Key"); + + b.HasIndex("LibraryId"); + + b.ToTable("Mangas"); + }); + + modelBuilder.Entity("API.Schema.MangaContext.MangaConnectorId", b => + { + b.Property("Key") + .HasColumnType("text"); + + b.Property("IdOnConnectorSite") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("MangaConnectorName") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("character varying(32)"); + + b.Property("ObjId") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("UseForDownload") + .HasColumnType("boolean"); + + b.Property("WebsiteUrl") + .HasMaxLength(512) + .HasColumnType("character varying(512)"); + + b.HasKey("Key"); + + b.HasIndex("MangaConnectorName"); + + b.HasIndex("ObjId"); + + b.ToTable("MangaConnectorToChapter"); + }); + + modelBuilder.Entity("API.Schema.MangaContext.MangaConnectorId", b => + { + b.Property("Key") + .HasColumnType("text"); + + b.Property("IdOnConnectorSite") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("MangaConnectorName") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("character varying(32)"); + + b.Property("ObjId") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("UseForDownload") + .HasColumnType("boolean"); + + b.Property("WebsiteUrl") + .HasMaxLength(512) + .HasColumnType("character varying(512)"); + + b.HasKey("Key"); + + b.HasIndex("MangaConnectorName"); + + b.HasIndex("ObjId"); + + b.ToTable("MangaConnectorToManga"); + }); + + modelBuilder.Entity("API.Schema.MangaContext.MangaConnectors.MangaConnector", b => + { + b.Property("Name") + .HasMaxLength(32) + .HasColumnType("character varying(32)"); + + b.PrimitiveCollection("BaseUris") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("text[]"); + + b.Property("Enabled") + .HasColumnType("boolean"); + + b.Property("IconUrl") + .IsRequired() + .HasMaxLength(2048) + .HasColumnType("character varying(2048)"); + + b.PrimitiveCollection("SupportedLanguages") + .IsRequired() + .HasMaxLength(8) + .HasColumnType("text[]"); + + b.HasKey("Name"); + + b.ToTable("MangaConnectors"); + + b.HasDiscriminator("Name").HasValue("MangaConnector"); + + b.UseTphMappingStrategy(); + }); + + modelBuilder.Entity("API.Schema.MangaContext.MangaTag", b => + { + b.Property("Tag") + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.HasKey("Tag"); + + b.ToTable("Tags"); + }); + + modelBuilder.Entity("API.Schema.MangaContext.MetadataFetchers.MetadataEntry", b => + { + b.Property("MetadataFetcherName") + .HasColumnType("text"); + + b.Property("Identifier") + .HasColumnType("text"); + + b.Property("MangaId") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("MetadataFetcherName", "Identifier"); + + b.HasIndex("MangaId"); + + b.ToTable("MetadataEntries"); + }); + + modelBuilder.Entity("API.Schema.MangaContext.MetadataFetchers.MetadataFetcher", b => + { + b.Property("Name") + .HasColumnType("text"); + + b.Property("MetadataEntry") + .IsRequired() + .HasMaxLength(21) + .HasColumnType("character varying(21)"); + + b.HasKey("Name"); + + b.ToTable("MetadataFetcher"); + + b.HasDiscriminator("MetadataEntry").HasValue("MetadataFetcher"); + + b.UseTphMappingStrategy(); + }); + + modelBuilder.Entity("AuthorToManga", b => + { + b.Property("AuthorIds") + .HasColumnType("text"); + + b.Property("MangaIds") + .HasColumnType("text"); + + b.HasKey("AuthorIds", "MangaIds"); + + b.HasIndex("MangaIds"); + + b.ToTable("AuthorToManga"); + }); + + modelBuilder.Entity("MangaTagToManga", b => + { + b.Property("MangaTagIds") + .HasColumnType("character varying(64)"); + + b.Property("MangaIds") + .HasColumnType("text"); + + b.HasKey("MangaTagIds", "MangaIds"); + + b.HasIndex("MangaIds"); + + b.ToTable("MangaTagToManga"); + }); + + modelBuilder.Entity("API.Schema.MangaContext.MangaConnectors.ComickIo", b => + { + b.HasBaseType("API.Schema.MangaContext.MangaConnectors.MangaConnector"); + + b.HasDiscriminator().HasValue("ComickIo"); + }); + + modelBuilder.Entity("API.Schema.MangaContext.MangaConnectors.Global", b => + { + b.HasBaseType("API.Schema.MangaContext.MangaConnectors.MangaConnector"); + + b.HasDiscriminator().HasValue("Global"); + }); + + modelBuilder.Entity("API.Schema.MangaContext.MangaConnectors.MangaDex", b => + { + b.HasBaseType("API.Schema.MangaContext.MangaConnectors.MangaConnector"); + + b.HasDiscriminator().HasValue("MangaDex"); + }); + + modelBuilder.Entity("API.Schema.MangaContext.MetadataFetchers.MyAnimeList", b => + { + b.HasBaseType("API.Schema.MangaContext.MetadataFetchers.MetadataFetcher"); + + b.HasDiscriminator().HasValue("MyAnimeList"); + }); + + modelBuilder.Entity("API.Schema.MangaContext.Chapter", b => + { + b.HasOne("API.Schema.MangaContext.Manga", "ParentManga") + .WithMany("Chapters") + .HasForeignKey("ParentMangaId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ParentManga"); + }); + + modelBuilder.Entity("API.Schema.MangaContext.Manga", b => + { + b.HasOne("API.Schema.MangaContext.FileLibrary", "Library") + .WithMany() + .HasForeignKey("LibraryId") + .OnDelete(DeleteBehavior.SetNull); + + b.OwnsMany("API.Schema.MangaContext.AltTitle", "AltTitles", b1 => + { + b1.Property("Key") + .HasColumnType("text"); + + b1.Property("Language") + .IsRequired() + .HasMaxLength(8) + .HasColumnType("character varying(8)"); + + b1.Property("MangaKey") + .IsRequired() + .HasColumnType("text"); + + b1.Property("Title") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b1.HasKey("Key"); + + b1.HasIndex("MangaKey"); + + b1.ToTable("AltTitle"); + + b1.WithOwner() + .HasForeignKey("MangaKey"); + }); + + b.OwnsMany("API.Schema.MangaContext.Link", "Links", b1 => + { + b1.Property("Key") + .HasColumnType("text"); + + b1.Property("LinkProvider") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b1.Property("LinkUrl") + .IsRequired() + .HasMaxLength(2048) + .HasColumnType("character varying(2048)"); + + b1.Property("MangaKey") + .IsRequired() + .HasColumnType("text"); + + b1.HasKey("Key"); + + b1.HasIndex("MangaKey"); + + b1.ToTable("Link"); + + b1.WithOwner() + .HasForeignKey("MangaKey"); + }); + + b.Navigation("AltTitles"); + + b.Navigation("Library"); + + b.Navigation("Links"); + }); + + modelBuilder.Entity("API.Schema.MangaContext.MangaConnectorId", b => + { + b.HasOne("API.Schema.MangaContext.MangaConnectors.MangaConnector", "MangaConnector") + .WithMany() + .HasForeignKey("MangaConnectorName") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("API.Schema.MangaContext.Chapter", "Obj") + .WithMany("MangaConnectorIds") + .HasForeignKey("ObjId") + .OnDelete(DeleteBehavior.NoAction) + .IsRequired(); + + b.Navigation("MangaConnector"); + + b.Navigation("Obj"); + }); + + modelBuilder.Entity("API.Schema.MangaContext.MangaConnectorId", b => + { + b.HasOne("API.Schema.MangaContext.MangaConnectors.MangaConnector", "MangaConnector") + .WithMany() + .HasForeignKey("MangaConnectorName") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("API.Schema.MangaContext.Manga", "Obj") + .WithMany("MangaConnectorIds") + .HasForeignKey("ObjId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("MangaConnector"); + + b.Navigation("Obj"); + }); + + modelBuilder.Entity("API.Schema.MangaContext.MetadataFetchers.MetadataEntry", b => + { + b.HasOne("API.Schema.MangaContext.Manga", "Manga") + .WithMany() + .HasForeignKey("MangaId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("API.Schema.MangaContext.MetadataFetchers.MetadataFetcher", "MetadataFetcher") + .WithMany() + .HasForeignKey("MetadataFetcherName") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Manga"); + + b.Navigation("MetadataFetcher"); + }); + + modelBuilder.Entity("AuthorToManga", b => + { + b.HasOne("API.Schema.MangaContext.Author", null) + .WithMany() + .HasForeignKey("AuthorIds") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("API.Schema.MangaContext.Manga", null) + .WithMany() + .HasForeignKey("MangaIds") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("MangaTagToManga", b => + { + b.HasOne("API.Schema.MangaContext.Manga", null) + .WithMany() + .HasForeignKey("MangaIds") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("API.Schema.MangaContext.MangaTag", null) + .WithMany() + .HasForeignKey("MangaTagIds") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("API.Schema.MangaContext.Chapter", b => + { + b.Navigation("MangaConnectorIds"); + }); + + modelBuilder.Entity("API.Schema.MangaContext.Manga", b => + { + b.Navigation("Chapters"); + + b.Navigation("MangaConnectorIds"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/API/Migrations/Notifications/20250703191820_Initial.Designer.cs b/API/Migrations/Notifications/20250703191820_Initial.Designer.cs new file mode 100644 index 0000000..92d67e4 --- /dev/null +++ b/API/Migrations/Notifications/20250703191820_Initial.Designer.cs @@ -0,0 +1,91 @@ +// +using System; +using System.Collections.Generic; +using API.Schema.NotificationsContext; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace API.Migrations.Notifications +{ + [DbContext(typeof(NotificationsContext))] + [Migration("20250703191820_Initial")] + partial class Initial + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "9.0.5") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.HasPostgresExtension(modelBuilder, "hstore"); + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("API.Schema.NotificationsContext.Notification", b => + { + b.Property("Key") + .HasColumnType("text"); + + b.Property("Date") + .HasColumnType("timestamp with time zone"); + + b.Property("IsSent") + .HasColumnType("boolean"); + + b.Property("Message") + .IsRequired() + .HasMaxLength(512) + .HasColumnType("character varying(512)"); + + b.Property("Title") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("character varying(128)"); + + b.Property("Urgency") + .HasColumnType("smallint"); + + b.HasKey("Key"); + + b.ToTable("Notifications"); + }); + + modelBuilder.Entity("API.Schema.NotificationsContext.NotificationConnectors.NotificationConnector", b => + { + b.Property("Name") + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("Body") + .IsRequired() + .HasMaxLength(4096) + .HasColumnType("character varying(4096)"); + + b.Property>("Headers") + .IsRequired() + .HasColumnType("hstore"); + + b.Property("HttpMethod") + .IsRequired() + .HasMaxLength(8) + .HasColumnType("character varying(8)"); + + b.Property("Url") + .IsRequired() + .HasMaxLength(2048) + .HasColumnType("character varying(2048)"); + + b.HasKey("Name"); + + b.ToTable("NotificationConnectors"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/API/Migrations/Notifications/20250703191820_Initial.cs b/API/Migrations/Notifications/20250703191820_Initial.cs new file mode 100644 index 0000000..8a80fc3 --- /dev/null +++ b/API/Migrations/Notifications/20250703191820_Initial.cs @@ -0,0 +1,60 @@ +using System; +using System.Collections.Generic; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace API.Migrations.Notifications +{ + /// + public partial class Initial : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AlterDatabase() + .Annotation("Npgsql:PostgresExtension:hstore", ",,"); + + migrationBuilder.CreateTable( + name: "NotificationConnectors", + columns: table => new + { + Name = table.Column(type: "character varying(64)", maxLength: 64, nullable: false), + Url = table.Column(type: "character varying(2048)", maxLength: 2048, nullable: false), + Headers = table.Column>(type: "hstore", nullable: false), + HttpMethod = table.Column(type: "character varying(8)", maxLength: 8, nullable: false), + Body = table.Column(type: "character varying(4096)", maxLength: 4096, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_NotificationConnectors", x => x.Name); + }); + + migrationBuilder.CreateTable( + name: "Notifications", + columns: table => new + { + Key = table.Column(type: "text", nullable: false), + Urgency = table.Column(type: "smallint", nullable: false), + Title = table.Column(type: "character varying(128)", maxLength: 128, nullable: false), + Message = table.Column(type: "character varying(512)", maxLength: 512, nullable: false), + Date = table.Column(type: "timestamp with time zone", nullable: false), + IsSent = table.Column(type: "boolean", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Notifications", x => x.Key); + }); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "NotificationConnectors"); + + migrationBuilder.DropTable( + name: "Notifications"); + } + } +} diff --git a/API/Migrations/Notifications/NotificationsContextModelSnapshot.cs b/API/Migrations/Notifications/NotificationsContextModelSnapshot.cs new file mode 100644 index 0000000..ad8af14 --- /dev/null +++ b/API/Migrations/Notifications/NotificationsContextModelSnapshot.cs @@ -0,0 +1,88 @@ +// +using System; +using System.Collections.Generic; +using API.Schema.NotificationsContext; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace API.Migrations.Notifications +{ + [DbContext(typeof(NotificationsContext))] + partial class NotificationsContextModelSnapshot : ModelSnapshot + { + protected override void BuildModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "9.0.5") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.HasPostgresExtension(modelBuilder, "hstore"); + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("API.Schema.NotificationsContext.Notification", b => + { + b.Property("Key") + .HasColumnType("text"); + + b.Property("Date") + .HasColumnType("timestamp with time zone"); + + b.Property("IsSent") + .HasColumnType("boolean"); + + b.Property("Message") + .IsRequired() + .HasMaxLength(512) + .HasColumnType("character varying(512)"); + + b.Property("Title") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("character varying(128)"); + + b.Property("Urgency") + .HasColumnType("smallint"); + + b.HasKey("Key"); + + b.ToTable("Notifications"); + }); + + modelBuilder.Entity("API.Schema.NotificationsContext.NotificationConnectors.NotificationConnector", b => + { + b.Property("Name") + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("Body") + .IsRequired() + .HasMaxLength(4096) + .HasColumnType("character varying(4096)"); + + b.Property>("Headers") + .IsRequired() + .HasColumnType("hstore"); + + b.Property("HttpMethod") + .IsRequired() + .HasMaxLength(8) + .HasColumnType("character varying(8)"); + + b.Property("Url") + .IsRequired() + .HasMaxLength(2048) + .HasColumnType("character varying(2048)"); + + b.HasKey("Name"); + + b.ToTable("NotificationConnectors"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/API/Program.cs b/API/Program.cs index dfc4c77..e76f53a 100644 --- a/API/Program.cs +++ b/API/Program.cs @@ -127,7 +127,8 @@ using (IServiceScope scope = app.Services.CreateScope()) { NotificationsContext context = scope.ServiceProvider.GetRequiredService(); context.Database.Migrate(); - + + context.Notifications.RemoveRange(context.Notifications); string[] emojis = { "(•‿•)", "(づ \u25d5‿\u25d5 )づ", "( \u02d8\u25bd\u02d8)っ\u2668", "=\uff3e\u25cf \u22cf \u25cf\uff3e=", "(ΦωΦ)", "(\u272a\u3268\u272a)", "( ノ・o・ )ノ", "(〜^\u2207^ )〜", "~(\u2267ω\u2266)~","૮ \u00b4• ﻌ \u00b4• ა", "(\u02c3ᆺ\u02c2)", "(=\ud83d\udf66 \u0f1d \ud83d\udf66=)"}; context.Notifications.Add(new Notification("Tranga Started", emojis[Random.Shared.Next(0, emojis.Length - 1)], NotificationUrgency.High)); @@ -142,7 +143,6 @@ using (IServiceScope scope = app.Services.CreateScope()) context.Sync(); } -TrangaSettings.Load(); Tranga.StartLogger(); Tranga.PeriodicWorkerStarterThread.Start(app.Services); diff --git a/API/Schema/LibraryContext/LibraryConnectors/LibraryConnector.cs b/API/Schema/LibraryContext/LibraryConnectors/LibraryConnector.cs index e58bba2..6b267cf 100644 --- a/API/Schema/LibraryContext/LibraryConnectors/LibraryConnector.cs +++ b/API/Schema/LibraryContext/LibraryConnectors/LibraryConnector.cs @@ -6,7 +6,7 @@ using Newtonsoft.Json; namespace API.Schema.LibraryContext.LibraryConnectors; -[PrimaryKey("LibraryConnectorId")] +[PrimaryKey("Key")] public abstract class LibraryConnector : Identifiable { [Required] diff --git a/API/Schema/MangaContext/MetadataFetchers/MetadataEntry.cs b/API/Schema/MangaContext/MetadataFetchers/MetadataEntry.cs index a239a08..ca0126d 100644 --- a/API/Schema/MangaContext/MetadataFetchers/MetadataEntry.cs +++ b/API/Schema/MangaContext/MetadataFetchers/MetadataEntry.cs @@ -3,7 +3,7 @@ using Newtonsoft.Json; namespace API.Schema.MangaContext.MetadataFetchers; -[PrimaryKey("Name", "Identifier")] +[PrimaryKey("MetadataFetcherName", "Identifier")] public class MetadataEntry { [JsonIgnore] diff --git a/API/Tranga.cs b/API/Tranga.cs index 1dea104..ab3fe72 100644 --- a/API/Tranga.cs +++ b/API/Tranga.cs @@ -1,11 +1,12 @@ using System.Diagnostics.CodeAnalysis; +using API.Schema.LibraryContext; using API.Schema.MangaContext; using API.Schema.MangaContext.MetadataFetchers; +using API.Schema.NotificationsContext; using API.Workers; using API.Workers.MaintenanceWorkers; using log4net; using log4net.Config; -using Microsoft.EntityFrameworkCore; namespace API; @@ -32,6 +33,7 @@ public static class Tranga internal static readonly CheckForNewChaptersWorker CheckForNewChaptersWorker = new(); internal static readonly CleanupMangaCoversWorker CleanupMangaCoversWorker = new(); internal static readonly StartNewChapterDownloadsWorker StartNewChapterDownloadsWorker = new(); + internal static readonly RemoveOldNotificationsWorker RemoveOldNotificationsWorker = new(); internal static void StartLogger() { @@ -45,6 +47,7 @@ public static class Tranga AddWorker(CheckForNewChaptersWorker); AddWorker(CleanupMangaCoversWorker); AddWorker(StartNewChapterDownloadsWorker); + AddWorker(RemoveOldNotificationsWorker); } internal static HashSet AllWorkers { get; private set; } = new (); @@ -90,9 +93,22 @@ public static class Tranga foreach (BaseWorker worker in StartWorkers) { - if (worker is BaseWorkerWithContext scopedWorker) - scopedWorker.SetScope(serviceProvider.CreateScope()); - RunningWorkers.Add(worker, worker.DoWork()); + if(RunningWorkers.ContainsKey(worker)) + continue; + if (worker is BaseWorkerWithContext mangaContextWorker) + { + mangaContextWorker.SetScope(serviceProvider.CreateScope()); + RunningWorkers.Add(mangaContextWorker, mangaContextWorker.DoWork()); + }else if (worker is BaseWorkerWithContext notificationContextWorker) + { + notificationContextWorker.SetScope(serviceProvider.CreateScope()); + RunningWorkers.Add(notificationContextWorker, notificationContextWorker.DoWork()); + }else if (worker is BaseWorkerWithContext libraryContextWorker) + { + libraryContextWorker.SetScope(serviceProvider.CreateScope()); + RunningWorkers.Add(libraryContextWorker, libraryContextWorker.DoWork()); + }else + RunningWorkers.Add(worker, worker.DoWork()); } Thread.Sleep(Settings.WorkCycleTimeoutMs); } diff --git a/API/TrangaSettings.cs b/API/TrangaSettings.cs index 60b57d3..ceb1299 100644 --- a/API/TrangaSettings.cs +++ b/API/TrangaSettings.cs @@ -16,10 +16,10 @@ public struct TrangaSettings() public string DownloadLocation => RuntimeInformation.IsOSPlatform(OSPlatform.Linux) ? "/Manga" : Path.Join(Directory.GetCurrentDirectory(), "Manga"); [JsonIgnore] internal static readonly string DefaultUserAgent = $"Tranga/2.0 ({Enum.GetName(Environment.OSVersion.Platform)}; {(Environment.Is64BitOperatingSystem ? "x64" : "")})"; - public string UserAgent { get; private set; } = DefaultUserAgent; - public int ImageCompression{ get; private set; } = 40; - public bool BlackWhiteImages { get; private set; } = false; - public string FlareSolverrUrl { get; private set; } = string.Empty; + public string UserAgent { get; set; } = DefaultUserAgent; + public int ImageCompression{ get; set; } = 40; + public bool BlackWhiteImages { get; set; } = false; + public string FlareSolverrUrl { get; set; } = string.Empty; /// /// Placeholders: /// %M Obj Name @@ -34,8 +34,8 @@ public struct TrangaSettings() /// ?_(...) replace _ with a value from above: /// Everything inside the braces will only be added if the value of %_ is not null /// - public string ChapterNamingScheme { get; private set; } = "%M - ?V(Vol.%V )Ch.%C?T( - %T)"; - public int WorkCycleTimeoutMs { get; private set; } = 20000; + public string ChapterNamingScheme { get; set; } = "%M - ?V(Vol.%V )Ch.%C?T( - %T)"; + public int WorkCycleTimeoutMs { get; set; } = 20000; [JsonIgnore] internal static readonly Dictionary DefaultRequestLimits = new () { @@ -46,12 +46,14 @@ public struct TrangaSettings() {RequestType.MangaCover, 60}, {RequestType.Default, 60} }; - public Dictionary RequestLimits { get; private set; } = DefaultRequestLimits; + public Dictionary RequestLimits { get; set; } = DefaultRequestLimits; - public string DownloadLanguage { get; private set; } = "en"; + public string DownloadLanguage { get; set; } = "en"; public static TrangaSettings Load() { + if (!File.Exists(settingsFilePath)) + new TrangaSettings().Save(); return JsonConvert.DeserializeObject(File.ReadAllText(settingsFilePath)); } diff --git a/API/Workers/BaseWorkerWithContext.cs b/API/Workers/BaseWorkerWithContext.cs index eba54e9..9dc233b 100644 --- a/API/Workers/BaseWorkerWithContext.cs +++ b/API/Workers/BaseWorkerWithContext.cs @@ -6,7 +6,13 @@ namespace API.Workers; public abstract class BaseWorkerWithContext(IEnumerable? dependsOn = null) : BaseWorker(dependsOn) where T : DbContext { protected T DbContext = null!; - public void SetScope(IServiceScope scope) => DbContext = scope.ServiceProvider.GetRequiredService(); + private IServiceScope? _scope; + + public void SetScope(IServiceScope scope) + { + this._scope = scope; + this.DbContext = scope.ServiceProvider.GetRequiredService(); + } /// Scope has not been set. public new Task DoWork() diff --git a/API/Workers/PeriodicWorkers/MaintenanceWorkers/CleanupMangaCoversWorker.cs b/API/Workers/PeriodicWorkers/MaintenanceWorkers/CleanupMangaCoversWorker.cs index d131f7c..a507fe4 100644 --- a/API/Workers/PeriodicWorkers/MaintenanceWorkers/CleanupMangaCoversWorker.cs +++ b/API/Workers/PeriodicWorkers/MaintenanceWorkers/CleanupMangaCoversWorker.cs @@ -2,7 +2,8 @@ using API.Schema.MangaContext; namespace API.Workers.MaintenanceWorkers; -public class CleanupMangaCoversWorker(TimeSpan? interval = null, IEnumerable? dependsOn = null) : BaseWorkerWithContext(dependsOn), IPeriodic +public class CleanupMangaCoversWorker(TimeSpan? interval = null, IEnumerable? dependsOn = null) + : BaseWorkerWithContext(dependsOn), IPeriodic { public DateTime LastExecution { get; set; } = DateTime.UnixEpoch; public TimeSpan Interval { get; set; } = interval ?? TimeSpan.FromHours(24); diff --git a/API/Workers/PeriodicWorkers/MaintenanceWorkers/RemoveOldNotificationsWorker.cs b/API/Workers/PeriodicWorkers/MaintenanceWorkers/RemoveOldNotificationsWorker.cs new file mode 100644 index 0000000..357fb3a --- /dev/null +++ b/API/Workers/PeriodicWorkers/MaintenanceWorkers/RemoveOldNotificationsWorker.cs @@ -0,0 +1,19 @@ +using API.Schema.NotificationsContext; + +namespace API.Workers.MaintenanceWorkers; + +public class RemoveOldNotificationsWorker(TimeSpan? interval = null, IEnumerable? dependsOn = null) + : BaseWorkerWithContext(dependsOn), IPeriodic +{ + public DateTime LastExecution { get; set; } = DateTime.UnixEpoch; + public TimeSpan Interval { get; set; } = interval ?? TimeSpan.FromHours(1); + + protected override BaseWorker[] DoWorkInternal() + { + IQueryable toRemove = DbContext.Notifications.Where(n => n.IsSent || DateTime.UtcNow - n.Date > Interval); + DbContext.Remove(toRemove); + DbContext.Sync(); + return []; + } + +} \ No newline at end of file