mirror of
https://github.com/C9Glax/tranga.git
synced 2025-07-06 10:54:17 +02:00
Compare commits
13 Commits
f36f34f212
...
Jikan
Author | SHA1 | Date | |
---|---|---|---|
7c9e0eddf9 | |||
ae0c6c8240 | |||
2eb0d941b2 | |||
8170e1d762 | |||
254383b006 | |||
df431e533a | |||
9a4cc0cbaf | |||
861cf7e166 | |||
7e34b3b91e | |||
29d36484f9 | |||
2c6e8e4d16 | |||
fab2886684 | |||
d9ccf71b21 |
@ -12,6 +12,7 @@
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Asp.Versioning.Mvc.ApiExplorer" Version="8.1.0" />
|
||||
<PackageReference Include="HtmlAgilityPack" Version="1.12.0" />
|
||||
<PackageReference Include="JikanDotNet" Version="2.9.1" />
|
||||
<PackageReference Include="log4net" Version="3.0.4" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="9.0.3" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="9.0.3" />
|
||||
|
@ -374,4 +374,25 @@ public class JobController(PgsqlContext context, ILog Log) : Controller
|
||||
{
|
||||
return StatusCode(Status501NotImplemented);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes failed and completed Jobs (that are not recurring)
|
||||
/// </summary>
|
||||
/// <response code="202">Job started</response>
|
||||
/// <response code="500">Error during Database Operation</response>
|
||||
[HttpPost("Cleanup")]
|
||||
public IActionResult CleanupJobs()
|
||||
{
|
||||
try
|
||||
{
|
||||
context.Jobs.RemoveRange(context.Jobs.Where(j => j.state == JobState.Failed || j.state == JobState.Completed));
|
||||
context.SaveChanges();
|
||||
return Ok();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e);
|
||||
return StatusCode(500, e.Message);
|
||||
}
|
||||
}
|
||||
}
|
166
API/Controllers/MetadataFetcherController.cs
Normal file
166
API/Controllers/MetadataFetcherController.cs
Normal file
@ -0,0 +1,166 @@
|
||||
using API.Schema;
|
||||
using API.Schema.Contexts;
|
||||
using API.Schema.MetadataFetchers;
|
||||
using Asp.Versioning;
|
||||
using log4net;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc.ModelBinding;
|
||||
using static Microsoft.AspNetCore.Http.StatusCodes;
|
||||
// ReSharper disable InconsistentNaming
|
||||
|
||||
namespace API.Controllers;
|
||||
|
||||
[ApiVersion(2)]
|
||||
[ApiController]
|
||||
[Route("v{v:apiVersion}/[controller]")]
|
||||
public class MetadataFetcherController(PgsqlContext context, ILog Log) : Controller
|
||||
{
|
||||
/// <summary>
|
||||
/// Get all available Connectors (Metadata-Sites)
|
||||
/// </summary>
|
||||
/// <response code="200">Names of Metadata-Fetchers</response>
|
||||
[HttpGet]
|
||||
[ProducesResponseType<string[]>(Status200OK, "application/json")]
|
||||
public IActionResult GetConnectors()
|
||||
{
|
||||
string[] connectors = Tranga.MetadataFetchers.Select(f => f.MetadataFetcherName).ToArray();
|
||||
return Ok(connectors);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns all Mangas which have a linked Metadata-Provider
|
||||
/// </summary>
|
||||
/// <response code="200"></response>
|
||||
[HttpGet("Links")]
|
||||
[ProducesResponseType<MetadataEntry>(Status200OK, "application/json")]
|
||||
public IActionResult GetLinkedEntries()
|
||||
{
|
||||
return Ok(context.MetadataEntries.ToArray());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Searches Metadata-Provider for Manga-Metadata
|
||||
/// </summary>
|
||||
/// <param name="searchTerm">Instead of using the Manga for search, use a specific term</param>
|
||||
/// <response code="200"></response>
|
||||
/// <response code="400">Metadata-fetcher with Name does not exist</response>
|
||||
/// <response code="404">Manga with ID not found</response>
|
||||
[HttpPost("{MetadataFetcherName}/SearchManga/{MangaId}")]
|
||||
[ProducesResponseType<MetadataSearchResult[]>(Status200OK, "application/json")]
|
||||
[ProducesResponseType(Status400BadRequest)]
|
||||
[ProducesResponseType(Status404NotFound)]
|
||||
public IActionResult SearchMangaMetadata(string MangaId, string MetadataFetcherName, [FromBody(EmptyBodyBehavior = EmptyBodyBehavior.Allow)]string? searchTerm = null)
|
||||
{
|
||||
if(context.Mangas.Find(MangaId) is not { } manga)
|
||||
return NotFound();
|
||||
if(Tranga.MetadataFetchers.FirstOrDefault(f => f.MetadataFetcherName == MetadataFetcherName) is not { } fetcher)
|
||||
return BadRequest();
|
||||
|
||||
MetadataSearchResult[] searchResults = searchTerm is null ? fetcher.SearchMetadataEntry(manga) : fetcher.SearchMetadataEntry(searchTerm);
|
||||
return Ok(searchResults);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Links Metadata-Provider using Provider-Specific Identifier to Manga
|
||||
/// </summary>
|
||||
/// <response code="200"></response>
|
||||
/// <response code="400">Metadata-fetcher with Name does not exist</response>
|
||||
/// <response code="404">Manga with ID not found</response>
|
||||
/// <response code="500">Error during Database Operation</response>
|
||||
[HttpPost("{MetadataFetcherName}/Link/{MangaId}")]
|
||||
[ProducesResponseType(Status200OK)]
|
||||
[ProducesResponseType(Status400BadRequest)]
|
||||
[ProducesResponseType(Status404NotFound)]
|
||||
[ProducesResponseType<string>(Status500InternalServerError, "text/plain")]
|
||||
public IActionResult LinkMangaMetadata(string MangaId, string MetadataFetcherName, [FromBody]string Identifier)
|
||||
{
|
||||
if(context.Mangas.Find(MangaId) is not { } manga)
|
||||
return NotFound();
|
||||
if(Tranga.MetadataFetchers.FirstOrDefault(f => f.MetadataFetcherName == MetadataFetcherName) is not { } fetcher)
|
||||
return BadRequest();
|
||||
MetadataEntry entry = fetcher.CreateMetadataEntry(manga, Identifier);
|
||||
|
||||
try
|
||||
{
|
||||
context.MetadataEntries.Add(entry);
|
||||
context.SaveChanges();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e);
|
||||
return StatusCode(500, e.Message);
|
||||
}
|
||||
return Ok();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Un-Links Metadata-Provider using Provider-Specific Identifier to Manga
|
||||
/// </summary>
|
||||
/// <response code="200"></response>
|
||||
/// <response code="400">Metadata-fetcher with Name does not exist</response>
|
||||
/// <response code="404">Manga with ID not found</response>
|
||||
/// <response code="412">No Entry linking Manga and Metadata-Provider found</response>
|
||||
/// <response code="500">Error during Database Operation</response>
|
||||
[HttpPost("{MetadataFetcherName}/Unlink/{MangaId}")]
|
||||
[ProducesResponseType(Status200OK)]
|
||||
[ProducesResponseType(Status400BadRequest)]
|
||||
[ProducesResponseType(Status404NotFound)]
|
||||
[ProducesResponseType<string>(Status412PreconditionFailed, "text/plain")]
|
||||
[ProducesResponseType<string>(Status500InternalServerError, "text/plain")]
|
||||
public IActionResult UnlinkMangaMetadata(string MangaId, string MetadataFetcherName)
|
||||
{
|
||||
if(context.Mangas.Find(MangaId) is not { } manga)
|
||||
return NotFound();
|
||||
if(Tranga.MetadataFetchers.FirstOrDefault(f => f.MetadataFetcherName == MetadataFetcherName) is not { } fetcher)
|
||||
return BadRequest();
|
||||
MetadataEntry? entry = context.MetadataEntries.FirstOrDefault(e => e.MangaId == MangaId && e.MetadataFetcherName == MetadataFetcherName);
|
||||
if (entry is null)
|
||||
return StatusCode(Status412PreconditionFailed, "No entry found");
|
||||
try
|
||||
{
|
||||
context.MetadataEntries.Remove(entry);
|
||||
context.SaveChanges();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e);
|
||||
return StatusCode(500, e.Message);
|
||||
}
|
||||
return Ok();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tries linking a Manga to a Metadata-Provider-Site
|
||||
/// </summary>
|
||||
/// <response code="200"></response>
|
||||
/// <response code="400">MetadataFetcher Name is invalid</response>
|
||||
/// <response code="404">Manga has no linked entry with MetadataFetcher</response>
|
||||
/// <response code="500">Error during Database Operation</response>
|
||||
[HttpPost("{MetadataFetcherName}/{MangaId}/UpdateMetadata")]
|
||||
[ProducesResponseType(Status200OK)]
|
||||
[ProducesResponseType(Status400BadRequest)]
|
||||
[ProducesResponseType(Status404NotFound)]
|
||||
[ProducesResponseType<string>(Status500InternalServerError, "text/plain")]
|
||||
public IActionResult UpdateMetadata(string MangaId, string MetadataFetcherName)
|
||||
{
|
||||
if(Tranga.MetadataFetchers
|
||||
.FirstOrDefault(f =>
|
||||
f.MetadataFetcherName.Equals(MetadataFetcherName, StringComparison.InvariantCultureIgnoreCase)) is not { } fetcher)
|
||||
return BadRequest();
|
||||
MetadataEntry? entry = context.MetadataEntries
|
||||
.FirstOrDefault(e =>
|
||||
e.MangaId == MangaId && e.MetadataFetcherName.Equals(MetadataFetcherName, StringComparison.InvariantCultureIgnoreCase));
|
||||
if (entry is null)
|
||||
return NotFound();
|
||||
try
|
||||
{
|
||||
fetcher.UpdateMetadata(entry, context);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e);
|
||||
return StatusCode(500, e.Message);
|
||||
}
|
||||
return Ok();
|
||||
}
|
||||
}
|
788
API/Migrations/pgsql/20250628204956_AddMAL.Designer.cs
generated
Normal file
788
API/Migrations/pgsql/20250628204956_AddMAL.Designer.cs
generated
Normal file
@ -0,0 +1,788 @@
|
||||
// <auto-generated />
|
||||
using System;
|
||||
using API.Schema.Contexts;
|
||||
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.pgsql
|
||||
{
|
||||
[DbContext(typeof(PgsqlContext))]
|
||||
[Migration("20250628204956_AddMAL")]
|
||||
partial class AddMAL
|
||||
{
|
||||
/// <inheritdoc />
|
||||
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.Author", b =>
|
||||
{
|
||||
b.Property<string>("AuthorId")
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("character varying(64)");
|
||||
|
||||
b.Property<string>("AuthorName")
|
||||
.IsRequired()
|
||||
.HasMaxLength(128)
|
||||
.HasColumnType("character varying(128)");
|
||||
|
||||
b.HasKey("AuthorId");
|
||||
|
||||
b.ToTable("Authors");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("API.Schema.Chapter", b =>
|
||||
{
|
||||
b.Property<string>("ChapterId")
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("character varying(64)");
|
||||
|
||||
b.Property<string>("ChapterNumber")
|
||||
.IsRequired()
|
||||
.HasMaxLength(10)
|
||||
.HasColumnType("character varying(10)");
|
||||
|
||||
b.Property<bool>("Downloaded")
|
||||
.HasColumnType("boolean");
|
||||
|
||||
b.Property<string>("FileName")
|
||||
.IsRequired()
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("character varying(256)");
|
||||
|
||||
b.Property<string>("IdOnConnectorSite")
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("character varying(256)");
|
||||
|
||||
b.Property<string>("ParentMangaId")
|
||||
.IsRequired()
|
||||
.HasColumnType("character varying(64)");
|
||||
|
||||
b.Property<string>("Title")
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("character varying(256)");
|
||||
|
||||
b.Property<string>("Url")
|
||||
.IsRequired()
|
||||
.HasMaxLength(2048)
|
||||
.HasColumnType("character varying(2048)");
|
||||
|
||||
b.Property<int?>("VolumeNumber")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.HasKey("ChapterId");
|
||||
|
||||
b.HasIndex("ParentMangaId");
|
||||
|
||||
b.ToTable("Chapters");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("API.Schema.Jobs.Job", b =>
|
||||
{
|
||||
b.Property<string>("JobId")
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("character varying(64)");
|
||||
|
||||
b.Property<bool>("Enabled")
|
||||
.HasColumnType("boolean");
|
||||
|
||||
b.Property<byte>("JobType")
|
||||
.HasColumnType("smallint");
|
||||
|
||||
b.Property<DateTime>("LastExecution")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.Property<string>("ParentJobId")
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("character varying(64)");
|
||||
|
||||
b.Property<decimal>("RecurrenceMs")
|
||||
.HasColumnType("numeric(20,0)");
|
||||
|
||||
b.Property<byte>("state")
|
||||
.HasColumnType("smallint");
|
||||
|
||||
b.HasKey("JobId");
|
||||
|
||||
b.HasIndex("ParentJobId");
|
||||
|
||||
b.ToTable("Jobs");
|
||||
|
||||
b.HasDiscriminator<byte>("JobType");
|
||||
|
||||
b.UseTphMappingStrategy();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("API.Schema.LocalLibrary", b =>
|
||||
{
|
||||
b.Property<string>("LocalLibraryId")
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("character varying(64)");
|
||||
|
||||
b.Property<string>("BasePath")
|
||||
.IsRequired()
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("character varying(256)");
|
||||
|
||||
b.Property<string>("LibraryName")
|
||||
.IsRequired()
|
||||
.HasMaxLength(512)
|
||||
.HasColumnType("character varying(512)");
|
||||
|
||||
b.HasKey("LocalLibraryId");
|
||||
|
||||
b.ToTable("LocalLibraries");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("API.Schema.Manga", b =>
|
||||
{
|
||||
b.Property<string>("MangaId")
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("character varying(64)");
|
||||
|
||||
b.Property<string>("CoverFileNameInCache")
|
||||
.HasMaxLength(512)
|
||||
.HasColumnType("character varying(512)");
|
||||
|
||||
b.Property<string>("CoverUrl")
|
||||
.IsRequired()
|
||||
.HasMaxLength(512)
|
||||
.HasColumnType("character varying(512)");
|
||||
|
||||
b.Property<string>("Description")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("DirectoryName")
|
||||
.IsRequired()
|
||||
.HasMaxLength(1024)
|
||||
.HasColumnType("character varying(1024)");
|
||||
|
||||
b.Property<string>("IdOnConnectorSite")
|
||||
.IsRequired()
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("character varying(256)");
|
||||
|
||||
b.Property<float>("IgnoreChaptersBefore")
|
||||
.HasColumnType("real");
|
||||
|
||||
b.Property<string>("LibraryId")
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("character varying(64)");
|
||||
|
||||
b.Property<string>("MangaConnectorName")
|
||||
.IsRequired()
|
||||
.HasMaxLength(32)
|
||||
.HasColumnType("character varying(32)");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasMaxLength(512)
|
||||
.HasColumnType("character varying(512)");
|
||||
|
||||
b.Property<string>("OriginalLanguage")
|
||||
.HasMaxLength(8)
|
||||
.HasColumnType("character varying(8)");
|
||||
|
||||
b.Property<byte>("ReleaseStatus")
|
||||
.HasColumnType("smallint");
|
||||
|
||||
b.Property<string>("WebsiteUrl")
|
||||
.IsRequired()
|
||||
.HasMaxLength(512)
|
||||
.HasColumnType("character varying(512)");
|
||||
|
||||
b.Property<long?>("Year")
|
||||
.HasColumnType("bigint");
|
||||
|
||||
b.HasKey("MangaId");
|
||||
|
||||
b.HasIndex("LibraryId");
|
||||
|
||||
b.HasIndex("MangaConnectorName");
|
||||
|
||||
b.ToTable("Mangas");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("API.Schema.MangaConnectors.MangaConnector", b =>
|
||||
{
|
||||
b.Property<string>("Name")
|
||||
.HasMaxLength(32)
|
||||
.HasColumnType("character varying(32)");
|
||||
|
||||
b.PrimitiveCollection<string[]>("BaseUris")
|
||||
.IsRequired()
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("text[]");
|
||||
|
||||
b.Property<bool>("Enabled")
|
||||
.HasColumnType("boolean");
|
||||
|
||||
b.Property<string>("IconUrl")
|
||||
.IsRequired()
|
||||
.HasMaxLength(2048)
|
||||
.HasColumnType("character varying(2048)");
|
||||
|
||||
b.PrimitiveCollection<string[]>("SupportedLanguages")
|
||||
.IsRequired()
|
||||
.HasMaxLength(8)
|
||||
.HasColumnType("text[]");
|
||||
|
||||
b.HasKey("Name");
|
||||
|
||||
b.ToTable("MangaConnectors");
|
||||
|
||||
b.HasDiscriminator<string>("Name").HasValue("MangaConnector");
|
||||
|
||||
b.UseTphMappingStrategy();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("API.Schema.MangaTag", b =>
|
||||
{
|
||||
b.Property<string>("Tag")
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("character varying(64)");
|
||||
|
||||
b.HasKey("Tag");
|
||||
|
||||
b.ToTable("Tags");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("API.Schema.MetadataFetchers.MetadataEntry", b =>
|
||||
{
|
||||
b.Property<string>("MangaId")
|
||||
.HasColumnType("character varying(64)");
|
||||
|
||||
b.Property<string>("MetadataFetcherName")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("Identifier")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.HasKey("MangaId", "MetadataFetcherName");
|
||||
|
||||
b.HasIndex("MetadataFetcherName");
|
||||
|
||||
b.ToTable("MetadataEntries");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("API.Schema.MetadataFetchers.MetadataFetcher", b =>
|
||||
{
|
||||
b.Property<string>("MetadataFetcherName")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("MetadataEntry")
|
||||
.IsRequired()
|
||||
.HasMaxLength(21)
|
||||
.HasColumnType("character varying(21)");
|
||||
|
||||
b.HasKey("MetadataFetcherName");
|
||||
|
||||
b.ToTable("MetadataFetcher");
|
||||
|
||||
b.HasDiscriminator<string>("MetadataEntry").HasValue("MetadataFetcher");
|
||||
|
||||
b.UseTphMappingStrategy();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("AuthorToManga", b =>
|
||||
{
|
||||
b.Property<string>("AuthorIds")
|
||||
.HasColumnType("character varying(64)");
|
||||
|
||||
b.Property<string>("MangaIds")
|
||||
.HasColumnType("character varying(64)");
|
||||
|
||||
b.HasKey("AuthorIds", "MangaIds");
|
||||
|
||||
b.HasIndex("MangaIds");
|
||||
|
||||
b.ToTable("AuthorToManga");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("JobJob", b =>
|
||||
{
|
||||
b.Property<string>("DependsOnJobsJobId")
|
||||
.HasColumnType("character varying(64)");
|
||||
|
||||
b.Property<string>("JobId")
|
||||
.HasColumnType("character varying(64)");
|
||||
|
||||
b.HasKey("DependsOnJobsJobId", "JobId");
|
||||
|
||||
b.HasIndex("JobId");
|
||||
|
||||
b.ToTable("JobJob");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("MangaTagToManga", b =>
|
||||
{
|
||||
b.Property<string>("MangaTagIds")
|
||||
.HasColumnType("character varying(64)");
|
||||
|
||||
b.Property<string>("MangaIds")
|
||||
.HasColumnType("character varying(64)");
|
||||
|
||||
b.HasKey("MangaTagIds", "MangaIds");
|
||||
|
||||
b.HasIndex("MangaIds");
|
||||
|
||||
b.ToTable("MangaTagToManga");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("API.Schema.Jobs.DownloadAvailableChaptersJob", b =>
|
||||
{
|
||||
b.HasBaseType("API.Schema.Jobs.Job");
|
||||
|
||||
b.Property<string>("MangaId")
|
||||
.IsRequired()
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("character varying(64)");
|
||||
|
||||
b.HasIndex("MangaId");
|
||||
|
||||
b.ToTable("Jobs", t =>
|
||||
{
|
||||
t.Property("MangaId")
|
||||
.HasColumnName("DownloadAvailableChaptersJob_MangaId");
|
||||
});
|
||||
|
||||
b.HasDiscriminator().HasValue((byte)1);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("API.Schema.Jobs.DownloadMangaCoverJob", b =>
|
||||
{
|
||||
b.HasBaseType("API.Schema.Jobs.Job");
|
||||
|
||||
b.Property<string>("MangaId")
|
||||
.IsRequired()
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("character varying(64)");
|
||||
|
||||
b.HasIndex("MangaId");
|
||||
|
||||
b.HasDiscriminator().HasValue((byte)4);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("API.Schema.Jobs.DownloadSingleChapterJob", b =>
|
||||
{
|
||||
b.HasBaseType("API.Schema.Jobs.Job");
|
||||
|
||||
b.Property<string>("ChapterId")
|
||||
.IsRequired()
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("character varying(64)");
|
||||
|
||||
b.HasIndex("ChapterId");
|
||||
|
||||
b.HasDiscriminator().HasValue((byte)0);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("API.Schema.Jobs.MoveFileOrFolderJob", b =>
|
||||
{
|
||||
b.HasBaseType("API.Schema.Jobs.Job");
|
||||
|
||||
b.Property<string>("FromLocation")
|
||||
.IsRequired()
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("character varying(256)");
|
||||
|
||||
b.Property<string>("ToLocation")
|
||||
.IsRequired()
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("character varying(256)");
|
||||
|
||||
b.HasDiscriminator().HasValue((byte)3);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("API.Schema.Jobs.MoveMangaLibraryJob", b =>
|
||||
{
|
||||
b.HasBaseType("API.Schema.Jobs.Job");
|
||||
|
||||
b.Property<string>("MangaId")
|
||||
.IsRequired()
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("character varying(64)");
|
||||
|
||||
b.Property<string>("ToLibraryId")
|
||||
.IsRequired()
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("character varying(64)");
|
||||
|
||||
b.HasIndex("MangaId");
|
||||
|
||||
b.HasIndex("ToLibraryId");
|
||||
|
||||
b.ToTable("Jobs", t =>
|
||||
{
|
||||
t.Property("MangaId")
|
||||
.HasColumnName("MoveMangaLibraryJob_MangaId");
|
||||
});
|
||||
|
||||
b.HasDiscriminator().HasValue((byte)7);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("API.Schema.Jobs.RetrieveChaptersJob", b =>
|
||||
{
|
||||
b.HasBaseType("API.Schema.Jobs.Job");
|
||||
|
||||
b.Property<string>("Language")
|
||||
.IsRequired()
|
||||
.HasMaxLength(8)
|
||||
.HasColumnType("character varying(8)");
|
||||
|
||||
b.Property<string>("MangaId")
|
||||
.IsRequired()
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("character varying(64)");
|
||||
|
||||
b.HasIndex("MangaId");
|
||||
|
||||
b.ToTable("Jobs", t =>
|
||||
{
|
||||
t.Property("MangaId")
|
||||
.HasColumnName("RetrieveChaptersJob_MangaId");
|
||||
});
|
||||
|
||||
b.HasDiscriminator().HasValue((byte)5);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("API.Schema.Jobs.UpdateChaptersDownloadedJob", b =>
|
||||
{
|
||||
b.HasBaseType("API.Schema.Jobs.Job");
|
||||
|
||||
b.Property<string>("MangaId")
|
||||
.IsRequired()
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("character varying(64)");
|
||||
|
||||
b.HasIndex("MangaId");
|
||||
|
||||
b.ToTable("Jobs", t =>
|
||||
{
|
||||
t.Property("MangaId")
|
||||
.HasColumnName("UpdateChaptersDownloadedJob_MangaId");
|
||||
});
|
||||
|
||||
b.HasDiscriminator().HasValue((byte)6);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("API.Schema.Jobs.UpdateCoverJob", b =>
|
||||
{
|
||||
b.HasBaseType("API.Schema.Jobs.Job");
|
||||
|
||||
b.Property<string>("MangaId")
|
||||
.IsRequired()
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("character varying(64)");
|
||||
|
||||
b.HasIndex("MangaId");
|
||||
|
||||
b.ToTable("Jobs", t =>
|
||||
{
|
||||
t.Property("MangaId")
|
||||
.HasColumnName("UpdateCoverJob_MangaId");
|
||||
});
|
||||
|
||||
b.HasDiscriminator().HasValue((byte)9);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("API.Schema.MangaConnectors.ComickIo", b =>
|
||||
{
|
||||
b.HasBaseType("API.Schema.MangaConnectors.MangaConnector");
|
||||
|
||||
b.HasDiscriminator().HasValue("ComickIo");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("API.Schema.MangaConnectors.Global", b =>
|
||||
{
|
||||
b.HasBaseType("API.Schema.MangaConnectors.MangaConnector");
|
||||
|
||||
b.HasDiscriminator().HasValue("Global");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("API.Schema.MangaConnectors.MangaDex", b =>
|
||||
{
|
||||
b.HasBaseType("API.Schema.MangaConnectors.MangaConnector");
|
||||
|
||||
b.HasDiscriminator().HasValue("MangaDex");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("API.Schema.MetadataFetchers.MyAnimeList", b =>
|
||||
{
|
||||
b.HasBaseType("API.Schema.MetadataFetchers.MetadataFetcher");
|
||||
|
||||
b.HasDiscriminator().HasValue("MyAnimeList");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("API.Schema.Chapter", b =>
|
||||
{
|
||||
b.HasOne("API.Schema.Manga", "ParentManga")
|
||||
.WithMany("Chapters")
|
||||
.HasForeignKey("ParentMangaId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("ParentManga");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("API.Schema.Jobs.Job", b =>
|
||||
{
|
||||
b.HasOne("API.Schema.Jobs.Job", "ParentJob")
|
||||
.WithMany()
|
||||
.HasForeignKey("ParentJobId")
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
|
||||
b.Navigation("ParentJob");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("API.Schema.Manga", b =>
|
||||
{
|
||||
b.HasOne("API.Schema.LocalLibrary", "Library")
|
||||
.WithMany()
|
||||
.HasForeignKey("LibraryId")
|
||||
.OnDelete(DeleteBehavior.SetNull);
|
||||
|
||||
b.HasOne("API.Schema.MangaConnectors.MangaConnector", "MangaConnector")
|
||||
.WithMany()
|
||||
.HasForeignKey("MangaConnectorName")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.OwnsMany("API.Schema.Link", "Links", b1 =>
|
||||
{
|
||||
b1.Property<string>("LinkId")
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("character varying(64)");
|
||||
|
||||
b1.Property<string>("LinkProvider")
|
||||
.IsRequired()
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("character varying(64)");
|
||||
|
||||
b1.Property<string>("LinkUrl")
|
||||
.IsRequired()
|
||||
.HasMaxLength(2048)
|
||||
.HasColumnType("character varying(2048)");
|
||||
|
||||
b1.Property<string>("MangaId")
|
||||
.IsRequired()
|
||||
.HasColumnType("character varying(64)");
|
||||
|
||||
b1.HasKey("LinkId");
|
||||
|
||||
b1.HasIndex("MangaId");
|
||||
|
||||
b1.ToTable("Link");
|
||||
|
||||
b1.WithOwner()
|
||||
.HasForeignKey("MangaId");
|
||||
});
|
||||
|
||||
b.OwnsMany("API.Schema.MangaAltTitle", "AltTitles", b1 =>
|
||||
{
|
||||
b1.Property<string>("AltTitleId")
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("character varying(64)");
|
||||
|
||||
b1.Property<string>("Language")
|
||||
.IsRequired()
|
||||
.HasMaxLength(8)
|
||||
.HasColumnType("character varying(8)");
|
||||
|
||||
b1.Property<string>("MangaId")
|
||||
.IsRequired()
|
||||
.HasColumnType("character varying(64)");
|
||||
|
||||
b1.Property<string>("Title")
|
||||
.IsRequired()
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("character varying(256)");
|
||||
|
||||
b1.HasKey("AltTitleId");
|
||||
|
||||
b1.HasIndex("MangaId");
|
||||
|
||||
b1.ToTable("MangaAltTitle");
|
||||
|
||||
b1.WithOwner()
|
||||
.HasForeignKey("MangaId");
|
||||
});
|
||||
|
||||
b.Navigation("AltTitles");
|
||||
|
||||
b.Navigation("Library");
|
||||
|
||||
b.Navigation("Links");
|
||||
|
||||
b.Navigation("MangaConnector");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("API.Schema.MetadataFetchers.MetadataEntry", b =>
|
||||
{
|
||||
b.HasOne("API.Schema.Manga", "Manga")
|
||||
.WithMany()
|
||||
.HasForeignKey("MangaId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("API.Schema.MetadataFetchers.MetadataFetcher", "MetadataFetcher")
|
||||
.WithMany()
|
||||
.HasForeignKey("MetadataFetcherName")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Manga");
|
||||
|
||||
b.Navigation("MetadataFetcher");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("AuthorToManga", b =>
|
||||
{
|
||||
b.HasOne("API.Schema.Author", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("AuthorIds")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("API.Schema.Manga", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("MangaIds")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("JobJob", b =>
|
||||
{
|
||||
b.HasOne("API.Schema.Jobs.Job", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("DependsOnJobsJobId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("API.Schema.Jobs.Job", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("JobId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("MangaTagToManga", b =>
|
||||
{
|
||||
b.HasOne("API.Schema.Manga", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("MangaIds")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("API.Schema.MangaTag", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("MangaTagIds")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("API.Schema.Jobs.DownloadAvailableChaptersJob", b =>
|
||||
{
|
||||
b.HasOne("API.Schema.Manga", "Manga")
|
||||
.WithMany()
|
||||
.HasForeignKey("MangaId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Manga");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("API.Schema.Jobs.DownloadMangaCoverJob", b =>
|
||||
{
|
||||
b.HasOne("API.Schema.Manga", "Manga")
|
||||
.WithMany()
|
||||
.HasForeignKey("MangaId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Manga");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("API.Schema.Jobs.DownloadSingleChapterJob", b =>
|
||||
{
|
||||
b.HasOne("API.Schema.Chapter", "Chapter")
|
||||
.WithMany()
|
||||
.HasForeignKey("ChapterId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Chapter");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("API.Schema.Jobs.MoveMangaLibraryJob", b =>
|
||||
{
|
||||
b.HasOne("API.Schema.Manga", "Manga")
|
||||
.WithMany()
|
||||
.HasForeignKey("MangaId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("API.Schema.LocalLibrary", "ToLibrary")
|
||||
.WithMany()
|
||||
.HasForeignKey("ToLibraryId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Manga");
|
||||
|
||||
b.Navigation("ToLibrary");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("API.Schema.Jobs.RetrieveChaptersJob", b =>
|
||||
{
|
||||
b.HasOne("API.Schema.Manga", "Manga")
|
||||
.WithMany()
|
||||
.HasForeignKey("MangaId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Manga");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("API.Schema.Jobs.UpdateChaptersDownloadedJob", b =>
|
||||
{
|
||||
b.HasOne("API.Schema.Manga", "Manga")
|
||||
.WithMany()
|
||||
.HasForeignKey("MangaId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Manga");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("API.Schema.Jobs.UpdateCoverJob", b =>
|
||||
{
|
||||
b.HasOne("API.Schema.Manga", "Manga")
|
||||
.WithMany()
|
||||
.HasForeignKey("MangaId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Manga");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("API.Schema.Manga", b =>
|
||||
{
|
||||
b.Navigation("Chapters");
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
}
|
66
API/Migrations/pgsql/20250628204956_AddMAL.cs
Normal file
66
API/Migrations/pgsql/20250628204956_AddMAL.cs
Normal file
@ -0,0 +1,66 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace API.Migrations.pgsql
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class AddMAL : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.CreateTable(
|
||||
name: "MetadataFetcher",
|
||||
columns: table => new
|
||||
{
|
||||
MetadataFetcherName = table.Column<string>(type: "text", nullable: false),
|
||||
MetadataEntry = table.Column<string>(type: "character varying(21)", maxLength: 21, nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_MetadataFetcher", x => x.MetadataFetcherName);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "MetadataEntries",
|
||||
columns: table => new
|
||||
{
|
||||
MangaId = table.Column<string>(type: "character varying(64)", nullable: false),
|
||||
MetadataFetcherName = table.Column<string>(type: "text", nullable: false),
|
||||
Identifier = table.Column<string>(type: "text", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_MetadataEntries", x => new { x.MangaId, x.MetadataFetcherName });
|
||||
table.ForeignKey(
|
||||
name: "FK_MetadataEntries_Mangas_MangaId",
|
||||
column: x => x.MangaId,
|
||||
principalTable: "Mangas",
|
||||
principalColumn: "MangaId",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
table.ForeignKey(
|
||||
name: "FK_MetadataEntries_MetadataFetcher_MetadataFetcherName",
|
||||
column: x => x.MetadataFetcherName,
|
||||
principalTable: "MetadataFetcher",
|
||||
principalColumn: "MetadataFetcherName",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_MetadataEntries_MetadataFetcherName",
|
||||
table: "MetadataEntries",
|
||||
column: "MetadataFetcherName");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropTable(
|
||||
name: "MetadataEntries");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "MetadataFetcher");
|
||||
}
|
||||
}
|
||||
}
|
788
API/Migrations/pgsql/20250629184056_MetadataEntry-PrimaryKeyChange.Designer.cs
generated
Normal file
788
API/Migrations/pgsql/20250629184056_MetadataEntry-PrimaryKeyChange.Designer.cs
generated
Normal file
@ -0,0 +1,788 @@
|
||||
// <auto-generated />
|
||||
using System;
|
||||
using API.Schema.Contexts;
|
||||
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.pgsql
|
||||
{
|
||||
[DbContext(typeof(PgsqlContext))]
|
||||
[Migration("20250629184056_MetadataEntry-PrimaryKeyChange")]
|
||||
partial class MetadataEntryPrimaryKeyChange
|
||||
{
|
||||
/// <inheritdoc />
|
||||
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.Author", b =>
|
||||
{
|
||||
b.Property<string>("AuthorId")
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("character varying(64)");
|
||||
|
||||
b.Property<string>("AuthorName")
|
||||
.IsRequired()
|
||||
.HasMaxLength(128)
|
||||
.HasColumnType("character varying(128)");
|
||||
|
||||
b.HasKey("AuthorId");
|
||||
|
||||
b.ToTable("Authors");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("API.Schema.Chapter", b =>
|
||||
{
|
||||
b.Property<string>("ChapterId")
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("character varying(64)");
|
||||
|
||||
b.Property<string>("ChapterNumber")
|
||||
.IsRequired()
|
||||
.HasMaxLength(10)
|
||||
.HasColumnType("character varying(10)");
|
||||
|
||||
b.Property<bool>("Downloaded")
|
||||
.HasColumnType("boolean");
|
||||
|
||||
b.Property<string>("FileName")
|
||||
.IsRequired()
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("character varying(256)");
|
||||
|
||||
b.Property<string>("IdOnConnectorSite")
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("character varying(256)");
|
||||
|
||||
b.Property<string>("ParentMangaId")
|
||||
.IsRequired()
|
||||
.HasColumnType("character varying(64)");
|
||||
|
||||
b.Property<string>("Title")
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("character varying(256)");
|
||||
|
||||
b.Property<string>("Url")
|
||||
.IsRequired()
|
||||
.HasMaxLength(2048)
|
||||
.HasColumnType("character varying(2048)");
|
||||
|
||||
b.Property<int?>("VolumeNumber")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.HasKey("ChapterId");
|
||||
|
||||
b.HasIndex("ParentMangaId");
|
||||
|
||||
b.ToTable("Chapters");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("API.Schema.Jobs.Job", b =>
|
||||
{
|
||||
b.Property<string>("JobId")
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("character varying(64)");
|
||||
|
||||
b.Property<bool>("Enabled")
|
||||
.HasColumnType("boolean");
|
||||
|
||||
b.Property<byte>("JobType")
|
||||
.HasColumnType("smallint");
|
||||
|
||||
b.Property<DateTime>("LastExecution")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.Property<string>("ParentJobId")
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("character varying(64)");
|
||||
|
||||
b.Property<decimal>("RecurrenceMs")
|
||||
.HasColumnType("numeric(20,0)");
|
||||
|
||||
b.Property<byte>("state")
|
||||
.HasColumnType("smallint");
|
||||
|
||||
b.HasKey("JobId");
|
||||
|
||||
b.HasIndex("ParentJobId");
|
||||
|
||||
b.ToTable("Jobs");
|
||||
|
||||
b.HasDiscriminator<byte>("JobType");
|
||||
|
||||
b.UseTphMappingStrategy();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("API.Schema.LocalLibrary", b =>
|
||||
{
|
||||
b.Property<string>("LocalLibraryId")
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("character varying(64)");
|
||||
|
||||
b.Property<string>("BasePath")
|
||||
.IsRequired()
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("character varying(256)");
|
||||
|
||||
b.Property<string>("LibraryName")
|
||||
.IsRequired()
|
||||
.HasMaxLength(512)
|
||||
.HasColumnType("character varying(512)");
|
||||
|
||||
b.HasKey("LocalLibraryId");
|
||||
|
||||
b.ToTable("LocalLibraries");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("API.Schema.Manga", b =>
|
||||
{
|
||||
b.Property<string>("MangaId")
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("character varying(64)");
|
||||
|
||||
b.Property<string>("CoverFileNameInCache")
|
||||
.HasMaxLength(512)
|
||||
.HasColumnType("character varying(512)");
|
||||
|
||||
b.Property<string>("CoverUrl")
|
||||
.IsRequired()
|
||||
.HasMaxLength(512)
|
||||
.HasColumnType("character varying(512)");
|
||||
|
||||
b.Property<string>("Description")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("DirectoryName")
|
||||
.IsRequired()
|
||||
.HasMaxLength(1024)
|
||||
.HasColumnType("character varying(1024)");
|
||||
|
||||
b.Property<string>("IdOnConnectorSite")
|
||||
.IsRequired()
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("character varying(256)");
|
||||
|
||||
b.Property<float>("IgnoreChaptersBefore")
|
||||
.HasColumnType("real");
|
||||
|
||||
b.Property<string>("LibraryId")
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("character varying(64)");
|
||||
|
||||
b.Property<string>("MangaConnectorName")
|
||||
.IsRequired()
|
||||
.HasMaxLength(32)
|
||||
.HasColumnType("character varying(32)");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasMaxLength(512)
|
||||
.HasColumnType("character varying(512)");
|
||||
|
||||
b.Property<string>("OriginalLanguage")
|
||||
.HasMaxLength(8)
|
||||
.HasColumnType("character varying(8)");
|
||||
|
||||
b.Property<byte>("ReleaseStatus")
|
||||
.HasColumnType("smallint");
|
||||
|
||||
b.Property<string>("WebsiteUrl")
|
||||
.IsRequired()
|
||||
.HasMaxLength(512)
|
||||
.HasColumnType("character varying(512)");
|
||||
|
||||
b.Property<long?>("Year")
|
||||
.HasColumnType("bigint");
|
||||
|
||||
b.HasKey("MangaId");
|
||||
|
||||
b.HasIndex("LibraryId");
|
||||
|
||||
b.HasIndex("MangaConnectorName");
|
||||
|
||||
b.ToTable("Mangas");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("API.Schema.MangaConnectors.MangaConnector", b =>
|
||||
{
|
||||
b.Property<string>("Name")
|
||||
.HasMaxLength(32)
|
||||
.HasColumnType("character varying(32)");
|
||||
|
||||
b.PrimitiveCollection<string[]>("BaseUris")
|
||||
.IsRequired()
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("text[]");
|
||||
|
||||
b.Property<bool>("Enabled")
|
||||
.HasColumnType("boolean");
|
||||
|
||||
b.Property<string>("IconUrl")
|
||||
.IsRequired()
|
||||
.HasMaxLength(2048)
|
||||
.HasColumnType("character varying(2048)");
|
||||
|
||||
b.PrimitiveCollection<string[]>("SupportedLanguages")
|
||||
.IsRequired()
|
||||
.HasMaxLength(8)
|
||||
.HasColumnType("text[]");
|
||||
|
||||
b.HasKey("Name");
|
||||
|
||||
b.ToTable("MangaConnectors");
|
||||
|
||||
b.HasDiscriminator<string>("Name").HasValue("MangaConnector");
|
||||
|
||||
b.UseTphMappingStrategy();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("API.Schema.MangaTag", b =>
|
||||
{
|
||||
b.Property<string>("Tag")
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("character varying(64)");
|
||||
|
||||
b.HasKey("Tag");
|
||||
|
||||
b.ToTable("Tags");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("API.Schema.MetadataFetchers.MetadataEntry", b =>
|
||||
{
|
||||
b.Property<string>("MetadataFetcherName")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("Identifier")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("MangaId")
|
||||
.IsRequired()
|
||||
.HasColumnType("character varying(64)");
|
||||
|
||||
b.HasKey("MetadataFetcherName", "Identifier");
|
||||
|
||||
b.HasIndex("MangaId");
|
||||
|
||||
b.ToTable("MetadataEntries");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("API.Schema.MetadataFetchers.MetadataFetcher", b =>
|
||||
{
|
||||
b.Property<string>("MetadataFetcherName")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("MetadataEntry")
|
||||
.IsRequired()
|
||||
.HasMaxLength(21)
|
||||
.HasColumnType("character varying(21)");
|
||||
|
||||
b.HasKey("MetadataFetcherName");
|
||||
|
||||
b.ToTable("MetadataFetcher");
|
||||
|
||||
b.HasDiscriminator<string>("MetadataEntry").HasValue("MetadataFetcher");
|
||||
|
||||
b.UseTphMappingStrategy();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("AuthorToManga", b =>
|
||||
{
|
||||
b.Property<string>("AuthorIds")
|
||||
.HasColumnType("character varying(64)");
|
||||
|
||||
b.Property<string>("MangaIds")
|
||||
.HasColumnType("character varying(64)");
|
||||
|
||||
b.HasKey("AuthorIds", "MangaIds");
|
||||
|
||||
b.HasIndex("MangaIds");
|
||||
|
||||
b.ToTable("AuthorToManga");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("JobJob", b =>
|
||||
{
|
||||
b.Property<string>("DependsOnJobsJobId")
|
||||
.HasColumnType("character varying(64)");
|
||||
|
||||
b.Property<string>("JobId")
|
||||
.HasColumnType("character varying(64)");
|
||||
|
||||
b.HasKey("DependsOnJobsJobId", "JobId");
|
||||
|
||||
b.HasIndex("JobId");
|
||||
|
||||
b.ToTable("JobJob");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("MangaTagToManga", b =>
|
||||
{
|
||||
b.Property<string>("MangaTagIds")
|
||||
.HasColumnType("character varying(64)");
|
||||
|
||||
b.Property<string>("MangaIds")
|
||||
.HasColumnType("character varying(64)");
|
||||
|
||||
b.HasKey("MangaTagIds", "MangaIds");
|
||||
|
||||
b.HasIndex("MangaIds");
|
||||
|
||||
b.ToTable("MangaTagToManga");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("API.Schema.Jobs.DownloadAvailableChaptersJob", b =>
|
||||
{
|
||||
b.HasBaseType("API.Schema.Jobs.Job");
|
||||
|
||||
b.Property<string>("MangaId")
|
||||
.IsRequired()
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("character varying(64)");
|
||||
|
||||
b.HasIndex("MangaId");
|
||||
|
||||
b.ToTable("Jobs", t =>
|
||||
{
|
||||
t.Property("MangaId")
|
||||
.HasColumnName("DownloadAvailableChaptersJob_MangaId");
|
||||
});
|
||||
|
||||
b.HasDiscriminator().HasValue((byte)1);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("API.Schema.Jobs.DownloadMangaCoverJob", b =>
|
||||
{
|
||||
b.HasBaseType("API.Schema.Jobs.Job");
|
||||
|
||||
b.Property<string>("MangaId")
|
||||
.IsRequired()
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("character varying(64)");
|
||||
|
||||
b.HasIndex("MangaId");
|
||||
|
||||
b.HasDiscriminator().HasValue((byte)4);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("API.Schema.Jobs.DownloadSingleChapterJob", b =>
|
||||
{
|
||||
b.HasBaseType("API.Schema.Jobs.Job");
|
||||
|
||||
b.Property<string>("ChapterId")
|
||||
.IsRequired()
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("character varying(64)");
|
||||
|
||||
b.HasIndex("ChapterId");
|
||||
|
||||
b.HasDiscriminator().HasValue((byte)0);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("API.Schema.Jobs.MoveFileOrFolderJob", b =>
|
||||
{
|
||||
b.HasBaseType("API.Schema.Jobs.Job");
|
||||
|
||||
b.Property<string>("FromLocation")
|
||||
.IsRequired()
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("character varying(256)");
|
||||
|
||||
b.Property<string>("ToLocation")
|
||||
.IsRequired()
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("character varying(256)");
|
||||
|
||||
b.HasDiscriminator().HasValue((byte)3);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("API.Schema.Jobs.MoveMangaLibraryJob", b =>
|
||||
{
|
||||
b.HasBaseType("API.Schema.Jobs.Job");
|
||||
|
||||
b.Property<string>("MangaId")
|
||||
.IsRequired()
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("character varying(64)");
|
||||
|
||||
b.Property<string>("ToLibraryId")
|
||||
.IsRequired()
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("character varying(64)");
|
||||
|
||||
b.HasIndex("MangaId");
|
||||
|
||||
b.HasIndex("ToLibraryId");
|
||||
|
||||
b.ToTable("Jobs", t =>
|
||||
{
|
||||
t.Property("MangaId")
|
||||
.HasColumnName("MoveMangaLibraryJob_MangaId");
|
||||
});
|
||||
|
||||
b.HasDiscriminator().HasValue((byte)7);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("API.Schema.Jobs.RetrieveChaptersJob", b =>
|
||||
{
|
||||
b.HasBaseType("API.Schema.Jobs.Job");
|
||||
|
||||
b.Property<string>("Language")
|
||||
.IsRequired()
|
||||
.HasMaxLength(8)
|
||||
.HasColumnType("character varying(8)");
|
||||
|
||||
b.Property<string>("MangaId")
|
||||
.IsRequired()
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("character varying(64)");
|
||||
|
||||
b.HasIndex("MangaId");
|
||||
|
||||
b.ToTable("Jobs", t =>
|
||||
{
|
||||
t.Property("MangaId")
|
||||
.HasColumnName("RetrieveChaptersJob_MangaId");
|
||||
});
|
||||
|
||||
b.HasDiscriminator().HasValue((byte)5);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("API.Schema.Jobs.UpdateChaptersDownloadedJob", b =>
|
||||
{
|
||||
b.HasBaseType("API.Schema.Jobs.Job");
|
||||
|
||||
b.Property<string>("MangaId")
|
||||
.IsRequired()
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("character varying(64)");
|
||||
|
||||
b.HasIndex("MangaId");
|
||||
|
||||
b.ToTable("Jobs", t =>
|
||||
{
|
||||
t.Property("MangaId")
|
||||
.HasColumnName("UpdateChaptersDownloadedJob_MangaId");
|
||||
});
|
||||
|
||||
b.HasDiscriminator().HasValue((byte)6);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("API.Schema.Jobs.UpdateCoverJob", b =>
|
||||
{
|
||||
b.HasBaseType("API.Schema.Jobs.Job");
|
||||
|
||||
b.Property<string>("MangaId")
|
||||
.IsRequired()
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("character varying(64)");
|
||||
|
||||
b.HasIndex("MangaId");
|
||||
|
||||
b.ToTable("Jobs", t =>
|
||||
{
|
||||
t.Property("MangaId")
|
||||
.HasColumnName("UpdateCoverJob_MangaId");
|
||||
});
|
||||
|
||||
b.HasDiscriminator().HasValue((byte)9);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("API.Schema.MangaConnectors.ComickIo", b =>
|
||||
{
|
||||
b.HasBaseType("API.Schema.MangaConnectors.MangaConnector");
|
||||
|
||||
b.HasDiscriminator().HasValue("ComickIo");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("API.Schema.MangaConnectors.Global", b =>
|
||||
{
|
||||
b.HasBaseType("API.Schema.MangaConnectors.MangaConnector");
|
||||
|
||||
b.HasDiscriminator().HasValue("Global");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("API.Schema.MangaConnectors.MangaDex", b =>
|
||||
{
|
||||
b.HasBaseType("API.Schema.MangaConnectors.MangaConnector");
|
||||
|
||||
b.HasDiscriminator().HasValue("MangaDex");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("API.Schema.MetadataFetchers.MyAnimeList", b =>
|
||||
{
|
||||
b.HasBaseType("API.Schema.MetadataFetchers.MetadataFetcher");
|
||||
|
||||
b.HasDiscriminator().HasValue("MyAnimeList");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("API.Schema.Chapter", b =>
|
||||
{
|
||||
b.HasOne("API.Schema.Manga", "ParentManga")
|
||||
.WithMany("Chapters")
|
||||
.HasForeignKey("ParentMangaId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("ParentManga");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("API.Schema.Jobs.Job", b =>
|
||||
{
|
||||
b.HasOne("API.Schema.Jobs.Job", "ParentJob")
|
||||
.WithMany()
|
||||
.HasForeignKey("ParentJobId")
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
|
||||
b.Navigation("ParentJob");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("API.Schema.Manga", b =>
|
||||
{
|
||||
b.HasOne("API.Schema.LocalLibrary", "Library")
|
||||
.WithMany()
|
||||
.HasForeignKey("LibraryId")
|
||||
.OnDelete(DeleteBehavior.SetNull);
|
||||
|
||||
b.HasOne("API.Schema.MangaConnectors.MangaConnector", "MangaConnector")
|
||||
.WithMany()
|
||||
.HasForeignKey("MangaConnectorName")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.OwnsMany("API.Schema.Link", "Links", b1 =>
|
||||
{
|
||||
b1.Property<string>("LinkId")
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("character varying(64)");
|
||||
|
||||
b1.Property<string>("LinkProvider")
|
||||
.IsRequired()
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("character varying(64)");
|
||||
|
||||
b1.Property<string>("LinkUrl")
|
||||
.IsRequired()
|
||||
.HasMaxLength(2048)
|
||||
.HasColumnType("character varying(2048)");
|
||||
|
||||
b1.Property<string>("MangaId")
|
||||
.IsRequired()
|
||||
.HasColumnType("character varying(64)");
|
||||
|
||||
b1.HasKey("LinkId");
|
||||
|
||||
b1.HasIndex("MangaId");
|
||||
|
||||
b1.ToTable("Link");
|
||||
|
||||
b1.WithOwner()
|
||||
.HasForeignKey("MangaId");
|
||||
});
|
||||
|
||||
b.OwnsMany("API.Schema.MangaAltTitle", "AltTitles", b1 =>
|
||||
{
|
||||
b1.Property<string>("AltTitleId")
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("character varying(64)");
|
||||
|
||||
b1.Property<string>("Language")
|
||||
.IsRequired()
|
||||
.HasMaxLength(8)
|
||||
.HasColumnType("character varying(8)");
|
||||
|
||||
b1.Property<string>("MangaId")
|
||||
.IsRequired()
|
||||
.HasColumnType("character varying(64)");
|
||||
|
||||
b1.Property<string>("Title")
|
||||
.IsRequired()
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("character varying(256)");
|
||||
|
||||
b1.HasKey("AltTitleId");
|
||||
|
||||
b1.HasIndex("MangaId");
|
||||
|
||||
b1.ToTable("MangaAltTitle");
|
||||
|
||||
b1.WithOwner()
|
||||
.HasForeignKey("MangaId");
|
||||
});
|
||||
|
||||
b.Navigation("AltTitles");
|
||||
|
||||
b.Navigation("Library");
|
||||
|
||||
b.Navigation("Links");
|
||||
|
||||
b.Navigation("MangaConnector");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("API.Schema.MetadataFetchers.MetadataEntry", b =>
|
||||
{
|
||||
b.HasOne("API.Schema.Manga", "Manga")
|
||||
.WithMany()
|
||||
.HasForeignKey("MangaId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("API.Schema.MetadataFetchers.MetadataFetcher", "MetadataFetcher")
|
||||
.WithMany()
|
||||
.HasForeignKey("MetadataFetcherName")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Manga");
|
||||
|
||||
b.Navigation("MetadataFetcher");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("AuthorToManga", b =>
|
||||
{
|
||||
b.HasOne("API.Schema.Author", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("AuthorIds")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("API.Schema.Manga", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("MangaIds")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("JobJob", b =>
|
||||
{
|
||||
b.HasOne("API.Schema.Jobs.Job", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("DependsOnJobsJobId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("API.Schema.Jobs.Job", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("JobId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("MangaTagToManga", b =>
|
||||
{
|
||||
b.HasOne("API.Schema.Manga", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("MangaIds")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("API.Schema.MangaTag", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("MangaTagIds")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("API.Schema.Jobs.DownloadAvailableChaptersJob", b =>
|
||||
{
|
||||
b.HasOne("API.Schema.Manga", "Manga")
|
||||
.WithMany()
|
||||
.HasForeignKey("MangaId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Manga");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("API.Schema.Jobs.DownloadMangaCoverJob", b =>
|
||||
{
|
||||
b.HasOne("API.Schema.Manga", "Manga")
|
||||
.WithMany()
|
||||
.HasForeignKey("MangaId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Manga");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("API.Schema.Jobs.DownloadSingleChapterJob", b =>
|
||||
{
|
||||
b.HasOne("API.Schema.Chapter", "Chapter")
|
||||
.WithMany()
|
||||
.HasForeignKey("ChapterId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Chapter");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("API.Schema.Jobs.MoveMangaLibraryJob", b =>
|
||||
{
|
||||
b.HasOne("API.Schema.Manga", "Manga")
|
||||
.WithMany()
|
||||
.HasForeignKey("MangaId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("API.Schema.LocalLibrary", "ToLibrary")
|
||||
.WithMany()
|
||||
.HasForeignKey("ToLibraryId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Manga");
|
||||
|
||||
b.Navigation("ToLibrary");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("API.Schema.Jobs.RetrieveChaptersJob", b =>
|
||||
{
|
||||
b.HasOne("API.Schema.Manga", "Manga")
|
||||
.WithMany()
|
||||
.HasForeignKey("MangaId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Manga");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("API.Schema.Jobs.UpdateChaptersDownloadedJob", b =>
|
||||
{
|
||||
b.HasOne("API.Schema.Manga", "Manga")
|
||||
.WithMany()
|
||||
.HasForeignKey("MangaId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Manga");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("API.Schema.Jobs.UpdateCoverJob", b =>
|
||||
{
|
||||
b.HasOne("API.Schema.Manga", "Manga")
|
||||
.WithMany()
|
||||
.HasForeignKey("MangaId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Manga");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("API.Schema.Manga", b =>
|
||||
{
|
||||
b.Navigation("Chapters");
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,54 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace API.Migrations.pgsql
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class MetadataEntryPrimaryKeyChange : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropPrimaryKey(
|
||||
name: "PK_MetadataEntries",
|
||||
table: "MetadataEntries");
|
||||
|
||||
migrationBuilder.DropIndex(
|
||||
name: "IX_MetadataEntries_MetadataFetcherName",
|
||||
table: "MetadataEntries");
|
||||
|
||||
migrationBuilder.AddPrimaryKey(
|
||||
name: "PK_MetadataEntries",
|
||||
table: "MetadataEntries",
|
||||
columns: new[] { "MetadataFetcherName", "Identifier" });
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_MetadataEntries_MangaId",
|
||||
table: "MetadataEntries",
|
||||
column: "MangaId");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropPrimaryKey(
|
||||
name: "PK_MetadataEntries",
|
||||
table: "MetadataEntries");
|
||||
|
||||
migrationBuilder.DropIndex(
|
||||
name: "IX_MetadataEntries_MangaId",
|
||||
table: "MetadataEntries");
|
||||
|
||||
migrationBuilder.AddPrimaryKey(
|
||||
name: "PK_MetadataEntries",
|
||||
table: "MetadataEntries",
|
||||
columns: new[] { "MangaId", "MetadataFetcherName" });
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_MetadataEntries_MetadataFetcherName",
|
||||
table: "MetadataEntries",
|
||||
column: "MetadataFetcherName");
|
||||
}
|
||||
}
|
||||
}
|
@ -255,6 +255,44 @@ namespace API.Migrations.pgsql
|
||||
b.ToTable("Tags");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("API.Schema.MetadataFetchers.MetadataEntry", b =>
|
||||
{
|
||||
b.Property<string>("MetadataFetcherName")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("Identifier")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("MangaId")
|
||||
.IsRequired()
|
||||
.HasColumnType("character varying(64)");
|
||||
|
||||
b.HasKey("MetadataFetcherName", "Identifier");
|
||||
|
||||
b.HasIndex("MangaId");
|
||||
|
||||
b.ToTable("MetadataEntries");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("API.Schema.MetadataFetchers.MetadataFetcher", b =>
|
||||
{
|
||||
b.Property<string>("MetadataFetcherName")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("MetadataEntry")
|
||||
.IsRequired()
|
||||
.HasMaxLength(21)
|
||||
.HasColumnType("character varying(21)");
|
||||
|
||||
b.HasKey("MetadataFetcherName");
|
||||
|
||||
b.ToTable("MetadataFetcher");
|
||||
|
||||
b.HasDiscriminator<string>("MetadataEntry").HasValue("MetadataFetcher");
|
||||
|
||||
b.UseTphMappingStrategy();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("AuthorToManga", b =>
|
||||
{
|
||||
b.Property<string>("AuthorIds")
|
||||
@ -478,6 +516,13 @@ namespace API.Migrations.pgsql
|
||||
b.HasDiscriminator().HasValue("MangaDex");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("API.Schema.MetadataFetchers.MyAnimeList", b =>
|
||||
{
|
||||
b.HasBaseType("API.Schema.MetadataFetchers.MetadataFetcher");
|
||||
|
||||
b.HasDiscriminator().HasValue("MyAnimeList");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("API.Schema.Chapter", b =>
|
||||
{
|
||||
b.HasOne("API.Schema.Manga", "ParentManga")
|
||||
@ -581,6 +626,25 @@ namespace API.Migrations.pgsql
|
||||
b.Navigation("MangaConnector");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("API.Schema.MetadataFetchers.MetadataEntry", b =>
|
||||
{
|
||||
b.HasOne("API.Schema.Manga", "Manga")
|
||||
.WithMany()
|
||||
.HasForeignKey("MangaId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("API.Schema.MetadataFetchers.MetadataFetcher", "MetadataFetcher")
|
||||
.WithMany()
|
||||
.HasForeignKey("MetadataFetcherName")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Manga");
|
||||
|
||||
b.Navigation("MetadataFetcher");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("AuthorToManga", b =>
|
||||
{
|
||||
b.HasOne("API.Schema.Author", null)
|
||||
|
@ -187,6 +187,8 @@ public class Chapter : IComparable<Chapter>
|
||||
comicInfo.Add(new XElement("Writer", string.Join(',', ParentManga.Authors.Select(author => author.AuthorName))));
|
||||
if(ParentManga.OriginalLanguage is not null)
|
||||
comicInfo.Add(new XElement("LanguageISO", ParentManga.OriginalLanguage));
|
||||
if(ParentManga.Description != string.Empty)
|
||||
comicInfo.Add(new XElement("Summary", ParentManga.Description));
|
||||
return comicInfo.ToString();
|
||||
}
|
||||
|
||||
|
@ -1,5 +1,6 @@
|
||||
using API.Schema.Jobs;
|
||||
using API.Schema.MangaConnectors;
|
||||
using API.Schema.MetadataFetchers;
|
||||
using log4net;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Diagnostics;
|
||||
@ -15,6 +16,7 @@ public class PgsqlContext(DbContextOptions<PgsqlContext> options) : DbContext(op
|
||||
public DbSet<Chapter> Chapters { get; set; }
|
||||
public DbSet<Author> Authors { get; set; }
|
||||
public DbSet<MangaTag> Tags { get; set; }
|
||||
public DbSet<MetadataEntry> MetadataEntries { get; set; }
|
||||
private ILog Log => LogManager.GetLogger(GetType());
|
||||
|
||||
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
|
||||
@ -191,5 +193,18 @@ public class PgsqlContext(DbContextOptions<PgsqlContext> options) : DbContext(op
|
||||
modelBuilder.Entity<Manga>()
|
||||
.Navigation(m => m.Library)
|
||||
.AutoInclude();
|
||||
|
||||
modelBuilder.Entity<MetadataFetcher>()
|
||||
.HasDiscriminator<string>(nameof(MetadataEntry))
|
||||
.HasValue<MyAnimeList>(nameof(MyAnimeList));
|
||||
//MetadataEntry
|
||||
modelBuilder.Entity<MetadataEntry>()
|
||||
.HasOne<Manga>(entry => entry.Manga)
|
||||
.WithMany()
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
modelBuilder.Entity<MetadataEntry>()
|
||||
.HasOne<MetadataFetcher>(entry => entry.MetadataFetcher)
|
||||
.WithMany()
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
}
|
||||
}
|
@ -45,6 +45,11 @@ public class DownloadSingleChapterJob : Job
|
||||
|
||||
protected override IEnumerable<Job> RunInternal(PgsqlContext context)
|
||||
{
|
||||
if (Chapter.Downloaded)
|
||||
{
|
||||
Log.Info("Chapter was already downloaded.");
|
||||
return [];
|
||||
}
|
||||
string[] imageUrls = Chapter.ParentManga.MangaConnector.GetChapterImageUrls(Chapter);
|
||||
if (imageUrls.Length < 1)
|
||||
{
|
||||
@ -129,20 +134,38 @@ public class DownloadSingleChapterJob : Job
|
||||
{
|
||||
if (!TrangaSettings.bwImages && TrangaSettings.compression == 100)
|
||||
{
|
||||
Log.Debug($"No processing requested for image");
|
||||
Log.Debug("No processing requested for image");
|
||||
return;
|
||||
}
|
||||
|
||||
Log.Debug($"Processing image: {imagePath}");
|
||||
|
||||
using Image image = Image.Load(imagePath);
|
||||
File.Delete(imagePath);
|
||||
if(TrangaSettings.bwImages)
|
||||
image.Mutate(i => i.ApplyProcessor(new AdaptiveThresholdProcessor()));
|
||||
image.SaveAsJpeg(imagePath, new JpegEncoder()
|
||||
|
||||
try
|
||||
{
|
||||
Quality = TrangaSettings.compression
|
||||
});
|
||||
using Image image = Image.Load(imagePath);
|
||||
if (TrangaSettings.bwImages)
|
||||
image.Mutate(i => i.ApplyProcessor(new AdaptiveThresholdProcessor()));
|
||||
File.Delete(imagePath);
|
||||
image.SaveAsJpeg(imagePath, new JpegEncoder()
|
||||
{
|
||||
Quality = TrangaSettings.compression
|
||||
});
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
if (e is UnknownImageFormatException or NotSupportedException)
|
||||
{
|
||||
//If the Image-Format is not processable by ImageSharp, we can't modify it.
|
||||
Log.Debug($"Unable to process {imagePath}: Not supported image format");
|
||||
}else if (e is InvalidImageContentException)
|
||||
{
|
||||
Log.Debug($"Unable to process {imagePath}: Invalid Content");
|
||||
}
|
||||
else
|
||||
{
|
||||
Log.Error(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void CopyCoverFromCacheToDownloadLocation(Manga manga)
|
||||
|
@ -78,7 +78,7 @@ public class ComickIo : MangaConnector
|
||||
public override Chapter[] GetChapters(Manga manga, string? language = null)
|
||||
{
|
||||
Log.Info($"Getting Chapters: {manga.IdOnConnectorSite}");
|
||||
List<string> chapterHids = new();
|
||||
List<Chapter> chapters = new();
|
||||
int page = 1;
|
||||
while(page < 50)
|
||||
{
|
||||
@ -95,16 +95,13 @@ public class ComickIo : MangaConnector
|
||||
JToken data = JToken.Parse(sr.ReadToEnd());
|
||||
JArray? chaptersArray = data["chapters"] as JArray;
|
||||
|
||||
if (chaptersArray?.Count < 1)
|
||||
if (chaptersArray is null || chaptersArray.Count < 1)
|
||||
break;
|
||||
|
||||
chapterHids.AddRange(chaptersArray?.Select(token => token.Value<string>("hid")!)!);
|
||||
chapters.AddRange(ParseChapters(manga, chaptersArray));
|
||||
|
||||
page++;
|
||||
}
|
||||
Log.Debug($"Getting chapters for {manga.Name} yielded {chapterHids.Count} hids. Requesting chapters now...");
|
||||
|
||||
List<Chapter> chapters = chapterHids.Select(hid => ChapterFromHid(manga, hid)).ToList();
|
||||
|
||||
return chapters.ToArray();
|
||||
}
|
||||
@ -219,29 +216,23 @@ public class ComickIo : MangaConnector
|
||||
year: year, originalLanguage: originalLanguage);
|
||||
}
|
||||
|
||||
private Chapter ChapterFromHid(Manga parentManga, string hid)
|
||||
private List<Chapter> ParseChapters(Manga parentManga, JArray chaptersArray)
|
||||
{
|
||||
string requestUrl = $"https://api.comick.fun/chapter/{hid}";
|
||||
RequestResult result = downloadClient.MakeRequest(requestUrl, RequestType.Default);
|
||||
if ((int)result.statusCode < 200 || (int)result.statusCode >= 300)
|
||||
List<Chapter> chapters = new ();
|
||||
foreach (JToken chapter in chaptersArray)
|
||||
{
|
||||
Log.Error("Request failed");
|
||||
throw new Exception("Request failed");
|
||||
string? chapterNum = chapter.Value<string>("chap");
|
||||
string? volumeNumStr = chapter.Value<string>("vol");
|
||||
int? volumeNum = volumeNumStr is null ? null : int.Parse(volumeNumStr);
|
||||
string? title = chapter.Value<string>("title");
|
||||
string? hid = chapter.Value<string>("hid");
|
||||
string url = $"https://comick.io/comic/{parentManga.IdOnConnectorSite}/{hid}";
|
||||
|
||||
if(chapterNum is null || hid is null)
|
||||
continue;
|
||||
|
||||
chapters.Add(new (parentManga, url, chapterNum, volumeNum, hid, title));
|
||||
}
|
||||
|
||||
using StreamReader sr = new (result.result);
|
||||
JToken data = JToken.Parse(sr.ReadToEnd());
|
||||
|
||||
string? canonical = data.Value<string>("canonical");
|
||||
string? chapterNum = data["chapter"]?.Value<string>("chap");
|
||||
string? volumeNumStr = data["chapter"]?.Value<string>("vol");
|
||||
int? volumeNum = volumeNumStr is null ? null : int.Parse(volumeNumStr);
|
||||
string? title = data["chapter"]?.Value<string>("title");
|
||||
|
||||
if(chapterNum is null)
|
||||
throw new Exception("chapterNum is null");
|
||||
|
||||
string url = $"https://comick.io{canonical}";
|
||||
return new Chapter(parentManga, url, chapterNum, volumeNum, hid, title);
|
||||
return chapters;
|
||||
}
|
||||
}
|
35
API/Schema/MetadataFetchers/MetadataEntry.cs
Normal file
35
API/Schema/MetadataFetchers/MetadataEntry.cs
Normal file
@ -0,0 +1,35 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace API.Schema.MetadataFetchers;
|
||||
|
||||
[PrimaryKey("MetadataFetcherName", "Identifier")]
|
||||
public class MetadataEntry
|
||||
{
|
||||
[JsonIgnore]
|
||||
public Manga Manga { get; init; } = null!;
|
||||
public string MangaId { get; init; }
|
||||
[JsonIgnore]
|
||||
public MetadataFetcher MetadataFetcher { get; init; } = null!;
|
||||
public string MetadataFetcherName { get; init; }
|
||||
public string Identifier { get; init; }
|
||||
|
||||
public MetadataEntry(MetadataFetcher fetcher, Manga manga, string identifier)
|
||||
{
|
||||
this.Manga = manga;
|
||||
this.MangaId = manga.MangaId;
|
||||
this.MetadataFetcher = fetcher;
|
||||
this.MetadataFetcherName = fetcher.MetadataFetcherName;
|
||||
this.Identifier = identifier;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// EFCORE only!!!!
|
||||
/// </summary>
|
||||
internal MetadataEntry(string mangaId, string identifier, string metadataFetcherName)
|
||||
{
|
||||
this.MangaId = mangaId;
|
||||
this.Identifier = identifier;
|
||||
this.MetadataFetcherName = metadataFetcherName;
|
||||
}
|
||||
}
|
37
API/Schema/MetadataFetchers/MetadataFetcher.cs
Normal file
37
API/Schema/MetadataFetchers/MetadataFetcher.cs
Normal file
@ -0,0 +1,37 @@
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using API.Schema.Contexts;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace API.Schema.MetadataFetchers;
|
||||
|
||||
[PrimaryKey("MetadataFetcherName")]
|
||||
public abstract class MetadataFetcher
|
||||
{
|
||||
// ReSharper disable once EntityFramework.ModelValidation.UnlimitedStringLength
|
||||
public string MetadataFetcherName { get; init; }
|
||||
|
||||
protected MetadataFetcher()
|
||||
{
|
||||
this.MetadataFetcherName = this.GetType().Name;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// EFCORE ONLY!!!
|
||||
/// </summary>
|
||||
internal MetadataFetcher(string metadataFetcherName)
|
||||
{
|
||||
this.MetadataFetcherName = metadataFetcherName;
|
||||
}
|
||||
|
||||
internal MetadataEntry CreateMetadataEntry(Manga manga, string identifier) =>
|
||||
new (this, manga, identifier);
|
||||
|
||||
public abstract MetadataSearchResult[] SearchMetadataEntry(Manga manga);
|
||||
|
||||
public abstract MetadataSearchResult[] SearchMetadataEntry(string searchTerm);
|
||||
|
||||
/// <summary>
|
||||
/// Updates the Manga linked in the MetadataEntry
|
||||
/// </summary>
|
||||
public abstract void UpdateMetadata(MetadataEntry metadataEntry, PgsqlContext dbContext);
|
||||
}
|
3
API/Schema/MetadataFetchers/MetadataSearchResult.cs
Normal file
3
API/Schema/MetadataFetchers/MetadataSearchResult.cs
Normal file
@ -0,0 +1,3 @@
|
||||
namespace API.Schema.MetadataFetchers;
|
||||
|
||||
public record MetadataSearchResult(string Identifier, string Name, string Url, string? Description = null, string? CoverUrl = null);
|
79
API/Schema/MetadataFetchers/MyAnimeList.cs
Normal file
79
API/Schema/MetadataFetchers/MyAnimeList.cs
Normal file
@ -0,0 +1,79 @@
|
||||
using System.Text.RegularExpressions;
|
||||
using API.Schema.Contexts;
|
||||
using JikanDotNet;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace API.Schema.MetadataFetchers;
|
||||
|
||||
public class MyAnimeList : MetadataFetcher
|
||||
{
|
||||
private static readonly Jikan Jikan = new ();
|
||||
private static readonly Regex GetIdFromUrl = new(@"https?:\/\/myanimelist\.net\/manga\/([0-9]+)\/?.*");
|
||||
|
||||
public override MetadataSearchResult[] SearchMetadataEntry(Manga manga)
|
||||
{
|
||||
if (manga.Links.Any(link => link.LinkProvider.Equals("MyAnimeList", StringComparison.InvariantCultureIgnoreCase)))
|
||||
{
|
||||
string url = manga.Links.First(link => link.LinkProvider.Equals("MyAnimeList", StringComparison.InvariantCultureIgnoreCase)).LinkUrl;
|
||||
Match m = GetIdFromUrl.Match(url);
|
||||
if (m.Success && m.Groups[1].Success)
|
||||
{
|
||||
long id = long.Parse(m.Groups[1].Value);
|
||||
JikanDotNet.Manga data = Jikan.GetMangaAsync(id).Result.Data;
|
||||
return [new MetadataSearchResult(id.ToString(), data.Titles.First().Title, data.Url, data.Synopsis)];
|
||||
}
|
||||
}
|
||||
|
||||
return SearchMetadataEntry(manga.Name);
|
||||
}
|
||||
|
||||
public override MetadataSearchResult[] SearchMetadataEntry(string searchTerm)
|
||||
{
|
||||
|
||||
ICollection<JikanDotNet.Manga> resultData = Jikan.SearchMangaAsync(searchTerm).Result.Data;
|
||||
if (resultData.Count < 1)
|
||||
return [];
|
||||
return resultData.Select(data =>
|
||||
new MetadataSearchResult(data.MalId.ToString(), data.Titles.First().Title, data.Url, data.Synopsis))
|
||||
.ToArray();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the Manga linked in the MetadataEntry
|
||||
/// </summary>
|
||||
/// <param name="metadataEntry"></param>
|
||||
/// <param name="dbContext"></param>
|
||||
/// <exception cref="FormatException"></exception>
|
||||
/// <exception cref="DbUpdateException"></exception>
|
||||
public override void UpdateMetadata(MetadataEntry metadataEntry, PgsqlContext dbContext)
|
||||
{
|
||||
Manga dbManga = dbContext.Mangas.Find(metadataEntry.MangaId)!;
|
||||
MangaFull resultData;
|
||||
try
|
||||
{
|
||||
long id = long.Parse(metadataEntry.Identifier);
|
||||
resultData = Jikan.GetMangaFullDataAsync(id).Result.Data;
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
throw new FormatException("ID was not in correct format");
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
dbManga.Name = resultData.Titles.First().Title;
|
||||
dbManga.Description = resultData.Synopsis;
|
||||
dbManga.AltTitles.Clear();
|
||||
dbManga.AltTitles = resultData.Titles.Select(t => new MangaAltTitle(t.Type, t.Title)).ToList();
|
||||
dbManga.Authors.Clear();
|
||||
dbManga.Authors = resultData.Authors.Select(a => new Author(a.Name)).ToList();
|
||||
|
||||
dbContext.SaveChanges();
|
||||
}
|
||||
catch (DbUpdateException e)
|
||||
{
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -2,6 +2,7 @@
|
||||
using API.Schema.Contexts;
|
||||
using API.Schema.Jobs;
|
||||
using API.Schema.MangaConnectors;
|
||||
using API.Schema.MetadataFetchers;
|
||||
using API.Schema.NotificationConnectors;
|
||||
using log4net;
|
||||
using log4net.Config;
|
||||
@ -24,6 +25,7 @@ public static class Tranga
|
||||
public static Thread NotificationSenderThread { get; } = new (NotificationSender);
|
||||
public static Thread JobStarterThread { get; } = new (JobStarter);
|
||||
private static readonly ILog Log = LogManager.GetLogger(typeof(Tranga));
|
||||
internal static MetadataFetcher[] MetadataFetchers = [new MyAnimeList()];
|
||||
|
||||
internal static void StartLogger()
|
||||
{
|
||||
@ -169,11 +171,15 @@ public static class Tranga
|
||||
while(!running)
|
||||
Thread.Sleep(10);
|
||||
}
|
||||
Log.Debug($"Running: {runningJobs.Count} Waiting: {waitingJobs.Count} Due: {dueJobs.Count} of which \n" +
|
||||
$"{jobsWithoutDependencies.Count} without missing dependencies, of which\n" +
|
||||
Log.Debug($"Running: {runningJobs.Count}\n" +
|
||||
$"{string.Join("\n", runningJobs.Select(s => "\t- " + s))}\n" +
|
||||
$"Waiting: {waitingJobs.Count} Due: {dueJobs.Count}\n" +
|
||||
$"{string.Join("\n", dueJobs.Select(s => "\t- " + s))}\n" +
|
||||
$"of which {jobsWithoutDependencies.Count} without missing dependencies, of which\n" +
|
||||
$"\t{jobsWithoutDownloading.Count} without downloading\n" +
|
||||
$"\t{jobsNotHeldBackByConnector.Count} not held back by Connector\n" +
|
||||
$"{startJobs.Count} were started.");
|
||||
$"{startJobs.Count} were started:\n" +
|
||||
$"{string.Join("\n", startJobs.Select(s => "\t- " + s))}");
|
||||
|
||||
if (Log.IsDebugEnabled && dueJobs.Count < 1)
|
||||
if(waitingJobs.MinBy(j => j.NextExecution) is { } nextJob)
|
||||
|
@ -36,7 +36,7 @@ public static class TrangaSettings
|
||||
[JsonIgnore]
|
||||
public static string coverImageCache => Path.Join(workingDirectory, "imageCache");
|
||||
public static bool aprilFoolsMode { get; private set; } = true;
|
||||
public static int startNewJobTimeoutMs { get; private set; } = 5000;
|
||||
public static int startNewJobTimeoutMs { get; private set; } = 20000;
|
||||
[JsonIgnore]
|
||||
internal static readonly Dictionary<RequestType, int> DefaultRequestLimits = new ()
|
||||
{
|
||||
|
@ -39,4 +39,4 @@ WORKDIR /publish
|
||||
COPY --chown=1000:1000 --from=build-env /publish .
|
||||
USER 0
|
||||
ENTRYPOINT ["dotnet", "/publish/API.dll"]
|
||||
CMD ["-f", "-c", "-l", "/usr/share/tranga-api/logs"]
|
||||
CMD [""]
|
20
README.md
20
README.md
@ -84,17 +84,18 @@ Endpoints are documented in Swagger. Just spin up an instance, and go to `http:/
|
||||
|
||||
## Built With
|
||||
|
||||
- .NET
|
||||
- ASP.NET
|
||||
- ASP.NET
|
||||
- Entity Framework Core
|
||||
- [PostgreSQL](https://www.postgresql.org/about/licence/)
|
||||
- [Swagger](https://github.com/domaindrivendev/Swashbuckle.AspNetCore/blob/master/LICENSE)
|
||||
- [Ngpsql](https://github.com/npgsql/npgsql/blob/main/LICENSE)
|
||||
- [Swagger](https://github.com/domaindrivendev/Swashbuckle.AspNetCore/blob/master/LICENSE)
|
||||
- [Newtonsoft.Json](https://github.com/JamesNK/Newtonsoft.Json/blob/master/LICENSE.md)
|
||||
- [Sixlabors.ImageSharp](https://docs-v2.sixlabors.com/articles/imagesharp/index.html#license)
|
||||
- [PuppeteerSharp](https://github.com/hardkoded/puppeteer-sharp/blob/master/LICENSE)
|
||||
- [Html Agility Pack (HAP)](https://github.com/zzzprojects/html-agility-pack/blob/master/LICENSE)
|
||||
- [Soenneker.Utils.String.NeedlemanWunsch](https://github.com/soenneker/soenneker.utils.string.needlemanwunsch/blob/main/LICENSE)
|
||||
- [Sixlabors.ImageSharp](https://docs-v2.sixlabors.com/articles/imagesharp/index.html#license)
|
||||
- [Jikan](https://jikan.moe/)
|
||||
- [Jikan.Net](https://github.com/Ervie/jikan.net)
|
||||
- 💙 Blåhaj 🦈
|
||||
|
||||
<p align="right">(<a href="#readme-top">back to top</a>)</p>
|
||||
@ -126,13 +127,13 @@ access the folder. Permission conflicts with Komga and Kavita should thus be lim
|
||||
|
||||
### Bare-Metal
|
||||
|
||||
While not supported/currently built, Tranga will also run Bare-Metal without issue.
|
||||
While not supported/currently built, Tranga should also run Bare-Metal without issue.
|
||||
|
||||
Configuration-Files will be stored per OS:
|
||||
- Linux `/usr/share/tranga-api`
|
||||
- Windows `%appdata%/tranga-api`
|
||||
|
||||
Downloads (default) are stored in - but this can be configured in `settings.json`:
|
||||
Downloads (default) are stored in - but this can be configured in `settings.json` (which will be generated on first after first launch):
|
||||
- Linux `/Manga`
|
||||
- Windows `%currentDirectory%/Downloads`
|
||||
|
||||
@ -148,9 +149,10 @@ If you want to contribute, please feel free to fork and create a Pull-Request!
|
||||
General rules:
|
||||
- Strongly-type your variables. This improves readability.
|
||||
```csharp
|
||||
var xyz = Object.GetSomething(); //Do not do this. What type is xyz?
|
||||
var xyz = Object.GetSomething(); //Do not do this. What type is xyz (without looking at Method returns etc.)?
|
||||
Manga[] zyx = Object.GetAnotherThing(); //I can now easily see that zyx is an Array.
|
||||
```
|
||||
Tranga is using a code-first Entity-Framework Core approach. If you modify the db-table structure you need to create a migration.
|
||||
|
||||
**A broad overview of where is what:**<br />
|
||||
|
||||
@ -171,6 +173,10 @@ If you want to add a new Website-Connector: <br />
|
||||
in the constructor).
|
||||
4. In `Program.cs` add a new Object to the Array.
|
||||
|
||||
### How to test locally
|
||||
|
||||
In the Project root a `docker-compose.local.yaml` file will compile the code and create the container(s).
|
||||
|
||||
<!-- LICENSE -->
|
||||
## License
|
||||
|
||||
|
@ -2,6 +2,7 @@
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=altnames/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=authorsartists/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=Gotify/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=jikan/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=jjob/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=Komga/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=lunasea/@EntryIndexedValue">True</s:Boolean>
|
||||
|
@ -16,6 +16,11 @@ services:
|
||||
environment:
|
||||
- POSTGRES_HOST=tranga-pg
|
||||
restart: unless-stopped
|
||||
logging:
|
||||
driver: json-file
|
||||
options:
|
||||
max-size: "10m"
|
||||
max-file: "5"
|
||||
tranga-pg:
|
||||
image: postgres:latest
|
||||
container_name: tranga-pg
|
||||
@ -30,3 +35,8 @@ services:
|
||||
retries: 5
|
||||
start_period: 80s
|
||||
restart: unless-stopped
|
||||
logging:
|
||||
driver: json-file
|
||||
options:
|
||||
max-size: "10m"
|
||||
max-file: "5"
|
||||
|
@ -1,7 +1,7 @@
|
||||
version: '3'
|
||||
services:
|
||||
tranga-api:
|
||||
image: glax/tranga-api:latest
|
||||
image: glax/tranga-api:Server-V2
|
||||
container_name: tranga-api
|
||||
volumes:
|
||||
- ./Manga:/Manga
|
||||
@ -14,14 +14,24 @@ services:
|
||||
environment:
|
||||
- POSTGRES_HOST=tranga-pg
|
||||
restart: unless-stopped
|
||||
logging:
|
||||
driver: json-file
|
||||
options:
|
||||
max-size: "10m"
|
||||
max-file: "5"
|
||||
tranga-website:
|
||||
image: glax/tranga-website:latest
|
||||
image: glax/tranga-website:Server-V2
|
||||
container_name: tranga-website
|
||||
ports:
|
||||
- "9555:80"
|
||||
depends_on:
|
||||
- tranga-api
|
||||
restart: unless-stopped
|
||||
logging:
|
||||
driver: json-file
|
||||
options:
|
||||
max-size: "10m"
|
||||
max-file: "5"
|
||||
tranga-pg:
|
||||
image: postgres:latest
|
||||
container_name: tranga-pg
|
||||
@ -36,3 +46,8 @@ services:
|
||||
retries: 5
|
||||
start_period: 80s
|
||||
restart: unless-stopped
|
||||
logging:
|
||||
driver: json-file
|
||||
options:
|
||||
max-size: "10m"
|
||||
max-file: "5"
|
||||
|
Reference in New Issue
Block a user