diff --git a/API/Controllers/MetadataFetcherController.cs b/API/Controllers/MetadataFetcherController.cs index 6d6d943..342b5c2 100644 --- a/API/Controllers/MetadataFetcherController.cs +++ b/API/Controllers/MetadataFetcherController.cs @@ -97,7 +97,7 @@ public class MetadataFetcherController(MangaContext context) : Controller [ProducesResponseType(Status400BadRequest)] [ProducesResponseType(Status404NotFound, "text/plain")] [ProducesResponseType(Status500InternalServerError, "text/plain")] - public async Task, BadRequest, NotFound, InternalServerError>> LinkMangaMetadata (string MangaId, string MetadataFetcherName, [FromBody]string Identifier) + public async Task, InternalServerError>> LinkMangaMetadata (string MangaId, string MetadataFetcherName, [FromBody]string Identifier) { if (await context.Mangas.FirstOrDefaultAsync(m => m.Key == MangaId, HttpContext.RequestAborted) is not { } manga) return TypedResults.NotFound(nameof(MangaId)); @@ -107,9 +107,9 @@ public class MetadataFetcherController(MangaContext context) : Controller MetadataEntry entry = fetcher.CreateMetadataEntry(manga, Identifier); context.MetadataEntries.Add(entry); - if(await context.Sync(HttpContext.RequestAborted, GetType(), System.Reflection.MethodBase.GetCurrentMethod()?.Name) is { success: false } result) + if(await context.Sync(HttpContext.RequestAborted, GetType(), "Link Metadatafetcher") is { success: false } result) return TypedResults.InternalServerError(result.exceptionMessage); - return TypedResults.Ok(entry); + return TypedResults.Ok(); } /// @@ -136,7 +136,7 @@ public class MetadataFetcherController(MangaContext context) : Controller .ExecuteDeleteAsync(HttpContext.RequestAborted) < 1) return TypedResults.StatusCode(Status412PreconditionFailed); - if(await context.Sync(HttpContext.RequestAborted, GetType(), System.Reflection.MethodBase.GetCurrentMethod()?.Name) is { success: false } result) + if(await context.Sync(HttpContext.RequestAborted, GetType(), "Unlink Metadatafetcher") is { success: false } result) return TypedResults.InternalServerError(result.exceptionMessage); return TypedResults.Ok(); } diff --git a/API/Migrations/Manga/20251014114546_MetadataFetcherNotDBEntity.Designer.cs b/API/Migrations/Manga/20251014114546_MetadataFetcherNotDBEntity.Designer.cs new file mode 100644 index 0000000..a21c508 --- /dev/null +++ b/API/Migrations/Manga/20251014114546_MetadataFetcherNotDBEntity.Designer.cs @@ -0,0 +1,533 @@ +// +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("20251014114546_MetadataFetcherNotDBEntity")] + partial class MetadataFetcherNotDBEntity + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "9.0.9") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("API.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("MangaConnector"); + + b.HasDiscriminator("Name").HasValue("MangaConnector"); + + b.UseTphMappingStrategy(); + }); + + modelBuilder.Entity("API.Schema.MangaContext.Author", b => + { + b.Property("Key") + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + 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") + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("ChapterNumber") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("character varying(10)"); + + b.Property("Downloaded") + .HasColumnType("boolean"); + + b.Property("FileName") + .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") + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + 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") + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + 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") + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + 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("ObjId"); + + b.ToTable("MangaConnectorToChapter"); + }); + + modelBuilder.Entity("API.Schema.MangaContext.MangaConnectorId", b => + { + b.Property("Key") + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + 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("ObjId"); + + b.ToTable("MangaConnectorToManga"); + }); + + 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("character varying(64)"); + + 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("character varying(64)"); + + b.Property("MangaIds") + .HasColumnType("character varying(64)"); + + 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("character varying(64)"); + + b.HasKey("MangaTagIds", "MangaIds"); + + b.HasIndex("MangaIds"); + + b.ToTable("MangaTagToManga"); + }); + + modelBuilder.Entity("API.MangaConnectors.Global", b => + { + b.HasBaseType("API.MangaConnectors.MangaConnector"); + + b.HasDiscriminator().HasValue("Global"); + }); + + modelBuilder.Entity("API.MangaConnectors.MangaDex", b => + { + b.HasBaseType("API.MangaConnectors.MangaConnector"); + + b.HasDiscriminator().HasValue("MangaDex"); + }); + + modelBuilder.Entity("API.MangaConnectors.MangaPark", b => + { + b.HasBaseType("API.MangaConnectors.MangaConnector"); + + b.HasDiscriminator().HasValue("MangaPark"); + }); + + modelBuilder.Entity("API.MangaConnectors.Mangaworld", b => + { + b.HasBaseType("API.MangaConnectors.MangaConnector"); + + b.HasDiscriminator().HasValue("Mangaworld"); + }); + + 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") + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b1.Property("Language") + .IsRequired() + .HasMaxLength(8) + .HasColumnType("character varying(8)"); + + b1.Property("MangaKey") + .IsRequired() + .HasColumnType("character varying(64)"); + + 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") + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + 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("character varying(64)"); + + 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.Chapter", "Obj") + .WithMany("MangaConnectorIds") + .HasForeignKey("ObjId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Obj"); + }); + + modelBuilder.Entity("API.Schema.MangaContext.MangaConnectorId", b => + { + b.HasOne("API.Schema.MangaContext.Manga", "Obj") + .WithMany("MangaConnectorIds") + .HasForeignKey("ObjId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + 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.Navigation("Manga"); + }); + + 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/20251014114546_MetadataFetcherNotDBEntity.cs b/API/Migrations/Manga/20251014114546_MetadataFetcherNotDBEntity.cs new file mode 100644 index 0000000..4c3a361 --- /dev/null +++ b/API/Migrations/Manga/20251014114546_MetadataFetcherNotDBEntity.cs @@ -0,0 +1,30 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace API.Migrations.Manga +{ + /// + public partial class MetadataFetcherNotDBEntity : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropForeignKey( + name: "FK_MetadataEntries_MetadataFetcher_MetadataFetcherName", + table: "MetadataEntries"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddForeignKey( + name: "FK_MetadataEntries_MetadataFetcher_MetadataFetcherName", + table: "MetadataEntries", + column: "MetadataFetcherName", + principalTable: "MetadataFetcher", + principalColumn: "Name", + onDelete: ReferentialAction.Cascade); + } + } +} diff --git a/API/Migrations/Manga/MangaContextModelSnapshot.cs b/API/Migrations/Manga/MangaContextModelSnapshot.cs index ec904a7..2b89918 100644 --- a/API/Migrations/Manga/MangaContextModelSnapshot.cs +++ b/API/Migrations/Manga/MangaContextModelSnapshot.cs @@ -480,15 +480,7 @@ namespace API.Migrations.Manga .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 => diff --git a/API/Schema/MangaContext/Manga.cs b/API/Schema/MangaContext/Manga.cs index 6cf9197..b16ecb5 100644 --- a/API/Schema/MangaContext/Manga.cs +++ b/API/Schema/MangaContext/Manga.cs @@ -18,7 +18,6 @@ public class Manga : Identifiable public MangaReleaseStatus ReleaseStatus { get; internal set; } [StringLength(64)] public string? LibraryId { get; private set; } public FileLibrary? Library = null!; - public ICollection Authors { get; internal set; } = null!; public ICollection MangaTags { get; internal set; } = null!; public ICollection Links { get; internal set; } = null!; @@ -31,13 +30,20 @@ public class Manga : Identifiable /// Library not loaded - [NotMapped] public string FullDirectoryPath => EnsureDirectoryExists(); + [NotMapped] + [JsonIgnore] + public string FullDirectoryPath => EnsureDirectoryExists(); - [NotMapped] public ICollection ChapterIds => Chapters.Select(c => c.Key).ToList(); + [NotMapped] + public ICollection ChapterIds => Chapters.Select(c => c.Key).ToList(); + [JsonIgnore] public ICollection Chapters = null!; - [NotMapped] public Dictionary IdsOnMangaConnectors => MangaConnectorIds.ToDictionary(id => id.MangaConnectorName, id => id.IdOnConnectorSite); - [NotMapped] public ICollection MangaConnectorIdsIds => MangaConnectorIds.Select(id => id.Key).ToList(); + [NotMapped] + public Dictionary IdsOnMangaConnectors => MangaConnectorIds.ToDictionary(id => id.MangaConnectorName, id => id.IdOnConnectorSite); + [NotMapped] + public ICollection MangaConnectorIdsIds => MangaConnectorIds.Select(id => id.Key).ToList(); + [JsonIgnore] public ICollection> MangaConnectorIds = null!; public Manga(string name, string description, string coverUrl, MangaReleaseStatus releaseStatus, diff --git a/API/Schema/MangaContext/MangaContext.cs b/API/Schema/MangaContext/MangaContext.cs index 61f3cb8..07a48c5 100644 --- a/API/Schema/MangaContext/MangaContext.cs +++ b/API/Schema/MangaContext/MangaContext.cs @@ -99,10 +99,6 @@ public class MangaContext(DbContextOptions options) : TrangaBaseCo .HasOne(entry => entry.Manga) .WithMany() .OnDelete(DeleteBehavior.Cascade); - modelBuilder.Entity() - .HasOne(entry => entry.MetadataFetcher) - .WithMany() - .OnDelete(DeleteBehavior.Cascade); } public async Task FindMangaLike(Manga other, CancellationToken token) diff --git a/API/Schema/MangaContext/MetadataFetchers/MetadataEntry.cs b/API/Schema/MangaContext/MetadataFetchers/MetadataEntry.cs index d6390fa..9a97c05 100644 --- a/API/Schema/MangaContext/MetadataFetchers/MetadataEntry.cs +++ b/API/Schema/MangaContext/MetadataFetchers/MetadataEntry.cs @@ -1,3 +1,4 @@ +using System.ComponentModel.DataAnnotations.Schema; using Microsoft.EntityFrameworkCore; using Newtonsoft.Json; @@ -10,6 +11,7 @@ public class MetadataEntry public Manga Manga { get; init; } = null!; public string MangaId { get; init; } [JsonIgnore] + [NotMapped] public MetadataFetcher MetadataFetcher { get; init; } = null!; public string MetadataFetcherName { get; init; } public string Identifier { get; init; }