mirror of
https://github.com/C9Glax/tranga.git
synced 2025-05-22 06:03:01 +02:00
MangaConnectors do not have to return an Object with 6 Parameters.
Job-Start Logic readable and optimized More robust Database design
This commit is contained in:
parent
7477f4d04d
commit
7d4a6be569
@ -0,0 +1,5 @@
|
|||||||
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
|
||||||
|
namespace API.APIEndpointRecords;
|
||||||
|
|
||||||
|
public record DownloadAvailableChaptersJobRecord([Required]string language, [Required]ulong recurrenceTimeMs, [Required]string localLibraryId);
|
@ -1,5 +0,0 @@
|
|||||||
using System.ComponentModel.DataAnnotations;
|
|
||||||
|
|
||||||
namespace API.APIEndpointRecords;
|
|
||||||
|
|
||||||
public record DownloadAvailableJobsRecord([Required]ulong recurrenceTimeMs, [Required]string localLibraryId);
|
|
@ -2,15 +2,17 @@
|
|||||||
using API.Schema;
|
using API.Schema;
|
||||||
using API.Schema.Jobs;
|
using API.Schema.Jobs;
|
||||||
using Asp.Versioning;
|
using Asp.Versioning;
|
||||||
|
using log4net;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using static Microsoft.AspNetCore.Http.StatusCodes;
|
using static Microsoft.AspNetCore.Http.StatusCodes;
|
||||||
|
// ReSharper disable InconsistentNaming
|
||||||
|
|
||||||
namespace API.Controllers;
|
namespace API.Controllers;
|
||||||
|
|
||||||
[ApiVersion(2)]
|
[ApiVersion(2)]
|
||||||
[ApiController]
|
[ApiController]
|
||||||
[Route("v{version:apiVersion}/[controller]")]
|
[Route("v{version:apiVersion}/[controller]")]
|
||||||
public class JobController(PgsqlContext context) : Controller
|
public class JobController(PgsqlContext context, ILog Log) : Controller
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Returns all Jobs
|
/// Returns all Jobs
|
||||||
@ -102,7 +104,7 @@ public class JobController(PgsqlContext context) : Controller
|
|||||||
/// <param name="MangaId">ID of Manga</param>
|
/// <param name="MangaId">ID of Manga</param>
|
||||||
/// <param name="record">Job-Configuration</param>
|
/// <param name="record">Job-Configuration</param>
|
||||||
/// <response code="201">Job-IDs</response>
|
/// <response code="201">Job-IDs</response>
|
||||||
/// <response code="400">Could not find Library with ID</response>
|
/// <response code="400">Could not find ToLibrary with ID</response>
|
||||||
/// <response code="404">Could not find Manga with ID</response>
|
/// <response code="404">Could not find Manga with ID</response>
|
||||||
/// <response code="500">Error during Database Operation</response>
|
/// <response code="500">Error during Database Operation</response>
|
||||||
[HttpPut("DownloadAvailableChaptersJob/{MangaId}")]
|
[HttpPut("DownloadAvailableChaptersJob/{MangaId}")]
|
||||||
@ -110,7 +112,7 @@ public class JobController(PgsqlContext context) : Controller
|
|||||||
[ProducesResponseType(Status400BadRequest)]
|
[ProducesResponseType(Status400BadRequest)]
|
||||||
[ProducesResponseType(Status404NotFound)]
|
[ProducesResponseType(Status404NotFound)]
|
||||||
[ProducesResponseType<string>(Status500InternalServerError, "text/plain")]
|
[ProducesResponseType<string>(Status500InternalServerError, "text/plain")]
|
||||||
public IActionResult CreateDownloadAvailableChaptersJob(string MangaId, [FromBody]DownloadAvailableJobsRecord record)
|
public IActionResult CreateDownloadAvailableChaptersJob(string MangaId, [FromBody]DownloadAvailableChaptersJobRecord record)
|
||||||
{
|
{
|
||||||
if (context.Mangas.Find(MangaId) is not { } m)
|
if (context.Mangas.Find(MangaId) is not { } m)
|
||||||
return NotFound();
|
return NotFound();
|
||||||
@ -126,13 +128,13 @@ public class JobController(PgsqlContext context) : Controller
|
|||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
|
Log.Error(e);
|
||||||
return StatusCode(500, e.Message);
|
return StatusCode(500, e.Message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Job job = new DownloadAvailableChaptersJob(record.recurrenceTimeMs, MangaId);
|
Job retrieveChapters = new RetrieveChaptersJob(m, record.language, record.recurrenceTimeMs);
|
||||||
Job dep = new RetrieveChaptersJob(record.recurrenceTimeMs, MangaId, job.JobId);
|
Job downloadChapters = new DownloadAvailableChaptersJob(m, record.recurrenceTimeMs, dependsOnJobs: [retrieveChapters]);
|
||||||
job.DependsOnJobsIds?.Add(dep.JobId);
|
return AddJobs([retrieveChapters, downloadChapters]);
|
||||||
return AddJobs([dep, job]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -148,9 +150,9 @@ public class JobController(PgsqlContext context) : Controller
|
|||||||
[ProducesResponseType<string>(Status500InternalServerError, "text/plain")]
|
[ProducesResponseType<string>(Status500InternalServerError, "text/plain")]
|
||||||
public IActionResult CreateNewDownloadChapterJob(string ChapterId)
|
public IActionResult CreateNewDownloadChapterJob(string ChapterId)
|
||||||
{
|
{
|
||||||
if(context.Chapters.Find(ChapterId) is null)
|
if(context.Chapters.Find(ChapterId) is not { } c)
|
||||||
return NotFound();
|
return NotFound();
|
||||||
Job job = new DownloadSingleChapterJob(ChapterId);
|
Job job = new DownloadSingleChapterJob(c);
|
||||||
return AddJobs([job]);
|
return AddJobs([job]);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -167,9 +169,9 @@ public class JobController(PgsqlContext context) : Controller
|
|||||||
[ProducesResponseType<string>(Status500InternalServerError, "text/plain")]
|
[ProducesResponseType<string>(Status500InternalServerError, "text/plain")]
|
||||||
public IActionResult CreateUpdateFilesDownloadedJob(string MangaId)
|
public IActionResult CreateUpdateFilesDownloadedJob(string MangaId)
|
||||||
{
|
{
|
||||||
if(context.Mangas.Find(MangaId) is null)
|
if(context.Mangas.Find(MangaId) is not { } m)
|
||||||
return NotFound();
|
return NotFound();
|
||||||
Job job = new UpdateFilesDownloadedJob(0, MangaId);
|
Job job = new UpdateFilesDownloadedJob(m, 0);
|
||||||
return AddJobs([job]);
|
return AddJobs([job]);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -183,8 +185,7 @@ public class JobController(PgsqlContext context) : Controller
|
|||||||
[ProducesResponseType<string>(Status500InternalServerError, "text/plain")]
|
[ProducesResponseType<string>(Status500InternalServerError, "text/plain")]
|
||||||
public IActionResult CreateUpdateAllFilesDownloadedJob()
|
public IActionResult CreateUpdateAllFilesDownloadedJob()
|
||||||
{
|
{
|
||||||
List<string> ids = context.Mangas.Select(m => m.MangaId).ToList();
|
List<UpdateFilesDownloadedJob> jobs = context.Mangas.Select(m => new UpdateFilesDownloadedJob(m, 0, null, null)).ToList();
|
||||||
List<UpdateFilesDownloadedJob> jobs = ids.Select(id => new UpdateFilesDownloadedJob(0, id)).ToList();
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
context.Jobs.AddRange(jobs);
|
context.Jobs.AddRange(jobs);
|
||||||
@ -193,12 +194,13 @@ public class JobController(PgsqlContext context) : Controller
|
|||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
|
Log.Error(e);
|
||||||
return StatusCode(500, e.Message);
|
return StatusCode(500, e.Message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Create a new UpdateMetadataJob
|
/// Not Implemented: Create a new UpdateMetadataJob
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="MangaId">ID of the Manga</param>
|
/// <param name="MangaId">ID of the Manga</param>
|
||||||
/// <response code="201">Job-IDs</response>
|
/// <response code="201">Job-IDs</response>
|
||||||
@ -210,14 +212,11 @@ public class JobController(PgsqlContext context) : Controller
|
|||||||
[ProducesResponseType<string>(Status500InternalServerError, "text/plain")]
|
[ProducesResponseType<string>(Status500InternalServerError, "text/plain")]
|
||||||
public IActionResult CreateUpdateMetadataJob(string MangaId)
|
public IActionResult CreateUpdateMetadataJob(string MangaId)
|
||||||
{
|
{
|
||||||
if(context.Mangas.Find(MangaId) is null)
|
return StatusCode(Status501NotImplemented);
|
||||||
return NotFound();
|
|
||||||
Job job = new UpdateMetadataJob(0, MangaId);
|
|
||||||
return AddJobs([job]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Create a new UpdateMetadataJob for all Manga
|
/// Not Implemented: Create a new UpdateMetadataJob for all Manga
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <response code="201">Job-IDs</response>
|
/// <response code="201">Job-IDs</response>
|
||||||
/// <response code="500">Error during Database Operation</response>
|
/// <response code="500">Error during Database Operation</response>
|
||||||
@ -226,18 +225,7 @@ public class JobController(PgsqlContext context) : Controller
|
|||||||
[ProducesResponseType<string>(Status500InternalServerError, "text/plain")]
|
[ProducesResponseType<string>(Status500InternalServerError, "text/plain")]
|
||||||
public IActionResult CreateUpdateAllMetadataJob()
|
public IActionResult CreateUpdateAllMetadataJob()
|
||||||
{
|
{
|
||||||
List<string> ids = context.Mangas.Select(m => m.MangaId).ToList();
|
return StatusCode(Status501NotImplemented);
|
||||||
List<UpdateMetadataJob> jobs = ids.Select(id => new UpdateMetadataJob(0, id)).ToList();
|
|
||||||
try
|
|
||||||
{
|
|
||||||
context.Jobs.AddRange(jobs);
|
|
||||||
context.SaveChanges();
|
|
||||||
return Created();
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
return StatusCode(500, e.Message);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private IActionResult AddJobs(Job[] jobs)
|
private IActionResult AddJobs(Job[] jobs)
|
||||||
@ -250,6 +238,7 @@ public class JobController(PgsqlContext context) : Controller
|
|||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
|
Log.Error(e);
|
||||||
return StatusCode(500, e.Message);
|
return StatusCode(500, e.Message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -269,8 +258,7 @@ public class JobController(PgsqlContext context) : Controller
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
Job? ret = context.Jobs.Find(JobId);
|
if(context.Jobs.Find(JobId) is not { } ret)
|
||||||
if(ret is null)
|
|
||||||
return NotFound();
|
return NotFound();
|
||||||
|
|
||||||
context.Remove(ret);
|
context.Remove(ret);
|
||||||
@ -279,6 +267,7 @@ public class JobController(PgsqlContext context) : Controller
|
|||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
|
Log.Error(e);
|
||||||
return StatusCode(500, e.Message);
|
return StatusCode(500, e.Message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -322,6 +311,7 @@ public class JobController(PgsqlContext context) : Controller
|
|||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
|
Log.Error(e);
|
||||||
return StatusCode(500, e.Message);
|
return StatusCode(500, e.Message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -354,6 +344,7 @@ public class JobController(PgsqlContext context) : Controller
|
|||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
|
Log.Error(e);
|
||||||
return StatusCode(500, e.Message);
|
return StatusCode(500, e.Message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -367,6 +358,6 @@ public class JobController(PgsqlContext context) : Controller
|
|||||||
[ProducesResponseType(Status501NotImplemented)]
|
[ProducesResponseType(Status501NotImplemented)]
|
||||||
public IActionResult StopJob(string JobId)
|
public IActionResult StopJob(string JobId)
|
||||||
{
|
{
|
||||||
return StatusCode(501);
|
return StatusCode(Status501NotImplemented);
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,6 +1,7 @@
|
|||||||
using API.Schema;
|
using API.Schema;
|
||||||
using API.Schema.LibraryConnectors;
|
using API.Schema.LibraryConnectors;
|
||||||
using Asp.Versioning;
|
using Asp.Versioning;
|
||||||
|
using log4net;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using static Microsoft.AspNetCore.Http.StatusCodes;
|
using static Microsoft.AspNetCore.Http.StatusCodes;
|
||||||
|
|
||||||
@ -9,10 +10,10 @@ namespace API.Controllers;
|
|||||||
[ApiVersion(2)]
|
[ApiVersion(2)]
|
||||||
[ApiController]
|
[ApiController]
|
||||||
[Route("v{v:apiVersion}/[controller]")]
|
[Route("v{v:apiVersion}/[controller]")]
|
||||||
public class LibraryConnectorController(PgsqlContext context) : Controller
|
public class LibraryConnectorController(PgsqlContext context, ILog Log) : Controller
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets all configured Library-Connectors
|
/// Gets all configured ToLibrary-Connectors
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <response code="200"></response>
|
/// <response code="200"></response>
|
||||||
[HttpGet]
|
[HttpGet]
|
||||||
@ -24,9 +25,9 @@ public class LibraryConnectorController(PgsqlContext context) : Controller
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Returns Library-Connector with requested ID
|
/// Returns ToLibrary-Connector with requested ID
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="LibraryControllerId">Library-Connector-ID</param>
|
/// <param name="LibraryControllerId">ToLibrary-Connector-ID</param>
|
||||||
/// <response code="200"></response>
|
/// <response code="200"></response>
|
||||||
/// <response code="404">Connector with ID not found.</response>
|
/// <response code="404">Connector with ID not found.</response>
|
||||||
[HttpGet("{LibraryControllerId}")]
|
[HttpGet("{LibraryControllerId}")]
|
||||||
@ -43,9 +44,9 @@ public class LibraryConnectorController(PgsqlContext context) : Controller
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Creates a new Library-Connector
|
/// Creates a new ToLibrary-Connector
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="libraryConnector">Library-Connector</param>
|
/// <param name="libraryConnector">ToLibrary-Connector</param>
|
||||||
/// <response code="201"></response>
|
/// <response code="201"></response>
|
||||||
/// <response code="500">Error during Database Operation</response>
|
/// <response code="500">Error during Database Operation</response>
|
||||||
[HttpPut]
|
[HttpPut]
|
||||||
@ -61,14 +62,15 @@ public class LibraryConnectorController(PgsqlContext context) : Controller
|
|||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
|
Log.Error(e);
|
||||||
return StatusCode(500, e.Message);
|
return StatusCode(500, e.Message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Deletes the Library-Connector with the requested ID
|
/// Deletes the ToLibrary-Connector with the requested ID
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="LibraryControllerId">Library-Connector-ID</param>
|
/// <param name="LibraryControllerId">ToLibrary-Connector-ID</param>
|
||||||
/// <response code="200"></response>
|
/// <response code="200"></response>
|
||||||
/// <response code="404">Connector with ID not found.</response>
|
/// <response code="404">Connector with ID not found.</response>
|
||||||
/// <response code="500">Error during Database Operation</response>
|
/// <response code="500">Error during Database Operation</response>
|
||||||
@ -90,6 +92,7 @@ public class LibraryConnectorController(PgsqlContext context) : Controller
|
|||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
|
Log.Error(e);
|
||||||
return StatusCode(500, e.Message);
|
return StatusCode(500, e.Message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
using API.APIEndpointRecords;
|
using API.APIEndpointRecords;
|
||||||
using API.Schema;
|
using API.Schema;
|
||||||
using Asp.Versioning;
|
using Asp.Versioning;
|
||||||
|
using log4net;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using static Microsoft.AspNetCore.Http.StatusCodes;
|
using static Microsoft.AspNetCore.Http.StatusCodes;
|
||||||
|
|
||||||
@ -9,7 +10,7 @@ namespace API.Controllers;
|
|||||||
[ApiVersion(2)]
|
[ApiVersion(2)]
|
||||||
[ApiController]
|
[ApiController]
|
||||||
[Route("v{v:apiVersion}/[controller]")]
|
[Route("v{v:apiVersion}/[controller]")]
|
||||||
public class LocalLibrariesController(PgsqlContext context) : Controller
|
public class LocalLibrariesController(PgsqlContext context, ILog Log) : Controller
|
||||||
{
|
{
|
||||||
[HttpGet]
|
[HttpGet]
|
||||||
[ProducesResponseType<LocalLibrary[]>(Status200OK, "application/json")]
|
[ProducesResponseType<LocalLibrary[]>(Status200OK, "application/json")]
|
||||||
@ -52,6 +53,7 @@ public class LocalLibrariesController(PgsqlContext context) : Controller
|
|||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
|
Log.Error(e);
|
||||||
return StatusCode(500, e.Message);
|
return StatusCode(500, e.Message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -79,6 +81,7 @@ public class LocalLibrariesController(PgsqlContext context) : Controller
|
|||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
|
Log.Error(e);
|
||||||
return StatusCode(500, e.Message);
|
return StatusCode(500, e.Message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -106,6 +109,7 @@ public class LocalLibrariesController(PgsqlContext context) : Controller
|
|||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
|
Log.Error(e);
|
||||||
return StatusCode(500, e.Message);
|
return StatusCode(500, e.Message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -128,6 +132,7 @@ public class LocalLibrariesController(PgsqlContext context) : Controller
|
|||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
|
Log.Error(e);
|
||||||
return StatusCode(500, e.Message);
|
return StatusCode(500, e.Message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -151,6 +156,7 @@ public class LocalLibrariesController(PgsqlContext context) : Controller
|
|||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
|
Log.Error(e);
|
||||||
return StatusCode(500, e.Message);
|
return StatusCode(500, e.Message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
using API.Schema;
|
using API.Schema;
|
||||||
using API.Schema.MangaConnectors;
|
using API.Schema.MangaConnectors;
|
||||||
using Asp.Versioning;
|
using Asp.Versioning;
|
||||||
|
using log4net;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using static Microsoft.AspNetCore.Http.StatusCodes;
|
using static Microsoft.AspNetCore.Http.StatusCodes;
|
||||||
|
|
||||||
@ -9,7 +10,7 @@ namespace API.Controllers;
|
|||||||
[ApiVersion(2)]
|
[ApiVersion(2)]
|
||||||
[ApiController]
|
[ApiController]
|
||||||
[Route("v{v:apiVersion}/[controller]")]
|
[Route("v{v:apiVersion}/[controller]")]
|
||||||
public class MangaConnectorController(PgsqlContext context) : Controller
|
public class MangaConnectorController(PgsqlContext context, ILog Log) : Controller
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Get all available Connectors (Scanlation-Sites)
|
/// Get all available Connectors (Scanlation-Sites)
|
||||||
@ -74,6 +75,7 @@ public class MangaConnectorController(PgsqlContext context) : Controller
|
|||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
|
Log.Error(e);
|
||||||
return StatusCode(500, e.Message);
|
return StatusCode(500, e.Message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,19 +1,21 @@
|
|||||||
using API.Schema;
|
using API.Schema;
|
||||||
using API.Schema.Jobs;
|
using API.Schema.Jobs;
|
||||||
using Asp.Versioning;
|
using Asp.Versioning;
|
||||||
|
using log4net;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using SixLabors.ImageSharp;
|
using SixLabors.ImageSharp;
|
||||||
using SixLabors.ImageSharp.Formats.Jpeg;
|
using SixLabors.ImageSharp.Formats.Jpeg;
|
||||||
using SixLabors.ImageSharp.Processing;
|
using SixLabors.ImageSharp.Processing;
|
||||||
using SixLabors.ImageSharp.Processing.Processors.Transforms;
|
using SixLabors.ImageSharp.Processing.Processors.Transforms;
|
||||||
using static Microsoft.AspNetCore.Http.StatusCodes;
|
using static Microsoft.AspNetCore.Http.StatusCodes;
|
||||||
|
// ReSharper disable InconsistentNaming
|
||||||
|
|
||||||
namespace API.Controllers;
|
namespace API.Controllers;
|
||||||
|
|
||||||
[ApiVersion(2)]
|
[ApiVersion(2)]
|
||||||
[ApiController]
|
[ApiController]
|
||||||
[Route("v{v:apiVersion}/[controller]")]
|
[Route("v{v:apiVersion}/[controller]")]
|
||||||
public class MangaController(PgsqlContext context) : Controller
|
public class MangaController(PgsqlContext context, ILog Log) : Controller
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Returns all cached Manga
|
/// Returns all cached Manga
|
||||||
@ -82,6 +84,7 @@ public class MangaController(PgsqlContext context) : Controller
|
|||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
|
Log.Error(e);
|
||||||
return StatusCode(500, e.Message);
|
return StatusCode(500, e.Message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -287,11 +290,12 @@ public class MangaController(PgsqlContext context) : Controller
|
|||||||
|
|
||||||
return Ok(max);
|
return Ok(max);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Configure the cut-off for Manga
|
/// Configure the cut-off for Manga
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="MangaId">Manga-ID</param>
|
/// <param name="MangaId">Manga-ID</param>
|
||||||
|
/// <param name="chapterThreshold">Threshold (Chapter Number)</param>
|
||||||
/// <response code="200"></response>
|
/// <response code="200"></response>
|
||||||
/// <response code="404">Manga with ID not found.</response>
|
/// <response code="404">Manga with ID not found.</response>
|
||||||
/// <response code="500">Error during Database Operation</response>
|
/// <response code="500">Error during Database Operation</response>
|
||||||
@ -307,21 +311,22 @@ public class MangaController(PgsqlContext context) : Controller
|
|||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
m.IgnoreChapterBefore = chapterThreshold;
|
m.IgnoreChaptersBefore = chapterThreshold;
|
||||||
context.SaveChanges();
|
context.SaveChanges();
|
||||||
return Ok();
|
return Ok();
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
|
Log.Error(e);
|
||||||
return StatusCode(500, e.Message);
|
return StatusCode(500, e.Message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Move Manga to different Library
|
/// Move Manga to different ToLibrary
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="MangaId">Manga-ID</param>
|
/// <param name="MangaId">Manga-ID</param>
|
||||||
/// <param name="LibraryId">Library-Id</param>
|
/// <param name="LibraryId">ToLibrary-Id</param>
|
||||||
/// <response code="202">Folder is going to be moved</response>
|
/// <response code="202">Folder is going to be moved</response>
|
||||||
/// <response code="404">MangaId or LibraryId not found</response>
|
/// <response code="404">MangaId or LibraryId not found</response>
|
||||||
/// <response code="500">Error during Database Operation</response>
|
/// <response code="500">Error during Database Operation</response>
|
||||||
@ -331,24 +336,23 @@ public class MangaController(PgsqlContext context) : Controller
|
|||||||
[ProducesResponseType<string>(Status500InternalServerError, "text/plain")]
|
[ProducesResponseType<string>(Status500InternalServerError, "text/plain")]
|
||||||
public IActionResult MoveFolder(string MangaId, string LibraryId)
|
public IActionResult MoveFolder(string MangaId, string LibraryId)
|
||||||
{
|
{
|
||||||
Manga? manga = context.Mangas.Find(MangaId);
|
if (context.Mangas.Find(MangaId) is not { } manga)
|
||||||
if (manga is null)
|
|
||||||
return NotFound();
|
return NotFound();
|
||||||
LocalLibrary? library = context.LocalLibraries.Find(LibraryId);
|
if(context.LocalLibraries.Find(LibraryId) is not { } library)
|
||||||
if (library is null)
|
|
||||||
return NotFound();
|
return NotFound();
|
||||||
|
|
||||||
MoveMangaLibraryJob dep = new (MangaId, LibraryId);
|
MoveMangaLibraryJob moveLibrary = new(manga, library);
|
||||||
UpdateFilesDownloadedJob up = new (0, manga.MangaId, null, [dep.JobId]);
|
UpdateFilesDownloadedJob updateDownloadedFiles = new(manga, 0, dependsOnJobs: [moveLibrary]);
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
context.Jobs.AddRange([dep, up]);
|
context.Jobs.AddRange(moveLibrary, updateDownloadedFiles);
|
||||||
context.SaveChanges();
|
context.SaveChanges();
|
||||||
return Accepted();
|
return Accepted();
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
|
Log.Error(e);
|
||||||
return StatusCode(500, e.Message);
|
return StatusCode(500, e.Message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,7 @@ using API.APIEndpointRecords;
|
|||||||
using API.Schema;
|
using API.Schema;
|
||||||
using API.Schema.NotificationConnectors;
|
using API.Schema.NotificationConnectors;
|
||||||
using Asp.Versioning;
|
using Asp.Versioning;
|
||||||
|
using log4net;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using static Microsoft.AspNetCore.Http.StatusCodes;
|
using static Microsoft.AspNetCore.Http.StatusCodes;
|
||||||
|
|
||||||
@ -12,7 +13,7 @@ namespace API.Controllers;
|
|||||||
[ApiController]
|
[ApiController]
|
||||||
[Produces("application/json")]
|
[Produces("application/json")]
|
||||||
[Route("v{v:apiVersion}/[controller]")]
|
[Route("v{v:apiVersion}/[controller]")]
|
||||||
public class NotificationConnectorController(PgsqlContext context) : Controller
|
public class NotificationConnectorController(PgsqlContext context, ILog Log) : Controller
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets all configured Notification-Connectors
|
/// Gets all configured Notification-Connectors
|
||||||
@ -69,6 +70,7 @@ public class NotificationConnectorController(PgsqlContext context) : Controller
|
|||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
|
Log.Error(e);
|
||||||
return StatusCode(500, e.Message);
|
return StatusCode(500, e.Message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -209,6 +211,7 @@ public class NotificationConnectorController(PgsqlContext context) : Controller
|
|||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
|
Log.Error(e);
|
||||||
return StatusCode(500, e.Message);
|
return StatusCode(500, e.Message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,14 +1,16 @@
|
|||||||
using API.Schema;
|
using API.Schema;
|
||||||
using Asp.Versioning;
|
using Asp.Versioning;
|
||||||
|
using log4net;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using static Microsoft.AspNetCore.Http.StatusCodes;
|
using static Microsoft.AspNetCore.Http.StatusCodes;
|
||||||
|
// ReSharper disable InconsistentNaming
|
||||||
|
|
||||||
namespace API.Controllers;
|
namespace API.Controllers;
|
||||||
|
|
||||||
[ApiVersion(2)]
|
[ApiVersion(2)]
|
||||||
[ApiController]
|
[ApiController]
|
||||||
[Route("v{v:apiVersion}/[controller]")]
|
[Route("v{v:apiVersion}/[controller]")]
|
||||||
public class QueryController(PgsqlContext context) : Controller
|
public class QueryController(PgsqlContext context, ILog Log) : Controller
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Returns the Author-Information for Author-ID
|
/// Returns the Author-Information for Author-ID
|
||||||
@ -32,13 +34,16 @@ public class QueryController(PgsqlContext context) : Controller
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="AuthorId">Author-ID</param>
|
/// <param name="AuthorId">Author-ID</param>
|
||||||
/// <response code="200"></response>
|
/// <response code="200"></response>
|
||||||
|
/// <response code="404">Author not found</response>
|
||||||
[HttpGet("Mangas/WithAuthorId/{AuthorId}")]
|
[HttpGet("Mangas/WithAuthorId/{AuthorId}")]
|
||||||
[ProducesResponseType<Manga[]>(Status200OK, "application/json")]
|
[ProducesResponseType<Manga[]>(Status200OK, "application/json")]
|
||||||
public IActionResult GetMangaWithAuthorIds(string AuthorId)
|
public IActionResult GetMangaWithAuthorIds(string AuthorId)
|
||||||
{
|
{
|
||||||
return Ok(context.Mangas.Where(m => m.AuthorIds.Contains(AuthorId)));
|
if(context.Authors.Find(AuthorId) is not { } a)
|
||||||
|
return NotFound();
|
||||||
|
return Ok(context.Mangas.Where(m => m.Authors.Contains(a)));
|
||||||
}
|
}
|
||||||
|
/*
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Returns Link-Information for Link-Id
|
/// Returns Link-Information for Link-Id
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -71,18 +76,21 @@ public class QueryController(PgsqlContext context) : Controller
|
|||||||
if (ret is null)
|
if (ret is null)
|
||||||
return NotFound();
|
return NotFound();
|
||||||
return Ok(ret);
|
return Ok(ret);
|
||||||
}
|
}*/
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Returns all Manga with Tag
|
/// Returns all Manga with Tag
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="Tag"></param>
|
/// <param name="Tag"></param>
|
||||||
/// <response code="200"></response>
|
/// <response code="200"></response>
|
||||||
|
/// <response code="404">Tag not found</response>
|
||||||
[HttpGet("Mangas/WithTag/{Tag}")]
|
[HttpGet("Mangas/WithTag/{Tag}")]
|
||||||
[ProducesResponseType<Manga[]>(Status200OK, "application/json")]
|
[ProducesResponseType<Manga[]>(Status200OK, "application/json")]
|
||||||
public IActionResult GetMangasWithTag(string Tag)
|
public IActionResult GetMangasWithTag(string Tag)
|
||||||
{
|
{
|
||||||
return Ok(context.Mangas.Where(m => m.Tags.Contains(Tag)));
|
if(context.Tags.Find(Tag) is not { } t)
|
||||||
|
return NotFound();
|
||||||
|
return Ok(context.Mangas.Where(m => m.MangaTags.Contains(t)));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -2,85 +2,53 @@ using API.Schema;
|
|||||||
using API.Schema.Jobs;
|
using API.Schema.Jobs;
|
||||||
using API.Schema.MangaConnectors;
|
using API.Schema.MangaConnectors;
|
||||||
using Asp.Versioning;
|
using Asp.Versioning;
|
||||||
|
using log4net;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using static Microsoft.AspNetCore.Http.StatusCodes;
|
using static Microsoft.AspNetCore.Http.StatusCodes;
|
||||||
|
// ReSharper disable InconsistentNaming
|
||||||
|
|
||||||
namespace API.Controllers;
|
namespace API.Controllers;
|
||||||
|
|
||||||
[ApiVersion(2)]
|
[ApiVersion(2)]
|
||||||
[ApiController]
|
[ApiController]
|
||||||
[Route("v{v:apiVersion}/[controller]")]
|
[Route("v{v:apiVersion}/[controller]")]
|
||||||
public class SearchController(PgsqlContext context) : Controller
|
public class SearchController(PgsqlContext context, ILog Log) : Controller
|
||||||
{
|
{
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Initiate a search for a Manga on all Connectors
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="name">Name/Title of the Manga</param>
|
|
||||||
/// <response code="200"></response>
|
|
||||||
/// <response code="500">Error during Database Operation</response>
|
|
||||||
[HttpPost("Name")]
|
|
||||||
[ProducesResponseType<Manga[]>(Status200OK, "application/json")]
|
|
||||||
[ProducesResponseType<string>(Status500InternalServerError, "text/plain")]
|
|
||||||
public IActionResult SearchMangaGlobal([FromBody]string name)
|
|
||||||
{
|
|
||||||
List<(Manga, List<Author>?, List<MangaTag>?, List<Link>?, List<MangaAltTitle>?)> allManga = new();
|
|
||||||
foreach (MangaConnector contextMangaConnector in context.MangaConnectors.Where(connector => connector.Enabled))
|
|
||||||
allManga.AddRange(contextMangaConnector.GetManga(name));
|
|
||||||
|
|
||||||
List<Manga> retMangas = new();
|
|
||||||
foreach ((Manga? manga, List<Author>? authors, List<MangaTag>? tags, List<Link>? links, List<MangaAltTitle>? altTitles) in allManga)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
Manga? add = AddMangaToContext(manga, authors, tags, links, altTitles);
|
|
||||||
if(add is not null)
|
|
||||||
retMangas.Add(add);
|
|
||||||
}
|
|
||||||
catch (DbUpdateException e)
|
|
||||||
{
|
|
||||||
return StatusCode(500, e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return Ok(retMangas.ToArray());
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initiate a search for a Manga on a specific Connector
|
/// Initiate a search for a Manga on a specific Connector
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="MangaConnectorName">Manga-Connector-ID</param>
|
/// <param name="MangaConnectorName"></param>
|
||||||
/// <param name="name">Name/Title of the Manga</param>
|
/// <param name="Query"></param>
|
||||||
/// <response code="200"></response>
|
/// <response code="200"></response>
|
||||||
/// <response code="404">MangaConnector with ID not found</response>
|
/// <response code="404">MangaConnector with ID not found</response>
|
||||||
/// <response code="406">MangaConnector with ID is disabled</response>
|
/// <response code="406">MangaConnector with ID is disabled</response>
|
||||||
/// <response code="500">Error during Database Operation</response>
|
/// <response code="500">Error during Database Operation</response>
|
||||||
[HttpPost("{MangaConnectorName}")]
|
[HttpPost("{MangaConnectorName}/{Query}")]
|
||||||
[ProducesResponseType<Manga[]>(Status200OK, "application/json")]
|
[ProducesResponseType<Manga[]>(Status200OK, "application/json")]
|
||||||
[ProducesResponseType(Status404NotFound)]
|
[ProducesResponseType(Status404NotFound)]
|
||||||
[ProducesResponseType(Status406NotAcceptable)]
|
[ProducesResponseType(Status406NotAcceptable)]
|
||||||
[ProducesResponseType<string>(Status500InternalServerError, "text/plain")]
|
[ProducesResponseType<string>(Status500InternalServerError, "text/plain")]
|
||||||
public IActionResult SearchManga(string MangaConnectorName, [FromBody]string name)
|
public IActionResult SearchManga(string MangaConnectorName, string Query)
|
||||||
{
|
{
|
||||||
MangaConnector? connector = context.MangaConnectors.Find(MangaConnectorName);
|
if(context.MangaConnectors.Find(MangaConnectorName) is not { } connector)
|
||||||
if (connector is null)
|
|
||||||
return NotFound();
|
return NotFound();
|
||||||
else if (connector.Enabled is false)
|
else if (connector.Enabled is false)
|
||||||
return StatusCode(406);
|
return StatusCode(Status406NotAcceptable);
|
||||||
|
|
||||||
(Manga, List<Author>?, List<MangaTag>?, List<Link>?, List<MangaAltTitle>?)[] mangas = connector.GetManga(name);
|
Manga[] mangas = connector.SearchManga(Query);
|
||||||
List<Manga> retMangas = new();
|
List<Manga> retMangas = new();
|
||||||
foreach ((Manga? manga, List<Author>? authors, List<MangaTag>? tags, List<Link>? links, List<MangaAltTitle>? altTitles) in mangas)
|
foreach (Manga manga in mangas)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
Manga? add = AddMangaToContext(manga, authors, tags, links, altTitles);
|
if(AddMangaToContext(manga) is { } add)
|
||||||
if(add is not null)
|
|
||||||
retMangas.Add(add);
|
retMangas.Add(add);
|
||||||
}
|
}
|
||||||
catch (DbUpdateException e)
|
catch (DbUpdateException e)
|
||||||
{
|
{
|
||||||
return StatusCode(500, e.Message);
|
Log.Error(e);
|
||||||
|
return StatusCode(Status500InternalServerError, e.Message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -104,98 +72,77 @@ public class SearchController(PgsqlContext context) : Controller
|
|||||||
[ProducesResponseType<string>(Status500InternalServerError, "text/plain")]
|
[ProducesResponseType<string>(Status500InternalServerError, "text/plain")]
|
||||||
public IActionResult GetMangaFromUrl([FromBody]string url)
|
public IActionResult GetMangaFromUrl([FromBody]string url)
|
||||||
{
|
{
|
||||||
List<MangaConnector> connectors = context.MangaConnectors.AsEnumerable().Where(c => c.ValidateUrl(url)).ToList();
|
List<MangaConnector> connectors = context.MangaConnectors.AsEnumerable().Where(c => c.UrlMatchesConnector(url)).ToList();
|
||||||
if (connectors.Count == 0)
|
if (connectors.Count == 0)
|
||||||
return NotFound();
|
return NotFound();
|
||||||
else if (connectors.Count > 1)
|
else if (connectors.Count > 1)
|
||||||
return StatusCode(Status300MultipleChoices);
|
return StatusCode(Status300MultipleChoices);
|
||||||
|
|
||||||
(Manga manga, List<Author>? authors, List<MangaTag>? tags, List<Link>? links, List<MangaAltTitle>? altTitles)? x = connectors.First().GetMangaFromUrl(url);
|
if(connectors.First().GetMangaFromUrl(url) is not { } manga)
|
||||||
if (x is null)
|
|
||||||
return BadRequest();
|
return BadRequest();
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
Manga? add = AddMangaToContext(x.Value.manga, x.Value.authors, x.Value.tags, x.Value.links, x.Value.altTitles);
|
if(AddMangaToContext(manga) is { } add)
|
||||||
if (add is not null)
|
|
||||||
return Ok(add);
|
return Ok(add);
|
||||||
return StatusCode(500);
|
return StatusCode(500);
|
||||||
}
|
}
|
||||||
catch (DbUpdateException e)
|
catch (DbUpdateException e)
|
||||||
{
|
{
|
||||||
|
Log.Error(e);
|
||||||
return StatusCode(500, e.Message);
|
return StatusCode(500, e.Message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private Manga? AddMangaToContext(Manga? manga, List<Author>? authors, List<MangaTag>? tags, List<Link>? links,
|
private Manga? AddMangaToContext(Manga manga)
|
||||||
List<MangaAltTitle>? altTitles)
|
|
||||||
{
|
{
|
||||||
if (manga is null)
|
|
||||||
return null;
|
|
||||||
|
|
||||||
Manga? existing = context.Mangas.Find(manga.MangaId);
|
Manga? existing = context.Mangas.Find(manga.MangaId);
|
||||||
|
|
||||||
if (tags is not null)
|
IEnumerable<MangaTag> mergedTags = manga.MangaTags.Select(mt =>
|
||||||
{
|
{
|
||||||
IEnumerable<MangaTag> mergedTags = tags.Select(mt =>
|
MangaTag? inDb = context.Tags.Find(mt.Tag);
|
||||||
{
|
return inDb ?? mt;
|
||||||
MangaTag? inDb = context.Tags.Find(mt.Tag);
|
});
|
||||||
return inDb ?? mt;
|
manga.MangaTags = mergedTags.ToList();
|
||||||
});
|
|
||||||
manga.MangaTags = mergedTags.ToList();
|
|
||||||
IEnumerable<MangaTag> newTags = manga.MangaTags
|
|
||||||
.Where(mt => !context.Tags.Select(t => t.Tag).Contains(mt.Tag));
|
|
||||||
context.Tags.AddRange(newTags);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (authors is not null)
|
IEnumerable<Author> mergedAuthors = manga.Authors.Select(ma =>
|
||||||
{
|
{
|
||||||
IEnumerable<Author> mergedAuthors = authors.Select(ma =>
|
Author? inDb = context.Authors.Find(ma.AuthorId);
|
||||||
{
|
return inDb ?? ma;
|
||||||
Author? inDb = context.Authors.Find(ma.AuthorId);
|
});
|
||||||
return inDb ?? ma;
|
manga.Authors = mergedAuthors.ToList();
|
||||||
});
|
|
||||||
manga.Authors = mergedAuthors.ToList();
|
|
||||||
IEnumerable<Author> newAuthors = manga.Authors
|
|
||||||
.Where(ma => !context.Authors.Select(a => a.AuthorId).Contains(ma.AuthorId));
|
|
||||||
context.Authors.AddRange(newAuthors);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (links is not null)
|
/*
|
||||||
|
IEnumerable<Link> mergedLinks = manga.Links.Select(ml =>
|
||||||
{
|
{
|
||||||
IEnumerable<Link> mergedLinks = links.Select(ml =>
|
Link? inDb = context.Links.Find(ml.LinkId);
|
||||||
{
|
return inDb ?? ml;
|
||||||
Link? inDb = context.Links.Find(ml.LinkId);
|
});
|
||||||
return inDb ?? ml;
|
manga.Links = mergedLinks.ToList();
|
||||||
});
|
|
||||||
manga.Links = mergedLinks.ToList();
|
|
||||||
IEnumerable<Link> newLinks = manga.Links
|
|
||||||
.Where(ml => !context.Links.Select(l => l.LinkId).Contains(ml.LinkId));
|
|
||||||
context.Links.AddRange(newLinks);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (altTitles is not null)
|
IEnumerable<MangaAltTitle> mergedAltTitles = manga.AltTitles.Select(mat =>
|
||||||
{
|
{
|
||||||
IEnumerable<MangaAltTitle> mergedAltTitles = altTitles.Select(mat =>
|
MangaAltTitle? inDb = context.AltTitles.Find(mat.AltTitleId);
|
||||||
|
return inDb ?? mat;
|
||||||
|
});
|
||||||
|
manga.AltTitles = mergedAltTitles.ToList();
|
||||||
|
*/
|
||||||
|
try
|
||||||
|
{
|
||||||
|
|
||||||
|
if (context.Mangas.Find(manga.MangaId) is { } r)
|
||||||
{
|
{
|
||||||
MangaAltTitle? inDb = context.AltTitles.Find(mat.AltTitleId);
|
context.Mangas.Remove(r);
|
||||||
return inDb ?? mat;
|
context.SaveChanges();
|
||||||
});
|
}
|
||||||
manga.AltTitles = mergedAltTitles.ToList();
|
|
||||||
IEnumerable<MangaAltTitle> newAltTitles = manga.AltTitles
|
|
||||||
.Where(mat => !context.AltTitles.Select(at => at.AltTitleId).Contains(mat.AltTitleId));
|
|
||||||
context.AltTitles.AddRange(newAltTitles);
|
|
||||||
}
|
|
||||||
|
|
||||||
existing?.UpdateWithInfo(manga);
|
|
||||||
if(existing is not null)
|
|
||||||
context.Mangas.Update(existing);
|
|
||||||
else
|
|
||||||
context.Mangas.Add(manga);
|
context.Mangas.Add(manga);
|
||||||
|
context.Jobs.Add(new DownloadMangaCoverJob(manga));
|
||||||
context.Jobs.Add(new DownloadMangaCoverJob(manga.MangaId));
|
context.SaveChanges();
|
||||||
context.Jobs.Add(new RetrieveChaptersJob(0, manga.MangaId));
|
}
|
||||||
|
catch (DbUpdateException e)
|
||||||
context.SaveChanges();
|
{
|
||||||
return existing ?? manga;
|
Log.Error(e);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return manga;
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -2,6 +2,7 @@
|
|||||||
using API.Schema;
|
using API.Schema;
|
||||||
using API.Schema.Jobs;
|
using API.Schema.Jobs;
|
||||||
using Asp.Versioning;
|
using Asp.Versioning;
|
||||||
|
using log4net;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using Newtonsoft.Json.Linq;
|
using Newtonsoft.Json.Linq;
|
||||||
using static Microsoft.AspNetCore.Http.StatusCodes;
|
using static Microsoft.AspNetCore.Http.StatusCodes;
|
||||||
@ -11,7 +12,7 @@ namespace API.Controllers;
|
|||||||
[ApiVersion(2)]
|
[ApiVersion(2)]
|
||||||
[ApiController]
|
[ApiController]
|
||||||
[Route("v{v:apiVersion}/[controller]")]
|
[Route("v{v:apiVersion}/[controller]")]
|
||||||
public class SettingsController(PgsqlContext context) : Controller
|
public class SettingsController(PgsqlContext context, ILog Log) : Controller
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Get all Settings
|
/// Get all Settings
|
||||||
@ -252,14 +253,16 @@ public class SettingsController(PgsqlContext context) : Controller
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
Dictionary<Chapter, string> oldPaths = context.Chapters.ToDictionary(c => c, c => c.FullArchiveFilePath);
|
||||||
TrangaSettings.UpdateChapterNamingScheme(namingScheme);
|
TrangaSettings.UpdateChapterNamingScheme(namingScheme);
|
||||||
MoveFileOrFolderJob[] newJobs =
|
MoveFileOrFolderJob[] newJobs = oldPaths
|
||||||
context.Chapters.Where(c => c.Downloaded).Select(c => c.UpdateArchiveFileName()).Where(x => x != null).ToArray()!;
|
.Select(kv => new MoveFileOrFolderJob(kv.Value, kv.Key.FullArchiveFilePath)).ToArray();
|
||||||
context.Jobs.AddRange(newJobs);
|
context.Jobs.AddRange(newJobs);
|
||||||
return Ok();
|
return Ok();
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
|
Log.Error(e);
|
||||||
return StatusCode(500, e);
|
return StatusCode(500, e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,42 +0,0 @@
|
|||||||
using Microsoft.EntityFrameworkCore.Migrations;
|
|
||||||
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
namespace API.Migrations
|
|
||||||
{
|
|
||||||
/// <inheritdoc />
|
|
||||||
public partial class dev1603252 : Migration
|
|
||||||
{
|
|
||||||
/// <inheritdoc />
|
|
||||||
protected override void Up(MigrationBuilder migrationBuilder)
|
|
||||||
{
|
|
||||||
migrationBuilder.DropForeignKey(
|
|
||||||
name: "FK_Mangas_LocalLibraries_LibraryLocalLibraryId",
|
|
||||||
table: "Mangas");
|
|
||||||
|
|
||||||
migrationBuilder.AddForeignKey(
|
|
||||||
name: "FK_Mangas_LocalLibraries_LibraryLocalLibraryId",
|
|
||||||
table: "Mangas",
|
|
||||||
column: "LibraryLocalLibraryId",
|
|
||||||
principalTable: "LocalLibraries",
|
|
||||||
principalColumn: "LocalLibraryId",
|
|
||||||
onDelete: ReferentialAction.Restrict);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
protected override void Down(MigrationBuilder migrationBuilder)
|
|
||||||
{
|
|
||||||
migrationBuilder.DropForeignKey(
|
|
||||||
name: "FK_Mangas_LocalLibraries_LibraryLocalLibraryId",
|
|
||||||
table: "Mangas");
|
|
||||||
|
|
||||||
migrationBuilder.AddForeignKey(
|
|
||||||
name: "FK_Mangas_LocalLibraries_LibraryLocalLibraryId",
|
|
||||||
table: "Mangas",
|
|
||||||
column: "LibraryLocalLibraryId",
|
|
||||||
principalTable: "LocalLibraries",
|
|
||||||
principalColumn: "LocalLibraryId",
|
|
||||||
onDelete: ReferentialAction.Cascade);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
827
API/Migrations/20250401001439_dev-010425-1.Designer.cs
generated
827
API/Migrations/20250401001439_dev-010425-1.Designer.cs
generated
@ -1,827 +0,0 @@
|
|||||||
// <auto-generated />
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using API.Schema;
|
|
||||||
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
|
|
||||||
{
|
|
||||||
[DbContext(typeof(PgsqlContext))]
|
|
||||||
[Migration("20250401001439_dev-010425-1")]
|
|
||||||
partial class dev0104251
|
|
||||||
{
|
|
||||||
/// <inheritdoc />
|
|
||||||
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
|
||||||
{
|
|
||||||
#pragma warning disable 612, 618
|
|
||||||
modelBuilder
|
|
||||||
.HasAnnotation("ProductVersion", "9.0.3")
|
|
||||||
.HasAnnotation("Relational:MaxIdentifierLength", 63);
|
|
||||||
|
|
||||||
NpgsqlModelBuilderExtensions.HasPostgresExtension(modelBuilder, "hstore");
|
|
||||||
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>("ParentMangaId")
|
|
||||||
.IsRequired()
|
|
||||||
.HasMaxLength(64)
|
|
||||||
.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.PrimitiveCollection<string[]>("DependsOnJobsIds")
|
|
||||||
.HasMaxLength(64)
|
|
||||||
.HasColumnType("text[]");
|
|
||||||
|
|
||||||
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.LibraryConnectors.LibraryConnector", b =>
|
|
||||||
{
|
|
||||||
b.Property<string>("LibraryConnectorId")
|
|
||||||
.HasMaxLength(64)
|
|
||||||
.HasColumnType("character varying(64)");
|
|
||||||
|
|
||||||
b.Property<string>("Auth")
|
|
||||||
.IsRequired()
|
|
||||||
.HasMaxLength(256)
|
|
||||||
.HasColumnType("character varying(256)");
|
|
||||||
|
|
||||||
b.Property<string>("BaseUrl")
|
|
||||||
.IsRequired()
|
|
||||||
.HasMaxLength(256)
|
|
||||||
.HasColumnType("character varying(256)");
|
|
||||||
|
|
||||||
b.Property<byte>("LibraryType")
|
|
||||||
.HasColumnType("smallint");
|
|
||||||
|
|
||||||
b.HasKey("LibraryConnectorId");
|
|
||||||
|
|
||||||
b.ToTable("LibraryConnectors");
|
|
||||||
|
|
||||||
b.HasDiscriminator<byte>("LibraryType");
|
|
||||||
|
|
||||||
b.UseTphMappingStrategy();
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("API.Schema.Link", b =>
|
|
||||||
{
|
|
||||||
b.Property<string>("LinkId")
|
|
||||||
.HasMaxLength(64)
|
|
||||||
.HasColumnType("character varying(64)");
|
|
||||||
|
|
||||||
b.Property<string>("LinkProvider")
|
|
||||||
.IsRequired()
|
|
||||||
.HasMaxLength(64)
|
|
||||||
.HasColumnType("character varying(64)");
|
|
||||||
|
|
||||||
b.Property<string>("LinkUrl")
|
|
||||||
.IsRequired()
|
|
||||||
.HasMaxLength(2048)
|
|
||||||
.HasColumnType("character varying(2048)");
|
|
||||||
|
|
||||||
b.Property<string>("MangaId")
|
|
||||||
.HasColumnType("character varying(64)");
|
|
||||||
|
|
||||||
b.HasKey("LinkId");
|
|
||||||
|
|
||||||
b.HasIndex("MangaId");
|
|
||||||
|
|
||||||
b.ToTable("Links");
|
|
||||||
});
|
|
||||||
|
|
||||||
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")
|
|
||||||
.HasColumnType("text");
|
|
||||||
|
|
||||||
b.Property<string>("CoverUrl")
|
|
||||||
.IsRequired()
|
|
||||||
.HasColumnType("text");
|
|
||||||
|
|
||||||
b.Property<string>("Description")
|
|
||||||
.IsRequired()
|
|
||||||
.HasColumnType("text");
|
|
||||||
|
|
||||||
b.Property<string>("DirectoryName")
|
|
||||||
.IsRequired()
|
|
||||||
.HasMaxLength(256)
|
|
||||||
.HasColumnType("character varying(256)");
|
|
||||||
|
|
||||||
b.Property<string>("IdOnConnectorSite")
|
|
||||||
.IsRequired()
|
|
||||||
.HasMaxLength(128)
|
|
||||||
.HasColumnType("character varying(128)");
|
|
||||||
|
|
||||||
b.Property<float>("IgnoreChapterBefore")
|
|
||||||
.HasColumnType("real");
|
|
||||||
|
|
||||||
b.Property<string>("LibraryLocalLibraryId")
|
|
||||||
.HasColumnType("character varying(64)");
|
|
||||||
|
|
||||||
b.Property<string>("MangaConnectorId")
|
|
||||||
.IsRequired()
|
|
||||||
.HasMaxLength(64)
|
|
||||||
.HasColumnType("character varying(64)");
|
|
||||||
|
|
||||||
b.Property<string>("Name")
|
|
||||||
.IsRequired()
|
|
||||||
.HasMaxLength(256)
|
|
||||||
.HasColumnType("character varying(256)");
|
|
||||||
|
|
||||||
b.Property<string>("OriginalLanguage")
|
|
||||||
.HasMaxLength(8)
|
|
||||||
.HasColumnType("character varying(8)");
|
|
||||||
|
|
||||||
b.Property<byte>("ReleaseStatus")
|
|
||||||
.HasColumnType("smallint");
|
|
||||||
|
|
||||||
b.Property<string>("WebsiteUrl")
|
|
||||||
.IsRequired()
|
|
||||||
.HasMaxLength(256)
|
|
||||||
.HasColumnType("character varying(256)");
|
|
||||||
|
|
||||||
b.Property<long>("Year")
|
|
||||||
.HasColumnType("bigint");
|
|
||||||
|
|
||||||
b.HasKey("MangaId");
|
|
||||||
|
|
||||||
b.HasIndex("LibraryLocalLibraryId");
|
|
||||||
|
|
||||||
b.HasIndex("MangaConnectorId");
|
|
||||||
|
|
||||||
b.ToTable("Mangas");
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("API.Schema.MangaAltTitle", b =>
|
|
||||||
{
|
|
||||||
b.Property<string>("AltTitleId")
|
|
||||||
.HasMaxLength(64)
|
|
||||||
.HasColumnType("character varying(64)");
|
|
||||||
|
|
||||||
b.Property<string>("Language")
|
|
||||||
.IsRequired()
|
|
||||||
.HasMaxLength(8)
|
|
||||||
.HasColumnType("character varying(8)");
|
|
||||||
|
|
||||||
b.Property<string>("MangaId")
|
|
||||||
.HasColumnType("character varying(64)");
|
|
||||||
|
|
||||||
b.Property<string>("Title")
|
|
||||||
.IsRequired()
|
|
||||||
.HasMaxLength(256)
|
|
||||||
.HasColumnType("character varying(256)");
|
|
||||||
|
|
||||||
b.HasKey("AltTitleId");
|
|
||||||
|
|
||||||
b.HasIndex("MangaId");
|
|
||||||
|
|
||||||
b.ToTable("AltTitles");
|
|
||||||
});
|
|
||||||
|
|
||||||
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.Notification", b =>
|
|
||||||
{
|
|
||||||
b.Property<string>("NotificationId")
|
|
||||||
.HasMaxLength(64)
|
|
||||||
.HasColumnType("character varying(64)");
|
|
||||||
|
|
||||||
b.Property<DateTime>("Date")
|
|
||||||
.HasColumnType("timestamp with time zone");
|
|
||||||
|
|
||||||
b.Property<string>("Message")
|
|
||||||
.IsRequired()
|
|
||||||
.HasMaxLength(512)
|
|
||||||
.HasColumnType("character varying(512)");
|
|
||||||
|
|
||||||
b.Property<string>("Title")
|
|
||||||
.IsRequired()
|
|
||||||
.HasMaxLength(128)
|
|
||||||
.HasColumnType("character varying(128)");
|
|
||||||
|
|
||||||
b.Property<byte>("Urgency")
|
|
||||||
.HasColumnType("smallint");
|
|
||||||
|
|
||||||
b.HasKey("NotificationId");
|
|
||||||
|
|
||||||
b.ToTable("Notifications");
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("API.Schema.NotificationConnectors.NotificationConnector", b =>
|
|
||||||
{
|
|
||||||
b.Property<string>("Name")
|
|
||||||
.HasMaxLength(64)
|
|
||||||
.HasColumnType("character varying(64)");
|
|
||||||
|
|
||||||
b.Property<string>("Body")
|
|
||||||
.IsRequired()
|
|
||||||
.HasMaxLength(4096)
|
|
||||||
.HasColumnType("character varying(4096)");
|
|
||||||
|
|
||||||
b.Property<Dictionary<string, string>>("Headers")
|
|
||||||
.IsRequired()
|
|
||||||
.HasColumnType("hstore");
|
|
||||||
|
|
||||||
b.Property<string>("HttpMethod")
|
|
||||||
.IsRequired()
|
|
||||||
.HasMaxLength(8)
|
|
||||||
.HasColumnType("character varying(8)");
|
|
||||||
|
|
||||||
b.Property<string>("Url")
|
|
||||||
.IsRequired()
|
|
||||||
.HasMaxLength(2048)
|
|
||||||
.HasColumnType("character varying(2048)");
|
|
||||||
|
|
||||||
b.HasKey("Name");
|
|
||||||
|
|
||||||
b.ToTable("NotificationConnectors");
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("AuthorManga", b =>
|
|
||||||
{
|
|
||||||
b.Property<string>("AuthorsAuthorId")
|
|
||||||
.HasColumnType("character varying(64)");
|
|
||||||
|
|
||||||
b.Property<string>("MangaId")
|
|
||||||
.HasColumnType("character varying(64)");
|
|
||||||
|
|
||||||
b.HasKey("AuthorsAuthorId", "MangaId");
|
|
||||||
|
|
||||||
b.HasIndex("MangaId");
|
|
||||||
|
|
||||||
b.ToTable("AuthorManga");
|
|
||||||
});
|
|
||||||
|
|
||||||
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("MangaMangaTag", b =>
|
|
||||||
{
|
|
||||||
b.Property<string>("MangaId")
|
|
||||||
.HasColumnType("character varying(64)");
|
|
||||||
|
|
||||||
b.Property<string>("MangaTagsTag")
|
|
||||||
.HasColumnType("character varying(64)");
|
|
||||||
|
|
||||||
b.HasKey("MangaId", "MangaTagsTag");
|
|
||||||
|
|
||||||
b.HasIndex("MangaTagsTag");
|
|
||||||
|
|
||||||
b.ToTable("MangaMangaTag");
|
|
||||||
});
|
|
||||||
|
|
||||||
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.RetrieveChaptersJob", 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("RetrieveChaptersJob_MangaId");
|
|
||||||
});
|
|
||||||
|
|
||||||
b.HasDiscriminator().HasValue((byte)5);
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("API.Schema.Jobs.UpdateFilesDownloadedJob", 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("UpdateFilesDownloadedJob_MangaId");
|
|
||||||
});
|
|
||||||
|
|
||||||
b.HasDiscriminator().HasValue((byte)6);
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("API.Schema.Jobs.UpdateMetadataJob", 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("UpdateMetadataJob_MangaId");
|
|
||||||
});
|
|
||||||
|
|
||||||
b.HasDiscriminator().HasValue((byte)2);
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("API.Schema.LibraryConnectors.Kavita", b =>
|
|
||||||
{
|
|
||||||
b.HasBaseType("API.Schema.LibraryConnectors.LibraryConnector");
|
|
||||||
|
|
||||||
b.HasDiscriminator().HasValue((byte)1);
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("API.Schema.LibraryConnectors.Komga", b =>
|
|
||||||
{
|
|
||||||
b.HasBaseType("API.Schema.LibraryConnectors.LibraryConnector");
|
|
||||||
|
|
||||||
b.HasDiscriminator().HasValue((byte)0);
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("API.Schema.MangaConnectors.AsuraToon", b =>
|
|
||||||
{
|
|
||||||
b.HasBaseType("API.Schema.MangaConnectors.MangaConnector");
|
|
||||||
|
|
||||||
b.HasDiscriminator().HasValue("AsuraToon");
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("API.Schema.MangaConnectors.Bato", b =>
|
|
||||||
{
|
|
||||||
b.HasBaseType("API.Schema.MangaConnectors.MangaConnector");
|
|
||||||
|
|
||||||
b.HasDiscriminator().HasValue("Bato");
|
|
||||||
});
|
|
||||||
|
|
||||||
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.MangaConnectors.MangaHere", b =>
|
|
||||||
{
|
|
||||||
b.HasBaseType("API.Schema.MangaConnectors.MangaConnector");
|
|
||||||
|
|
||||||
b.HasDiscriminator().HasValue("MangaHere");
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("API.Schema.MangaConnectors.MangaKatana", b =>
|
|
||||||
{
|
|
||||||
b.HasBaseType("API.Schema.MangaConnectors.MangaConnector");
|
|
||||||
|
|
||||||
b.HasDiscriminator().HasValue("MangaKatana");
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("API.Schema.MangaConnectors.Manganato", b =>
|
|
||||||
{
|
|
||||||
b.HasBaseType("API.Schema.MangaConnectors.MangaConnector");
|
|
||||||
|
|
||||||
b.HasDiscriminator().HasValue("Manganato");
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("API.Schema.MangaConnectors.Mangaworld", b =>
|
|
||||||
{
|
|
||||||
b.HasBaseType("API.Schema.MangaConnectors.MangaConnector");
|
|
||||||
|
|
||||||
b.HasDiscriminator().HasValue("Mangaworld");
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("API.Schema.MangaConnectors.ManhuaPlus", b =>
|
|
||||||
{
|
|
||||||
b.HasBaseType("API.Schema.MangaConnectors.MangaConnector");
|
|
||||||
|
|
||||||
b.HasDiscriminator().HasValue("ManhuaPlus");
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("API.Schema.MangaConnectors.Weebcentral", b =>
|
|
||||||
{
|
|
||||||
b.HasBaseType("API.Schema.MangaConnectors.MangaConnector");
|
|
||||||
|
|
||||||
b.HasDiscriminator().HasValue("Weebcentral");
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("API.Schema.Chapter", b =>
|
|
||||||
{
|
|
||||||
b.HasOne("API.Schema.Manga", "ParentManga")
|
|
||||||
.WithMany()
|
|
||||||
.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.Link", b =>
|
|
||||||
{
|
|
||||||
b.HasOne("API.Schema.Manga", null)
|
|
||||||
.WithMany("Links")
|
|
||||||
.HasForeignKey("MangaId")
|
|
||||||
.OnDelete(DeleteBehavior.Cascade);
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("API.Schema.Manga", b =>
|
|
||||||
{
|
|
||||||
b.HasOne("API.Schema.LocalLibrary", "Library")
|
|
||||||
.WithMany()
|
|
||||||
.HasForeignKey("LibraryLocalLibraryId")
|
|
||||||
.OnDelete(DeleteBehavior.Restrict);
|
|
||||||
|
|
||||||
b.HasOne("API.Schema.MangaConnectors.MangaConnector", "MangaConnector")
|
|
||||||
.WithMany()
|
|
||||||
.HasForeignKey("MangaConnectorId")
|
|
||||||
.OnDelete(DeleteBehavior.Cascade)
|
|
||||||
.IsRequired();
|
|
||||||
|
|
||||||
b.Navigation("Library");
|
|
||||||
|
|
||||||
b.Navigation("MangaConnector");
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("API.Schema.MangaAltTitle", b =>
|
|
||||||
{
|
|
||||||
b.HasOne("API.Schema.Manga", null)
|
|
||||||
.WithMany("AltTitles")
|
|
||||||
.HasForeignKey("MangaId")
|
|
||||||
.OnDelete(DeleteBehavior.Cascade);
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("AuthorManga", b =>
|
|
||||||
{
|
|
||||||
b.HasOne("API.Schema.Author", null)
|
|
||||||
.WithMany()
|
|
||||||
.HasForeignKey("AuthorsAuthorId")
|
|
||||||
.OnDelete(DeleteBehavior.Cascade)
|
|
||||||
.IsRequired();
|
|
||||||
|
|
||||||
b.HasOne("API.Schema.Manga", null)
|
|
||||||
.WithMany()
|
|
||||||
.HasForeignKey("MangaId")
|
|
||||||
.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("MangaMangaTag", b =>
|
|
||||||
{
|
|
||||||
b.HasOne("API.Schema.Manga", null)
|
|
||||||
.WithMany()
|
|
||||||
.HasForeignKey("MangaId")
|
|
||||||
.OnDelete(DeleteBehavior.Cascade)
|
|
||||||
.IsRequired();
|
|
||||||
|
|
||||||
b.HasOne("API.Schema.MangaTag", null)
|
|
||||||
.WithMany()
|
|
||||||
.HasForeignKey("MangaTagsTag")
|
|
||||||
.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.RetrieveChaptersJob", b =>
|
|
||||||
{
|
|
||||||
b.HasOne("API.Schema.Manga", "Manga")
|
|
||||||
.WithMany()
|
|
||||||
.HasForeignKey("MangaId")
|
|
||||||
.OnDelete(DeleteBehavior.Cascade)
|
|
||||||
.IsRequired();
|
|
||||||
|
|
||||||
b.Navigation("Manga");
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("API.Schema.Jobs.UpdateFilesDownloadedJob", b =>
|
|
||||||
{
|
|
||||||
b.HasOne("API.Schema.Manga", "Manga")
|
|
||||||
.WithMany()
|
|
||||||
.HasForeignKey("MangaId")
|
|
||||||
.OnDelete(DeleteBehavior.Cascade)
|
|
||||||
.IsRequired();
|
|
||||||
|
|
||||||
b.Navigation("Manga");
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("API.Schema.Jobs.UpdateMetadataJob", 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("AltTitles");
|
|
||||||
|
|
||||||
b.Navigation("Links");
|
|
||||||
});
|
|
||||||
#pragma warning restore 612, 618
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,98 +0,0 @@
|
|||||||
using Microsoft.EntityFrameworkCore.Migrations;
|
|
||||||
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
namespace API.Migrations
|
|
||||||
{
|
|
||||||
/// <inheritdoc />
|
|
||||||
public partial class dev0104252Longer_Var_Chars : Migration
|
|
||||||
{
|
|
||||||
/// <inheritdoc />
|
|
||||||
protected override void Up(MigrationBuilder migrationBuilder)
|
|
||||||
{
|
|
||||||
migrationBuilder.AlterColumn<string>(
|
|
||||||
name: "WebsiteUrl",
|
|
||||||
table: "Mangas",
|
|
||||||
type: "character varying(512)",
|
|
||||||
maxLength: 512,
|
|
||||||
nullable: false,
|
|
||||||
oldClrType: typeof(string),
|
|
||||||
oldType: "character varying(256)",
|
|
||||||
oldMaxLength: 256);
|
|
||||||
|
|
||||||
migrationBuilder.AlterColumn<string>(
|
|
||||||
name: "Name",
|
|
||||||
table: "Mangas",
|
|
||||||
type: "character varying(512)",
|
|
||||||
maxLength: 512,
|
|
||||||
nullable: false,
|
|
||||||
oldClrType: typeof(string),
|
|
||||||
oldType: "character varying(256)",
|
|
||||||
oldMaxLength: 256);
|
|
||||||
|
|
||||||
migrationBuilder.AlterColumn<string>(
|
|
||||||
name: "IdOnConnectorSite",
|
|
||||||
table: "Mangas",
|
|
||||||
type: "character varying(256)",
|
|
||||||
maxLength: 256,
|
|
||||||
nullable: false,
|
|
||||||
oldClrType: typeof(string),
|
|
||||||
oldType: "character varying(128)",
|
|
||||||
oldMaxLength: 128);
|
|
||||||
|
|
||||||
migrationBuilder.AlterColumn<string>(
|
|
||||||
name: "DirectoryName",
|
|
||||||
table: "Mangas",
|
|
||||||
type: "character varying(1024)",
|
|
||||||
maxLength: 1024,
|
|
||||||
nullable: false,
|
|
||||||
oldClrType: typeof(string),
|
|
||||||
oldType: "character varying(256)",
|
|
||||||
oldMaxLength: 256);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
protected override void Down(MigrationBuilder migrationBuilder)
|
|
||||||
{
|
|
||||||
migrationBuilder.AlterColumn<string>(
|
|
||||||
name: "WebsiteUrl",
|
|
||||||
table: "Mangas",
|
|
||||||
type: "character varying(256)",
|
|
||||||
maxLength: 256,
|
|
||||||
nullable: false,
|
|
||||||
oldClrType: typeof(string),
|
|
||||||
oldType: "character varying(512)",
|
|
||||||
oldMaxLength: 512);
|
|
||||||
|
|
||||||
migrationBuilder.AlterColumn<string>(
|
|
||||||
name: "Name",
|
|
||||||
table: "Mangas",
|
|
||||||
type: "character varying(256)",
|
|
||||||
maxLength: 256,
|
|
||||||
nullable: false,
|
|
||||||
oldClrType: typeof(string),
|
|
||||||
oldType: "character varying(512)",
|
|
||||||
oldMaxLength: 512);
|
|
||||||
|
|
||||||
migrationBuilder.AlterColumn<string>(
|
|
||||||
name: "IdOnConnectorSite",
|
|
||||||
table: "Mangas",
|
|
||||||
type: "character varying(128)",
|
|
||||||
maxLength: 128,
|
|
||||||
nullable: false,
|
|
||||||
oldClrType: typeof(string),
|
|
||||||
oldType: "character varying(256)",
|
|
||||||
oldMaxLength: 256);
|
|
||||||
|
|
||||||
migrationBuilder.AlterColumn<string>(
|
|
||||||
name: "DirectoryName",
|
|
||||||
table: "Mangas",
|
|
||||||
type: "character varying(256)",
|
|
||||||
maxLength: 256,
|
|
||||||
nullable: false,
|
|
||||||
oldClrType: typeof(string),
|
|
||||||
oldType: "character varying(1024)",
|
|
||||||
oldMaxLength: 1024);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
762
API/Migrations/20250402001438_dev-010425-4.Designer.cs
generated
762
API/Migrations/20250402001438_dev-010425-4.Designer.cs
generated
@ -1,762 +0,0 @@
|
|||||||
// <auto-generated />
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using API.Schema;
|
|
||||||
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
|
|
||||||
{
|
|
||||||
[DbContext(typeof(PgsqlContext))]
|
|
||||||
[Migration("20250402001438_dev-010425-4")]
|
|
||||||
partial class dev0104254
|
|
||||||
{
|
|
||||||
/// <inheritdoc />
|
|
||||||
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
|
||||||
{
|
|
||||||
#pragma warning disable 612, 618
|
|
||||||
modelBuilder
|
|
||||||
.HasAnnotation("ProductVersion", "9.0.3")
|
|
||||||
.HasAnnotation("Relational:MaxIdentifierLength", 63);
|
|
||||||
|
|
||||||
NpgsqlModelBuilderExtensions.HasPostgresExtension(modelBuilder, "hstore");
|
|
||||||
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>("ParentMangaId")
|
|
||||||
.IsRequired()
|
|
||||||
.HasMaxLength(64)
|
|
||||||
.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.PrimitiveCollection<string[]>("DependsOnJobsIds")
|
|
||||||
.HasMaxLength(64)
|
|
||||||
.HasColumnType("text[]");
|
|
||||||
|
|
||||||
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.LibraryConnectors.LibraryConnector", b =>
|
|
||||||
{
|
|
||||||
b.Property<string>("LibraryConnectorId")
|
|
||||||
.HasMaxLength(64)
|
|
||||||
.HasColumnType("character varying(64)");
|
|
||||||
|
|
||||||
b.Property<string>("Auth")
|
|
||||||
.IsRequired()
|
|
||||||
.HasMaxLength(256)
|
|
||||||
.HasColumnType("character varying(256)");
|
|
||||||
|
|
||||||
b.Property<string>("BaseUrl")
|
|
||||||
.IsRequired()
|
|
||||||
.HasMaxLength(256)
|
|
||||||
.HasColumnType("character varying(256)");
|
|
||||||
|
|
||||||
b.Property<byte>("LibraryType")
|
|
||||||
.HasColumnType("smallint");
|
|
||||||
|
|
||||||
b.HasKey("LibraryConnectorId");
|
|
||||||
|
|
||||||
b.ToTable("LibraryConnectors");
|
|
||||||
|
|
||||||
b.HasDiscriminator<byte>("LibraryType");
|
|
||||||
|
|
||||||
b.UseTphMappingStrategy();
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("API.Schema.Link", b =>
|
|
||||||
{
|
|
||||||
b.Property<string>("LinkId")
|
|
||||||
.HasMaxLength(64)
|
|
||||||
.HasColumnType("character varying(64)");
|
|
||||||
|
|
||||||
b.Property<string>("LinkProvider")
|
|
||||||
.IsRequired()
|
|
||||||
.HasMaxLength(64)
|
|
||||||
.HasColumnType("character varying(64)");
|
|
||||||
|
|
||||||
b.Property<string>("LinkUrl")
|
|
||||||
.IsRequired()
|
|
||||||
.HasMaxLength(2048)
|
|
||||||
.HasColumnType("character varying(2048)");
|
|
||||||
|
|
||||||
b.Property<string>("MangaId")
|
|
||||||
.HasColumnType("character varying(64)");
|
|
||||||
|
|
||||||
b.HasKey("LinkId");
|
|
||||||
|
|
||||||
b.HasIndex("MangaId");
|
|
||||||
|
|
||||||
b.ToTable("Links");
|
|
||||||
});
|
|
||||||
|
|
||||||
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")
|
|
||||||
.HasColumnType("text");
|
|
||||||
|
|
||||||
b.Property<string>("CoverUrl")
|
|
||||||
.IsRequired()
|
|
||||||
.HasColumnType("text");
|
|
||||||
|
|
||||||
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>("IgnoreChapterBefore")
|
|
||||||
.HasColumnType("real");
|
|
||||||
|
|
||||||
b.Property<string>("LibraryLocalLibraryId")
|
|
||||||
.HasColumnType("character varying(64)");
|
|
||||||
|
|
||||||
b.Property<string>("MangaConnectorId")
|
|
||||||
.IsRequired()
|
|
||||||
.HasMaxLength(64)
|
|
||||||
.HasColumnType("character varying(64)");
|
|
||||||
|
|
||||||
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("LibraryLocalLibraryId");
|
|
||||||
|
|
||||||
b.HasIndex("MangaConnectorId");
|
|
||||||
|
|
||||||
b.ToTable("Mangas");
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("API.Schema.MangaAltTitle", b =>
|
|
||||||
{
|
|
||||||
b.Property<string>("AltTitleId")
|
|
||||||
.HasMaxLength(64)
|
|
||||||
.HasColumnType("character varying(64)");
|
|
||||||
|
|
||||||
b.Property<string>("Language")
|
|
||||||
.IsRequired()
|
|
||||||
.HasMaxLength(8)
|
|
||||||
.HasColumnType("character varying(8)");
|
|
||||||
|
|
||||||
b.Property<string>("MangaId")
|
|
||||||
.HasColumnType("character varying(64)");
|
|
||||||
|
|
||||||
b.Property<string>("Title")
|
|
||||||
.IsRequired()
|
|
||||||
.HasMaxLength(256)
|
|
||||||
.HasColumnType("character varying(256)");
|
|
||||||
|
|
||||||
b.HasKey("AltTitleId");
|
|
||||||
|
|
||||||
b.HasIndex("MangaId");
|
|
||||||
|
|
||||||
b.ToTable("AltTitles");
|
|
||||||
});
|
|
||||||
|
|
||||||
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.Notification", b =>
|
|
||||||
{
|
|
||||||
b.Property<string>("NotificationId")
|
|
||||||
.HasMaxLength(64)
|
|
||||||
.HasColumnType("character varying(64)");
|
|
||||||
|
|
||||||
b.Property<DateTime>("Date")
|
|
||||||
.HasColumnType("timestamp with time zone");
|
|
||||||
|
|
||||||
b.Property<string>("Message")
|
|
||||||
.IsRequired()
|
|
||||||
.HasMaxLength(512)
|
|
||||||
.HasColumnType("character varying(512)");
|
|
||||||
|
|
||||||
b.Property<string>("Title")
|
|
||||||
.IsRequired()
|
|
||||||
.HasMaxLength(128)
|
|
||||||
.HasColumnType("character varying(128)");
|
|
||||||
|
|
||||||
b.Property<byte>("Urgency")
|
|
||||||
.HasColumnType("smallint");
|
|
||||||
|
|
||||||
b.HasKey("NotificationId");
|
|
||||||
|
|
||||||
b.ToTable("Notifications");
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("API.Schema.NotificationConnectors.NotificationConnector", b =>
|
|
||||||
{
|
|
||||||
b.Property<string>("Name")
|
|
||||||
.HasMaxLength(64)
|
|
||||||
.HasColumnType("character varying(64)");
|
|
||||||
|
|
||||||
b.Property<string>("Body")
|
|
||||||
.IsRequired()
|
|
||||||
.HasMaxLength(4096)
|
|
||||||
.HasColumnType("character varying(4096)");
|
|
||||||
|
|
||||||
b.Property<Dictionary<string, string>>("Headers")
|
|
||||||
.IsRequired()
|
|
||||||
.HasColumnType("hstore");
|
|
||||||
|
|
||||||
b.Property<string>("HttpMethod")
|
|
||||||
.IsRequired()
|
|
||||||
.HasMaxLength(8)
|
|
||||||
.HasColumnType("character varying(8)");
|
|
||||||
|
|
||||||
b.Property<string>("Url")
|
|
||||||
.IsRequired()
|
|
||||||
.HasMaxLength(2048)
|
|
||||||
.HasColumnType("character varying(2048)");
|
|
||||||
|
|
||||||
b.HasKey("Name");
|
|
||||||
|
|
||||||
b.ToTable("NotificationConnectors");
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("AuthorManga", b =>
|
|
||||||
{
|
|
||||||
b.Property<string>("AuthorsAuthorId")
|
|
||||||
.HasColumnType("character varying(64)");
|
|
||||||
|
|
||||||
b.Property<string>("MangaId")
|
|
||||||
.HasColumnType("character varying(64)");
|
|
||||||
|
|
||||||
b.HasKey("AuthorsAuthorId", "MangaId");
|
|
||||||
|
|
||||||
b.HasIndex("MangaId");
|
|
||||||
|
|
||||||
b.ToTable("AuthorManga");
|
|
||||||
});
|
|
||||||
|
|
||||||
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("MangaMangaTag", b =>
|
|
||||||
{
|
|
||||||
b.Property<string>("MangaId")
|
|
||||||
.HasColumnType("character varying(64)");
|
|
||||||
|
|
||||||
b.Property<string>("MangaTagsTag")
|
|
||||||
.HasColumnType("character varying(64)");
|
|
||||||
|
|
||||||
b.HasKey("MangaId", "MangaTagsTag");
|
|
||||||
|
|
||||||
b.HasIndex("MangaTagsTag");
|
|
||||||
|
|
||||||
b.ToTable("MangaMangaTag");
|
|
||||||
});
|
|
||||||
|
|
||||||
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.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.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.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.RetrieveChaptersJob", b =>
|
|
||||||
{
|
|
||||||
b.HasBaseType("API.Schema.Jobs.Job");
|
|
||||||
|
|
||||||
b.Property<string>("MangaId")
|
|
||||||
.IsRequired()
|
|
||||||
.HasMaxLength(64)
|
|
||||||
.HasColumnType("character varying(64)");
|
|
||||||
|
|
||||||
b.ToTable("Jobs", t =>
|
|
||||||
{
|
|
||||||
t.Property("MangaId")
|
|
||||||
.HasColumnName("RetrieveChaptersJob_MangaId");
|
|
||||||
});
|
|
||||||
|
|
||||||
b.HasDiscriminator().HasValue((byte)5);
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("API.Schema.Jobs.UpdateFilesDownloadedJob", b =>
|
|
||||||
{
|
|
||||||
b.HasBaseType("API.Schema.Jobs.Job");
|
|
||||||
|
|
||||||
b.Property<string>("MangaId")
|
|
||||||
.IsRequired()
|
|
||||||
.HasMaxLength(64)
|
|
||||||
.HasColumnType("character varying(64)");
|
|
||||||
|
|
||||||
b.ToTable("Jobs", t =>
|
|
||||||
{
|
|
||||||
t.Property("MangaId")
|
|
||||||
.HasColumnName("UpdateFilesDownloadedJob_MangaId");
|
|
||||||
});
|
|
||||||
|
|
||||||
b.HasDiscriminator().HasValue((byte)6);
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("API.Schema.Jobs.UpdateMetadataJob", 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("UpdateMetadataJob_MangaId");
|
|
||||||
});
|
|
||||||
|
|
||||||
b.HasDiscriminator().HasValue((byte)2);
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("API.Schema.LibraryConnectors.Kavita", b =>
|
|
||||||
{
|
|
||||||
b.HasBaseType("API.Schema.LibraryConnectors.LibraryConnector");
|
|
||||||
|
|
||||||
b.HasDiscriminator().HasValue((byte)1);
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("API.Schema.LibraryConnectors.Komga", b =>
|
|
||||||
{
|
|
||||||
b.HasBaseType("API.Schema.LibraryConnectors.LibraryConnector");
|
|
||||||
|
|
||||||
b.HasDiscriminator().HasValue((byte)0);
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("API.Schema.MangaConnectors.AsuraToon", b =>
|
|
||||||
{
|
|
||||||
b.HasBaseType("API.Schema.MangaConnectors.MangaConnector");
|
|
||||||
|
|
||||||
b.HasDiscriminator().HasValue("AsuraToon");
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("API.Schema.MangaConnectors.Bato", b =>
|
|
||||||
{
|
|
||||||
b.HasBaseType("API.Schema.MangaConnectors.MangaConnector");
|
|
||||||
|
|
||||||
b.HasDiscriminator().HasValue("Bato");
|
|
||||||
});
|
|
||||||
|
|
||||||
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.MangaConnectors.MangaHere", b =>
|
|
||||||
{
|
|
||||||
b.HasBaseType("API.Schema.MangaConnectors.MangaConnector");
|
|
||||||
|
|
||||||
b.HasDiscriminator().HasValue("MangaHere");
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("API.Schema.MangaConnectors.MangaKatana", b =>
|
|
||||||
{
|
|
||||||
b.HasBaseType("API.Schema.MangaConnectors.MangaConnector");
|
|
||||||
|
|
||||||
b.HasDiscriminator().HasValue("MangaKatana");
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("API.Schema.MangaConnectors.Manganato", b =>
|
|
||||||
{
|
|
||||||
b.HasBaseType("API.Schema.MangaConnectors.MangaConnector");
|
|
||||||
|
|
||||||
b.HasDiscriminator().HasValue("Manganato");
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("API.Schema.MangaConnectors.Mangaworld", b =>
|
|
||||||
{
|
|
||||||
b.HasBaseType("API.Schema.MangaConnectors.MangaConnector");
|
|
||||||
|
|
||||||
b.HasDiscriminator().HasValue("Mangaworld");
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("API.Schema.MangaConnectors.ManhuaPlus", b =>
|
|
||||||
{
|
|
||||||
b.HasBaseType("API.Schema.MangaConnectors.MangaConnector");
|
|
||||||
|
|
||||||
b.HasDiscriminator().HasValue("ManhuaPlus");
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("API.Schema.MangaConnectors.Weebcentral", b =>
|
|
||||||
{
|
|
||||||
b.HasBaseType("API.Schema.MangaConnectors.MangaConnector");
|
|
||||||
|
|
||||||
b.HasDiscriminator().HasValue("Weebcentral");
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("API.Schema.Chapter", b =>
|
|
||||||
{
|
|
||||||
b.HasOne("API.Schema.Manga", "ParentManga")
|
|
||||||
.WithMany()
|
|
||||||
.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.Link", b =>
|
|
||||||
{
|
|
||||||
b.HasOne("API.Schema.Manga", null)
|
|
||||||
.WithMany("Links")
|
|
||||||
.HasForeignKey("MangaId")
|
|
||||||
.OnDelete(DeleteBehavior.Cascade);
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("API.Schema.Manga", b =>
|
|
||||||
{
|
|
||||||
b.HasOne("API.Schema.LocalLibrary", "Library")
|
|
||||||
.WithMany()
|
|
||||||
.HasForeignKey("LibraryLocalLibraryId")
|
|
||||||
.OnDelete(DeleteBehavior.Restrict);
|
|
||||||
|
|
||||||
b.HasOne("API.Schema.MangaConnectors.MangaConnector", "MangaConnector")
|
|
||||||
.WithMany()
|
|
||||||
.HasForeignKey("MangaConnectorId")
|
|
||||||
.OnDelete(DeleteBehavior.Cascade)
|
|
||||||
.IsRequired();
|
|
||||||
|
|
||||||
b.Navigation("Library");
|
|
||||||
|
|
||||||
b.Navigation("MangaConnector");
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("API.Schema.MangaAltTitle", b =>
|
|
||||||
{
|
|
||||||
b.HasOne("API.Schema.Manga", null)
|
|
||||||
.WithMany("AltTitles")
|
|
||||||
.HasForeignKey("MangaId")
|
|
||||||
.OnDelete(DeleteBehavior.Cascade);
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("AuthorManga", b =>
|
|
||||||
{
|
|
||||||
b.HasOne("API.Schema.Author", null)
|
|
||||||
.WithMany()
|
|
||||||
.HasForeignKey("AuthorsAuthorId")
|
|
||||||
.OnDelete(DeleteBehavior.Cascade)
|
|
||||||
.IsRequired();
|
|
||||||
|
|
||||||
b.HasOne("API.Schema.Manga", null)
|
|
||||||
.WithMany()
|
|
||||||
.HasForeignKey("MangaId")
|
|
||||||
.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("MangaMangaTag", b =>
|
|
||||||
{
|
|
||||||
b.HasOne("API.Schema.Manga", null)
|
|
||||||
.WithMany()
|
|
||||||
.HasForeignKey("MangaId")
|
|
||||||
.OnDelete(DeleteBehavior.Cascade)
|
|
||||||
.IsRequired();
|
|
||||||
|
|
||||||
b.HasOne("API.Schema.MangaTag", null)
|
|
||||||
.WithMany()
|
|
||||||
.HasForeignKey("MangaTagsTag")
|
|
||||||
.OnDelete(DeleteBehavior.Cascade)
|
|
||||||
.IsRequired();
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("API.Schema.Jobs.UpdateMetadataJob", 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("AltTitles");
|
|
||||||
|
|
||||||
b.Navigation("Links");
|
|
||||||
});
|
|
||||||
#pragma warning restore 612, 618
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,123 +0,0 @@
|
|||||||
using Microsoft.EntityFrameworkCore.Migrations;
|
|
||||||
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
namespace API.Migrations
|
|
||||||
{
|
|
||||||
/// <inheritdoc />
|
|
||||||
public partial class dev0104254 : Migration
|
|
||||||
{
|
|
||||||
/// <inheritdoc />
|
|
||||||
protected override void Up(MigrationBuilder migrationBuilder)
|
|
||||||
{
|
|
||||||
migrationBuilder.DropForeignKey(
|
|
||||||
name: "FK_Jobs_Chapters_ChapterId",
|
|
||||||
table: "Jobs");
|
|
||||||
|
|
||||||
migrationBuilder.DropForeignKey(
|
|
||||||
name: "FK_Jobs_Mangas_DownloadAvailableChaptersJob_MangaId",
|
|
||||||
table: "Jobs");
|
|
||||||
|
|
||||||
migrationBuilder.DropForeignKey(
|
|
||||||
name: "FK_Jobs_Mangas_MangaId",
|
|
||||||
table: "Jobs");
|
|
||||||
|
|
||||||
migrationBuilder.DropForeignKey(
|
|
||||||
name: "FK_Jobs_Mangas_RetrieveChaptersJob_MangaId",
|
|
||||||
table: "Jobs");
|
|
||||||
|
|
||||||
migrationBuilder.DropForeignKey(
|
|
||||||
name: "FK_Jobs_Mangas_UpdateFilesDownloadedJob_MangaId",
|
|
||||||
table: "Jobs");
|
|
||||||
|
|
||||||
migrationBuilder.DropIndex(
|
|
||||||
name: "IX_Jobs_ChapterId",
|
|
||||||
table: "Jobs");
|
|
||||||
|
|
||||||
migrationBuilder.DropIndex(
|
|
||||||
name: "IX_Jobs_DownloadAvailableChaptersJob_MangaId",
|
|
||||||
table: "Jobs");
|
|
||||||
|
|
||||||
migrationBuilder.DropIndex(
|
|
||||||
name: "IX_Jobs_MangaId",
|
|
||||||
table: "Jobs");
|
|
||||||
|
|
||||||
migrationBuilder.DropIndex(
|
|
||||||
name: "IX_Jobs_RetrieveChaptersJob_MangaId",
|
|
||||||
table: "Jobs");
|
|
||||||
|
|
||||||
migrationBuilder.DropIndex(
|
|
||||||
name: "IX_Jobs_UpdateFilesDownloadedJob_MangaId",
|
|
||||||
table: "Jobs");
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
protected override void Down(MigrationBuilder migrationBuilder)
|
|
||||||
{
|
|
||||||
migrationBuilder.CreateIndex(
|
|
||||||
name: "IX_Jobs_ChapterId",
|
|
||||||
table: "Jobs",
|
|
||||||
column: "ChapterId");
|
|
||||||
|
|
||||||
migrationBuilder.CreateIndex(
|
|
||||||
name: "IX_Jobs_DownloadAvailableChaptersJob_MangaId",
|
|
||||||
table: "Jobs",
|
|
||||||
column: "DownloadAvailableChaptersJob_MangaId");
|
|
||||||
|
|
||||||
migrationBuilder.CreateIndex(
|
|
||||||
name: "IX_Jobs_MangaId",
|
|
||||||
table: "Jobs",
|
|
||||||
column: "MangaId");
|
|
||||||
|
|
||||||
migrationBuilder.CreateIndex(
|
|
||||||
name: "IX_Jobs_RetrieveChaptersJob_MangaId",
|
|
||||||
table: "Jobs",
|
|
||||||
column: "RetrieveChaptersJob_MangaId");
|
|
||||||
|
|
||||||
migrationBuilder.CreateIndex(
|
|
||||||
name: "IX_Jobs_UpdateFilesDownloadedJob_MangaId",
|
|
||||||
table: "Jobs",
|
|
||||||
column: "UpdateFilesDownloadedJob_MangaId");
|
|
||||||
|
|
||||||
migrationBuilder.AddForeignKey(
|
|
||||||
name: "FK_Jobs_Chapters_ChapterId",
|
|
||||||
table: "Jobs",
|
|
||||||
column: "ChapterId",
|
|
||||||
principalTable: "Chapters",
|
|
||||||
principalColumn: "ChapterId",
|
|
||||||
onDelete: ReferentialAction.Cascade);
|
|
||||||
|
|
||||||
migrationBuilder.AddForeignKey(
|
|
||||||
name: "FK_Jobs_Mangas_DownloadAvailableChaptersJob_MangaId",
|
|
||||||
table: "Jobs",
|
|
||||||
column: "DownloadAvailableChaptersJob_MangaId",
|
|
||||||
principalTable: "Mangas",
|
|
||||||
principalColumn: "MangaId",
|
|
||||||
onDelete: ReferentialAction.Cascade);
|
|
||||||
|
|
||||||
migrationBuilder.AddForeignKey(
|
|
||||||
name: "FK_Jobs_Mangas_MangaId",
|
|
||||||
table: "Jobs",
|
|
||||||
column: "MangaId",
|
|
||||||
principalTable: "Mangas",
|
|
||||||
principalColumn: "MangaId",
|
|
||||||
onDelete: ReferentialAction.Cascade);
|
|
||||||
|
|
||||||
migrationBuilder.AddForeignKey(
|
|
||||||
name: "FK_Jobs_Mangas_RetrieveChaptersJob_MangaId",
|
|
||||||
table: "Jobs",
|
|
||||||
column: "RetrieveChaptersJob_MangaId",
|
|
||||||
principalTable: "Mangas",
|
|
||||||
principalColumn: "MangaId",
|
|
||||||
onDelete: ReferentialAction.Cascade);
|
|
||||||
|
|
||||||
migrationBuilder.AddForeignKey(
|
|
||||||
name: "FK_Jobs_Mangas_UpdateFilesDownloadedJob_MangaId",
|
|
||||||
table: "Jobs",
|
|
||||||
column: "UpdateFilesDownloadedJob_MangaId",
|
|
||||||
principalTable: "Mangas",
|
|
||||||
principalColumn: "MangaId",
|
|
||||||
onDelete: ReferentialAction.Cascade);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -13,8 +13,8 @@ using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
|||||||
namespace API.Migrations
|
namespace API.Migrations
|
||||||
{
|
{
|
||||||
[DbContext(typeof(PgsqlContext))]
|
[DbContext(typeof(PgsqlContext))]
|
||||||
[Migration("20250401162026_dev-010425-2-Longer_Var_Chars")]
|
[Migration("20250509033915_Initial")]
|
||||||
partial class dev0104252Longer_Var_Chars
|
partial class Initial
|
||||||
{
|
{
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||||
@ -64,7 +64,6 @@ namespace API.Migrations
|
|||||||
|
|
||||||
b.Property<string>("ParentMangaId")
|
b.Property<string>("ParentMangaId")
|
||||||
.IsRequired()
|
.IsRequired()
|
||||||
.HasMaxLength(64)
|
|
||||||
.HasColumnType("character varying(64)");
|
.HasColumnType("character varying(64)");
|
||||||
|
|
||||||
b.Property<string>("Title")
|
b.Property<string>("Title")
|
||||||
@ -92,10 +91,6 @@ namespace API.Migrations
|
|||||||
.HasMaxLength(64)
|
.HasMaxLength(64)
|
||||||
.HasColumnType("character varying(64)");
|
.HasColumnType("character varying(64)");
|
||||||
|
|
||||||
b.PrimitiveCollection<string[]>("DependsOnJobsIds")
|
|
||||||
.HasMaxLength(64)
|
|
||||||
.HasColumnType("text[]");
|
|
||||||
|
|
||||||
b.Property<bool>("Enabled")
|
b.Property<bool>("Enabled")
|
||||||
.HasColumnType("boolean");
|
.HasColumnType("boolean");
|
||||||
|
|
||||||
@ -106,6 +101,7 @@ namespace API.Migrations
|
|||||||
.HasColumnType("timestamp with time zone");
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
b.Property<string>("ParentJobId")
|
b.Property<string>("ParentJobId")
|
||||||
|
.IsRequired()
|
||||||
.HasMaxLength(64)
|
.HasMaxLength(64)
|
||||||
.HasColumnType("character varying(64)");
|
.HasColumnType("character varying(64)");
|
||||||
|
|
||||||
@ -154,32 +150,6 @@ namespace API.Migrations
|
|||||||
b.UseTphMappingStrategy();
|
b.UseTphMappingStrategy();
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("API.Schema.Link", b =>
|
|
||||||
{
|
|
||||||
b.Property<string>("LinkId")
|
|
||||||
.HasMaxLength(64)
|
|
||||||
.HasColumnType("character varying(64)");
|
|
||||||
|
|
||||||
b.Property<string>("LinkProvider")
|
|
||||||
.IsRequired()
|
|
||||||
.HasMaxLength(64)
|
|
||||||
.HasColumnType("character varying(64)");
|
|
||||||
|
|
||||||
b.Property<string>("LinkUrl")
|
|
||||||
.IsRequired()
|
|
||||||
.HasMaxLength(2048)
|
|
||||||
.HasColumnType("character varying(2048)");
|
|
||||||
|
|
||||||
b.Property<string>("MangaId")
|
|
||||||
.HasColumnType("character varying(64)");
|
|
||||||
|
|
||||||
b.HasKey("LinkId");
|
|
||||||
|
|
||||||
b.HasIndex("MangaId");
|
|
||||||
|
|
||||||
b.ToTable("Links");
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("API.Schema.LocalLibrary", b =>
|
modelBuilder.Entity("API.Schema.LocalLibrary", b =>
|
||||||
{
|
{
|
||||||
b.Property<string>("LocalLibraryId")
|
b.Property<string>("LocalLibraryId")
|
||||||
@ -208,11 +178,13 @@ namespace API.Migrations
|
|||||||
.HasColumnType("character varying(64)");
|
.HasColumnType("character varying(64)");
|
||||||
|
|
||||||
b.Property<string>("CoverFileNameInCache")
|
b.Property<string>("CoverFileNameInCache")
|
||||||
.HasColumnType("text");
|
.HasMaxLength(512)
|
||||||
|
.HasColumnType("character varying(512)");
|
||||||
|
|
||||||
b.Property<string>("CoverUrl")
|
b.Property<string>("CoverUrl")
|
||||||
.IsRequired()
|
.IsRequired()
|
||||||
.HasColumnType("text");
|
.HasMaxLength(512)
|
||||||
|
.HasColumnType("character varying(512)");
|
||||||
|
|
||||||
b.Property<string>("Description")
|
b.Property<string>("Description")
|
||||||
.IsRequired()
|
.IsRequired()
|
||||||
@ -228,17 +200,19 @@ namespace API.Migrations
|
|||||||
.HasMaxLength(256)
|
.HasMaxLength(256)
|
||||||
.HasColumnType("character varying(256)");
|
.HasColumnType("character varying(256)");
|
||||||
|
|
||||||
b.Property<float>("IgnoreChapterBefore")
|
b.Property<float>("IgnoreChaptersBefore")
|
||||||
.HasColumnType("real");
|
.HasColumnType("real");
|
||||||
|
|
||||||
b.Property<string>("LibraryLocalLibraryId")
|
b.Property<string>("LibraryId")
|
||||||
.HasColumnType("character varying(64)");
|
|
||||||
|
|
||||||
b.Property<string>("MangaConnectorId")
|
|
||||||
.IsRequired()
|
.IsRequired()
|
||||||
.HasMaxLength(64)
|
.HasMaxLength(64)
|
||||||
.HasColumnType("character varying(64)");
|
.HasColumnType("character varying(64)");
|
||||||
|
|
||||||
|
b.Property<string>("MangaConnectorName")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(32)
|
||||||
|
.HasColumnType("character varying(32)");
|
||||||
|
|
||||||
b.Property<string>("Name")
|
b.Property<string>("Name")
|
||||||
.IsRequired()
|
.IsRequired()
|
||||||
.HasMaxLength(512)
|
.HasMaxLength(512)
|
||||||
@ -261,39 +235,13 @@ namespace API.Migrations
|
|||||||
|
|
||||||
b.HasKey("MangaId");
|
b.HasKey("MangaId");
|
||||||
|
|
||||||
b.HasIndex("LibraryLocalLibraryId");
|
b.HasIndex("LibraryId");
|
||||||
|
|
||||||
b.HasIndex("MangaConnectorId");
|
b.HasIndex("MangaConnectorName");
|
||||||
|
|
||||||
b.ToTable("Mangas");
|
b.ToTable("Mangas");
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("API.Schema.MangaAltTitle", b =>
|
|
||||||
{
|
|
||||||
b.Property<string>("AltTitleId")
|
|
||||||
.HasMaxLength(64)
|
|
||||||
.HasColumnType("character varying(64)");
|
|
||||||
|
|
||||||
b.Property<string>("Language")
|
|
||||||
.IsRequired()
|
|
||||||
.HasMaxLength(8)
|
|
||||||
.HasColumnType("character varying(8)");
|
|
||||||
|
|
||||||
b.Property<string>("MangaId")
|
|
||||||
.HasColumnType("character varying(64)");
|
|
||||||
|
|
||||||
b.Property<string>("Title")
|
|
||||||
.IsRequired()
|
|
||||||
.HasMaxLength(256)
|
|
||||||
.HasColumnType("character varying(256)");
|
|
||||||
|
|
||||||
b.HasKey("AltTitleId");
|
|
||||||
|
|
||||||
b.HasIndex("MangaId");
|
|
||||||
|
|
||||||
b.ToTable("AltTitles");
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("API.Schema.MangaConnectors.MangaConnector", b =>
|
modelBuilder.Entity("API.Schema.MangaConnectors.MangaConnector", b =>
|
||||||
{
|
{
|
||||||
b.Property<string>("Name")
|
b.Property<string>("Name")
|
||||||
@ -395,19 +343,19 @@ namespace API.Migrations
|
|||||||
b.ToTable("NotificationConnectors");
|
b.ToTable("NotificationConnectors");
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("AuthorManga", b =>
|
modelBuilder.Entity("AuthorToManga", b =>
|
||||||
{
|
{
|
||||||
b.Property<string>("AuthorsAuthorId")
|
b.Property<string>("AuthorIds")
|
||||||
.HasColumnType("character varying(64)");
|
.HasColumnType("character varying(64)");
|
||||||
|
|
||||||
b.Property<string>("MangaId")
|
b.Property<string>("MangaIds")
|
||||||
.HasColumnType("character varying(64)");
|
.HasColumnType("character varying(64)");
|
||||||
|
|
||||||
b.HasKey("AuthorsAuthorId", "MangaId");
|
b.HasKey("AuthorIds", "MangaIds");
|
||||||
|
|
||||||
b.HasIndex("MangaId");
|
b.HasIndex("MangaIds");
|
||||||
|
|
||||||
b.ToTable("AuthorManga");
|
b.ToTable("AuthorToManga");
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("JobJob", b =>
|
modelBuilder.Entity("JobJob", b =>
|
||||||
@ -425,19 +373,19 @@ namespace API.Migrations
|
|||||||
b.ToTable("JobJob");
|
b.ToTable("JobJob");
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("MangaMangaTag", b =>
|
modelBuilder.Entity("MangaTagToManga", b =>
|
||||||
{
|
{
|
||||||
b.Property<string>("MangaId")
|
b.Property<string>("MangaTagIds")
|
||||||
.HasColumnType("character varying(64)");
|
.HasColumnType("character varying(64)");
|
||||||
|
|
||||||
b.Property<string>("MangaTagsTag")
|
b.Property<string>("MangaIds")
|
||||||
.HasColumnType("character varying(64)");
|
.HasColumnType("character varying(64)");
|
||||||
|
|
||||||
b.HasKey("MangaId", "MangaTagsTag");
|
b.HasKey("MangaTagIds", "MangaIds");
|
||||||
|
|
||||||
b.HasIndex("MangaTagsTag");
|
b.HasIndex("MangaIds");
|
||||||
|
|
||||||
b.ToTable("MangaMangaTag");
|
b.ToTable("MangaTagToManga");
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("API.Schema.Jobs.DownloadAvailableChaptersJob", b =>
|
modelBuilder.Entity("API.Schema.Jobs.DownloadAvailableChaptersJob", b =>
|
||||||
@ -505,10 +453,42 @@ namespace API.Migrations
|
|||||||
b.HasDiscriminator().HasValue((byte)3);
|
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 =>
|
modelBuilder.Entity("API.Schema.Jobs.RetrieveChaptersJob", b =>
|
||||||
{
|
{
|
||||||
b.HasBaseType("API.Schema.Jobs.Job");
|
b.HasBaseType("API.Schema.Jobs.Job");
|
||||||
|
|
||||||
|
b.Property<string>("Language")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(8)
|
||||||
|
.HasColumnType("character varying(8)");
|
||||||
|
|
||||||
b.Property<string>("MangaId")
|
b.Property<string>("MangaId")
|
||||||
.IsRequired()
|
.IsRequired()
|
||||||
.HasMaxLength(64)
|
.HasMaxLength(64)
|
||||||
@ -545,26 +525,6 @@ namespace API.Migrations
|
|||||||
b.HasDiscriminator().HasValue((byte)6);
|
b.HasDiscriminator().HasValue((byte)6);
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("API.Schema.Jobs.UpdateMetadataJob", 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("UpdateMetadataJob_MangaId");
|
|
||||||
});
|
|
||||||
|
|
||||||
b.HasDiscriminator().HasValue((byte)2);
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("API.Schema.LibraryConnectors.Kavita", b =>
|
modelBuilder.Entity("API.Schema.LibraryConnectors.Kavita", b =>
|
||||||
{
|
{
|
||||||
b.HasBaseType("API.Schema.LibraryConnectors.LibraryConnector");
|
b.HasBaseType("API.Schema.LibraryConnectors.LibraryConnector");
|
||||||
@ -579,20 +539,6 @@ namespace API.Migrations
|
|||||||
b.HasDiscriminator().HasValue((byte)0);
|
b.HasDiscriminator().HasValue((byte)0);
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("API.Schema.MangaConnectors.AsuraToon", b =>
|
|
||||||
{
|
|
||||||
b.HasBaseType("API.Schema.MangaConnectors.MangaConnector");
|
|
||||||
|
|
||||||
b.HasDiscriminator().HasValue("AsuraToon");
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("API.Schema.MangaConnectors.Bato", b =>
|
|
||||||
{
|
|
||||||
b.HasBaseType("API.Schema.MangaConnectors.MangaConnector");
|
|
||||||
|
|
||||||
b.HasDiscriminator().HasValue("Bato");
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("API.Schema.MangaConnectors.Global", b =>
|
modelBuilder.Entity("API.Schema.MangaConnectors.Global", b =>
|
||||||
{
|
{
|
||||||
b.HasBaseType("API.Schema.MangaConnectors.MangaConnector");
|
b.HasBaseType("API.Schema.MangaConnectors.MangaConnector");
|
||||||
@ -607,52 +553,10 @@ namespace API.Migrations
|
|||||||
b.HasDiscriminator().HasValue("MangaDex");
|
b.HasDiscriminator().HasValue("MangaDex");
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("API.Schema.MangaConnectors.MangaHere", b =>
|
|
||||||
{
|
|
||||||
b.HasBaseType("API.Schema.MangaConnectors.MangaConnector");
|
|
||||||
|
|
||||||
b.HasDiscriminator().HasValue("MangaHere");
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("API.Schema.MangaConnectors.MangaKatana", b =>
|
|
||||||
{
|
|
||||||
b.HasBaseType("API.Schema.MangaConnectors.MangaConnector");
|
|
||||||
|
|
||||||
b.HasDiscriminator().HasValue("MangaKatana");
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("API.Schema.MangaConnectors.Manganato", b =>
|
|
||||||
{
|
|
||||||
b.HasBaseType("API.Schema.MangaConnectors.MangaConnector");
|
|
||||||
|
|
||||||
b.HasDiscriminator().HasValue("Manganato");
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("API.Schema.MangaConnectors.Mangaworld", b =>
|
|
||||||
{
|
|
||||||
b.HasBaseType("API.Schema.MangaConnectors.MangaConnector");
|
|
||||||
|
|
||||||
b.HasDiscriminator().HasValue("Mangaworld");
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("API.Schema.MangaConnectors.ManhuaPlus", b =>
|
|
||||||
{
|
|
||||||
b.HasBaseType("API.Schema.MangaConnectors.MangaConnector");
|
|
||||||
|
|
||||||
b.HasDiscriminator().HasValue("ManhuaPlus");
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("API.Schema.MangaConnectors.Weebcentral", b =>
|
|
||||||
{
|
|
||||||
b.HasBaseType("API.Schema.MangaConnectors.MangaConnector");
|
|
||||||
|
|
||||||
b.HasDiscriminator().HasValue("Weebcentral");
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("API.Schema.Chapter", b =>
|
modelBuilder.Entity("API.Schema.Chapter", b =>
|
||||||
{
|
{
|
||||||
b.HasOne("API.Schema.Manga", "ParentManga")
|
b.HasOne("API.Schema.Manga", "ParentManga")
|
||||||
.WithMany()
|
.WithMany("Chapters")
|
||||||
.HasForeignKey("ParentMangaId")
|
.HasForeignKey("ParentMangaId")
|
||||||
.OnDelete(DeleteBehavior.Cascade)
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
.IsRequired();
|
.IsRequired();
|
||||||
@ -670,51 +574,100 @@ namespace API.Migrations
|
|||||||
b.Navigation("ParentJob");
|
b.Navigation("ParentJob");
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("API.Schema.Link", b =>
|
|
||||||
{
|
|
||||||
b.HasOne("API.Schema.Manga", null)
|
|
||||||
.WithMany("Links")
|
|
||||||
.HasForeignKey("MangaId")
|
|
||||||
.OnDelete(DeleteBehavior.Cascade);
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("API.Schema.Manga", b =>
|
modelBuilder.Entity("API.Schema.Manga", b =>
|
||||||
{
|
{
|
||||||
b.HasOne("API.Schema.LocalLibrary", "Library")
|
b.HasOne("API.Schema.LocalLibrary", "Library")
|
||||||
.WithMany()
|
.WithMany()
|
||||||
.HasForeignKey("LibraryLocalLibraryId")
|
.HasForeignKey("LibraryId")
|
||||||
.OnDelete(DeleteBehavior.Restrict);
|
.OnDelete(DeleteBehavior.SetNull)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
b.HasOne("API.Schema.MangaConnectors.MangaConnector", "MangaConnector")
|
b.HasOne("API.Schema.MangaConnectors.MangaConnector", "MangaConnector")
|
||||||
.WithMany()
|
.WithMany()
|
||||||
.HasForeignKey("MangaConnectorId")
|
.HasForeignKey("MangaConnectorName")
|
||||||
.OnDelete(DeleteBehavior.Cascade)
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
.IsRequired();
|
.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("Links");
|
||||||
|
|
||||||
|
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("AltTitles");
|
||||||
|
|
||||||
|
b1.WithOwner()
|
||||||
|
.HasForeignKey("MangaId");
|
||||||
|
});
|
||||||
|
|
||||||
|
b.Navigation("AltTitles");
|
||||||
|
|
||||||
b.Navigation("Library");
|
b.Navigation("Library");
|
||||||
|
|
||||||
|
b.Navigation("Links");
|
||||||
|
|
||||||
b.Navigation("MangaConnector");
|
b.Navigation("MangaConnector");
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("API.Schema.MangaAltTitle", b =>
|
modelBuilder.Entity("AuthorToManga", b =>
|
||||||
{
|
|
||||||
b.HasOne("API.Schema.Manga", null)
|
|
||||||
.WithMany("AltTitles")
|
|
||||||
.HasForeignKey("MangaId")
|
|
||||||
.OnDelete(DeleteBehavior.Cascade);
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("AuthorManga", b =>
|
|
||||||
{
|
{
|
||||||
b.HasOne("API.Schema.Author", null)
|
b.HasOne("API.Schema.Author", null)
|
||||||
.WithMany()
|
.WithMany()
|
||||||
.HasForeignKey("AuthorsAuthorId")
|
.HasForeignKey("AuthorIds")
|
||||||
.OnDelete(DeleteBehavior.Cascade)
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
.IsRequired();
|
.IsRequired();
|
||||||
|
|
||||||
b.HasOne("API.Schema.Manga", null)
|
b.HasOne("API.Schema.Manga", null)
|
||||||
.WithMany()
|
.WithMany()
|
||||||
.HasForeignKey("MangaId")
|
.HasForeignKey("MangaIds")
|
||||||
.OnDelete(DeleteBehavior.Cascade)
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
.IsRequired();
|
.IsRequired();
|
||||||
});
|
});
|
||||||
@ -734,17 +687,17 @@ namespace API.Migrations
|
|||||||
.IsRequired();
|
.IsRequired();
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("MangaMangaTag", b =>
|
modelBuilder.Entity("MangaTagToManga", b =>
|
||||||
{
|
{
|
||||||
b.HasOne("API.Schema.Manga", null)
|
b.HasOne("API.Schema.Manga", null)
|
||||||
.WithMany()
|
.WithMany()
|
||||||
.HasForeignKey("MangaId")
|
.HasForeignKey("MangaIds")
|
||||||
.OnDelete(DeleteBehavior.Cascade)
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
.IsRequired();
|
.IsRequired();
|
||||||
|
|
||||||
b.HasOne("API.Schema.MangaTag", null)
|
b.HasOne("API.Schema.MangaTag", null)
|
||||||
.WithMany()
|
.WithMany()
|
||||||
.HasForeignKey("MangaTagsTag")
|
.HasForeignKey("MangaTagIds")
|
||||||
.OnDelete(DeleteBehavior.Cascade)
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
.IsRequired();
|
.IsRequired();
|
||||||
});
|
});
|
||||||
@ -782,6 +735,25 @@ namespace API.Migrations
|
|||||||
b.Navigation("Chapter");
|
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 =>
|
modelBuilder.Entity("API.Schema.Jobs.RetrieveChaptersJob", b =>
|
||||||
{
|
{
|
||||||
b.HasOne("API.Schema.Manga", "Manga")
|
b.HasOne("API.Schema.Manga", "Manga")
|
||||||
@ -804,22 +776,9 @@ namespace API.Migrations
|
|||||||
b.Navigation("Manga");
|
b.Navigation("Manga");
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("API.Schema.Jobs.UpdateMetadataJob", b =>
|
|
||||||
{
|
|
||||||
b.HasOne("API.Schema.Manga", "Manga")
|
|
||||||
.WithMany()
|
|
||||||
.HasForeignKey("MangaId")
|
|
||||||
.OnDelete(DeleteBehavior.Cascade)
|
|
||||||
.IsRequired();
|
|
||||||
|
|
||||||
b.Navigation("Manga");
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("API.Schema.Manga", b =>
|
modelBuilder.Entity("API.Schema.Manga", b =>
|
||||||
{
|
{
|
||||||
b.Navigation("AltTitles");
|
b.Navigation("Chapters");
|
||||||
|
|
||||||
b.Navigation("Links");
|
|
||||||
});
|
});
|
||||||
#pragma warning restore 612, 618
|
#pragma warning restore 612, 618
|
||||||
}
|
}
|
@ -7,7 +7,7 @@ using Microsoft.EntityFrameworkCore.Migrations;
|
|||||||
namespace API.Migrations
|
namespace API.Migrations
|
||||||
{
|
{
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public partial class dev160325Initial : Migration
|
public partial class Initial : Migration
|
||||||
{
|
{
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
protected override void Up(MigrationBuilder migrationBuilder)
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
@ -115,32 +115,32 @@ namespace API.Migrations
|
|||||||
columns: table => new
|
columns: table => new
|
||||||
{
|
{
|
||||||
MangaId = table.Column<string>(type: "character varying(64)", maxLength: 64, nullable: false),
|
MangaId = table.Column<string>(type: "character varying(64)", maxLength: 64, nullable: false),
|
||||||
IdOnConnectorSite = table.Column<string>(type: "character varying(128)", maxLength: 128, nullable: false),
|
IdOnConnectorSite = table.Column<string>(type: "character varying(256)", maxLength: 256, nullable: false),
|
||||||
Name = table.Column<string>(type: "character varying(256)", maxLength: 256, nullable: false),
|
Name = table.Column<string>(type: "character varying(512)", maxLength: 512, nullable: false),
|
||||||
Description = table.Column<string>(type: "text", nullable: false),
|
Description = table.Column<string>(type: "text", nullable: false),
|
||||||
WebsiteUrl = table.Column<string>(type: "character varying(256)", maxLength: 256, nullable: false),
|
WebsiteUrl = table.Column<string>(type: "character varying(512)", maxLength: 512, nullable: false),
|
||||||
CoverUrl = table.Column<string>(type: "text", nullable: false),
|
CoverUrl = table.Column<string>(type: "character varying(512)", maxLength: 512, nullable: false),
|
||||||
CoverFileNameInCache = table.Column<string>(type: "text", nullable: true),
|
|
||||||
Year = table.Column<long>(type: "bigint", nullable: false),
|
|
||||||
OriginalLanguage = table.Column<string>(type: "character varying(8)", maxLength: 8, nullable: false),
|
|
||||||
ReleaseStatus = table.Column<byte>(type: "smallint", nullable: false),
|
ReleaseStatus = table.Column<byte>(type: "smallint", nullable: false),
|
||||||
DirectoryName = table.Column<string>(type: "character varying(256)", maxLength: 256, nullable: false),
|
LibraryId = table.Column<string>(type: "character varying(64)", maxLength: 64, nullable: false),
|
||||||
LibraryLocalLibraryId = table.Column<string>(type: "character varying(64)", nullable: true),
|
MangaConnectorName = table.Column<string>(type: "character varying(32)", maxLength: 32, nullable: false),
|
||||||
IgnoreChapterBefore = table.Column<float>(type: "real", nullable: false),
|
IgnoreChaptersBefore = table.Column<float>(type: "real", nullable: false),
|
||||||
MangaConnectorId = table.Column<string>(type: "character varying(64)", maxLength: 64, nullable: false)
|
DirectoryName = table.Column<string>(type: "character varying(1024)", maxLength: 1024, nullable: false),
|
||||||
|
CoverFileNameInCache = table.Column<string>(type: "character varying(512)", maxLength: 512, nullable: true),
|
||||||
|
Year = table.Column<long>(type: "bigint", nullable: false),
|
||||||
|
OriginalLanguage = table.Column<string>(type: "character varying(8)", maxLength: 8, nullable: true)
|
||||||
},
|
},
|
||||||
constraints: table =>
|
constraints: table =>
|
||||||
{
|
{
|
||||||
table.PrimaryKey("PK_Mangas", x => x.MangaId);
|
table.PrimaryKey("PK_Mangas", x => x.MangaId);
|
||||||
table.ForeignKey(
|
table.ForeignKey(
|
||||||
name: "FK_Mangas_LocalLibraries_LibraryLocalLibraryId",
|
name: "FK_Mangas_LocalLibraries_LibraryId",
|
||||||
column: x => x.LibraryLocalLibraryId,
|
column: x => x.LibraryId,
|
||||||
principalTable: "LocalLibraries",
|
principalTable: "LocalLibraries",
|
||||||
principalColumn: "LocalLibraryId",
|
principalColumn: "LocalLibraryId",
|
||||||
onDelete: ReferentialAction.Cascade);
|
onDelete: ReferentialAction.SetNull);
|
||||||
table.ForeignKey(
|
table.ForeignKey(
|
||||||
name: "FK_Mangas_MangaConnectors_MangaConnectorId",
|
name: "FK_Mangas_MangaConnectors_MangaConnectorName",
|
||||||
column: x => x.MangaConnectorId,
|
column: x => x.MangaConnectorName,
|
||||||
principalTable: "MangaConnectors",
|
principalTable: "MangaConnectors",
|
||||||
principalColumn: "Name",
|
principalColumn: "Name",
|
||||||
onDelete: ReferentialAction.Cascade);
|
onDelete: ReferentialAction.Cascade);
|
||||||
@ -153,7 +153,7 @@ namespace API.Migrations
|
|||||||
AltTitleId = table.Column<string>(type: "character varying(64)", maxLength: 64, nullable: false),
|
AltTitleId = table.Column<string>(type: "character varying(64)", maxLength: 64, nullable: false),
|
||||||
Language = table.Column<string>(type: "character varying(8)", maxLength: 8, nullable: false),
|
Language = table.Column<string>(type: "character varying(8)", maxLength: 8, nullable: false),
|
||||||
Title = table.Column<string>(type: "character varying(256)", maxLength: 256, nullable: false),
|
Title = table.Column<string>(type: "character varying(256)", maxLength: 256, nullable: false),
|
||||||
MangaId = table.Column<string>(type: "character varying(64)", nullable: true)
|
MangaId = table.Column<string>(type: "character varying(64)", nullable: false)
|
||||||
},
|
},
|
||||||
constraints: table =>
|
constraints: table =>
|
||||||
{
|
{
|
||||||
@ -167,24 +167,24 @@ namespace API.Migrations
|
|||||||
});
|
});
|
||||||
|
|
||||||
migrationBuilder.CreateTable(
|
migrationBuilder.CreateTable(
|
||||||
name: "AuthorManga",
|
name: "AuthorToManga",
|
||||||
columns: table => new
|
columns: table => new
|
||||||
{
|
{
|
||||||
AuthorsAuthorId = table.Column<string>(type: "character varying(64)", nullable: false),
|
AuthorIds = table.Column<string>(type: "character varying(64)", nullable: false),
|
||||||
MangaId = table.Column<string>(type: "character varying(64)", nullable: false)
|
MangaIds = table.Column<string>(type: "character varying(64)", nullable: false)
|
||||||
},
|
},
|
||||||
constraints: table =>
|
constraints: table =>
|
||||||
{
|
{
|
||||||
table.PrimaryKey("PK_AuthorManga", x => new { x.AuthorsAuthorId, x.MangaId });
|
table.PrimaryKey("PK_AuthorToManga", x => new { x.AuthorIds, x.MangaIds });
|
||||||
table.ForeignKey(
|
table.ForeignKey(
|
||||||
name: "FK_AuthorManga_Authors_AuthorsAuthorId",
|
name: "FK_AuthorToManga_Authors_AuthorIds",
|
||||||
column: x => x.AuthorsAuthorId,
|
column: x => x.AuthorIds,
|
||||||
principalTable: "Authors",
|
principalTable: "Authors",
|
||||||
principalColumn: "AuthorId",
|
principalColumn: "AuthorId",
|
||||||
onDelete: ReferentialAction.Cascade);
|
onDelete: ReferentialAction.Cascade);
|
||||||
table.ForeignKey(
|
table.ForeignKey(
|
||||||
name: "FK_AuthorManga_Mangas_MangaId",
|
name: "FK_AuthorToManga_Mangas_MangaIds",
|
||||||
column: x => x.MangaId,
|
column: x => x.MangaIds,
|
||||||
principalTable: "Mangas",
|
principalTable: "Mangas",
|
||||||
principalColumn: "MangaId",
|
principalColumn: "MangaId",
|
||||||
onDelete: ReferentialAction.Cascade);
|
onDelete: ReferentialAction.Cascade);
|
||||||
@ -195,13 +195,13 @@ namespace API.Migrations
|
|||||||
columns: table => new
|
columns: table => new
|
||||||
{
|
{
|
||||||
ChapterId = table.Column<string>(type: "character varying(64)", maxLength: 64, nullable: false),
|
ChapterId = table.Column<string>(type: "character varying(64)", maxLength: 64, nullable: false),
|
||||||
|
ParentMangaId = table.Column<string>(type: "character varying(64)", nullable: false),
|
||||||
VolumeNumber = table.Column<int>(type: "integer", nullable: true),
|
VolumeNumber = table.Column<int>(type: "integer", nullable: true),
|
||||||
ChapterNumber = table.Column<string>(type: "character varying(10)", maxLength: 10, nullable: false),
|
ChapterNumber = table.Column<string>(type: "character varying(10)", maxLength: 10, nullable: false),
|
||||||
Url = table.Column<string>(type: "character varying(2048)", maxLength: 2048, nullable: false),
|
Url = table.Column<string>(type: "character varying(2048)", maxLength: 2048, nullable: false),
|
||||||
Title = table.Column<string>(type: "character varying(256)", maxLength: 256, nullable: true),
|
Title = table.Column<string>(type: "character varying(256)", maxLength: 256, nullable: true),
|
||||||
FileName = table.Column<string>(type: "character varying(256)", maxLength: 256, nullable: false),
|
FileName = table.Column<string>(type: "character varying(256)", maxLength: 256, nullable: false),
|
||||||
Downloaded = table.Column<bool>(type: "boolean", nullable: false),
|
Downloaded = table.Column<bool>(type: "boolean", nullable: false)
|
||||||
ParentMangaId = table.Column<string>(type: "character varying(64)", maxLength: 64, nullable: false)
|
|
||||||
},
|
},
|
||||||
constraints: table =>
|
constraints: table =>
|
||||||
{
|
{
|
||||||
@ -221,7 +221,7 @@ namespace API.Migrations
|
|||||||
LinkId = table.Column<string>(type: "character varying(64)", maxLength: 64, nullable: false),
|
LinkId = table.Column<string>(type: "character varying(64)", maxLength: 64, nullable: false),
|
||||||
LinkProvider = table.Column<string>(type: "character varying(64)", maxLength: 64, nullable: false),
|
LinkProvider = table.Column<string>(type: "character varying(64)", maxLength: 64, nullable: false),
|
||||||
LinkUrl = table.Column<string>(type: "character varying(2048)", maxLength: 2048, nullable: false),
|
LinkUrl = table.Column<string>(type: "character varying(2048)", maxLength: 2048, nullable: false),
|
||||||
MangaId = table.Column<string>(type: "character varying(64)", nullable: true)
|
MangaId = table.Column<string>(type: "character varying(64)", nullable: false)
|
||||||
},
|
},
|
||||||
constraints: table =>
|
constraints: table =>
|
||||||
{
|
{
|
||||||
@ -235,24 +235,24 @@ namespace API.Migrations
|
|||||||
});
|
});
|
||||||
|
|
||||||
migrationBuilder.CreateTable(
|
migrationBuilder.CreateTable(
|
||||||
name: "MangaMangaTag",
|
name: "MangaTagToManga",
|
||||||
columns: table => new
|
columns: table => new
|
||||||
{
|
{
|
||||||
MangaId = table.Column<string>(type: "character varying(64)", nullable: false),
|
MangaTagIds = table.Column<string>(type: "character varying(64)", nullable: false),
|
||||||
MangaTagsTag = table.Column<string>(type: "character varying(64)", nullable: false)
|
MangaIds = table.Column<string>(type: "character varying(64)", nullable: false)
|
||||||
},
|
},
|
||||||
constraints: table =>
|
constraints: table =>
|
||||||
{
|
{
|
||||||
table.PrimaryKey("PK_MangaMangaTag", x => new { x.MangaId, x.MangaTagsTag });
|
table.PrimaryKey("PK_MangaTagToManga", x => new { x.MangaTagIds, x.MangaIds });
|
||||||
table.ForeignKey(
|
table.ForeignKey(
|
||||||
name: "FK_MangaMangaTag_Mangas_MangaId",
|
name: "FK_MangaTagToManga_Mangas_MangaIds",
|
||||||
column: x => x.MangaId,
|
column: x => x.MangaIds,
|
||||||
principalTable: "Mangas",
|
principalTable: "Mangas",
|
||||||
principalColumn: "MangaId",
|
principalColumn: "MangaId",
|
||||||
onDelete: ReferentialAction.Cascade);
|
onDelete: ReferentialAction.Cascade);
|
||||||
table.ForeignKey(
|
table.ForeignKey(
|
||||||
name: "FK_MangaMangaTag_Tags_MangaTagsTag",
|
name: "FK_MangaTagToManga_Tags_MangaTagIds",
|
||||||
column: x => x.MangaTagsTag,
|
column: x => x.MangaTagIds,
|
||||||
principalTable: "Tags",
|
principalTable: "Tags",
|
||||||
principalColumn: "Tag",
|
principalColumn: "Tag",
|
||||||
onDelete: ReferentialAction.Cascade);
|
onDelete: ReferentialAction.Cascade);
|
||||||
@ -263,8 +263,7 @@ namespace API.Migrations
|
|||||||
columns: table => new
|
columns: table => new
|
||||||
{
|
{
|
||||||
JobId = table.Column<string>(type: "character varying(64)", maxLength: 64, nullable: false),
|
JobId = table.Column<string>(type: "character varying(64)", maxLength: 64, nullable: false),
|
||||||
ParentJobId = table.Column<string>(type: "character varying(64)", maxLength: 64, nullable: true),
|
ParentJobId = table.Column<string>(type: "character varying(64)", maxLength: 64, nullable: false),
|
||||||
DependsOnJobsIds = table.Column<string[]>(type: "text[]", maxLength: 64, nullable: true),
|
|
||||||
JobType = table.Column<byte>(type: "smallint", nullable: false),
|
JobType = table.Column<byte>(type: "smallint", nullable: false),
|
||||||
RecurrenceMs = table.Column<decimal>(type: "numeric(20,0)", nullable: false),
|
RecurrenceMs = table.Column<decimal>(type: "numeric(20,0)", nullable: false),
|
||||||
LastExecution = table.Column<DateTime>(type: "timestamp with time zone", nullable: false),
|
LastExecution = table.Column<DateTime>(type: "timestamp with time zone", nullable: false),
|
||||||
@ -275,9 +274,11 @@ namespace API.Migrations
|
|||||||
ChapterId = table.Column<string>(type: "character varying(64)", maxLength: 64, nullable: true),
|
ChapterId = table.Column<string>(type: "character varying(64)", maxLength: 64, nullable: true),
|
||||||
FromLocation = table.Column<string>(type: "character varying(256)", maxLength: 256, nullable: true),
|
FromLocation = table.Column<string>(type: "character varying(256)", maxLength: 256, nullable: true),
|
||||||
ToLocation = table.Column<string>(type: "character varying(256)", maxLength: 256, nullable: true),
|
ToLocation = table.Column<string>(type: "character varying(256)", maxLength: 256, nullable: true),
|
||||||
|
MoveMangaLibraryJob_MangaId = table.Column<string>(type: "character varying(64)", maxLength: 64, nullable: true),
|
||||||
|
ToLibraryId = table.Column<string>(type: "character varying(64)", maxLength: 64, nullable: true),
|
||||||
RetrieveChaptersJob_MangaId = table.Column<string>(type: "character varying(64)", maxLength: 64, nullable: true),
|
RetrieveChaptersJob_MangaId = table.Column<string>(type: "character varying(64)", maxLength: 64, nullable: true),
|
||||||
UpdateFilesDownloadedJob_MangaId = table.Column<string>(type: "character varying(64)", maxLength: 64, nullable: true),
|
Language = table.Column<string>(type: "character varying(8)", maxLength: 8, nullable: true),
|
||||||
UpdateMetadataJob_MangaId = table.Column<string>(type: "character varying(64)", maxLength: 64, nullable: true)
|
UpdateFilesDownloadedJob_MangaId = table.Column<string>(type: "character varying(64)", maxLength: 64, nullable: true)
|
||||||
},
|
},
|
||||||
constraints: table =>
|
constraints: table =>
|
||||||
{
|
{
|
||||||
@ -294,6 +295,12 @@ namespace API.Migrations
|
|||||||
principalTable: "Jobs",
|
principalTable: "Jobs",
|
||||||
principalColumn: "JobId",
|
principalColumn: "JobId",
|
||||||
onDelete: ReferentialAction.Cascade);
|
onDelete: ReferentialAction.Cascade);
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "FK_Jobs_LocalLibraries_ToLibraryId",
|
||||||
|
column: x => x.ToLibraryId,
|
||||||
|
principalTable: "LocalLibraries",
|
||||||
|
principalColumn: "LocalLibraryId",
|
||||||
|
onDelete: ReferentialAction.Cascade);
|
||||||
table.ForeignKey(
|
table.ForeignKey(
|
||||||
name: "FK_Jobs_Mangas_DownloadAvailableChaptersJob_MangaId",
|
name: "FK_Jobs_Mangas_DownloadAvailableChaptersJob_MangaId",
|
||||||
column: x => x.DownloadAvailableChaptersJob_MangaId,
|
column: x => x.DownloadAvailableChaptersJob_MangaId,
|
||||||
@ -306,6 +313,12 @@ namespace API.Migrations
|
|||||||
principalTable: "Mangas",
|
principalTable: "Mangas",
|
||||||
principalColumn: "MangaId",
|
principalColumn: "MangaId",
|
||||||
onDelete: ReferentialAction.Cascade);
|
onDelete: ReferentialAction.Cascade);
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "FK_Jobs_Mangas_MoveMangaLibraryJob_MangaId",
|
||||||
|
column: x => x.MoveMangaLibraryJob_MangaId,
|
||||||
|
principalTable: "Mangas",
|
||||||
|
principalColumn: "MangaId",
|
||||||
|
onDelete: ReferentialAction.Cascade);
|
||||||
table.ForeignKey(
|
table.ForeignKey(
|
||||||
name: "FK_Jobs_Mangas_RetrieveChaptersJob_MangaId",
|
name: "FK_Jobs_Mangas_RetrieveChaptersJob_MangaId",
|
||||||
column: x => x.RetrieveChaptersJob_MangaId,
|
column: x => x.RetrieveChaptersJob_MangaId,
|
||||||
@ -318,12 +331,6 @@ namespace API.Migrations
|
|||||||
principalTable: "Mangas",
|
principalTable: "Mangas",
|
||||||
principalColumn: "MangaId",
|
principalColumn: "MangaId",
|
||||||
onDelete: ReferentialAction.Cascade);
|
onDelete: ReferentialAction.Cascade);
|
||||||
table.ForeignKey(
|
|
||||||
name: "FK_Jobs_Mangas_UpdateMetadataJob_MangaId",
|
|
||||||
column: x => x.UpdateMetadataJob_MangaId,
|
|
||||||
principalTable: "Mangas",
|
|
||||||
principalColumn: "MangaId",
|
|
||||||
onDelete: ReferentialAction.Cascade);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
migrationBuilder.CreateTable(
|
migrationBuilder.CreateTable(
|
||||||
@ -356,9 +363,9 @@ namespace API.Migrations
|
|||||||
column: "MangaId");
|
column: "MangaId");
|
||||||
|
|
||||||
migrationBuilder.CreateIndex(
|
migrationBuilder.CreateIndex(
|
||||||
name: "IX_AuthorManga_MangaId",
|
name: "IX_AuthorToManga_MangaIds",
|
||||||
table: "AuthorManga",
|
table: "AuthorToManga",
|
||||||
column: "MangaId");
|
column: "MangaIds");
|
||||||
|
|
||||||
migrationBuilder.CreateIndex(
|
migrationBuilder.CreateIndex(
|
||||||
name: "IX_Chapters_ParentMangaId",
|
name: "IX_Chapters_ParentMangaId",
|
||||||
@ -385,6 +392,11 @@ namespace API.Migrations
|
|||||||
table: "Jobs",
|
table: "Jobs",
|
||||||
column: "MangaId");
|
column: "MangaId");
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_Jobs_MoveMangaLibraryJob_MangaId",
|
||||||
|
table: "Jobs",
|
||||||
|
column: "MoveMangaLibraryJob_MangaId");
|
||||||
|
|
||||||
migrationBuilder.CreateIndex(
|
migrationBuilder.CreateIndex(
|
||||||
name: "IX_Jobs_ParentJobId",
|
name: "IX_Jobs_ParentJobId",
|
||||||
table: "Jobs",
|
table: "Jobs",
|
||||||
@ -395,35 +407,35 @@ namespace API.Migrations
|
|||||||
table: "Jobs",
|
table: "Jobs",
|
||||||
column: "RetrieveChaptersJob_MangaId");
|
column: "RetrieveChaptersJob_MangaId");
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_Jobs_ToLibraryId",
|
||||||
|
table: "Jobs",
|
||||||
|
column: "ToLibraryId");
|
||||||
|
|
||||||
migrationBuilder.CreateIndex(
|
migrationBuilder.CreateIndex(
|
||||||
name: "IX_Jobs_UpdateFilesDownloadedJob_MangaId",
|
name: "IX_Jobs_UpdateFilesDownloadedJob_MangaId",
|
||||||
table: "Jobs",
|
table: "Jobs",
|
||||||
column: "UpdateFilesDownloadedJob_MangaId");
|
column: "UpdateFilesDownloadedJob_MangaId");
|
||||||
|
|
||||||
migrationBuilder.CreateIndex(
|
|
||||||
name: "IX_Jobs_UpdateMetadataJob_MangaId",
|
|
||||||
table: "Jobs",
|
|
||||||
column: "UpdateMetadataJob_MangaId");
|
|
||||||
|
|
||||||
migrationBuilder.CreateIndex(
|
migrationBuilder.CreateIndex(
|
||||||
name: "IX_Links_MangaId",
|
name: "IX_Links_MangaId",
|
||||||
table: "Links",
|
table: "Links",
|
||||||
column: "MangaId");
|
column: "MangaId");
|
||||||
|
|
||||||
migrationBuilder.CreateIndex(
|
migrationBuilder.CreateIndex(
|
||||||
name: "IX_MangaMangaTag_MangaTagsTag",
|
name: "IX_Mangas_LibraryId",
|
||||||
table: "MangaMangaTag",
|
table: "Mangas",
|
||||||
column: "MangaTagsTag");
|
column: "LibraryId");
|
||||||
|
|
||||||
migrationBuilder.CreateIndex(
|
migrationBuilder.CreateIndex(
|
||||||
name: "IX_Mangas_LibraryLocalLibraryId",
|
name: "IX_Mangas_MangaConnectorName",
|
||||||
table: "Mangas",
|
table: "Mangas",
|
||||||
column: "LibraryLocalLibraryId");
|
column: "MangaConnectorName");
|
||||||
|
|
||||||
migrationBuilder.CreateIndex(
|
migrationBuilder.CreateIndex(
|
||||||
name: "IX_Mangas_MangaConnectorId",
|
name: "IX_MangaTagToManga_MangaIds",
|
||||||
table: "Mangas",
|
table: "MangaTagToManga",
|
||||||
column: "MangaConnectorId");
|
column: "MangaIds");
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
@ -433,7 +445,7 @@ namespace API.Migrations
|
|||||||
name: "AltTitles");
|
name: "AltTitles");
|
||||||
|
|
||||||
migrationBuilder.DropTable(
|
migrationBuilder.DropTable(
|
||||||
name: "AuthorManga");
|
name: "AuthorToManga");
|
||||||
|
|
||||||
migrationBuilder.DropTable(
|
migrationBuilder.DropTable(
|
||||||
name: "JobJob");
|
name: "JobJob");
|
||||||
@ -445,7 +457,7 @@ namespace API.Migrations
|
|||||||
name: "Links");
|
name: "Links");
|
||||||
|
|
||||||
migrationBuilder.DropTable(
|
migrationBuilder.DropTable(
|
||||||
name: "MangaMangaTag");
|
name: "MangaTagToManga");
|
||||||
|
|
||||||
migrationBuilder.DropTable(
|
migrationBuilder.DropTable(
|
||||||
name: "NotificationConnectors");
|
name: "NotificationConnectors");
|
@ -13,8 +13,8 @@ using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
|||||||
namespace API.Migrations
|
namespace API.Migrations
|
||||||
{
|
{
|
||||||
[DbContext(typeof(PgsqlContext))]
|
[DbContext(typeof(PgsqlContext))]
|
||||||
[Migration("20250401234456_dev-010425-3-ParentJobOwnership")]
|
[Migration("20250509034207_Initial-2")]
|
||||||
partial class dev0104253ParentJobOwnership
|
partial class Initial2
|
||||||
{
|
{
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||||
@ -64,7 +64,6 @@ namespace API.Migrations
|
|||||||
|
|
||||||
b.Property<string>("ParentMangaId")
|
b.Property<string>("ParentMangaId")
|
||||||
.IsRequired()
|
.IsRequired()
|
||||||
.HasMaxLength(64)
|
|
||||||
.HasColumnType("character varying(64)");
|
.HasColumnType("character varying(64)");
|
||||||
|
|
||||||
b.Property<string>("Title")
|
b.Property<string>("Title")
|
||||||
@ -92,10 +91,6 @@ namespace API.Migrations
|
|||||||
.HasMaxLength(64)
|
.HasMaxLength(64)
|
||||||
.HasColumnType("character varying(64)");
|
.HasColumnType("character varying(64)");
|
||||||
|
|
||||||
b.PrimitiveCollection<string[]>("DependsOnJobsIds")
|
|
||||||
.HasMaxLength(64)
|
|
||||||
.HasColumnType("text[]");
|
|
||||||
|
|
||||||
b.Property<bool>("Enabled")
|
b.Property<bool>("Enabled")
|
||||||
.HasColumnType("boolean");
|
.HasColumnType("boolean");
|
||||||
|
|
||||||
@ -106,6 +101,7 @@ namespace API.Migrations
|
|||||||
.HasColumnType("timestamp with time zone");
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
b.Property<string>("ParentJobId")
|
b.Property<string>("ParentJobId")
|
||||||
|
.IsRequired()
|
||||||
.HasMaxLength(64)
|
.HasMaxLength(64)
|
||||||
.HasColumnType("character varying(64)");
|
.HasColumnType("character varying(64)");
|
||||||
|
|
||||||
@ -154,32 +150,6 @@ namespace API.Migrations
|
|||||||
b.UseTphMappingStrategy();
|
b.UseTphMappingStrategy();
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("API.Schema.Link", b =>
|
|
||||||
{
|
|
||||||
b.Property<string>("LinkId")
|
|
||||||
.HasMaxLength(64)
|
|
||||||
.HasColumnType("character varying(64)");
|
|
||||||
|
|
||||||
b.Property<string>("LinkProvider")
|
|
||||||
.IsRequired()
|
|
||||||
.HasMaxLength(64)
|
|
||||||
.HasColumnType("character varying(64)");
|
|
||||||
|
|
||||||
b.Property<string>("LinkUrl")
|
|
||||||
.IsRequired()
|
|
||||||
.HasMaxLength(2048)
|
|
||||||
.HasColumnType("character varying(2048)");
|
|
||||||
|
|
||||||
b.Property<string>("MangaId")
|
|
||||||
.HasColumnType("character varying(64)");
|
|
||||||
|
|
||||||
b.HasKey("LinkId");
|
|
||||||
|
|
||||||
b.HasIndex("MangaId");
|
|
||||||
|
|
||||||
b.ToTable("Links");
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("API.Schema.LocalLibrary", b =>
|
modelBuilder.Entity("API.Schema.LocalLibrary", b =>
|
||||||
{
|
{
|
||||||
b.Property<string>("LocalLibraryId")
|
b.Property<string>("LocalLibraryId")
|
||||||
@ -208,11 +178,13 @@ namespace API.Migrations
|
|||||||
.HasColumnType("character varying(64)");
|
.HasColumnType("character varying(64)");
|
||||||
|
|
||||||
b.Property<string>("CoverFileNameInCache")
|
b.Property<string>("CoverFileNameInCache")
|
||||||
.HasColumnType("text");
|
.HasMaxLength(512)
|
||||||
|
.HasColumnType("character varying(512)");
|
||||||
|
|
||||||
b.Property<string>("CoverUrl")
|
b.Property<string>("CoverUrl")
|
||||||
.IsRequired()
|
.IsRequired()
|
||||||
.HasColumnType("text");
|
.HasMaxLength(512)
|
||||||
|
.HasColumnType("character varying(512)");
|
||||||
|
|
||||||
b.Property<string>("Description")
|
b.Property<string>("Description")
|
||||||
.IsRequired()
|
.IsRequired()
|
||||||
@ -228,17 +200,19 @@ namespace API.Migrations
|
|||||||
.HasMaxLength(256)
|
.HasMaxLength(256)
|
||||||
.HasColumnType("character varying(256)");
|
.HasColumnType("character varying(256)");
|
||||||
|
|
||||||
b.Property<float>("IgnoreChapterBefore")
|
b.Property<float>("IgnoreChaptersBefore")
|
||||||
.HasColumnType("real");
|
.HasColumnType("real");
|
||||||
|
|
||||||
b.Property<string>("LibraryLocalLibraryId")
|
b.Property<string>("LibraryId")
|
||||||
.HasColumnType("character varying(64)");
|
|
||||||
|
|
||||||
b.Property<string>("MangaConnectorId")
|
|
||||||
.IsRequired()
|
.IsRequired()
|
||||||
.HasMaxLength(64)
|
.HasMaxLength(64)
|
||||||
.HasColumnType("character varying(64)");
|
.HasColumnType("character varying(64)");
|
||||||
|
|
||||||
|
b.Property<string>("MangaConnectorName")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(32)
|
||||||
|
.HasColumnType("character varying(32)");
|
||||||
|
|
||||||
b.Property<string>("Name")
|
b.Property<string>("Name")
|
||||||
.IsRequired()
|
.IsRequired()
|
||||||
.HasMaxLength(512)
|
.HasMaxLength(512)
|
||||||
@ -261,39 +235,13 @@ namespace API.Migrations
|
|||||||
|
|
||||||
b.HasKey("MangaId");
|
b.HasKey("MangaId");
|
||||||
|
|
||||||
b.HasIndex("LibraryLocalLibraryId");
|
b.HasIndex("LibraryId");
|
||||||
|
|
||||||
b.HasIndex("MangaConnectorId");
|
b.HasIndex("MangaConnectorName");
|
||||||
|
|
||||||
b.ToTable("Mangas");
|
b.ToTable("Mangas");
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("API.Schema.MangaAltTitle", b =>
|
|
||||||
{
|
|
||||||
b.Property<string>("AltTitleId")
|
|
||||||
.HasMaxLength(64)
|
|
||||||
.HasColumnType("character varying(64)");
|
|
||||||
|
|
||||||
b.Property<string>("Language")
|
|
||||||
.IsRequired()
|
|
||||||
.HasMaxLength(8)
|
|
||||||
.HasColumnType("character varying(8)");
|
|
||||||
|
|
||||||
b.Property<string>("MangaId")
|
|
||||||
.HasColumnType("character varying(64)");
|
|
||||||
|
|
||||||
b.Property<string>("Title")
|
|
||||||
.IsRequired()
|
|
||||||
.HasMaxLength(256)
|
|
||||||
.HasColumnType("character varying(256)");
|
|
||||||
|
|
||||||
b.HasKey("AltTitleId");
|
|
||||||
|
|
||||||
b.HasIndex("MangaId");
|
|
||||||
|
|
||||||
b.ToTable("AltTitles");
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("API.Schema.MangaConnectors.MangaConnector", b =>
|
modelBuilder.Entity("API.Schema.MangaConnectors.MangaConnector", b =>
|
||||||
{
|
{
|
||||||
b.Property<string>("Name")
|
b.Property<string>("Name")
|
||||||
@ -395,19 +343,19 @@ namespace API.Migrations
|
|||||||
b.ToTable("NotificationConnectors");
|
b.ToTable("NotificationConnectors");
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("AuthorManga", b =>
|
modelBuilder.Entity("AuthorToManga", b =>
|
||||||
{
|
{
|
||||||
b.Property<string>("AuthorsAuthorId")
|
b.Property<string>("AuthorIds")
|
||||||
.HasColumnType("character varying(64)");
|
.HasColumnType("character varying(64)");
|
||||||
|
|
||||||
b.Property<string>("MangaId")
|
b.Property<string>("MangaIds")
|
||||||
.HasColumnType("character varying(64)");
|
.HasColumnType("character varying(64)");
|
||||||
|
|
||||||
b.HasKey("AuthorsAuthorId", "MangaId");
|
b.HasKey("AuthorIds", "MangaIds");
|
||||||
|
|
||||||
b.HasIndex("MangaId");
|
b.HasIndex("MangaIds");
|
||||||
|
|
||||||
b.ToTable("AuthorManga");
|
b.ToTable("AuthorToManga");
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("JobJob", b =>
|
modelBuilder.Entity("JobJob", b =>
|
||||||
@ -425,19 +373,19 @@ namespace API.Migrations
|
|||||||
b.ToTable("JobJob");
|
b.ToTable("JobJob");
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("MangaMangaTag", b =>
|
modelBuilder.Entity("MangaTagToManga", b =>
|
||||||
{
|
{
|
||||||
b.Property<string>("MangaId")
|
b.Property<string>("MangaTagIds")
|
||||||
.HasColumnType("character varying(64)");
|
.HasColumnType("character varying(64)");
|
||||||
|
|
||||||
b.Property<string>("MangaTagsTag")
|
b.Property<string>("MangaIds")
|
||||||
.HasColumnType("character varying(64)");
|
.HasColumnType("character varying(64)");
|
||||||
|
|
||||||
b.HasKey("MangaId", "MangaTagsTag");
|
b.HasKey("MangaTagIds", "MangaIds");
|
||||||
|
|
||||||
b.HasIndex("MangaTagsTag");
|
b.HasIndex("MangaIds");
|
||||||
|
|
||||||
b.ToTable("MangaMangaTag");
|
b.ToTable("MangaTagToManga");
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("API.Schema.Jobs.DownloadAvailableChaptersJob", b =>
|
modelBuilder.Entity("API.Schema.Jobs.DownloadAvailableChaptersJob", b =>
|
||||||
@ -505,10 +453,42 @@ namespace API.Migrations
|
|||||||
b.HasDiscriminator().HasValue((byte)3);
|
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 =>
|
modelBuilder.Entity("API.Schema.Jobs.RetrieveChaptersJob", b =>
|
||||||
{
|
{
|
||||||
b.HasBaseType("API.Schema.Jobs.Job");
|
b.HasBaseType("API.Schema.Jobs.Job");
|
||||||
|
|
||||||
|
b.Property<string>("Language")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(8)
|
||||||
|
.HasColumnType("character varying(8)");
|
||||||
|
|
||||||
b.Property<string>("MangaId")
|
b.Property<string>("MangaId")
|
||||||
.IsRequired()
|
.IsRequired()
|
||||||
.HasMaxLength(64)
|
.HasMaxLength(64)
|
||||||
@ -545,26 +525,6 @@ namespace API.Migrations
|
|||||||
b.HasDiscriminator().HasValue((byte)6);
|
b.HasDiscriminator().HasValue((byte)6);
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("API.Schema.Jobs.UpdateMetadataJob", 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("UpdateMetadataJob_MangaId");
|
|
||||||
});
|
|
||||||
|
|
||||||
b.HasDiscriminator().HasValue((byte)2);
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("API.Schema.LibraryConnectors.Kavita", b =>
|
modelBuilder.Entity("API.Schema.LibraryConnectors.Kavita", b =>
|
||||||
{
|
{
|
||||||
b.HasBaseType("API.Schema.LibraryConnectors.LibraryConnector");
|
b.HasBaseType("API.Schema.LibraryConnectors.LibraryConnector");
|
||||||
@ -579,20 +539,6 @@ namespace API.Migrations
|
|||||||
b.HasDiscriminator().HasValue((byte)0);
|
b.HasDiscriminator().HasValue((byte)0);
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("API.Schema.MangaConnectors.AsuraToon", b =>
|
|
||||||
{
|
|
||||||
b.HasBaseType("API.Schema.MangaConnectors.MangaConnector");
|
|
||||||
|
|
||||||
b.HasDiscriminator().HasValue("AsuraToon");
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("API.Schema.MangaConnectors.Bato", b =>
|
|
||||||
{
|
|
||||||
b.HasBaseType("API.Schema.MangaConnectors.MangaConnector");
|
|
||||||
|
|
||||||
b.HasDiscriminator().HasValue("Bato");
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("API.Schema.MangaConnectors.Global", b =>
|
modelBuilder.Entity("API.Schema.MangaConnectors.Global", b =>
|
||||||
{
|
{
|
||||||
b.HasBaseType("API.Schema.MangaConnectors.MangaConnector");
|
b.HasBaseType("API.Schema.MangaConnectors.MangaConnector");
|
||||||
@ -607,52 +553,10 @@ namespace API.Migrations
|
|||||||
b.HasDiscriminator().HasValue("MangaDex");
|
b.HasDiscriminator().HasValue("MangaDex");
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("API.Schema.MangaConnectors.MangaHere", b =>
|
|
||||||
{
|
|
||||||
b.HasBaseType("API.Schema.MangaConnectors.MangaConnector");
|
|
||||||
|
|
||||||
b.HasDiscriminator().HasValue("MangaHere");
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("API.Schema.MangaConnectors.MangaKatana", b =>
|
|
||||||
{
|
|
||||||
b.HasBaseType("API.Schema.MangaConnectors.MangaConnector");
|
|
||||||
|
|
||||||
b.HasDiscriminator().HasValue("MangaKatana");
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("API.Schema.MangaConnectors.Manganato", b =>
|
|
||||||
{
|
|
||||||
b.HasBaseType("API.Schema.MangaConnectors.MangaConnector");
|
|
||||||
|
|
||||||
b.HasDiscriminator().HasValue("Manganato");
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("API.Schema.MangaConnectors.Mangaworld", b =>
|
|
||||||
{
|
|
||||||
b.HasBaseType("API.Schema.MangaConnectors.MangaConnector");
|
|
||||||
|
|
||||||
b.HasDiscriminator().HasValue("Mangaworld");
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("API.Schema.MangaConnectors.ManhuaPlus", b =>
|
|
||||||
{
|
|
||||||
b.HasBaseType("API.Schema.MangaConnectors.MangaConnector");
|
|
||||||
|
|
||||||
b.HasDiscriminator().HasValue("ManhuaPlus");
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("API.Schema.MangaConnectors.Weebcentral", b =>
|
|
||||||
{
|
|
||||||
b.HasBaseType("API.Schema.MangaConnectors.MangaConnector");
|
|
||||||
|
|
||||||
b.HasDiscriminator().HasValue("Weebcentral");
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("API.Schema.Chapter", b =>
|
modelBuilder.Entity("API.Schema.Chapter", b =>
|
||||||
{
|
{
|
||||||
b.HasOne("API.Schema.Manga", "ParentManga")
|
b.HasOne("API.Schema.Manga", "ParentManga")
|
||||||
.WithMany()
|
.WithMany("Chapters")
|
||||||
.HasForeignKey("ParentMangaId")
|
.HasForeignKey("ParentMangaId")
|
||||||
.OnDelete(DeleteBehavior.Cascade)
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
.IsRequired();
|
.IsRequired();
|
||||||
@ -670,51 +574,100 @@ namespace API.Migrations
|
|||||||
b.Navigation("ParentJob");
|
b.Navigation("ParentJob");
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("API.Schema.Link", b =>
|
|
||||||
{
|
|
||||||
b.HasOne("API.Schema.Manga", null)
|
|
||||||
.WithMany("Links")
|
|
||||||
.HasForeignKey("MangaId")
|
|
||||||
.OnDelete(DeleteBehavior.Cascade);
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("API.Schema.Manga", b =>
|
modelBuilder.Entity("API.Schema.Manga", b =>
|
||||||
{
|
{
|
||||||
b.HasOne("API.Schema.LocalLibrary", "Library")
|
b.HasOne("API.Schema.LocalLibrary", "Library")
|
||||||
.WithMany()
|
.WithMany()
|
||||||
.HasForeignKey("LibraryLocalLibraryId")
|
.HasForeignKey("LibraryId")
|
||||||
.OnDelete(DeleteBehavior.Restrict);
|
.OnDelete(DeleteBehavior.SetNull)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
b.HasOne("API.Schema.MangaConnectors.MangaConnector", "MangaConnector")
|
b.HasOne("API.Schema.MangaConnectors.MangaConnector", "MangaConnector")
|
||||||
.WithMany()
|
.WithMany()
|
||||||
.HasForeignKey("MangaConnectorId")
|
.HasForeignKey("MangaConnectorName")
|
||||||
.OnDelete(DeleteBehavior.Cascade)
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
.IsRequired();
|
.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("Links");
|
||||||
|
|
||||||
|
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("AltTitles");
|
||||||
|
|
||||||
|
b1.WithOwner()
|
||||||
|
.HasForeignKey("MangaId");
|
||||||
|
});
|
||||||
|
|
||||||
|
b.Navigation("AltTitles");
|
||||||
|
|
||||||
b.Navigation("Library");
|
b.Navigation("Library");
|
||||||
|
|
||||||
|
b.Navigation("Links");
|
||||||
|
|
||||||
b.Navigation("MangaConnector");
|
b.Navigation("MangaConnector");
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("API.Schema.MangaAltTitle", b =>
|
modelBuilder.Entity("AuthorToManga", b =>
|
||||||
{
|
|
||||||
b.HasOne("API.Schema.Manga", null)
|
|
||||||
.WithMany("AltTitles")
|
|
||||||
.HasForeignKey("MangaId")
|
|
||||||
.OnDelete(DeleteBehavior.Cascade);
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("AuthorManga", b =>
|
|
||||||
{
|
{
|
||||||
b.HasOne("API.Schema.Author", null)
|
b.HasOne("API.Schema.Author", null)
|
||||||
.WithMany()
|
.WithMany()
|
||||||
.HasForeignKey("AuthorsAuthorId")
|
.HasForeignKey("AuthorIds")
|
||||||
.OnDelete(DeleteBehavior.Cascade)
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
.IsRequired();
|
.IsRequired();
|
||||||
|
|
||||||
b.HasOne("API.Schema.Manga", null)
|
b.HasOne("API.Schema.Manga", null)
|
||||||
.WithMany()
|
.WithMany()
|
||||||
.HasForeignKey("MangaId")
|
.HasForeignKey("MangaIds")
|
||||||
.OnDelete(DeleteBehavior.Cascade)
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
.IsRequired();
|
.IsRequired();
|
||||||
});
|
});
|
||||||
@ -734,17 +687,17 @@ namespace API.Migrations
|
|||||||
.IsRequired();
|
.IsRequired();
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("MangaMangaTag", b =>
|
modelBuilder.Entity("MangaTagToManga", b =>
|
||||||
{
|
{
|
||||||
b.HasOne("API.Schema.Manga", null)
|
b.HasOne("API.Schema.Manga", null)
|
||||||
.WithMany()
|
.WithMany()
|
||||||
.HasForeignKey("MangaId")
|
.HasForeignKey("MangaIds")
|
||||||
.OnDelete(DeleteBehavior.Cascade)
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
.IsRequired();
|
.IsRequired();
|
||||||
|
|
||||||
b.HasOne("API.Schema.MangaTag", null)
|
b.HasOne("API.Schema.MangaTag", null)
|
||||||
.WithMany()
|
.WithMany()
|
||||||
.HasForeignKey("MangaTagsTag")
|
.HasForeignKey("MangaTagIds")
|
||||||
.OnDelete(DeleteBehavior.Cascade)
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
.IsRequired();
|
.IsRequired();
|
||||||
});
|
});
|
||||||
@ -782,6 +735,25 @@ namespace API.Migrations
|
|||||||
b.Navigation("Chapter");
|
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 =>
|
modelBuilder.Entity("API.Schema.Jobs.RetrieveChaptersJob", b =>
|
||||||
{
|
{
|
||||||
b.HasOne("API.Schema.Manga", "Manga")
|
b.HasOne("API.Schema.Manga", "Manga")
|
||||||
@ -804,22 +776,9 @@ namespace API.Migrations
|
|||||||
b.Navigation("Manga");
|
b.Navigation("Manga");
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("API.Schema.Jobs.UpdateMetadataJob", b =>
|
|
||||||
{
|
|
||||||
b.HasOne("API.Schema.Manga", "Manga")
|
|
||||||
.WithMany()
|
|
||||||
.HasForeignKey("MangaId")
|
|
||||||
.OnDelete(DeleteBehavior.Cascade)
|
|
||||||
.IsRequired();
|
|
||||||
|
|
||||||
b.Navigation("Manga");
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("API.Schema.Manga", b =>
|
modelBuilder.Entity("API.Schema.Manga", b =>
|
||||||
{
|
{
|
||||||
b.Navigation("AltTitles");
|
b.Navigation("Chapters");
|
||||||
|
|
||||||
b.Navigation("Links");
|
|
||||||
});
|
});
|
||||||
#pragma warning restore 612, 618
|
#pragma warning restore 612, 618
|
||||||
}
|
}
|
@ -5,7 +5,7 @@
|
|||||||
namespace API.Migrations
|
namespace API.Migrations
|
||||||
{
|
{
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public partial class dev0104253ParentJobOwnership : Migration
|
public partial class Initial2 : Migration
|
||||||
{
|
{
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
protected override void Up(MigrationBuilder migrationBuilder)
|
protected override void Up(MigrationBuilder migrationBuilder)
|
@ -13,8 +13,8 @@ using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
|||||||
namespace API.Migrations
|
namespace API.Migrations
|
||||||
{
|
{
|
||||||
[DbContext(typeof(PgsqlContext))]
|
[DbContext(typeof(PgsqlContext))]
|
||||||
[Migration("20250316150158_dev-160325-2")]
|
[Migration("20250509035413_Initial-3")]
|
||||||
partial class dev1603252
|
partial class Initial3
|
||||||
{
|
{
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||||
@ -64,7 +64,6 @@ namespace API.Migrations
|
|||||||
|
|
||||||
b.Property<string>("ParentMangaId")
|
b.Property<string>("ParentMangaId")
|
||||||
.IsRequired()
|
.IsRequired()
|
||||||
.HasMaxLength(64)
|
|
||||||
.HasColumnType("character varying(64)");
|
.HasColumnType("character varying(64)");
|
||||||
|
|
||||||
b.Property<string>("Title")
|
b.Property<string>("Title")
|
||||||
@ -92,10 +91,6 @@ namespace API.Migrations
|
|||||||
.HasMaxLength(64)
|
.HasMaxLength(64)
|
||||||
.HasColumnType("character varying(64)");
|
.HasColumnType("character varying(64)");
|
||||||
|
|
||||||
b.PrimitiveCollection<string[]>("DependsOnJobsIds")
|
|
||||||
.HasMaxLength(64)
|
|
||||||
.HasColumnType("text[]");
|
|
||||||
|
|
||||||
b.Property<bool>("Enabled")
|
b.Property<bool>("Enabled")
|
||||||
.HasColumnType("boolean");
|
.HasColumnType("boolean");
|
||||||
|
|
||||||
@ -106,6 +101,7 @@ namespace API.Migrations
|
|||||||
.HasColumnType("timestamp with time zone");
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
b.Property<string>("ParentJobId")
|
b.Property<string>("ParentJobId")
|
||||||
|
.IsRequired()
|
||||||
.HasMaxLength(64)
|
.HasMaxLength(64)
|
||||||
.HasColumnType("character varying(64)");
|
.HasColumnType("character varying(64)");
|
||||||
|
|
||||||
@ -154,32 +150,6 @@ namespace API.Migrations
|
|||||||
b.UseTphMappingStrategy();
|
b.UseTphMappingStrategy();
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("API.Schema.Link", b =>
|
|
||||||
{
|
|
||||||
b.Property<string>("LinkId")
|
|
||||||
.HasMaxLength(64)
|
|
||||||
.HasColumnType("character varying(64)");
|
|
||||||
|
|
||||||
b.Property<string>("LinkProvider")
|
|
||||||
.IsRequired()
|
|
||||||
.HasMaxLength(64)
|
|
||||||
.HasColumnType("character varying(64)");
|
|
||||||
|
|
||||||
b.Property<string>("LinkUrl")
|
|
||||||
.IsRequired()
|
|
||||||
.HasMaxLength(2048)
|
|
||||||
.HasColumnType("character varying(2048)");
|
|
||||||
|
|
||||||
b.Property<string>("MangaId")
|
|
||||||
.HasColumnType("character varying(64)");
|
|
||||||
|
|
||||||
b.HasKey("LinkId");
|
|
||||||
|
|
||||||
b.HasIndex("MangaId");
|
|
||||||
|
|
||||||
b.ToTable("Links");
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("API.Schema.LocalLibrary", b =>
|
modelBuilder.Entity("API.Schema.LocalLibrary", b =>
|
||||||
{
|
{
|
||||||
b.Property<string>("LocalLibraryId")
|
b.Property<string>("LocalLibraryId")
|
||||||
@ -208,11 +178,13 @@ namespace API.Migrations
|
|||||||
.HasColumnType("character varying(64)");
|
.HasColumnType("character varying(64)");
|
||||||
|
|
||||||
b.Property<string>("CoverFileNameInCache")
|
b.Property<string>("CoverFileNameInCache")
|
||||||
.HasColumnType("text");
|
.HasMaxLength(512)
|
||||||
|
.HasColumnType("character varying(512)");
|
||||||
|
|
||||||
b.Property<string>("CoverUrl")
|
b.Property<string>("CoverUrl")
|
||||||
.IsRequired()
|
.IsRequired()
|
||||||
.HasColumnType("text");
|
.HasMaxLength(512)
|
||||||
|
.HasColumnType("character varying(512)");
|
||||||
|
|
||||||
b.Property<string>("Description")
|
b.Property<string>("Description")
|
||||||
.IsRequired()
|
.IsRequired()
|
||||||
@ -220,32 +192,33 @@ namespace API.Migrations
|
|||||||
|
|
||||||
b.Property<string>("DirectoryName")
|
b.Property<string>("DirectoryName")
|
||||||
.IsRequired()
|
.IsRequired()
|
||||||
.HasMaxLength(256)
|
.HasMaxLength(1024)
|
||||||
.HasColumnType("character varying(256)");
|
.HasColumnType("character varying(1024)");
|
||||||
|
|
||||||
b.Property<string>("IdOnConnectorSite")
|
b.Property<string>("IdOnConnectorSite")
|
||||||
.IsRequired()
|
.IsRequired()
|
||||||
.HasMaxLength(128)
|
.HasMaxLength(256)
|
||||||
.HasColumnType("character varying(128)");
|
.HasColumnType("character varying(256)");
|
||||||
|
|
||||||
b.Property<float>("IgnoreChapterBefore")
|
b.Property<float>("IgnoreChaptersBefore")
|
||||||
.HasColumnType("real");
|
.HasColumnType("real");
|
||||||
|
|
||||||
b.Property<string>("LibraryLocalLibraryId")
|
b.Property<string>("LibraryId")
|
||||||
.HasColumnType("character varying(64)");
|
|
||||||
|
|
||||||
b.Property<string>("MangaConnectorId")
|
|
||||||
.IsRequired()
|
.IsRequired()
|
||||||
.HasMaxLength(64)
|
.HasMaxLength(64)
|
||||||
.HasColumnType("character varying(64)");
|
.HasColumnType("character varying(64)");
|
||||||
|
|
||||||
|
b.Property<string>("MangaConnectorName")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(32)
|
||||||
|
.HasColumnType("character varying(32)");
|
||||||
|
|
||||||
b.Property<string>("Name")
|
b.Property<string>("Name")
|
||||||
.IsRequired()
|
.IsRequired()
|
||||||
.HasMaxLength(256)
|
.HasMaxLength(512)
|
||||||
.HasColumnType("character varying(256)");
|
.HasColumnType("character varying(512)");
|
||||||
|
|
||||||
b.Property<string>("OriginalLanguage")
|
b.Property<string>("OriginalLanguage")
|
||||||
.IsRequired()
|
|
||||||
.HasMaxLength(8)
|
.HasMaxLength(8)
|
||||||
.HasColumnType("character varying(8)");
|
.HasColumnType("character varying(8)");
|
||||||
|
|
||||||
@ -254,47 +227,21 @@ namespace API.Migrations
|
|||||||
|
|
||||||
b.Property<string>("WebsiteUrl")
|
b.Property<string>("WebsiteUrl")
|
||||||
.IsRequired()
|
.IsRequired()
|
||||||
.HasMaxLength(256)
|
.HasMaxLength(512)
|
||||||
.HasColumnType("character varying(256)");
|
.HasColumnType("character varying(512)");
|
||||||
|
|
||||||
b.Property<long>("Year")
|
b.Property<long>("Year")
|
||||||
.HasColumnType("bigint");
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
b.HasKey("MangaId");
|
b.HasKey("MangaId");
|
||||||
|
|
||||||
b.HasIndex("LibraryLocalLibraryId");
|
b.HasIndex("LibraryId");
|
||||||
|
|
||||||
b.HasIndex("MangaConnectorId");
|
b.HasIndex("MangaConnectorName");
|
||||||
|
|
||||||
b.ToTable("Mangas");
|
b.ToTable("Mangas");
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("API.Schema.MangaAltTitle", b =>
|
|
||||||
{
|
|
||||||
b.Property<string>("AltTitleId")
|
|
||||||
.HasMaxLength(64)
|
|
||||||
.HasColumnType("character varying(64)");
|
|
||||||
|
|
||||||
b.Property<string>("Language")
|
|
||||||
.IsRequired()
|
|
||||||
.HasMaxLength(8)
|
|
||||||
.HasColumnType("character varying(8)");
|
|
||||||
|
|
||||||
b.Property<string>("MangaId")
|
|
||||||
.HasColumnType("character varying(64)");
|
|
||||||
|
|
||||||
b.Property<string>("Title")
|
|
||||||
.IsRequired()
|
|
||||||
.HasMaxLength(256)
|
|
||||||
.HasColumnType("character varying(256)");
|
|
||||||
|
|
||||||
b.HasKey("AltTitleId");
|
|
||||||
|
|
||||||
b.HasIndex("MangaId");
|
|
||||||
|
|
||||||
b.ToTable("AltTitles");
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("API.Schema.MangaConnectors.MangaConnector", b =>
|
modelBuilder.Entity("API.Schema.MangaConnectors.MangaConnector", b =>
|
||||||
{
|
{
|
||||||
b.Property<string>("Name")
|
b.Property<string>("Name")
|
||||||
@ -396,19 +343,19 @@ namespace API.Migrations
|
|||||||
b.ToTable("NotificationConnectors");
|
b.ToTable("NotificationConnectors");
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("AuthorManga", b =>
|
modelBuilder.Entity("AuthorToManga", b =>
|
||||||
{
|
{
|
||||||
b.Property<string>("AuthorsAuthorId")
|
b.Property<string>("AuthorIds")
|
||||||
.HasColumnType("character varying(64)");
|
.HasColumnType("character varying(64)");
|
||||||
|
|
||||||
b.Property<string>("MangaId")
|
b.Property<string>("MangaIds")
|
||||||
.HasColumnType("character varying(64)");
|
.HasColumnType("character varying(64)");
|
||||||
|
|
||||||
b.HasKey("AuthorsAuthorId", "MangaId");
|
b.HasKey("AuthorIds", "MangaIds");
|
||||||
|
|
||||||
b.HasIndex("MangaId");
|
b.HasIndex("MangaIds");
|
||||||
|
|
||||||
b.ToTable("AuthorManga");
|
b.ToTable("AuthorToManga");
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("JobJob", b =>
|
modelBuilder.Entity("JobJob", b =>
|
||||||
@ -426,19 +373,19 @@ namespace API.Migrations
|
|||||||
b.ToTable("JobJob");
|
b.ToTable("JobJob");
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("MangaMangaTag", b =>
|
modelBuilder.Entity("MangaTagToManga", b =>
|
||||||
{
|
{
|
||||||
b.Property<string>("MangaId")
|
b.Property<string>("MangaTagIds")
|
||||||
.HasColumnType("character varying(64)");
|
.HasColumnType("character varying(64)");
|
||||||
|
|
||||||
b.Property<string>("MangaTagsTag")
|
b.Property<string>("MangaIds")
|
||||||
.HasColumnType("character varying(64)");
|
.HasColumnType("character varying(64)");
|
||||||
|
|
||||||
b.HasKey("MangaId", "MangaTagsTag");
|
b.HasKey("MangaTagIds", "MangaIds");
|
||||||
|
|
||||||
b.HasIndex("MangaTagsTag");
|
b.HasIndex("MangaIds");
|
||||||
|
|
||||||
b.ToTable("MangaMangaTag");
|
b.ToTable("MangaTagToManga");
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("API.Schema.Jobs.DownloadAvailableChaptersJob", b =>
|
modelBuilder.Entity("API.Schema.Jobs.DownloadAvailableChaptersJob", b =>
|
||||||
@ -506,10 +453,42 @@ namespace API.Migrations
|
|||||||
b.HasDiscriminator().HasValue((byte)3);
|
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 =>
|
modelBuilder.Entity("API.Schema.Jobs.RetrieveChaptersJob", b =>
|
||||||
{
|
{
|
||||||
b.HasBaseType("API.Schema.Jobs.Job");
|
b.HasBaseType("API.Schema.Jobs.Job");
|
||||||
|
|
||||||
|
b.Property<string>("Language")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(8)
|
||||||
|
.HasColumnType("character varying(8)");
|
||||||
|
|
||||||
b.Property<string>("MangaId")
|
b.Property<string>("MangaId")
|
||||||
.IsRequired()
|
.IsRequired()
|
||||||
.HasMaxLength(64)
|
.HasMaxLength(64)
|
||||||
@ -546,26 +525,6 @@ namespace API.Migrations
|
|||||||
b.HasDiscriminator().HasValue((byte)6);
|
b.HasDiscriminator().HasValue((byte)6);
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("API.Schema.Jobs.UpdateMetadataJob", 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("UpdateMetadataJob_MangaId");
|
|
||||||
});
|
|
||||||
|
|
||||||
b.HasDiscriminator().HasValue((byte)2);
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("API.Schema.LibraryConnectors.Kavita", b =>
|
modelBuilder.Entity("API.Schema.LibraryConnectors.Kavita", b =>
|
||||||
{
|
{
|
||||||
b.HasBaseType("API.Schema.LibraryConnectors.LibraryConnector");
|
b.HasBaseType("API.Schema.LibraryConnectors.LibraryConnector");
|
||||||
@ -580,18 +539,11 @@ namespace API.Migrations
|
|||||||
b.HasDiscriminator().HasValue((byte)0);
|
b.HasDiscriminator().HasValue((byte)0);
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("API.Schema.MangaConnectors.AsuraToon", b =>
|
modelBuilder.Entity("API.Schema.MangaConnectors.Global", b =>
|
||||||
{
|
{
|
||||||
b.HasBaseType("API.Schema.MangaConnectors.MangaConnector");
|
b.HasBaseType("API.Schema.MangaConnectors.MangaConnector");
|
||||||
|
|
||||||
b.HasDiscriminator().HasValue("AsuraToon");
|
b.HasDiscriminator().HasValue("Global");
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("API.Schema.MangaConnectors.Bato", b =>
|
|
||||||
{
|
|
||||||
b.HasBaseType("API.Schema.MangaConnectors.MangaConnector");
|
|
||||||
|
|
||||||
b.HasDiscriminator().HasValue("Bato");
|
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("API.Schema.MangaConnectors.MangaDex", b =>
|
modelBuilder.Entity("API.Schema.MangaConnectors.MangaDex", b =>
|
||||||
@ -601,52 +553,10 @@ namespace API.Migrations
|
|||||||
b.HasDiscriminator().HasValue("MangaDex");
|
b.HasDiscriminator().HasValue("MangaDex");
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("API.Schema.MangaConnectors.MangaHere", b =>
|
|
||||||
{
|
|
||||||
b.HasBaseType("API.Schema.MangaConnectors.MangaConnector");
|
|
||||||
|
|
||||||
b.HasDiscriminator().HasValue("MangaHere");
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("API.Schema.MangaConnectors.MangaKatana", b =>
|
|
||||||
{
|
|
||||||
b.HasBaseType("API.Schema.MangaConnectors.MangaConnector");
|
|
||||||
|
|
||||||
b.HasDiscriminator().HasValue("MangaKatana");
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("API.Schema.MangaConnectors.Manganato", b =>
|
|
||||||
{
|
|
||||||
b.HasBaseType("API.Schema.MangaConnectors.MangaConnector");
|
|
||||||
|
|
||||||
b.HasDiscriminator().HasValue("Manganato");
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("API.Schema.MangaConnectors.Mangaworld", b =>
|
|
||||||
{
|
|
||||||
b.HasBaseType("API.Schema.MangaConnectors.MangaConnector");
|
|
||||||
|
|
||||||
b.HasDiscriminator().HasValue("Mangaworld");
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("API.Schema.MangaConnectors.ManhuaPlus", b =>
|
|
||||||
{
|
|
||||||
b.HasBaseType("API.Schema.MangaConnectors.MangaConnector");
|
|
||||||
|
|
||||||
b.HasDiscriminator().HasValue("ManhuaPlus");
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("API.Schema.MangaConnectors.Weebcentral", b =>
|
|
||||||
{
|
|
||||||
b.HasBaseType("API.Schema.MangaConnectors.MangaConnector");
|
|
||||||
|
|
||||||
b.HasDiscriminator().HasValue("Weebcentral");
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("API.Schema.Chapter", b =>
|
modelBuilder.Entity("API.Schema.Chapter", b =>
|
||||||
{
|
{
|
||||||
b.HasOne("API.Schema.Manga", "ParentManga")
|
b.HasOne("API.Schema.Manga", "ParentManga")
|
||||||
.WithMany()
|
.WithMany("Chapters")
|
||||||
.HasForeignKey("ParentMangaId")
|
.HasForeignKey("ParentMangaId")
|
||||||
.OnDelete(DeleteBehavior.Cascade)
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
.IsRequired();
|
.IsRequired();
|
||||||
@ -664,51 +574,100 @@ namespace API.Migrations
|
|||||||
b.Navigation("ParentJob");
|
b.Navigation("ParentJob");
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("API.Schema.Link", b =>
|
|
||||||
{
|
|
||||||
b.HasOne("API.Schema.Manga", null)
|
|
||||||
.WithMany("Links")
|
|
||||||
.HasForeignKey("MangaId")
|
|
||||||
.OnDelete(DeleteBehavior.Cascade);
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("API.Schema.Manga", b =>
|
modelBuilder.Entity("API.Schema.Manga", b =>
|
||||||
{
|
{
|
||||||
b.HasOne("API.Schema.LocalLibrary", "Library")
|
b.HasOne("API.Schema.LocalLibrary", "Library")
|
||||||
.WithMany()
|
.WithMany()
|
||||||
.HasForeignKey("LibraryLocalLibraryId")
|
.HasForeignKey("LibraryId")
|
||||||
.OnDelete(DeleteBehavior.Restrict);
|
.OnDelete(DeleteBehavior.SetNull)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
b.HasOne("API.Schema.MangaConnectors.MangaConnector", "MangaConnector")
|
b.HasOne("API.Schema.MangaConnectors.MangaConnector", "MangaConnector")
|
||||||
.WithMany()
|
.WithMany()
|
||||||
.HasForeignKey("MangaConnectorId")
|
.HasForeignKey("MangaConnectorName")
|
||||||
.OnDelete(DeleteBehavior.Cascade)
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
.IsRequired();
|
.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("Library");
|
||||||
|
|
||||||
|
b.Navigation("Links");
|
||||||
|
|
||||||
b.Navigation("MangaConnector");
|
b.Navigation("MangaConnector");
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("API.Schema.MangaAltTitle", b =>
|
modelBuilder.Entity("AuthorToManga", b =>
|
||||||
{
|
|
||||||
b.HasOne("API.Schema.Manga", null)
|
|
||||||
.WithMany("AltTitles")
|
|
||||||
.HasForeignKey("MangaId")
|
|
||||||
.OnDelete(DeleteBehavior.Cascade);
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("AuthorManga", b =>
|
|
||||||
{
|
{
|
||||||
b.HasOne("API.Schema.Author", null)
|
b.HasOne("API.Schema.Author", null)
|
||||||
.WithMany()
|
.WithMany()
|
||||||
.HasForeignKey("AuthorsAuthorId")
|
.HasForeignKey("AuthorIds")
|
||||||
.OnDelete(DeleteBehavior.Cascade)
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
.IsRequired();
|
.IsRequired();
|
||||||
|
|
||||||
b.HasOne("API.Schema.Manga", null)
|
b.HasOne("API.Schema.Manga", null)
|
||||||
.WithMany()
|
.WithMany()
|
||||||
.HasForeignKey("MangaId")
|
.HasForeignKey("MangaIds")
|
||||||
.OnDelete(DeleteBehavior.Cascade)
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
.IsRequired();
|
.IsRequired();
|
||||||
});
|
});
|
||||||
@ -728,17 +687,17 @@ namespace API.Migrations
|
|||||||
.IsRequired();
|
.IsRequired();
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("MangaMangaTag", b =>
|
modelBuilder.Entity("MangaTagToManga", b =>
|
||||||
{
|
{
|
||||||
b.HasOne("API.Schema.Manga", null)
|
b.HasOne("API.Schema.Manga", null)
|
||||||
.WithMany()
|
.WithMany()
|
||||||
.HasForeignKey("MangaId")
|
.HasForeignKey("MangaIds")
|
||||||
.OnDelete(DeleteBehavior.Cascade)
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
.IsRequired();
|
.IsRequired();
|
||||||
|
|
||||||
b.HasOne("API.Schema.MangaTag", null)
|
b.HasOne("API.Schema.MangaTag", null)
|
||||||
.WithMany()
|
.WithMany()
|
||||||
.HasForeignKey("MangaTagsTag")
|
.HasForeignKey("MangaTagIds")
|
||||||
.OnDelete(DeleteBehavior.Cascade)
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
.IsRequired();
|
.IsRequired();
|
||||||
});
|
});
|
||||||
@ -776,6 +735,25 @@ namespace API.Migrations
|
|||||||
b.Navigation("Chapter");
|
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 =>
|
modelBuilder.Entity("API.Schema.Jobs.RetrieveChaptersJob", b =>
|
||||||
{
|
{
|
||||||
b.HasOne("API.Schema.Manga", "Manga")
|
b.HasOne("API.Schema.Manga", "Manga")
|
||||||
@ -798,22 +776,9 @@ namespace API.Migrations
|
|||||||
b.Navigation("Manga");
|
b.Navigation("Manga");
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("API.Schema.Jobs.UpdateMetadataJob", b =>
|
|
||||||
{
|
|
||||||
b.HasOne("API.Schema.Manga", "Manga")
|
|
||||||
.WithMany()
|
|
||||||
.HasForeignKey("MangaId")
|
|
||||||
.OnDelete(DeleteBehavior.Cascade)
|
|
||||||
.IsRequired();
|
|
||||||
|
|
||||||
b.Navigation("Manga");
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("API.Schema.Manga", b =>
|
modelBuilder.Entity("API.Schema.Manga", b =>
|
||||||
{
|
{
|
||||||
b.Navigation("AltTitles");
|
b.Navigation("Chapters");
|
||||||
|
|
||||||
b.Navigation("Links");
|
|
||||||
});
|
});
|
||||||
#pragma warning restore 612, 618
|
#pragma warning restore 612, 618
|
||||||
}
|
}
|
130
API/Migrations/20250509035413_Initial-3.cs
Normal file
130
API/Migrations/20250509035413_Initial-3.cs
Normal file
@ -0,0 +1,130 @@
|
|||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace API.Migrations
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public partial class Initial3 : Migration
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "AltTitles");
|
||||||
|
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "Links");
|
||||||
|
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "Link",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
LinkId = table.Column<string>(type: "character varying(64)", maxLength: 64, nullable: false),
|
||||||
|
LinkProvider = table.Column<string>(type: "character varying(64)", maxLength: 64, nullable: false),
|
||||||
|
LinkUrl = table.Column<string>(type: "character varying(2048)", maxLength: 2048, nullable: false),
|
||||||
|
MangaId = table.Column<string>(type: "character varying(64)", nullable: false)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("PK_Link", x => x.LinkId);
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "FK_Link_Mangas_MangaId",
|
||||||
|
column: x => x.MangaId,
|
||||||
|
principalTable: "Mangas",
|
||||||
|
principalColumn: "MangaId",
|
||||||
|
onDelete: ReferentialAction.Cascade);
|
||||||
|
});
|
||||||
|
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "MangaAltTitle",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
AltTitleId = table.Column<string>(type: "character varying(64)", maxLength: 64, nullable: false),
|
||||||
|
Language = table.Column<string>(type: "character varying(8)", maxLength: 8, nullable: false),
|
||||||
|
Title = table.Column<string>(type: "character varying(256)", maxLength: 256, nullable: false),
|
||||||
|
MangaId = table.Column<string>(type: "character varying(64)", nullable: false)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("PK_MangaAltTitle", x => x.AltTitleId);
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "FK_MangaAltTitle_Mangas_MangaId",
|
||||||
|
column: x => x.MangaId,
|
||||||
|
principalTable: "Mangas",
|
||||||
|
principalColumn: "MangaId",
|
||||||
|
onDelete: ReferentialAction.Cascade);
|
||||||
|
});
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_Link_MangaId",
|
||||||
|
table: "Link",
|
||||||
|
column: "MangaId");
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_MangaAltTitle_MangaId",
|
||||||
|
table: "MangaAltTitle",
|
||||||
|
column: "MangaId");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "Link");
|
||||||
|
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "MangaAltTitle");
|
||||||
|
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "AltTitles",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
AltTitleId = table.Column<string>(type: "character varying(64)", maxLength: 64, nullable: false),
|
||||||
|
Language = table.Column<string>(type: "character varying(8)", maxLength: 8, nullable: false),
|
||||||
|
MangaId = table.Column<string>(type: "character varying(64)", nullable: false),
|
||||||
|
Title = table.Column<string>(type: "character varying(256)", maxLength: 256, nullable: false)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("PK_AltTitles", x => x.AltTitleId);
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "FK_AltTitles_Mangas_MangaId",
|
||||||
|
column: x => x.MangaId,
|
||||||
|
principalTable: "Mangas",
|
||||||
|
principalColumn: "MangaId",
|
||||||
|
onDelete: ReferentialAction.Cascade);
|
||||||
|
});
|
||||||
|
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "Links",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
LinkId = table.Column<string>(type: "character varying(64)", maxLength: 64, nullable: false),
|
||||||
|
LinkProvider = table.Column<string>(type: "character varying(64)", maxLength: 64, nullable: false),
|
||||||
|
LinkUrl = table.Column<string>(type: "character varying(2048)", maxLength: 2048, nullable: false),
|
||||||
|
MangaId = table.Column<string>(type: "character varying(64)", nullable: false)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("PK_Links", x => x.LinkId);
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "FK_Links_Mangas_MangaId",
|
||||||
|
column: x => x.MangaId,
|
||||||
|
principalTable: "Mangas",
|
||||||
|
principalColumn: "MangaId",
|
||||||
|
onDelete: ReferentialAction.Cascade);
|
||||||
|
});
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_AltTitles_MangaId",
|
||||||
|
table: "AltTitles",
|
||||||
|
column: "MangaId");
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_Links_MangaId",
|
||||||
|
table: "Links",
|
||||||
|
column: "MangaId");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -13,8 +13,8 @@ using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
|||||||
namespace API.Migrations
|
namespace API.Migrations
|
||||||
{
|
{
|
||||||
[DbContext(typeof(PgsqlContext))]
|
[DbContext(typeof(PgsqlContext))]
|
||||||
[Migration("20250316143014_dev-160325-Initial")]
|
[Migration("20250509035606_Initial-4")]
|
||||||
partial class dev160325Initial
|
partial class Initial4
|
||||||
{
|
{
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||||
@ -64,7 +64,6 @@ namespace API.Migrations
|
|||||||
|
|
||||||
b.Property<string>("ParentMangaId")
|
b.Property<string>("ParentMangaId")
|
||||||
.IsRequired()
|
.IsRequired()
|
||||||
.HasMaxLength(64)
|
|
||||||
.HasColumnType("character varying(64)");
|
.HasColumnType("character varying(64)");
|
||||||
|
|
||||||
b.Property<string>("Title")
|
b.Property<string>("Title")
|
||||||
@ -92,10 +91,6 @@ namespace API.Migrations
|
|||||||
.HasMaxLength(64)
|
.HasMaxLength(64)
|
||||||
.HasColumnType("character varying(64)");
|
.HasColumnType("character varying(64)");
|
||||||
|
|
||||||
b.PrimitiveCollection<string[]>("DependsOnJobsIds")
|
|
||||||
.HasMaxLength(64)
|
|
||||||
.HasColumnType("text[]");
|
|
||||||
|
|
||||||
b.Property<bool>("Enabled")
|
b.Property<bool>("Enabled")
|
||||||
.HasColumnType("boolean");
|
.HasColumnType("boolean");
|
||||||
|
|
||||||
@ -106,6 +101,7 @@ namespace API.Migrations
|
|||||||
.HasColumnType("timestamp with time zone");
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
b.Property<string>("ParentJobId")
|
b.Property<string>("ParentJobId")
|
||||||
|
.IsRequired()
|
||||||
.HasMaxLength(64)
|
.HasMaxLength(64)
|
||||||
.HasColumnType("character varying(64)");
|
.HasColumnType("character varying(64)");
|
||||||
|
|
||||||
@ -154,32 +150,6 @@ namespace API.Migrations
|
|||||||
b.UseTphMappingStrategy();
|
b.UseTphMappingStrategy();
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("API.Schema.Link", b =>
|
|
||||||
{
|
|
||||||
b.Property<string>("LinkId")
|
|
||||||
.HasMaxLength(64)
|
|
||||||
.HasColumnType("character varying(64)");
|
|
||||||
|
|
||||||
b.Property<string>("LinkProvider")
|
|
||||||
.IsRequired()
|
|
||||||
.HasMaxLength(64)
|
|
||||||
.HasColumnType("character varying(64)");
|
|
||||||
|
|
||||||
b.Property<string>("LinkUrl")
|
|
||||||
.IsRequired()
|
|
||||||
.HasMaxLength(2048)
|
|
||||||
.HasColumnType("character varying(2048)");
|
|
||||||
|
|
||||||
b.Property<string>("MangaId")
|
|
||||||
.HasColumnType("character varying(64)");
|
|
||||||
|
|
||||||
b.HasKey("LinkId");
|
|
||||||
|
|
||||||
b.HasIndex("MangaId");
|
|
||||||
|
|
||||||
b.ToTable("Links");
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("API.Schema.LocalLibrary", b =>
|
modelBuilder.Entity("API.Schema.LocalLibrary", b =>
|
||||||
{
|
{
|
||||||
b.Property<string>("LocalLibraryId")
|
b.Property<string>("LocalLibraryId")
|
||||||
@ -208,11 +178,13 @@ namespace API.Migrations
|
|||||||
.HasColumnType("character varying(64)");
|
.HasColumnType("character varying(64)");
|
||||||
|
|
||||||
b.Property<string>("CoverFileNameInCache")
|
b.Property<string>("CoverFileNameInCache")
|
||||||
.HasColumnType("text");
|
.HasMaxLength(512)
|
||||||
|
.HasColumnType("character varying(512)");
|
||||||
|
|
||||||
b.Property<string>("CoverUrl")
|
b.Property<string>("CoverUrl")
|
||||||
.IsRequired()
|
.IsRequired()
|
||||||
.HasColumnType("text");
|
.HasMaxLength(512)
|
||||||
|
.HasColumnType("character varying(512)");
|
||||||
|
|
||||||
b.Property<string>("Description")
|
b.Property<string>("Description")
|
||||||
.IsRequired()
|
.IsRequired()
|
||||||
@ -220,32 +192,32 @@ namespace API.Migrations
|
|||||||
|
|
||||||
b.Property<string>("DirectoryName")
|
b.Property<string>("DirectoryName")
|
||||||
.IsRequired()
|
.IsRequired()
|
||||||
.HasMaxLength(256)
|
.HasMaxLength(1024)
|
||||||
.HasColumnType("character varying(256)");
|
.HasColumnType("character varying(1024)");
|
||||||
|
|
||||||
b.Property<string>("IdOnConnectorSite")
|
b.Property<string>("IdOnConnectorSite")
|
||||||
.IsRequired()
|
|
||||||
.HasMaxLength(128)
|
|
||||||
.HasColumnType("character varying(128)");
|
|
||||||
|
|
||||||
b.Property<float>("IgnoreChapterBefore")
|
|
||||||
.HasColumnType("real");
|
|
||||||
|
|
||||||
b.Property<string>("LibraryLocalLibraryId")
|
|
||||||
.HasColumnType("character varying(64)");
|
|
||||||
|
|
||||||
b.Property<string>("MangaConnectorId")
|
|
||||||
.IsRequired()
|
|
||||||
.HasMaxLength(64)
|
|
||||||
.HasColumnType("character varying(64)");
|
|
||||||
|
|
||||||
b.Property<string>("Name")
|
|
||||||
.IsRequired()
|
.IsRequired()
|
||||||
.HasMaxLength(256)
|
.HasMaxLength(256)
|
||||||
.HasColumnType("character varying(256)");
|
.HasColumnType("character varying(256)");
|
||||||
|
|
||||||
b.Property<string>("OriginalLanguage")
|
b.Property<float>("IgnoreChaptersBefore")
|
||||||
|
.HasColumnType("real");
|
||||||
|
|
||||||
|
b.Property<string>("LibraryId")
|
||||||
|
.HasMaxLength(64)
|
||||||
|
.HasColumnType("character varying(64)");
|
||||||
|
|
||||||
|
b.Property<string>("MangaConnectorName")
|
||||||
.IsRequired()
|
.IsRequired()
|
||||||
|
.HasMaxLength(32)
|
||||||
|
.HasColumnType("character varying(32)");
|
||||||
|
|
||||||
|
b.Property<string>("Name")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(512)
|
||||||
|
.HasColumnType("character varying(512)");
|
||||||
|
|
||||||
|
b.Property<string>("OriginalLanguage")
|
||||||
.HasMaxLength(8)
|
.HasMaxLength(8)
|
||||||
.HasColumnType("character varying(8)");
|
.HasColumnType("character varying(8)");
|
||||||
|
|
||||||
@ -254,47 +226,21 @@ namespace API.Migrations
|
|||||||
|
|
||||||
b.Property<string>("WebsiteUrl")
|
b.Property<string>("WebsiteUrl")
|
||||||
.IsRequired()
|
.IsRequired()
|
||||||
.HasMaxLength(256)
|
.HasMaxLength(512)
|
||||||
.HasColumnType("character varying(256)");
|
.HasColumnType("character varying(512)");
|
||||||
|
|
||||||
b.Property<long>("Year")
|
b.Property<long>("Year")
|
||||||
.HasColumnType("bigint");
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
b.HasKey("MangaId");
|
b.HasKey("MangaId");
|
||||||
|
|
||||||
b.HasIndex("LibraryLocalLibraryId");
|
b.HasIndex("LibraryId");
|
||||||
|
|
||||||
b.HasIndex("MangaConnectorId");
|
b.HasIndex("MangaConnectorName");
|
||||||
|
|
||||||
b.ToTable("Mangas");
|
b.ToTable("Mangas");
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("API.Schema.MangaAltTitle", b =>
|
|
||||||
{
|
|
||||||
b.Property<string>("AltTitleId")
|
|
||||||
.HasMaxLength(64)
|
|
||||||
.HasColumnType("character varying(64)");
|
|
||||||
|
|
||||||
b.Property<string>("Language")
|
|
||||||
.IsRequired()
|
|
||||||
.HasMaxLength(8)
|
|
||||||
.HasColumnType("character varying(8)");
|
|
||||||
|
|
||||||
b.Property<string>("MangaId")
|
|
||||||
.HasColumnType("character varying(64)");
|
|
||||||
|
|
||||||
b.Property<string>("Title")
|
|
||||||
.IsRequired()
|
|
||||||
.HasMaxLength(256)
|
|
||||||
.HasColumnType("character varying(256)");
|
|
||||||
|
|
||||||
b.HasKey("AltTitleId");
|
|
||||||
|
|
||||||
b.HasIndex("MangaId");
|
|
||||||
|
|
||||||
b.ToTable("AltTitles");
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("API.Schema.MangaConnectors.MangaConnector", b =>
|
modelBuilder.Entity("API.Schema.MangaConnectors.MangaConnector", b =>
|
||||||
{
|
{
|
||||||
b.Property<string>("Name")
|
b.Property<string>("Name")
|
||||||
@ -396,19 +342,19 @@ namespace API.Migrations
|
|||||||
b.ToTable("NotificationConnectors");
|
b.ToTable("NotificationConnectors");
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("AuthorManga", b =>
|
modelBuilder.Entity("AuthorToManga", b =>
|
||||||
{
|
{
|
||||||
b.Property<string>("AuthorsAuthorId")
|
b.Property<string>("AuthorIds")
|
||||||
.HasColumnType("character varying(64)");
|
.HasColumnType("character varying(64)");
|
||||||
|
|
||||||
b.Property<string>("MangaId")
|
b.Property<string>("MangaIds")
|
||||||
.HasColumnType("character varying(64)");
|
.HasColumnType("character varying(64)");
|
||||||
|
|
||||||
b.HasKey("AuthorsAuthorId", "MangaId");
|
b.HasKey("AuthorIds", "MangaIds");
|
||||||
|
|
||||||
b.HasIndex("MangaId");
|
b.HasIndex("MangaIds");
|
||||||
|
|
||||||
b.ToTable("AuthorManga");
|
b.ToTable("AuthorToManga");
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("JobJob", b =>
|
modelBuilder.Entity("JobJob", b =>
|
||||||
@ -426,19 +372,19 @@ namespace API.Migrations
|
|||||||
b.ToTable("JobJob");
|
b.ToTable("JobJob");
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("MangaMangaTag", b =>
|
modelBuilder.Entity("MangaTagToManga", b =>
|
||||||
{
|
{
|
||||||
b.Property<string>("MangaId")
|
b.Property<string>("MangaTagIds")
|
||||||
.HasColumnType("character varying(64)");
|
.HasColumnType("character varying(64)");
|
||||||
|
|
||||||
b.Property<string>("MangaTagsTag")
|
b.Property<string>("MangaIds")
|
||||||
.HasColumnType("character varying(64)");
|
.HasColumnType("character varying(64)");
|
||||||
|
|
||||||
b.HasKey("MangaId", "MangaTagsTag");
|
b.HasKey("MangaTagIds", "MangaIds");
|
||||||
|
|
||||||
b.HasIndex("MangaTagsTag");
|
b.HasIndex("MangaIds");
|
||||||
|
|
||||||
b.ToTable("MangaMangaTag");
|
b.ToTable("MangaTagToManga");
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("API.Schema.Jobs.DownloadAvailableChaptersJob", b =>
|
modelBuilder.Entity("API.Schema.Jobs.DownloadAvailableChaptersJob", b =>
|
||||||
@ -506,10 +452,42 @@ namespace API.Migrations
|
|||||||
b.HasDiscriminator().HasValue((byte)3);
|
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 =>
|
modelBuilder.Entity("API.Schema.Jobs.RetrieveChaptersJob", b =>
|
||||||
{
|
{
|
||||||
b.HasBaseType("API.Schema.Jobs.Job");
|
b.HasBaseType("API.Schema.Jobs.Job");
|
||||||
|
|
||||||
|
b.Property<string>("Language")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(8)
|
||||||
|
.HasColumnType("character varying(8)");
|
||||||
|
|
||||||
b.Property<string>("MangaId")
|
b.Property<string>("MangaId")
|
||||||
.IsRequired()
|
.IsRequired()
|
||||||
.HasMaxLength(64)
|
.HasMaxLength(64)
|
||||||
@ -546,26 +524,6 @@ namespace API.Migrations
|
|||||||
b.HasDiscriminator().HasValue((byte)6);
|
b.HasDiscriminator().HasValue((byte)6);
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("API.Schema.Jobs.UpdateMetadataJob", 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("UpdateMetadataJob_MangaId");
|
|
||||||
});
|
|
||||||
|
|
||||||
b.HasDiscriminator().HasValue((byte)2);
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("API.Schema.LibraryConnectors.Kavita", b =>
|
modelBuilder.Entity("API.Schema.LibraryConnectors.Kavita", b =>
|
||||||
{
|
{
|
||||||
b.HasBaseType("API.Schema.LibraryConnectors.LibraryConnector");
|
b.HasBaseType("API.Schema.LibraryConnectors.LibraryConnector");
|
||||||
@ -580,18 +538,11 @@ namespace API.Migrations
|
|||||||
b.HasDiscriminator().HasValue((byte)0);
|
b.HasDiscriminator().HasValue((byte)0);
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("API.Schema.MangaConnectors.AsuraToon", b =>
|
modelBuilder.Entity("API.Schema.MangaConnectors.Global", b =>
|
||||||
{
|
{
|
||||||
b.HasBaseType("API.Schema.MangaConnectors.MangaConnector");
|
b.HasBaseType("API.Schema.MangaConnectors.MangaConnector");
|
||||||
|
|
||||||
b.HasDiscriminator().HasValue("AsuraToon");
|
b.HasDiscriminator().HasValue("Global");
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("API.Schema.MangaConnectors.Bato", b =>
|
|
||||||
{
|
|
||||||
b.HasBaseType("API.Schema.MangaConnectors.MangaConnector");
|
|
||||||
|
|
||||||
b.HasDiscriminator().HasValue("Bato");
|
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("API.Schema.MangaConnectors.MangaDex", b =>
|
modelBuilder.Entity("API.Schema.MangaConnectors.MangaDex", b =>
|
||||||
@ -601,52 +552,10 @@ namespace API.Migrations
|
|||||||
b.HasDiscriminator().HasValue("MangaDex");
|
b.HasDiscriminator().HasValue("MangaDex");
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("API.Schema.MangaConnectors.MangaHere", b =>
|
|
||||||
{
|
|
||||||
b.HasBaseType("API.Schema.MangaConnectors.MangaConnector");
|
|
||||||
|
|
||||||
b.HasDiscriminator().HasValue("MangaHere");
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("API.Schema.MangaConnectors.MangaKatana", b =>
|
|
||||||
{
|
|
||||||
b.HasBaseType("API.Schema.MangaConnectors.MangaConnector");
|
|
||||||
|
|
||||||
b.HasDiscriminator().HasValue("MangaKatana");
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("API.Schema.MangaConnectors.Manganato", b =>
|
|
||||||
{
|
|
||||||
b.HasBaseType("API.Schema.MangaConnectors.MangaConnector");
|
|
||||||
|
|
||||||
b.HasDiscriminator().HasValue("Manganato");
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("API.Schema.MangaConnectors.Mangaworld", b =>
|
|
||||||
{
|
|
||||||
b.HasBaseType("API.Schema.MangaConnectors.MangaConnector");
|
|
||||||
|
|
||||||
b.HasDiscriminator().HasValue("Mangaworld");
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("API.Schema.MangaConnectors.ManhuaPlus", b =>
|
|
||||||
{
|
|
||||||
b.HasBaseType("API.Schema.MangaConnectors.MangaConnector");
|
|
||||||
|
|
||||||
b.HasDiscriminator().HasValue("ManhuaPlus");
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("API.Schema.MangaConnectors.Weebcentral", b =>
|
|
||||||
{
|
|
||||||
b.HasBaseType("API.Schema.MangaConnectors.MangaConnector");
|
|
||||||
|
|
||||||
b.HasDiscriminator().HasValue("Weebcentral");
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("API.Schema.Chapter", b =>
|
modelBuilder.Entity("API.Schema.Chapter", b =>
|
||||||
{
|
{
|
||||||
b.HasOne("API.Schema.Manga", "ParentManga")
|
b.HasOne("API.Schema.Manga", "ParentManga")
|
||||||
.WithMany()
|
.WithMany("Chapters")
|
||||||
.HasForeignKey("ParentMangaId")
|
.HasForeignKey("ParentMangaId")
|
||||||
.OnDelete(DeleteBehavior.Cascade)
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
.IsRequired();
|
.IsRequired();
|
||||||
@ -664,51 +573,99 @@ namespace API.Migrations
|
|||||||
b.Navigation("ParentJob");
|
b.Navigation("ParentJob");
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("API.Schema.Link", b =>
|
|
||||||
{
|
|
||||||
b.HasOne("API.Schema.Manga", null)
|
|
||||||
.WithMany("Links")
|
|
||||||
.HasForeignKey("MangaId")
|
|
||||||
.OnDelete(DeleteBehavior.Cascade);
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("API.Schema.Manga", b =>
|
modelBuilder.Entity("API.Schema.Manga", b =>
|
||||||
{
|
{
|
||||||
b.HasOne("API.Schema.LocalLibrary", "Library")
|
b.HasOne("API.Schema.LocalLibrary", "Library")
|
||||||
.WithMany()
|
.WithMany()
|
||||||
.HasForeignKey("LibraryLocalLibraryId")
|
.HasForeignKey("LibraryId")
|
||||||
.OnDelete(DeleteBehavior.Cascade);
|
.OnDelete(DeleteBehavior.SetNull);
|
||||||
|
|
||||||
b.HasOne("API.Schema.MangaConnectors.MangaConnector", "MangaConnector")
|
b.HasOne("API.Schema.MangaConnectors.MangaConnector", "MangaConnector")
|
||||||
.WithMany()
|
.WithMany()
|
||||||
.HasForeignKey("MangaConnectorId")
|
.HasForeignKey("MangaConnectorName")
|
||||||
.OnDelete(DeleteBehavior.Cascade)
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
.IsRequired();
|
.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("Library");
|
||||||
|
|
||||||
|
b.Navigation("Links");
|
||||||
|
|
||||||
b.Navigation("MangaConnector");
|
b.Navigation("MangaConnector");
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("API.Schema.MangaAltTitle", b =>
|
modelBuilder.Entity("AuthorToManga", b =>
|
||||||
{
|
|
||||||
b.HasOne("API.Schema.Manga", null)
|
|
||||||
.WithMany("AltTitles")
|
|
||||||
.HasForeignKey("MangaId")
|
|
||||||
.OnDelete(DeleteBehavior.Cascade);
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("AuthorManga", b =>
|
|
||||||
{
|
{
|
||||||
b.HasOne("API.Schema.Author", null)
|
b.HasOne("API.Schema.Author", null)
|
||||||
.WithMany()
|
.WithMany()
|
||||||
.HasForeignKey("AuthorsAuthorId")
|
.HasForeignKey("AuthorIds")
|
||||||
.OnDelete(DeleteBehavior.Cascade)
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
.IsRequired();
|
.IsRequired();
|
||||||
|
|
||||||
b.HasOne("API.Schema.Manga", null)
|
b.HasOne("API.Schema.Manga", null)
|
||||||
.WithMany()
|
.WithMany()
|
||||||
.HasForeignKey("MangaId")
|
.HasForeignKey("MangaIds")
|
||||||
.OnDelete(DeleteBehavior.Cascade)
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
.IsRequired();
|
.IsRequired();
|
||||||
});
|
});
|
||||||
@ -728,17 +685,17 @@ namespace API.Migrations
|
|||||||
.IsRequired();
|
.IsRequired();
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("MangaMangaTag", b =>
|
modelBuilder.Entity("MangaTagToManga", b =>
|
||||||
{
|
{
|
||||||
b.HasOne("API.Schema.Manga", null)
|
b.HasOne("API.Schema.Manga", null)
|
||||||
.WithMany()
|
.WithMany()
|
||||||
.HasForeignKey("MangaId")
|
.HasForeignKey("MangaIds")
|
||||||
.OnDelete(DeleteBehavior.Cascade)
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
.IsRequired();
|
.IsRequired();
|
||||||
|
|
||||||
b.HasOne("API.Schema.MangaTag", null)
|
b.HasOne("API.Schema.MangaTag", null)
|
||||||
.WithMany()
|
.WithMany()
|
||||||
.HasForeignKey("MangaTagsTag")
|
.HasForeignKey("MangaTagIds")
|
||||||
.OnDelete(DeleteBehavior.Cascade)
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
.IsRequired();
|
.IsRequired();
|
||||||
});
|
});
|
||||||
@ -776,6 +733,25 @@ namespace API.Migrations
|
|||||||
b.Navigation("Chapter");
|
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 =>
|
modelBuilder.Entity("API.Schema.Jobs.RetrieveChaptersJob", b =>
|
||||||
{
|
{
|
||||||
b.HasOne("API.Schema.Manga", "Manga")
|
b.HasOne("API.Schema.Manga", "Manga")
|
||||||
@ -798,22 +774,9 @@ namespace API.Migrations
|
|||||||
b.Navigation("Manga");
|
b.Navigation("Manga");
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("API.Schema.Jobs.UpdateMetadataJob", b =>
|
|
||||||
{
|
|
||||||
b.HasOne("API.Schema.Manga", "Manga")
|
|
||||||
.WithMany()
|
|
||||||
.HasForeignKey("MangaId")
|
|
||||||
.OnDelete(DeleteBehavior.Cascade)
|
|
||||||
.IsRequired();
|
|
||||||
|
|
||||||
b.Navigation("Manga");
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("API.Schema.Manga", b =>
|
modelBuilder.Entity("API.Schema.Manga", b =>
|
||||||
{
|
{
|
||||||
b.Navigation("AltTitles");
|
b.Navigation("Chapters");
|
||||||
|
|
||||||
b.Navigation("Links");
|
|
||||||
});
|
});
|
||||||
#pragma warning restore 612, 618
|
#pragma warning restore 612, 618
|
||||||
}
|
}
|
@ -5,35 +5,35 @@
|
|||||||
namespace API.Migrations
|
namespace API.Migrations
|
||||||
{
|
{
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public partial class dev0104251 : Migration
|
public partial class Initial4 : Migration
|
||||||
{
|
{
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
protected override void Up(MigrationBuilder migrationBuilder)
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
{
|
{
|
||||||
migrationBuilder.AlterColumn<string>(
|
migrationBuilder.AlterColumn<string>(
|
||||||
name: "OriginalLanguage",
|
name: "LibraryId",
|
||||||
table: "Mangas",
|
table: "Mangas",
|
||||||
type: "character varying(8)",
|
type: "character varying(64)",
|
||||||
maxLength: 8,
|
maxLength: 64,
|
||||||
nullable: true,
|
nullable: true,
|
||||||
oldClrType: typeof(string),
|
oldClrType: typeof(string),
|
||||||
oldType: "character varying(8)",
|
oldType: "character varying(64)",
|
||||||
oldMaxLength: 8);
|
oldMaxLength: 64);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
protected override void Down(MigrationBuilder migrationBuilder)
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
{
|
{
|
||||||
migrationBuilder.AlterColumn<string>(
|
migrationBuilder.AlterColumn<string>(
|
||||||
name: "OriginalLanguage",
|
name: "LibraryId",
|
||||||
table: "Mangas",
|
table: "Mangas",
|
||||||
type: "character varying(8)",
|
type: "character varying(64)",
|
||||||
maxLength: 8,
|
maxLength: 64,
|
||||||
nullable: false,
|
nullable: false,
|
||||||
defaultValue: "",
|
defaultValue: "",
|
||||||
oldClrType: typeof(string),
|
oldClrType: typeof(string),
|
||||||
oldType: "character varying(8)",
|
oldType: "character varying(64)",
|
||||||
oldMaxLength: 8,
|
oldMaxLength: 64,
|
||||||
oldNullable: true);
|
oldNullable: true);
|
||||||
}
|
}
|
||||||
}
|
}
|
783
API/Migrations/20250509035754_Initial-5.Designer.cs
generated
Normal file
783
API/Migrations/20250509035754_Initial-5.Designer.cs
generated
Normal file
@ -0,0 +1,783 @@
|
|||||||
|
// <auto-generated />
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using API.Schema;
|
||||||
|
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
|
||||||
|
{
|
||||||
|
[DbContext(typeof(PgsqlContext))]
|
||||||
|
[Migration("20250509035754_Initial-5")]
|
||||||
|
partial class Initial5
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||||
|
{
|
||||||
|
#pragma warning disable 612, 618
|
||||||
|
modelBuilder
|
||||||
|
.HasAnnotation("ProductVersion", "9.0.3")
|
||||||
|
.HasAnnotation("Relational:MaxIdentifierLength", 63);
|
||||||
|
|
||||||
|
NpgsqlModelBuilderExtensions.HasPostgresExtension(modelBuilder, "hstore");
|
||||||
|
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>("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.LibraryConnectors.LibraryConnector", b =>
|
||||||
|
{
|
||||||
|
b.Property<string>("LibraryConnectorId")
|
||||||
|
.HasMaxLength(64)
|
||||||
|
.HasColumnType("character varying(64)");
|
||||||
|
|
||||||
|
b.Property<string>("Auth")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(256)
|
||||||
|
.HasColumnType("character varying(256)");
|
||||||
|
|
||||||
|
b.Property<string>("BaseUrl")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(256)
|
||||||
|
.HasColumnType("character varying(256)");
|
||||||
|
|
||||||
|
b.Property<byte>("LibraryType")
|
||||||
|
.HasColumnType("smallint");
|
||||||
|
|
||||||
|
b.HasKey("LibraryConnectorId");
|
||||||
|
|
||||||
|
b.ToTable("LibraryConnectors");
|
||||||
|
|
||||||
|
b.HasDiscriminator<byte>("LibraryType");
|
||||||
|
|
||||||
|
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.Notification", b =>
|
||||||
|
{
|
||||||
|
b.Property<string>("NotificationId")
|
||||||
|
.HasMaxLength(64)
|
||||||
|
.HasColumnType("character varying(64)");
|
||||||
|
|
||||||
|
b.Property<DateTime>("Date")
|
||||||
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
|
b.Property<string>("Message")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(512)
|
||||||
|
.HasColumnType("character varying(512)");
|
||||||
|
|
||||||
|
b.Property<string>("Title")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(128)
|
||||||
|
.HasColumnType("character varying(128)");
|
||||||
|
|
||||||
|
b.Property<byte>("Urgency")
|
||||||
|
.HasColumnType("smallint");
|
||||||
|
|
||||||
|
b.HasKey("NotificationId");
|
||||||
|
|
||||||
|
b.ToTable("Notifications");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("API.Schema.NotificationConnectors.NotificationConnector", b =>
|
||||||
|
{
|
||||||
|
b.Property<string>("Name")
|
||||||
|
.HasMaxLength(64)
|
||||||
|
.HasColumnType("character varying(64)");
|
||||||
|
|
||||||
|
b.Property<string>("Body")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(4096)
|
||||||
|
.HasColumnType("character varying(4096)");
|
||||||
|
|
||||||
|
b.Property<Dictionary<string, string>>("Headers")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("hstore");
|
||||||
|
|
||||||
|
b.Property<string>("HttpMethod")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(8)
|
||||||
|
.HasColumnType("character varying(8)");
|
||||||
|
|
||||||
|
b.Property<string>("Url")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(2048)
|
||||||
|
.HasColumnType("character varying(2048)");
|
||||||
|
|
||||||
|
b.HasKey("Name");
|
||||||
|
|
||||||
|
b.ToTable("NotificationConnectors");
|
||||||
|
});
|
||||||
|
|
||||||
|
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.UpdateFilesDownloadedJob", 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("UpdateFilesDownloadedJob_MangaId");
|
||||||
|
});
|
||||||
|
|
||||||
|
b.HasDiscriminator().HasValue((byte)6);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("API.Schema.LibraryConnectors.Kavita", b =>
|
||||||
|
{
|
||||||
|
b.HasBaseType("API.Schema.LibraryConnectors.LibraryConnector");
|
||||||
|
|
||||||
|
b.HasDiscriminator().HasValue((byte)1);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("API.Schema.LibraryConnectors.Komga", b =>
|
||||||
|
{
|
||||||
|
b.HasBaseType("API.Schema.LibraryConnectors.LibraryConnector");
|
||||||
|
|
||||||
|
b.HasDiscriminator().HasValue((byte)0);
|
||||||
|
});
|
||||||
|
|
||||||
|
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.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("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.UpdateFilesDownloadedJob", 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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
40
API/Migrations/20250509035754_Initial-5.cs
Normal file
40
API/Migrations/20250509035754_Initial-5.cs
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace API.Migrations
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public partial class Initial5 : Migration
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.AlterColumn<string>(
|
||||||
|
name: "ParentJobId",
|
||||||
|
table: "Jobs",
|
||||||
|
type: "character varying(64)",
|
||||||
|
maxLength: 64,
|
||||||
|
nullable: true,
|
||||||
|
oldClrType: typeof(string),
|
||||||
|
oldType: "character varying(64)",
|
||||||
|
oldMaxLength: 64);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.AlterColumn<string>(
|
||||||
|
name: "ParentJobId",
|
||||||
|
table: "Jobs",
|
||||||
|
type: "character varying(64)",
|
||||||
|
maxLength: 64,
|
||||||
|
nullable: false,
|
||||||
|
defaultValue: "",
|
||||||
|
oldClrType: typeof(string),
|
||||||
|
oldType: "character varying(64)",
|
||||||
|
oldMaxLength: 64,
|
||||||
|
oldNullable: true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -61,7 +61,6 @@ namespace API.Migrations
|
|||||||
|
|
||||||
b.Property<string>("ParentMangaId")
|
b.Property<string>("ParentMangaId")
|
||||||
.IsRequired()
|
.IsRequired()
|
||||||
.HasMaxLength(64)
|
|
||||||
.HasColumnType("character varying(64)");
|
.HasColumnType("character varying(64)");
|
||||||
|
|
||||||
b.Property<string>("Title")
|
b.Property<string>("Title")
|
||||||
@ -89,10 +88,6 @@ namespace API.Migrations
|
|||||||
.HasMaxLength(64)
|
.HasMaxLength(64)
|
||||||
.HasColumnType("character varying(64)");
|
.HasColumnType("character varying(64)");
|
||||||
|
|
||||||
b.PrimitiveCollection<string[]>("DependsOnJobsIds")
|
|
||||||
.HasMaxLength(64)
|
|
||||||
.HasColumnType("text[]");
|
|
||||||
|
|
||||||
b.Property<bool>("Enabled")
|
b.Property<bool>("Enabled")
|
||||||
.HasColumnType("boolean");
|
.HasColumnType("boolean");
|
||||||
|
|
||||||
@ -151,32 +146,6 @@ namespace API.Migrations
|
|||||||
b.UseTphMappingStrategy();
|
b.UseTphMappingStrategy();
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("API.Schema.Link", b =>
|
|
||||||
{
|
|
||||||
b.Property<string>("LinkId")
|
|
||||||
.HasMaxLength(64)
|
|
||||||
.HasColumnType("character varying(64)");
|
|
||||||
|
|
||||||
b.Property<string>("LinkProvider")
|
|
||||||
.IsRequired()
|
|
||||||
.HasMaxLength(64)
|
|
||||||
.HasColumnType("character varying(64)");
|
|
||||||
|
|
||||||
b.Property<string>("LinkUrl")
|
|
||||||
.IsRequired()
|
|
||||||
.HasMaxLength(2048)
|
|
||||||
.HasColumnType("character varying(2048)");
|
|
||||||
|
|
||||||
b.Property<string>("MangaId")
|
|
||||||
.HasColumnType("character varying(64)");
|
|
||||||
|
|
||||||
b.HasKey("LinkId");
|
|
||||||
|
|
||||||
b.HasIndex("MangaId");
|
|
||||||
|
|
||||||
b.ToTable("Links");
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("API.Schema.LocalLibrary", b =>
|
modelBuilder.Entity("API.Schema.LocalLibrary", b =>
|
||||||
{
|
{
|
||||||
b.Property<string>("LocalLibraryId")
|
b.Property<string>("LocalLibraryId")
|
||||||
@ -205,11 +174,13 @@ namespace API.Migrations
|
|||||||
.HasColumnType("character varying(64)");
|
.HasColumnType("character varying(64)");
|
||||||
|
|
||||||
b.Property<string>("CoverFileNameInCache")
|
b.Property<string>("CoverFileNameInCache")
|
||||||
.HasColumnType("text");
|
.HasMaxLength(512)
|
||||||
|
.HasColumnType("character varying(512)");
|
||||||
|
|
||||||
b.Property<string>("CoverUrl")
|
b.Property<string>("CoverUrl")
|
||||||
.IsRequired()
|
.IsRequired()
|
||||||
.HasColumnType("text");
|
.HasMaxLength(512)
|
||||||
|
.HasColumnType("character varying(512)");
|
||||||
|
|
||||||
b.Property<string>("Description")
|
b.Property<string>("Description")
|
||||||
.IsRequired()
|
.IsRequired()
|
||||||
@ -225,17 +196,18 @@ namespace API.Migrations
|
|||||||
.HasMaxLength(256)
|
.HasMaxLength(256)
|
||||||
.HasColumnType("character varying(256)");
|
.HasColumnType("character varying(256)");
|
||||||
|
|
||||||
b.Property<float>("IgnoreChapterBefore")
|
b.Property<float>("IgnoreChaptersBefore")
|
||||||
.HasColumnType("real");
|
.HasColumnType("real");
|
||||||
|
|
||||||
b.Property<string>("LibraryLocalLibraryId")
|
b.Property<string>("LibraryId")
|
||||||
.HasColumnType("character varying(64)");
|
|
||||||
|
|
||||||
b.Property<string>("MangaConnectorId")
|
|
||||||
.IsRequired()
|
|
||||||
.HasMaxLength(64)
|
.HasMaxLength(64)
|
||||||
.HasColumnType("character varying(64)");
|
.HasColumnType("character varying(64)");
|
||||||
|
|
||||||
|
b.Property<string>("MangaConnectorName")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(32)
|
||||||
|
.HasColumnType("character varying(32)");
|
||||||
|
|
||||||
b.Property<string>("Name")
|
b.Property<string>("Name")
|
||||||
.IsRequired()
|
.IsRequired()
|
||||||
.HasMaxLength(512)
|
.HasMaxLength(512)
|
||||||
@ -258,39 +230,13 @@ namespace API.Migrations
|
|||||||
|
|
||||||
b.HasKey("MangaId");
|
b.HasKey("MangaId");
|
||||||
|
|
||||||
b.HasIndex("LibraryLocalLibraryId");
|
b.HasIndex("LibraryId");
|
||||||
|
|
||||||
b.HasIndex("MangaConnectorId");
|
b.HasIndex("MangaConnectorName");
|
||||||
|
|
||||||
b.ToTable("Mangas");
|
b.ToTable("Mangas");
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("API.Schema.MangaAltTitle", b =>
|
|
||||||
{
|
|
||||||
b.Property<string>("AltTitleId")
|
|
||||||
.HasMaxLength(64)
|
|
||||||
.HasColumnType("character varying(64)");
|
|
||||||
|
|
||||||
b.Property<string>("Language")
|
|
||||||
.IsRequired()
|
|
||||||
.HasMaxLength(8)
|
|
||||||
.HasColumnType("character varying(8)");
|
|
||||||
|
|
||||||
b.Property<string>("MangaId")
|
|
||||||
.HasColumnType("character varying(64)");
|
|
||||||
|
|
||||||
b.Property<string>("Title")
|
|
||||||
.IsRequired()
|
|
||||||
.HasMaxLength(256)
|
|
||||||
.HasColumnType("character varying(256)");
|
|
||||||
|
|
||||||
b.HasKey("AltTitleId");
|
|
||||||
|
|
||||||
b.HasIndex("MangaId");
|
|
||||||
|
|
||||||
b.ToTable("AltTitles");
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("API.Schema.MangaConnectors.MangaConnector", b =>
|
modelBuilder.Entity("API.Schema.MangaConnectors.MangaConnector", b =>
|
||||||
{
|
{
|
||||||
b.Property<string>("Name")
|
b.Property<string>("Name")
|
||||||
@ -392,19 +338,19 @@ namespace API.Migrations
|
|||||||
b.ToTable("NotificationConnectors");
|
b.ToTable("NotificationConnectors");
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("AuthorManga", b =>
|
modelBuilder.Entity("AuthorToManga", b =>
|
||||||
{
|
{
|
||||||
b.Property<string>("AuthorsAuthorId")
|
b.Property<string>("AuthorIds")
|
||||||
.HasColumnType("character varying(64)");
|
.HasColumnType("character varying(64)");
|
||||||
|
|
||||||
b.Property<string>("MangaId")
|
b.Property<string>("MangaIds")
|
||||||
.HasColumnType("character varying(64)");
|
.HasColumnType("character varying(64)");
|
||||||
|
|
||||||
b.HasKey("AuthorsAuthorId", "MangaId");
|
b.HasKey("AuthorIds", "MangaIds");
|
||||||
|
|
||||||
b.HasIndex("MangaId");
|
b.HasIndex("MangaIds");
|
||||||
|
|
||||||
b.ToTable("AuthorManga");
|
b.ToTable("AuthorToManga");
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("JobJob", b =>
|
modelBuilder.Entity("JobJob", b =>
|
||||||
@ -422,19 +368,19 @@ namespace API.Migrations
|
|||||||
b.ToTable("JobJob");
|
b.ToTable("JobJob");
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("MangaMangaTag", b =>
|
modelBuilder.Entity("MangaTagToManga", b =>
|
||||||
{
|
{
|
||||||
b.Property<string>("MangaId")
|
b.Property<string>("MangaTagIds")
|
||||||
.HasColumnType("character varying(64)");
|
.HasColumnType("character varying(64)");
|
||||||
|
|
||||||
b.Property<string>("MangaTagsTag")
|
b.Property<string>("MangaIds")
|
||||||
.HasColumnType("character varying(64)");
|
.HasColumnType("character varying(64)");
|
||||||
|
|
||||||
b.HasKey("MangaId", "MangaTagsTag");
|
b.HasKey("MangaTagIds", "MangaIds");
|
||||||
|
|
||||||
b.HasIndex("MangaTagsTag");
|
b.HasIndex("MangaIds");
|
||||||
|
|
||||||
b.ToTable("MangaMangaTag");
|
b.ToTable("MangaTagToManga");
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("API.Schema.Jobs.DownloadAvailableChaptersJob", b =>
|
modelBuilder.Entity("API.Schema.Jobs.DownloadAvailableChaptersJob", b =>
|
||||||
@ -446,6 +392,8 @@ namespace API.Migrations
|
|||||||
.HasMaxLength(64)
|
.HasMaxLength(64)
|
||||||
.HasColumnType("character varying(64)");
|
.HasColumnType("character varying(64)");
|
||||||
|
|
||||||
|
b.HasIndex("MangaId");
|
||||||
|
|
||||||
b.ToTable("Jobs", t =>
|
b.ToTable("Jobs", t =>
|
||||||
{
|
{
|
||||||
t.Property("MangaId")
|
t.Property("MangaId")
|
||||||
@ -464,6 +412,8 @@ namespace API.Migrations
|
|||||||
.HasMaxLength(64)
|
.HasMaxLength(64)
|
||||||
.HasColumnType("character varying(64)");
|
.HasColumnType("character varying(64)");
|
||||||
|
|
||||||
|
b.HasIndex("MangaId");
|
||||||
|
|
||||||
b.HasDiscriminator().HasValue((byte)4);
|
b.HasDiscriminator().HasValue((byte)4);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -476,6 +426,8 @@ namespace API.Migrations
|
|||||||
.HasMaxLength(64)
|
.HasMaxLength(64)
|
||||||
.HasColumnType("character varying(64)");
|
.HasColumnType("character varying(64)");
|
||||||
|
|
||||||
|
b.HasIndex("ChapterId");
|
||||||
|
|
||||||
b.HasDiscriminator().HasValue((byte)0);
|
b.HasDiscriminator().HasValue((byte)0);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -496,7 +448,7 @@ namespace API.Migrations
|
|||||||
b.HasDiscriminator().HasValue((byte)3);
|
b.HasDiscriminator().HasValue((byte)3);
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("API.Schema.Jobs.RetrieveChaptersJob", b =>
|
modelBuilder.Entity("API.Schema.Jobs.MoveMangaLibraryJob", b =>
|
||||||
{
|
{
|
||||||
b.HasBaseType("API.Schema.Jobs.Job");
|
b.HasBaseType("API.Schema.Jobs.Job");
|
||||||
|
|
||||||
@ -505,6 +457,40 @@ namespace API.Migrations
|
|||||||
.HasMaxLength(64)
|
.HasMaxLength(64)
|
||||||
.HasColumnType("character varying(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 =>
|
b.ToTable("Jobs", t =>
|
||||||
{
|
{
|
||||||
t.Property("MangaId")
|
t.Property("MangaId")
|
||||||
@ -523,6 +509,8 @@ namespace API.Migrations
|
|||||||
.HasMaxLength(64)
|
.HasMaxLength(64)
|
||||||
.HasColumnType("character varying(64)");
|
.HasColumnType("character varying(64)");
|
||||||
|
|
||||||
|
b.HasIndex("MangaId");
|
||||||
|
|
||||||
b.ToTable("Jobs", t =>
|
b.ToTable("Jobs", t =>
|
||||||
{
|
{
|
||||||
t.Property("MangaId")
|
t.Property("MangaId")
|
||||||
@ -532,26 +520,6 @@ namespace API.Migrations
|
|||||||
b.HasDiscriminator().HasValue((byte)6);
|
b.HasDiscriminator().HasValue((byte)6);
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("API.Schema.Jobs.UpdateMetadataJob", 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("UpdateMetadataJob_MangaId");
|
|
||||||
});
|
|
||||||
|
|
||||||
b.HasDiscriminator().HasValue((byte)2);
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("API.Schema.LibraryConnectors.Kavita", b =>
|
modelBuilder.Entity("API.Schema.LibraryConnectors.Kavita", b =>
|
||||||
{
|
{
|
||||||
b.HasBaseType("API.Schema.LibraryConnectors.LibraryConnector");
|
b.HasBaseType("API.Schema.LibraryConnectors.LibraryConnector");
|
||||||
@ -566,20 +534,6 @@ namespace API.Migrations
|
|||||||
b.HasDiscriminator().HasValue((byte)0);
|
b.HasDiscriminator().HasValue((byte)0);
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("API.Schema.MangaConnectors.AsuraToon", b =>
|
|
||||||
{
|
|
||||||
b.HasBaseType("API.Schema.MangaConnectors.MangaConnector");
|
|
||||||
|
|
||||||
b.HasDiscriminator().HasValue("AsuraToon");
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("API.Schema.MangaConnectors.Bato", b =>
|
|
||||||
{
|
|
||||||
b.HasBaseType("API.Schema.MangaConnectors.MangaConnector");
|
|
||||||
|
|
||||||
b.HasDiscriminator().HasValue("Bato");
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("API.Schema.MangaConnectors.Global", b =>
|
modelBuilder.Entity("API.Schema.MangaConnectors.Global", b =>
|
||||||
{
|
{
|
||||||
b.HasBaseType("API.Schema.MangaConnectors.MangaConnector");
|
b.HasBaseType("API.Schema.MangaConnectors.MangaConnector");
|
||||||
@ -594,52 +548,10 @@ namespace API.Migrations
|
|||||||
b.HasDiscriminator().HasValue("MangaDex");
|
b.HasDiscriminator().HasValue("MangaDex");
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("API.Schema.MangaConnectors.MangaHere", b =>
|
|
||||||
{
|
|
||||||
b.HasBaseType("API.Schema.MangaConnectors.MangaConnector");
|
|
||||||
|
|
||||||
b.HasDiscriminator().HasValue("MangaHere");
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("API.Schema.MangaConnectors.MangaKatana", b =>
|
|
||||||
{
|
|
||||||
b.HasBaseType("API.Schema.MangaConnectors.MangaConnector");
|
|
||||||
|
|
||||||
b.HasDiscriminator().HasValue("MangaKatana");
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("API.Schema.MangaConnectors.Manganato", b =>
|
|
||||||
{
|
|
||||||
b.HasBaseType("API.Schema.MangaConnectors.MangaConnector");
|
|
||||||
|
|
||||||
b.HasDiscriminator().HasValue("Manganato");
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("API.Schema.MangaConnectors.Mangaworld", b =>
|
|
||||||
{
|
|
||||||
b.HasBaseType("API.Schema.MangaConnectors.MangaConnector");
|
|
||||||
|
|
||||||
b.HasDiscriminator().HasValue("Mangaworld");
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("API.Schema.MangaConnectors.ManhuaPlus", b =>
|
|
||||||
{
|
|
||||||
b.HasBaseType("API.Schema.MangaConnectors.MangaConnector");
|
|
||||||
|
|
||||||
b.HasDiscriminator().HasValue("ManhuaPlus");
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("API.Schema.MangaConnectors.Weebcentral", b =>
|
|
||||||
{
|
|
||||||
b.HasBaseType("API.Schema.MangaConnectors.MangaConnector");
|
|
||||||
|
|
||||||
b.HasDiscriminator().HasValue("Weebcentral");
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("API.Schema.Chapter", b =>
|
modelBuilder.Entity("API.Schema.Chapter", b =>
|
||||||
{
|
{
|
||||||
b.HasOne("API.Schema.Manga", "ParentManga")
|
b.HasOne("API.Schema.Manga", "ParentManga")
|
||||||
.WithMany()
|
.WithMany("Chapters")
|
||||||
.HasForeignKey("ParentMangaId")
|
.HasForeignKey("ParentMangaId")
|
||||||
.OnDelete(DeleteBehavior.Cascade)
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
.IsRequired();
|
.IsRequired();
|
||||||
@ -657,51 +569,99 @@ namespace API.Migrations
|
|||||||
b.Navigation("ParentJob");
|
b.Navigation("ParentJob");
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("API.Schema.Link", b =>
|
|
||||||
{
|
|
||||||
b.HasOne("API.Schema.Manga", null)
|
|
||||||
.WithMany("Links")
|
|
||||||
.HasForeignKey("MangaId")
|
|
||||||
.OnDelete(DeleteBehavior.Cascade);
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("API.Schema.Manga", b =>
|
modelBuilder.Entity("API.Schema.Manga", b =>
|
||||||
{
|
{
|
||||||
b.HasOne("API.Schema.LocalLibrary", "Library")
|
b.HasOne("API.Schema.LocalLibrary", "Library")
|
||||||
.WithMany()
|
.WithMany()
|
||||||
.HasForeignKey("LibraryLocalLibraryId")
|
.HasForeignKey("LibraryId")
|
||||||
.OnDelete(DeleteBehavior.Restrict);
|
.OnDelete(DeleteBehavior.SetNull);
|
||||||
|
|
||||||
b.HasOne("API.Schema.MangaConnectors.MangaConnector", "MangaConnector")
|
b.HasOne("API.Schema.MangaConnectors.MangaConnector", "MangaConnector")
|
||||||
.WithMany()
|
.WithMany()
|
||||||
.HasForeignKey("MangaConnectorId")
|
.HasForeignKey("MangaConnectorName")
|
||||||
.OnDelete(DeleteBehavior.Cascade)
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
.IsRequired();
|
.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("Library");
|
||||||
|
|
||||||
|
b.Navigation("Links");
|
||||||
|
|
||||||
b.Navigation("MangaConnector");
|
b.Navigation("MangaConnector");
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("API.Schema.MangaAltTitle", b =>
|
modelBuilder.Entity("AuthorToManga", b =>
|
||||||
{
|
|
||||||
b.HasOne("API.Schema.Manga", null)
|
|
||||||
.WithMany("AltTitles")
|
|
||||||
.HasForeignKey("MangaId")
|
|
||||||
.OnDelete(DeleteBehavior.Cascade);
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("AuthorManga", b =>
|
|
||||||
{
|
{
|
||||||
b.HasOne("API.Schema.Author", null)
|
b.HasOne("API.Schema.Author", null)
|
||||||
.WithMany()
|
.WithMany()
|
||||||
.HasForeignKey("AuthorsAuthorId")
|
.HasForeignKey("AuthorIds")
|
||||||
.OnDelete(DeleteBehavior.Cascade)
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
.IsRequired();
|
.IsRequired();
|
||||||
|
|
||||||
b.HasOne("API.Schema.Manga", null)
|
b.HasOne("API.Schema.Manga", null)
|
||||||
.WithMany()
|
.WithMany()
|
||||||
.HasForeignKey("MangaId")
|
.HasForeignKey("MangaIds")
|
||||||
.OnDelete(DeleteBehavior.Cascade)
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
.IsRequired();
|
.IsRequired();
|
||||||
});
|
});
|
||||||
@ -721,22 +681,85 @@ namespace API.Migrations
|
|||||||
.IsRequired();
|
.IsRequired();
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("MangaMangaTag", b =>
|
modelBuilder.Entity("MangaTagToManga", b =>
|
||||||
{
|
{
|
||||||
b.HasOne("API.Schema.Manga", null)
|
b.HasOne("API.Schema.Manga", null)
|
||||||
.WithMany()
|
.WithMany()
|
||||||
.HasForeignKey("MangaId")
|
.HasForeignKey("MangaIds")
|
||||||
.OnDelete(DeleteBehavior.Cascade)
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
.IsRequired();
|
.IsRequired();
|
||||||
|
|
||||||
b.HasOne("API.Schema.MangaTag", null)
|
b.HasOne("API.Schema.MangaTag", null)
|
||||||
.WithMany()
|
.WithMany()
|
||||||
.HasForeignKey("MangaTagsTag")
|
.HasForeignKey("MangaTagIds")
|
||||||
.OnDelete(DeleteBehavior.Cascade)
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
.IsRequired();
|
.IsRequired();
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("API.Schema.Jobs.UpdateMetadataJob", b =>
|
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.UpdateFilesDownloadedJob", b =>
|
||||||
{
|
{
|
||||||
b.HasOne("API.Schema.Manga", "Manga")
|
b.HasOne("API.Schema.Manga", "Manga")
|
||||||
.WithMany()
|
.WithMany()
|
||||||
@ -749,9 +772,7 @@ namespace API.Migrations
|
|||||||
|
|
||||||
modelBuilder.Entity("API.Schema.Manga", b =>
|
modelBuilder.Entity("API.Schema.Manga", b =>
|
||||||
{
|
{
|
||||||
b.Navigation("AltTitles");
|
b.Navigation("Chapters");
|
||||||
|
|
||||||
b.Navigation("Links");
|
|
||||||
});
|
});
|
||||||
#pragma warning restore 612, 618
|
#pragma warning restore 612, 618
|
||||||
}
|
}
|
||||||
|
@ -7,6 +7,7 @@ using API.Schema.MangaConnectors;
|
|||||||
using Asp.Versioning;
|
using Asp.Versioning;
|
||||||
using Asp.Versioning.Builder;
|
using Asp.Versioning.Builder;
|
||||||
using Asp.Versioning.Conventions;
|
using Asp.Versioning.Conventions;
|
||||||
|
using log4net;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
using Newtonsoft.Json.Converters;
|
using Newtonsoft.Json.Converters;
|
||||||
@ -69,6 +70,7 @@ builder.Services.AddControllers().AddNewtonsoftJson(opts =>
|
|||||||
opts.SerializerSettings.Converters.Add(new StringEnumConverter());
|
opts.SerializerSettings.Converters.Add(new StringEnumConverter());
|
||||||
opts.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore;
|
opts.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore;
|
||||||
});
|
});
|
||||||
|
builder.Services.AddScoped<ILog>(opts => LogManager.GetLogger("API"));
|
||||||
|
|
||||||
builder.WebHost.UseUrls("http://*:6531");
|
builder.WebHost.UseUrls("http://*:6531");
|
||||||
|
|
||||||
@ -109,26 +111,18 @@ using (var scope = app.Services.CreateScope())
|
|||||||
|
|
||||||
MangaConnector[] connectors =
|
MangaConnector[] connectors =
|
||||||
[
|
[
|
||||||
new AsuraToon(),
|
|
||||||
new Bato(),
|
|
||||||
new MangaDex(),
|
new MangaDex(),
|
||||||
new MangaHere(),
|
|
||||||
new MangaKatana(),
|
|
||||||
new Mangaworld(),
|
|
||||||
new ManhuaPlus(),
|
|
||||||
new Weebcentral(),
|
|
||||||
//new Manganato(),
|
|
||||||
new Global(scope.ServiceProvider.GetService<PgsqlContext>()!)
|
new Global(scope.ServiceProvider.GetService<PgsqlContext>()!)
|
||||||
];
|
];
|
||||||
MangaConnector[] newConnectors = connectors.Where(c => !context.MangaConnectors.Contains(c)).ToArray();
|
MangaConnector[] newConnectors = connectors.Where(c => !context.MangaConnectors.Contains(c)).ToArray();
|
||||||
context.MangaConnectors.AddRange(newConnectors);
|
context.MangaConnectors.AddRange(newConnectors);
|
||||||
|
|
||||||
context.Jobs.AddRange(context.Mangas.AsEnumerable().Select(m => new UpdateFilesDownloadedJob(0, m.MangaId)));
|
context.Jobs.AddRange(context.Mangas.AsEnumerable().Select(m => new UpdateFilesDownloadedJob(m, 0)));
|
||||||
|
|
||||||
context.Jobs.RemoveRange(context.Jobs.Where(j => j.state == JobState.Completed && j.RecurrenceMs < 1));
|
context.Jobs.RemoveRange(context.Jobs.Where(j => j.state == JobState.Completed && j.RecurrenceMs < 1));
|
||||||
|
|
||||||
if (!context.LocalLibraries.Any())
|
if (!context.LocalLibraries.Any())
|
||||||
context.LocalLibraries.Add(new LocalLibrary(TrangaSettings.downloadLocation, "Default Library"));
|
context.LocalLibraries.Add(new LocalLibrary(TrangaSettings.downloadLocation, "Default ToLibrary"));
|
||||||
|
|
||||||
string[] emojis = { "(•‿•)", "(づ \u25d5‿\u25d5 )づ", "( \u02d8\u25bd\u02d8)っ\u2668", "=\uff3e\u25cf \u22cf \u25cf\uff3e=", "(ΦωΦ)", "(\u272a\u3268\u272a)", "( ノ・o・ )ノ", "(〜^\u2207^ )〜", "~(\u2267ω\u2266)~","૮ \u00b4• ﻌ \u00b4• ა", "(\u02c3ᆺ\u02c2)", "(=\ud83d\udf66 \u0f1d \ud83d\udf66=)"};
|
string[] emojis = { "(•‿•)", "(づ \u25d5‿\u25d5 )づ", "( \u02d8\u25bd\u02d8)っ\u2668", "=\uff3e\u25cf \u22cf \u25cf\uff3e=", "(ΦωΦ)", "(\u272a\u3268\u272a)", "( ノ・o・ )ノ", "(〜^\u2207^ )〜", "~(\u2267ω\u2266)~","૮ \u00b4• ﻌ \u00b4• ა", "(\u02c3ᆺ\u02c2)", "(=\ud83d\udf66 \u0f1d \ud83d\udf66=)"};
|
||||||
context.Notifications.Add(new Notification("Tranga Started", emojis[Random.Shared.Next(0, emojis.Length - 1)], NotificationUrgency.High));
|
context.Notifications.Add(new Notification("Tranga Started", emojis[Random.Shared.Next(0, emojis.Length - 1)], NotificationUrgency.High));
|
||||||
|
@ -3,7 +3,6 @@ using System.ComponentModel.DataAnnotations.Schema;
|
|||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
using System.Xml.Linq;
|
using System.Xml.Linq;
|
||||||
using API.Schema.Jobs;
|
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
|
|
||||||
@ -12,51 +11,50 @@ namespace API.Schema;
|
|||||||
[PrimaryKey("ChapterId")]
|
[PrimaryKey("ChapterId")]
|
||||||
public class Chapter : IComparable<Chapter>
|
public class Chapter : IComparable<Chapter>
|
||||||
{
|
{
|
||||||
public Chapter(Manga parentManga, string url, string chapterNumber, int? volumeNumber = null, string? title = null)
|
[StringLength(64)] [Required] public string ChapterId { get; init; }
|
||||||
: this(parentManga.MangaId, url, chapterNumber, volumeNumber, title)
|
|
||||||
{
|
|
||||||
ParentManga = parentManga;
|
|
||||||
FileName = GetArchiveFilePath(parentManga.Name);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Chapter(string parentMangaId, string url, string chapterNumber,
|
public string ParentMangaId { get; init; }
|
||||||
int? volumeNumber = null, string? title = null)
|
[JsonIgnore] public Manga ParentManga { get; init; } = null!;
|
||||||
{
|
|
||||||
ChapterId = TokenGen.CreateToken(typeof(Chapter), parentMangaId, (volumeNumber ?? 0).ToString(), chapterNumber);
|
|
||||||
ParentMangaId = parentMangaId;
|
|
||||||
Url = url;
|
|
||||||
ChapterNumber = chapterNumber;
|
|
||||||
VolumeNumber = volumeNumber;
|
|
||||||
Title = title;
|
|
||||||
}
|
|
||||||
|
|
||||||
[StringLength(64)]
|
|
||||||
[Required]
|
|
||||||
public string ChapterId { get; init; }
|
|
||||||
public int? VolumeNumber { get; private set; }
|
public int? VolumeNumber { get; private set; }
|
||||||
[StringLength(10)]
|
[StringLength(10)] [Required] public string ChapterNumber { get; private set; }
|
||||||
[Required]
|
|
||||||
public string ChapterNumber { get; private set; }
|
|
||||||
|
|
||||||
[StringLength(2048)]
|
[StringLength(2048)] [Required] [Url] public string Url { get; internal set; }
|
||||||
[Required]
|
|
||||||
[Url]
|
[StringLength(256)] public string? Title { get; private set; }
|
||||||
public string Url { get; internal set; }
|
|
||||||
[StringLength(256)]
|
[StringLength(256)] [Required] public string FileName { get; private set; }
|
||||||
public string? Title { get; private set; }
|
|
||||||
[StringLength(256)]
|
[Required] public bool Downloaded { get; internal set; }
|
||||||
[Required]
|
[JsonIgnore] [NotMapped] public string FullArchiveFilePath => Path.Join(ParentManga.FullDirectoryPath, FileName);
|
||||||
public string FileName { get; private set; }
|
|
||||||
[JsonIgnore]
|
public Chapter(Manga parentManga, string url, string chapterNumber, int? volumeNumber = null, string? title = null)
|
||||||
[NotMapped]
|
{
|
||||||
public string? FullArchiveFilePath => ParentManga is { } m ? Path.Join(m.FullDirectoryPath, FileName) : null;
|
this.ChapterId = TokenGen.CreateToken(typeof(Chapter), parentManga.MangaId, chapterNumber);
|
||||||
[Required]
|
this.ParentMangaId = parentManga.MangaId;
|
||||||
public bool Downloaded { get; internal set; } = false;
|
this.ParentManga = parentManga;
|
||||||
[Required]
|
this.VolumeNumber = volumeNumber;
|
||||||
[StringLength(64)]
|
this.ChapterNumber = chapterNumber;
|
||||||
public string ParentMangaId { get; internal set; }
|
this.Url = url;
|
||||||
[JsonIgnore]
|
this.Title = title;
|
||||||
public Manga? ParentManga { get; init; }
|
this.FileName = GetArchiveFilePath();
|
||||||
|
this.Downloaded = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// EF ONLY!!!
|
||||||
|
/// </summary>
|
||||||
|
internal Chapter(string chapterId, string parentMangaId, int? volumeNumber, string chapterNumber, string url, string? title, string fileName, bool downloaded)
|
||||||
|
{
|
||||||
|
this.ChapterId = chapterId;
|
||||||
|
this.ParentMangaId = parentMangaId;
|
||||||
|
this.VolumeNumber = volumeNumber;
|
||||||
|
this.ChapterNumber = chapterNumber;
|
||||||
|
this.Url = url;
|
||||||
|
this.Title = title;
|
||||||
|
this.FileName = fileName;
|
||||||
|
this.Downloaded = downloaded;
|
||||||
|
}
|
||||||
|
|
||||||
public int CompareTo(Chapter? other)
|
public int CompareTo(Chapter? other)
|
||||||
{
|
{
|
||||||
@ -70,43 +68,11 @@ public class Chapter : IComparable<Chapter>
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
public MoveFileOrFolderJob? UpdateChapterNumber(string chapterNumber)
|
|
||||||
{
|
|
||||||
ChapterNumber = chapterNumber;
|
|
||||||
return UpdateArchiveFileName();
|
|
||||||
}
|
|
||||||
|
|
||||||
public MoveFileOrFolderJob? UpdateVolumeNumber(int? volumeNumber)
|
|
||||||
{
|
|
||||||
VolumeNumber = volumeNumber;
|
|
||||||
return UpdateArchiveFileName();
|
|
||||||
}
|
|
||||||
|
|
||||||
public MoveFileOrFolderJob? UpdateTitle(string? title)
|
|
||||||
{
|
|
||||||
Title = title;
|
|
||||||
return UpdateArchiveFileName();
|
|
||||||
}
|
|
||||||
|
|
||||||
internal MoveFileOrFolderJob? UpdateArchiveFileName()
|
|
||||||
{
|
|
||||||
string? oldPath = FullArchiveFilePath;
|
|
||||||
if (oldPath is null)
|
|
||||||
return null;
|
|
||||||
string newPath = GetArchiveFilePath();
|
|
||||||
FileName = newPath;
|
|
||||||
return Downloaded ? new MoveFileOrFolderJob(oldPath, newPath) : null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Checks the filesystem if an archive at the ArchiveFilePath exists
|
/// Checks the filesystem if an archive at the ArchiveFilePath exists
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <returns>True if archive exists on disk</returns>
|
/// <returns>True if archive exists on disk</returns>
|
||||||
public bool IsDownloaded()
|
public bool CheckDownloaded() => File.Exists(FullArchiveFilePath);
|
||||||
{
|
|
||||||
string path = GetArchiveFilePath();
|
|
||||||
return File.Exists(path);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Placeholders:
|
/// Placeholders:
|
||||||
/// %M Manga Name
|
/// %M Manga Name
|
||||||
@ -119,7 +85,7 @@ public class Chapter : IComparable<Chapter>
|
|||||||
/// %Y Year (Manga)
|
/// %Y Year (Manga)
|
||||||
private static readonly Regex NullableRex = new(@"\?([a-zA-Z])\(([^\)]*)\)|(.+?)");
|
private static readonly Regex NullableRex = new(@"\?([a-zA-Z])\(([^\)]*)\)|(.+?)");
|
||||||
private static readonly Regex ReplaceRexx = new(@"%([a-zA-Z])|(.+?)");
|
private static readonly Regex ReplaceRexx = new(@"%([a-zA-Z])|(.+?)");
|
||||||
private string GetArchiveFilePath(string? parentMangaName = null)
|
private string GetArchiveFilePath()
|
||||||
{
|
{
|
||||||
string archiveNamingScheme = TrangaSettings.chapterNamingScheme;
|
string archiveNamingScheme = TrangaSettings.chapterNamingScheme;
|
||||||
StringBuilder stringBuilder = new();
|
StringBuilder stringBuilder = new();
|
||||||
@ -134,13 +100,13 @@ public class Chapter : IComparable<Chapter>
|
|||||||
char placeholder = nullable.Groups[1].Value[0];
|
char placeholder = nullable.Groups[1].Value[0];
|
||||||
bool isNull = placeholder switch
|
bool isNull = placeholder switch
|
||||||
{
|
{
|
||||||
'M' => ParentManga?.Name is null && parentMangaName is null,
|
'M' => ParentManga?.Name is null,
|
||||||
'V' => VolumeNumber is null,
|
'V' => VolumeNumber is null,
|
||||||
'C' => ChapterNumber is null,
|
'C' => ChapterNumber is null,
|
||||||
'T' => Title is null,
|
'T' => Title is null,
|
||||||
'A' => ParentManga?.Authors?.FirstOrDefault()?.AuthorName is null,
|
'A' => ParentManga?.Authors?.FirstOrDefault()?.AuthorName is null,
|
||||||
'I' => ChapterId is null,
|
'I' => ChapterId is null,
|
||||||
'i' => ParentMangaId is null,
|
'i' => ParentManga?.MangaId is null,
|
||||||
'Y' => ParentManga?.Year is null,
|
'Y' => ParentManga?.Year is null,
|
||||||
_ => true
|
_ => true
|
||||||
};
|
};
|
||||||
@ -162,13 +128,13 @@ public class Chapter : IComparable<Chapter>
|
|||||||
char placeholder = replace.Groups[1].Value[0];
|
char placeholder = replace.Groups[1].Value[0];
|
||||||
string? value = placeholder switch
|
string? value = placeholder switch
|
||||||
{
|
{
|
||||||
'M' => ParentManga?.Name ?? parentMangaName,
|
'M' => ParentManga?.Name,
|
||||||
'V' => VolumeNumber?.ToString(),
|
'V' => VolumeNumber?.ToString(),
|
||||||
'C' => ChapterNumber,
|
'C' => ChapterNumber,
|
||||||
'T' => Title,
|
'T' => Title,
|
||||||
'A' => ParentManga?.Authors?.FirstOrDefault()?.AuthorName,
|
'A' => ParentManga?.Authors?.FirstOrDefault()?.AuthorName,
|
||||||
'I' => ChapterId,
|
'I' => ChapterId,
|
||||||
'i' => ParentMangaId,
|
'i' => ParentManga?.MangaId,
|
||||||
'Y' => ParentManga?.Year.ToString(),
|
'Y' => ParentManga?.Year.ToString(),
|
||||||
_ => null
|
_ => null
|
||||||
};
|
};
|
||||||
|
@ -1,17 +1,31 @@
|
|||||||
using System.ComponentModel.DataAnnotations;
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
|
||||||
namespace API.Schema.Jobs;
|
namespace API.Schema.Jobs;
|
||||||
|
|
||||||
public class DownloadAvailableChaptersJob(ulong recurrenceMs, string mangaId, string? parentJobId = null, ICollection<string>? dependsOnJobsIds = null)
|
public class DownloadAvailableChaptersJob : Job
|
||||||
: Job(TokenGen.CreateToken(typeof(DownloadAvailableChaptersJob)), JobType.DownloadAvailableChaptersJob, recurrenceMs, parentJobId, dependsOnJobsIds)
|
|
||||||
{
|
{
|
||||||
[StringLength(64)]
|
[StringLength(64)] [Required] public string MangaId { get; init; }
|
||||||
[Required]
|
[JsonIgnore] public Manga Manga { get; init; } = null!;
|
||||||
public string MangaId { get; init; } = mangaId;
|
|
||||||
|
public DownloadAvailableChaptersJob(Manga manga, ulong recurrenceMs, Job? parentJob = null, ICollection<Job>? dependsOnJobs = null)
|
||||||
|
: base(TokenGen.CreateToken(typeof(DownloadAvailableChaptersJob)), JobType.DownloadAvailableChaptersJob, recurrenceMs, parentJob, dependsOnJobs)
|
||||||
|
{
|
||||||
|
this.MangaId = manga.MangaId;
|
||||||
|
this.Manga = manga;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// EF ONLY!!!
|
||||||
|
/// </summary>
|
||||||
|
public DownloadAvailableChaptersJob(string mangaId, ulong recurrenceMs, string? parentJobId = null)
|
||||||
|
: base(TokenGen.CreateToken(typeof(DownloadAvailableChaptersJob)), JobType.DownloadAvailableChaptersJob, recurrenceMs, parentJobId)
|
||||||
|
{
|
||||||
|
this.MangaId = mangaId;
|
||||||
|
}
|
||||||
|
|
||||||
protected override IEnumerable<Job> RunInternal(PgsqlContext context)
|
protected override IEnumerable<Job> RunInternal(PgsqlContext context)
|
||||||
{
|
{
|
||||||
return context.Chapters.Where(c => c.ParentMangaId == MangaId).AsEnumerable()
|
return Manga.Chapters.Select(chapter => new DownloadSingleChapterJob(chapter, this));
|
||||||
.Select(chapter => new DownloadSingleChapterJob(chapter.ChapterId, this.JobId));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,26 +1,41 @@
|
|||||||
using System.ComponentModel.DataAnnotations;
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
|
||||||
namespace API.Schema.Jobs;
|
namespace API.Schema.Jobs;
|
||||||
|
|
||||||
public class DownloadMangaCoverJob(string mangaId, string? parentJobId = null, ICollection<string>? dependsOnJobsIds = null)
|
public class DownloadMangaCoverJob : Job
|
||||||
: Job(TokenGen.CreateToken(typeof(DownloadMangaCoverJob)), JobType.DownloadMangaCoverJob, 0, parentJobId, dependsOnJobsIds)
|
|
||||||
{
|
{
|
||||||
[StringLength(64)]
|
[StringLength(64)] [Required] public string MangaId { get; init; }
|
||||||
[Required]
|
[JsonIgnore] public Manga Manga { get; init; } = null!;
|
||||||
public string MangaId { get; init; } = mangaId;
|
|
||||||
|
public DownloadMangaCoverJob(Manga manga, Job? parentJob = null, ICollection<Job>? dependsOnJobs = null)
|
||||||
|
: base(TokenGen.CreateToken(typeof(DownloadMangaCoverJob)), JobType.DownloadMangaCoverJob, 0, parentJob, dependsOnJobs)
|
||||||
|
{
|
||||||
|
this.MangaId = manga.MangaId;
|
||||||
|
this.Manga = manga;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// EF ONLY!!!
|
||||||
|
/// </summary>
|
||||||
|
public DownloadMangaCoverJob(string mangaId, string? parentJobId = null)
|
||||||
|
: base(TokenGen.CreateToken(typeof(DownloadMangaCoverJob)), JobType.DownloadMangaCoverJob, 0, parentJobId)
|
||||||
|
{
|
||||||
|
this.MangaId = mangaId;
|
||||||
|
}
|
||||||
|
|
||||||
protected override IEnumerable<Job> RunInternal(PgsqlContext context)
|
protected override IEnumerable<Job> RunInternal(PgsqlContext context)
|
||||||
{
|
{
|
||||||
Manga? manga = context.Mangas.Find(this.MangaId);
|
try
|
||||||
if (manga is null)
|
|
||||||
{
|
{
|
||||||
Log.Error($"Manga {this.MangaId} not found.");
|
Manga.CoverFileNameInCache = Manga.MangaConnector.SaveCoverImageToCache(Manga);
|
||||||
return [];
|
context.SaveChanges();
|
||||||
|
}
|
||||||
|
catch (DbUpdateException e)
|
||||||
|
{
|
||||||
|
Log.Error(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
manga.CoverFileNameInCache = manga.SaveCoverImageToCache();
|
|
||||||
context.SaveChanges();
|
|
||||||
Log.Info($"Saved cover for Manga {this.MangaId} to cache at {manga.CoverFileNameInCache}.");
|
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -2,7 +2,7 @@
|
|||||||
using System.IO.Compression;
|
using System.IO.Compression;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
using API.MangaDownloadClients;
|
using API.MangaDownloadClients;
|
||||||
using API.Schema.MangaConnectors;
|
using Newtonsoft.Json;
|
||||||
using SixLabors.ImageSharp;
|
using SixLabors.ImageSharp;
|
||||||
using SixLabors.ImageSharp.Formats.Jpeg;
|
using SixLabors.ImageSharp.Formats.Jpeg;
|
||||||
using SixLabors.ImageSharp.Processing;
|
using SixLabors.ImageSharp.Processing;
|
||||||
@ -11,45 +11,37 @@ using static System.IO.UnixFileMode;
|
|||||||
|
|
||||||
namespace API.Schema.Jobs;
|
namespace API.Schema.Jobs;
|
||||||
|
|
||||||
public class DownloadSingleChapterJob(string chapterId, string? parentJobId = null, ICollection<string>? dependsOnJobsIds = null)
|
public class DownloadSingleChapterJob : Job
|
||||||
: Job(TokenGen.CreateToken(typeof(DownloadSingleChapterJob)), JobType.DownloadSingleChapterJob, 0, parentJobId, dependsOnJobsIds)
|
|
||||||
{
|
{
|
||||||
[StringLength(64)]
|
[StringLength(64)] [Required] public string ChapterId { get; init; }
|
||||||
[Required]
|
|
||||||
public string ChapterId { get; init; } = chapterId;
|
[JsonIgnore] public Chapter Chapter { get; init; } = null!;
|
||||||
|
|
||||||
|
public DownloadSingleChapterJob(Chapter chapter, Job? parentJob = null, ICollection<Job>? dependsOnJobs = null)
|
||||||
|
: base(TokenGen.CreateToken(typeof(DownloadSingleChapterJob)), JobType.DownloadSingleChapterJob, 0, parentJob, dependsOnJobs)
|
||||||
|
{
|
||||||
|
this.ChapterId = chapter.ChapterId;
|
||||||
|
this.Chapter = chapter;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// EF ONLY!!!
|
||||||
|
/// </summary>
|
||||||
|
public DownloadSingleChapterJob(string chapterId, string? parentJobId = null)
|
||||||
|
: base(TokenGen.CreateToken(typeof(DownloadSingleChapterJob)), JobType.DownloadSingleChapterJob, 0)
|
||||||
|
{
|
||||||
|
this.ChapterId = chapterId;
|
||||||
|
}
|
||||||
|
|
||||||
protected override IEnumerable<Job> RunInternal(PgsqlContext context)
|
protected override IEnumerable<Job> RunInternal(PgsqlContext context)
|
||||||
{
|
{
|
||||||
Chapter? chapter = context.Chapters.Find(ChapterId);
|
string[] imageUrls = Chapter.ParentManga.MangaConnector.GetChapterImageUrls(Chapter);
|
||||||
if (chapter is null)
|
|
||||||
{
|
|
||||||
Log.Error("Chapter is null.");
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
Manga? manga = context.Mangas.Find(chapter.ParentMangaId) ?? chapter.ParentManga;
|
|
||||||
if (manga is null)
|
|
||||||
{
|
|
||||||
Log.Error("Manga is null.");
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
MangaConnector? connector = context.MangaConnectors.Find(manga.MangaConnectorId) ?? manga.MangaConnector;
|
|
||||||
if (connector is null)
|
|
||||||
{
|
|
||||||
Log.Error("Connector is null.");
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
string[] imageUrls = connector.GetChapterImageUrls(chapter);
|
|
||||||
if (imageUrls.Length < 1)
|
if (imageUrls.Length < 1)
|
||||||
{
|
{
|
||||||
Log.Info($"No imageUrls for chapter {ChapterId}");
|
Log.Info($"No imageUrls for chapter {ChapterId}");
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
string? saveArchiveFilePath = chapter.FullArchiveFilePath;
|
string saveArchiveFilePath = Chapter.FullArchiveFilePath;
|
||||||
if (saveArchiveFilePath is null)
|
|
||||||
{
|
|
||||||
Log.Error("saveArchiveFilePath is null.");
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
//Check if Publication Directory already exists
|
//Check if Publication Directory already exists
|
||||||
string directoryPath = Path.GetDirectoryName(saveArchiveFilePath)!;
|
string directoryPath = Path.GetDirectoryName(saveArchiveFilePath)!;
|
||||||
@ -88,10 +80,10 @@ public class DownloadSingleChapterJob(string chapterId, string? parentJobId = nu
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
CopyCoverFromCacheToDownloadLocation(manga);
|
CopyCoverFromCacheToDownloadLocation(Chapter.ParentManga);
|
||||||
|
|
||||||
Log.Debug($"Creating ComicInfo.xml {ChapterId}");
|
Log.Debug($"Creating ComicInfo.xml {ChapterId}");
|
||||||
File.WriteAllText(Path.Join(tempFolder, "ComicInfo.xml"), chapter.GetComicInfoXmlString());
|
File.WriteAllText(Path.Join(tempFolder, "ComicInfo.xml"), Chapter.GetComicInfoXmlString());
|
||||||
|
|
||||||
Log.Debug($"Packaging images to archive {ChapterId}");
|
Log.Debug($"Packaging images to archive {ChapterId}");
|
||||||
//ZIP-it and ship-it
|
//ZIP-it and ship-it
|
||||||
@ -100,10 +92,10 @@ public class DownloadSingleChapterJob(string chapterId, string? parentJobId = nu
|
|||||||
File.SetUnixFileMode(saveArchiveFilePath, UserRead | UserWrite | UserExecute | GroupRead | GroupWrite | GroupExecute | OtherRead | OtherExecute);
|
File.SetUnixFileMode(saveArchiveFilePath, UserRead | UserWrite | UserExecute | GroupRead | GroupWrite | GroupExecute | OtherRead | OtherExecute);
|
||||||
Directory.Delete(tempFolder, true); //Cleanup
|
Directory.Delete(tempFolder, true); //Cleanup
|
||||||
|
|
||||||
chapter.Downloaded = true;
|
Chapter.Downloaded = true;
|
||||||
context.SaveChanges();
|
context.SaveChanges();
|
||||||
|
|
||||||
return [new UpdateFilesDownloadedJob(0, manga.MangaId, this.JobId)];
|
return [new UpdateFilesDownloadedJob(Chapter.ParentManga, 0, this)];
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ProcessImage(string imagePath)
|
private void ProcessImage(string imagePath)
|
||||||
@ -138,7 +130,7 @@ public class DownloadSingleChapterJob(string chapterId, string? parentJobId = nu
|
|||||||
}
|
}
|
||||||
|
|
||||||
Log.Info($"Copying cover to {publicationFolder}");
|
Log.Info($"Copying cover to {publicationFolder}");
|
||||||
string? fileInCache = manga.CoverFileNameInCache ?? manga.SaveCoverImageToCache();
|
string? fileInCache = manga.CoverFileNameInCache ?? manga.MangaConnector.SaveCoverImageToCache(manga);
|
||||||
if (fileInCache is null)
|
if (fileInCache is null)
|
||||||
{
|
{
|
||||||
Log.Error($"File {fileInCache} does not exist");
|
Log.Error($"File {fileInCache} does not exist");
|
||||||
|
@ -12,49 +12,50 @@ public abstract class Job
|
|||||||
[StringLength(64)]
|
[StringLength(64)]
|
||||||
[Required]
|
[Required]
|
||||||
public string JobId { get; init; }
|
public string JobId { get; init; }
|
||||||
[StringLength(64)]
|
|
||||||
public string? ParentJobId { get; init; }
|
|
||||||
[JsonIgnore]
|
|
||||||
public Job? ParentJob { get; init; }
|
|
||||||
[StringLength(64)]
|
|
||||||
public ICollection<string>? DependsOnJobsIds { get; init; }
|
|
||||||
[JsonIgnore]
|
|
||||||
public ICollection<Job>? DependsOnJobs { get; init; }
|
|
||||||
|
|
||||||
[Required]
|
|
||||||
public JobType JobType { get; init; }
|
|
||||||
[Required]
|
|
||||||
public ulong RecurrenceMs { get; set; }
|
|
||||||
[Required]
|
|
||||||
public DateTime LastExecution { get; internal set; } = DateTime.UnixEpoch;
|
|
||||||
|
|
||||||
[NotMapped]
|
|
||||||
[Required]
|
|
||||||
public DateTime NextExecution => LastExecution.AddMilliseconds(RecurrenceMs);
|
|
||||||
[Required]
|
|
||||||
public JobState state { get; internal set; } = JobState.Waiting;
|
|
||||||
[Required]
|
|
||||||
public bool Enabled { get; internal set; } = true;
|
|
||||||
|
|
||||||
[NotMapped]
|
|
||||||
[JsonIgnore]
|
|
||||||
protected ILog Log { get; init; }
|
|
||||||
|
|
||||||
public Job(string jobId, JobType jobType, ulong recurrenceMs, Job? parentJob = null, ICollection<Job>? dependsOnJobs = null)
|
[StringLength(64)] public string? ParentJobId { get; init; }
|
||||||
: this(jobId, jobType, recurrenceMs, parentJob?.JobId, dependsOnJobs?.Select(j => j.JobId).ToList())
|
[JsonIgnore] public Job? ParentJob { get; init; }
|
||||||
|
[JsonIgnore] public ICollection<Job> DependsOnJobs { get; init; }
|
||||||
|
|
||||||
|
[Required] public JobType JobType { get; init; }
|
||||||
|
|
||||||
|
[Required] public ulong RecurrenceMs { get; set; }
|
||||||
|
|
||||||
|
[Required] public DateTime LastExecution { get; internal set; } = DateTime.UnixEpoch;
|
||||||
|
|
||||||
|
[NotMapped] [Required] public DateTime NextExecution => LastExecution.AddMilliseconds(RecurrenceMs);
|
||||||
|
[Required] public JobState state { get; internal set; } = JobState.FirstExecution;
|
||||||
|
[Required] public bool Enabled { get; internal set; } = true;
|
||||||
|
|
||||||
|
[JsonIgnore] [NotMapped] internal bool IsCompleted => state is >= (JobState)128 and < (JobState)192;
|
||||||
|
[JsonIgnore] [NotMapped] internal bool DependenciesFulfilled => DependsOnJobs.All(j => j.IsCompleted);
|
||||||
|
|
||||||
|
[NotMapped] [JsonIgnore] protected ILog Log { get; init; }
|
||||||
|
|
||||||
|
protected Job(string jobId, JobType jobType, ulong recurrenceMs, Job? parentJob = null, ICollection<Job>? dependsOnJobs = null)
|
||||||
{
|
{
|
||||||
|
this.JobId = jobId;
|
||||||
|
this.JobType = jobType;
|
||||||
|
this.RecurrenceMs = recurrenceMs;
|
||||||
|
this.ParentJobId = parentJob?.JobId;
|
||||||
this.ParentJob = parentJob;
|
this.ParentJob = parentJob;
|
||||||
this.DependsOnJobs = dependsOnJobs;
|
this.DependsOnJobs = dependsOnJobs ?? [];
|
||||||
|
|
||||||
|
this.Log = LogManager.GetLogger(this.GetType());
|
||||||
}
|
}
|
||||||
|
|
||||||
public Job(string jobId, JobType jobType, ulong recurrenceMs, string? parentJobId = null, ICollection<string>? dependsOnJobsIds = null)
|
/// <summary>
|
||||||
|
/// EF ONLY!!!
|
||||||
|
/// </summary>
|
||||||
|
protected Job(string jobId, JobType jobType, ulong recurrenceMs, string? parentJobId)
|
||||||
{
|
{
|
||||||
Log = LogManager.GetLogger(GetType());
|
this.JobId = jobId;
|
||||||
JobId = jobId;
|
this.JobType = jobType;
|
||||||
ParentJobId = parentJobId;
|
this.RecurrenceMs = recurrenceMs;
|
||||||
DependsOnJobsIds = dependsOnJobsIds;
|
this.ParentJobId = parentJobId;
|
||||||
JobType = jobType;
|
this.DependsOnJobs = [];
|
||||||
RecurrenceMs = recurrenceMs;
|
|
||||||
|
this.Log = LogManager.GetLogger(this.GetType());
|
||||||
}
|
}
|
||||||
|
|
||||||
public IEnumerable<Job> Run(IServiceProvider serviceProvider)
|
public IEnumerable<Job> Run(IServiceProvider serviceProvider)
|
||||||
|
@ -3,11 +3,12 @@
|
|||||||
public enum JobState : byte
|
public enum JobState : byte
|
||||||
{
|
{
|
||||||
//Values 0-63 Preparation Stages
|
//Values 0-63 Preparation Stages
|
||||||
Waiting = 0,
|
FirstExecution = 0,
|
||||||
//64-127 Running Stages
|
//64-127 Running Stages
|
||||||
Running = 64,
|
Running = 64,
|
||||||
//128-191 Completion Stages
|
//128-191 Completion Stages
|
||||||
Completed = 128,
|
Completed = 128,
|
||||||
|
CompletedWaiting = 159,
|
||||||
//192-255 Error stages
|
//192-255 Error stages
|
||||||
Failed = 192
|
Failed = 192
|
||||||
}
|
}
|
@ -2,15 +2,31 @@
|
|||||||
|
|
||||||
namespace API.Schema.Jobs;
|
namespace API.Schema.Jobs;
|
||||||
|
|
||||||
public class MoveFileOrFolderJob(string fromLocation, string toLocation, string? parentJobId = null, ICollection<string>? dependsOnJobsIds = null)
|
public class MoveFileOrFolderJob : Job
|
||||||
: Job(TokenGen.CreateToken(typeof(MoveFileOrFolderJob)), JobType.MoveFileOrFolderJob, 0, parentJobId, dependsOnJobsIds)
|
|
||||||
{
|
{
|
||||||
[StringLength(256)]
|
[StringLength(256)]
|
||||||
[Required]
|
[Required]
|
||||||
public string FromLocation { get; init; } = fromLocation;
|
public string FromLocation { get; init; }
|
||||||
[StringLength(256)]
|
[StringLength(256)]
|
||||||
[Required]
|
[Required]
|
||||||
public string ToLocation { get; init; } = toLocation;
|
public string ToLocation { get; init; }
|
||||||
|
|
||||||
|
public MoveFileOrFolderJob(string fromLocation, string toLocation, Job? parentJob = null, ICollection<Job>? dependsOnJobs = null)
|
||||||
|
: base(TokenGen.CreateToken(typeof(MoveFileOrFolderJob)), JobType.MoveFileOrFolderJob, 0, parentJob, dependsOnJobs)
|
||||||
|
{
|
||||||
|
this.FromLocation = fromLocation;
|
||||||
|
this.ToLocation = toLocation;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// EF ONLY!!!
|
||||||
|
/// </summary>
|
||||||
|
public MoveFileOrFolderJob(string jobId, string fromLocation, string toLocation, string? parentJobId = null)
|
||||||
|
: base(jobId, JobType.MoveFileOrFolderJob, 0, parentJobId)
|
||||||
|
{
|
||||||
|
this.FromLocation = fromLocation;
|
||||||
|
this.ToLocation = toLocation;
|
||||||
|
}
|
||||||
|
|
||||||
protected override IEnumerable<Job> RunInternal(PgsqlContext context)
|
protected override IEnumerable<Job> RunInternal(PgsqlContext context)
|
||||||
{
|
{
|
||||||
|
@ -1,35 +1,39 @@
|
|||||||
using System.ComponentModel.DataAnnotations;
|
using System.ComponentModel.DataAnnotations;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
|
||||||
namespace API.Schema.Jobs;
|
namespace API.Schema.Jobs;
|
||||||
|
|
||||||
public class MoveMangaLibraryJob(string mangaId, string toLibraryId, string? parentJobId = null, ICollection<string>? dependsOnJobsIds = null)
|
public class MoveMangaLibraryJob : Job
|
||||||
: Job(TokenGen.CreateToken(typeof(MoveMangaLibraryJob)), JobType.MoveMangaLibraryJob, 0, parentJobId, dependsOnJobsIds)
|
|
||||||
{
|
{
|
||||||
[StringLength(64)]
|
[StringLength(64)] [Required] public string MangaId { get; init; }
|
||||||
[Required]
|
[JsonIgnore] public Manga Manga { get; init; } = null!;
|
||||||
public string MangaId { get; init; } = mangaId;
|
[StringLength(64)] [Required] public string ToLibraryId { get; init; }
|
||||||
[StringLength(64)]
|
public LocalLibrary ToLibrary { get; init; } = null!;
|
||||||
[Required]
|
|
||||||
public string ToLibraryId { get; init; } = toLibraryId;
|
public MoveMangaLibraryJob(Manga manga, LocalLibrary toLibrary, Job? parentJob = null, ICollection<Job>? dependsOnJobs = null)
|
||||||
|
: base(TokenGen.CreateToken(typeof(MoveMangaLibraryJob)), JobType.MoveMangaLibraryJob, 0, parentJob, dependsOnJobs)
|
||||||
|
{
|
||||||
|
this.MangaId = manga.MangaId;
|
||||||
|
this.Manga = manga;
|
||||||
|
this.ToLibraryId = toLibrary.LocalLibraryId;
|
||||||
|
this.ToLibrary = toLibrary;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// EF ONLY!!!
|
||||||
|
/// </summary>
|
||||||
|
public MoveMangaLibraryJob(string mangaId, string toLibraryId, string? parentJobId = null)
|
||||||
|
: base(TokenGen.CreateToken(typeof(MoveMangaLibraryJob)), JobType.MoveMangaLibraryJob, 0, parentJobId)
|
||||||
|
{
|
||||||
|
this.MangaId = mangaId;
|
||||||
|
this.ToLibraryId = toLibraryId;
|
||||||
|
}
|
||||||
|
|
||||||
protected override IEnumerable<Job> RunInternal(PgsqlContext context)
|
protected override IEnumerable<Job> RunInternal(PgsqlContext context)
|
||||||
{
|
{
|
||||||
Manga? manga = context.Mangas.Find(MangaId);
|
Dictionary<Chapter, string> oldPath = Manga.Chapters.ToDictionary(c => c, c => c.FullArchiveFilePath);
|
||||||
if (manga is null)
|
Manga.Library = ToLibrary;
|
||||||
{
|
|
||||||
Log.Error("Manga not found");
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
LocalLibrary? library = context.LocalLibraries.Find(ToLibraryId);
|
|
||||||
if (library is null)
|
|
||||||
{
|
|
||||||
Log.Error("LocalLibrary not found");
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
Chapter[] chapters = context.Chapters.Where(c => c.ParentMangaId == MangaId).ToArray();
|
|
||||||
Dictionary<Chapter, string> oldPath = chapters.ToDictionary(c => c, c => c.FullArchiveFilePath!);
|
|
||||||
manga.Library = library;
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
context.SaveChanges();
|
context.SaveChanges();
|
||||||
@ -40,6 +44,6 @@ public class MoveMangaLibraryJob(string mangaId, string toLibraryId, string? par
|
|||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
return chapters.Select(c => new MoveFileOrFolderJob(oldPath[c], c.FullArchiveFilePath!));
|
return Manga.Chapters.Select(c => new MoveFileOrFolderJob(oldPath[c], c.FullArchiveFilePath));
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,40 +1,43 @@
|
|||||||
using System.ComponentModel.DataAnnotations;
|
using System.ComponentModel.DataAnnotations;
|
||||||
using API.Schema.MangaConnectors;
|
using API.Schema.MangaConnectors;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
|
||||||
namespace API.Schema.Jobs;
|
namespace API.Schema.Jobs;
|
||||||
|
|
||||||
public class RetrieveChaptersJob(ulong recurrenceMs, string mangaId, string? parentJobId = null, ICollection<string>? dependsOnJobsIds = null)
|
public class RetrieveChaptersJob : Job
|
||||||
: Job(TokenGen.CreateToken(typeof(RetrieveChaptersJob)), JobType.RetrieveChaptersJob, recurrenceMs, parentJobId, dependsOnJobsIds)
|
|
||||||
{
|
{
|
||||||
[StringLength(64)]
|
[StringLength(64)] [Required] public string MangaId { get; init; }
|
||||||
[Required]
|
[JsonIgnore] public Manga Manga { get; init; } = null!;
|
||||||
public string MangaId { get; init; } = mangaId;
|
[StringLength(8)] [Required] public string Language { get; private set; }
|
||||||
|
|
||||||
|
public RetrieveChaptersJob(Manga manga, string language, ulong recurrenceMs, Job? parentJob = null, ICollection<Job>? dependsOnJobs = null)
|
||||||
|
: base(TokenGen.CreateToken(typeof(RetrieveChaptersJob)), JobType.RetrieveChaptersJob, recurrenceMs, parentJob, dependsOnJobs)
|
||||||
|
{
|
||||||
|
this.MangaId = manga.MangaId;
|
||||||
|
this.Manga = manga;
|
||||||
|
this.Language = language;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// EF ONLY!!!
|
||||||
|
/// </summary>
|
||||||
|
public RetrieveChaptersJob(string mangaId, string language, ulong recurrenceMs, string? parentJobId = null)
|
||||||
|
: base(TokenGen.CreateToken(typeof(RetrieveChaptersJob)), JobType.RetrieveChaptersJob, recurrenceMs, parentJobId)
|
||||||
|
{
|
||||||
|
this.MangaId = mangaId;
|
||||||
|
this.Language = language;
|
||||||
|
}
|
||||||
|
|
||||||
protected override IEnumerable<Job> RunInternal(PgsqlContext context)
|
protected override IEnumerable<Job> RunInternal(PgsqlContext context)
|
||||||
{
|
{
|
||||||
Manga? manga = context.Mangas.Find(MangaId);
|
|
||||||
if (manga is null)
|
|
||||||
{
|
|
||||||
Log.Error("Manga is null.");
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
MangaConnector? connector = manga.MangaConnector ?? context.MangaConnectors.Find(manga.MangaConnectorId);
|
|
||||||
if (connector is null)
|
|
||||||
{
|
|
||||||
Log.Error("Connector is null.");
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
// This gets all chapters that are not downloaded
|
// This gets all chapters that are not downloaded
|
||||||
Chapter[] allNewChapters = connector.GetNewChapters(manga).DistinctBy(c => c.ChapterId).ToArray();
|
Chapter[] allChapters = Manga.MangaConnector.GetChapters(Manga, Language);
|
||||||
Log.Info($"{allNewChapters.Length} new chapters.");
|
Chapter[] newChapters = allChapters.Where(chapter => context.Chapters.Contains(chapter) == false).ToArray();
|
||||||
|
Log.Info($"{newChapters.Length} new chapters.");
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
// This filters out chapters that are not downloaded but already exist in the DB
|
|
||||||
string[] chapterIds = context.Chapters.Where(chapter => chapter.ParentMangaId == manga.MangaId)
|
|
||||||
.Select(chapter => chapter.ChapterId).ToArray();
|
|
||||||
Chapter[] newChapters = allNewChapters.Where(chapter => !chapterIds.Contains(chapter.ChapterId)).ToArray();
|
|
||||||
context.Chapters.AddRange(newChapters);
|
context.Chapters.AddRange(newChapters);
|
||||||
context.SaveChanges();
|
context.SaveChanges();
|
||||||
}
|
}
|
||||||
|
@ -1,22 +1,43 @@
|
|||||||
using System.ComponentModel.DataAnnotations;
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
|
|
||||||
namespace API.Schema.Jobs;
|
namespace API.Schema.Jobs;
|
||||||
|
|
||||||
public class UpdateFilesDownloadedJob(ulong recurrenceMs, string mangaId, string? parentJobId = null, ICollection<string>? dependsOnJobsIds = null)
|
public class UpdateFilesDownloadedJob : Job
|
||||||
: Job(TokenGen.CreateToken(typeof(UpdateFilesDownloadedJob)), JobType.UpdateFilesDownloadedJob, recurrenceMs, parentJobId, dependsOnJobsIds)
|
|
||||||
{
|
{
|
||||||
[StringLength(64)]
|
[StringLength(64)] [Required] public string MangaId { get; init; }
|
||||||
[Required]
|
[JsonIgnore] public Manga Manga { get; init; } = null!;
|
||||||
public string MangaId { get; init; } = mangaId;
|
|
||||||
|
public UpdateFilesDownloadedJob(Manga manga, ulong recurrenceMs, Job? parentJob = null, ICollection<Job>? dependsOnJobs = null)
|
||||||
|
: base(TokenGen.CreateToken(typeof(UpdateFilesDownloadedJob)), JobType.UpdateFilesDownloadedJob, recurrenceMs, parentJob, dependsOnJobs)
|
||||||
|
{
|
||||||
|
this.MangaId = manga.MangaId;
|
||||||
|
this.Manga = manga;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// EF ONLY!!!
|
||||||
|
/// </summary>
|
||||||
|
public UpdateFilesDownloadedJob(string mangaId, ulong recurrenceMs, string? parentJobId = null)
|
||||||
|
: base(TokenGen.CreateToken(typeof(UpdateFilesDownloadedJob)), JobType.UpdateFilesDownloadedJob, recurrenceMs, parentJobId)
|
||||||
|
{
|
||||||
|
this.MangaId = mangaId;
|
||||||
|
}
|
||||||
|
|
||||||
protected override IEnumerable<Job> RunInternal(PgsqlContext context)
|
protected override IEnumerable<Job> RunInternal(PgsqlContext context)
|
||||||
{
|
{
|
||||||
IQueryable<Chapter> chapters = context.Chapters.Where(c => c.ParentMangaId == MangaId);
|
foreach (Chapter chapter in Manga.Chapters)
|
||||||
foreach (Chapter chapter in chapters)
|
chapter.Downloaded = chapter.CheckDownloaded();
|
||||||
chapter.Downloaded = chapter.IsDownloaded();
|
|
||||||
|
|
||||||
context.SaveChanges();
|
try
|
||||||
|
{
|
||||||
|
context.SaveChanges();
|
||||||
|
}
|
||||||
|
catch (DbUpdateException e)
|
||||||
|
{
|
||||||
|
Log.Error(e);
|
||||||
|
}
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,27 +0,0 @@
|
|||||||
using System.ComponentModel.DataAnnotations;
|
|
||||||
using Newtonsoft.Json;
|
|
||||||
|
|
||||||
namespace API.Schema.Jobs;
|
|
||||||
|
|
||||||
public class UpdateMetadataJob(ulong recurrenceMs, string mangaId, string? parentJobId = null, ICollection<string>? dependsOnJobsIds = null)
|
|
||||||
: Job(TokenGen.CreateToken(typeof(UpdateMetadataJob)), JobType.UpdateMetaDataJob, recurrenceMs, parentJobId, dependsOnJobsIds)
|
|
||||||
{
|
|
||||||
[StringLength(64)]
|
|
||||||
[Required]
|
|
||||||
public string MangaId { get; init; } = mangaId;
|
|
||||||
|
|
||||||
[JsonIgnore]
|
|
||||||
public virtual Manga? Manga { get; init; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Updates all data related to Manga.
|
|
||||||
/// Retrieves data from Mangaconnector
|
|
||||||
/// Updates Chapter-info
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="context"></param>
|
|
||||||
protected override IEnumerable<Job> RunInternal(PgsqlContext context)
|
|
||||||
{
|
|
||||||
Log.Warn("NOT IMPLEMENTED.");
|
|
||||||
return [];//TODO
|
|
||||||
}
|
|
||||||
}
|
|
@ -53,13 +53,13 @@ public class Kavita : LibraryConnector
|
|||||||
protected override void UpdateLibraryInternal()
|
protected override void UpdateLibraryInternal()
|
||||||
{
|
{
|
||||||
foreach (KavitaLibrary lib in GetLibraries())
|
foreach (KavitaLibrary lib in GetLibraries())
|
||||||
NetClient.MakePost($"{BaseUrl}/api/Library/scan?libraryId={lib.id}", "Bearer", Auth);
|
NetClient.MakePost($"{BaseUrl}/api/ToLibrary/scan?libraryId={lib.id}", "Bearer", Auth);
|
||||||
}
|
}
|
||||||
|
|
||||||
internal override bool Test()
|
internal override bool Test()
|
||||||
{
|
{
|
||||||
foreach (KavitaLibrary lib in GetLibraries())
|
foreach (KavitaLibrary lib in GetLibraries())
|
||||||
if (NetClient.MakePost($"{BaseUrl}/api/Library/scan?libraryId={lib.id}", "Bearer", Auth))
|
if (NetClient.MakePost($"{BaseUrl}/api/ToLibrary/scan?libraryId={lib.id}", "Bearer", Auth))
|
||||||
return true;
|
return true;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -70,7 +70,7 @@ public class Kavita : LibraryConnector
|
|||||||
/// <returns>Array of KavitaLibrary</returns>
|
/// <returns>Array of KavitaLibrary</returns>
|
||||||
private IEnumerable<KavitaLibrary> GetLibraries()
|
private IEnumerable<KavitaLibrary> GetLibraries()
|
||||||
{
|
{
|
||||||
Stream data = NetClient.MakeRequest($"{BaseUrl}/api/Library/libraries", "Bearer", Auth);
|
Stream data = NetClient.MakeRequest($"{BaseUrl}/api/ToLibrary/libraries", "Bearer", Auth);
|
||||||
if (data == Stream.Null)
|
if (data == Stream.Null)
|
||||||
{
|
{
|
||||||
Log.Info("No libraries found");
|
Log.Info("No libraries found");
|
||||||
|
@ -1,12 +1,7 @@
|
|||||||
using System.ComponentModel.DataAnnotations;
|
using System.ComponentModel.DataAnnotations;
|
||||||
using System.ComponentModel.DataAnnotations.Schema;
|
using System.ComponentModel.DataAnnotations.Schema;
|
||||||
using System.Diagnostics.CodeAnalysis;
|
|
||||||
using System.Net;
|
|
||||||
using System.Runtime.CompilerServices;
|
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
using System.Text.RegularExpressions;
|
using System.Text;
|
||||||
using API.MangaDownloadClients;
|
|
||||||
using API.Schema.Jobs;
|
|
||||||
using API.Schema.MangaConnectors;
|
using API.Schema.MangaConnectors;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
@ -20,166 +15,129 @@ public class Manga
|
|||||||
[StringLength(64)]
|
[StringLength(64)]
|
||||||
[Required]
|
[Required]
|
||||||
public string MangaId { get; init; }
|
public string MangaId { get; init; }
|
||||||
[StringLength(256)]
|
[StringLength(256)] [Required] public string IdOnConnectorSite { get; init; }
|
||||||
[Required]
|
[StringLength(512)] [Required] public string Name { get; internal set; }
|
||||||
public string IdOnConnectorSite { get; init; }
|
[Required] public string Description { get; internal set; }
|
||||||
[StringLength(512)]
|
[Url] [StringLength(512)] [Required] public string WebsiteUrl { get; internal init; }
|
||||||
[Required]
|
[JsonIgnore] [Url] [StringLength(512)] public string CoverUrl { get; internal set; }
|
||||||
public string Name { get; internal set; }
|
[Required] public MangaReleaseStatus ReleaseStatus { get; internal set; }
|
||||||
[Required]
|
|
||||||
public string Description { get; internal set; }
|
|
||||||
[Url]
|
|
||||||
[StringLength(512)]
|
|
||||||
[Required]
|
|
||||||
public string WebsiteUrl { get; internal set; }
|
|
||||||
[JsonIgnore]
|
|
||||||
[Url]
|
|
||||||
public string CoverUrl { get; internal set; }
|
|
||||||
[JsonIgnore]
|
|
||||||
public string? CoverFileNameInCache { get; internal set; }
|
|
||||||
[Required]
|
|
||||||
public uint Year { get; internal set; }
|
|
||||||
[StringLength(8)]
|
|
||||||
public string? OriginalLanguage { get; internal set; }
|
|
||||||
[Required]
|
|
||||||
public MangaReleaseStatus ReleaseStatus { get; internal set; }
|
|
||||||
[StringLength(1024)]
|
|
||||||
[Required]
|
|
||||||
public string DirectoryName { get; private set; }
|
|
||||||
public LocalLibrary? Library { get; internal set; }
|
|
||||||
[JsonIgnore]
|
|
||||||
[NotMapped]
|
|
||||||
public string LibraryPath => Library is null ? TrangaSettings.downloadLocation : Library.BasePath;
|
|
||||||
[JsonIgnore]
|
|
||||||
[NotMapped]
|
|
||||||
public string FullDirectoryPath => Path.Join(LibraryPath, DirectoryName);
|
|
||||||
[Required]
|
|
||||||
public float IgnoreChapterBefore { get; internal set; }
|
|
||||||
[StringLength(64)]
|
|
||||||
[Required]
|
|
||||||
public string MangaConnectorId { get; private set; }
|
|
||||||
[JsonIgnore] public MangaConnector? MangaConnector { get; private set; }
|
|
||||||
|
|
||||||
[JsonIgnore] public ICollection<Author>? Authors { get; internal set; }
|
[StringLength(64)]
|
||||||
[NotMapped]
|
public string? LibraryId { get; init; }
|
||||||
[StringLength(64)]
|
[JsonIgnore] public LocalLibrary? Library { get; internal set; }
|
||||||
[Required]
|
|
||||||
public IEnumerable<string> AuthorIds => Authors?.Select(a => a.AuthorId) ?? [];
|
|
||||||
|
|
||||||
[JsonIgnore] public ICollection<MangaTag>? MangaTags { get; internal set; }
|
[StringLength(32)]
|
||||||
[NotMapped]
|
|
||||||
[StringLength(64)]
|
|
||||||
[Required]
|
[Required]
|
||||||
public IEnumerable<string> Tags => MangaTags?.Select(t => t.Tag) ?? [];
|
public string MangaConnectorName { get; init; }
|
||||||
|
[JsonIgnore] public MangaConnector MangaConnector { get; init; } = null!;
|
||||||
|
|
||||||
[JsonIgnore] public ICollection<Link>? Links { get; internal set; }
|
|
||||||
[NotMapped]
|
|
||||||
[StringLength(64)]
|
|
||||||
[Required]
|
|
||||||
public IEnumerable<string> LinkIds => Links?.Select(l => l.LinkId) ?? [];
|
|
||||||
|
|
||||||
[JsonIgnore] public ICollection<MangaAltTitle>? AltTitles { get; internal set; }
|
|
||||||
[NotMapped]
|
|
||||||
[StringLength(64)]
|
|
||||||
[Required]
|
|
||||||
public IEnumerable<string> AltTitleIds => AltTitles?.Select(a => a.AltTitleId) ?? [];
|
|
||||||
|
|
||||||
public Manga(string idOnConnectorSite, string name, string description, string websiteUrl, string coverUrl,
|
public ICollection<Author> Authors { get; internal set; }= null!;
|
||||||
string? coverFileNameInCache, uint year, string? originalLanguage, MangaReleaseStatus releaseStatus,
|
public ICollection<MangaTag> MangaTags { get; internal set; }= null!;
|
||||||
float ignoreChapterBefore, MangaConnector mangaConnector, ICollection<Author> authors,
|
public ICollection<Link> Links { get; internal set; }= null!;
|
||||||
ICollection<MangaTag> mangaTags, ICollection<Link> links, ICollection<MangaAltTitle> altTitles,
|
public ICollection<MangaAltTitle> AltTitles { get; internal set; } = null!;
|
||||||
LocalLibrary? library = null)
|
[Required] public float IgnoreChaptersBefore { get; internal set; }
|
||||||
: this(idOnConnectorSite, name, description, websiteUrl, coverUrl, coverFileNameInCache, year, originalLanguage,
|
[StringLength(1024)] [Required] public string DirectoryName { get; private set; }
|
||||||
releaseStatus, ignoreChapterBefore, mangaConnector.Name)
|
|
||||||
|
[JsonIgnore] [StringLength(512)] public string? CoverFileNameInCache { get; internal set; } = null;
|
||||||
|
[Required] public uint? Year { get; internal init; }
|
||||||
|
[StringLength(8)] public string? OriginalLanguage { get; internal init; }
|
||||||
|
|
||||||
|
[JsonIgnore]
|
||||||
|
[NotMapped]
|
||||||
|
public string? FullDirectoryPath => Library is not null ? Path.Join(Library.BasePath, DirectoryName) : null;
|
||||||
|
|
||||||
|
[JsonIgnore] public ICollection<Chapter> Chapters { get; internal set; } = [];
|
||||||
|
|
||||||
|
public Manga(string idOnConnector, string name, string description, string websiteUrl, string coverUrl, MangaReleaseStatus releaseStatus,
|
||||||
|
MangaConnector mangaConnector, ICollection<Author> authors, ICollection<MangaTag> mangaTags, ICollection<Link> links, ICollection<MangaAltTitle> altTitles,
|
||||||
|
LocalLibrary? library = null, float ignoreChaptersBefore = 0f, uint? year = null, string? originalLanguage = null)
|
||||||
{
|
{
|
||||||
|
this.MangaId = TokenGen.CreateToken(typeof(Manga), mangaConnector.Name, idOnConnector);
|
||||||
|
this.IdOnConnectorSite = idOnConnector;
|
||||||
|
this.Name = name;
|
||||||
|
this.Description = description;
|
||||||
|
this.WebsiteUrl = websiteUrl;
|
||||||
|
this.CoverUrl = coverUrl;
|
||||||
|
this.ReleaseStatus = releaseStatus;
|
||||||
|
this.LibraryId = library?.LocalLibraryId;
|
||||||
|
this.Library = library;
|
||||||
|
this.MangaConnectorName = mangaConnector.Name;
|
||||||
|
this.MangaConnector = mangaConnector;
|
||||||
this.Authors = authors;
|
this.Authors = authors;
|
||||||
this.MangaTags = mangaTags;
|
this.MangaTags = mangaTags;
|
||||||
this.Links = links;
|
this.Links = links;
|
||||||
this.AltTitles = altTitles;
|
this.AltTitles = altTitles;
|
||||||
this.Library = library;
|
this.IgnoreChaptersBefore = ignoreChaptersBefore;
|
||||||
|
this.DirectoryName = CleanDirectoryName(name);
|
||||||
|
this.Year = year;
|
||||||
|
this.OriginalLanguage = originalLanguage;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// EF ONLY!!!
|
||||||
|
/// </summary>
|
||||||
|
public Manga(string mangaId, string idOnConnectorSite, string name, string description, string websiteUrl, string coverUrl, MangaReleaseStatus releaseStatus,
|
||||||
|
string mangaConnectorName, string directoryName, float ignoreChaptersBefore, string? libraryId, uint? year, string? originalLanguage)
|
||||||
|
{
|
||||||
|
this.MangaId = mangaId;
|
||||||
|
this.IdOnConnectorSite = idOnConnectorSite;
|
||||||
|
this.Name = name;
|
||||||
|
this.Description = description;
|
||||||
|
this.WebsiteUrl = websiteUrl;
|
||||||
|
this.CoverUrl = coverUrl;
|
||||||
|
this.ReleaseStatus = releaseStatus;
|
||||||
|
this.MangaConnectorName = mangaConnectorName;
|
||||||
|
this.DirectoryName = directoryName;
|
||||||
|
this.LibraryId = libraryId;
|
||||||
|
this.IgnoreChaptersBefore = ignoreChaptersBefore;
|
||||||
|
this.Year = year;
|
||||||
|
this.OriginalLanguage = originalLanguage;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Manga(string idOnConnectorSite, string name, string description, string websiteUrl, string coverUrl,
|
|
||||||
string? coverFileNameInCache, uint year, string? originalLanguage, MangaReleaseStatus releaseStatus,
|
|
||||||
float ignoreChapterBefore, string mangaConnectorId)
|
|
||||||
{
|
|
||||||
MangaId = TokenGen.CreateToken(typeof(Manga), mangaConnectorId, idOnConnectorSite);
|
|
||||||
IdOnConnectorSite = idOnConnectorSite;
|
|
||||||
Name = name;
|
|
||||||
Description = description;
|
|
||||||
WebsiteUrl = websiteUrl;
|
|
||||||
CoverUrl = coverUrl;
|
|
||||||
CoverFileNameInCache = coverFileNameInCache;
|
|
||||||
Year = year;
|
|
||||||
OriginalLanguage = originalLanguage;
|
|
||||||
ReleaseStatus = releaseStatus;
|
|
||||||
IgnoreChapterBefore = ignoreChapterBefore;
|
|
||||||
MangaConnectorId = mangaConnectorId;
|
|
||||||
DirectoryName = BuildFolderName(name);
|
|
||||||
}
|
|
||||||
|
|
||||||
public MoveFileOrFolderJob UpdateFolderName(string downloadLocation, string newName)
|
|
||||||
{
|
|
||||||
string oldName = this.DirectoryName;
|
|
||||||
this.DirectoryName = newName;
|
|
||||||
return new MoveFileOrFolderJob(Path.Join(downloadLocation, oldName), Path.Join(downloadLocation, this.DirectoryName));
|
|
||||||
}
|
|
||||||
|
|
||||||
internal void UpdateWithInfo(Manga other)
|
|
||||||
{
|
|
||||||
this.Name = other.Name;
|
|
||||||
this.Year = other.Year;
|
|
||||||
this.Description = other.Description;
|
|
||||||
this.CoverUrl = other.CoverUrl;
|
|
||||||
this.OriginalLanguage = other.OriginalLanguage;
|
|
||||||
this.Authors = other.Authors;
|
|
||||||
this.Links = other.Links;
|
|
||||||
this.MangaTags = other.MangaTags;
|
|
||||||
this.AltTitles = other.AltTitles;
|
|
||||||
this.ReleaseStatus = other.ReleaseStatus;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static string BuildFolderName(string mangaName)
|
|
||||||
{
|
|
||||||
return mangaName;
|
|
||||||
}
|
|
||||||
|
|
||||||
internal string? SaveCoverImageToCache(int retries = 3)
|
|
||||||
{
|
|
||||||
if(retries < 0)
|
|
||||||
return null;
|
|
||||||
|
|
||||||
Regex urlRex = new (@"https?:\/\/((?:[a-zA-Z0-9-]+\.)+[a-zA-Z0-9]+)\/(?:.+\/)*(.+\.([a-zA-Z]+))");
|
|
||||||
//https?:\/\/[a-zA-Z0-9-]+\.([a-zA-Z0-9-]+\.[a-zA-Z0-9]+)\/(?:.+\/)*(.+\.([a-zA-Z]+)) for only second level domains
|
|
||||||
Match match = urlRex.Match(CoverUrl);
|
|
||||||
string filename = $"{match.Groups[1].Value}-{MangaId}.{match.Groups[3].Value}";
|
|
||||||
string saveImagePath = Path.Join(TrangaSettings.coverImageCache, filename);
|
|
||||||
|
|
||||||
if (File.Exists(saveImagePath))
|
|
||||||
return saveImagePath;
|
|
||||||
|
|
||||||
RequestResult coverResult = new HttpDownloadClient().MakeRequest(CoverUrl, RequestType.MangaCover, $"https://{match.Groups[1].Value}");
|
|
||||||
if (coverResult.statusCode is < HttpStatusCode.OK or >= HttpStatusCode.Ambiguous)
|
|
||||||
return SaveCoverImageToCache(--retries);
|
|
||||||
|
|
||||||
using MemoryStream ms = new();
|
|
||||||
coverResult.result.CopyTo(ms);
|
|
||||||
Directory.CreateDirectory(TrangaSettings.coverImageCache);
|
|
||||||
File.WriteAllBytes(saveImagePath, ms.ToArray());
|
|
||||||
|
|
||||||
return saveImagePath;
|
|
||||||
}
|
|
||||||
|
|
||||||
public string CreatePublicationFolder()
|
public string CreatePublicationFolder()
|
||||||
{
|
{
|
||||||
string publicationFolder = Path.Join(LibraryPath, this.DirectoryName);
|
string publicationFolder = FullDirectoryPath;
|
||||||
if(!Directory.Exists(publicationFolder))
|
if(!Directory.Exists(publicationFolder))
|
||||||
Directory.CreateDirectory(publicationFolder);
|
Directory.CreateDirectory(publicationFolder);
|
||||||
if(RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
|
if(RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
|
||||||
File.SetUnixFileMode(publicationFolder, GroupRead | GroupWrite | GroupExecute | OtherRead | OtherWrite | OtherExecute | UserRead | UserWrite | UserExecute);
|
File.SetUnixFileMode(publicationFolder, GroupRead | GroupWrite | GroupExecute | OtherRead | OtherWrite | OtherExecute | UserRead | UserWrite | UserExecute);
|
||||||
return publicationFolder;
|
return publicationFolder;
|
||||||
}
|
}
|
||||||
|
|
||||||
//TODO onchanges create job to update metadata files in archives, etc.
|
//https://learn.microsoft.com/en-us/windows/win32/fileio/naming-a-file
|
||||||
|
//less than 32 is control *forbidden*
|
||||||
|
//34 is " *forbidden*
|
||||||
|
//42 is * *forbidden*
|
||||||
|
//47 is / *forbidden*
|
||||||
|
//58 is : *forbidden*
|
||||||
|
//60 is < *forbidden*
|
||||||
|
//62 is > *forbidden*
|
||||||
|
//63 is ? *forbidden*
|
||||||
|
//92 is \ *forbidden*
|
||||||
|
//124 is | *forbidden*
|
||||||
|
//127 is delete *forbidden*
|
||||||
|
//Below 127 all except *******
|
||||||
|
private static readonly int[] ForbiddenCharsBelow127 = [34, 42, 47, 58, 60, 62, 63, 92, 124, 127];
|
||||||
|
//Above 127 none except *******
|
||||||
|
private static readonly int[] IncludeCharsAbove127 = [128, 138, 142];
|
||||||
|
//128 is € include
|
||||||
|
//138 is Š include
|
||||||
|
//142 is Ž include
|
||||||
|
//152 through 255 looks fine except 157, 172, 173, 175 *******
|
||||||
|
private static readonly int[] ForbiddenCharsAbove152 = [157, 172, 173, 175];
|
||||||
|
private static string CleanDirectoryName(string name)
|
||||||
|
{
|
||||||
|
StringBuilder sb = new ();
|
||||||
|
foreach (char c in name)
|
||||||
|
{
|
||||||
|
if (c > 32 && c < 127 && ForbiddenCharsBelow127.Contains(c) == false)
|
||||||
|
sb.Append(c);
|
||||||
|
else if (c > 127 && c < 152 && IncludeCharsAbove127.Contains(c))
|
||||||
|
sb.Append(c);
|
||||||
|
else if(c >= 152 && c <= 255 && ForbiddenCharsAbove152.Contains(c) == false)
|
||||||
|
sb.Append(c);
|
||||||
|
}
|
||||||
|
return sb.ToString();
|
||||||
|
}
|
||||||
}
|
}
|
@ -1,5 +1,4 @@
|
|||||||
using System.ComponentModel.DataAnnotations;
|
using System.ComponentModel.DataAnnotations;
|
||||||
using System.ComponentModel.DataAnnotations.Schema;
|
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
|
||||||
namespace API.Schema;
|
namespace API.Schema;
|
||||||
|
@ -1,191 +0,0 @@
|
|||||||
using System.Text.RegularExpressions;
|
|
||||||
using API.MangaDownloadClients;
|
|
||||||
using HtmlAgilityPack;
|
|
||||||
using log4net;
|
|
||||||
|
|
||||||
namespace API.Schema.MangaConnectors;
|
|
||||||
|
|
||||||
public class AsuraToon : MangaConnector
|
|
||||||
{
|
|
||||||
|
|
||||||
public AsuraToon() : base("AsuraToon", ["en"], ["asuracomic.net"], "https://asuracomic.net/images/logo.webp")
|
|
||||||
{
|
|
||||||
this.downloadClient = new ChromiumDownloadClient();
|
|
||||||
}
|
|
||||||
|
|
||||||
public override (Manga, List<Author>?, List<MangaTag>?, List<Link>?, List<MangaAltTitle>?)[] GetManga(string publicationTitle = "")
|
|
||||||
{
|
|
||||||
string sanitizedTitle = string.Join(' ', Regex.Matches(publicationTitle, "[A-z]*").Where(m => m.Value.Length > 0)).ToLower();
|
|
||||||
string requestUrl = $"https://asuracomic.net/series?name={sanitizedTitle}";
|
|
||||||
RequestResult requestResult =
|
|
||||||
downloadClient.MakeRequest(requestUrl, RequestType.Default);
|
|
||||||
if ((int)requestResult.statusCode < 200 || (int)requestResult.statusCode >= 300)
|
|
||||||
return [];
|
|
||||||
|
|
||||||
if (requestResult.htmlDocument is null)
|
|
||||||
{
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
(Manga, List<Author>?, List<MangaTag>?, List<Link>?, List<MangaAltTitle>?)[] publications = ParsePublicationsFromHtml(requestResult.htmlDocument);
|
|
||||||
return publications;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override (Manga, List<Author>?, List<MangaTag>?, List<Link>?, List<MangaAltTitle>?)? GetMangaFromId(string publicationId)
|
|
||||||
{
|
|
||||||
return GetMangaFromUrl($"https://asuracomic.net/series/{publicationId}");
|
|
||||||
}
|
|
||||||
|
|
||||||
public override (Manga, List<Author>?, List<MangaTag>?, List<Link>?, List<MangaAltTitle>?)? GetMangaFromUrl(string url)
|
|
||||||
{
|
|
||||||
RequestResult requestResult = downloadClient.MakeRequest(url, RequestType.MangaInfo);
|
|
||||||
if ((int)requestResult.statusCode < 200 || (int)requestResult.statusCode >= 300)
|
|
||||||
return null;
|
|
||||||
if (requestResult.htmlDocument is null)
|
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return ParseSinglePublicationFromHtml(requestResult.htmlDocument, url.Split('/')[^1], url);
|
|
||||||
}
|
|
||||||
|
|
||||||
private (Manga, List<Author>?, List<MangaTag>?, List<Link>?, List<MangaAltTitle>?)[] ParsePublicationsFromHtml(HtmlDocument document)
|
|
||||||
{
|
|
||||||
HtmlNodeCollection mangaList = document.DocumentNode.SelectNodes("//a[starts-with(@href,'series')]");
|
|
||||||
if (mangaList is null || mangaList.Count < 1)
|
|
||||||
return [];
|
|
||||||
|
|
||||||
IEnumerable<string> urls = mangaList.Select(a => $"https://asuracomic.net/{a.GetAttributeValue("href", "")}");
|
|
||||||
|
|
||||||
List<(Manga, List<Author>?, List<MangaTag>?, List<Link>?, List<MangaAltTitle>?)> ret = new();
|
|
||||||
foreach (string url in urls)
|
|
||||||
{
|
|
||||||
(Manga, List<Author>?, List<MangaTag>?, List<Link>?, List<MangaAltTitle>?)? manga = GetMangaFromUrl(url);
|
|
||||||
if (manga is { } x)
|
|
||||||
ret.Add(x);
|
|
||||||
}
|
|
||||||
|
|
||||||
return ret.ToArray();
|
|
||||||
}
|
|
||||||
|
|
||||||
private (Manga, List<Author>?, List<MangaTag>?, List<Link>?, List<MangaAltTitle>?) ParseSinglePublicationFromHtml(HtmlDocument document, string publicationId, string websiteUrl)
|
|
||||||
{
|
|
||||||
string? originalLanguage = null;
|
|
||||||
Dictionary<string, string> altTitles = new(), links = new();
|
|
||||||
|
|
||||||
HtmlNodeCollection genreNodes = document.DocumentNode.SelectNodes("//h3[text()='Genres']/../div/button");
|
|
||||||
string[] tags = genreNodes.Select(b => b.InnerText).ToArray();
|
|
||||||
List<MangaTag> mangaTags = tags.Select(t => new MangaTag(t)).ToList();
|
|
||||||
|
|
||||||
HtmlNode statusNode = document.DocumentNode.SelectSingleNode("//h3[text()='Status']/../h3[2]");
|
|
||||||
MangaReleaseStatus releaseStatus = statusNode.InnerText.ToLower() switch
|
|
||||||
{
|
|
||||||
"ongoing" => MangaReleaseStatus.Continuing,
|
|
||||||
"hiatus" => MangaReleaseStatus.OnHiatus,
|
|
||||||
"completed" => MangaReleaseStatus.Completed,
|
|
||||||
"dropped" => MangaReleaseStatus.Cancelled,
|
|
||||||
"season end" => MangaReleaseStatus.Continuing,
|
|
||||||
"coming soon" => MangaReleaseStatus.Unreleased,
|
|
||||||
_ => MangaReleaseStatus.Unreleased
|
|
||||||
};
|
|
||||||
|
|
||||||
HtmlNode coverNode =
|
|
||||||
document.DocumentNode.SelectSingleNode("//img[@alt='poster']");
|
|
||||||
string coverUrl = coverNode.GetAttributeValue("src", "");
|
|
||||||
|
|
||||||
HtmlNode titleNode =
|
|
||||||
document.DocumentNode.SelectSingleNode("//title");
|
|
||||||
string sortName = Regex.Match(titleNode.InnerText, @"(.*) - Asura Scans").Groups[1].Value;
|
|
||||||
|
|
||||||
HtmlNode descriptionNode =
|
|
||||||
document.DocumentNode.SelectSingleNode("//h3[starts-with(text(),'Synopsis')]/../span");
|
|
||||||
string description = descriptionNode?.InnerText??"";
|
|
||||||
|
|
||||||
HtmlNodeCollection authorNodes = document.DocumentNode.SelectNodes("//h3[text()='Author']/../h3[not(text()='Author' or text()='_')]");
|
|
||||||
HtmlNodeCollection artistNodes = document.DocumentNode.SelectNodes("//h3[text()='Artist']/../h3[not(text()='Artist' or text()='_')]");
|
|
||||||
IEnumerable<string> authorNames = authorNodes is null ? [] : authorNodes.Select(a => a.InnerText);
|
|
||||||
IEnumerable<string> artistNames = artistNodes is null ? [] : artistNodes.Select(a => a.InnerText);
|
|
||||||
List<string> authorStrings = authorNames.Concat(artistNames).ToList();
|
|
||||||
List<Author> authors = authorStrings.Select(author => new Author(author)).ToList();
|
|
||||||
|
|
||||||
HtmlNode? firstChapterNode = document.DocumentNode.SelectSingleNode("//a[contains(@href, 'chapter/1')]/../following-sibling::h3");
|
|
||||||
uint year = uint.Parse(firstChapterNode?.InnerText.Split(' ')[^1] ?? "2000");
|
|
||||||
|
|
||||||
Manga manga = new (publicationId, sortName, description, websiteUrl, coverUrl, null, year,
|
|
||||||
originalLanguage, releaseStatus, -1,
|
|
||||||
this,
|
|
||||||
authors,
|
|
||||||
mangaTags,
|
|
||||||
[],
|
|
||||||
[]);
|
|
||||||
|
|
||||||
return (manga, authors, mangaTags, [], []);
|
|
||||||
}
|
|
||||||
|
|
||||||
public override Chapter[] GetChapters(Manga manga, string language="en")
|
|
||||||
{
|
|
||||||
string requestUrl = $"https://asuracomic.net/series/{manga.MangaId}";
|
|
||||||
// Leaving this in for verification if the page exists
|
|
||||||
RequestResult requestResult =
|
|
||||||
downloadClient.MakeRequest(requestUrl, RequestType.Default);
|
|
||||||
if ((int)requestResult.statusCode < 200 || (int)requestResult.statusCode >= 300)
|
|
||||||
return [];
|
|
||||||
|
|
||||||
//Return Chapters ordered by Chapter-Number
|
|
||||||
List<Chapter> chapters = ParseChaptersFromHtml(manga, requestUrl);
|
|
||||||
return chapters.Order().ToArray();
|
|
||||||
}
|
|
||||||
|
|
||||||
private List<Chapter> ParseChaptersFromHtml(Manga manga, string mangaUrl)
|
|
||||||
{
|
|
||||||
RequestResult result = downloadClient.MakeRequest(mangaUrl, RequestType.Default);
|
|
||||||
if ((int)result.statusCode < 200 || (int)result.statusCode >= 300 || result.htmlDocument is null)
|
|
||||||
{
|
|
||||||
return new List<Chapter>();
|
|
||||||
}
|
|
||||||
|
|
||||||
List<Chapter> ret = new();
|
|
||||||
|
|
||||||
HtmlNodeCollection chapterURLNodes = result.htmlDocument.DocumentNode.SelectNodes("//a[contains(@href, '/chapter/')]");
|
|
||||||
Regex infoRex = new(@"Chapter ([0-9]+)(.*)?");
|
|
||||||
|
|
||||||
foreach (HtmlNode chapterInfo in chapterURLNodes)
|
|
||||||
{
|
|
||||||
string chapterUrl = chapterInfo.GetAttributeValue("href", "");
|
|
||||||
|
|
||||||
Match match = infoRex.Match(chapterInfo.InnerText);
|
|
||||||
string chapterNumber = new(match.Groups[1].Value);
|
|
||||||
string? chapterName = match.Groups[2].Success && match.Groups[2].Length > 1 ? match.Groups[2].Value : null;
|
|
||||||
string url = $"https://asuracomic.net/series/{chapterUrl}";
|
|
||||||
try
|
|
||||||
{
|
|
||||||
ret.Add(new Chapter(manga, url, chapterNumber, null, chapterName));
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
internal override string[] GetChapterImageUrls(Chapter chapter)
|
|
||||||
{
|
|
||||||
string requestUrl = chapter.Url;
|
|
||||||
// Leaving this in to check if the page exists
|
|
||||||
RequestResult requestResult =
|
|
||||||
downloadClient.MakeRequest(requestUrl, RequestType.Default);
|
|
||||||
if ((int)requestResult.statusCode < 200 || (int)requestResult.statusCode >= 300 || requestResult.htmlDocument is null)
|
|
||||||
{
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
string[] imageUrls = ParseImageUrlsFromHtml(requestResult.htmlDocument);
|
|
||||||
return imageUrls;
|
|
||||||
}
|
|
||||||
|
|
||||||
private string[] ParseImageUrlsFromHtml(HtmlDocument document)
|
|
||||||
{
|
|
||||||
HtmlNodeCollection images = document.DocumentNode.SelectNodes("//img[contains(@alt, 'chapter page')]");
|
|
||||||
|
|
||||||
return images.Select(i => i.GetAttributeValue("src", "")).ToArray();
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,203 +0,0 @@
|
|||||||
using System.Net;
|
|
||||||
using System.Text.RegularExpressions;
|
|
||||||
using API.MangaDownloadClients;
|
|
||||||
using HtmlAgilityPack;
|
|
||||||
|
|
||||||
namespace API.Schema.MangaConnectors;
|
|
||||||
|
|
||||||
public class Bato : MangaConnector
|
|
||||||
{
|
|
||||||
|
|
||||||
public Bato() : base("Bato", ["en"], ["bato.to"], "https://bato.to/amsta/img/batoto/favicon.ico")
|
|
||||||
{
|
|
||||||
this.downloadClient = new HttpDownloadClient();
|
|
||||||
}
|
|
||||||
|
|
||||||
public override (Manga, List<Author>?, List<MangaTag>?, List<Link>?, List<MangaAltTitle>?)[] GetManga(string publicationTitle = "")
|
|
||||||
{
|
|
||||||
string sanitizedTitle = string.Join(' ', Regex.Matches(publicationTitle, "[A-z]*").Where(m => m.Value.Length > 0)).ToLower();
|
|
||||||
string requestUrl = $"https://bato.to/v3x-search?word={sanitizedTitle}&lang=en";
|
|
||||||
RequestResult requestResult =
|
|
||||||
downloadClient.MakeRequest(requestUrl, RequestType.Default);
|
|
||||||
if ((int)requestResult.statusCode < 200 || (int)requestResult.statusCode >= 300)
|
|
||||||
return [];
|
|
||||||
|
|
||||||
if (requestResult.htmlDocument is null)
|
|
||||||
{
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
(Manga, List<Author>?, List<MangaTag>?, List<Link>?, List<MangaAltTitle>?)[] publications = ParsePublicationsFromHtml(requestResult.htmlDocument);
|
|
||||||
return publications;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override (Manga, List<Author>?, List<MangaTag>?, List<Link>?, List<MangaAltTitle>?)? GetMangaFromId(string publicationId)
|
|
||||||
{
|
|
||||||
return GetMangaFromUrl($"https://bato.to/title/{publicationId}");
|
|
||||||
}
|
|
||||||
|
|
||||||
public override (Manga, List<Author>?, List<MangaTag>?, List<Link>?, List<MangaAltTitle>?)? GetMangaFromUrl(string url)
|
|
||||||
{
|
|
||||||
RequestResult requestResult = downloadClient.MakeRequest(url, RequestType.MangaInfo);
|
|
||||||
if ((int)requestResult.statusCode < 200 || (int)requestResult.statusCode >= 300)
|
|
||||||
return null;
|
|
||||||
if (requestResult.htmlDocument is null)
|
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return ParseSinglePublicationFromHtml(requestResult.htmlDocument, url.Split('/')[^1], url);
|
|
||||||
}
|
|
||||||
|
|
||||||
private (Manga, List<Author>?, List<MangaTag>?, List<Link>?, List<MangaAltTitle>?)[] ParsePublicationsFromHtml(HtmlDocument document)
|
|
||||||
{
|
|
||||||
HtmlNode mangaList = document.DocumentNode.SelectSingleNode("//div[@data-hk='0-0-2']");
|
|
||||||
if (!mangaList.ChildNodes.Any(node => node.Name == "div"))
|
|
||||||
return [];
|
|
||||||
|
|
||||||
List<string> urls = mangaList.ChildNodes
|
|
||||||
.Select(node => $"https://bato.to{node.Descendants("div").First().FirstChild.GetAttributeValue("href", "")}").ToList();
|
|
||||||
|
|
||||||
HashSet<(Manga, List<Author>?, List<MangaTag>?, List<Link>?, List<MangaAltTitle>?)> ret = new();
|
|
||||||
foreach (string url in urls)
|
|
||||||
{
|
|
||||||
(Manga, List<Author>?, List<MangaTag>?, List<Link>?, List<MangaAltTitle>?)? manga = GetMangaFromUrl(url);
|
|
||||||
if (manga is { } x)
|
|
||||||
ret.Add(x);
|
|
||||||
}
|
|
||||||
|
|
||||||
return ret.ToArray();
|
|
||||||
}
|
|
||||||
|
|
||||||
private (Manga, List<Author>?, List<MangaTag>?, List<Link>?, List<MangaAltTitle>?) ParseSinglePublicationFromHtml(HtmlDocument document, string publicationId, string websiteUrl)
|
|
||||||
{
|
|
||||||
HtmlNode infoNode = document.DocumentNode.SelectSingleNode("/html/body/div/main/div[1]/div[2]");
|
|
||||||
|
|
||||||
string sortName = infoNode.Descendants("h3").First().InnerText;
|
|
||||||
string description = document.DocumentNode
|
|
||||||
.SelectSingleNode("//div[contains(concat(' ',normalize-space(@class),' '),'prose')]").InnerText;
|
|
||||||
|
|
||||||
string[] altTitlesList = infoNode.ChildNodes[1].ChildNodes[2].InnerText.Split('/');
|
|
||||||
int i = 0;
|
|
||||||
List<MangaAltTitle> altTitles = altTitlesList.Select(a => new MangaAltTitle(i++.ToString(), a)).ToList();
|
|
||||||
|
|
||||||
string coverUrl = document.DocumentNode.SelectNodes("//img")
|
|
||||||
.First(child => child.GetAttributeValue("data-hk", "") == "0-1-0").GetAttributeValue("src", "").Replace("&", "&");
|
|
||||||
|
|
||||||
List<HtmlNode> genreNodes = document.DocumentNode.SelectSingleNode("//b[text()='Genres:']/..").SelectNodes("span").ToList();
|
|
||||||
string[] tags = genreNodes.Select(node => node.FirstChild.InnerText).ToArray();
|
|
||||||
List<MangaTag> mangaTags = tags.Select(s => new MangaTag(s)).ToList();
|
|
||||||
|
|
||||||
List<HtmlNode> authorsNodes = infoNode.ChildNodes[1].ChildNodes[3].Descendants("a").ToList();
|
|
||||||
List<string> authorNames = authorsNodes.Select(node => node.InnerText.Replace("amp;", "")).ToList();
|
|
||||||
List<Author> authors = authorNames.Select(n => new Author(n)).ToList();
|
|
||||||
|
|
||||||
HtmlNode? originalLanguageNode = document.DocumentNode.SelectSingleNode("//span[text()='Tr From']/..");
|
|
||||||
string originalLanguage = originalLanguageNode is not null ? originalLanguageNode.LastChild.InnerText : "";
|
|
||||||
|
|
||||||
if (!uint.TryParse(
|
|
||||||
document.DocumentNode.SelectSingleNode("//span[text()='Original Publication:']/..").LastChild.InnerText.Split('-')[0],
|
|
||||||
out uint year))
|
|
||||||
year = (uint)DateTime.UtcNow.Year;
|
|
||||||
|
|
||||||
string status = document.DocumentNode.SelectSingleNode("//span[text()='Original Publication:']/..")
|
|
||||||
.ChildNodes[2].InnerText;
|
|
||||||
MangaReleaseStatus releaseStatus = MangaReleaseStatus.Unreleased;
|
|
||||||
switch (status.ToLower())
|
|
||||||
{
|
|
||||||
case "ongoing": releaseStatus = MangaReleaseStatus.Continuing; break;
|
|
||||||
case "completed": releaseStatus = MangaReleaseStatus.Completed; break;
|
|
||||||
case "hiatus": releaseStatus = MangaReleaseStatus.OnHiatus; break;
|
|
||||||
case "cancelled": releaseStatus = MangaReleaseStatus.Cancelled; break;
|
|
||||||
case "pending": releaseStatus = MangaReleaseStatus.Unreleased; break;
|
|
||||||
}
|
|
||||||
|
|
||||||
Manga manga = new (publicationId, sortName, description, websiteUrl, coverUrl, null, year,
|
|
||||||
originalLanguage, releaseStatus, -1,
|
|
||||||
this,
|
|
||||||
authors,
|
|
||||||
mangaTags,
|
|
||||||
[],
|
|
||||||
altTitles);
|
|
||||||
|
|
||||||
return (manga, authors, mangaTags, [], altTitles);
|
|
||||||
}
|
|
||||||
|
|
||||||
public override Chapter[] GetChapters(Manga manga, string language="en")
|
|
||||||
{
|
|
||||||
string requestUrl = $"https://bato.to/title/{manga.MangaId}";
|
|
||||||
// Leaving this in for verification if the page exists
|
|
||||||
RequestResult requestResult =
|
|
||||||
downloadClient.MakeRequest(requestUrl, RequestType.Default);
|
|
||||||
if ((int)requestResult.statusCode < 200 || (int)requestResult.statusCode >= 300)
|
|
||||||
return [];
|
|
||||||
|
|
||||||
//Return Chapters ordered by Chapter-Number
|
|
||||||
List<Chapter> chapters = ParseChaptersFromHtml(manga, requestUrl);
|
|
||||||
return chapters.Order().ToArray();
|
|
||||||
}
|
|
||||||
|
|
||||||
private List<Chapter> ParseChaptersFromHtml(Manga manga, string mangaUrl)
|
|
||||||
{
|
|
||||||
RequestResult result = downloadClient.MakeRequest(mangaUrl, RequestType.Default);
|
|
||||||
if ((int)result.statusCode < 200 || (int)result.statusCode >= 300 || result.htmlDocument is null)
|
|
||||||
{
|
|
||||||
return new List<Chapter>();
|
|
||||||
}
|
|
||||||
|
|
||||||
List<Chapter> ret = new();
|
|
||||||
|
|
||||||
HtmlNode chapterList =
|
|
||||||
result.htmlDocument.DocumentNode.SelectSingleNode("/html/body/div/main/div[3]/astro-island/div/div[2]/div/div/astro-slot");
|
|
||||||
|
|
||||||
Regex numberRex = new(@"\/title\/.+\/([0-9])+(?:-vol_([0-9]+))?-ch_([0-9\.]+)");
|
|
||||||
|
|
||||||
foreach (HtmlNode chapterInfo in chapterList.SelectNodes("div"))
|
|
||||||
{
|
|
||||||
HtmlNode infoNode = chapterInfo.FirstChild.FirstChild;
|
|
||||||
string chapterUrl = infoNode.GetAttributeValue("href", "");
|
|
||||||
|
|
||||||
Match match = numberRex.Match(chapterUrl);
|
|
||||||
string id = match.Groups[1].Value;
|
|
||||||
int? volumeNumber = match.Groups[2].Success ? int.Parse(match.Groups[2].Value) : null;
|
|
||||||
string chapterNumber = new(match.Groups[3].Value);
|
|
||||||
string url = $"https://bato.to{chapterUrl}?load=2";
|
|
||||||
try
|
|
||||||
{
|
|
||||||
ret.Add(new Chapter(manga, url, chapterNumber, volumeNumber, null));
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
internal override string[] GetChapterImageUrls(Chapter chapter)
|
|
||||||
{
|
|
||||||
string requestUrl = chapter.Url;
|
|
||||||
// Leaving this in to check if the page exists
|
|
||||||
RequestResult requestResult =
|
|
||||||
downloadClient.MakeRequest(requestUrl, RequestType.Default);
|
|
||||||
if ((int)requestResult.statusCode < 200 || (int)requestResult.statusCode >= 300 || requestResult.htmlDocument is null)
|
|
||||||
{
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
string[] imageUrls = ParseImageUrlsFromHtml(requestResult.htmlDocument);
|
|
||||||
return imageUrls;
|
|
||||||
}
|
|
||||||
|
|
||||||
private string[] ParseImageUrlsFromHtml(HtmlDocument document)
|
|
||||||
{
|
|
||||||
HtmlNode images = document.DocumentNode.SelectNodes("//astro-island").First(node =>
|
|
||||||
node.GetAttributeValue("component-url", "").Contains("/_astro/ImageList."));
|
|
||||||
|
|
||||||
string weirdString = images.OuterHtml;
|
|
||||||
string weirdString2 = Regex.Match(weirdString, @"props=\""(.*)}\""").Groups[1].Value;
|
|
||||||
string[] urls = Regex.Matches(weirdString2, @"(https:\/\/[A-z\-0-9\.\?\&\;\=\/]+)\\")
|
|
||||||
.Select(match => match.Groups[1].Value.Replace("&", "&")).ToArray();
|
|
||||||
|
|
||||||
return urls;
|
|
||||||
}
|
|
||||||
}
|
|
@ -8,15 +8,14 @@ public class Global : MangaConnector
|
|||||||
this.context = context;
|
this.context = context;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override (Manga, List<Author>?, List<MangaTag>?, List<Link>?, List<MangaAltTitle>?)[] GetManga(string publicationTitle = "")
|
public override Manga[] SearchManga(string mangaSearchName)
|
||||||
{
|
{
|
||||||
//Get all enabled Connectors
|
//Get all enabled Connectors
|
||||||
MangaConnector[] enabledConnectors = context.MangaConnectors.Where(c => c.Enabled && c.Name != "Global").ToArray();
|
MangaConnector[] enabledConnectors = context.MangaConnectors.Where(c => c.Enabled && c.Name != "Global").ToArray();
|
||||||
|
|
||||||
//Create Task for each MangaConnector to search simulatneously
|
//Create Task for each MangaConnector to search simulatneously
|
||||||
Task<(Manga, List<Author>?, List<MangaTag>?, List<Link>?, List<MangaAltTitle>?)[]>[] tasks =
|
Task<Manga[]>[] tasks =
|
||||||
enabledConnectors.Select(c =>
|
enabledConnectors.Select(c => new Task<Manga[]>(() => c.SearchManga(mangaSearchName))).ToArray();
|
||||||
new Task<(Manga, List<Author>?, List<MangaTag>?, List<Link>?, List<MangaAltTitle>?)[]>(() => c.GetManga(publicationTitle))).ToArray();
|
|
||||||
foreach (var task in tasks)
|
foreach (var task in tasks)
|
||||||
task.Start();
|
task.Start();
|
||||||
|
|
||||||
@ -27,29 +26,28 @@ public class Global : MangaConnector
|
|||||||
}while(tasks.Any(t => t.Status < TaskStatus.RanToCompletion));
|
}while(tasks.Any(t => t.Status < TaskStatus.RanToCompletion));
|
||||||
|
|
||||||
//Concatenate all results into one
|
//Concatenate all results into one
|
||||||
(Manga, List<Author>?, List<MangaTag>?, List<Link>?, List<MangaAltTitle>?)[] ret =
|
Manga[] ret = tasks.Select(t => t.IsCompletedSuccessfully ? t.Result : []).ToArray().SelectMany(i => i).ToArray();
|
||||||
tasks.Select(t => t.IsCompletedSuccessfully ? t.Result : []).ToArray().SelectMany(i => i).ToArray();
|
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override (Manga, List<Author>?, List<MangaTag>?, List<Link>?, List<MangaAltTitle>?)? GetMangaFromUrl(string url)
|
public override Manga? GetMangaFromUrl(string url)
|
||||||
{
|
{
|
||||||
MangaConnector? mc = context.MangaConnectors.ToArray().FirstOrDefault(c => c.ValidateUrl(url));
|
MangaConnector? mc = context.MangaConnectors.ToArray().FirstOrDefault(c => c.UrlMatchesConnector(url));
|
||||||
return mc?.GetMangaFromUrl(url) ?? null;
|
return mc?.GetMangaFromUrl(url) ?? null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override (Manga, List<Author>?, List<MangaTag>?, List<Link>?, List<MangaAltTitle>?)? GetMangaFromId(string publicationId)
|
public override Manga? GetMangaFromId(string mangaIdOnSite)
|
||||||
{
|
{
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override Chapter[] GetChapters(Manga manga, string language = "en")
|
public override Chapter[] GetChapters(Manga manga, string? language = null)
|
||||||
{
|
{
|
||||||
return manga.MangaConnector?.GetChapters(manga) ?? [];
|
return manga.MangaConnector.GetChapters(manga, language);
|
||||||
}
|
}
|
||||||
|
|
||||||
internal override string[] GetChapterImageUrls(Chapter chapter)
|
internal override string[] GetChapterImageUrls(Chapter chapter)
|
||||||
{
|
{
|
||||||
return chapter.ParentManga?.MangaConnector?.GetChapterImageUrls(chapter) ?? [];
|
return chapter.ParentManga.MangaConnector.GetChapterImageUrls(chapter);
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -11,6 +11,14 @@ namespace API.Schema.MangaConnectors;
|
|||||||
[PrimaryKey("Name")]
|
[PrimaryKey("Name")]
|
||||||
public abstract class MangaConnector(string name, string[] supportedLanguages, string[] baseUris, string iconUrl)
|
public abstract class MangaConnector(string name, string[] supportedLanguages, string[] baseUris, string iconUrl)
|
||||||
{
|
{
|
||||||
|
[JsonIgnore]
|
||||||
|
[NotMapped]
|
||||||
|
internal DownloadClient downloadClient { get; init; } = null!;
|
||||||
|
|
||||||
|
[JsonIgnore]
|
||||||
|
[NotMapped]
|
||||||
|
protected ILog Log { get; init; } = LogManager.GetLogger(name);
|
||||||
|
|
||||||
[StringLength(32)]
|
[StringLength(32)]
|
||||||
[Required]
|
[Required]
|
||||||
public string Name { get; init; } = name;
|
public string Name { get; init; } = name;
|
||||||
@ -26,32 +34,41 @@ public abstract class MangaConnector(string name, string[] supportedLanguages, s
|
|||||||
[Required]
|
[Required]
|
||||||
public bool Enabled { get; internal set; } = true;
|
public bool Enabled { get; internal set; } = true;
|
||||||
|
|
||||||
public abstract (Manga, List<Author>?, List<MangaTag>?, List<Link>?, List<MangaAltTitle>?)[] GetManga(string publicationTitle = "");
|
public abstract Manga[] SearchManga(string mangaSearchName);
|
||||||
|
|
||||||
public abstract (Manga, List<Author>?, List<MangaTag>?, List<Link>?, List<MangaAltTitle>?)? GetMangaFromUrl(string url);
|
public abstract Manga? GetMangaFromUrl(string url);
|
||||||
|
|
||||||
public abstract (Manga, List<Author>?, List<MangaTag>?, List<Link>?, List<MangaAltTitle>?)? GetMangaFromId(string publicationId);
|
public abstract Manga? GetMangaFromId(string mangaIdOnSite);
|
||||||
|
|
||||||
public abstract Chapter[] GetChapters(Manga manga, string language="en");
|
public abstract Chapter[] GetChapters(Manga manga, string? language = null);
|
||||||
|
|
||||||
[JsonIgnore]
|
|
||||||
[NotMapped]
|
|
||||||
internal DownloadClient downloadClient { get; init; } = null!;
|
|
||||||
|
|
||||||
[JsonIgnore]
|
|
||||||
[NotMapped]
|
|
||||||
protected ILog Log { get; init; } = LogManager.GetLogger(name);
|
|
||||||
|
|
||||||
public Chapter[] GetNewChapters(Manga manga)
|
|
||||||
{
|
|
||||||
Chapter[] allChapters = GetChapters(manga);
|
|
||||||
if (allChapters.Length < 1)
|
|
||||||
return [];
|
|
||||||
|
|
||||||
return allChapters.Where(chapter => !chapter.IsDownloaded()).ToArray();
|
|
||||||
}
|
|
||||||
|
|
||||||
internal abstract string[] GetChapterImageUrls(Chapter chapter);
|
internal abstract string[] GetChapterImageUrls(Chapter chapter);
|
||||||
|
|
||||||
public bool ValidateUrl(string url) => BaseUris.Any(baseUri => Regex.IsMatch(url, "https?://" + baseUri + "/.*"));
|
public bool UrlMatchesConnector(string url) => BaseUris.Any(baseUri => Regex.IsMatch(url, "https?://" + baseUri + "/.*"));
|
||||||
|
|
||||||
|
internal string? SaveCoverImageToCache(Manga manga, int retries = 3)
|
||||||
|
{
|
||||||
|
if(retries < 0)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
Regex urlRex = new (@"https?:\/\/((?:[a-zA-Z0-9-]+\.)+[a-zA-Z0-9]+)\/(?:.+\/)*(.+\.([a-zA-Z]+))");
|
||||||
|
//https?:\/\/[a-zA-Z0-9-]+\.([a-zA-Z0-9-]+\.[a-zA-Z0-9]+)\/(?:.+\/)*(.+\.([a-zA-Z]+)) for only second level domains
|
||||||
|
Match match = urlRex.Match(manga.CoverUrl);
|
||||||
|
string filename = $"{match.Groups[1].Value}-{manga.MangaId}.{match.Groups[3].Value}";
|
||||||
|
string saveImagePath = Path.Join(TrangaSettings.coverImageCache, filename);
|
||||||
|
|
||||||
|
if (File.Exists(saveImagePath))
|
||||||
|
return saveImagePath;
|
||||||
|
|
||||||
|
RequestResult coverResult = downloadClient.MakeRequest(manga.CoverUrl, RequestType.MangaCover, $"https://{match.Groups[1].Value}");
|
||||||
|
if ((int)coverResult.statusCode < 200 || (int)coverResult.statusCode >= 300)
|
||||||
|
return SaveCoverImageToCache(manga, --retries);
|
||||||
|
|
||||||
|
using MemoryStream ms = new();
|
||||||
|
coverResult.result.CopyTo(ms);
|
||||||
|
Directory.CreateDirectory(TrangaSettings.coverImageCache);
|
||||||
|
File.WriteAllBytes(saveImagePath, ms.ToArray());
|
||||||
|
|
||||||
|
return saveImagePath;
|
||||||
|
}
|
||||||
}
|
}
|
@ -1,8 +1,6 @@
|
|||||||
using System.Net;
|
using System.Text.RegularExpressions;
|
||||||
using System.Text.Json.Nodes;
|
|
||||||
using System.Text.RegularExpressions;
|
|
||||||
using API.MangaDownloadClients;
|
using API.MangaDownloadClients;
|
||||||
using JsonSerializer = System.Text.Json.JsonSerializer;
|
using Newtonsoft.Json.Linq;
|
||||||
|
|
||||||
namespace API.Schema.MangaConnectors;
|
namespace API.Schema.MangaConnectors;
|
||||||
|
|
||||||
@ -11,313 +9,327 @@ public class MangaDex : MangaConnector
|
|||||||
//https://api.mangadex.org/docs/3-enumerations/#language-codes--localization
|
//https://api.mangadex.org/docs/3-enumerations/#language-codes--localization
|
||||||
//https://en.wikipedia.org/wiki/List_of_ISO_639_language_codes
|
//https://en.wikipedia.org/wiki/List_of_ISO_639_language_codes
|
||||||
//https://gist.github.com/Josantonius/b455e315bc7f790d14b136d61d9ae469
|
//https://gist.github.com/Josantonius/b455e315bc7f790d14b136d61d9ae469
|
||||||
public MangaDex() : base("MangaDex", ["en","pt","pt-br","it","de","ru","aa","ab","ae","af","ak","am","an","ar-ae","ar-bh","ar-dz","ar-eg","ar-iq","ar-jo","ar-kw","ar-lb","ar-ly","ar-ma","ar-om","ar-qa","ar-sa","ar-sy","ar-tn","ar-ye","ar","as","av","ay","az","ba","be","bg","bh","bi","bm","bn","bo","br","bs","ca","ce","ch","co","cr","cs","cu","cv","cy","da","de-at","de-ch","de-de","de-li","de-lu","div","dv","dz","ee","el","en-au","en-bz","en-ca","en-cb","en-gb","en-ie","en-jm","en-nz","en-ph","en-tt","en-us","en-za","en-zw","eo","es-ar","es-bo","es-cl","es-co","es-cr","es-do","es-ec","es-es","es-gt","es-hn","es-la","es-mx","es-ni","es-pa","es-pe","es-pr","es-py","es-sv","es-us","es-uy","es-ve","es","et","eu","fa","ff","fi","fj","fo","fr-be","fr-ca","fr-ch","fr-fr","fr-lu","fr-mc","fr","fy","ga","gd","gl","gn","gu","gv","ha","he","hi","ho","hr-ba","hr-hr","hr","ht","hu","hy","hz","ia","id","ie","ig","ii","ik","in","io","is","it-ch","it-it","iu","iw","ja","ja-ro","ji","jv","jw","ka","kg","ki","kj","kk","kl","km","kn","ko","ko-ro","kr","ks","ku","kv","kw","ky","kz","la","lb","lg","li","ln","lo","ls","lt","lu","lv","mg","mh","mi","mk","ml","mn","mo","mr","ms-bn","ms-my","ms","mt","my","na","nb","nd","ne","ng","nl-be","nl-nl","nl","nn","no","nr","ns","nv","ny","oc","oj","om","or","os","pa","pi","pl","ps","pt-pt","qu-bo","qu-ec","qu-pe","qu","rm","rn","ro","rw","sa","sb","sc","sd","se-fi","se-no","se-se","se","sg","sh","si","sk","sl","sm","sn","so","sq","sr-ba","sr-sp","sr","ss","st","su","sv-fi","sv-se","sv","sw","sx","syr","ta","te","tg","th","ti","tk","tl","tn","to","tr","ts","tt","tw","ty","ug","uk","ur","us","uz","ve","vi","vo","wa","wo","xh","yi","yo","za","zh-cn","zh-hk","zh-mo","zh-ro","zh-sg","zh-tw","zh","zu"], ["mangadex.org"], "https://mangadex.org/favicon.ico")
|
public MangaDex() : base("MangaDex",
|
||||||
|
["en","pt","pt-br","it","de","ru","aa","ab","ae","af","ak","am","an","ar-ae","ar-bh","ar-dz","ar-eg","ar-iq","ar-jo","ar-kw","ar-lb","ar-ly","ar-ma","ar-om","ar-qa","ar-sa","ar-sy","ar-tn","ar-ye","ar","as","av","ay","az","ba","be","bg","bh","bi","bm","bn","bo","br","bs","ca","ce","ch","co","cr","cs","cu","cv","cy","da","de-at","de-ch","de-de","de-li","de-lu","div","dv","dz","ee","el","en-au","en-bz","en-ca","en-cb","en-gb","en-ie","en-jm","en-nz","en-ph","en-tt","en-us","en-za","en-zw","eo","es-ar","es-bo","es-cl","es-co","es-cr","es-do","es-ec","es-es","es-gt","es-hn","es-la","es-mx","es-ni","es-pa","es-pe","es-pr","es-py","es-sv","es-us","es-uy","es-ve","es","et","eu","fa","ff","fi","fj","fo","fr-be","fr-ca","fr-ch","fr-fr","fr-lu","fr-mc","fr","fy","ga","gd","gl","gn","gu","gv","ha","he","hi","ho","hr-ba","hr-hr","hr","ht","hu","hy","hz","ia","id","ie","ig","ii","ik","in","io","is","it-ch","it-it","iu","iw","ja","ja-ro","ji","jv","jw","ka","kg","ki","kj","kk","kl","km","kn","ko","ko-ro","kr","ks","ku","kv","kw","ky","kz","la","lb","lg","li","ln","lo","ls","lt","lu","lv","mg","mh","mi","mk","ml","mn","mo","mr","ms-bn","ms-my","ms","mt","my","na","nb","nd","ne","ng","nl-be","nl-nl","nl","nn","no","nr","ns","nv","ny","oc","oj","om","or","os","pa","pi","pl","ps","pt-pt","qu-bo","qu-ec","qu-pe","qu","rm","rn","ro","rw","sa","sb","sc","sd","se-fi","se-no","se-se","se","sg","sh","si","sk","sl","sm","sn","so","sq","sr-ba","sr-sp","sr","ss","st","su","sv-fi","sv-se","sv","sw","sx","syr","ta","te","tg","th","ti","tk","tl","tn","to","tr","ts","tt","tw","ty","ug","uk","ur","us","uz","ve","vi","vo","wa","wo","xh","yi","yo","za","zh-cn","zh-hk","zh-mo","zh-ro","zh-sg","zh-tw","zh","zu"],
|
||||||
|
["mangadex.org"],
|
||||||
|
"https://mangadex.org/favicon.ico")
|
||||||
{
|
{
|
||||||
this.downloadClient = new HttpDownloadClient();
|
this.downloadClient = new HttpDownloadClient();
|
||||||
}
|
}
|
||||||
|
|
||||||
public override (Manga, List<Author>?, List<MangaTag>?, List<Link>?, List<MangaAltTitle>?)[] GetManga(string publicationTitle = "")
|
private const int Limit = 100;
|
||||||
|
public override Manga[] SearchManga(string mangaSearchName)
|
||||||
{
|
{
|
||||||
const int limit = 100; //How many values we want returned at once
|
Log.Info($"Searching Manga: {mangaSearchName}");
|
||||||
int offset = 0; //"Page"
|
List<Manga> mangas = new ();
|
||||||
int total = int.MaxValue; //How many total results are there, is updated on first request
|
|
||||||
HashSet<(Manga, List<Author>?, List<MangaTag>?, List<Link>?, List<MangaAltTitle>?)> retManga = new();
|
|
||||||
List<JsonNode> results = new();
|
|
||||||
|
|
||||||
//Request all search-results
|
int offset = 0;
|
||||||
while (offset < total) //As long as we haven't requested all "Pages"
|
int total = int.MaxValue;
|
||||||
|
while(offset < total)
|
||||||
{
|
{
|
||||||
//Request next Page
|
|
||||||
string requestUrl =
|
string requestUrl =
|
||||||
$"https://api.mangadex.org/manga?limit={limit}&title={publicationTitle}&offset={offset}" +
|
$"https://api.mangadex.org/manga?limit={Limit}&offset={offset}&title={mangaSearchName}" +
|
||||||
$"&includes[]=manga&includes[]=cover_art&includes[]=author&includes[]=artist&includes[]=tag";
|
$"&contentRating%5B%5D=safe&contentRating%5B%5D=suggestive&contentRating%5B%5D=erotica" +
|
||||||
RequestResult requestResult = downloadClient.MakeRequest(requestUrl, RequestType.MangaInfo);
|
$"&includes%5B%5D=manga&includes%5B%5D=cover_art&includes%5B%5D=author&includes%5B%5D=artist&includes%5B%5D=tag'";
|
||||||
if ((int)requestResult.statusCode < 200 || (int)requestResult.statusCode >= 300)
|
offset += Limit;
|
||||||
{
|
|
||||||
Log.Info($"{requestResult.statusCode}: {requestUrl}");
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
JsonObject? result = JsonSerializer.Deserialize<JsonObject>(requestResult.result);
|
|
||||||
|
|
||||||
offset += limit;
|
|
||||||
if (result is null)
|
|
||||||
{
|
|
||||||
Log.Info($"result was null: {requestUrl}");
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(result.ContainsKey("total"))
|
|
||||||
total = result["total"]!.GetValue<int>(); //Update the total number of Publications
|
|
||||||
else continue;
|
|
||||||
|
|
||||||
if (result.ContainsKey("data"))
|
RequestResult result = downloadClient.MakeRequest(requestUrl, RequestType.MangaDexFeed);
|
||||||
results.AddRange(result["data"]!.AsArray()!);//Manga-data-Array
|
if ((int)result.statusCode < 200 || (int)result.statusCode >= 300)
|
||||||
|
{
|
||||||
|
Log.Error("Request failed");
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
using StreamReader sr = new (result.result);
|
||||||
|
JObject jObject = JObject.Parse(sr.ReadToEnd());
|
||||||
|
|
||||||
|
if (jObject.Value<string>("result") != "ok")
|
||||||
|
{
|
||||||
|
JArray? errors = jObject["errors"] as JArray;
|
||||||
|
Log.Error($"Request failed: {string.Join(',', errors?.Select(e => e.Value<string>("title")) ?? [])}");
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
total = jObject.Value<int>("total");
|
||||||
|
|
||||||
|
JArray? data = jObject.Value<JArray>("data");
|
||||||
|
if (data is null)
|
||||||
|
{
|
||||||
|
Log.Error("Data was null");
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
mangas.AddRange(data.Select(ParseMangaFromJToken));
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (JsonNode mangaNode in results)
|
Log.Info($"Search {mangaSearchName} yielded {mangas.Count} results.");
|
||||||
{
|
return mangas.ToArray();
|
||||||
if(MangaFromJsonObject(mangaNode.AsObject()) is { } manga)
|
|
||||||
retManga.Add(manga); //Add Publication (Manga) to result
|
|
||||||
}
|
|
||||||
return retManga.ToArray();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public override (Manga, List<Author>?, List<MangaTag>?, List<Link>?, List<MangaAltTitle>?)? GetMangaFromId(string publicationId)
|
private static readonly Regex GetMangaIdFromUrl = new(@"https?:\/\/mangadex\.org\/title\/([a-z0-9-]+)\/?.*");
|
||||||
|
public override Manga? GetMangaFromUrl(string url)
|
||||||
{
|
{
|
||||||
string url = $"https://api.mangadex.org/manga/{publicationId}" +
|
Log.Info($"Getting Manga: {url}");
|
||||||
$"?includes%5B%5D=manga&includes%5B%5D=cover_art&includes%5B%5D=author&includes%5B%5D=artist&includes%5B%5D=tag";
|
if (!UrlMatchesConnector(url))
|
||||||
RequestResult requestResult = downloadClient.MakeRequest(url, RequestType.MangaInfo);
|
|
||||||
if ((int)requestResult.statusCode < 200 || (int)requestResult.statusCode >= 300)
|
|
||||||
{
|
{
|
||||||
Log.Info($"{requestResult.statusCode}: {url}");
|
Log.Debug($"Url is not for Connector. {url}");
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
JsonObject? result = JsonSerializer.Deserialize<JsonObject>(requestResult.result);
|
|
||||||
if(result is not null)
|
|
||||||
return MangaFromJsonObject(result["data"]!.AsObject());
|
|
||||||
Log.Info($"result was null: {url}");
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override (Manga, List<Author>?, List<MangaTag>?, List<Link>?, List<MangaAltTitle>?)? GetMangaFromUrl(string url)
|
Match match = GetMangaIdFromUrl.Match(url);
|
||||||
{
|
if (!match.Success || !match.Groups[1].Success)
|
||||||
Regex idRex = new (@"https:\/\/mangadex.org\/title\/([A-z0-9-]*)\/.*");
|
{
|
||||||
string id = idRex.Match(url).Groups[1].Value;
|
Log.Debug($"Url is not for Connector (Could not retrieve id). {url}");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
string id = match.Groups[1].Value;
|
||||||
|
|
||||||
return GetMangaFromId(id);
|
return GetMangaFromId(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
private (Manga, List<Author>?, List<MangaTag>?, List<Link>?, List<MangaAltTitle>?)? MangaFromJsonObject(JsonObject manga)
|
public override Manga? GetMangaFromId(string mangaIdOnSite)
|
||||||
{
|
{
|
||||||
if (!manga.TryGetPropertyValue("id", out JsonNode? idNode))
|
Log.Info($"Getting Manga: {mangaIdOnSite}");
|
||||||
|
string requestUrl =
|
||||||
|
$"https://api.mangadex.org/manga/{mangaIdOnSite}" +
|
||||||
|
$"?includes%5B%5D=manga&includes%5B%5D=cover_art&includes%5B%5D=author&includes%5B%5D=artist&includes%5B%5D=tag'";
|
||||||
|
|
||||||
|
RequestResult result = downloadClient.MakeRequest(requestUrl, RequestType.MangaDexFeed);
|
||||||
|
if ((int)result.statusCode < 200 || (int)result.statusCode >= 300)
|
||||||
{
|
{
|
||||||
Log.Info("id was null");
|
Log.Error("Request failed");
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
string publicationId = idNode!.GetValue<string>();
|
|
||||||
|
|
||||||
if (!manga.TryGetPropertyValue("attributes", out JsonNode? attributesNode))
|
|
||||||
{
|
|
||||||
Log.Info("attributes was null");
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
JsonObject attributes = attributesNode!.AsObject();
|
|
||||||
|
|
||||||
if (!attributes.TryGetPropertyValue("title", out JsonNode? titleNode))
|
using StreamReader sr = new (result.result);
|
||||||
{
|
JObject jObject = JObject.Parse(sr.ReadToEnd());
|
||||||
Log.Info("title was null");
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
string sortName = titleNode!.AsObject().ContainsKey("en") switch
|
|
||||||
{
|
|
||||||
true => titleNode.AsObject()["en"]!.GetValue<string>(),
|
|
||||||
false => titleNode.AsObject().First().Value!.GetValue<string>()
|
|
||||||
};
|
|
||||||
|
|
||||||
Dictionary<string, string> altTitlesDict = new();
|
|
||||||
if (attributes.TryGetPropertyValue("altTitles", out JsonNode? altTitlesNode))
|
|
||||||
{
|
|
||||||
foreach (JsonNode? altTitleNode in altTitlesNode!.AsArray())
|
|
||||||
{
|
|
||||||
JsonObject altTitleNodeObject = altTitleNode!.AsObject();
|
|
||||||
altTitlesDict.TryAdd(altTitleNodeObject.First().Key, altTitleNodeObject.First().Value!.GetValue<string>());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
List<MangaAltTitle> altTitles = altTitlesDict.Select(t => new MangaAltTitle(t.Key, t.Value)).ToList();
|
|
||||||
|
|
||||||
if (!attributes.TryGetPropertyValue("description", out JsonNode? descriptionNode))
|
if (jObject.Value<string>("result") != "ok")
|
||||||
{
|
{
|
||||||
Log.Info("description was null");
|
JArray? errors = jObject["errors"] as JArray;
|
||||||
return null;
|
Log.Error($"Request failed: {string.Join(',', errors?.Select(e => e.Value<string>("title")) ?? [])}");
|
||||||
}
|
|
||||||
string description = descriptionNode!.AsObject().ContainsKey("en") switch
|
|
||||||
{
|
|
||||||
true => descriptionNode.AsObject()["en"]!.GetValue<string>(),
|
|
||||||
false => descriptionNode.AsObject().FirstOrDefault().Value?.GetValue<string>() ?? ""
|
|
||||||
};
|
|
||||||
|
|
||||||
Dictionary<string, string> linksDict = new();
|
|
||||||
if (attributes.TryGetPropertyValue("links", out JsonNode? linksNode) && linksNode is not null)
|
|
||||||
foreach (KeyValuePair<string, JsonNode?> linkKv in linksNode!.AsObject())
|
|
||||||
linksDict.TryAdd(linkKv.Key, linkKv.Value.GetValue<string>());
|
|
||||||
List<Link> links = linksDict.Select(x => new Link(x.Key, x.Value)).ToList();
|
|
||||||
|
|
||||||
string? originalLanguage =
|
|
||||||
attributes.TryGetPropertyValue("originalLanguage", out JsonNode? originalLanguageNode) switch
|
|
||||||
{
|
|
||||||
true => originalLanguageNode?.GetValue<string>(),
|
|
||||||
false => null
|
|
||||||
};
|
|
||||||
|
|
||||||
MangaReleaseStatus releaseStatus = MangaReleaseStatus.Unreleased;
|
|
||||||
if (attributes.TryGetPropertyValue("status", out JsonNode? statusNode))
|
|
||||||
{
|
|
||||||
releaseStatus = statusNode?.GetValue<string>().ToLower() switch
|
|
||||||
{
|
|
||||||
"ongoing" => MangaReleaseStatus.Continuing,
|
|
||||||
"completed" => MangaReleaseStatus.Completed,
|
|
||||||
"hiatus" => MangaReleaseStatus.OnHiatus,
|
|
||||||
"cancelled" => MangaReleaseStatus.Cancelled,
|
|
||||||
_ => MangaReleaseStatus.Unreleased
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
uint year = attributes.TryGetPropertyValue("year", out JsonNode? yearNode) switch
|
|
||||||
{
|
|
||||||
true => yearNode?.GetValue<uint>()??0,
|
|
||||||
false => 0
|
|
||||||
};
|
|
||||||
|
|
||||||
HashSet<string> tags = new(128);
|
|
||||||
if (attributes.TryGetPropertyValue("tags", out JsonNode? tagsNode))
|
|
||||||
foreach (JsonNode? tagNode in tagsNode!.AsArray())
|
|
||||||
tags.Add(tagNode!["attributes"]!["name"]!["en"]!.GetValue<string>());
|
|
||||||
List<MangaTag> mangaTags = tags.Select(t => new MangaTag(t)).ToList();
|
|
||||||
|
|
||||||
if (!manga.TryGetPropertyValue("relationships", out JsonNode? relationshipsNode))
|
|
||||||
{
|
|
||||||
Log.Info("relationships was null");
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
JsonNode? coverNode = relationshipsNode!.AsArray()
|
JObject? data = jObject["data"] as JObject;
|
||||||
.FirstOrDefault(rel => rel!["type"]!.GetValue<string>().Equals("cover_art"));
|
if (data is null)
|
||||||
if (coverNode is null)
|
|
||||||
{
|
{
|
||||||
Log.Info("coverNode was null");
|
Log.Error("Data was null");
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
string fileName = coverNode["attributes"]!["fileName"]!.GetValue<string>();
|
|
||||||
string coverUrl = $"https://uploads.mangadex.org/covers/{publicationId}/{fileName}";
|
|
||||||
|
|
||||||
List<string> authorNames = new();
|
|
||||||
JsonNode?[] authorNodes = relationshipsNode.AsArray()
|
|
||||||
.Where(rel => rel!["type"]!.GetValue<string>().Equals("author") || rel!["type"]!.GetValue<string>().Equals("artist")).ToArray();
|
|
||||||
foreach (JsonNode? authorNode in authorNodes)
|
|
||||||
{
|
|
||||||
string authorName = authorNode!["attributes"]!["name"]!.GetValue<string>();
|
|
||||||
if(!authorNames.Contains(authorName))
|
|
||||||
authorNames.Add(authorName);
|
|
||||||
}
|
|
||||||
List<Author> authors = authorNames.Select(a => new Author(a)).ToList();
|
|
||||||
|
|
||||||
Manga pub = new (publicationId, sortName, description, $"https://mangadex.org/title/{publicationId}", coverUrl, null, year,
|
Manga manga = ParseMangaFromJToken(data);
|
||||||
originalLanguage, releaseStatus, -1,
|
return manga;
|
||||||
this,
|
|
||||||
authors,
|
|
||||||
mangaTags,
|
|
||||||
links,
|
|
||||||
altTitles);
|
|
||||||
|
|
||||||
return (pub, authors, mangaTags, links, altTitles);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public override Chapter[] GetChapters(Manga manga, string language="en")
|
public override Chapter[] GetChapters(Manga manga, string? language = null)
|
||||||
{
|
{
|
||||||
const int limit = 100; //How many values we want returned at once
|
Log.Info($"Getting Chapters: {manga.IdOnConnectorSite}");
|
||||||
int offset = 0; //"Page"
|
List<Chapter> chapters = new ();
|
||||||
int total = int.MaxValue; //How many total results are there, is updated on first request
|
|
||||||
List<Chapter> chapters = new();
|
int offset = 0;
|
||||||
//As long as we haven't requested all "Pages"
|
int total = int.MaxValue;
|
||||||
while (offset < total)
|
while(offset < total)
|
||||||
{
|
{
|
||||||
//Request next "Page"
|
string requestUrl =
|
||||||
string requestUrl = $"https://api.mangadex.org/manga/{manga.IdOnConnectorSite}/feed?limit={limit}&offset={offset}&translatedLanguage%5B%5D={language}" +
|
$"https://api.mangadex.org/manga/{manga.IdOnConnectorSite}/feed?limit={Limit}&offset={offset}&" +
|
||||||
$"&contentRating%5B%5D=safe&contentRating%5B%5D=suggestive&contentRating%5B%5D=erotica&contentRating%5B%5D=pornographic";
|
$"translatedLanguage%5B%5D={language}&" +
|
||||||
RequestResult requestResult = downloadClient.MakeRequest(requestUrl, RequestType.MangaDexFeed);
|
$"contentRating%5B%5D=safe&contentRating%5B%5D=suggestive&contentRating%5B%5D=erotica&includeFutureUpdates=0&includes%5B%5D=";
|
||||||
if ((int)requestResult.statusCode < 200 || (int)requestResult.statusCode >= 300)
|
offset += Limit;
|
||||||
{
|
|
||||||
Log.Info($"{requestResult.statusCode}: {requestUrl}");
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
JsonObject? result = JsonSerializer.Deserialize<JsonObject>(requestResult.result);
|
|
||||||
|
|
||||||
offset += limit;
|
|
||||||
if (result is null)
|
|
||||||
{
|
|
||||||
Log.Info($"result was null: {requestUrl}");
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
total = result["total"]!.GetValue<int>();
|
|
||||||
JsonArray chaptersInResult = result["data"]!.AsArray();
|
|
||||||
//Loop through all Chapters in result and extract information from JSON
|
|
||||||
foreach (JsonNode? jsonNode in chaptersInResult)
|
|
||||||
{
|
|
||||||
JsonObject chapter = (JsonObject)jsonNode!;
|
|
||||||
JsonObject attributes = chapter["attributes"]!.AsObject();
|
|
||||||
|
|
||||||
string chapterId = chapter["id"]!.GetValue<string>();
|
|
||||||
string url = $"https://mangadex.org/chapter/{chapterId}";
|
|
||||||
|
|
||||||
string? title = attributes.ContainsKey("title") && attributes["title"] is not null
|
|
||||||
? attributes["title"]!.GetValue<string>()
|
|
||||||
: null;
|
|
||||||
|
|
||||||
int? volume = attributes.ContainsKey("volume") && attributes["volume"] is not null
|
|
||||||
? int.Parse(attributes["volume"]!.GetValue<string>())
|
|
||||||
: null;
|
|
||||||
|
|
||||||
string? chapterNumStr = attributes.ContainsKey("chapter") && attributes["chapter"] is not null
|
|
||||||
? attributes["chapter"]!.GetValue<string>()
|
|
||||||
: null;
|
|
||||||
|
|
||||||
string chapterNumber = new(chapterNumStr);
|
|
||||||
|
|
||||||
|
|
||||||
if (attributes.ContainsKey("pages") && attributes["pages"] is not null &&
|
|
||||||
attributes["pages"]!.GetValue<int>() < 1)
|
|
||||||
{
|
|
||||||
Log.Info($"No pages: {chapterId}");
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
Chapter newChapter = new(manga, url, chapterNumber, volume, title);
|
|
||||||
if(!chapters.Contains(newChapter))
|
|
||||||
chapters.Add(newChapter);
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
Log.Debug(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//Return Chapters ordered by Chapter-Number
|
RequestResult result = downloadClient.MakeRequest(requestUrl, RequestType.MangaDexFeed);
|
||||||
|
if ((int)result.statusCode < 200 || (int)result.statusCode >= 300)
|
||||||
|
{
|
||||||
|
Log.Error("Request failed");
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
using StreamReader sr = new (result.result);
|
||||||
|
JObject jObject = JObject.Parse(sr.ReadToEnd());
|
||||||
|
|
||||||
|
if (jObject.Value<string>("result") != "ok")
|
||||||
|
{
|
||||||
|
JArray? errors = jObject["errors"] as JArray;
|
||||||
|
Log.Error($"Request failed: {string.Join(',', errors?.Select(e => e.Value<string>("title")) ?? [])}");
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
total = jObject.Value<int>("total");
|
||||||
|
|
||||||
|
JArray? data = jObject.Value<JArray>("data");
|
||||||
|
if (data is null)
|
||||||
|
{
|
||||||
|
Log.Error("Data was null");
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
chapters.AddRange(data.Select(d => ParseChapterFromJToken(manga, d)));
|
||||||
|
}
|
||||||
|
|
||||||
|
Log.Info($"Request for chapters for {manga.Name} yielded {chapters.Count} results.");
|
||||||
return chapters.ToArray();
|
return chapters.ToArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static readonly Regex GetChapterIdFromUrl = new(@"https?:\/\/mangadex\.org\/chapter\/([a-z0-9-]+)\/?.*");
|
||||||
internal override string[] GetChapterImageUrls(Chapter chapter)
|
internal override string[] GetChapterImageUrls(Chapter chapter)
|
||||||
{//Request URLs for Chapter-Images
|
{
|
||||||
Match m = Regex.Match(chapter.Url, @"https?:\/\/mangadex.org\/chapter\/([0-9\-a-z]+)");
|
Log.Info($"Getting Chapter Image-Urls: {chapter.Url}");
|
||||||
if (!m.Success)
|
if (!UrlMatchesConnector(chapter.Url))
|
||||||
{
|
{
|
||||||
Log.Error($"Could not parse Chapter ID: {chapter.Url}");
|
Log.Debug($"Url is not for Connector. {chapter.Url}");
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
string url = $"https://api.mangadex.org/at-home/server/{m.Groups[1].Value}?forcePort443=false";
|
Match match = GetChapterIdFromUrl.Match(chapter.Url);
|
||||||
RequestResult requestResult =
|
if (!match.Success || !match.Groups[1].Success)
|
||||||
downloadClient.MakeRequest(url, RequestType.MangaDexImage);
|
|
||||||
if ((int)requestResult.statusCode < 200 || (int)requestResult.statusCode >= 300)
|
|
||||||
{
|
{
|
||||||
Log.Info($"{requestResult.statusCode}: {url}");
|
Log.Debug($"Url is not for Connector (Could not retrieve id). {chapter.Url}");
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
JsonObject? result = JsonSerializer.Deserialize<JsonObject>(requestResult.result);
|
|
||||||
if (result is null)
|
string id = match.Groups[1].Value;
|
||||||
|
string requestUrl = $"https://api.mangadex.org/at-home/server/{id}";
|
||||||
|
|
||||||
|
RequestResult result = downloadClient.MakeRequest(requestUrl, RequestType.Default);
|
||||||
|
if ((int)result.statusCode < 200 || (int)result.statusCode >= 300)
|
||||||
{
|
{
|
||||||
Log.Info($"Result was null: {url}");
|
Log.Error("Request failed");
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
string baseUrl = result["baseUrl"]!.GetValue<string>();
|
|
||||||
string hash = result["chapter"]!["hash"]!.GetValue<string>();
|
using StreamReader sr = new (result.result);
|
||||||
JsonArray imageFileNames = result["chapter"]!["data"]!.AsArray();
|
JObject jObject = JObject.Parse(sr.ReadToEnd());
|
||||||
//Loop through all imageNames and construct urls (imageUrl)
|
|
||||||
List<string> imageUrls = new();
|
if (jObject.Value<string>("result") != "ok")
|
||||||
foreach (JsonNode? image in imageFileNames)
|
{
|
||||||
imageUrls.Add($"{baseUrl}/data/{hash}/{image!.GetValue<string>()}");
|
JArray? errors = jObject["errors"] as JArray;
|
||||||
return imageUrls.ToArray();
|
Log.Error($"Request failed: {string.Join(',', errors?.Select(e => e.Value<string>("title")) ?? [])}");
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
string? baseUrl = jObject.Value<string>("baseUrl");
|
||||||
|
JToken? chapterToken = jObject["chapter"];
|
||||||
|
string? hash = chapterToken?.Value<string>("hash");
|
||||||
|
JArray? data = chapterToken?["data"] as JArray;
|
||||||
|
|
||||||
|
if (baseUrl is null || hash is null || data is null)
|
||||||
|
{
|
||||||
|
Log.Error("Data was null");
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
IEnumerable<string> urls = data.Select(t => $"{baseUrl}/data/{hash}/{t.Value<string>()}");
|
||||||
|
|
||||||
|
return urls.ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
private Manga ParseMangaFromJToken(JToken jToken)
|
||||||
|
{
|
||||||
|
string? id = jToken.Value<string>("id");
|
||||||
|
|
||||||
|
JObject? attributes = jToken["attributes"] as JObject;
|
||||||
|
string? name = attributes?["title"]?.Value<string>("en");
|
||||||
|
string? description = attributes?["description"]?.Value<string>("en");
|
||||||
|
string? status = attributes?["status"]?.Value<string>();
|
||||||
|
uint? year = attributes?["year"]?.Value<uint>();
|
||||||
|
string? originalLanguage = attributes?["originalLanguage"]?.Value<string>();
|
||||||
|
JArray? altTitlesJArray = attributes?["altTitles"] as JArray;
|
||||||
|
JArray? tagsJArray = attributes?["tags"] as JArray;
|
||||||
|
|
||||||
|
JArray? relationships = jToken["relationships"] as JArray;
|
||||||
|
string? coverFileName =
|
||||||
|
relationships?.FirstOrDefault(r => r["type"]?.Value<string>() == "cover_art")?["attributes"]?.Value<string>("fileName");
|
||||||
|
|
||||||
|
if (id is null || attributes is null || name is null || description is null || status is null ||
|
||||||
|
altTitlesJArray is null || tagsJArray is null || relationships is null || coverFileName is null)
|
||||||
|
throw new Exception("jToken was not in expected format");
|
||||||
|
|
||||||
|
List<Link> links = attributes["links"]?
|
||||||
|
.ToObject<Dictionary<string,string>>()?
|
||||||
|
.Select(kv =>
|
||||||
|
{
|
||||||
|
//https://api.mangadex.org/docs/3-enumerations/#manga-links-data
|
||||||
|
string url = kv.Key switch
|
||||||
|
{
|
||||||
|
"al" => $"https://anilist.co/manga/{kv.Value}",
|
||||||
|
"ap" => $"https://www.anime-planet.com/manga/{kv.Value}",
|
||||||
|
"bw" => $"https://bookwalker.jp/{kv.Value}",
|
||||||
|
"mu" => $"https://www.mangaupdates.com/series.html?id={kv.Value}",
|
||||||
|
"nu" => $"https://www.novelupdates.com/series/{kv.Value}",
|
||||||
|
"mal" => $"https://myanimelist.net/manga/{kv.Value}",
|
||||||
|
_ => kv.Value
|
||||||
|
};
|
||||||
|
string key = kv.Key switch
|
||||||
|
{
|
||||||
|
"al" => "AniList",
|
||||||
|
"ap" => "Anime Planet",
|
||||||
|
"bw" => "BookWalker",
|
||||||
|
"mu" => "Manga Updates",
|
||||||
|
"nu" => "Novel Updates",
|
||||||
|
"kt" => "Kitsu.io",
|
||||||
|
"amz" => "Amazon",
|
||||||
|
"ebj" => "eBookJapan",
|
||||||
|
"mal" => "MyAnimeList",
|
||||||
|
"cdj" => "CDJapan",
|
||||||
|
_ => kv.Key
|
||||||
|
};
|
||||||
|
return new Link(key, url);
|
||||||
|
}).ToList()!;
|
||||||
|
|
||||||
|
List<MangaAltTitle> altTitles = altTitlesJArray
|
||||||
|
.Select(t =>
|
||||||
|
{
|
||||||
|
JObject? j = t as JObject;
|
||||||
|
JProperty? p = j?.Properties().First();
|
||||||
|
if (p is null)
|
||||||
|
return null;
|
||||||
|
return new MangaAltTitle(p.Name, p.Value.ToString());
|
||||||
|
}).Where(x => x is not null).ToList()!;
|
||||||
|
|
||||||
|
List<MangaTag> tags = tagsJArray
|
||||||
|
.Where(t => t.Value<string>("type") == "tag")
|
||||||
|
.Select(t => t["attributes"]?["name"]?.Value<string>("en"))
|
||||||
|
.Select(str => str is not null ? new MangaTag(str) : null)
|
||||||
|
.Where(x => x is not null).ToList()!;
|
||||||
|
|
||||||
|
List<Author> authors = relationships
|
||||||
|
.Where(r => r["type"]?.Value<string>() == "author")
|
||||||
|
.Select(t => t["attributes"]?.Value<string>("name"))
|
||||||
|
.Select(str => str is not null ? new Author(str) : null)
|
||||||
|
.Where(x => x is not null).ToList()!;
|
||||||
|
|
||||||
|
|
||||||
|
MangaReleaseStatus releaseStatus = status switch
|
||||||
|
{
|
||||||
|
"completed" => MangaReleaseStatus.Completed,
|
||||||
|
"ongoing" => MangaReleaseStatus.Continuing,
|
||||||
|
"cancelled" => MangaReleaseStatus.Cancelled,
|
||||||
|
"hiatus" => MangaReleaseStatus.OnHiatus,
|
||||||
|
_ => MangaReleaseStatus.Unreleased
|
||||||
|
};
|
||||||
|
string websiteUrl = $"https://mangadex.org/title/{id}";
|
||||||
|
string coverUrl = $"https://uploads.mangadex.org/covers/{id}/{coverFileName}";
|
||||||
|
|
||||||
|
return new Manga(id, name, description, websiteUrl, coverUrl, releaseStatus, this,
|
||||||
|
authors, tags, links,altTitles,
|
||||||
|
null, 0f, year, originalLanguage);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Chapter ParseChapterFromJToken(Manga parentManga, JToken jToken)
|
||||||
|
{
|
||||||
|
string? id = jToken.Value<string>("id");
|
||||||
|
JToken? attributes = jToken["attributes"];
|
||||||
|
string? chapter = attributes?.Value<string>("chapter");
|
||||||
|
string? volumeStr = attributes?.Value<string>("volume");
|
||||||
|
int? volume = null;
|
||||||
|
string? title = attributes?.Value<string>("title");
|
||||||
|
|
||||||
|
if(id is null || chapter is null)
|
||||||
|
throw new Exception("jToken was not in expected format");
|
||||||
|
if(volumeStr is not null)
|
||||||
|
volume = int.Parse(volumeStr);
|
||||||
|
|
||||||
|
string url = $"https://mangadex.org/chapter/{id}";
|
||||||
|
return new Chapter(parentManga, url, chapter, volume, title);
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,183 +0,0 @@
|
|||||||
using System.Text.RegularExpressions;
|
|
||||||
using API.MangaDownloadClients;
|
|
||||||
using HtmlAgilityPack;
|
|
||||||
|
|
||||||
namespace API.Schema.MangaConnectors;
|
|
||||||
|
|
||||||
public class MangaHere : MangaConnector
|
|
||||||
{
|
|
||||||
public MangaHere() : base("MangaHere", ["en"], ["www.mangahere.cc"], "http://www.mangahere.cc/favicon.ico")
|
|
||||||
{
|
|
||||||
this.downloadClient = new ChromiumDownloadClient();
|
|
||||||
}
|
|
||||||
|
|
||||||
public override (Manga, List<Author>?, List<MangaTag>?, List<Link>?, List<MangaAltTitle>?)[] GetManga(string publicationTitle = "")
|
|
||||||
{
|
|
||||||
string sanitizedTitle = string.Join('+', Regex.Matches(publicationTitle, "[A-z]*").Where(str => str.Length > 0)).ToLower();
|
|
||||||
string requestUrl = $"https://www.mangahere.cc/search?title={sanitizedTitle}";
|
|
||||||
RequestResult requestResult =
|
|
||||||
downloadClient.MakeRequest(requestUrl, RequestType.Default);
|
|
||||||
if ((int)requestResult.statusCode < 200 || (int)requestResult.statusCode >= 300 || requestResult.htmlDocument is null)
|
|
||||||
return [];
|
|
||||||
|
|
||||||
(Manga, List<Author>?, List<MangaTag>?, List<Link>?, List<MangaAltTitle>?)[] publications = ParsePublicationsFromHtml(requestResult.htmlDocument);
|
|
||||||
return publications;
|
|
||||||
}
|
|
||||||
|
|
||||||
private (Manga, List<Author>?, List<MangaTag>?, List<Link>?, List<MangaAltTitle>?)[] ParsePublicationsFromHtml(HtmlDocument document)
|
|
||||||
{
|
|
||||||
if (document.DocumentNode.SelectNodes("//div[contains(concat(' ',normalize-space(@class),' '),' container ')]").Any(node => node.ChildNodes.Any(cNode => cNode.HasClass("search-keywords"))))
|
|
||||||
return [];
|
|
||||||
|
|
||||||
List<string> urls = document.DocumentNode
|
|
||||||
.SelectNodes("//a[contains(@href, '/manga/') and not(contains(@href, '.html'))]")
|
|
||||||
.Select(thumb => $"https://www.mangahere.cc{thumb.GetAttributeValue("href", "")}").Distinct().ToList();
|
|
||||||
|
|
||||||
HashSet<(Manga, List<Author>?, List<MangaTag>?, List<Link>?, List<MangaAltTitle>?)> ret = new();
|
|
||||||
foreach (string url in urls)
|
|
||||||
{
|
|
||||||
(Manga, List<Author>?, List<MangaTag>?, List<Link>?, List<MangaAltTitle>?)? manga = GetMangaFromUrl(url);
|
|
||||||
if (manga is { } x)
|
|
||||||
ret.Add(x);
|
|
||||||
}
|
|
||||||
|
|
||||||
return ret.ToArray();
|
|
||||||
}
|
|
||||||
|
|
||||||
public override (Manga, List<Author>?, List<MangaTag>?, List<Link>?, List<MangaAltTitle>?)? GetMangaFromId(string publicationId)
|
|
||||||
{
|
|
||||||
return GetMangaFromUrl($"https://www.mangahere.cc/manga/{publicationId}");
|
|
||||||
}
|
|
||||||
|
|
||||||
public override (Manga, List<Author>?, List<MangaTag>?, List<Link>?, List<MangaAltTitle>?)? GetMangaFromUrl(string url)
|
|
||||||
{
|
|
||||||
RequestResult requestResult =
|
|
||||||
downloadClient.MakeRequest(url, RequestType.MangaInfo);
|
|
||||||
if ((int)requestResult.statusCode < 200 || (int)requestResult.statusCode >= 300 || requestResult.htmlDocument is null)
|
|
||||||
return null;
|
|
||||||
|
|
||||||
Regex idRex = new (@"https:\/\/www\.mangahere\.[a-z]{0,63}\/manga\/([0-9A-z\-]+).*");
|
|
||||||
string id = idRex.Match(url).Groups[1].Value;
|
|
||||||
return ParseSinglePublicationFromHtml(requestResult.htmlDocument, id, url);
|
|
||||||
}
|
|
||||||
|
|
||||||
private (Manga, List<Author>?, List<MangaTag>?, List<Link>?, List<MangaAltTitle>?) ParseSinglePublicationFromHtml(HtmlDocument document, string publicationId, string websiteUrl)
|
|
||||||
{
|
|
||||||
string originalLanguage = "", status = "";
|
|
||||||
Dictionary<string, string> altTitles = new(), links = new();
|
|
||||||
MangaReleaseStatus releaseStatus = MangaReleaseStatus.Unreleased;
|
|
||||||
|
|
||||||
//We dont get posters, because same origin bs HtmlNode posterNode = document.DocumentNode.SelectSingleNode("//img[contains(concat(' ',normalize-space(@class),' '),' detail-info-cover-img ')]");
|
|
||||||
string coverUrl = "http://static.mangahere.cc/v20230914/mangahere/images/nopicture.jpg";
|
|
||||||
|
|
||||||
HtmlNode titleNode = document.DocumentNode.SelectSingleNode("//span[contains(concat(' ',normalize-space(@class),' '),' detail-info-right-title-font ')]");
|
|
||||||
string sortName = titleNode.InnerText;
|
|
||||||
|
|
||||||
List<string> authorNames = document.DocumentNode
|
|
||||||
.SelectNodes("//p[contains(concat(' ',normalize-space(@class),' '),' detail-info-right-say ')]/a")
|
|
||||||
.Select(node => node.InnerText)
|
|
||||||
.ToList();
|
|
||||||
List<Author> authors = authorNames.Select(n => new Author(n)).ToList();
|
|
||||||
|
|
||||||
HashSet<string> tags = document.DocumentNode
|
|
||||||
.SelectNodes("//p[contains(concat(' ',normalize-space(@class),' '),' detail-info-right-tag-list ')]/a")
|
|
||||||
.Select(node => node.InnerText)
|
|
||||||
.ToHashSet();
|
|
||||||
List<MangaTag> mangaTags = tags.Select(n => new MangaTag(n)).ToList();
|
|
||||||
|
|
||||||
status = document.DocumentNode.SelectSingleNode("//span[contains(concat(' ',normalize-space(@class),' '),' detail-info-right-title-tip ')]").InnerText;
|
|
||||||
switch (status.ToLower())
|
|
||||||
{
|
|
||||||
case "cancelled": releaseStatus = MangaReleaseStatus.Cancelled; break;
|
|
||||||
case "hiatus": releaseStatus = MangaReleaseStatus.OnHiatus; break;
|
|
||||||
case "discontinued": releaseStatus = MangaReleaseStatus.Cancelled; break;
|
|
||||||
case "complete": releaseStatus = MangaReleaseStatus.Completed; break;
|
|
||||||
case "ongoing": releaseStatus = MangaReleaseStatus.Continuing; break;
|
|
||||||
}
|
|
||||||
|
|
||||||
HtmlNode descriptionNode = document.DocumentNode
|
|
||||||
.SelectSingleNode("//p[contains(concat(' ',normalize-space(@class),' '),' fullcontent ')]");
|
|
||||||
string description = descriptionNode.InnerText;
|
|
||||||
|
|
||||||
Manga manga = new (publicationId, sortName, description, websiteUrl, coverUrl, null, 0,
|
|
||||||
originalLanguage, releaseStatus, -1,
|
|
||||||
this,
|
|
||||||
authors,
|
|
||||||
mangaTags,
|
|
||||||
[],
|
|
||||||
[]);
|
|
||||||
|
|
||||||
return (manga, authors, mangaTags, [], []);
|
|
||||||
}
|
|
||||||
|
|
||||||
public override Chapter[] GetChapters(Manga manga, string language="en")
|
|
||||||
{
|
|
||||||
string requestUrl = $"https://www.mangahere.cc/manga/{manga.MangaId}";
|
|
||||||
RequestResult requestResult =
|
|
||||||
downloadClient.MakeRequest(requestUrl, RequestType.Default);
|
|
||||||
if ((int)requestResult.statusCode < 200 || (int)requestResult.statusCode >= 300 || requestResult.htmlDocument is null)
|
|
||||||
return Array.Empty<Chapter>();
|
|
||||||
|
|
||||||
List<string> urls = requestResult.htmlDocument.DocumentNode.SelectNodes("//div[@id='list-1']/ul//li//a[contains(@href, '/manga/')]")
|
|
||||||
.Select(node => node.GetAttributeValue("href", "")).ToList();
|
|
||||||
Regex chapterRex = new(@".*\/manga\/[a-zA-Z0-9\-\._\~\!\$\&\'\(\)\*\+\,\;\=\:\@]+\/v([0-9(TBD)]+)\/c([0-9\.]+)\/.*");
|
|
||||||
|
|
||||||
List<Chapter> chapters = new();
|
|
||||||
foreach (string url in urls)
|
|
||||||
{
|
|
||||||
Match rexMatch = chapterRex.Match(url);
|
|
||||||
|
|
||||||
int? volumeNumber = rexMatch.Groups[1].Value == "TBD" ? null : int.Parse(rexMatch.Groups[1].Value);
|
|
||||||
string chapterNumber = new(rexMatch.Groups[2].Value);
|
|
||||||
string fullUrl = $"https://www.mangahere.cc{url}";
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
chapters.Add(new Chapter(manga, fullUrl, chapterNumber, volumeNumber, null));
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
|
||||||
//Return Chapters ordered by Chapter-Number
|
|
||||||
return chapters.Order().ToArray();
|
|
||||||
}
|
|
||||||
|
|
||||||
internal override string[] GetChapterImageUrls(Chapter chapter)
|
|
||||||
{
|
|
||||||
List<string> imageUrls = new();
|
|
||||||
|
|
||||||
int downloaded = 1;
|
|
||||||
int images = 1;
|
|
||||||
string url = string.Join('/', chapter.Url.Split('/')[..^1]);
|
|
||||||
do
|
|
||||||
{
|
|
||||||
RequestResult requestResult =
|
|
||||||
downloadClient.MakeRequest($"{url}/{downloaded}.html", RequestType.Default);
|
|
||||||
if ((int)requestResult.statusCode < 200 || (int)requestResult.statusCode >= 300 || requestResult.htmlDocument is null)
|
|
||||||
{
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
imageUrls.AddRange(ParseImageUrlsFromHtml(requestResult.htmlDocument));
|
|
||||||
|
|
||||||
images = requestResult.htmlDocument.DocumentNode
|
|
||||||
.SelectNodes("//a[contains(@href, '/manga/')]")
|
|
||||||
.MaxBy(node => node.GetAttributeValue("data-page", 0))!.GetAttributeValue("data-page", 0);
|
|
||||||
} while (downloaded++ <= images);
|
|
||||||
|
|
||||||
return imageUrls.ToArray();
|
|
||||||
}
|
|
||||||
|
|
||||||
private string[] ParseImageUrlsFromHtml(HtmlDocument document)
|
|
||||||
{
|
|
||||||
return document.DocumentNode
|
|
||||||
.SelectNodes("//img[contains(concat(' ',normalize-space(@class),' '),' reader-main-img ')]")
|
|
||||||
.Select(node =>
|
|
||||||
{
|
|
||||||
string url = node.GetAttributeValue("src", "");
|
|
||||||
return url.StartsWith("//") ? $"https:{url}" : url;
|
|
||||||
})
|
|
||||||
.ToArray();
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,233 +0,0 @@
|
|||||||
using System.Text.RegularExpressions;
|
|
||||||
using API.MangaDownloadClients;
|
|
||||||
using HtmlAgilityPack;
|
|
||||||
|
|
||||||
namespace API.Schema.MangaConnectors;
|
|
||||||
|
|
||||||
public class MangaKatana : MangaConnector
|
|
||||||
{
|
|
||||||
public MangaKatana() : base("MangaKatana", ["en"], ["mangakatana.com"], "https://mangakatana.com/static/img/fav.png")
|
|
||||||
{
|
|
||||||
this.downloadClient = new HttpDownloadClient();
|
|
||||||
}
|
|
||||||
|
|
||||||
public override (Manga, List<Author>?, List<MangaTag>?, List<Link>?, List<MangaAltTitle>?)[] GetManga(string publicationTitle = "")
|
|
||||||
{
|
|
||||||
string sanitizedTitle = string.Join("%20", Regex.Matches(publicationTitle, "[A-z]*").Where(m => m.Value.Length > 0)).ToLower();
|
|
||||||
string requestUrl = $"https://mangakatana.com/?search={sanitizedTitle}&search_by=book_name";
|
|
||||||
RequestResult requestResult =
|
|
||||||
downloadClient.MakeRequest(requestUrl, RequestType.Default);
|
|
||||||
if ((int)requestResult.statusCode < 200 || (int)requestResult.statusCode >= 300)
|
|
||||||
return [];
|
|
||||||
|
|
||||||
// ReSharper disable once MergeIntoPattern
|
|
||||||
// If a single result is found, the user will be redirected to the results directly instead of a result page
|
|
||||||
if(requestResult.hasBeenRedirected
|
|
||||||
&& requestResult.redirectedToUrl is not null
|
|
||||||
&& requestResult.redirectedToUrl.Contains("mangakatana.com/manga"))
|
|
||||||
{
|
|
||||||
return new [] { ParseSinglePublicationFromHtml(requestResult.result, requestResult.redirectedToUrl.Split('/')[^1], requestResult.redirectedToUrl) };
|
|
||||||
}
|
|
||||||
|
|
||||||
(Manga, List<Author>?, List<MangaTag>?, List<Link>?, List<MangaAltTitle>?)[] publications = ParsePublicationsFromHtml(requestResult.result);
|
|
||||||
return publications;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override (Manga, List<Author>?, List<MangaTag>?, List<Link>?, List<MangaAltTitle>?)? GetMangaFromId(string publicationId)
|
|
||||||
{
|
|
||||||
return GetMangaFromUrl($"https://mangakatana.com/manga/{publicationId}");
|
|
||||||
}
|
|
||||||
|
|
||||||
public override (Manga, List<Author>?, List<MangaTag>?, List<Link>?, List<MangaAltTitle>?)? GetMangaFromUrl(string url)
|
|
||||||
{
|
|
||||||
RequestResult requestResult =
|
|
||||||
downloadClient.MakeRequest(url, RequestType.MangaInfo);
|
|
||||||
if ((int)requestResult.statusCode < 200 || (int)requestResult.statusCode >= 300)
|
|
||||||
return null;
|
|
||||||
return ParseSinglePublicationFromHtml(requestResult.result, url.Split('/')[^1], url);
|
|
||||||
}
|
|
||||||
|
|
||||||
private (Manga, List<Author>?, List<MangaTag>?, List<Link>?, List<MangaAltTitle>?)[] ParsePublicationsFromHtml(Stream html)
|
|
||||||
{
|
|
||||||
StreamReader reader = new(html);
|
|
||||||
string htmlString = reader.ReadToEnd();
|
|
||||||
HtmlDocument document = new();
|
|
||||||
document.LoadHtml(htmlString);
|
|
||||||
IEnumerable<HtmlNode> searchResults = document.DocumentNode.SelectNodes("//*[@id='book_list']/div");
|
|
||||||
if (searchResults is null || !searchResults.Any())
|
|
||||||
return [];
|
|
||||||
List<string> urls = new();
|
|
||||||
foreach (HtmlNode mangaResult in searchResults)
|
|
||||||
{
|
|
||||||
urls.Add(mangaResult.Descendants("a").First().GetAttributes()
|
|
||||||
.First(a => a.Name == "href").Value);
|
|
||||||
}
|
|
||||||
|
|
||||||
HashSet<(Manga, List<Author>?, List<MangaTag>?, List<Link>?, List<MangaAltTitle>?)> ret = new();
|
|
||||||
foreach (string url in urls)
|
|
||||||
{
|
|
||||||
(Manga, List<Author>?, List<MangaTag>?, List<Link>?, List<MangaAltTitle>?)? manga = GetMangaFromUrl(url);
|
|
||||||
if (manga is { } x)
|
|
||||||
ret.Add(x);
|
|
||||||
}
|
|
||||||
|
|
||||||
return ret.ToArray();
|
|
||||||
}
|
|
||||||
|
|
||||||
private (Manga, List<Author>?, List<MangaTag>?, List<Link>?, List<MangaAltTitle>?) ParseSinglePublicationFromHtml(Stream html, string publicationId, string websiteUrl)
|
|
||||||
{
|
|
||||||
StreamReader reader = new(html);
|
|
||||||
string htmlString = reader.ReadToEnd();
|
|
||||||
HtmlDocument document = new();
|
|
||||||
document.LoadHtml(htmlString);
|
|
||||||
Dictionary<string, string> altTitlesDict = new();
|
|
||||||
Dictionary<string, string>? links = null;
|
|
||||||
HashSet<string> tags = new();
|
|
||||||
string[] authorNames = [];
|
|
||||||
string originalLanguage = "";
|
|
||||||
MangaReleaseStatus releaseStatus = MangaReleaseStatus.Unreleased;
|
|
||||||
|
|
||||||
HtmlNode infoNode = document.DocumentNode.SelectSingleNode("//*[@id='single_book']");
|
|
||||||
string sortName = infoNode.Descendants("h1").First(n => n.HasClass("heading")).InnerText;
|
|
||||||
HtmlNode infoTable = infoNode.SelectSingleNode("//*[@id='single_book']/div[2]/div/ul");
|
|
||||||
|
|
||||||
foreach (HtmlNode row in infoTable.Descendants("li"))
|
|
||||||
{
|
|
||||||
string key = row.SelectNodes("div").First().InnerText.ToLower();
|
|
||||||
string value = row.SelectNodes("div").Last().InnerText;
|
|
||||||
string keySanitized = string.Concat(Regex.Matches(key, "[a-z]"));
|
|
||||||
|
|
||||||
switch (keySanitized)
|
|
||||||
{
|
|
||||||
case "altnames":
|
|
||||||
string[] alts = value.Split(" ; ");
|
|
||||||
for (int i = 0; i < alts.Length; i++)
|
|
||||||
altTitlesDict.Add(i.ToString(), alts[i]);
|
|
||||||
break;
|
|
||||||
case "authorsartists":
|
|
||||||
authorNames = value.Split(',');
|
|
||||||
break;
|
|
||||||
case "status":
|
|
||||||
switch (value.ToLower())
|
|
||||||
{
|
|
||||||
case "ongoing": releaseStatus = MangaReleaseStatus.Continuing; break;
|
|
||||||
case "completed": releaseStatus = MangaReleaseStatus.Completed; break;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case "genres":
|
|
||||||
tags = row.SelectNodes("div").Last().Descendants("a").Select(a => a.InnerText).ToHashSet();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
string coverUrl = document.DocumentNode.SelectSingleNode("//*[@id='single_book']/div[1]/div").Descendants("img").First()
|
|
||||||
.GetAttributes().First(a => a.Name == "src").Value;
|
|
||||||
|
|
||||||
string description = document.DocumentNode.SelectSingleNode("//*[@id='single_book']/div[3]/p").InnerText;
|
|
||||||
while (description.StartsWith('\n'))
|
|
||||||
description = description.Substring(1);
|
|
||||||
|
|
||||||
uint year = (uint)DateTime.UtcNow.Year;
|
|
||||||
string yearString = infoTable.Descendants("div").First(d => d.HasClass("updateAt"))
|
|
||||||
.InnerText.Split('-')[^1];
|
|
||||||
|
|
||||||
if(yearString.Contains("ago") == false)
|
|
||||||
{
|
|
||||||
year = uint.Parse(yearString);
|
|
||||||
}
|
|
||||||
List<Author> authors = authorNames.Select(n => new Author(n)).ToList();
|
|
||||||
List<MangaTag> mangaTags = tags.Select(n => new MangaTag(n)).ToList();
|
|
||||||
List<MangaAltTitle> altTitles = altTitlesDict.Select(x => new MangaAltTitle(x.Key, x.Value)).ToList();
|
|
||||||
|
|
||||||
Manga manga = new (publicationId, sortName, description, websiteUrl, coverUrl, null, year,
|
|
||||||
originalLanguage, releaseStatus, -1,
|
|
||||||
this,
|
|
||||||
authors,
|
|
||||||
mangaTags,
|
|
||||||
[],
|
|
||||||
altTitles);
|
|
||||||
|
|
||||||
return (manga, authors, mangaTags, [], altTitles);
|
|
||||||
}
|
|
||||||
|
|
||||||
public override Chapter[] GetChapters(Manga manga, string language="en")
|
|
||||||
{
|
|
||||||
string requestUrl = $"https://mangakatana.com/manga/{manga.MangaId}";
|
|
||||||
// Leaving this in for verification if the page exists
|
|
||||||
RequestResult requestResult =
|
|
||||||
downloadClient.MakeRequest(requestUrl, RequestType.Default);
|
|
||||||
if ((int)requestResult.statusCode < 200 || (int)requestResult.statusCode >= 300)
|
|
||||||
return Array.Empty<Chapter>();
|
|
||||||
|
|
||||||
//Return Chapters ordered by Chapter-Number
|
|
||||||
List<Chapter> chapters = ParseChaptersFromHtml(manga, requestUrl);
|
|
||||||
return chapters.Order().ToArray();
|
|
||||||
}
|
|
||||||
|
|
||||||
private List<Chapter> ParseChaptersFromHtml(Manga manga, string mangaUrl)
|
|
||||||
{
|
|
||||||
// Using HtmlWeb will include the chapters since they are loaded with js
|
|
||||||
HtmlWeb web = new();
|
|
||||||
HtmlDocument document = web.Load(mangaUrl);
|
|
||||||
|
|
||||||
List<Chapter> ret = new();
|
|
||||||
|
|
||||||
HtmlNode chapterList = document.DocumentNode.SelectSingleNode("//div[contains(@class, 'chapters')]/table/tbody");
|
|
||||||
|
|
||||||
Regex volumeRex = new(@"[0-9a-z\-\.]+\/[0-9a-z\-]*v([0-9\.]+)");
|
|
||||||
Regex chapterNumRex = new(@"[0-9a-z\-\.]+\/[0-9a-z\-]*c([0-9\.]+)");
|
|
||||||
Regex chapterNameRex = new(@"Chapter [0-9\.]+:? (.*)");
|
|
||||||
|
|
||||||
foreach (HtmlNode chapterInfo in chapterList.Descendants("tr"))
|
|
||||||
{
|
|
||||||
string fullString = chapterInfo.Descendants("a").First().InnerText;
|
|
||||||
string url = chapterInfo.Descendants("a").First()
|
|
||||||
.GetAttributeValue("href", "");
|
|
||||||
|
|
||||||
int? volumeNumber = volumeRex.IsMatch(url) ? int.Parse(volumeRex.Match(url).Groups[1].Value) : null;
|
|
||||||
|
|
||||||
string chapterNumber = new(chapterNumRex.Match(url).Groups[1].Value);
|
|
||||||
string chapterName = chapterNameRex.Match(fullString).Groups[1].Value;
|
|
||||||
try
|
|
||||||
{
|
|
||||||
ret.Add(new Chapter(manga, url, chapterNumber, volumeNumber, chapterName));
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
internal override string[] GetChapterImageUrls(Chapter chapter)
|
|
||||||
{
|
|
||||||
string requestUrl = chapter.Url;
|
|
||||||
// Leaving this in to check if the page exists
|
|
||||||
RequestResult requestResult =
|
|
||||||
downloadClient.MakeRequest(requestUrl, RequestType.Default);
|
|
||||||
if ((int)requestResult.statusCode < 200 || (int)requestResult.statusCode >= 300 || requestResult.htmlDocument is null)
|
|
||||||
{
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
string[] imageUrls = ParseImageUrlsFromHtml(requestResult.htmlDocument);
|
|
||||||
return imageUrls;
|
|
||||||
}
|
|
||||||
|
|
||||||
private string[] ParseImageUrlsFromHtml(HtmlDocument document)
|
|
||||||
{
|
|
||||||
// Images are loaded dynamically, but the urls are present in a piece of js code on the page
|
|
||||||
string js = document.DocumentNode.SelectSingleNode("//script[contains(., 'data-src')]").InnerText
|
|
||||||
.Replace("\r", "")
|
|
||||||
.Replace("\n", "")
|
|
||||||
.Replace("\t", "");
|
|
||||||
|
|
||||||
// ReSharper disable once StringLiteralTypo
|
|
||||||
string regexPat = @"(var thzq=\[')(.*)(,];function)";
|
|
||||||
var group = Regex.Matches(js, regexPat).First().Groups[2].Value.Replace("'", "");
|
|
||||||
var urls = group.Split(',');
|
|
||||||
|
|
||||||
return urls;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,219 +0,0 @@
|
|||||||
using System.Globalization;
|
|
||||||
using System.Net;
|
|
||||||
using System.Text.RegularExpressions;
|
|
||||||
using API.MangaDownloadClients;
|
|
||||||
using HtmlAgilityPack;
|
|
||||||
|
|
||||||
namespace API.Schema.MangaConnectors;
|
|
||||||
|
|
||||||
public class Manganato : MangaConnector
|
|
||||||
{
|
|
||||||
public Manganato() : base("Manganato", ["en"],
|
|
||||||
["natomanga.com", "manganato.gg", "mangakakalot.gg", "nelomanga.com"],
|
|
||||||
"https://www.manganato.gg/images/favicon-manganato.webp")
|
|
||||||
{
|
|
||||||
this.downloadClient = new HttpDownloadClient();
|
|
||||||
}
|
|
||||||
|
|
||||||
public override (Manga, List<Author>?, List<MangaTag>?, List<Link>?, List<MangaAltTitle>?)[] GetManga(
|
|
||||||
string publicationTitle = "")
|
|
||||||
{
|
|
||||||
string sanitizedTitle = string.Join('_', Regex.Matches(publicationTitle, "[A-z]*").Where(str => str.Length > 0))
|
|
||||||
.ToLower();
|
|
||||||
string requestUrl = $"https://manganato.gg/search/story/{sanitizedTitle}";
|
|
||||||
RequestResult requestResult =
|
|
||||||
downloadClient.MakeRequest(requestUrl, RequestType.Default);
|
|
||||||
if ((int)requestResult.statusCode < 200 || (int)requestResult.statusCode >= 300)
|
|
||||||
return [];
|
|
||||||
|
|
||||||
if (requestResult.htmlDocument is null)
|
|
||||||
return [];
|
|
||||||
(Manga, List<Author>?, List<MangaTag>?, List<Link>?, List<MangaAltTitle>?)[] publications =
|
|
||||||
ParsePublicationsFromHtml(requestResult.htmlDocument);
|
|
||||||
return publications;
|
|
||||||
}
|
|
||||||
|
|
||||||
private (Manga, List<Author>?, List<MangaTag>?, List<Link>?, List<MangaAltTitle>?)[] ParsePublicationsFromHtml(
|
|
||||||
HtmlDocument document)
|
|
||||||
{
|
|
||||||
List<HtmlNode> searchResults =
|
|
||||||
document.DocumentNode.Descendants("div").Where(n => n.HasClass("story_item")).ToList();
|
|
||||||
List<string> urls = new();
|
|
||||||
foreach (HtmlNode mangaResult in searchResults)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
urls.Add(mangaResult.Descendants("h3").First(n => n.HasClass("story_name"))
|
|
||||||
.Descendants("a").First().GetAttributeValue("href", ""));
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
//failed to get a url, send it to the void
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
List<(Manga, List<Author>?, List<MangaTag>?, List<Link>?, List<MangaAltTitle>?)> ret = new();
|
|
||||||
foreach (string url in urls)
|
|
||||||
{
|
|
||||||
(Manga, List<Author>?, List<MangaTag>?, List<Link>?, List<MangaAltTitle>?)? manga = GetMangaFromUrl(url);
|
|
||||||
if (manga is { } m)
|
|
||||||
ret.Add(m);
|
|
||||||
}
|
|
||||||
|
|
||||||
return ret.ToArray();
|
|
||||||
}
|
|
||||||
|
|
||||||
public override (Manga, List<Author>?, List<MangaTag>?, List<Link>?, List<MangaAltTitle>?)? GetMangaFromId(
|
|
||||||
string publicationId)
|
|
||||||
{
|
|
||||||
return GetMangaFromUrl($"https://chapmanganato.com/{publicationId}");
|
|
||||||
}
|
|
||||||
|
|
||||||
public override (Manga, List<Author>?, List<MangaTag>?, List<Link>?, List<MangaAltTitle>?)?
|
|
||||||
GetMangaFromUrl(string url)
|
|
||||||
{
|
|
||||||
RequestResult requestResult =
|
|
||||||
downloadClient.MakeRequest(url, RequestType.MangaInfo);
|
|
||||||
if ((int)requestResult.statusCode < 200 || (int)requestResult.statusCode >= 300)
|
|
||||||
return null;
|
|
||||||
|
|
||||||
if (requestResult.htmlDocument is null)
|
|
||||||
return null;
|
|
||||||
return ParseSinglePublicationFromHtml(requestResult.htmlDocument, url.Split('/')[^1], url);
|
|
||||||
}
|
|
||||||
|
|
||||||
private (Manga, List<Author>?, List<MangaTag>?, List<Link>?, List<MangaAltTitle>?) ParseSinglePublicationFromHtml(
|
|
||||||
HtmlDocument document, string publicationId, string websiteUrl)
|
|
||||||
{
|
|
||||||
Dictionary<string, string> altTitles = new();
|
|
||||||
List<MangaTag> tags = new();
|
|
||||||
List<Author> authors = new();
|
|
||||||
MangaReleaseStatus releaseStatus = MangaReleaseStatus.Unreleased;
|
|
||||||
|
|
||||||
HtmlNode infoNode = document.DocumentNode.Descendants("ul").First(d => d.HasClass("manga-info-text"));
|
|
||||||
|
|
||||||
string sortName = infoNode.Descendants("h1").First().InnerText;
|
|
||||||
|
|
||||||
foreach (HtmlNode li in infoNode.Descendants("li"))
|
|
||||||
{
|
|
||||||
string text = li.InnerText.Trim().ToLower();
|
|
||||||
|
|
||||||
if (text.StartsWith("author(s) :"))
|
|
||||||
{
|
|
||||||
authors = li.Descendants("a").Select(a => a.InnerText.Trim()).Select(a => new Author(a)).ToList();
|
|
||||||
}
|
|
||||||
else if (text.StartsWith("status :"))
|
|
||||||
{
|
|
||||||
string status = text.Replace("status :", "").Trim().ToLower();
|
|
||||||
if (string.IsNullOrWhiteSpace(status))
|
|
||||||
releaseStatus = MangaReleaseStatus.Continuing;
|
|
||||||
else if (status == "ongoing")
|
|
||||||
releaseStatus = MangaReleaseStatus.Continuing;
|
|
||||||
else
|
|
||||||
releaseStatus = Enum.Parse<MangaReleaseStatus>(status, true);
|
|
||||||
}
|
|
||||||
else if (li.HasClass("genres"))
|
|
||||||
{
|
|
||||||
tags = li.Descendants("a").Select(a => new MangaTag(a.InnerText.Trim())).ToList();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
string posterUrl = document.DocumentNode.Descendants("div").First(s => s.HasClass("manga-info-pic"))
|
|
||||||
.Descendants("img").First()
|
|
||||||
.GetAttributes().First(a => a.Name == "src").Value;
|
|
||||||
|
|
||||||
string description = document.DocumentNode.SelectSingleNode("//div[@id='contentBox']")
|
|
||||||
.InnerText.Replace("Description :", "");
|
|
||||||
while (description.StartsWith('\n'))
|
|
||||||
description = description.Substring(1);
|
|
||||||
|
|
||||||
string pattern = "MMM-dd-yyyy HH:mm";
|
|
||||||
|
|
||||||
HtmlNode? oldestChapter = document.DocumentNode
|
|
||||||
.SelectNodes("//div[contains(concat(' ',normalize-space(@class),' '),' row ')]/span[@title]").MaxBy(
|
|
||||||
node => DateTime.ParseExact(node.GetAttributeValue("title", "Dec-31-2400 23:59"), pattern,
|
|
||||||
CultureInfo.InvariantCulture).Millisecond);
|
|
||||||
|
|
||||||
|
|
||||||
uint year = Convert.ToUInt32(DateTime.ParseExact(
|
|
||||||
oldestChapter?.GetAttributeValue("title", "Dec 31 2400, 23:59") ?? "Dec 31 2400, 23:59", pattern,
|
|
||||||
CultureInfo.InvariantCulture).Year);
|
|
||||||
|
|
||||||
Manga manga = new(publicationId, sortName, description, websiteUrl, posterUrl, null, year, null, releaseStatus,
|
|
||||||
-1, this, authors, tags, [], []);
|
|
||||||
return (manga, authors, tags, [], []);
|
|
||||||
}
|
|
||||||
|
|
||||||
public override Chapter[] GetChapters(Manga manga, string language = "en")
|
|
||||||
{
|
|
||||||
string requestUrl = manga.WebsiteUrl;
|
|
||||||
RequestResult requestResult =
|
|
||||||
downloadClient.MakeRequest(requestUrl, RequestType.Default);
|
|
||||||
if ((int)requestResult.statusCode < 200 || (int)requestResult.statusCode >= 300)
|
|
||||||
return Array.Empty<Chapter>();
|
|
||||||
|
|
||||||
//Return Chapters ordered by Chapter-Number
|
|
||||||
if (requestResult.htmlDocument is null)
|
|
||||||
return Array.Empty<Chapter>();
|
|
||||||
List<Chapter> chapters = ParseChaptersFromHtml(manga, requestResult.htmlDocument);
|
|
||||||
return chapters.Order().ToArray();
|
|
||||||
}
|
|
||||||
|
|
||||||
internal override string[] GetChapterImageUrls(Chapter chapter)
|
|
||||||
{
|
|
||||||
string requestUrl = chapter.Url;
|
|
||||||
RequestResult requestResult =
|
|
||||||
downloadClient.MakeRequest(requestUrl, RequestType.Default);
|
|
||||||
if ((int)requestResult.statusCode < 200 || (int)requestResult.statusCode >= 300 ||
|
|
||||||
requestResult.htmlDocument is null)
|
|
||||||
return [];
|
|
||||||
|
|
||||||
string[] imageUrls = ParseImageUrlsFromHtml(requestResult.htmlDocument);
|
|
||||||
|
|
||||||
return imageUrls;
|
|
||||||
}
|
|
||||||
|
|
||||||
private List<Chapter> ParseChaptersFromHtml(Manga manga, HtmlDocument document)
|
|
||||||
{
|
|
||||||
List<Chapter> ret = new();
|
|
||||||
|
|
||||||
HtmlNode chapterList = document.DocumentNode.Descendants("div").First(l => l.HasClass("chapter-list"));
|
|
||||||
|
|
||||||
Regex volRex = new(@"Vol\.([0-9]+).*");
|
|
||||||
Regex chapterRex = new(@"https:\/\/chapmanganato.[A-z]+\/manga-[A-z0-9]+\/chapter-([0-9\.]+)");
|
|
||||||
Regex nameRex = new(@"Chapter ([0-9]+(\.[0-9]+)*){1}:? (.*)");
|
|
||||||
|
|
||||||
foreach (HtmlNode chapterInfo in chapterList.Descendants("div").Where(x => x.HasClass("row")))
|
|
||||||
{
|
|
||||||
string url = chapterInfo.Descendants("a").First().GetAttributeValue("href", "");
|
|
||||||
var name = chapterInfo.Descendants("a").First().InnerText.Trim();
|
|
||||||
string chapterName = nameRex.Match(name).Groups[3].Value;
|
|
||||||
string chapterNumber = Regex.Match(name, @"Chapter ([0-9]+(\.[0-9]+)*)").Groups[1].Value;
|
|
||||||
string? volumeNumber = Regex.Match(chapterName, @"Vol\.([0-9]+)").Groups[1].Value;
|
|
||||||
if (string.IsNullOrWhiteSpace(volumeNumber))
|
|
||||||
volumeNumber = "0";
|
|
||||||
try
|
|
||||||
{
|
|
||||||
ret.Add(new Chapter(manga, url, chapterNumber, int.Parse(volumeNumber), chapterName));
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ret.Reverse();
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
private string[] ParseImageUrlsFromHtml(HtmlDocument document)
|
|
||||||
{
|
|
||||||
List<string> ret = new();
|
|
||||||
|
|
||||||
HtmlNode imageContainer =
|
|
||||||
document.DocumentNode.Descendants("div").First(i => i.HasClass("container-chapter-reader"));
|
|
||||||
foreach (HtmlNode imageNode in imageContainer.Descendants("img"))
|
|
||||||
ret.Add(imageNode.GetAttributeValue("src", ""));
|
|
||||||
|
|
||||||
return ret.ToArray();
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,223 +0,0 @@
|
|||||||
using System.Text.RegularExpressions;
|
|
||||||
using API.MangaDownloadClients;
|
|
||||||
using HtmlAgilityPack;
|
|
||||||
|
|
||||||
namespace API.Schema.MangaConnectors;
|
|
||||||
|
|
||||||
public class Mangaworld : MangaConnector
|
|
||||||
{
|
|
||||||
public Mangaworld() : base("Mangaworld", ["it"], ["www.mangaworld.ac", "www.mangaworld.nz"], "https://www.mangaworld.nz/public/assets/seo/android-icon-192x192.png")
|
|
||||||
{
|
|
||||||
this.downloadClient = new ChromiumDownloadClient();
|
|
||||||
}
|
|
||||||
|
|
||||||
public override (Manga, List<Author>?, List<MangaTag>?, List<Link>?, List<MangaAltTitle>?)[] GetManga(string publicationTitle = "")
|
|
||||||
{
|
|
||||||
string sanitizedTitle = string.Join(' ', Regex.Matches(publicationTitle, "[A-z]*").Where(str => str.Length > 0)).ToLower();
|
|
||||||
string requestUrl = $"https://www.mangaworld.ac/archive?keyword={sanitizedTitle}";
|
|
||||||
RequestResult requestResult =
|
|
||||||
downloadClient.MakeRequest(requestUrl, RequestType.Default);
|
|
||||||
if ((int)requestResult.statusCode < 200 || (int)requestResult.statusCode >= 300)
|
|
||||||
return [];
|
|
||||||
|
|
||||||
if (requestResult.htmlDocument is null)
|
|
||||||
return [];
|
|
||||||
(Manga, List<Author>?, List<MangaTag>?, List<Link>?, List<MangaAltTitle>?)[] publications = ParsePublicationsFromHtml(requestResult.htmlDocument);
|
|
||||||
return publications;
|
|
||||||
}
|
|
||||||
|
|
||||||
private (Manga, List<Author>?, List<MangaTag>?, List<Link>?, List<MangaAltTitle>?)[] ParsePublicationsFromHtml(HtmlDocument document)
|
|
||||||
{
|
|
||||||
if (!document.DocumentNode.SelectSingleNode("//div[@class='comics-grid']").ChildNodes
|
|
||||||
.Any(node => node.HasClass("entry")))
|
|
||||||
return [];
|
|
||||||
|
|
||||||
List<string> urls = document.DocumentNode
|
|
||||||
.SelectNodes(
|
|
||||||
"//div[@class='comics-grid']//div[@class='entry']//a[contains(concat(' ',normalize-space(@class),' '),'thumb')]")
|
|
||||||
.Select(thumb => thumb.GetAttributeValue("href", "")).ToList();
|
|
||||||
|
|
||||||
List<(Manga, List<Author>?, List<MangaTag>?, List<Link>?, List<MangaAltTitle>?)> ret = new();
|
|
||||||
foreach (string url in urls)
|
|
||||||
{
|
|
||||||
(Manga, List<Author>?, List<MangaTag>?, List<Link>?, List<MangaAltTitle>?)? manga = GetMangaFromUrl(url);
|
|
||||||
if (manga is { } x)
|
|
||||||
ret.Add(x);
|
|
||||||
}
|
|
||||||
|
|
||||||
return ret.ToArray();
|
|
||||||
}
|
|
||||||
|
|
||||||
public override (Manga, List<Author>?, List<MangaTag>?, List<Link>?, List<MangaAltTitle>?)? GetMangaFromId(string publicationId)
|
|
||||||
{
|
|
||||||
return GetMangaFromUrl($"https://www.mangaworld.ac/manga/{publicationId}");
|
|
||||||
}
|
|
||||||
|
|
||||||
public override (Manga, List<Author>?, List<MangaTag>?, List<Link>?, List<MangaAltTitle>?)? GetMangaFromUrl(string url)
|
|
||||||
{
|
|
||||||
RequestResult requestResult =
|
|
||||||
downloadClient.MakeRequest(url, RequestType.MangaInfo);
|
|
||||||
if ((int)requestResult.statusCode < 200 || (int)requestResult.statusCode >= 300)
|
|
||||||
return null;
|
|
||||||
|
|
||||||
if (requestResult.htmlDocument is null)
|
|
||||||
return null;
|
|
||||||
|
|
||||||
Regex idRex = new (@"https:\/\/www\.mangaworld\.[a-z]{0,63}\/manga\/([0-9]+\/[0-9A-z\-]+).*");
|
|
||||||
string id = idRex.Match(url).Groups[1].Value;
|
|
||||||
return ParseSinglePublicationFromHtml(requestResult.htmlDocument, id, url);
|
|
||||||
}
|
|
||||||
|
|
||||||
private (Manga, List<Author>?, List<MangaTag>?, List<Link>?, List<MangaAltTitle>?) ParseSinglePublicationFromHtml(HtmlDocument document, string publicationId, string websiteUrl)
|
|
||||||
{
|
|
||||||
Dictionary<string, string> altTitlesDict = new();
|
|
||||||
string originalLanguage = "";
|
|
||||||
MangaReleaseStatus releaseStatus = MangaReleaseStatus.Unreleased;
|
|
||||||
|
|
||||||
HtmlNode infoNode = document.DocumentNode.Descendants("div").First(d => d.HasClass("info"));
|
|
||||||
|
|
||||||
string sortName = infoNode.Descendants("h1").First().InnerText;
|
|
||||||
|
|
||||||
HtmlNode metadata = infoNode.Descendants().First(d => d.HasClass("meta-data"));
|
|
||||||
|
|
||||||
HtmlNode altTitlesNode = metadata.SelectSingleNode("//span[text()='Titoli alternativi: ' or text()='Titolo alternativo: ']/..").ChildNodes[1];
|
|
||||||
string[] alts = altTitlesNode.InnerText.Split(", ");
|
|
||||||
for(int i = 0; i < alts.Length; i++)
|
|
||||||
altTitlesDict.Add(i.ToString(), alts[i]);
|
|
||||||
List<MangaAltTitle> altTitles = altTitlesDict.Select(a => new MangaAltTitle(a.Key, a.Value)).ToList();
|
|
||||||
|
|
||||||
HtmlNode genresNode =
|
|
||||||
metadata.SelectSingleNode("//span[text()='Generi: ' or text()='Genero: ']/..");
|
|
||||||
HashSet<string> tags = genresNode.SelectNodes("a").Select(node => node.InnerText).ToHashSet();
|
|
||||||
List<MangaTag> mangaTags = tags.Select(t => new MangaTag(t)).ToList();
|
|
||||||
|
|
||||||
HtmlNode authorsNode =
|
|
||||||
metadata.SelectSingleNode("//span[text()='Autore: ' or text()='Autori: ']/..");
|
|
||||||
string[] authorNames = authorsNode.SelectNodes("a").Select(node => node.InnerText).ToArray();
|
|
||||||
List<Author> authors = authorNames.Select(n => new Author(n)).ToList();
|
|
||||||
|
|
||||||
string status = metadata.SelectSingleNode("//span[text()='Stato: ']/..").SelectNodes("a").First().InnerText;
|
|
||||||
// ReSharper disable 5 times StringLiteralTypo
|
|
||||||
switch (status.ToLower())
|
|
||||||
{
|
|
||||||
case "cancellato": releaseStatus = MangaReleaseStatus.Cancelled; break;
|
|
||||||
case "in pausa": releaseStatus = MangaReleaseStatus.OnHiatus; break;
|
|
||||||
case "droppato": releaseStatus = MangaReleaseStatus.Cancelled; break;
|
|
||||||
case "finito": releaseStatus = MangaReleaseStatus.Completed; break;
|
|
||||||
case "in corso": releaseStatus = MangaReleaseStatus.Continuing; break;
|
|
||||||
}
|
|
||||||
|
|
||||||
string coverUrl = document.DocumentNode.SelectSingleNode("//img[@class='rounded']").GetAttributeValue("src", "");
|
|
||||||
|
|
||||||
string description = document.DocumentNode.SelectSingleNode("//div[@id='noidungm']").InnerText;
|
|
||||||
|
|
||||||
string yearString = metadata.SelectSingleNode("//span[text()='Anno di uscita: ']/..").SelectNodes("a").First().InnerText;
|
|
||||||
uint year = uint.Parse(yearString);
|
|
||||||
|
|
||||||
Manga manga = new (publicationId, sortName, description, websiteUrl, coverUrl, null, year,
|
|
||||||
originalLanguage, releaseStatus, -1,
|
|
||||||
this,
|
|
||||||
authors,
|
|
||||||
mangaTags,
|
|
||||||
[],
|
|
||||||
altTitles);
|
|
||||||
|
|
||||||
return (manga, authors, mangaTags, [], altTitles);
|
|
||||||
}
|
|
||||||
|
|
||||||
public override Chapter[] GetChapters(Manga manga, string language="en")
|
|
||||||
{
|
|
||||||
string requestUrl = $"https://www.mangaworld.ac/manga/{manga.MangaId}";
|
|
||||||
RequestResult requestResult =
|
|
||||||
downloadClient.MakeRequest(requestUrl, RequestType.Default);
|
|
||||||
if ((int)requestResult.statusCode < 200 || (int)requestResult.statusCode >= 300 || requestResult.htmlDocument is null)
|
|
||||||
return [];
|
|
||||||
|
|
||||||
List<Chapter> chapters = ParseChaptersFromHtml(manga, requestResult.htmlDocument);
|
|
||||||
return chapters.Order().ToArray();
|
|
||||||
}
|
|
||||||
|
|
||||||
private List<Chapter> ParseChaptersFromHtml(Manga manga, HtmlDocument document)
|
|
||||||
{
|
|
||||||
List<Chapter> ret = new();
|
|
||||||
|
|
||||||
HtmlNode chaptersWrapper =
|
|
||||||
document.DocumentNode.SelectSingleNode(
|
|
||||||
"//div[contains(concat(' ',normalize-space(@class),' '),'chapters-wrapper')]");
|
|
||||||
|
|
||||||
Regex volumeRex = new(@"[Vv]olume ([0-9]+).*");
|
|
||||||
Regex chapterRex = new(@"[Cc]apitolo ([0-9]+(?:\.[0-9]+)?).*");
|
|
||||||
Regex idRex = new(@".*\/read\/([a-z0-9]+)(?:[?\/].*)?");
|
|
||||||
if (chaptersWrapper.Descendants("div").Any(descendant => descendant.HasClass("volume-element")))
|
|
||||||
{
|
|
||||||
foreach (HtmlNode volNode in document.DocumentNode.SelectNodes("//div[contains(concat(' ',normalize-space(@class),' '),'volume-element')]"))
|
|
||||||
{
|
|
||||||
string volumeStr = volumeRex.Match(volNode.SelectNodes("div").First(node => node.HasClass("volume")).SelectSingleNode("p").InnerText).Groups[1].Value;
|
|
||||||
int volume = int.Parse(volumeStr);
|
|
||||||
foreach (HtmlNode chNode in volNode.SelectNodes("div").First(node => node.HasClass("volume-chapters")).SelectNodes("div"))
|
|
||||||
{
|
|
||||||
|
|
||||||
string numberStr = chapterRex.Match(chNode.SelectSingleNode("a").SelectSingleNode("span").InnerText).Groups[1].Value;
|
|
||||||
|
|
||||||
string chapterNumber = new(numberStr);
|
|
||||||
string url = chNode.SelectSingleNode("a").GetAttributeValue("href", "");
|
|
||||||
string id = idRex.Match(chNode.SelectSingleNode("a").GetAttributeValue("href", "")).Groups[1].Value;
|
|
||||||
try
|
|
||||||
{
|
|
||||||
ret.Add(new Chapter(manga, url, chapterNumber, volume, null));
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
foreach (HtmlNode chNode in chaptersWrapper.SelectNodes("div").Where(node => node.HasClass("chapter")))
|
|
||||||
{
|
|
||||||
string numberStr = chapterRex.Match(chNode.SelectSingleNode("a").SelectSingleNode("span").InnerText).Groups[1].Value;
|
|
||||||
|
|
||||||
string chapterNumber = new(numberStr);
|
|
||||||
string url = chNode.SelectSingleNode("a").GetAttributeValue("href", "");
|
|
||||||
string id = idRex.Match(chNode.SelectSingleNode("a").GetAttributeValue("href", "")).Groups[1].Value;
|
|
||||||
try
|
|
||||||
{
|
|
||||||
ret.Add(new Chapter(manga, url, chapterNumber, null, null));
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ret.Reverse();
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
internal override string[] GetChapterImageUrls(Chapter chapter)
|
|
||||||
{
|
|
||||||
string requestUrl = $"{chapter.Url}?style=list";
|
|
||||||
RequestResult requestResult =
|
|
||||||
downloadClient.MakeRequest(requestUrl, RequestType.Default);
|
|
||||||
if ((int)requestResult.statusCode < 200 || (int)requestResult.statusCode >= 300 || requestResult.htmlDocument is null)
|
|
||||||
{
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
string[] imageUrls = ParseImageUrlsFromHtml(requestResult.htmlDocument);
|
|
||||||
return imageUrls;
|
|
||||||
}
|
|
||||||
|
|
||||||
private string[] ParseImageUrlsFromHtml(HtmlDocument document)
|
|
||||||
{
|
|
||||||
List<string> ret = new();
|
|
||||||
|
|
||||||
HtmlNode imageContainer =
|
|
||||||
document.DocumentNode.SelectSingleNode("//div[@id='page']");
|
|
||||||
foreach(HtmlNode imageNode in imageContainer.Descendants("img"))
|
|
||||||
ret.Add(imageNode.GetAttributeValue("src", ""));
|
|
||||||
|
|
||||||
return ret.ToArray();
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,179 +0,0 @@
|
|||||||
using System.Text.RegularExpressions;
|
|
||||||
using API.MangaDownloadClients;
|
|
||||||
using HtmlAgilityPack;
|
|
||||||
|
|
||||||
namespace API.Schema.MangaConnectors;
|
|
||||||
|
|
||||||
public class ManhuaPlus : MangaConnector
|
|
||||||
{
|
|
||||||
public ManhuaPlus() : base("ManhuaPlus", ["en"], ["manhuaplus.org"], "https://manhuaplus.org/uploads/images/favicon.png")
|
|
||||||
{
|
|
||||||
this.downloadClient = new ChromiumDownloadClient();
|
|
||||||
}
|
|
||||||
|
|
||||||
public override (Manga, List<Author>?, List<MangaTag>?, List<Link>?, List<MangaAltTitle>?)[] GetManga(string publicationTitle = "")
|
|
||||||
{
|
|
||||||
string sanitizedTitle = string.Join(' ', Regex.Matches(publicationTitle, "[A-z]*").Where(str => str.Length > 0)).ToLower();
|
|
||||||
string requestUrl = $"https://manhuaplus.org/search?keyword={sanitizedTitle}";
|
|
||||||
RequestResult requestResult =
|
|
||||||
downloadClient.MakeRequest(requestUrl, RequestType.Default);
|
|
||||||
if ((int)requestResult.statusCode < 200 || (int)requestResult.statusCode >= 300 || requestResult.htmlDocument is null)
|
|
||||||
return [];
|
|
||||||
|
|
||||||
(Manga, List<Author>?, List<MangaTag>?, List<Link>?, List<MangaAltTitle>?)[] publications = ParsePublicationsFromHtml(requestResult.htmlDocument);
|
|
||||||
return publications;
|
|
||||||
}
|
|
||||||
|
|
||||||
private (Manga, List<Author>?, List<MangaTag>?, List<Link>?, List<MangaAltTitle>?)[] ParsePublicationsFromHtml(HtmlDocument document)
|
|
||||||
{
|
|
||||||
if (document.DocumentNode.SelectSingleNode("//h1/../..").ChildNodes//I already want to not.
|
|
||||||
.Any(node => node.InnerText.Contains("No manga found")))
|
|
||||||
return [];
|
|
||||||
|
|
||||||
List<string> urls = document.DocumentNode
|
|
||||||
.SelectNodes("//h1/../..//a[contains(@href, 'https://manhuaplus.org/manga/') and contains(concat(' ',normalize-space(@class),' '),' clamp ') and not(contains(@href, '/chapter'))]")
|
|
||||||
.Select(mangaNode => mangaNode.GetAttributeValue("href", "")).ToList();
|
|
||||||
|
|
||||||
List<(Manga, List<Author>?, List<MangaTag>?, List<Link>?, List<MangaAltTitle>?)> ret = new();
|
|
||||||
foreach (string url in urls)
|
|
||||||
{
|
|
||||||
(Manga, List<Author>?, List<MangaTag>?, List<Link>?, List<MangaAltTitle>?)? manga = GetMangaFromUrl(url);
|
|
||||||
if (manga is { } x)
|
|
||||||
ret.Add(x);
|
|
||||||
}
|
|
||||||
|
|
||||||
return ret.ToArray();
|
|
||||||
}
|
|
||||||
|
|
||||||
public override (Manga, List<Author>?, List<MangaTag>?, List<Link>?, List<MangaAltTitle>?)? GetMangaFromId(string publicationId)
|
|
||||||
{
|
|
||||||
return GetMangaFromUrl($"https://manhuaplus.org/manga/{publicationId}");
|
|
||||||
}
|
|
||||||
|
|
||||||
public override (Manga, List<Author>?, List<MangaTag>?, List<Link>?, List<MangaAltTitle>?)? GetMangaFromUrl(string url)
|
|
||||||
{
|
|
||||||
Regex publicationIdRex = new(@"https:\/\/manhuaplus.org\/manga\/(.*)(\/.*)*");
|
|
||||||
string publicationId = publicationIdRex.Match(url).Groups[1].Value;
|
|
||||||
|
|
||||||
RequestResult requestResult = this.downloadClient.MakeRequest(url, RequestType.MangaInfo);
|
|
||||||
if((int)requestResult.statusCode < 300 && (int)requestResult.statusCode >= 200 && requestResult.htmlDocument is not null && requestResult.redirectedToUrl != "https://manhuaplus.org/home") //When manga doesnt exists it redirects to home
|
|
||||||
return ParseSinglePublicationFromHtml(requestResult.htmlDocument, publicationId, url);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private (Manga, List<Author>?, List<MangaTag>?, List<Link>?, List<MangaAltTitle>?) ParseSinglePublicationFromHtml(HtmlDocument document, string publicationId, string websiteUrl)
|
|
||||||
{
|
|
||||||
string originalLanguage = "", status = "";
|
|
||||||
Dictionary<string, string> altTitles = new(), links = new();
|
|
||||||
HashSet<string> tags = new();
|
|
||||||
MangaReleaseStatus releaseStatus = MangaReleaseStatus.Unreleased;
|
|
||||||
|
|
||||||
HtmlNode posterNode = document.DocumentNode.SelectSingleNode("/html/body/main/div/div/div[2]/div[1]/figure/a/img");//BRUH
|
|
||||||
Regex posterRex = new(@".*(\/uploads/covers/[a-zA-Z0-9\-\._\~\!\$\&\'\(\)\*\+\,\;\=\:\@]+).*");
|
|
||||||
string coverUrl = $"https://manhuaplus.org/{posterRex.Match(posterNode.GetAttributeValue("src", "")).Groups[1].Value}";
|
|
||||||
|
|
||||||
HtmlNode titleNode = document.DocumentNode.SelectSingleNode("//h1");
|
|
||||||
string sortName = titleNode.InnerText.Replace("\n", "");
|
|
||||||
|
|
||||||
List<string> authorNames = new();
|
|
||||||
try
|
|
||||||
{
|
|
||||||
HtmlNode[] authorsNodes = document.DocumentNode
|
|
||||||
.SelectNodes("//a[contains(@href, 'https://manhuaplus.org/authors/')]")
|
|
||||||
.ToArray();
|
|
||||||
foreach (HtmlNode authorNode in authorsNodes)
|
|
||||||
authorNames.Add(authorNode.InnerText);
|
|
||||||
}
|
|
||||||
catch (ArgumentNullException e)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
List<Author> authors = authorNames.Select(a => new Author(a)).ToList();
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
HtmlNode[] genreNodes = document.DocumentNode
|
|
||||||
.SelectNodes("//a[contains(@href, 'https://manhuaplus.org/genres/')]").ToArray();
|
|
||||||
foreach (HtmlNode genreNode in genreNodes)
|
|
||||||
tags.Add(genreNode.InnerText.Replace("\n", ""));
|
|
||||||
}
|
|
||||||
catch (ArgumentNullException e)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
List<MangaTag> mangaTags = tags.Select(t => new MangaTag(t)).ToList();
|
|
||||||
|
|
||||||
Regex yearRex = new(@"(?:[0-9]{1,2}\/){2}([0-9]{2,4}) [0-9]{1,2}:[0-9]{1,2}");
|
|
||||||
HtmlNode yearNode = document.DocumentNode.SelectSingleNode("//aside//i[contains(concat(' ',normalize-space(@class),' '),' fa-clock ')]/../span");
|
|
||||||
Match match = yearRex.Match(yearNode.InnerText);
|
|
||||||
uint year = match.Success && match.Groups[1].Success ? uint.Parse(match.Groups[1].Value) : 0;
|
|
||||||
|
|
||||||
status = document.DocumentNode.SelectSingleNode("//aside//i[contains(concat(' ',normalize-space(@class),' '),' fa-rss ')]/../span").InnerText.Replace("\n", "");
|
|
||||||
switch (status.ToLower())
|
|
||||||
{
|
|
||||||
case "cancelled": releaseStatus = MangaReleaseStatus.Cancelled; break;
|
|
||||||
case "hiatus": releaseStatus = MangaReleaseStatus.OnHiatus; break;
|
|
||||||
case "discontinued": releaseStatus = MangaReleaseStatus.Cancelled; break;
|
|
||||||
case "complete": releaseStatus = MangaReleaseStatus.Completed; break;
|
|
||||||
case "ongoing": releaseStatus = MangaReleaseStatus.Continuing; break;
|
|
||||||
}
|
|
||||||
|
|
||||||
HtmlNode descriptionNode = document.DocumentNode
|
|
||||||
.SelectSingleNode("//div[@id='syn-target']");
|
|
||||||
string description = descriptionNode.InnerText;
|
|
||||||
|
|
||||||
Manga manga = new (publicationId, sortName, description, websiteUrl, coverUrl, null, year,
|
|
||||||
originalLanguage, releaseStatus, -1,
|
|
||||||
this,
|
|
||||||
authors,
|
|
||||||
mangaTags,
|
|
||||||
[],
|
|
||||||
[]);
|
|
||||||
|
|
||||||
return (manga, authors, mangaTags, [], []);
|
|
||||||
}
|
|
||||||
|
|
||||||
public override Chapter[] GetChapters(Manga manga, string language="en")
|
|
||||||
{
|
|
||||||
RequestResult result = downloadClient.MakeRequest($"https://manhuaplus.org/manga/{manga.MangaId}", RequestType.Default);
|
|
||||||
if ((int)result.statusCode < 200 || (int)result.statusCode >= 300 || result.htmlDocument is null)
|
|
||||||
{
|
|
||||||
return Array.Empty<Chapter>();
|
|
||||||
}
|
|
||||||
|
|
||||||
HtmlNodeCollection chapterNodes = result.htmlDocument.DocumentNode.SelectNodes("//li[contains(concat(' ',normalize-space(@class),' '),' chapter ')]//a");
|
|
||||||
string[] urls = chapterNodes.Select(node => node.GetAttributeValue("href", "")).ToArray();
|
|
||||||
Regex urlRex = new (@".*\/chapter-([0-9\-]+).*");
|
|
||||||
|
|
||||||
List<Chapter> chapters = new();
|
|
||||||
foreach (string url in urls)
|
|
||||||
{
|
|
||||||
Match rexMatch = urlRex.Match(url);
|
|
||||||
|
|
||||||
string chapterNumber = new(rexMatch.Groups[1].Value);
|
|
||||||
string fullUrl = url;
|
|
||||||
try
|
|
||||||
{
|
|
||||||
chapters.Add(new Chapter(manga, fullUrl, chapterNumber, null, null));
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
|
||||||
//Return Chapters ordered by Chapter-Number
|
|
||||||
return chapters.Order().ToArray();
|
|
||||||
}
|
|
||||||
|
|
||||||
internal override string[] GetChapterImageUrls(Chapter chapter)
|
|
||||||
{
|
|
||||||
RequestResult requestResult = this.downloadClient.MakeRequest(chapter.Url, RequestType.Default);
|
|
||||||
if ((int)requestResult.statusCode < 200 || (int)requestResult.statusCode >= 300 || requestResult.htmlDocument is null)
|
|
||||||
{
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
HtmlDocument document = requestResult.htmlDocument;
|
|
||||||
|
|
||||||
HtmlNode[] images = document.DocumentNode.SelectNodes("//a[contains(concat(' ',normalize-space(@class),' '),' readImg ')]/img").ToArray();
|
|
||||||
List<string> urls = images.Select(node => node.GetAttributeValue("src", "")).ToList();
|
|
||||||
return urls.ToArray();
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,259 +0,0 @@
|
|||||||
using System.Net;
|
|
||||||
using System.Text.RegularExpressions;
|
|
||||||
using API.MangaDownloadClients;
|
|
||||||
using HtmlAgilityPack;
|
|
||||||
|
|
||||||
namespace API.Schema.MangaConnectors;
|
|
||||||
|
|
||||||
public class Webtoons : MangaConnector
|
|
||||||
{
|
|
||||||
|
|
||||||
public Webtoons() : base("Webtoons", ["en"], ["www.webtoons.com"], "https://webtoons-static.pstatic.net/image/favicon/favicon.ico")
|
|
||||||
{
|
|
||||||
this.downloadClient = new HttpDownloadClient();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Done
|
|
||||||
public override (Manga, List<Author>?, List<MangaTag>?, List<Link>?, List<MangaAltTitle>?)[] GetManga(string publicationTitle = "")
|
|
||||||
{
|
|
||||||
string sanitizedTitle = string.Join(' ', Regex.Matches(publicationTitle, "[A-z]*").Where(m => m.Value.Length > 0)).ToLower();
|
|
||||||
string requestUrl = $"https://www.webtoons.com/en/search?keyword={sanitizedTitle}&searchType=WEBTOON";
|
|
||||||
RequestResult requestResult =
|
|
||||||
downloadClient.MakeRequest(requestUrl, RequestType.Default);
|
|
||||||
if ((int)requestResult.statusCode < 200 || (int)requestResult.statusCode >= 300) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
if (requestResult.htmlDocument is null)
|
|
||||||
{
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
(Manga, List<Author>, List<MangaTag>, List<Link>, List<MangaAltTitle>)[] publications =
|
|
||||||
ParsePublicationsFromHtml(requestResult.htmlDocument);
|
|
||||||
return publications;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Done
|
|
||||||
public override (Manga, List<Author>, List<MangaTag>, List<Link>, List<MangaAltTitle>)? GetMangaFromId(string publicationId)
|
|
||||||
{
|
|
||||||
PublicationManager pb = new PublicationManager(publicationId);
|
|
||||||
return GetMangaFromUrl($"https://www.webtoons.com/en/{pb.Category}/{pb.Title}/list?title_no={pb.Id}");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Done
|
|
||||||
public override (Manga, List<Author>, List<MangaTag>, List<Link>, List<MangaAltTitle>)? GetMangaFromUrl(string url)
|
|
||||||
{
|
|
||||||
RequestResult requestResult = downloadClient.MakeRequest(url, RequestType.MangaInfo);
|
|
||||||
if ((int)requestResult.statusCode < 200 || (int)requestResult.statusCode >= 300) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
if (requestResult.htmlDocument is null)
|
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
Regex regex = new Regex(@".*webtoons\.com/en/(?<category>[^/]+)/(?<title>[^/]+)/list\?title_no=(?<id>\d+).*");
|
|
||||||
Match match = regex.Match(url);
|
|
||||||
|
|
||||||
if(match.Success) {
|
|
||||||
PublicationManager pm = new PublicationManager(match.Groups["title"].Value, match.Groups["category"].Value, match.Groups["id"].Value);
|
|
||||||
return ParseSinglePublicationFromHtml(requestResult.htmlDocument, pm.getPublicationId(), url);
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Done
|
|
||||||
private (Manga, List<Author>, List<MangaTag>, List<Link>, List<MangaAltTitle>)[] ParsePublicationsFromHtml(HtmlDocument document)
|
|
||||||
{
|
|
||||||
HtmlNode mangaList = document.DocumentNode.SelectSingleNode("//ul[contains(@class, 'card_lst')]");
|
|
||||||
if (!mangaList.ChildNodes.Any(node => node.Name == "li")) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
List<string> urls = document.DocumentNode
|
|
||||||
.SelectNodes("//ul[contains(@class, 'card_lst')]/li/a")
|
|
||||||
.Select(node => node.GetAttributeValue("href", "https://www.webtoons.com"))
|
|
||||||
.ToList();
|
|
||||||
|
|
||||||
List<(Manga, List<Author>, List<MangaTag>, List<Link>, List<MangaAltTitle>)> ret = new();
|
|
||||||
foreach (string url in urls)
|
|
||||||
{
|
|
||||||
(Manga, List<Author>, List<MangaTag>, List<Link>, List<MangaAltTitle>)? manga = GetMangaFromUrl(url);
|
|
||||||
if(manga is { } m)
|
|
||||||
ret.Add(m);
|
|
||||||
}
|
|
||||||
|
|
||||||
return ret.ToArray();
|
|
||||||
}
|
|
||||||
|
|
||||||
private string capitalizeString(string str = "") {
|
|
||||||
if(str.Length == 0) return "";
|
|
||||||
if(str.Length == 1) return str.ToUpper();
|
|
||||||
return char.ToUpper(str[0]) + str.Substring(1).ToLower();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Done
|
|
||||||
private (Manga, List<Author>, List<MangaTag>, List<Link>, List<MangaAltTitle>) ParseSinglePublicationFromHtml(HtmlDocument document, string publicationId, string websiteUrl)
|
|
||||||
{
|
|
||||||
HtmlNode infoNode1 = document.DocumentNode.SelectSingleNode("//*[@id='content']/div[2]/div[1]/div[1]");
|
|
||||||
HtmlNode infoNode2 = document.DocumentNode.SelectSingleNode("//*[@id='content']/div[2]/div[2]/div[2]");
|
|
||||||
|
|
||||||
string sortName = infoNode1.SelectSingleNode(".//h1[contains(@class, 'subj')]").InnerText;
|
|
||||||
string description = infoNode2.SelectSingleNode(".//p[contains(@class, 'summary')]")
|
|
||||||
.InnerText.Trim();
|
|
||||||
|
|
||||||
HtmlNode posterNode = document.DocumentNode.SelectSingleNode("//div[contains(@class, 'detail_body') and contains(@class, 'banner')]");
|
|
||||||
|
|
||||||
Regex regex = new Regex(@"url\((?<url>.*?)\)");
|
|
||||||
Match match = regex.Match(posterNode.GetAttributeValue("style", ""));
|
|
||||||
|
|
||||||
string coverUrl = match.Groups["url"].Value;
|
|
||||||
|
|
||||||
string genre = infoNode1.SelectSingleNode(".//h2[contains(@class, 'genre')]")
|
|
||||||
.InnerText.Trim();
|
|
||||||
List<MangaTag> mangaTags = [new MangaTag(genre)];
|
|
||||||
|
|
||||||
List<HtmlNode> authorsNodes = infoNode1.SelectSingleNode(".//div[contains(@class, 'author_area')]").Descendants("a").ToList();
|
|
||||||
List<Author> authors = authorsNodes.Select(node => new Author(node.InnerText.Trim())).ToList();
|
|
||||||
|
|
||||||
string originalLanguage = "";
|
|
||||||
|
|
||||||
uint year = 0;
|
|
||||||
|
|
||||||
string status1 = infoNode2.SelectSingleNode(".//p").InnerText;
|
|
||||||
string status2 = infoNode2.SelectSingleNode(".//p/span").InnerText;
|
|
||||||
MangaReleaseStatus releaseStatus = MangaReleaseStatus.Unreleased;
|
|
||||||
if(status2.Length == 0 || status1.ToLower() == "completed") {
|
|
||||||
releaseStatus = MangaReleaseStatus.Completed;
|
|
||||||
} else if(status2.ToLower() == "up") {
|
|
||||||
releaseStatus = MangaReleaseStatus.Continuing;
|
|
||||||
}
|
|
||||||
|
|
||||||
Manga manga = new (publicationId, sortName, description, websiteUrl, coverUrl, null, year,
|
|
||||||
originalLanguage, releaseStatus, -1,
|
|
||||||
this,
|
|
||||||
authors,
|
|
||||||
mangaTags,
|
|
||||||
[],
|
|
||||||
[]);
|
|
||||||
|
|
||||||
return (manga, authors, mangaTags, [], []);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Done
|
|
||||||
public override Chapter[] GetChapters(Manga manga, string language = "en")
|
|
||||||
{
|
|
||||||
PublicationManager pm = new(manga.MangaId);
|
|
||||||
string requestUrl = $"https://www.webtoons.com/en/{pm.Category}/{pm.Title}/list?title_no={pm.Id}";
|
|
||||||
// Leaving this in for verification if the page exists
|
|
||||||
RequestResult requestResult =
|
|
||||||
downloadClient.MakeRequest(requestUrl, RequestType.Default);
|
|
||||||
if ((int)requestResult.statusCode < 200 || (int)requestResult.statusCode >= 300)
|
|
||||||
return Array.Empty<Chapter>();
|
|
||||||
|
|
||||||
// Get number of pages
|
|
||||||
int pages = requestResult.htmlDocument.DocumentNode
|
|
||||||
.SelectNodes("//div[contains(@class, 'paginate')]/a")
|
|
||||||
.ToList()
|
|
||||||
.Count;
|
|
||||||
List<Chapter> chapters = new List<Chapter>();
|
|
||||||
|
|
||||||
for(int page = 1; page <= pages; page++) {
|
|
||||||
string pageRequestUrl = $"{requestUrl}&page={page}";
|
|
||||||
chapters.AddRange(ParseChaptersFromHtml(manga, pageRequestUrl));
|
|
||||||
}
|
|
||||||
|
|
||||||
return chapters.Order().ToArray();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Done
|
|
||||||
private List<Chapter> ParseChaptersFromHtml(Manga manga, string mangaUrl)
|
|
||||||
{
|
|
||||||
RequestResult result = downloadClient.MakeRequest(mangaUrl, RequestType.Default);
|
|
||||||
if ((int)result.statusCode < 200 || (int)result.statusCode >= 300 || result.htmlDocument is null)
|
|
||||||
{
|
|
||||||
return new List<Chapter>();
|
|
||||||
}
|
|
||||||
|
|
||||||
List<Chapter> ret = new();
|
|
||||||
|
|
||||||
foreach (HtmlNode chapterInfo in result.htmlDocument.DocumentNode.SelectNodes("//ul/li[contains(@class, '_episodeItem')]"))
|
|
||||||
{
|
|
||||||
HtmlNode infoNode = chapterInfo.SelectSingleNode(".//a");
|
|
||||||
string url = infoNode.GetAttributeValue("href", "");
|
|
||||||
|
|
||||||
string id = chapterInfo.GetAttributeValue("id", "");
|
|
||||||
if(id == "") continue;
|
|
||||||
string chapterNumber = chapterInfo.GetAttributeValue("data-episode-no", "");
|
|
||||||
if(chapterNumber == "") continue;
|
|
||||||
string chapterName = infoNode.SelectSingleNode(".//span[contains(@class, 'subj')]/span").InnerText.Trim();
|
|
||||||
ret.Add(new Chapter(manga, url, chapterNumber, null, chapterName));
|
|
||||||
}
|
|
||||||
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
internal override string[] GetChapterImageUrls(Chapter chapter)
|
|
||||||
{
|
|
||||||
string requestUrl = chapter.Url;
|
|
||||||
// Leaving this in to check if the page exists
|
|
||||||
RequestResult requestResult =
|
|
||||||
downloadClient.MakeRequest(requestUrl, RequestType.Default);
|
|
||||||
if ((int)requestResult.statusCode < 200 || (int)requestResult.statusCode >= 300)
|
|
||||||
{
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
string[] imageUrls = ParseImageUrlsFromHtml(requestUrl);
|
|
||||||
return imageUrls;
|
|
||||||
}
|
|
||||||
|
|
||||||
private string[] ParseImageUrlsFromHtml(string mangaUrl)
|
|
||||||
{
|
|
||||||
RequestResult requestResult =
|
|
||||||
downloadClient.MakeRequest(mangaUrl, RequestType.Default);
|
|
||||||
if ((int)requestResult.statusCode < 200 || (int)requestResult.statusCode >= 300)
|
|
||||||
{
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
if (requestResult.htmlDocument is null)
|
|
||||||
{
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
return requestResult.htmlDocument.DocumentNode
|
|
||||||
.SelectNodes("//*[@id='_imageList']/img")
|
|
||||||
.Select(node =>
|
|
||||||
node.GetAttributeValue("data-url", ""))
|
|
||||||
.ToArray();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
internal class PublicationManager {
|
|
||||||
public PublicationManager(string title = "", string category = "", string id = "") {
|
|
||||||
this.Title = title;
|
|
||||||
this.Category = category;
|
|
||||||
this.Id = id;
|
|
||||||
}
|
|
||||||
|
|
||||||
public PublicationManager(string publicationId) {
|
|
||||||
string[] parts = publicationId.Split("|");
|
|
||||||
if(parts.Length == 3) {
|
|
||||||
this.Title = parts[0];
|
|
||||||
this.Category = parts[1];
|
|
||||||
this.Id = parts[2];
|
|
||||||
} else {
|
|
||||||
this.Title = "";
|
|
||||||
this.Category = "";
|
|
||||||
this.Id = "";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public string getPublicationId() {
|
|
||||||
return $"{this.Title}|{this.Category}|{this.Id}";
|
|
||||||
}
|
|
||||||
|
|
||||||
public string Title { get; set; }
|
|
||||||
public string Category { get; set; }
|
|
||||||
public string Id { get; set; }
|
|
||||||
}
|
|
@ -1,175 +0,0 @@
|
|||||||
using System.Net;
|
|
||||||
using System.Text.RegularExpressions;
|
|
||||||
using API.MangaDownloadClients;
|
|
||||||
using HtmlAgilityPack;
|
|
||||||
|
|
||||||
namespace API.Schema.MangaConnectors;
|
|
||||||
|
|
||||||
public class Weebcentral : MangaConnector
|
|
||||||
{
|
|
||||||
private readonly string[] _filterWords =
|
|
||||||
{ "a", "the", "of", "as", "to", "no", "for", "on", "with", "be", "and", "in", "wa", "at", "be", "ni" };
|
|
||||||
|
|
||||||
public Weebcentral() : base("Weebcentral", ["en"], ["weebcentral.com"], "https://weebcentral.com/favicon.ico")
|
|
||||||
{
|
|
||||||
downloadClient = new ChromiumDownloadClient();
|
|
||||||
}
|
|
||||||
|
|
||||||
public override (Manga, List<Author>?, List<MangaTag>?, List<Link>?, List<MangaAltTitle>?)[] GetManga(string publicationTitle = "")
|
|
||||||
{
|
|
||||||
const int limit = 32; //How many values we want returned at once
|
|
||||||
var offset = 0; //"Page"
|
|
||||||
var requestUrl =
|
|
||||||
$"https://{BaseUris[0]}/search/data?limit={limit}&offset={offset}&text={publicationTitle}&sort=Best+Match&order=Ascending&official=Any&display_mode=Minimal%20Display";
|
|
||||||
var requestResult =
|
|
||||||
downloadClient.MakeRequest(requestUrl, RequestType.Default);
|
|
||||||
if ((int)requestResult.statusCode < 200 || (int)requestResult.statusCode >= 300 ||
|
|
||||||
requestResult.htmlDocument == null)
|
|
||||||
{
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
var publications = ParsePublicationsFromHtml(requestResult.htmlDocument);
|
|
||||||
|
|
||||||
return publications;
|
|
||||||
}
|
|
||||||
|
|
||||||
private (Manga, List<Author>?, List<MangaTag>?, List<Link>?, List<MangaAltTitle>?)[] ParsePublicationsFromHtml(HtmlDocument document)
|
|
||||||
{
|
|
||||||
if (document.DocumentNode.SelectNodes("//article").Count < 1)
|
|
||||||
return [];
|
|
||||||
|
|
||||||
var urls = document.DocumentNode.SelectNodes("/html/body/article/a[contains(concat(' ',normalize-space(@class),' '),' link ')]")
|
|
||||||
.Select(elem => elem.GetAttributeValue("href", "")).ToList();
|
|
||||||
|
|
||||||
List<(Manga, List<Author>?, List<MangaTag>?, List<Link>?, List<MangaAltTitle>?)> ret = new();
|
|
||||||
foreach (var url in urls)
|
|
||||||
{
|
|
||||||
(Manga, List<Author>?, List<MangaTag>?, List<Link>?, List<MangaAltTitle>?)? manga = GetMangaFromUrl(url);
|
|
||||||
if (manga is { })
|
|
||||||
ret.Add(((Manga, List<Author>?, List<MangaTag>?, List<Link>?, List<MangaAltTitle>?))manga);
|
|
||||||
}
|
|
||||||
|
|
||||||
return ret.ToArray();
|
|
||||||
}
|
|
||||||
|
|
||||||
public override (Manga, List<Author>?, List<MangaTag>?, List<Link>?, List<MangaAltTitle>?)? GetMangaFromUrl(string url)
|
|
||||||
{
|
|
||||||
Regex publicationIdRex = new(@"https:\/\/weebcentral\.com\/series\/(\w*)\/(.*)");
|
|
||||||
var publicationId = publicationIdRex.Match(url).Groups[1].Value;
|
|
||||||
|
|
||||||
var requestResult = downloadClient.MakeRequest(url, RequestType.MangaInfo);
|
|
||||||
if ((int)requestResult.statusCode < 300 && (int)requestResult.statusCode >= 200 &&
|
|
||||||
requestResult.htmlDocument is not null)
|
|
||||||
return ParseSinglePublicationFromHtml(requestResult.htmlDocument, publicationId, url);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private (Manga, List<Author>?, List<MangaTag>?, List<Link>?, List<MangaAltTitle>?) ParseSinglePublicationFromHtml(HtmlDocument document, string publicationId, string websiteUrl)
|
|
||||||
{
|
|
||||||
HtmlNode posterNode =
|
|
||||||
document.DocumentNode.SelectSingleNode("//section[@class='flex items-center justify-center']/picture/img");
|
|
||||||
string posterUrl = posterNode?.GetAttributeValue("src", "") ?? "";
|
|
||||||
|
|
||||||
HtmlNode titleNode = document.DocumentNode.SelectSingleNode("//section/h1");
|
|
||||||
string sortName = titleNode?.InnerText ?? "Undefined";
|
|
||||||
|
|
||||||
HtmlNode[] authorsNodes =
|
|
||||||
document.DocumentNode.SelectNodes("//ul/li[strong/text() = 'Author(s): ']/span").ToArray();
|
|
||||||
List<Author> authors = authorsNodes.Select(n => new Author(n.InnerText)).ToList();
|
|
||||||
|
|
||||||
HtmlNode[] genreNodes =
|
|
||||||
document.DocumentNode.SelectNodes("//ul/li[strong/text() = 'Tags(s): ']/span").ToArray();
|
|
||||||
List<MangaTag> tags = genreNodes.Select(n => new MangaTag(n.InnerText.EndsWith(',') ? n.InnerText.Substring(0,n.InnerText.Length-1) : n.InnerText)).ToList();
|
|
||||||
|
|
||||||
HtmlNode statusNode = document.DocumentNode.SelectSingleNode("//ul/li[strong/text() = 'Status: ']/a");
|
|
||||||
string statusText = statusNode?.InnerText ?? "";
|
|
||||||
MangaReleaseStatus releaseStatus = statusText.ToLower() switch
|
|
||||||
{
|
|
||||||
"cancelled" => MangaReleaseStatus.Cancelled,
|
|
||||||
"hiatus" => MangaReleaseStatus.OnHiatus,
|
|
||||||
"complete" => MangaReleaseStatus.Completed,
|
|
||||||
"ongoing" => MangaReleaseStatus.Continuing,
|
|
||||||
_ => MangaReleaseStatus.Unreleased
|
|
||||||
};
|
|
||||||
|
|
||||||
HtmlNode yearNode = document.DocumentNode.SelectSingleNode("//ul/li[strong/text() = 'Released: ']/span");
|
|
||||||
uint year = Convert.ToUInt32(yearNode?.InnerText ?? "0");
|
|
||||||
|
|
||||||
HtmlNode descriptionNode = document.DocumentNode.SelectSingleNode("//ul/li[strong/text() = 'Description']/p");
|
|
||||||
string description = descriptionNode?.InnerText ?? "Undefined";
|
|
||||||
|
|
||||||
HtmlNode[] altTitleNodes = document.DocumentNode
|
|
||||||
.SelectNodes("//ul/li[strong/text() = 'Associated Name(s)']/ul/li")?.ToArray() ?? [];
|
|
||||||
List<MangaAltTitle> altTitles = altTitleNodes.Select(n => new MangaAltTitle("", n.InnerText)).ToList();
|
|
||||||
|
|
||||||
Manga m = new(publicationId, sortName, description, websiteUrl, posterUrl, null, year, null, releaseStatus, -1,
|
|
||||||
this, authors, tags, [], altTitles);
|
|
||||||
return (m, authors, tags, [], altTitles);
|
|
||||||
}
|
|
||||||
|
|
||||||
public override (Manga, List<Author>?, List<MangaTag>?, List<Link>?, List<MangaAltTitle>?)? GetMangaFromId(string publicationId)
|
|
||||||
{
|
|
||||||
return GetMangaFromUrl($"https://{BaseUris[0]}/series/{publicationId}");
|
|
||||||
}
|
|
||||||
|
|
||||||
public override Chapter[] GetChapters(Manga manga, string language = "en")
|
|
||||||
{
|
|
||||||
var requestUrl = $"https://{BaseUris[0]}/series/{manga.MangaConnectorId}/full-chapter-list";
|
|
||||||
var requestResult =
|
|
||||||
downloadClient.MakeRequest(requestUrl, RequestType.Default);
|
|
||||||
if ((int)requestResult.statusCode < 200 || (int)requestResult.statusCode >= 300)
|
|
||||||
return [];
|
|
||||||
|
|
||||||
//Return Chapters ordered by Chapter-Number
|
|
||||||
if (requestResult.htmlDocument is null)
|
|
||||||
return [];
|
|
||||||
var chapters = ParseChaptersFromHtml(manga, requestResult.htmlDocument);
|
|
||||||
return chapters.Order().ToArray();
|
|
||||||
}
|
|
||||||
|
|
||||||
internal override string[] GetChapterImageUrls(Chapter chapter)
|
|
||||||
{
|
|
||||||
var requestResult = downloadClient.MakeRequest(chapter.Url, RequestType.Default);
|
|
||||||
if (requestResult.htmlDocument is null)
|
|
||||||
return [];
|
|
||||||
|
|
||||||
var document = requestResult.htmlDocument;
|
|
||||||
|
|
||||||
var imageNodes =
|
|
||||||
document.DocumentNode.SelectNodes($"//section[@hx-get='{chapter.Url}/images']/img")?.ToArray() ?? [];
|
|
||||||
var urls = imageNodes.Select(imgNode => imgNode.GetAttributeValue("src", "")).ToArray();
|
|
||||||
|
|
||||||
return urls;
|
|
||||||
}
|
|
||||||
|
|
||||||
private List<Chapter> ParseChaptersFromHtml(Manga manga, HtmlDocument document)
|
|
||||||
{
|
|
||||||
var chaptersWrapper = document.DocumentNode.SelectSingleNode("/html/body");
|
|
||||||
|
|
||||||
Regex chapterRex = new(@"(\d+(?:\.\d+)*)");
|
|
||||||
Regex idRex = new(@"https:\/\/weebcentral\.com\/chapters\/(\w*)");
|
|
||||||
|
|
||||||
var ret = chaptersWrapper.Descendants("a").Select(elem =>
|
|
||||||
{
|
|
||||||
var url = elem.GetAttributeValue("href", "") ?? "Undefined";
|
|
||||||
|
|
||||||
if (!url.StartsWith("https://") && !url.StartsWith("http://"))
|
|
||||||
return new Chapter(manga, "", "");
|
|
||||||
|
|
||||||
var idMatch = idRex.Match(url);
|
|
||||||
var id = idMatch.Success ? idMatch.Groups[1].Value : null;
|
|
||||||
|
|
||||||
var chapterNode = elem.SelectSingleNode("span[@class='grow flex items-center gap-2']/span")?.InnerText ??
|
|
||||||
"Undefined";
|
|
||||||
|
|
||||||
var chapterNumberMatch = chapterRex.Match(chapterNode);
|
|
||||||
var chapterNumber = chapterNumberMatch.Success ? chapterNumberMatch.Groups[1].Value : "-1";
|
|
||||||
|
|
||||||
return new Chapter(manga, url, chapterNumber);
|
|
||||||
}).Where(elem => elem.ChapterNumber != String.Empty && elem.Url != string.Empty).ToList();
|
|
||||||
|
|
||||||
ret.Reverse();
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
}
|
|
@ -14,100 +14,159 @@ public class PgsqlContext(DbContextOptions<PgsqlContext> options) : DbContext(op
|
|||||||
public DbSet<LocalLibrary> LocalLibraries { get; set; }
|
public DbSet<LocalLibrary> LocalLibraries { get; set; }
|
||||||
public DbSet<Chapter> Chapters { get; set; }
|
public DbSet<Chapter> Chapters { get; set; }
|
||||||
public DbSet<Author> Authors { get; set; }
|
public DbSet<Author> Authors { get; set; }
|
||||||
public DbSet<Link> Links { get; set; }
|
|
||||||
public DbSet<MangaTag> Tags { get; set; }
|
public DbSet<MangaTag> Tags { get; set; }
|
||||||
public DbSet<MangaAltTitle> AltTitles { get; set; }
|
|
||||||
public DbSet<LibraryConnector> LibraryConnectors { get; set; }
|
public DbSet<LibraryConnector> LibraryConnectors { get; set; }
|
||||||
public DbSet<NotificationConnector> NotificationConnectors { get; set; }
|
public DbSet<NotificationConnector> NotificationConnectors { get; set; }
|
||||||
public DbSet<Notification> Notifications { get; set; }
|
public DbSet<Notification> Notifications { get; set; }
|
||||||
|
|
||||||
protected override void OnModelCreating(ModelBuilder modelBuilder)
|
protected override void OnModelCreating(ModelBuilder modelBuilder)
|
||||||
{
|
{
|
||||||
modelBuilder.Entity<MangaConnector>()
|
//Job Types
|
||||||
.HasDiscriminator(c => c.Name)
|
|
||||||
.HasValue<Global>("Global")
|
|
||||||
.HasValue<AsuraToon>("AsuraToon")
|
|
||||||
.HasValue<Bato>("Bato")
|
|
||||||
.HasValue<MangaHere>("MangaHere")
|
|
||||||
.HasValue<MangaKatana>("MangaKatana")
|
|
||||||
.HasValue<Mangaworld>("Mangaworld")
|
|
||||||
.HasValue<ManhuaPlus>("ManhuaPlus")
|
|
||||||
.HasValue<Weebcentral>("Weebcentral")
|
|
||||||
.HasValue<Manganato>("Manganato")
|
|
||||||
.HasValue<MangaDex>("MangaDex");
|
|
||||||
modelBuilder.Entity<LibraryConnector>()
|
|
||||||
.HasDiscriminator<LibraryType>(l => l.LibraryType)
|
|
||||||
.HasValue<Komga>(LibraryType.Komga)
|
|
||||||
.HasValue<Kavita>(LibraryType.Kavita);
|
|
||||||
|
|
||||||
modelBuilder.Entity<Job>()
|
modelBuilder.Entity<Job>()
|
||||||
.HasDiscriminator<JobType>(j => j.JobType)
|
.HasDiscriminator(j => j.JobType)
|
||||||
.HasValue<MoveFileOrFolderJob>(JobType.MoveFileOrFolderJob)
|
.HasValue<MoveFileOrFolderJob>(JobType.MoveFileOrFolderJob)
|
||||||
|
.HasValue<MoveMangaLibraryJob>(JobType.MoveMangaLibraryJob)
|
||||||
.HasValue<DownloadAvailableChaptersJob>(JobType.DownloadAvailableChaptersJob)
|
.HasValue<DownloadAvailableChaptersJob>(JobType.DownloadAvailableChaptersJob)
|
||||||
.HasValue<DownloadSingleChapterJob>(JobType.DownloadSingleChapterJob)
|
.HasValue<DownloadSingleChapterJob>(JobType.DownloadSingleChapterJob)
|
||||||
.HasValue<DownloadMangaCoverJob>(JobType.DownloadMangaCoverJob)
|
.HasValue<DownloadMangaCoverJob>(JobType.DownloadMangaCoverJob)
|
||||||
.HasValue<UpdateMetadataJob>(JobType.UpdateMetaDataJob)
|
|
||||||
.HasValue<RetrieveChaptersJob>(JobType.RetrieveChaptersJob)
|
.HasValue<RetrieveChaptersJob>(JobType.RetrieveChaptersJob)
|
||||||
.HasValue<UpdateFilesDownloadedJob>(JobType.UpdateFilesDownloadedJob);
|
.HasValue<UpdateFilesDownloadedJob>(JobType.UpdateFilesDownloadedJob);
|
||||||
|
|
||||||
|
//Job specification
|
||||||
|
modelBuilder.Entity<DownloadAvailableChaptersJob>()
|
||||||
|
.HasOne<Manga>(j => j.Manga)
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey(j => j.MangaId)
|
||||||
|
.IsRequired()
|
||||||
|
.OnDelete(DeleteBehavior.Cascade);
|
||||||
|
modelBuilder.Entity<DownloadAvailableChaptersJob>()
|
||||||
|
.Navigation(j => j.Manga)
|
||||||
|
.AutoInclude();
|
||||||
|
modelBuilder.Entity<DownloadMangaCoverJob>()
|
||||||
|
.HasOne<Manga>(j => j.Manga)
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey(j => j.MangaId)
|
||||||
|
.IsRequired()
|
||||||
|
.OnDelete(DeleteBehavior.Cascade);
|
||||||
|
modelBuilder.Entity<DownloadMangaCoverJob>()
|
||||||
|
.Navigation(j => j.Manga)
|
||||||
|
.AutoInclude();
|
||||||
|
modelBuilder.Entity<DownloadSingleChapterJob>()
|
||||||
|
.HasOne<Chapter>(j => j.Chapter)
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey(j => j.ChapterId)
|
||||||
|
.IsRequired()
|
||||||
|
.OnDelete(DeleteBehavior.Cascade);
|
||||||
|
modelBuilder.Entity<DownloadSingleChapterJob>()
|
||||||
|
.Navigation(j => j.Chapter)
|
||||||
|
.AutoInclude();
|
||||||
|
modelBuilder.Entity<MoveMangaLibraryJob>()
|
||||||
|
.HasOne<Manga>(j => j.Manga)
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey(j => j.MangaId)
|
||||||
|
.IsRequired()
|
||||||
|
.OnDelete(DeleteBehavior.Cascade);
|
||||||
|
modelBuilder.Entity<MoveMangaLibraryJob>()
|
||||||
|
.Navigation(j => j.Manga)
|
||||||
|
.AutoInclude();
|
||||||
|
modelBuilder.Entity<MoveMangaLibraryJob>()
|
||||||
|
.HasOne<LocalLibrary>(j => j.ToLibrary)
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey(j => j.ToLibraryId)
|
||||||
|
.IsRequired()
|
||||||
|
.OnDelete(DeleteBehavior.Cascade);
|
||||||
|
modelBuilder.Entity<MoveMangaLibraryJob>()
|
||||||
|
.Navigation(j => j.ToLibrary)
|
||||||
|
.AutoInclude();
|
||||||
|
modelBuilder.Entity<RetrieveChaptersJob>()
|
||||||
|
.HasOne<Manga>(j => j.Manga)
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey(j => j.MangaId)
|
||||||
|
.IsRequired()
|
||||||
|
.OnDelete(DeleteBehavior.Cascade);
|
||||||
|
modelBuilder.Entity<RetrieveChaptersJob>()
|
||||||
|
.Navigation(j => j.Manga)
|
||||||
|
.AutoInclude();
|
||||||
|
|
||||||
|
//Job has possible ParentJob
|
||||||
modelBuilder.Entity<Job>()
|
modelBuilder.Entity<Job>()
|
||||||
.HasMany<Job>()
|
.HasMany<Job>()
|
||||||
.WithOne(j => j.ParentJob)
|
.WithOne(childJob => childJob.ParentJob)
|
||||||
|
.HasForeignKey(childjob => childjob.ParentJobId)
|
||||||
|
.IsRequired(false)
|
||||||
.OnDelete(DeleteBehavior.Cascade);
|
.OnDelete(DeleteBehavior.Cascade);
|
||||||
|
//Job might be dependent on other Jobs
|
||||||
modelBuilder.Entity<Job>()
|
modelBuilder.Entity<Job>()
|
||||||
.HasMany<Job>(j => j.DependsOnJobs)
|
.HasMany<Job>(root => root.DependsOnJobs)
|
||||||
.WithMany();
|
.WithMany();
|
||||||
modelBuilder.Entity<UpdateMetadataJob>()
|
modelBuilder.Entity<Job>()
|
||||||
.Navigation(umj => umj.Manga)
|
.Navigation(root => root.DependsOnJobs)
|
||||||
.AutoInclude();
|
.AutoInclude(false);
|
||||||
|
|
||||||
modelBuilder.Entity<Manga>()
|
//MangaConnector Types
|
||||||
.HasOne<MangaConnector>(m => m.MangaConnector)
|
modelBuilder.Entity<MangaConnector>()
|
||||||
.WithMany()
|
.HasDiscriminator(c => c.Name)
|
||||||
.HasForeignKey(m => m.MangaConnectorId)
|
.HasValue<Global>("Global")
|
||||||
|
.HasValue<MangaDex>("MangaDex");
|
||||||
|
//MangaConnector is responsible for many Manga
|
||||||
|
modelBuilder.Entity<MangaConnector>()
|
||||||
|
.HasMany<Manga>()
|
||||||
|
.WithOne(m => m.MangaConnector)
|
||||||
|
.HasForeignKey(m => m.MangaConnectorName)
|
||||||
|
.IsRequired()
|
||||||
.OnDelete(DeleteBehavior.Cascade);
|
.OnDelete(DeleteBehavior.Cascade);
|
||||||
modelBuilder.Entity<Manga>()
|
modelBuilder.Entity<Manga>()
|
||||||
.Navigation(m => m.MangaConnector)
|
.Navigation(m => m.MangaConnector)
|
||||||
.AutoInclude();
|
.AutoInclude();
|
||||||
|
|
||||||
|
//Manga has many Chapters
|
||||||
modelBuilder.Entity<Manga>()
|
modelBuilder.Entity<Manga>()
|
||||||
.HasOne<LocalLibrary>(m => m.Library)
|
.HasMany<Chapter>(m => m.Chapters)
|
||||||
.WithMany()
|
.WithOne(c => c.ParentManga)
|
||||||
.OnDelete(DeleteBehavior.Restrict);
|
|
||||||
modelBuilder.Entity<Manga>()
|
|
||||||
.Navigation(m => m.Library)
|
|
||||||
.AutoInclude();
|
|
||||||
modelBuilder.Entity<Manga>()
|
|
||||||
.HasMany<Author>(m => m.Authors)
|
|
||||||
.WithMany();
|
|
||||||
modelBuilder.Entity<Manga>()
|
|
||||||
.Navigation(m => m.Authors)
|
|
||||||
.AutoInclude();
|
|
||||||
modelBuilder.Entity<Manga>()
|
|
||||||
.HasMany<MangaTag>(m => m.MangaTags)
|
|
||||||
.WithMany();
|
|
||||||
modelBuilder.Entity<Manga>()
|
|
||||||
.Navigation(m => m.MangaTags)
|
|
||||||
.AutoInclude();
|
|
||||||
modelBuilder.Entity<Manga>()
|
|
||||||
.HasMany<Link>(m => m.Links)
|
|
||||||
.WithOne()
|
|
||||||
.OnDelete(DeleteBehavior.Cascade);
|
|
||||||
modelBuilder.Entity<Manga>()
|
|
||||||
.Navigation(m => m.Links)
|
|
||||||
.AutoInclude();
|
|
||||||
modelBuilder.Entity<Manga>()
|
|
||||||
.HasMany<MangaAltTitle>(m => m.AltTitles)
|
|
||||||
.WithOne()
|
|
||||||
.OnDelete(DeleteBehavior.Cascade);
|
|
||||||
modelBuilder.Entity<Manga>()
|
|
||||||
.Navigation(m => m.AltTitles)
|
|
||||||
.AutoInclude();
|
|
||||||
modelBuilder.Entity<Chapter>()
|
|
||||||
.HasOne<Manga>(c => c.ParentManga)
|
|
||||||
.WithMany()
|
|
||||||
.HasForeignKey(c => c.ParentMangaId)
|
.HasForeignKey(c => c.ParentMangaId)
|
||||||
|
.IsRequired()
|
||||||
.OnDelete(DeleteBehavior.Cascade);
|
.OnDelete(DeleteBehavior.Cascade);
|
||||||
modelBuilder.Entity<Chapter>()
|
modelBuilder.Entity<Chapter>()
|
||||||
.Navigation(c => c.ParentManga)
|
.Navigation(c => c.ParentManga)
|
||||||
.AutoInclude();
|
.AutoInclude();
|
||||||
|
//Manga owns MangaAltTitles
|
||||||
|
modelBuilder.Entity<Manga>()
|
||||||
|
.OwnsMany<MangaAltTitle>(m => m.AltTitles)
|
||||||
|
.WithOwner();
|
||||||
|
//Manga owns Links
|
||||||
|
modelBuilder.Entity<Manga>()
|
||||||
|
.OwnsMany<Link>(m => m.Links)
|
||||||
|
.WithOwner();
|
||||||
|
//Manga has many Tags associated with many Manga
|
||||||
|
modelBuilder.Entity<Manga>()
|
||||||
|
.HasMany<MangaTag>(m => m.MangaTags)
|
||||||
|
.WithMany()
|
||||||
|
.UsingEntity("MangaTagToManga",
|
||||||
|
l=> l.HasOne(typeof(MangaTag)).WithMany().HasForeignKey("MangaTagIds").HasPrincipalKey(nameof(MangaTag.Tag)),
|
||||||
|
r => r.HasOne(typeof(Manga)).WithMany().HasForeignKey("MangaIds").HasPrincipalKey(nameof(Manga.MangaId)),
|
||||||
|
j => j.HasKey("MangaTagIds", "MangaIds")
|
||||||
|
);
|
||||||
|
//Manga has many Authors associated with many Manga
|
||||||
|
modelBuilder.Entity<Manga>()
|
||||||
|
.HasMany<Author>(m => m.Authors)
|
||||||
|
.WithMany()
|
||||||
|
.UsingEntity("AuthorToManga",
|
||||||
|
l=> l.HasOne(typeof(Author)).WithMany().HasForeignKey("AuthorIds").HasPrincipalKey(nameof(Author.AuthorId)),
|
||||||
|
r => r.HasOne(typeof(Manga)).WithMany().HasForeignKey("MangaIds").HasPrincipalKey(nameof(Manga.MangaId)),
|
||||||
|
j => j.HasKey("AuthorIds", "MangaIds")
|
||||||
|
);
|
||||||
|
|
||||||
|
//LocalLibrary has many Mangas
|
||||||
|
modelBuilder.Entity<LocalLibrary>()
|
||||||
|
.HasMany<Manga>()
|
||||||
|
.WithOne(m => m.Library)
|
||||||
|
.HasForeignKey(m => m.LibraryId)
|
||||||
|
.OnDelete(DeleteBehavior.SetNull);
|
||||||
|
|
||||||
|
//LibraryConnector Types
|
||||||
|
modelBuilder.Entity<LibraryConnector>()
|
||||||
|
.HasDiscriminator(l => l.LibraryType)
|
||||||
|
.HasValue<Komga>(LibraryType.Komga)
|
||||||
|
.HasValue<Kavita>(LibraryType.Kavita);
|
||||||
}
|
}
|
||||||
}
|
}
|
172
API/Tranga.cs
172
API/Tranga.cs
@ -119,50 +119,35 @@ public static class Tranga
|
|||||||
Log.Info("JobStarter Thread running.");
|
Log.Info("JobStarter Thread running.");
|
||||||
while (true)
|
while (true)
|
||||||
{
|
{
|
||||||
List<Job> completedJobs = context.Jobs.Where(j => j.state >= JobState.Completed).ToList();
|
//Update finished Jobs to new states
|
||||||
Log.Debug($"Completed jobs: {completedJobs.Count}");
|
List<Job> completedJobs = context.Jobs.Where(j => j.state == JobState.Completed).ToList();
|
||||||
foreach (Job job in completedJobs)
|
foreach (Job completedJob in completedJobs)
|
||||||
if (job.RecurrenceMs <= 0)
|
if (completedJob.RecurrenceMs <= 0)
|
||||||
context.Jobs.Remove(job);
|
context.Jobs.Remove(completedJob);
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
if (job.state >= JobState.Failed)
|
completedJob.state = JobState.CompletedWaiting;
|
||||||
job.Enabled = false;
|
completedJob.LastExecution = DateTime.UtcNow;
|
||||||
else
|
|
||||||
job.state = JobState.Waiting;
|
|
||||||
job.LastExecution = DateTime.UtcNow;
|
|
||||||
}
|
}
|
||||||
|
List<Job> failedJobs = context.Jobs.Where(j => j.state == JobState.Failed).ToList();
|
||||||
List<Job> runJobs = context.Jobs.Where(j => j.state <= JobState.Running && j.Enabled == true).ToList()
|
foreach (Job failedJob in failedJobs)
|
||||||
.Where(j => j.NextExecution < DateTime.UtcNow).ToList();
|
|
||||||
IEnumerable<Job> orderedJobs = OrderJobs(runJobs, context).ToList();
|
|
||||||
Log.Debug($"Jobs Due: {runJobs.Count} Running: {RunningJobs.Count} Ordered: {orderedJobs.Count()}");
|
|
||||||
foreach (Job job in orderedJobs)
|
|
||||||
{
|
{
|
||||||
// If the job is already running, skip it
|
failedJob.Enabled = false;
|
||||||
if (RunningJobs.Values.Any(j => j.JobId == job.JobId)) continue;
|
failedJob.LastExecution = DateTime.UtcNow;
|
||||||
|
}
|
||||||
|
|
||||||
//If a Job for that connector is already running, skip it
|
//Retrieve waiting and due Jobs
|
||||||
if (job is DownloadAvailableChaptersJob dncj)
|
List<Job> waitingJobs = context.Jobs.Where(j =>
|
||||||
{
|
j.Enabled && (j.state == JobState.FirstExecution || j.state == JobState.CompletedWaiting)).ToList();
|
||||||
if (RunningJobs.Values.Any(j =>
|
List<Job> runningJobs = context.Jobs.Where(j => j.state == JobState.Running).ToList();
|
||||||
j is DownloadAvailableChaptersJob rdncj &&
|
List<Job> dueJobs = waitingJobs.Where(j => j.NextExecution < DateTime.UtcNow).ToList();
|
||||||
context.Mangas.Find(rdncj.MangaId)?.MangaConnector == context.Mangas.Find(dncj.MangaId)?.MangaConnector))
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (job is DownloadSingleChapterJob dscj)
|
|
||||||
{
|
|
||||||
if (RunningJobs.Values.Any(j =>
|
|
||||||
j is DownloadSingleChapterJob rdscj &&
|
|
||||||
context.Chapters.Find(rdscj.ChapterId)?.ParentManga?.MangaConnector ==
|
|
||||||
context.Chapters.Find(dscj.ChapterId)?.ParentManga?.MangaConnector))
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
List<MangaConnector> busyConnectors = GetBusyConnectors(runningJobs);
|
||||||
|
List<Job> startJobs = FilterJobPreconditions(dueJobs, busyConnectors);
|
||||||
|
|
||||||
|
//Start Jobs that are allowed to run (preconditions match)
|
||||||
|
foreach (Job job in startJobs)
|
||||||
|
{
|
||||||
Thread t = new(() =>
|
Thread t = new(() =>
|
||||||
{
|
{
|
||||||
job.Run(serviceProvider);
|
job.Run(serviceProvider);
|
||||||
@ -170,6 +155,10 @@ public static class Tranga
|
|||||||
RunningJobs.Add(t, job);
|
RunningJobs.Add(t, job);
|
||||||
t.Start();
|
t.Start();
|
||||||
}
|
}
|
||||||
|
Log.Debug($"Jobs Completed: {completedJobs.Count} Failed: {failedJobs.Count} Running: {runningJobs.Count}\n" +
|
||||||
|
$"Waiting: {waitingJobs.Count}\n" +
|
||||||
|
$"\tof which Due: {dueJobs.Count}\n" +
|
||||||
|
$"\t\tof which Started: {startJobs.Count}");
|
||||||
|
|
||||||
(Thread, Job)[] removeFromThreadsList = RunningJobs.Where(t => !t.Key.IsAlive)
|
(Thread, Job)[] removeFromThreadsList = RunningJobs.Where(t => !t.Key.IsAlive)
|
||||||
.Select(t => (t.Key, t.Value)).ToArray();
|
.Select(t => (t.Key, t.Value)).ToArray();
|
||||||
@ -191,84 +180,39 @@ public static class Tranga
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static IEnumerable<Job> OrderJobs(List<Job> jobs, PgsqlContext context)
|
private static List<MangaConnector> GetBusyConnectors(List<Job> runningJobs)
|
||||||
{
|
{
|
||||||
Dictionary<JobType, List<Job>> jobsByType = new();
|
HashSet<MangaConnector> busyConnectors = new();
|
||||||
foreach (Job job in jobs)
|
foreach (Job runningJob in runningJobs)
|
||||||
if(!jobsByType.TryAdd(job.JobType, [job]))
|
{
|
||||||
jobsByType[job.JobType].Add(job);
|
if(GetJobConnector(runningJob) is { } mangaConnector)
|
||||||
|
busyConnectors.Add(mangaConnector);
|
||||||
|
}
|
||||||
|
return busyConnectors.ToList();
|
||||||
|
}
|
||||||
|
|
||||||
IEnumerable<Job> ret = new List<Job>();
|
private static List<Job> FilterJobPreconditions(List<Job> dueJobs, List<MangaConnector> busyConnectors) =>
|
||||||
if(jobsByType.ContainsKey(JobType.MoveMangaLibraryJob))
|
dueJobs
|
||||||
ret = ret.Concat(jobsByType[JobType.MoveMangaLibraryJob]);
|
.Where(j => j.DependenciesFulfilled)
|
||||||
if(jobsByType.ContainsKey(JobType.MoveFileOrFolderJob))
|
.Where(j =>
|
||||||
ret = ret.Concat(jobsByType[JobType.MoveFileOrFolderJob]);
|
{
|
||||||
if(jobsByType.ContainsKey(JobType.DownloadMangaCoverJob))
|
//Filter jobs with busy connectors
|
||||||
ret = ret.Concat(jobsByType[JobType.DownloadMangaCoverJob]);
|
if (GetJobConnector(j) is { } mangaConnector)
|
||||||
if(jobsByType.ContainsKey(JobType.UpdateFilesDownloadedJob))
|
return busyConnectors.Contains(mangaConnector) == false;
|
||||||
ret = ret.Concat(jobsByType[JobType.UpdateFilesDownloadedJob]);
|
return true;
|
||||||
|
})
|
||||||
|
.ToList();
|
||||||
|
|
||||||
Dictionary<MangaConnector, List<Job>> metadataJobsByConnector = new();
|
private static MangaConnector? GetJobConnector(Job job)
|
||||||
if (jobsByType.ContainsKey(JobType.DownloadAvailableChaptersJob))
|
{
|
||||||
{
|
if (job is DownloadAvailableChaptersJob dacj)
|
||||||
foreach (DownloadAvailableChaptersJob job in jobsByType[JobType.DownloadAvailableChaptersJob])
|
return dacj.Manga.MangaConnector;
|
||||||
{
|
if (job is DownloadMangaCoverJob dmcj)
|
||||||
Manga? manga = context.Mangas.Find(job.MangaId);
|
return dmcj.Manga.MangaConnector;
|
||||||
if(manga is null)
|
if (job is DownloadSingleChapterJob dscj)
|
||||||
continue;
|
return dscj.Chapter.ParentManga.MangaConnector;
|
||||||
MangaConnector connector = manga.MangaConnector ?? context.MangaConnectors.Find(manga.MangaConnectorId)!;
|
if (job is RetrieveChaptersJob rcj)
|
||||||
if(!metadataJobsByConnector.TryAdd(connector, [job]))
|
return rcj.Manga.MangaConnector;
|
||||||
metadataJobsByConnector[connector].Add(job);
|
return null;
|
||||||
}
|
|
||||||
}
|
|
||||||
if (jobsByType.ContainsKey(JobType.UpdateMetaDataJob))
|
|
||||||
{
|
|
||||||
foreach (UpdateMetadataJob job in jobsByType[JobType.UpdateMetaDataJob])
|
|
||||||
{
|
|
||||||
Manga manga = job.Manga ?? context.Mangas.Find(job.MangaId)!;
|
|
||||||
MangaConnector connector = manga.MangaConnector ?? context.MangaConnectors.Find(manga.MangaConnectorId)!;
|
|
||||||
if(!metadataJobsByConnector.TryAdd(connector, [job]))
|
|
||||||
metadataJobsByConnector[connector].Add(job);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (jobsByType.ContainsKey(JobType.RetrieveChaptersJob))
|
|
||||||
{
|
|
||||||
foreach (RetrieveChaptersJob job in jobsByType[JobType.RetrieveChaptersJob])
|
|
||||||
{
|
|
||||||
Manga? manga = context.Mangas.Find(job.MangaId);
|
|
||||||
if(manga is null)
|
|
||||||
continue;
|
|
||||||
MangaConnector connector = manga.MangaConnector ?? context.MangaConnectors.Find(manga.MangaConnectorId)!;
|
|
||||||
if(!metadataJobsByConnector.TryAdd(connector, [job]))
|
|
||||||
metadataJobsByConnector[connector].Add(job);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
foreach (List<Job> metadataJobs in metadataJobsByConnector.Values)
|
|
||||||
ret = ret.Append(metadataJobs.MinBy(j => j.NextExecution))!;
|
|
||||||
|
|
||||||
if (jobsByType.ContainsKey(JobType.DownloadSingleChapterJob))
|
|
||||||
{
|
|
||||||
|
|
||||||
Dictionary<MangaConnector, List<DownloadSingleChapterJob>> downloadJobsByConnector = new();
|
|
||||||
foreach (DownloadSingleChapterJob job in jobsByType[JobType.DownloadSingleChapterJob])
|
|
||||||
{
|
|
||||||
Chapter? chapter = context.Chapters.Find(job.ChapterId);
|
|
||||||
if(chapter is null)
|
|
||||||
continue;
|
|
||||||
Manga manga = chapter.ParentManga ?? context.Mangas.Find(chapter.ParentMangaId)!;
|
|
||||||
MangaConnector connector = manga.MangaConnector ?? context.MangaConnectors.Find(manga.MangaConnectorId)!;
|
|
||||||
|
|
||||||
if(!downloadJobsByConnector.TryAdd(connector, [job]))
|
|
||||||
downloadJobsByConnector[connector].Add(job);
|
|
||||||
}
|
|
||||||
//From all jobs select those that are supposed to be executed soonest, then select the minimum chapternumber
|
|
||||||
foreach (List<DownloadSingleChapterJob> downloadJobs in downloadJobsByConnector.Values)
|
|
||||||
ret = ret.Append(
|
|
||||||
downloadJobs.Where(j => j.NextExecution == downloadJobs
|
|
||||||
.MinBy(mj => mj.NextExecution)!.NextExecution)
|
|
||||||
.MinBy(j => context.Chapters.Find(j.ChapterId)!))!;
|
|
||||||
}
|
|
||||||
|
|
||||||
return ret;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
Loading…
x
Reference in New Issue
Block a user