diff --git a/API/Controllers/JobController.cs b/API/Controllers/JobController.cs
deleted file mode 100644
index f47317a..0000000
--- a/API/Controllers/JobController.cs
+++ /dev/null
@@ -1,389 +0,0 @@
-using API.APIEndpointRecords;
-using API.Schema;
-using API.Schema.Contexts;
-using API.Schema.Jobs;
-using Asp.Versioning;
-using log4net;
-using Microsoft.AspNetCore.Mvc;
-using Microsoft.AspNetCore.Mvc.ModelBinding;
-using static Microsoft.AspNetCore.Http.StatusCodes;
-// ReSharper disable InconsistentNaming
-
-namespace API.Controllers;
-
-[ApiVersion(2)]
-[ApiController]
-[Route("v{version:apiVersion}/[controller]")]
-public class JobController(PgsqlContext context, ILog Log) : Controller
-{
- ///
- /// Returns all Jobs
- ///
- ///
- [HttpGet]
- [ProducesResponseType(Status200OK, "application/json")]
- public IActionResult GetAllJobs()
- {
- Job[] ret = context.Jobs.ToArray();
- return Ok(ret);
- }
-
- ///
- /// Returns Jobs with requested Job-IDs
- ///
- /// Array of Job-IDs
- ///
- [HttpPost("WithIDs")]
- [ProducesResponseType(Status200OK, "application/json")]
- public IActionResult GetJobs([FromBody]string[] ids)
- {
- Job[] ret = context.Jobs.Where(job => ids.Contains(job.Key)).ToArray();
- return Ok(ret);
- }
-
- ///
- /// Get all Jobs in requested State
- ///
- /// Requested Job-State
- ///
- [HttpGet("State/{JobState}")]
- [ProducesResponseType(Status200OK, "application/json")]
- public IActionResult GetJobsInState(JobState JobState)
- {
- Job[] jobsInState = context.Jobs.Where(job => job.state == JobState).ToArray();
- return Ok(jobsInState);
- }
-
- ///
- /// Returns all Jobs of requested Type
- ///
- /// Requested Job-Type
- ///
- [HttpGet("Type/{JobType}")]
- [ProducesResponseType(Status200OK, "application/json")]
- public IActionResult GetJobsOfType(JobType JobType)
- {
- Job[] jobsOfType = context.Jobs.Where(job => job.JobType == JobType).ToArray();
- return Ok(jobsOfType);
- }
-
- ///
- /// Returns all Jobs of requested Type and State
- ///
- /// Requested Job-Type
- /// Requested Job-State
- ///
- [HttpGet("TypeAndState/{JobType}/{JobState}")]
- [ProducesResponseType(Status200OK, "application/json")]
- public IActionResult GetJobsOfType(JobType JobType, JobState JobState)
- {
- Job[] jobsOfType = context.Jobs.Where(job => job.JobType == JobType && job.state == JobState).ToArray();
- return Ok(jobsOfType);
- }
-
- ///
- /// Return Job with ID
- ///
- /// Job-ID
- ///
- /// Job with ID could not be found
- [HttpGet("{JobId}")]
- [ProducesResponseType(Status200OK, "application/json")]
- [ProducesResponseType(Status404NotFound)]
- public IActionResult GetJob(string JobId)
- {
- Job? ret = context.Jobs.Find(JobId);
- return (ret is not null) switch
- {
- true => Ok(ret),
- false => NotFound()
- };
- }
-
- ///
- /// Create a new DownloadAvailableChaptersJob
- ///
- /// ID of Obj
- /// Job-Configuration
- /// Job-IDs
- /// Could not find ToFileLibrary with ID
- /// Could not find Obj with ID
- /// Error during Database Operation
- [HttpPut("DownloadAvailableChaptersJob/{MangaId}")]
- [ProducesResponseType(Status201Created, "application/json")]
- [ProducesResponseType(Status400BadRequest)]
- [ProducesResponseType(Status404NotFound)]
- [ProducesResponseType(Status500InternalServerError, "text/plain")]
- public IActionResult CreateDownloadAvailableChaptersJob(string MangaId, [FromBody]DownloadAvailableChaptersJobRecord record)
- {
- if (context.Mangas.Find(MangaId) is not { } m)
- return NotFound();
- else
- {
- try
- {
- FileLibrary? l = context.LocalLibraries.Find(record.localLibraryId);
- if (l is null)
- return BadRequest();
- m.Library = l;
- context.SaveChanges();
- }
- catch (Exception e)
- {
- Log.Error(e);
- return StatusCode(500, e.Message);
- }
- }
- Job retrieveChapters = new RetrieveChaptersJob(m, record.language, record.recurrenceTimeMs);
- Job updateFilesDownloaded =
- new UpdateChaptersDownloadedJob(m, record.recurrenceTimeMs, dependsOnJobs: [retrieveChapters]);
- Job downloadChapters = new DownloadAvailableChaptersJob(m, record.recurrenceTimeMs, dependsOnJobs: [retrieveChapters, updateFilesDownloaded]);
- Job UpdateCover = new UpdateCoverJob(m, record.recurrenceTimeMs, downloadChapters);
- retrieveChapters.ParentJob = downloadChapters;
- updateFilesDownloaded.ParentJob = retrieveChapters;
- return AddJobs([retrieveChapters, downloadChapters, updateFilesDownloaded, UpdateCover]);
- }
-
- ///
- /// Create a new DownloadSingleChapterJob
- ///
- /// ID of the Chapter
- /// Job-IDs
- /// Could not find Chapter with ID
- /// Error during Database Operation
- [HttpPut("DownloadSingleChapterJob/{ChapterId}")]
- [ProducesResponseType(Status201Created, "application/json")]
- [ProducesResponseType(Status404NotFound)]
- [ProducesResponseType(Status500InternalServerError, "text/plain")]
- public IActionResult CreateNewDownloadChapterJob(string ChapterId)
- {
- if(context.Chapters.Find(ChapterId) is not { } c)
- return NotFound();
- Job job = new DownloadSingleChapterJob(c);
- return AddJobs([job]);
- }
-
- ///
- /// Create a new UpdateChaptersDownloadedJob
- ///
- /// ID of the Obj
- /// Job-IDs
- /// Could not find Obj with ID
- /// Error during Database Operation
- [HttpPut("UpdateFilesJob/{MangaId}")]
- [ProducesResponseType(Status201Created, "application/json")]
- [ProducesResponseType(Status404NotFound)]
- [ProducesResponseType(Status500InternalServerError, "text/plain")]
- public IActionResult CreateUpdateFilesDownloadedJob(string MangaId)
- {
- if(context.Mangas.Find(MangaId) is not { } m)
- return NotFound();
- Job job = new UpdateChaptersDownloadedJob(m, 0);
- return AddJobs([job]);
- }
-
- ///
- /// Create a new UpdateMetadataJob for all Obj
- ///
- /// Job-IDs
- /// Error during Database Operation
- [HttpPut("UpdateAllFilesJob")]
- [ProducesResponseType(Status201Created, "application/json")]
- [ProducesResponseType(Status500InternalServerError, "text/plain")]
- public IActionResult CreateUpdateAllFilesDownloadedJob()
- {
- List jobs = context.Mangas.Select(m => new UpdateChaptersDownloadedJob(m, 0, null, null)).ToList();
- try
- {
- context.Jobs.AddRange(jobs);
- context.SaveChanges();
- return Created();
- }
- catch (Exception e)
- {
- Log.Error(e);
- return StatusCode(500, e.Message);
- }
- }
-
- ///
- /// Not Implemented: Create a new UpdateMetadataJob
- ///
- /// ID of the Obj
- /// Job-IDs
- /// Could not find Obj with ID
- /// Error during Database Operation
- [HttpPut("UpdateMetadataJob/{MangaId}")]
- [ProducesResponseType(Status201Created, "application/json")]
- [ProducesResponseType(Status404NotFound)]
- [ProducesResponseType(Status500InternalServerError, "text/plain")]
- public IActionResult CreateUpdateMetadataJob(string MangaId)
- {
- return StatusCode(Status501NotImplemented);
- }
-
- ///
- /// Not Implemented: Create a new UpdateMetadataJob for all Obj
- ///
- /// Job-IDs
- /// Error during Database Operation
- [HttpPut("UpdateAllMetadataJob")]
- [ProducesResponseType(Status201Created, "application/json")]
- [ProducesResponseType(Status500InternalServerError, "text/plain")]
- public IActionResult CreateUpdateAllMetadataJob()
- {
- return StatusCode(Status501NotImplemented);
- }
-
- private IActionResult AddJobs(Job[] jobs)
- {
- try
- {
- context.Jobs.AddRange(jobs);
- context.SaveChanges();
- return new CreatedResult((string?)null, jobs.Select(j => j.Key).ToArray());
- }
- catch (Exception e)
- {
- Log.Error(e);
- return StatusCode(500, e.Message);
- }
- }
-
- ///
- /// Delete Job with ID and all children
- ///
- /// Job-ID
- ///
- /// Job could not be found
- /// Error during Database Operation
- [HttpDelete("{JobId}")]
- [ProducesResponseType(Status200OK)]
- [ProducesResponseType(Status404NotFound)]
- [ProducesResponseType(Status500InternalServerError, "text/plain")]
- public IActionResult DeleteJob(string JobId)
- {
- try
- {
- if(context.Jobs.Find(JobId) is not { } ret)
- return NotFound();
-
- context.Remove(ret);
- context.SaveChanges();
- return Ok();
- }
- catch (Exception e)
- {
- Log.Error(e);
- return StatusCode(500, e.Message);
- }
- }
-
- ///
- /// Modify Job with ID
- ///
- /// Job-ID
- /// Fields to modify, set to null to keep previous value
- /// Job modified
- /// Malformed request
- /// Job with ID not found
- /// Error during Database Operation
- [HttpPatch("{JobId}")]
- [ProducesResponseType(Status202Accepted, "application/json")]
- [ProducesResponseType(Status400BadRequest)]
- [ProducesResponseType(Status404NotFound)]
- [ProducesResponseType(Status500InternalServerError, "text/plain")]
- public IActionResult ModifyJob(string JobId, [FromBody]ModifyJobRecord modifyJobRecord)
- {
- try
- {
- Job? ret = context.Jobs.Find(JobId);
- if(ret is null)
- return NotFound();
-
- ret.RecurrenceMs = modifyJobRecord.RecurrenceMs ?? ret.RecurrenceMs;
- ret.Enabled = modifyJobRecord.Enabled ?? ret.Enabled;
-
- context.SaveChanges();
- return new AcceptedResult(ret.Key, ret);
- }
- catch (Exception e)
- {
- Log.Error(e);
- return StatusCode(500, e.Message);
- }
- }
-
- ///
- /// Starts the Job with the requested ID
- ///
- /// Job-ID
- /// Start Jobs necessary for execution
- /// Job started
- /// Job with ID not found
- /// Job was already running
- /// Error during Database Operation
- [HttpPost("{JobId}/Start")]
- [ProducesResponseType(Status202Accepted)]
- [ProducesResponseType(Status404NotFound)]
- [ProducesResponseType(Status409Conflict)]
- [ProducesResponseType(Status500InternalServerError, "text/plain")]
- public IActionResult StartJob(string JobId, [FromBody(EmptyBodyBehavior = EmptyBodyBehavior.Allow)]bool startDependencies = false)
- {
- Job? ret = context.Jobs.Find(JobId);
- if (ret is null)
- return NotFound();
- List dependencies = startDependencies ? ret.GetDependenciesAndSelf() : [ret];
-
- try
- {
- if(dependencies.Any(d => d.state >= JobState.Running && d.state < JobState.Completed))
- return new ConflictResult();
- dependencies.ForEach(d =>
- {
- d.LastExecution = DateTime.UnixEpoch;
- d.state = JobState.CompletedWaiting;
- });
- context.SaveChanges();
- return Accepted();
- }
- catch (Exception e)
- {
- Log.Error(e);
- return StatusCode(500, e.Message);
- }
- }
-
- ///
- /// Stops the Job with the requested ID
- ///
- /// Job-ID
- /// NOT IMPLEMENTED
- [HttpPost("{JobId}/Stop")]
- [ProducesResponseType(Status501NotImplemented)]
- public IActionResult StopJob(string JobId)
- {
- return StatusCode(Status501NotImplemented);
- }
-
- ///
- /// Removes failed and completed Jobs (that are not recurring)
- ///
- /// Job started
- /// Error during Database Operation
- [HttpPost("Cleanup")]
- public IActionResult CleanupJobs()
- {
- try
- {
- context.Jobs.RemoveRange(context.Jobs.Where(j => j.state == JobState.Failed || j.state == JobState.Completed));
- context.SaveChanges();
- return Ok();
- }
- catch (Exception e)
- {
- Log.Error(e);
- return StatusCode(500, e.Message);
- }
- }
-}
\ No newline at end of file
diff --git a/API/Controllers/LibraryConnectorController.cs b/API/Controllers/LibraryConnectorController.cs
index ccf4419..f69fdf8 100644
--- a/API/Controllers/LibraryConnectorController.cs
+++ b/API/Controllers/LibraryConnectorController.cs
@@ -1,5 +1,5 @@
-using API.Schema.Contexts;
-using API.Schema.LibraryConnectors;
+using API.Schema.LibraryContext;
+using API.Schema.LibraryContext.LibraryConnectors;
using Asp.Versioning;
using log4net;
using Microsoft.AspNetCore.Mvc;
diff --git a/API/Controllers/LocalLibrariesController.cs b/API/Controllers/LocalLibrariesController.cs
index 8ca8e6d..8302662 100644
--- a/API/Controllers/LocalLibrariesController.cs
+++ b/API/Controllers/LocalLibrariesController.cs
@@ -1,6 +1,5 @@
using API.APIEndpointRecords;
-using API.Schema;
-using API.Schema.Contexts;
+using API.Schema.MangaContext;
using Asp.Versioning;
using log4net;
using Microsoft.AspNetCore.Mvc;
@@ -11,7 +10,7 @@ namespace API.Controllers;
[ApiVersion(2)]
[ApiController]
[Route("v{v:apiVersion}/[controller]")]
-public class LocalLibrariesController(PgsqlContext context, ILog Log) : Controller
+public class LocalLibrariesController(MangaContext context, ILog Log) : Controller
{
[HttpGet]
[ProducesResponseType(Status200OK, "application/json")]
diff --git a/API/Controllers/MangaConnectorController.cs b/API/Controllers/MangaConnectorController.cs
index ac317ab..60d8077 100644
--- a/API/Controllers/MangaConnectorController.cs
+++ b/API/Controllers/MangaConnectorController.cs
@@ -1,5 +1,5 @@
-using API.Schema.Contexts;
-using API.Schema.MangaConnectors;
+using API.Schema.MangaContext;
+using API.Schema.MangaContext.MangaConnectors;
using Asp.Versioning;
using log4net;
using Microsoft.AspNetCore.Mvc;
@@ -10,7 +10,7 @@ namespace API.Controllers;
[ApiVersion(2)]
[ApiController]
[Route("v{v:apiVersion}/[controller]")]
-public class MangaConnectorController(PgsqlContext context, ILog Log) : Controller
+public class MangaConnectorController(MangaContext context, ILog Log) : Controller
{
///
/// Get all available Connectors (Scanlation-Sites)
diff --git a/API/Controllers/MangaController.cs b/API/Controllers/MangaController.cs
index 5079583..554c339 100644
--- a/API/Controllers/MangaController.cs
+++ b/API/Controllers/MangaController.cs
@@ -1,6 +1,6 @@
-using API.Schema;
-using API.Schema.Contexts;
-using API.Schema.Jobs;
+using API.Schema.MangaContext;
+using API.Schema.MangaContext.MangaConnectors;
+using API.Workers;
using Asp.Versioning;
using log4net;
using Microsoft.AspNetCore.Mvc;
@@ -18,56 +18,58 @@ namespace API.Controllers;
[ApiVersion(2)]
[ApiController]
[Route("v{v:apiVersion}/[controller]")]
-public class MangaController(PgsqlContext context, ILog Log) : Controller
+public class MangaController(IServiceScope scope) : Controller
{
///
- /// Returns all cached Obj
+ /// Returns all cached
///
///
[HttpGet]
[ProducesResponseType(Status200OK, "application/json")]
public IActionResult GetAllManga()
{
+ MangaContext context = scope.ServiceProvider.GetRequiredService();
Manga[] ret = context.Mangas.ToArray();
return Ok(ret);
}
///
- /// Returns all cached Obj with IDs
+ /// Returns all cached with
///
- /// Array of Obj-IDs
+ /// Array of <.Key
///
[HttpPost("WithIDs")]
[ProducesResponseType(Status200OK, "application/json")]
- public IActionResult GetManga([FromBody]string[] ids)
+ public IActionResult GetManga([FromBody]string[] MangaIds)
{
- Manga[] ret = context.Mangas.Where(m => ids.Contains(m.Key)).ToArray();
+ MangaContext context = scope.ServiceProvider.GetRequiredService();
+ Manga[] ret = context.Mangas.Where(m => MangaIds.Contains(m.Key)).ToArray();
return Ok(ret);
}
///
- /// Return Obj with ID
+ /// Return with
///
- /// Obj-ID
+ /// .Key
///
- /// Obj with ID not found
+ /// with not found
[HttpGet("{MangaId}")]
[ProducesResponseType(Status200OK, "application/json")]
[ProducesResponseType(Status404NotFound)]
public IActionResult GetManga(string MangaId)
{
- Manga? ret = context.Mangas.Find(MangaId);
- if (ret is null)
- return NotFound();
- return Ok(ret);
+ MangaContext context = scope.ServiceProvider.GetRequiredService();
+ if (context.Mangas.Find(MangaId) is not { } manga)
+ return NotFound(nameof(MangaId));
+ return Ok(manga);
}
///
- /// Delete Obj with ID
+ /// Delete with
///
- /// Obj-ID
+ /// .Key
///
- /// Obj with ID not found
+ /// < with not found
/// Error during Database Operation
[HttpDelete("{MangaId}")]
[ProducesResponseType(Status200OK)]
@@ -75,62 +77,52 @@ public class MangaController(PgsqlContext context, ILog Log) : Controller
[ProducesResponseType(Status500InternalServerError, "text/plain")]
public IActionResult DeleteManga(string MangaId)
{
- try
- {
- Manga? ret = context.Mangas.Find(MangaId);
- if (ret is null)
- return NotFound();
-
- context.Remove(ret);
- context.SaveChanges();
- return Ok();
- }
- catch (Exception e)
- {
- Log.Error(e);
- return StatusCode(500, e.Message);
- }
+ MangaContext context = scope.ServiceProvider.GetRequiredService();
+ if (context.Mangas.Find(MangaId) is not { } manga)
+ return NotFound(nameof(MangaId));
+
+ context.Mangas.Remove(manga);
+
+ if(context.Sync().Result is { } errorMessage)
+ return StatusCode(Status500InternalServerError, errorMessage);
+ return Ok();
}
-
+
///
- /// Merge two Manga into one. THIS IS NOT REVERSIBLE!
+ /// Merge two into one. THIS IS NOT REVERSIBLE!
///
+ /// .Key of merging data from (getting deleted)
+ /// .Key of merging data into
///
- /// MangaId not found
- /// Error during Database Operation
- [HttpPatch("{MangaIdFrom}/MergeInto/{MangaIdTo}")]
+ /// with or not found
+ [HttpPatch("{MangaIdFrom}/MergeInto/{MangaIdInto}")]
[ProducesResponseType(Status200OK,"image/jpeg")]
[ProducesResponseType(Status404NotFound)]
- [ProducesResponseType(Status500InternalServerError, "text/plain")]
- public IActionResult MergeIntoManga(string MangaIdFrom, string MangaIdTo)
+ public IActionResult MergeIntoManga(string MangaIdFrom, string MangaIdInto)
{
- if(context.Mangas.Find(MangaIdFrom) is not { } from)
- return NotFound(MangaIdFrom);
- if(context.Mangas.Find(MangaIdTo) is not { } to)
- return NotFound(MangaIdTo);
- try
- {
- to.MergeFrom(from, context);
- return Ok();
- }
- catch (DbUpdateException e)
- {
- Log.Error(e);
- return StatusCode(500, e.Message);
- }
+ MangaContext context = scope.ServiceProvider.GetRequiredService();
+ if (context.Mangas.Find(MangaIdFrom) is not { } from)
+ return NotFound(nameof(MangaIdFrom));
+ if (context.Mangas.Find(MangaIdInto) is not { } into)
+ return NotFound(nameof(MangaIdInto));
+
+ BaseWorker[] newJobs = into.MergeFrom(from, context);
+ Tranga.AddWorkers(newJobs);
+
+ return Ok();
}
///
- /// Returns Cover of Obj
+ /// Returns Cover of with
///
- /// Obj-ID
- /// If width is provided, height needs to also be provided
- /// If height is provided, width needs to also be provided
+ /// .Key
+ /// If is provided, needs to also be provided
+ /// If is provided, needs to also be provided
/// JPEG Image
/// Cover not loaded
/// The formatting-request was invalid
- /// Obj with ID not found
+ /// with not found
/// Retry later, downloading cover
[HttpGet("{MangaId}/Cover")]
[ProducesResponseType(Status200OK,"image/jpeg")]
@@ -140,22 +132,22 @@ public class MangaController(PgsqlContext context, ILog Log) : Controller
[ProducesResponseType(Status503ServiceUnavailable, "text/plain")]
public IActionResult GetCover(string MangaId, [FromQuery]int? width, [FromQuery]int? height)
{
- if(context.Mangas.Find(MangaId) is not { } m)
- return NotFound();
+ MangaContext context = scope.ServiceProvider.GetRequiredService();
+ if (context.Mangas.Find(MangaId) is not { } manga)
+ return NotFound(nameof(MangaId));
- if (!System.IO.File.Exists(m.CoverFileNameInCache))
+ if (!System.IO.File.Exists(manga.CoverFileNameInCache))
{
- List coverDownloadJobs = context.Jobs.Where(j => j.JobType == JobType.DownloadMangaCoverJob).Include(j => ((DownloadMangaCoverJob)j).Manga).ToList();
- if (coverDownloadJobs.Any(j => j is DownloadMangaCoverJob dmc && dmc.MangaId == MangaId && dmc.state < JobState.Completed))
+ if (Tranga.GetRunningWorkers().Any(worker => worker is DownloadCoverFromMangaconnectorWorker w && w.MangaConnectorId.ObjId == MangaId))
{
- Response.Headers.Append("Retry-After", $"{TrangaSettings.startNewJobTimeoutMs * coverDownloadJobs.Count() * 2 / 1000:D}");
- return StatusCode(Status503ServiceUnavailable, TrangaSettings.startNewJobTimeoutMs * coverDownloadJobs.Count() * 2 / 1000);
+ Response.Headers.Append("Retry-After", $"{TrangaSettings.workCycleTimeout * 2 / 1000:D}");
+ return StatusCode(Status503ServiceUnavailable, TrangaSettings.workCycleTimeout * 2 / 1000);
}
else
return NoContent();
}
- Image image = Image.Load(m.CoverFileNameInCache);
+ Image image = Image.Load(manga.CoverFileNameInCache);
if (width is { } w && height is { } h)
{
@@ -170,46 +162,48 @@ public class MangaController(PgsqlContext context, ILog Log) : Controller
using MemoryStream ms = new();
image.Save(ms, new JpegEncoder(){Quality = 100});
- DateTime lastModified = new FileInfo(m.CoverFileNameInCache).LastWriteTime;
+ DateTime lastModified = new FileInfo(manga.CoverFileNameInCache).LastWriteTime;
HttpContext.Response.Headers.CacheControl = "public";
return File(ms.GetBuffer(), "image/jpeg", new DateTimeOffset(lastModified), EntityTagHeaderValue.Parse($"\"{lastModified.Ticks}\""));
}
///
- /// Returns all Chapters of Obj
+ /// Returns all of with
///
- /// Obj-ID
+ /// .Key
///
- /// Obj with ID not found
+ /// with not found
[HttpGet("{MangaId}/Chapters")]
[ProducesResponseType(Status200OK, "application/json")]
[ProducesResponseType(Status404NotFound)]
public IActionResult GetChapters(string MangaId)
{
- if(context.Mangas.Find(MangaId) is not { } m)
- return NotFound();
+ MangaContext context = scope.ServiceProvider.GetRequiredService();
+ if (context.Mangas.Find(MangaId) is not { } manga)
+ return NotFound(nameof(MangaId));
- Chapter[] chapters = m.Chapters.ToArray();
+ Chapter[] chapters = manga.Chapters.ToArray();
return Ok(chapters);
}
///
- /// Returns all downloaded Chapters for Obj with ID
+ /// Returns all downloaded for with
///
- /// Obj-ID
+ /// .Key
///
/// No available chapters
- /// Obj with ID not found.
+ /// with not found.
[HttpGet("{MangaId}/Chapters/Downloaded")]
[ProducesResponseType(Status200OK, "application/json")]
[ProducesResponseType(Status204NoContent)]
[ProducesResponseType(Status404NotFound)]
public IActionResult GetChaptersDownloaded(string MangaId)
{
- if(context.Mangas.Find(MangaId) is not { } m)
- return NotFound();
-
- List chapters = m.Chapters.ToList();
+ MangaContext context = scope.ServiceProvider.GetRequiredService();
+ if (context.Mangas.Find(MangaId) is not { } manga)
+ return NotFound(nameof(MangaId));
+
+ List chapters = manga.Chapters.Where(c => c.Downloaded).ToList();
if (chapters.Count == 0)
return NoContent();
@@ -217,22 +211,23 @@ public class MangaController(PgsqlContext context, ILog Log) : Controller
}
///
- /// Returns all Chapters not downloaded for Obj with ID
+ /// Returns all not downloaded for with
///
- /// Obj-ID
+ /// .Key
///
/// No available chapters
- /// Obj with ID not found.
+ /// with not found.
[HttpGet("{MangaId}/Chapters/NotDownloaded")]
[ProducesResponseType(Status200OK, "application/json")]
[ProducesResponseType(Status204NoContent)]
[ProducesResponseType(Status404NotFound)]
public IActionResult GetChaptersNotDownloaded(string MangaId)
{
- if(context.Mangas.Find(MangaId) is not { } m)
- return NotFound();
+ MangaContext context = scope.ServiceProvider.GetRequiredService();
+ if (context.Mangas.Find(MangaId) is not { } manga)
+ return NotFound(nameof(MangaId));
- List chapters = m.Chapters.ToList();
+ List chapters = manga.Chapters.Where(c => c.Downloaded == false).ToList();
if (chapters.Count == 0)
return NoContent();
@@ -240,13 +235,13 @@ public class MangaController(PgsqlContext context, ILog Log) : Controller
}
///
- /// Returns the latest Chapter of requested Obj available on Website
+ /// Returns the latest of requested available on
///
- /// Obj-ID
+ /// .Key
///
/// No available chapters
- /// Obj with ID not found.
- /// Could not retrieve the maximum chapter-number
+ /// with not found.
+ /// Could not retrieve the maximum chapter-number
/// Retry after timeout, updating value
[HttpGet("{MangaId}/Chapter/LatestAvailable")]
[ProducesResponseType(Status200OK, "application/json")]
@@ -256,130 +251,115 @@ public class MangaController(PgsqlContext context, ILog Log) : Controller
[ProducesResponseType(Status503ServiceUnavailable, "text/plain")]
public IActionResult GetLatestChapter(string MangaId)
{
- if(context.Mangas.Find(MangaId) is not { } m)
- return NotFound();
+ MangaContext context = scope.ServiceProvider.GetRequiredService();
+ if (context.Mangas.Find(MangaId) is not { } manga)
+ return NotFound(nameof(MangaId));
- List chapters = m.Chapters.ToList();
+ List chapters = manga.Chapters.ToList();
if (chapters.Count == 0)
{
- List retrieveChapterJobs = context.Jobs.Where(j => j.JobType == JobType.RetrieveChaptersJob).Include(j => ((RetrieveChaptersJob)j).Manga).ToList();
- if (retrieveChapterJobs.Any(j => j is RetrieveChaptersJob rcj && rcj.MangaId == MangaId && rcj.state < JobState.Completed))
+ if (Tranga.GetRunningWorkers().Any(worker => worker is RetrieveMangaChaptersFromMangaconnectorWorker w && w.MangaConnectorId.ObjId == MangaId && w.State < WorkerExecutionState.Completed))
{
- Response.Headers.Append("Retry-After", $"{TrangaSettings.startNewJobTimeoutMs * retrieveChapterJobs.Count() * 2 / 1000:D}");
- return StatusCode(Status503ServiceUnavailable, TrangaSettings.startNewJobTimeoutMs * retrieveChapterJobs.Count() * 2/ 1000);
+ Response.Headers.Append("Retry-After", $"{TrangaSettings.workCycleTimeout * 2 / 1000:D}");
+ return StatusCode(Status503ServiceUnavailable, TrangaSettings.workCycleTimeout * 2/ 1000);
}else
return Ok(0);
}
Chapter? max = chapters.Max();
if (max is null)
- return StatusCode(500, "Max chapter could not be found");
+ return StatusCode(Status500InternalServerError, "Max chapter could not be found");
return Ok(max);
}
///
- /// Returns the latest Chapter of requested Obj that is downloaded
+ /// Returns the latest of requested that is downloaded
///
- /// Obj-ID
+ /// .Key
///
/// No available chapters
- /// Obj with ID not found.
- /// Could not retrieve the maximum chapter-number
+ /// with not found.
+ /// Could not retrieve the maximum chapter-number
/// Retry after timeout, updating value
[HttpGet("{MangaId}/Chapter/LatestDownloaded")]
[ProducesResponseType(Status200OK, "application/json")]
[ProducesResponseType(Status204NoContent)]
[ProducesResponseType(Status404NotFound)]
- [ProducesResponseType(Status500InternalServerError, "text/plain")]
+ [ProducesResponseType(Status412PreconditionFailed, "text/plain")]
[ProducesResponseType(Status503ServiceUnavailable, "text/plain")]
public IActionResult GetLatestChapterDownloaded(string MangaId)
{
- if(context.Mangas.Find(MangaId) is not { } m)
- return NotFound();
+ MangaContext context = scope.ServiceProvider.GetRequiredService();
+ if (context.Mangas.Find(MangaId) is not { } manga)
+ return NotFound(nameof(MangaId));
- List chapters = m.Chapters.ToList();
+ List chapters = manga.Chapters.ToList();
if (chapters.Count == 0)
{
- List retrieveChapterJobs = context.Jobs.Where(j => j.JobType == JobType.RetrieveChaptersJob).Include(j => ((RetrieveChaptersJob)j).Manga).ToList();
- if (retrieveChapterJobs.Any(j => j is RetrieveChaptersJob rcj && rcj.MangaId == MangaId && rcj.state < JobState.Completed))
+ if (Tranga.GetRunningWorkers().Any(worker => worker is RetrieveMangaChaptersFromMangaconnectorWorker w && w.MangaConnectorId.ObjId == MangaId && w.State < WorkerExecutionState.Completed))
{
- Response.Headers.Append("Retry-After", $"{TrangaSettings.startNewJobTimeoutMs * retrieveChapterJobs.Count() * 2 / 1000:D}");
- return StatusCode(Status503ServiceUnavailable, TrangaSettings.startNewJobTimeoutMs * retrieveChapterJobs.Count() * 2 / 1000);
+ Response.Headers.Append("Retry-After", $"{TrangaSettings.workCycleTimeout * 2 / 1000:D}");
+ return StatusCode(Status503ServiceUnavailable, TrangaSettings.workCycleTimeout * 2/ 1000);
}else
return NoContent();
}
Chapter? max = chapters.Max();
if (max is null)
- return StatusCode(500, "Max chapter could not be found");
+ return StatusCode(Status412PreconditionFailed, "Max chapter could not be found");
return Ok(max);
}
///
- /// Configure the cut-off for Obj
+ /// Configure the cut-off for
///
- /// Obj-ID
- /// Threshold (Chapter Number)
- ///
- /// Obj with ID not found.
+ /// .Key
+ /// Threshold ( ChapterNumber)
+ ///
+ /// with not found.
/// Error during Database Operation
[HttpPatch("{MangaId}/IgnoreChaptersBefore")]
- [ProducesResponseType(Status200OK)]
+ [ProducesResponseType(Status202Accepted)]
[ProducesResponseType(Status404NotFound)]
[ProducesResponseType(Status500InternalServerError, "text/plain")]
public IActionResult IgnoreChaptersBefore(string MangaId, [FromBody]float chapterThreshold)
{
- Manga? m = context.Mangas.Find(MangaId);
- if (m is null)
+ MangaContext context = scope.ServiceProvider.GetRequiredService();
+ if (context.Mangas.Find(MangaId) is not { } manga)
return NotFound();
- try
- {
- m.IgnoreChaptersBefore = chapterThreshold;
- context.SaveChanges();
- return Ok();
- }
- catch (Exception e)
- {
- Log.Error(e);
- return StatusCode(500, e.Message);
- }
+ manga.IgnoreChaptersBefore = chapterThreshold;
+ if(context.Sync().Result is { } errorMessage)
+ return StatusCode(Status500InternalServerError, errorMessage);
+
+ return Accepted();
}
///
- /// Move Obj to different ToFileLibrary
+ /// Move to different
///
- /// Obj-ID
- /// ToFileLibrary-Id
+ /// .Key
+ /// .Key
/// Folder is going to be moved
- /// MangaId or LibraryId not found
- /// Error during Database Operation
+ /// or not found
[HttpPost("{MangaId}/ChangeLibrary/{LibraryId}")]
[ProducesResponseType(Status202Accepted)]
[ProducesResponseType(Status404NotFound)]
- [ProducesResponseType(Status500InternalServerError, "text/plain")]
public IActionResult MoveFolder(string MangaId, string LibraryId)
{
+ MangaContext context = scope.ServiceProvider.GetRequiredService();
if (context.Mangas.Find(MangaId) is not { } manga)
- return NotFound();
+ return NotFound(nameof(MangaId));
if(context.LocalLibraries.Find(LibraryId) is not { } library)
- return NotFound();
+ return NotFound(nameof(LibraryId));
- MoveMangaLibraryJob moveLibrary = new(manga, library);
- UpdateChaptersDownloadedJob updateDownloadedFiles = new(manga, 0, dependsOnJobs: [moveLibrary]);
+ MoveMangaLibraryWorker moveLibrary = new(manga, library, scope);
+ UpdateChaptersDownloadedWorker updateDownloadedFiles = new(manga, scope, [moveLibrary]);
- try
- {
- context.Jobs.AddRange(moveLibrary, updateDownloadedFiles);
- context.SaveChanges();
- return Accepted();
- }
- catch (Exception e)
- {
- Log.Error(e);
- return StatusCode(500, e.Message);
- }
+ Tranga.AddWorkers([moveLibrary, updateDownloadedFiles]);
+
+ return Accepted();
}
}
\ No newline at end of file
diff --git a/API/Controllers/MetadataFetcherController.cs b/API/Controllers/MetadataFetcherController.cs
index 33f67b0..4653a2b 100644
--- a/API/Controllers/MetadataFetcherController.cs
+++ b/API/Controllers/MetadataFetcherController.cs
@@ -1,6 +1,5 @@
-using API.Schema;
-using API.Schema.Contexts;
-using API.Schema.MetadataFetchers;
+using API.Schema.MangaContext;
+using API.Schema.MangaContext.MetadataFetchers;
using Asp.Versioning;
using log4net;
using Microsoft.AspNetCore.Mvc;
@@ -13,7 +12,7 @@ namespace API.Controllers;
[ApiVersion(2)]
[ApiController]
[Route("v{v:apiVersion}/[controller]")]
-public class MetadataFetcherController(PgsqlContext context, ILog Log) : Controller
+public class MetadataFetcherController(MangaContext context, ILog Log) : Controller
{
///
/// Get all available Connectors (Metadata-Sites)
diff --git a/API/Controllers/NotificationConnectorController.cs b/API/Controllers/NotificationConnectorController.cs
index 4ef4655..e521ad5 100644
--- a/API/Controllers/NotificationConnectorController.cs
+++ b/API/Controllers/NotificationConnectorController.cs
@@ -1,7 +1,7 @@
using System.Text;
using API.APIEndpointRecords;
-using API.Schema.Contexts;
-using API.Schema.NotificationConnectors;
+using API.Schema.NotificationsContext;
+using API.Schema.NotificationsContext.NotificationConnectors;
using Asp.Versioning;
using log4net;
using Microsoft.AspNetCore.Mvc;
diff --git a/API/Controllers/QueryController.cs b/API/Controllers/QueryController.cs
index bfa2160..d258260 100644
--- a/API/Controllers/QueryController.cs
+++ b/API/Controllers/QueryController.cs
@@ -1,5 +1,4 @@
-using API.Schema;
-using API.Schema.Contexts;
+using API.Schema.MangaContext;
using Asp.Versioning;
using log4net;
using Microsoft.AspNetCore.Mvc;
@@ -11,7 +10,7 @@ namespace API.Controllers;
[ApiVersion(2)]
[ApiController]
[Route("v{v:apiVersion}/[controller]")]
-public class QueryController(PgsqlContext context, ILog Log) : Controller
+public class QueryController(MangaContext context, ILog Log) : Controller
{
///
/// Returns the Author-Information for Author-ID
diff --git a/API/Controllers/SearchController.cs b/API/Controllers/SearchController.cs
index 7b10ec3..9edd200 100644
--- a/API/Controllers/SearchController.cs
+++ b/API/Controllers/SearchController.cs
@@ -1,5 +1,4 @@
-using API.Schema;
-using API.Schema.Contexts;
+using API.Schema.MangaContext;
using Asp.Versioning;
using log4net;
using Microsoft.AspNetCore.Mvc;
@@ -13,7 +12,7 @@ namespace API.Controllers;
[ApiVersion(2)]
[ApiController]
[Route("v{v:apiVersion}/[controller]")]
-public class SearchController(PgsqlContext context, ILog Log) : Controller
+public class SearchController(MangaContext context, ILog Log) : Controller
{
///
/// Initiate a search for a Obj on a specific Connector
@@ -104,7 +103,7 @@ public class SearchController(PgsqlContext context, ILog Log) : Controller
private Manga? AddMangaToContext((Manga, MangaConnectorId) manga) => AddMangaToContext(manga.Item1, manga.Item2, context);
- internal static Manga? AddMangaToContext(Manga addManga, MangaConnectorId addMcId, PgsqlContext context)
+ internal static Manga? AddMangaToContext(Manga addManga, MangaConnectorId addMcId, MangaContext context)
{
Manga manga = context.Mangas.Find(addManga.Key) ?? addManga;
MangaConnectorId mcId = context.MangaConnectorToManga.Find(addMcId.Key) ?? addMcId;
diff --git a/API/Controllers/SettingsController.cs b/API/Controllers/SettingsController.cs
index 3fdc2e3..09a2a48 100644
--- a/API/Controllers/SettingsController.cs
+++ b/API/Controllers/SettingsController.cs
@@ -1,7 +1,6 @@
using API.MangaDownloadClients;
-using API.Schema;
-using API.Schema.Contexts;
-using API.Schema.Jobs;
+using API.Schema.JobsContext.Jobs;
+using API.Schema.MangaContext;
using Asp.Versioning;
using log4net;
using Microsoft.AspNetCore.Mvc;
@@ -13,7 +12,7 @@ namespace API.Controllers;
[ApiVersion(2)]
[ApiController]
[Route("v{v:apiVersion}/[controller]")]
-public class SettingsController(PgsqlContext context, ILog Log) : Controller
+public class SettingsController(MangaContext context, ILog Log) : Controller
{
///
/// Get all Settings
diff --git a/API/Controllers/WorkerController.cs b/API/Controllers/WorkerController.cs
new file mode 100644
index 0000000..4349433
--- /dev/null
+++ b/API/Controllers/WorkerController.cs
@@ -0,0 +1,193 @@
+using API.APIEndpointRecords;
+using API.Schema.MangaContext;
+using API.Workers;
+using Asp.Versioning;
+using log4net;
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.AspNetCore.Mvc.ModelBinding;
+using static Microsoft.AspNetCore.Http.StatusCodes;
+// ReSharper disable InconsistentNaming
+
+namespace API.Controllers;
+
+[ApiVersion(2)]
+[ApiController]
+[Route("v{version:apiVersion}/[controller]")]
+public class WorkerController(ILog Log) : Controller
+{
+ ///
+ /// Returns all
+ ///
+ ///
+ [HttpGet]
+ [ProducesResponseType(Status200OK, "application/json")]
+ public IActionResult GetAllWorkers()
+ {
+ return Ok(Tranga.Workers.ToArray());
+ }
+
+ ///
+ /// Returns with requested
+ ///
+ /// Array of .Key
+ ///
+ [HttpPost("WithIDs")]
+ [ProducesResponseType(Status200OK, "application/json")]
+ public IActionResult GetJobs([FromBody]string[] WorkerIds)
+ {
+ return Ok(Tranga.Workers.Where(worker => WorkerIds.Contains(worker.Key)).ToArray());
+ }
+
+ ///
+ /// Get all in requested
+ ///
+ /// Requested
+ ///
+ [HttpGet("State/{State}")]
+ [ProducesResponseType(Status200OK, "application/json")]
+ public IActionResult GetJobsInState(WorkerExecutionState State)
+ {
+ return Ok(Tranga.Workers.Where(worker => worker.State == State).ToArray());
+ }
+
+ ///
+ /// Return with
+ ///
+ /// .Key
+ ///
+ /// with could not be found
+ [HttpGet("{WorkerId}")]
+ [ProducesResponseType(Status200OK, "application/json")]
+ [ProducesResponseType(Status404NotFound)]
+ public IActionResult GetJob(string WorkerId)
+ {
+ if(Tranga.Workers.FirstOrDefault(w => w.Key == WorkerId) is not { } worker)
+ return NotFound(nameof(WorkerId));
+ return Ok(worker);
+ }
+
+ ///
+ /// Delete with and all child-s
+ ///
+ /// .Key
+ ///
+ /// with could not be found
+ [HttpDelete("{WorkerId}")]
+ [ProducesResponseType(Status200OK)]
+ [ProducesResponseType(Status404NotFound)]
+ public IActionResult DeleteJob(string WorkerId)
+ {
+ if(Tranga.Workers.FirstOrDefault(w => w.Key == WorkerId) is not { } worker)
+ return NotFound(nameof(WorkerId));
+ Tranga.RemoveWorker(worker);
+ return Ok();
+ }
+
+ ///
+ /// Modify Job with ID
+ ///
+ /// Job-ID
+ /// Fields to modify, set to null to keep previous value
+ /// Job modified
+ /// Malformed request
+ /// Job with ID not found
+ /// Error during Database Operation
+ [HttpPatch("{JobId}")]
+ [ProducesResponseType(Status202Accepted, "application/json")]
+ [ProducesResponseType(Status400BadRequest)]
+ [ProducesResponseType(Status404NotFound)]
+ [ProducesResponseType(Status500InternalServerError, "text/plain")]
+ public IActionResult ModifyJob(string JobId, [FromBody]ModifyJobRecord modifyJobRecord)
+ {
+ try
+ {
+ Job? ret = context.Jobs.Find(JobId);
+ if(ret is null)
+ return NotFound();
+
+ ret.RecurrenceMs = modifyJobRecord.RecurrenceMs ?? ret.RecurrenceMs;
+ ret.Enabled = modifyJobRecord.Enabled ?? ret.Enabled;
+
+ context.SaveChanges();
+ return new AcceptedResult(ret.Key, ret);
+ }
+ catch (Exception e)
+ {
+ Log.Error(e);
+ return StatusCode(500, e.Message);
+ }
+ }
+
+ ///
+ /// Starts the Job with the requested ID
+ ///
+ /// Job-ID
+ /// Start Jobs necessary for execution
+ /// Job started
+ /// Job with ID not found
+ /// Job was already running
+ /// Error during Database Operation
+ [HttpPost("{JobId}/Start")]
+ [ProducesResponseType(Status202Accepted)]
+ [ProducesResponseType(Status404NotFound)]
+ [ProducesResponseType(Status409Conflict)]
+ [ProducesResponseType(Status500InternalServerError, "text/plain")]
+ public IActionResult StartJob(string JobId, [FromBody(EmptyBodyBehavior = EmptyBodyBehavior.Allow)]bool startDependencies = false)
+ {
+ Job? ret = context.Jobs.Find(JobId);
+ if (ret is null)
+ return NotFound();
+ List dependencies = startDependencies ? ret.GetDependenciesAndSelf() : [ret];
+
+ try
+ {
+ if(dependencies.Any(d => d.state >= JobState.Running && d.state < JobState.Completed))
+ return new ConflictResult();
+ dependencies.ForEach(d =>
+ {
+ d.LastExecution = DateTime.UnixEpoch;
+ d.state = JobState.CompletedWaiting;
+ });
+ context.SaveChanges();
+ return Accepted();
+ }
+ catch (Exception e)
+ {
+ Log.Error(e);
+ return StatusCode(500, e.Message);
+ }
+ }
+
+ ///
+ /// Stops the Job with the requested ID
+ ///
+ /// Job-ID
+ /// NOT IMPLEMENTED
+ [HttpPost("{JobId}/Stop")]
+ [ProducesResponseType(Status501NotImplemented)]
+ public IActionResult StopJob(string JobId)
+ {
+ return StatusCode(Status501NotImplemented);
+ }
+
+ ///
+ /// Removes failed and completed Jobs (that are not recurring)
+ ///
+ /// Job started
+ /// Error during Database Operation
+ [HttpPost("Cleanup")]
+ public IActionResult CleanupJobs()
+ {
+ try
+ {
+ context.Jobs.RemoveRange(context.Jobs.Where(j => j.state == JobState.Failed || j.state == JobState.Completed));
+ context.SaveChanges();
+ return Ok();
+ }
+ catch (Exception e)
+ {
+ Log.Error(e);
+ return StatusCode(500, e.Message);
+ }
+ }
+}
\ No newline at end of file
diff --git a/API/Migrations/library/20250515120732_Initial.Designer.cs b/API/Migrations/library/20250515120732_Initial.Designer.cs
deleted file mode 100644
index c086b4e..0000000
--- a/API/Migrations/library/20250515120732_Initial.Designer.cs
+++ /dev/null
@@ -1,71 +0,0 @@
-//
-using API.Schema.Contexts;
-using Microsoft.EntityFrameworkCore;
-using Microsoft.EntityFrameworkCore.Infrastructure;
-using Microsoft.EntityFrameworkCore.Migrations;
-using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
-using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
-
-#nullable disable
-
-namespace API.Migrations.library
-{
- [DbContext(typeof(LibraryContext))]
- [Migration("20250515120732_Initial")]
- partial class Initial
- {
- ///
- protected override void BuildTargetModel(ModelBuilder modelBuilder)
- {
-#pragma warning disable 612, 618
- modelBuilder
- .HasAnnotation("ProductVersion", "9.0.3")
- .HasAnnotation("Relational:MaxIdentifierLength", 63);
-
- NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
-
- modelBuilder.Entity("API.Schema.LibraryConnectors.LibraryConnector", b =>
- {
- b.Property("LibraryConnectorId")
- .HasMaxLength(64)
- .HasColumnType("character varying(64)");
-
- b.Property("Auth")
- .IsRequired()
- .HasMaxLength(256)
- .HasColumnType("character varying(256)");
-
- b.Property("BaseUrl")
- .IsRequired()
- .HasMaxLength(256)
- .HasColumnType("character varying(256)");
-
- b.Property("LibraryType")
- .HasColumnType("smallint");
-
- b.HasKey("LibraryConnectorId");
-
- b.ToTable("LibraryConnectors");
-
- b.HasDiscriminator("LibraryType");
-
- b.UseTphMappingStrategy();
- });
-
- 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);
- });
-#pragma warning restore 612, 618
- }
- }
-}
diff --git a/API/Migrations/library/20250515120732_Initial.cs b/API/Migrations/library/20250515120732_Initial.cs
deleted file mode 100644
index 3ad2851..0000000
--- a/API/Migrations/library/20250515120732_Initial.cs
+++ /dev/null
@@ -1,35 +0,0 @@
-using Microsoft.EntityFrameworkCore.Migrations;
-
-#nullable disable
-
-namespace API.Migrations.library
-{
- ///
- public partial class Initial : Migration
- {
- ///
- protected override void Up(MigrationBuilder migrationBuilder)
- {
- migrationBuilder.CreateTable(
- name: "LibraryConnectors",
- columns: table => new
- {
- LibraryConnectorId = table.Column(type: "character varying(64)", maxLength: 64, nullable: false),
- LibraryType = table.Column(type: "smallint", nullable: false),
- BaseUrl = table.Column(type: "character varying(256)", maxLength: 256, nullable: false),
- Auth = table.Column(type: "character varying(256)", maxLength: 256, nullable: false)
- },
- constraints: table =>
- {
- table.PrimaryKey("PK_LibraryConnectors", x => x.LibraryConnectorId);
- });
- }
-
- ///
- protected override void Down(MigrationBuilder migrationBuilder)
- {
- migrationBuilder.DropTable(
- name: "LibraryConnectors");
- }
- }
-}
diff --git a/API/Migrations/library/LibraryContextModelSnapshot.cs b/API/Migrations/library/LibraryContextModelSnapshot.cs
deleted file mode 100644
index d79d7f0..0000000
--- a/API/Migrations/library/LibraryContextModelSnapshot.cs
+++ /dev/null
@@ -1,68 +0,0 @@
-//
-using API.Schema.Contexts;
-using Microsoft.EntityFrameworkCore;
-using Microsoft.EntityFrameworkCore.Infrastructure;
-using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
-using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
-
-#nullable disable
-
-namespace API.Migrations.library
-{
- [DbContext(typeof(LibraryContext))]
- partial class LibraryContextModelSnapshot : ModelSnapshot
- {
- protected override void BuildModel(ModelBuilder modelBuilder)
- {
-#pragma warning disable 612, 618
- modelBuilder
- .HasAnnotation("ProductVersion", "9.0.3")
- .HasAnnotation("Relational:MaxIdentifierLength", 63);
-
- NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
-
- modelBuilder.Entity("API.Schema.LibraryConnectors.LibraryConnector", b =>
- {
- b.Property("LibraryConnectorId")
- .HasMaxLength(64)
- .HasColumnType("character varying(64)");
-
- b.Property("Auth")
- .IsRequired()
- .HasMaxLength(256)
- .HasColumnType("character varying(256)");
-
- b.Property("BaseUrl")
- .IsRequired()
- .HasMaxLength(256)
- .HasColumnType("character varying(256)");
-
- b.Property("LibraryType")
- .HasColumnType("smallint");
-
- b.HasKey("LibraryConnectorId");
-
- b.ToTable("LibraryConnectors");
-
- b.HasDiscriminator("LibraryType");
-
- b.UseTphMappingStrategy();
- });
-
- 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);
- });
-#pragma warning restore 612, 618
- }
- }
-}
diff --git a/API/Migrations/notifications/20250515120746_Initial.Designer.cs b/API/Migrations/notifications/20250515120746_Initial.Designer.cs
deleted file mode 100644
index 396d022..0000000
--- a/API/Migrations/notifications/20250515120746_Initial.Designer.cs
+++ /dev/null
@@ -1,89 +0,0 @@
-//
-using System;
-using System.Collections.Generic;
-using API.Schema.Contexts;
-using Microsoft.EntityFrameworkCore;
-using Microsoft.EntityFrameworkCore.Infrastructure;
-using Microsoft.EntityFrameworkCore.Migrations;
-using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
-using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
-
-#nullable disable
-
-namespace API.Migrations.notifications
-{
- [DbContext(typeof(NotificationsContext))]
- [Migration("20250515120746_Initial")]
- partial class Initial
- {
- ///
- 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.Notification", b =>
- {
- b.Property("NotificationId")
- .HasMaxLength(64)
- .HasColumnType("character varying(64)");
-
- b.Property("Date")
- .HasColumnType("timestamp with time zone");
-
- b.Property("Message")
- .IsRequired()
- .HasMaxLength(512)
- .HasColumnType("character varying(512)");
-
- b.Property("Title")
- .IsRequired()
- .HasMaxLength(128)
- .HasColumnType("character varying(128)");
-
- b.Property("Urgency")
- .HasColumnType("smallint");
-
- b.HasKey("NotificationId");
-
- b.ToTable("Notifications");
- });
-
- modelBuilder.Entity("API.Schema.NotificationConnectors.NotificationConnector", b =>
- {
- b.Property("Name")
- .HasMaxLength(64)
- .HasColumnType("character varying(64)");
-
- b.Property("Body")
- .IsRequired()
- .HasMaxLength(4096)
- .HasColumnType("character varying(4096)");
-
- b.Property>("Headers")
- .IsRequired()
- .HasColumnType("hstore");
-
- b.Property("HttpMethod")
- .IsRequired()
- .HasMaxLength(8)
- .HasColumnType("character varying(8)");
-
- b.Property("Url")
- .IsRequired()
- .HasMaxLength(2048)
- .HasColumnType("character varying(2048)");
-
- b.HasKey("Name");
-
- b.ToTable("NotificationConnectors");
- });
-#pragma warning restore 612, 618
- }
- }
-}
diff --git a/API/Migrations/notifications/20250515120746_Initial.cs b/API/Migrations/notifications/20250515120746_Initial.cs
deleted file mode 100644
index 8ab8590..0000000
--- a/API/Migrations/notifications/20250515120746_Initial.cs
+++ /dev/null
@@ -1,59 +0,0 @@
-using System;
-using System.Collections.Generic;
-using Microsoft.EntityFrameworkCore.Migrations;
-
-#nullable disable
-
-namespace API.Migrations.notifications
-{
- ///
- public partial class Initial : Migration
- {
- ///
- protected override void Up(MigrationBuilder migrationBuilder)
- {
- migrationBuilder.AlterDatabase()
- .Annotation("Npgsql:PostgresExtension:hstore", ",,");
-
- migrationBuilder.CreateTable(
- name: "NotificationConnectors",
- columns: table => new
- {
- Name = table.Column(type: "character varying(64)", maxLength: 64, nullable: false),
- Url = table.Column(type: "character varying(2048)", maxLength: 2048, nullable: false),
- Headers = table.Column>(type: "hstore", nullable: false),
- HttpMethod = table.Column(type: "character varying(8)", maxLength: 8, nullable: false),
- Body = table.Column(type: "character varying(4096)", maxLength: 4096, nullable: false)
- },
- constraints: table =>
- {
- table.PrimaryKey("PK_NotificationConnectors", x => x.Name);
- });
-
- migrationBuilder.CreateTable(
- name: "Notifications",
- columns: table => new
- {
- NotificationId = table.Column(type: "character varying(64)", maxLength: 64, nullable: false),
- Urgency = table.Column(type: "smallint", nullable: false),
- Title = table.Column(type: "character varying(128)", maxLength: 128, nullable: false),
- Message = table.Column(type: "character varying(512)", maxLength: 512, nullable: false),
- Date = table.Column(type: "timestamp with time zone", nullable: false)
- },
- constraints: table =>
- {
- table.PrimaryKey("PK_Notifications", x => x.NotificationId);
- });
- }
-
- ///
- protected override void Down(MigrationBuilder migrationBuilder)
- {
- migrationBuilder.DropTable(
- name: "NotificationConnectors");
-
- migrationBuilder.DropTable(
- name: "Notifications");
- }
- }
-}
diff --git a/API/Migrations/notifications/20250701203510_Notification-Identifiable.Designer.cs b/API/Migrations/notifications/20250701203510_Notification-Identifiable.Designer.cs
deleted file mode 100644
index 09dddbd..0000000
--- a/API/Migrations/notifications/20250701203510_Notification-Identifiable.Designer.cs
+++ /dev/null
@@ -1,88 +0,0 @@
-//
-using System;
-using System.Collections.Generic;
-using API.Schema.Contexts;
-using Microsoft.EntityFrameworkCore;
-using Microsoft.EntityFrameworkCore.Infrastructure;
-using Microsoft.EntityFrameworkCore.Migrations;
-using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
-using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
-
-#nullable disable
-
-namespace API.Migrations.notifications
-{
- [DbContext(typeof(NotificationsContext))]
- [Migration("20250701203510_Notification-Identifiable")]
- partial class NotificationIdentifiable
- {
- ///
- protected override void BuildTargetModel(ModelBuilder modelBuilder)
- {
-#pragma warning disable 612, 618
- modelBuilder
- .HasAnnotation("ProductVersion", "9.0.5")
- .HasAnnotation("Relational:MaxIdentifierLength", 63);
-
- NpgsqlModelBuilderExtensions.HasPostgresExtension(modelBuilder, "hstore");
- NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
-
- modelBuilder.Entity("API.Schema.Notification", b =>
- {
- b.Property("Key")
- .HasColumnType("text");
-
- b.Property("Date")
- .HasColumnType("timestamp with time zone");
-
- b.Property("Message")
- .IsRequired()
- .HasMaxLength(512)
- .HasColumnType("character varying(512)");
-
- b.Property("Title")
- .IsRequired()
- .HasMaxLength(128)
- .HasColumnType("character varying(128)");
-
- b.Property("Urgency")
- .HasColumnType("smallint");
-
- b.HasKey("Key");
-
- b.ToTable("Notifications");
- });
-
- modelBuilder.Entity("API.Schema.NotificationConnectors.NotificationConnector", b =>
- {
- b.Property("Name")
- .HasMaxLength(64)
- .HasColumnType("character varying(64)");
-
- b.Property("Body")
- .IsRequired()
- .HasMaxLength(4096)
- .HasColumnType("character varying(4096)");
-
- b.Property>("Headers")
- .IsRequired()
- .HasColumnType("hstore");
-
- b.Property("HttpMethod")
- .IsRequired()
- .HasMaxLength(8)
- .HasColumnType("character varying(8)");
-
- b.Property("Url")
- .IsRequired()
- .HasMaxLength(2048)
- .HasColumnType("character varying(2048)");
-
- b.HasKey("Name");
-
- b.ToTable("NotificationConnectors");
- });
-#pragma warning restore 612, 618
- }
- }
-}
diff --git a/API/Migrations/notifications/20250701203510_Notification-Identifiable.cs b/API/Migrations/notifications/20250701203510_Notification-Identifiable.cs
deleted file mode 100644
index 3cc52ad..0000000
--- a/API/Migrations/notifications/20250701203510_Notification-Identifiable.cs
+++ /dev/null
@@ -1,59 +0,0 @@
-using Microsoft.EntityFrameworkCore.Migrations;
-
-#nullable disable
-
-namespace API.Migrations.notifications
-{
- ///
- public partial class NotificationIdentifiable : Migration
- {
- ///
- protected override void Up(MigrationBuilder migrationBuilder)
- {
- migrationBuilder.DropPrimaryKey(
- name: "PK_Notifications",
- table: "Notifications");
-
- migrationBuilder.DropColumn(
- name: "NotificationId",
- table: "Notifications");
-
- migrationBuilder.AddColumn(
- name: "Key",
- table: "Notifications",
- type: "text",
- nullable: false,
- defaultValue: "");
-
- migrationBuilder.AddPrimaryKey(
- name: "PK_Notifications",
- table: "Notifications",
- column: "Key");
- }
-
- ///
- protected override void Down(MigrationBuilder migrationBuilder)
- {
- migrationBuilder.DropPrimaryKey(
- name: "PK_Notifications",
- table: "Notifications");
-
- migrationBuilder.DropColumn(
- name: "Key",
- table: "Notifications");
-
- migrationBuilder.AddColumn(
- name: "NotificationId",
- table: "Notifications",
- type: "character varying(64)",
- maxLength: 64,
- nullable: false,
- defaultValue: "");
-
- migrationBuilder.AddPrimaryKey(
- name: "PK_Notifications",
- table: "Notifications",
- column: "NotificationId");
- }
- }
-}
diff --git a/API/Migrations/notifications/NotificationsContextModelSnapshot.cs b/API/Migrations/notifications/NotificationsContextModelSnapshot.cs
deleted file mode 100644
index 94a333e..0000000
--- a/API/Migrations/notifications/NotificationsContextModelSnapshot.cs
+++ /dev/null
@@ -1,85 +0,0 @@
-//
-using System;
-using System.Collections.Generic;
-using API.Schema.Contexts;
-using Microsoft.EntityFrameworkCore;
-using Microsoft.EntityFrameworkCore.Infrastructure;
-using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
-using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
-
-#nullable disable
-
-namespace API.Migrations.notifications
-{
- [DbContext(typeof(NotificationsContext))]
- partial class NotificationsContextModelSnapshot : ModelSnapshot
- {
- protected override void BuildModel(ModelBuilder modelBuilder)
- {
-#pragma warning disable 612, 618
- modelBuilder
- .HasAnnotation("ProductVersion", "9.0.5")
- .HasAnnotation("Relational:MaxIdentifierLength", 63);
-
- NpgsqlModelBuilderExtensions.HasPostgresExtension(modelBuilder, "hstore");
- NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
-
- modelBuilder.Entity("API.Schema.Notification", b =>
- {
- b.Property("Key")
- .HasColumnType("text");
-
- b.Property("Date")
- .HasColumnType("timestamp with time zone");
-
- b.Property("Message")
- .IsRequired()
- .HasMaxLength(512)
- .HasColumnType("character varying(512)");
-
- b.Property("Title")
- .IsRequired()
- .HasMaxLength(128)
- .HasColumnType("character varying(128)");
-
- b.Property("Urgency")
- .HasColumnType("smallint");
-
- b.HasKey("Key");
-
- b.ToTable("Notifications");
- });
-
- modelBuilder.Entity("API.Schema.NotificationConnectors.NotificationConnector", b =>
- {
- b.Property("Name")
- .HasMaxLength(64)
- .HasColumnType("character varying(64)");
-
- b.Property("Body")
- .IsRequired()
- .HasMaxLength(4096)
- .HasColumnType("character varying(4096)");
-
- b.Property>("Headers")
- .IsRequired()
- .HasColumnType("hstore");
-
- b.Property("HttpMethod")
- .IsRequired()
- .HasMaxLength(8)
- .HasColumnType("character varying(8)");
-
- b.Property("Url")
- .IsRequired()
- .HasMaxLength(2048)
- .HasColumnType("character varying(2048)");
-
- b.HasKey("Name");
-
- b.ToTable("NotificationConnectors");
- });
-#pragma warning restore 612, 618
- }
- }
-}
diff --git a/API/Migrations/pgsql/20250515120724_Initial-1.Designer.cs b/API/Migrations/pgsql/20250515120724_Initial-1.Designer.cs
deleted file mode 100644
index 4b1b2cb..0000000
--- a/API/Migrations/pgsql/20250515120724_Initial-1.Designer.cs
+++ /dev/null
@@ -1,682 +0,0 @@
-//
-using System;
-using API.Schema.Contexts;
-using Microsoft.EntityFrameworkCore;
-using Microsoft.EntityFrameworkCore.Infrastructure;
-using Microsoft.EntityFrameworkCore.Migrations;
-using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
-using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
-
-#nullable disable
-
-namespace API.Migrations.pgsql
-{
- [DbContext(typeof(PgsqlContext))]
- [Migration("20250515120724_Initial-1")]
- partial class Initial1
- {
- ///
- protected override void BuildTargetModel(ModelBuilder modelBuilder)
- {
-#pragma warning disable 612, 618
- modelBuilder
- .HasAnnotation("ProductVersion", "9.0.3")
- .HasAnnotation("Relational:MaxIdentifierLength", 63);
-
- NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
-
- modelBuilder.Entity("API.Schema.Author", b =>
- {
- b.Property("AuthorId")
- .HasMaxLength(64)
- .HasColumnType("character varying(64)");
-
- b.Property("AuthorName")
- .IsRequired()
- .HasMaxLength(128)
- .HasColumnType("character varying(128)");
-
- b.HasKey("AuthorId");
-
- b.ToTable("Authors");
- });
-
- modelBuilder.Entity("API.Schema.Chapter", b =>
- {
- b.Property("ChapterId")
- .HasMaxLength(64)
- .HasColumnType("character varying(64)");
-
- b.Property("ChapterNumber")
- .IsRequired()
- .HasMaxLength(10)
- .HasColumnType("character varying(10)");
-
- b.Property("Downloaded")
- .HasColumnType("boolean");
-
- b.Property("FileName")
- .IsRequired()
- .HasMaxLength(256)
- .HasColumnType("character varying(256)");
-
- b.Property("ParentMangaId")
- .IsRequired()
- .HasColumnType("character varying(64)");
-
- b.Property("Title")
- .HasMaxLength(256)
- .HasColumnType("character varying(256)");
-
- b.Property("Url")
- .IsRequired()
- .HasMaxLength(2048)
- .HasColumnType("character varying(2048)");
-
- b.Property("VolumeNumber")
- .HasColumnType("integer");
-
- b.HasKey("ChapterId");
-
- b.HasIndex("ParentMangaId");
-
- b.ToTable("Chapters");
- });
-
- modelBuilder.Entity("API.Schema.Jobs.Job", b =>
- {
- b.Property("JobId")
- .HasMaxLength(64)
- .HasColumnType("character varying(64)");
-
- b.Property("Enabled")
- .HasColumnType("boolean");
-
- b.Property("JobType")
- .HasColumnType("smallint");
-
- b.Property("LastExecution")
- .HasColumnType("timestamp with time zone");
-
- b.Property("ParentJobId")
- .HasMaxLength(64)
- .HasColumnType("character varying(64)");
-
- b.Property("RecurrenceMs")
- .HasColumnType("numeric(20,0)");
-
- b.Property("state")
- .HasColumnType("smallint");
-
- b.HasKey("JobId");
-
- b.HasIndex("ParentJobId");
-
- b.ToTable("Jobs");
-
- b.HasDiscriminator("JobType");
-
- b.UseTphMappingStrategy();
- });
-
- modelBuilder.Entity("API.Schema.LocalLibrary", b =>
- {
- b.Property("LocalLibraryId")
- .HasMaxLength(64)
- .HasColumnType("character varying(64)");
-
- b.Property("BasePath")
- .IsRequired()
- .HasMaxLength(256)
- .HasColumnType("character varying(256)");
-
- b.Property("LibraryName")
- .IsRequired()
- .HasMaxLength(512)
- .HasColumnType("character varying(512)");
-
- b.HasKey("LocalLibraryId");
-
- b.ToTable("LocalLibraries");
- });
-
- modelBuilder.Entity("API.Schema.Manga", b =>
- {
- b.Property("MangaId")
- .HasMaxLength(64)
- .HasColumnType("character varying(64)");
-
- b.Property("CoverFileNameInCache")
- .HasMaxLength(512)
- .HasColumnType("character varying(512)");
-
- b.Property("CoverUrl")
- .IsRequired()
- .HasMaxLength(512)
- .HasColumnType("character varying(512)");
-
- b.Property("Description")
- .IsRequired()
- .HasColumnType("text");
-
- b.Property("DirectoryName")
- .IsRequired()
- .HasMaxLength(1024)
- .HasColumnType("character varying(1024)");
-
- b.Property("IdOnConnectorSite")
- .IsRequired()
- .HasMaxLength(256)
- .HasColumnType("character varying(256)");
-
- b.Property("IgnoreChaptersBefore")
- .HasColumnType("real");
-
- b.Property("LibraryId")
- .HasMaxLength(64)
- .HasColumnType("character varying(64)");
-
- b.Property("MangaConnectorName")
- .IsRequired()
- .HasMaxLength(32)
- .HasColumnType("character varying(32)");
-
- b.Property("Name")
- .IsRequired()
- .HasMaxLength(512)
- .HasColumnType("character varying(512)");
-
- b.Property("OriginalLanguage")
- .HasMaxLength(8)
- .HasColumnType("character varying(8)");
-
- b.Property("ReleaseStatus")
- .HasColumnType("smallint");
-
- b.Property("WebsiteUrl")
- .IsRequired()
- .HasMaxLength(512)
- .HasColumnType("character varying(512)");
-
- b.Property("Year")
- .HasColumnType("bigint");
-
- b.HasKey("MangaId");
-
- b.HasIndex("LibraryId");
-
- b.HasIndex("MangaConnectorName");
-
- b.ToTable("Mangas");
- });
-
- modelBuilder.Entity("API.Schema.MangaConnectors.MangaConnector", b =>
- {
- b.Property("Name")
- .HasMaxLength(32)
- .HasColumnType("character varying(32)");
-
- b.PrimitiveCollection("BaseUris")
- .IsRequired()
- .HasMaxLength(256)
- .HasColumnType("text[]");
-
- b.Property("Enabled")
- .HasColumnType("boolean");
-
- b.Property("IconUrl")
- .IsRequired()
- .HasMaxLength(2048)
- .HasColumnType("character varying(2048)");
-
- b.PrimitiveCollection("SupportedLanguages")
- .IsRequired()
- .HasMaxLength(8)
- .HasColumnType("text[]");
-
- b.HasKey("Name");
-
- b.ToTable("MangaConnectors");
-
- b.HasDiscriminator("Name").HasValue("MangaConnector");
-
- b.UseTphMappingStrategy();
- });
-
- modelBuilder.Entity("API.Schema.MangaTag", b =>
- {
- b.Property("Tag")
- .HasMaxLength(64)
- .HasColumnType("character varying(64)");
-
- b.HasKey("Tag");
-
- b.ToTable("Tags");
- });
-
- modelBuilder.Entity("AuthorToManga", b =>
- {
- b.Property("AuthorIds")
- .HasColumnType("character varying(64)");
-
- b.Property("MangaIds")
- .HasColumnType("character varying(64)");
-
- b.HasKey("AuthorIds", "MangaIds");
-
- b.HasIndex("MangaIds");
-
- b.ToTable("AuthorToManga");
- });
-
- modelBuilder.Entity("JobJob", b =>
- {
- b.Property("DependsOnJobsJobId")
- .HasColumnType("character varying(64)");
-
- b.Property("JobId")
- .HasColumnType("character varying(64)");
-
- b.HasKey("DependsOnJobsJobId", "JobId");
-
- b.HasIndex("JobId");
-
- b.ToTable("JobJob");
- });
-
- modelBuilder.Entity("MangaTagToManga", b =>
- {
- b.Property("MangaTagIds")
- .HasColumnType("character varying(64)");
-
- b.Property("MangaIds")
- .HasColumnType("character varying(64)");
-
- b.HasKey("MangaTagIds", "MangaIds");
-
- b.HasIndex("MangaIds");
-
- b.ToTable("MangaTagToManga");
- });
-
- modelBuilder.Entity("API.Schema.Jobs.DownloadAvailableChaptersJob", b =>
- {
- b.HasBaseType("API.Schema.Jobs.Job");
-
- b.Property("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("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("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("FromLocation")
- .IsRequired()
- .HasMaxLength(256)
- .HasColumnType("character varying(256)");
-
- b.Property("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("MangaId")
- .IsRequired()
- .HasMaxLength(64)
- .HasColumnType("character varying(64)");
-
- b.Property("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("Language")
- .IsRequired()
- .HasMaxLength(8)
- .HasColumnType("character varying(8)");
-
- b.Property("MangaId")
- .IsRequired()
- .HasMaxLength(64)
- .HasColumnType("character varying(64)");
-
- b.HasIndex("MangaId");
-
- b.ToTable("Jobs", t =>
- {
- t.Property("MangaId")
- .HasColumnName("RetrieveChaptersJob_MangaId");
- });
-
- b.HasDiscriminator().HasValue((byte)5);
- });
-
- modelBuilder.Entity("API.Schema.Jobs.UpdateChaptersDownloadedJob", b =>
- {
- b.HasBaseType("API.Schema.Jobs.Job");
-
- b.Property("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.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("LinkId")
- .HasMaxLength(64)
- .HasColumnType("character varying(64)");
-
- b1.Property("LinkProvider")
- .IsRequired()
- .HasMaxLength(64)
- .HasColumnType("character varying(64)");
-
- b1.Property("LinkUrl")
- .IsRequired()
- .HasMaxLength(2048)
- .HasColumnType("character varying(2048)");
-
- b1.Property("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("AltTitleId")
- .HasMaxLength(64)
- .HasColumnType("character varying(64)");
-
- b1.Property("Language")
- .IsRequired()
- .HasMaxLength(8)
- .HasColumnType("character varying(8)");
-
- b1.Property("MangaId")
- .IsRequired()
- .HasColumnType("character varying(64)");
-
- b1.Property("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.UpdateChaptersDownloadedJob", 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
- }
- }
-}
diff --git a/API/Migrations/pgsql/20250515120724_Initial-1.cs b/API/Migrations/pgsql/20250515120724_Initial-1.cs
deleted file mode 100644
index 97295f0..0000000
--- a/API/Migrations/pgsql/20250515120724_Initial-1.cs
+++ /dev/null
@@ -1,433 +0,0 @@
-using System;
-using Microsoft.EntityFrameworkCore.Migrations;
-
-#nullable disable
-
-namespace API.Migrations.pgsql
-{
- ///
- public partial class Initial1 : Migration
- {
- ///
- protected override void Up(MigrationBuilder migrationBuilder)
- {
- migrationBuilder.CreateTable(
- name: "Authors",
- columns: table => new
- {
- AuthorId = table.Column(type: "character varying(64)", maxLength: 64, nullable: false),
- AuthorName = table.Column(type: "character varying(128)", maxLength: 128, nullable: false)
- },
- constraints: table =>
- {
- table.PrimaryKey("PK_Authors", x => x.AuthorId);
- });
-
- migrationBuilder.CreateTable(
- name: "LocalLibraries",
- columns: table => new
- {
- LocalLibraryId = table.Column(type: "character varying(64)", maxLength: 64, nullable: false),
- BasePath = table.Column(type: "character varying(256)", maxLength: 256, nullable: false),
- LibraryName = table.Column(type: "character varying(512)", maxLength: 512, nullable: false)
- },
- constraints: table =>
- {
- table.PrimaryKey("PK_LocalLibraries", x => x.LocalLibraryId);
- });
-
- migrationBuilder.CreateTable(
- name: "MangaConnectors",
- columns: table => new
- {
- Name = table.Column(type: "character varying(32)", maxLength: 32, nullable: false),
- SupportedLanguages = table.Column(type: "text[]", maxLength: 8, nullable: false),
- IconUrl = table.Column(type: "character varying(2048)", maxLength: 2048, nullable: false),
- BaseUris = table.Column(type: "text[]", maxLength: 256, nullable: false),
- Enabled = table.Column(type: "boolean", nullable: false)
- },
- constraints: table =>
- {
- table.PrimaryKey("PK_MangaConnectors", x => x.Name);
- });
-
- migrationBuilder.CreateTable(
- name: "Tags",
- columns: table => new
- {
- Tag = table.Column(type: "character varying(64)", maxLength: 64, nullable: false)
- },
- constraints: table =>
- {
- table.PrimaryKey("PK_Tags", x => x.Tag);
- });
-
- migrationBuilder.CreateTable(
- name: "Mangas",
- columns: table => new
- {
- MangaId = table.Column(type: "character varying(64)", maxLength: 64, nullable: false),
- IdOnConnectorSite = table.Column(type: "character varying(256)", maxLength: 256, nullable: false),
- Name = table.Column(type: "character varying(512)", maxLength: 512, nullable: false),
- Description = table.Column(type: "text", nullable: false),
- WebsiteUrl = table.Column(type: "character varying(512)", maxLength: 512, nullable: false),
- CoverUrl = table.Column(type: "character varying(512)", maxLength: 512, nullable: false),
- ReleaseStatus = table.Column(type: "smallint", nullable: false),
- LibraryId = table.Column(type: "character varying(64)", maxLength: 64, nullable: true),
- MangaConnectorName = table.Column(type: "character varying(32)", maxLength: 32, nullable: false),
- IgnoreChaptersBefore = table.Column(type: "real", nullable: false),
- DirectoryName = table.Column(type: "character varying(1024)", maxLength: 1024, nullable: false),
- CoverFileNameInCache = table.Column(type: "character varying(512)", maxLength: 512, nullable: true),
- Year = table.Column(type: "bigint", nullable: false),
- OriginalLanguage = table.Column(type: "character varying(8)", maxLength: 8, nullable: true)
- },
- constraints: table =>
- {
- table.PrimaryKey("PK_Mangas", x => x.MangaId);
- table.ForeignKey(
- name: "FK_Mangas_LocalLibraries_LibraryId",
- column: x => x.LibraryId,
- principalTable: "LocalLibraries",
- principalColumn: "LocalLibraryId",
- onDelete: ReferentialAction.SetNull);
- table.ForeignKey(
- name: "FK_Mangas_MangaConnectors_MangaConnectorName",
- column: x => x.MangaConnectorName,
- principalTable: "MangaConnectors",
- principalColumn: "Name",
- onDelete: ReferentialAction.Cascade);
- });
-
- migrationBuilder.CreateTable(
- name: "AuthorToManga",
- columns: table => new
- {
- AuthorIds = table.Column(type: "character varying(64)", nullable: false),
- MangaIds = table.Column(type: "character varying(64)", nullable: false)
- },
- constraints: table =>
- {
- table.PrimaryKey("PK_AuthorToManga", x => new { x.AuthorIds, x.MangaIds });
- table.ForeignKey(
- name: "FK_AuthorToManga_Authors_AuthorIds",
- column: x => x.AuthorIds,
- principalTable: "Authors",
- principalColumn: "AuthorId",
- onDelete: ReferentialAction.Cascade);
- table.ForeignKey(
- name: "FK_AuthorToManga_Mangas_MangaIds",
- column: x => x.MangaIds,
- principalTable: "Mangas",
- principalColumn: "MangaId",
- onDelete: ReferentialAction.Cascade);
- });
-
- migrationBuilder.CreateTable(
- name: "Chapters",
- columns: table => new
- {
- ChapterId = table.Column(type: "character varying(64)", maxLength: 64, nullable: false),
- ParentMangaId = table.Column(type: "character varying(64)", nullable: false),
- VolumeNumber = table.Column(type: "integer", nullable: true),
- ChapterNumber = table.Column(type: "character varying(10)", maxLength: 10, nullable: false),
- Url = table.Column(type: "character varying(2048)", maxLength: 2048, nullable: false),
- Title = table.Column(type: "character varying(256)", maxLength: 256, nullable: true),
- FileName = table.Column(type: "character varying(256)", maxLength: 256, nullable: false),
- Downloaded = table.Column(type: "boolean", nullable: false)
- },
- constraints: table =>
- {
- table.PrimaryKey("PK_Chapters", x => x.ChapterId);
- table.ForeignKey(
- name: "FK_Chapters_Mangas_ParentMangaId",
- column: x => x.ParentMangaId,
- principalTable: "Mangas",
- principalColumn: "MangaId",
- onDelete: ReferentialAction.Cascade);
- });
-
- migrationBuilder.CreateTable(
- name: "Link",
- columns: table => new
- {
- LinkId = table.Column(type: "character varying(64)", maxLength: 64, nullable: false),
- LinkProvider = table.Column(type: "character varying(64)", maxLength: 64, nullable: false),
- LinkUrl = table.Column(type: "character varying(2048)", maxLength: 2048, nullable: false),
- MangaId = table.Column(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(type: "character varying(64)", maxLength: 64, nullable: false),
- Language = table.Column(type: "character varying(8)", maxLength: 8, nullable: false),
- Title = table.Column(type: "character varying(256)", maxLength: 256, nullable: false),
- MangaId = table.Column(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.CreateTable(
- name: "MangaTagToManga",
- columns: table => new
- {
- MangaTagIds = table.Column(type: "character varying(64)", nullable: false),
- MangaIds = table.Column(type: "character varying(64)", nullable: false)
- },
- constraints: table =>
- {
- table.PrimaryKey("PK_MangaTagToManga", x => new { x.MangaTagIds, x.MangaIds });
- table.ForeignKey(
- name: "FK_MangaTagToManga_Mangas_MangaIds",
- column: x => x.MangaIds,
- principalTable: "Mangas",
- principalColumn: "MangaId",
- onDelete: ReferentialAction.Cascade);
- table.ForeignKey(
- name: "FK_MangaTagToManga_Tags_MangaTagIds",
- column: x => x.MangaTagIds,
- principalTable: "Tags",
- principalColumn: "Tag",
- onDelete: ReferentialAction.Cascade);
- });
-
- migrationBuilder.CreateTable(
- name: "Jobs",
- columns: table => new
- {
- JobId = table.Column(type: "character varying(64)", maxLength: 64, nullable: false),
- ParentJobId = table.Column(type: "character varying(64)", maxLength: 64, nullable: true),
- JobType = table.Column