From e63545eed06cdabd20de96b2079530ce1eafdd15 Mon Sep 17 00:00:00 2001 From: glax Date: Tue, 14 Oct 2025 10:16:31 +0200 Subject: [PATCH] Update Endpoints --- API/Controllers/ChaptersController.cs | 220 +++ API/Controllers/FileLibraryController.cs | 45 +- API/Controllers/MangaConnectorController.cs | 22 +- API/Controllers/MangaController.cs | 250 +--- API/Controllers/QueryController.cs | 89 +- API/Controllers/SearchController.cs | 4 +- API/Controllers/SettingsController.cs | 15 +- API/Controllers/WorkerController.cs | 57 +- API/Tranga.cs | 14 +- API/openapi/API_v2.json | 1425 ++++++++----------- docker-compose.local.yaml | 1 + 11 files changed, 877 insertions(+), 1265 deletions(-) create mode 100644 API/Controllers/ChaptersController.cs diff --git a/API/Controllers/ChaptersController.cs b/API/Controllers/ChaptersController.cs new file mode 100644 index 0000000..ec21c42 --- /dev/null +++ b/API/Controllers/ChaptersController.cs @@ -0,0 +1,220 @@ +using API.Controllers.DTOs; +using API.Schema.MangaContext; +using Asp.Versioning; +using Microsoft.AspNetCore.Http.HttpResults; +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; +using static Microsoft.AspNetCore.Http.StatusCodes; +using Chapter = API.Controllers.DTOs.Chapter; +// ReSharper disable InconsistentNaming + +namespace API.Controllers; + +[ApiVersion(2)] +[ApiController] +[Route("v{v:apiVersion}/[controller]")] +public class ChaptersController(MangaContext context) : Controller +{ + /// + /// Returns all of with + /// + /// .Key + /// + /// with not found + [HttpGet("{MangaId}")] + [ProducesResponseType>(Status200OK, "application/json")] + [ProducesResponseType(Status404NotFound)] + public async Task>, NotFound>> GetChapters(string MangaId) + { + if(await context.Chapters.Include(ch => ch.MangaConnectorIds) + .Where(ch => ch.ParentMangaId == MangaId) + .ToListAsync(HttpContext.RequestAborted) + is not { } dbChapters) + return TypedResults.NotFound(nameof(MangaId)); + + List chapters = dbChapters.OrderDescending().Select(c => + { + IEnumerable ids = c.MangaConnectorIds.Select(id => + new MangaConnectorId(id.Key, id.MangaConnectorName, id.ObjId, id.WebsiteUrl, id.UseForDownload)); + return new Chapter(c.Key, c.ParentMangaId, c.VolumeNumber, c.ChapterNumber, c.Title, ids, c.Downloaded, c.FileName); + }).ToList(); + + return TypedResults.Ok(chapters); + } + + /// + /// Returns all downloaded for with + /// + /// .Key + /// + /// with not found. + [HttpGet("{MangaId}/Downloaded")] + [ProducesResponseType(Status200OK, "application/json")] + [ProducesResponseType(Status404NotFound, "text/plain")] + public async Task>, NotFound>> GetChaptersDownloaded(string MangaId) + { + if(await context.Chapters.Include(ch => ch.MangaConnectorIds) + .Where(ch => ch.ParentMangaId == MangaId && ch.Downloaded) + .ToListAsync(HttpContext.RequestAborted) + is not { } dbChapters) + return TypedResults.NotFound(nameof(MangaId)); + + List chapters = dbChapters.OrderDescending().Select(c => + { + IEnumerable ids = c.MangaConnectorIds.Select(id => + new MangaConnectorId(id.Key, id.MangaConnectorName, id.ObjId, id.WebsiteUrl, id.UseForDownload)); + return new Chapter(c.Key, c.ParentMangaId, c.VolumeNumber, c.ChapterNumber, c.Title, ids, c.Downloaded, c.FileName); + }).ToList(); + + return TypedResults.Ok(chapters); + } + + /// + /// Returns all not downloaded for with + /// + /// .Key + /// + /// with not found. + [HttpGet("{MangaId}/NotDownloaded")] + [ProducesResponseType>(Status200OK, "application/json")] + [ProducesResponseType(Status404NotFound, "text/plain")] + public async Task>, NoContent, NotFound>> GetChaptersNotDownloaded(string MangaId) + { + if(await context.Chapters.Include(ch => ch.MangaConnectorIds) + .Where(ch => ch.ParentMangaId == MangaId && ch.Downloaded == false) + .ToListAsync(HttpContext.RequestAborted) + is not { } dbChapters) + return TypedResults.NotFound(nameof(MangaId)); + + List chapters = dbChapters.OrderDescending().Select(c => + { + IEnumerable ids = c.MangaConnectorIds.Select(id => + new MangaConnectorId(id.Key, id.MangaConnectorName, id.ObjId, id.WebsiteUrl, id.UseForDownload)); + return new Chapter(c.Key, c.ParentMangaId, c.VolumeNumber, c.ChapterNumber, c.Title, ids, c.Downloaded, c.FileName); + }).ToList(); + + return TypedResults.Ok(chapters); + } + + /// + /// Returns the latest of requested + /// + /// .Key + /// + /// No available chapters + /// with not found. + [HttpGet("{MangaId}/LatestAvailable")] + [ProducesResponseType(Status200OK, "application/json")] + [ProducesResponseType(Status204NoContent)] + [ProducesResponseType(Status404NotFound, "text/plain")] + public async Task, NoContent, NotFound>> GetLatestChapter(string MangaId) + { + if(await context.Chapters.Include(ch => ch.MangaConnectorIds) + .Where(ch => ch.ParentMangaId == MangaId) + .ToListAsync(HttpContext.RequestAborted) + is not { } dbChapters) + return TypedResults.NotFound(nameof(MangaId)); + + Schema.MangaContext.Chapter? c = dbChapters.Max(); + if (c is null) + return TypedResults.NoContent(); + + IEnumerable ids = c.MangaConnectorIds.Select(id => + new MangaConnectorId(id.Key, id.MangaConnectorName, id.ObjId, id.WebsiteUrl, id.UseForDownload)); + return TypedResults.Ok(new Chapter(c.Key, c.ParentMangaId, c.VolumeNumber, c.ChapterNumber, c.Title, ids, c.Downloaded, c.FileName)); + } + + /// + /// Returns the latest of requested that is downloaded + /// + /// .Key + /// + /// No available chapters + /// with not found. + /// Could not retrieve the maximum chapter-number + /// Retry after timeout, updating value + [HttpGet("{MangaId}/LatestDownloaded")] + [ProducesResponseType(Status200OK, "application/json")] + [ProducesResponseType(Status204NoContent)] + [ProducesResponseType(Status404NotFound, "text/plain")] + [ProducesResponseType(Status412PreconditionFailed)] + [ProducesResponseType(Status503ServiceUnavailable)] + public async Task, NoContent, NotFound, StatusCodeHttpResult>> GetLatestChapterDownloaded(string MangaId) + { + if(await context.Chapters.Include(ch => ch.MangaConnectorIds) + .Where(ch => ch.ParentMangaId == MangaId && ch.Downloaded) + .ToListAsync(HttpContext.RequestAborted) + is not { } dbChapters) + return TypedResults.NotFound(nameof(MangaId)); + + Schema.MangaContext.Chapter? c = dbChapters.Max(); + if (c is null) + return TypedResults.NoContent(); + + IEnumerable ids = c.MangaConnectorIds.Select(id => + new MangaConnectorId(id.Key, id.MangaConnectorName, id.ObjId, id.WebsiteUrl, id.UseForDownload)); + return TypedResults.Ok(new Chapter(c.Key, c.ParentMangaId, c.VolumeNumber, c.ChapterNumber, c.Title, ids, c.Downloaded, c.FileName)); + } + + /// + /// Configure the cut-off for + /// + /// .Key + /// Threshold ( ChapterNumber) + /// + /// with not found. + /// Error during Database Operation + [HttpPatch("{MangaId}/IgnoreBefore")] + [ProducesResponseType(Status200OK)] + [ProducesResponseType(Status404NotFound, "text/plain")] + [ProducesResponseType(Status500InternalServerError, "text/plain")] + public async Task, InternalServerError>> IgnoreChaptersBefore(string MangaId, [FromBody]float chapterThreshold) + { + if (await context.Mangas.FirstOrDefaultAsync(m => m.Key == MangaId, HttpContext.RequestAborted) is not { } manga) + return TypedResults.NotFound(nameof(MangaId)); + + manga.IgnoreChaptersBefore = chapterThreshold; + if(await context.Sync(HttpContext.RequestAborted, GetType(), System.Reflection.MethodBase.GetCurrentMethod()?.Name) is { success: false } result) + return TypedResults.InternalServerError(result.exceptionMessage); + + return TypedResults.Ok(); + } + + /// + /// Returns with + /// + /// .Key + /// + /// with not found + [HttpGet("{ChapterId}")] + [ProducesResponseType(Status200OK, "application/json")] + [ProducesResponseType(Status404NotFound, "text/plain")] + public async Task, NotFound>> GetChapter (string ChapterId) + { + if (await context.Chapters.FirstOrDefaultAsync(c => c.Key == ChapterId, HttpContext.RequestAborted) is not { } chapter) + return TypedResults.NotFound(nameof(ChapterId)); + + IEnumerable ids = chapter.MangaConnectorIds.Select(id => + new MangaConnectorId(id.Key, id.MangaConnectorName, id.ObjId, id.WebsiteUrl, id.UseForDownload)); + return TypedResults.Ok(new Chapter(chapter.Key, chapter.ParentMangaId, chapter.VolumeNumber, chapter.ChapterNumber, chapter.Title,ids, chapter.Downloaded, chapter.FileName)); + } + + /// + /// Returns the with .Key + /// + /// Key of + /// + /// with not found + [HttpGet("{MangaConnectorIdId}")] + [ProducesResponseType(Status200OK, "application/json")] + [ProducesResponseType(Status404NotFound, "text/plain")] + public async Task, NotFound>> GetChapterMangaConnectorId (string MangaConnectorIdId) + { + if (await context.MangaConnectorToChapter.FirstOrDefaultAsync(c => c.Key == MangaConnectorIdId, HttpContext.RequestAborted) is not { } mcIdManga) + return TypedResults.NotFound(nameof(MangaConnectorIdId)); + + MangaConnectorId result = new (mcIdManga.Key, mcIdManga.MangaConnectorName, mcIdManga.ObjId, mcIdManga.WebsiteUrl, mcIdManga.UseForDownload); + + return TypedResults.Ok(result); + } +} \ No newline at end of file diff --git a/API/Controllers/FileLibraryController.cs b/API/Controllers/FileLibraryController.cs index 91a9056..126b913 100644 --- a/API/Controllers/FileLibraryController.cs +++ b/API/Controllers/FileLibraryController.cs @@ -59,46 +59,35 @@ public class FileLibraryController(MangaContext context) : Controller /// /// with not found. /// Error during Database Operation - [HttpPatch("{FileLibraryId}/ChangeBasePath")] + [HttpPatch("{FileLibraryId}")] [ProducesResponseType(Status200OK)] [ProducesResponseType(Status404NotFound, "text/plain")] [ProducesResponseType(Status500InternalServerError, "text/plain")] - public async Task, InternalServerError>> ChangeLibraryBasePath (string FileLibraryId, [FromBody]string newBasePath) + public async Task, InternalServerError>> ChangeLibraryBasePath (string FileLibraryId, [FromBody]PatchFileLibraryRecord requestData) { if(await context.FileLibraries.FirstOrDefaultAsync(l => l.Key == FileLibraryId, HttpContext.RequestAborted) is not { } library) return TypedResults.NotFound(nameof(FileLibraryId)); - - //TODO Path check - library.BasePath = newBasePath; + + if (requestData.Path is { } path) + library.BasePath = path; + if(requestData.Name is { } name) + library.LibraryName = name; if(await context.Sync(HttpContext.RequestAborted, GetType(), System.Reflection.MethodBase.GetCurrentMethod()?.Name) is { success: false } result) return TypedResults.InternalServerError(result.exceptionMessage); return TypedResults.Ok(); } - - /// - /// Changes the .LibraryName with - /// - /// .Key - /// New .LibraryName - /// - /// with not found. - /// Error during Database Operation - [HttpPatch("{FileLibraryId}/ChangeName")] - [ProducesResponseType(Status200OK)] - [ProducesResponseType(Status404NotFound, "text/plain")] - [ProducesResponseType(Status500InternalServerError, "text/plain")] - public async Task, InternalServerError>> ChangeLibraryName (string FileLibraryId, [FromBody] string newName) + + public record PatchFileLibraryRecord(string? Path, string? Name) { - if(await context.FileLibraries.FirstOrDefaultAsync(l => l.Key == FileLibraryId, HttpContext.RequestAborted) is not { } library) - return TypedResults.NotFound(nameof(FileLibraryId)); - - //TODO Name check - library.LibraryName = newName; - - if(await context.Sync(HttpContext.RequestAborted, GetType(), System.Reflection.MethodBase.GetCurrentMethod()?.Name) is { success: false } result) - return TypedResults.InternalServerError(result.exceptionMessage); - return TypedResults.Ok(); + /// + /// Directory Path + /// + public required string? Path { get; init; } = Path; + /// + /// Library Name + /// + public required string? Name { get; init; } = Name; } /// diff --git a/API/Controllers/MangaConnectorController.cs b/API/Controllers/MangaConnectorController.cs index 4158b2a..8edc1b5 100644 --- a/API/Controllers/MangaConnectorController.cs +++ b/API/Controllers/MangaConnectorController.cs @@ -44,29 +44,15 @@ public class MangaConnectorController(MangaContext context) : Controller } /// - /// Get all enabled (Scanlation-Sites) + /// Get all (Scanlation-Sites) with -Status /// /// - [HttpGet("Enabled")] + [HttpGet("{Enabled}")] [ProducesResponseType>(Status200OK, "application/json")] - public Ok> GetEnabledConnectors() + public Ok> GetEnabledConnectors(bool Enabled) { return TypedResults.Ok(Tranga.MangaConnectors - .Where(c => c.Enabled) - .Select(c => new MangaConnector(c.Name, c.Enabled, c.IconUrl, c.SupportedLanguages)) - .ToList()); - } - - /// - /// Get all disabled (Scanlation-Sites) - /// - /// - [HttpGet("Disabled")] - [ProducesResponseType>(Status200OK, "application/json")] - public Ok> GetDisabledConnectors() - { - return TypedResults.Ok(Tranga.MangaConnectors - .Where(c => c.Enabled == false) + .Where(c => c.Enabled == Enabled) .Select(c => new MangaConnector(c.Name, c.Enabled, c.IconUrl, c.SupportedLanguages)) .ToList()); } diff --git a/API/Controllers/MangaController.cs b/API/Controllers/MangaController.cs index 3d2a4b2..c2783ad 100644 --- a/API/Controllers/MangaController.cs +++ b/API/Controllers/MangaController.cs @@ -6,12 +6,11 @@ using Asp.Versioning; using Microsoft.AspNetCore.Http.HttpResults; using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.ChangeTracking; using Microsoft.Net.Http.Headers; +using Soenneker.Utils.String.NeedlemanWunsch; using static Microsoft.AspNetCore.Http.StatusCodes; using AltTitle = API.Controllers.DTOs.AltTitle; using Author = API.Controllers.DTOs.Author; -using Chapter = API.Controllers.DTOs.Chapter; using Link = API.Controllers.DTOs.Link; using Manga = API.Controllers.DTOs.Manga; @@ -71,33 +70,6 @@ public class MangaController(MangaContext context) : Controller return new MinimalManga(m.Key, m.Name, m.Description, m.ReleaseStatus, ids); }).ToList()); } - - /// - /// Returns all cached with - /// - /// Array of .Key - /// - /// Error during Database Operation - [HttpPost("WithIDs")] - [ProducesResponseType>(Status200OK, "application/json")] - [ProducesResponseType(Status500InternalServerError)] - public async Task>, InternalServerError>> GetMangaWithIds ([FromBody]string[] MangaIds) - { - if (await context.MangaIncludeAll() - .Where(m => MangaIds.Contains(m.Key)) - .ToArrayAsync(HttpContext.RequestAborted) is not { } result) - return TypedResults.InternalServerError(); - - return TypedResults.Ok(result.Select(m => - { - IEnumerable ids = m.MangaConnectorIds.Select(id => new MangaConnectorId(id.Key, id.MangaConnectorName, id.ObjId, id.WebsiteUrl, id.UseForDownload)); - IEnumerable authors = m.Authors.Select(a => new Author(a.Key, a.AuthorName)); - IEnumerable tags = m.MangaTags.Select(t => t.Tag); - IEnumerable links = m.Links.Select(l => new Link(l.Key, l.LinkProvider, l.LinkUrl)); - IEnumerable altTitles = m.AltTitles.Select(a => new AltTitle(a.Language, a.Title)); - return new Manga(m.Key, m.Name, m.Description, m.ReleaseStatus, ids, m.IgnoreChaptersBefore, m.Year, m.OriginalLanguage, m.ChapterIds, authors, tags, links, altTitles, m.LibraryId); - }).ToList()); - } /// /// Return with @@ -152,7 +124,7 @@ public class MangaController(MangaContext context) : Controller /// .Key of merging data into /// /// with or not found - [HttpPatch("{MangaIdFrom}/MergeInto/{MangaIdInto}")] + [HttpPost("{MangaIdFrom}/MergeInto/{MangaIdInto}")] [ProducesResponseType(Status200OK)] [ProducesResponseType(Status404NotFound, "text/plain")] public async Task>> MergeIntoManga (string MangaIdFrom, string MangaIdInto) @@ -219,171 +191,6 @@ public class MangaController(MangaContext context) : Controller } public enum CoverSize { Original, Large, Medium, Small } - /// - /// Returns all of with - /// - /// .Key - /// - /// with not found - [HttpGet("{MangaId}/Chapters")] - [ProducesResponseType>(Status200OK, "application/json")] - [ProducesResponseType(Status404NotFound)] - public async Task>, NotFound>> GetChapters(string MangaId) - { - if(await context.Chapters.Include(ch => ch.MangaConnectorIds) - .Where(ch => ch.ParentMangaId == MangaId) - .ToListAsync(HttpContext.RequestAborted) - is not { } dbChapters) - return TypedResults.NotFound(nameof(MangaId)); - - List chapters = dbChapters.OrderDescending().Select(c => - { - IEnumerable ids = c.MangaConnectorIds.Select(id => - new MangaConnectorId(id.Key, id.MangaConnectorName, id.ObjId, id.WebsiteUrl, id.UseForDownload)); - return new Chapter(c.Key, c.ParentMangaId, c.VolumeNumber, c.ChapterNumber, c.Title, ids, c.Downloaded, c.FileName); - }).ToList(); - - return TypedResults.Ok(chapters); - } - - /// - /// Returns all downloaded for with - /// - /// .Key - /// - /// with not found. - [HttpGet("{MangaId}/Chapters/Downloaded")] - [ProducesResponseType(Status200OK, "application/json")] - [ProducesResponseType(Status404NotFound, "text/plain")] - public async Task>, NotFound>> GetChaptersDownloaded(string MangaId) - { - if(await context.Chapters.Include(ch => ch.MangaConnectorIds) - .Where(ch => ch.ParentMangaId == MangaId && ch.Downloaded) - .ToListAsync(HttpContext.RequestAborted) - is not { } dbChapters) - return TypedResults.NotFound(nameof(MangaId)); - - List chapters = dbChapters.OrderDescending().Select(c => - { - IEnumerable ids = c.MangaConnectorIds.Select(id => - new MangaConnectorId(id.Key, id.MangaConnectorName, id.ObjId, id.WebsiteUrl, id.UseForDownload)); - return new Chapter(c.Key, c.ParentMangaId, c.VolumeNumber, c.ChapterNumber, c.Title, ids, c.Downloaded, c.FileName); - }).ToList(); - - return TypedResults.Ok(chapters); - } - - /// - /// Returns all not downloaded for with - /// - /// .Key - /// - /// with not found. - [HttpGet("{MangaId}/Chapters/NotDownloaded")] - [ProducesResponseType>(Status200OK, "application/json")] - [ProducesResponseType(Status404NotFound, "text/plain")] - public async Task>, NoContent, NotFound>> GetChaptersNotDownloaded(string MangaId) - { - if(await context.Chapters.Include(ch => ch.MangaConnectorIds) - .Where(ch => ch.ParentMangaId == MangaId && ch.Downloaded == false) - .ToListAsync(HttpContext.RequestAborted) - is not { } dbChapters) - return TypedResults.NotFound(nameof(MangaId)); - - List chapters = dbChapters.OrderDescending().Select(c => - { - IEnumerable ids = c.MangaConnectorIds.Select(id => - new MangaConnectorId(id.Key, id.MangaConnectorName, id.ObjId, id.WebsiteUrl, id.UseForDownload)); - return new Chapter(c.Key, c.ParentMangaId, c.VolumeNumber, c.ChapterNumber, c.Title, ids, c.Downloaded, c.FileName); - }).ToList(); - - return TypedResults.Ok(chapters); - } - - /// - /// Returns the latest of requested - /// - /// .Key - /// - /// No available chapters - /// with not found. - [HttpGet("{MangaId}/Chapter/LatestAvailable")] - [ProducesResponseType(Status200OK, "application/json")] - [ProducesResponseType(Status204NoContent)] - [ProducesResponseType(Status404NotFound, "text/plain")] - public async Task, NoContent, NotFound>> GetLatestChapter(string MangaId) - { - if(await context.Chapters.Include(ch => ch.MangaConnectorIds) - .Where(ch => ch.ParentMangaId == MangaId) - .ToListAsync(HttpContext.RequestAborted) - is not { } dbChapters) - return TypedResults.NotFound(nameof(MangaId)); - - Schema.MangaContext.Chapter? c = dbChapters.Max(); - if (c is null) - return TypedResults.NoContent(); - - IEnumerable ids = c.MangaConnectorIds.Select(id => - new MangaConnectorId(id.Key, id.MangaConnectorName, id.ObjId, id.WebsiteUrl, id.UseForDownload)); - return TypedResults.Ok(new Chapter(c.Key, c.ParentMangaId, c.VolumeNumber, c.ChapterNumber, c.Title, ids, c.Downloaded, c.FileName)); - } - - /// - /// Returns the latest of requested that is downloaded - /// - /// .Key - /// - /// No available chapters - /// 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, "text/plain")] - [ProducesResponseType(Status412PreconditionFailed)] - [ProducesResponseType(Status503ServiceUnavailable)] - public async Task, NoContent, NotFound, StatusCodeHttpResult>> GetLatestChapterDownloaded(string MangaId) - { - if(await context.Chapters.Include(ch => ch.MangaConnectorIds) - .Where(ch => ch.ParentMangaId == MangaId && ch.Downloaded) - .ToListAsync(HttpContext.RequestAborted) - is not { } dbChapters) - return TypedResults.NotFound(nameof(MangaId)); - - Schema.MangaContext.Chapter? c = dbChapters.Max(); - if (c is null) - return TypedResults.NoContent(); - - IEnumerable ids = c.MangaConnectorIds.Select(id => - new MangaConnectorId(id.Key, id.MangaConnectorName, id.ObjId, id.WebsiteUrl, id.UseForDownload)); - return TypedResults.Ok(new Chapter(c.Key, c.ParentMangaId, c.VolumeNumber, c.ChapterNumber, c.Title, ids, c.Downloaded, c.FileName)); - } - - /// - /// Configure the cut-off for - /// - /// .Key - /// Threshold ( ChapterNumber) - /// - /// with not found. - /// Error during Database Operation - [HttpPatch("{MangaId}/IgnoreChaptersBefore")] - [ProducesResponseType(Status200OK)] - [ProducesResponseType(Status404NotFound, "text/plain")] - [ProducesResponseType(Status500InternalServerError, "text/plain")] - public async Task, InternalServerError>> IgnoreChaptersBefore(string MangaId, [FromBody]float chapterThreshold) - { - if (await context.Mangas.FirstOrDefaultAsync(m => m.Key == MangaId, HttpContext.RequestAborted) is not { } manga) - return TypedResults.NotFound(nameof(MangaId)); - - manga.IgnoreChaptersBefore = chapterThreshold; - if(await context.Sync(HttpContext.RequestAborted, GetType(), System.Reflection.MethodBase.GetCurrentMethod()?.Name) is { success: false } result) - return TypedResults.InternalServerError(result.exceptionMessage); - - return TypedResults.Ok(); - } - /// /// Move to different /// @@ -422,7 +229,7 @@ public class MangaController(MangaContext context) : Controller /// was not linked to , so nothing changed /// is not linked to yet. Search for on first (to create a ). /// Error during Database Operation - [HttpPost("{MangaId}/SetAsDownloadFrom/{MangaConnectorName}/{IsRequested}")] + [HttpPatch("{MangaId}/DownloadFrom/{MangaConnectorName}/{IsRequested}")] [ProducesResponseType(Status200OK)] [ProducesResponseType(Status404NotFound, "text/plain")] [ProducesResponseType(Status412PreconditionFailed, "text/plain")] @@ -464,7 +271,7 @@ public class MangaController(MangaContext context) : Controller /// exert of /// with Name not found /// with Name is disabled - [HttpPost("{MangaId}/SearchOn/{MangaConnectorName}")] + [HttpGet("{MangaId}/OnMangaConnector/{MangaConnectorName}")] [ProducesResponseType>(Status200OK, "application/json")] [ProducesResponseType(Status404NotFound, "text/plain")] [ProducesResponseType(Status406NotAcceptable)] @@ -537,4 +344,53 @@ public class MangaController(MangaContext context) : Controller return new Manga(m.Key, m.Name, m.Description, m.ReleaseStatus, ids, m.IgnoreChaptersBefore, m.Year, m.OriginalLanguage, m.ChapterIds, authors, tags, links, altTitles, m.LibraryId); }).ToList()); } + + /// + /// Returns with names similar to (identified by ) + /// + /// Key of + /// + /// with not found + /// Error during Database Operation + [HttpGet("WithSimilarName/{MangaId}")] + [ProducesResponseType>(Status200OK, "application/json")] + [ProducesResponseType(Status404NotFound, "text/plain")] + [ProducesResponseType(Status500InternalServerError)] + public async Task>, NotFound, InternalServerError>> GetSimilarManga (string MangaId) + { + if (await context.Mangas.FirstOrDefaultAsync(m => m.Key == MangaId, HttpContext.RequestAborted) is not { } manga) + return TypedResults.NotFound(nameof(MangaId)); + + string name = manga.Name; + + if (await context.Mangas.Where(m => m.Key != MangaId) + .ToDictionaryAsync(m => m.Key, m => m.Name, HttpContext.RequestAborted) is not { } mangaNames) + return TypedResults.InternalServerError(); + + List similarIds = mangaNames + .Where(kv => NeedlemanWunschStringUtil.CalculateSimilarityPercentage(name, kv.Value) > 0.8) + .Select(kv => kv.Key) + .ToList(); + + return TypedResults.Ok(similarIds); + } + + /// + /// Returns the with .Key + /// + /// Key of + /// + /// with not found + [HttpGet("{MangaConnectorIdId}")] + [ProducesResponseType(Status200OK, "application/json")] + [ProducesResponseType(Status404NotFound, "text/plain")] + public async Task, NotFound>> GetMangaMangaConnectorId (string MangaConnectorIdId) + { + if (await context.MangaConnectorToManga.FirstOrDefaultAsync(c => c.Key == MangaConnectorIdId, HttpContext.RequestAborted) is not { } mcIdManga) + return TypedResults.NotFound(nameof(MangaConnectorIdId)); + + MangaConnectorId result = new (mcIdManga.Key, mcIdManga.MangaConnectorName, mcIdManga.ObjId, mcIdManga.WebsiteUrl, mcIdManga.UseForDownload); + + return TypedResults.Ok(result); + } } \ No newline at end of file diff --git a/API/Controllers/QueryController.cs b/API/Controllers/QueryController.cs index 394994d..4bb4ec8 100644 --- a/API/Controllers/QueryController.cs +++ b/API/Controllers/QueryController.cs @@ -15,7 +15,7 @@ namespace API.Controllers; [ApiVersion(2)] [ApiController] -[Route("v{v:apiVersion}/[controller]")] +[Route("v{v:apiVersion}/")] public class QueryController(MangaContext context) : Controller { /// @@ -34,91 +34,4 @@ public class QueryController(MangaContext context) : Controller return TypedResults.Ok(new Author(author.Key, author.AuthorName)); } - - /// - /// Returns with - /// - /// .Key - /// - /// with not found - [HttpGet("Chapter/{ChapterId}")] - [ProducesResponseType(Status200OK, "application/json")] - [ProducesResponseType(Status404NotFound, "text/plain")] - public async Task, NotFound>> GetChapter (string ChapterId) - { - if (await context.Chapters.FirstOrDefaultAsync(c => c.Key == ChapterId, HttpContext.RequestAborted) is not { } chapter) - return TypedResults.NotFound(nameof(ChapterId)); - - IEnumerable ids = chapter.MangaConnectorIds.Select(id => - new MangaConnectorId(id.Key, id.MangaConnectorName, id.ObjId, id.WebsiteUrl, id.UseForDownload)); - return TypedResults.Ok(new Chapter(chapter.Key, chapter.ParentMangaId, chapter.VolumeNumber, chapter.ChapterNumber, chapter.Title,ids, chapter.Downloaded, chapter.FileName)); - } - - /// - /// Returns the with .Key - /// - /// Key of - /// - /// with not found - [HttpGet("Manga/MangaConnectorId/{MangaConnectorIdId}")] - [ProducesResponseType(Status200OK, "application/json")] - [ProducesResponseType(Status404NotFound, "text/plain")] - public async Task, NotFound>> GetMangaMangaConnectorId (string MangaConnectorIdId) - { - if (await context.MangaConnectorToManga.FirstOrDefaultAsync(c => c.Key == MangaConnectorIdId, HttpContext.RequestAborted) is not { } mcIdManga) - return TypedResults.NotFound(nameof(MangaConnectorIdId)); - - MangaConnectorId result = new (mcIdManga.Key, mcIdManga.MangaConnectorName, mcIdManga.ObjId, mcIdManga.WebsiteUrl, mcIdManga.UseForDownload); - - return TypedResults.Ok(result); - } - - /// - /// Returns with names similar to (identified by ) - /// - /// Key of - /// - /// with not found - /// Error during Database Operation - [HttpGet("Manga/{MangaId}/SimilarName")] - [ProducesResponseType>(Status200OK, "application/json")] - [ProducesResponseType(Status404NotFound, "text/plain")] - [ProducesResponseType(Status500InternalServerError)] - public async Task>, NotFound, InternalServerError>> GetSimilarManga (string MangaId) - { - if (await context.Mangas.FirstOrDefaultAsync(m => m.Key == MangaId, HttpContext.RequestAborted) is not { } manga) - return TypedResults.NotFound(nameof(MangaId)); - - string name = manga.Name; - - if (await context.Mangas.Where(m => m.Key != MangaId) - .ToDictionaryAsync(m => m.Key, m => m.Name, HttpContext.RequestAborted) is not { } mangaNames) - return TypedResults.InternalServerError(); - - List similarIds = mangaNames - .Where(kv => NeedlemanWunschStringUtil.CalculateSimilarityPercentage(name, kv.Value) > 0.8) - .Select(kv => kv.Key) - .ToList(); - - return TypedResults.Ok(similarIds); - } - - /// - /// Returns the with .Key - /// - /// Key of - /// - /// with not found - [HttpGet("Chapter/MangaConnectorId/{MangaConnectorIdId}")] - [ProducesResponseType(Status200OK, "application/json")] - [ProducesResponseType(Status404NotFound, "text/plain")] - public async Task, NotFound>> GetChapterMangaConnectorId (string MangaConnectorIdId) - { - if (await context.MangaConnectorToManga.FirstOrDefaultAsync(c => c.Key == MangaConnectorIdId, HttpContext.RequestAborted) is not { } mcIdChapter) - return TypedResults.NotFound(nameof(MangaConnectorIdId)); - - MangaConnectorId result = new(mcIdChapter.Key, mcIdChapter.MangaConnectorName, mcIdChapter.ObjId, mcIdChapter.WebsiteUrl, mcIdChapter.UseForDownload); - - return TypedResults.Ok(result); - } } \ No newline at end of file diff --git a/API/Controllers/SearchController.cs b/API/Controllers/SearchController.cs index 7407d9f..5402977 100644 --- a/API/Controllers/SearchController.cs +++ b/API/Controllers/SearchController.cs @@ -58,11 +58,11 @@ public class SearchController(MangaContext context) : Controller /// exert of . /// not found /// Error during Database Operation - [HttpPost("Url")] + [HttpGet] [ProducesResponseType(Status200OK, "application/json")] [ProducesResponseType(Status404NotFound, "text/plain")] [ProducesResponseType(Status500InternalServerError, "text/plain")] - public async Task, NotFound, InternalServerError>> GetMangaFromUrl([FromBody]string url) + public async Task, NotFound, InternalServerError>> GetMangaFromUrl([FromQuery]string url) { if(Tranga.MangaConnectors.FirstOrDefault(c => c.Name.Equals("Global", StringComparison.InvariantCultureIgnoreCase)) is not { } connector) return TypedResults.InternalServerError("Could not find Global Connector."); diff --git a/API/Controllers/SettingsController.cs b/API/Controllers/SettingsController.cs index 4233ff8..ea97d3a 100644 --- a/API/Controllers/SettingsController.cs +++ b/API/Controllers/SettingsController.cs @@ -60,17 +60,6 @@ public class SettingsController() : Controller return TypedResults.Ok(); } - /// - /// Update all Request-Limits to new values - /// - ///

NOT IMPLEMENTED

- [HttpPatch("RequestLimits")] - [ProducesResponseType(Status501NotImplemented)] - public StatusCodeHttpResult SetRequestLimits() - { - return TypedResults.StatusCode(Status501NotImplemented); - } - /// /// Returns Level of Image-Compression for Images /// @@ -179,7 +168,7 @@ public class SettingsController() : Controller ///
/// URL of FlareSolverr-Instance /// - [HttpPost("FlareSolverr/Url")] + [HttpPatch("FlareSolverr/Url")] [ProducesResponseType(Status200OK)] public Ok SetFlareSolverrUrl([FromBody]string flareSolverrUrl) { @@ -212,7 +201,7 @@ public class SettingsController() : Controller const string knownProtectedUrl = "https://prowlarr.servarr.com/v1/ping"; FlareSolverrDownloadClient client = new(new ()); HttpResponseMessage result = await client.MakeRequest(knownProtectedUrl, RequestType.Default); - return (int)result.StatusCode >= 200 && (int)result.StatusCode < 300 ? TypedResults.Ok() : TypedResults.InternalServerError(); + return result.IsSuccessStatusCode ? TypedResults.Ok() : TypedResults.InternalServerError(); } /// diff --git a/API/Controllers/WorkerController.cs b/API/Controllers/WorkerController.cs index 41ca2e8..09c7837 100644 --- a/API/Controllers/WorkerController.cs +++ b/API/Controllers/WorkerController.cs @@ -21,21 +21,10 @@ public class WorkerController : Controller [ProducesResponseType>(Status200OK, "application/json")] public Ok> GetWorkers() { - IEnumerable result = Tranga.GetRunningWorkers().Select(w => + IEnumerable result = Tranga.GetKnownWorkers().Select(w => new Worker(w.Key, w.AllDependencies.Select(d => d.Key), w.MissingDependencies.Select(d => d.Key), w.AllDependenciesFulfilled, w.State)); return TypedResults.Ok(result.ToList()); } - - /// - /// Returns all .Keys - /// - /// - [HttpGet("Keys")] - [ProducesResponseType(Status200OK, "application/json")] - public Ok> GetWorkerIds() - { - return TypedResults.Ok(Tranga.GetRunningWorkers().Select(w => w.Key).ToList()); - } /// /// Get all in requested @@ -46,7 +35,7 @@ public class WorkerController : Controller [ProducesResponseType>(Status200OK, "application/json")] public Ok> GetWorkersInState(WorkerExecutionState State) { - IEnumerable result = Tranga.GetRunningWorkers().Where(worker => worker.State == State).Select(w => + IEnumerable result = Tranga.GetKnownWorkers().Where(worker => worker.State == State).Select(w => new Worker(w.Key, w.AllDependencies.Select(d => d.Key), w.MissingDependencies.Select(d => d.Key), w.AllDependenciesFulfilled, w.State)); return TypedResults.Ok(result.ToList()); } @@ -62,7 +51,7 @@ public class WorkerController : Controller [ProducesResponseType(Status404NotFound, "text/plain")] public Results, NotFound> GetWorker(string WorkerId) { - if(Tranga.GetRunningWorkers().FirstOrDefault(w => w.Key == WorkerId) is not { } w) + if(Tranga.GetKnownWorkers().FirstOrDefault(w => w.Key == WorkerId) is not { } w) return TypedResults.NotFound(nameof(WorkerId)); Worker result = new (w.Key, w.AllDependencies.Select(d => d.Key), w.MissingDependencies.Select(d => d.Key), w.AllDependenciesFulfilled, w.State); @@ -70,46 +59,6 @@ public class WorkerController : Controller return TypedResults.Ok(result); } - /// - /// Delete with and all child-s - /// - /// .Key - /// - /// with could not be found - [HttpDelete("{WorkerId}")] - [ProducesResponseType(Status200OK)] - [ProducesResponseType(Status404NotFound, "text/plain")] - public Results> DeleteWorker(string WorkerId) - { - if(Tranga.GetRunningWorkers().FirstOrDefault(w => w.Key == WorkerId) is not { } worker) - return TypedResults.NotFound(nameof(WorkerId)); - Tranga.StopWorker(worker); - return TypedResults.Ok(); - } - - /// - /// Starts with - /// - /// .Key - /// - /// with could not be found - /// was already running - [HttpPost("{WorkerId}/Start")] - [ProducesResponseType(Status202Accepted)] - [ProducesResponseType(Status404NotFound, "text/plain")] - [ProducesResponseType(Status412PreconditionFailed)] - public Results, StatusCodeHttpResult> StartWorker(string WorkerId) - { - if(Tranga.GetRunningWorkers().FirstOrDefault(w => w.Key == WorkerId) is not { } worker) - return TypedResults.NotFound(nameof(WorkerId)); - - if (worker.State >= WorkerExecutionState.Waiting) - return TypedResults.StatusCode(Status412PreconditionFailed); - - Tranga.StartWorker(worker); - return TypedResults.Ok(); - } - /// /// Stops with /// diff --git a/API/Tranga.cs b/API/Tranga.cs index 084554b..2506646 100644 --- a/API/Tranga.cs +++ b/API/Tranga.cs @@ -77,7 +77,11 @@ public static class Tranga public static void AddWorker(BaseWorker worker) { Log.Debug($"Adding Worker {worker}"); - StartWorker(worker); + KnownWorkers.Add(worker); + if(worker is not IPeriodic) + StartWorker(worker, RemoveFromKnownWorkers(worker)); + else + StartWorker(worker); if(worker is IPeriodic periodic) AddPeriodicWorker(worker, periodic); } @@ -109,6 +113,12 @@ public static class Tranga PeriodicWorkers.AddOrUpdate((worker as IPeriodic)!, periodicTask, (_, _) => periodicTask); periodicTask.Start(); }; + + private static Action RemoveFromKnownWorkers(BaseWorker worker) => () => + { + if (KnownWorkers.Contains(worker)) + KnownWorkers.Remove(worker); + }; public static void AddWorkers(IEnumerable workers) { @@ -116,6 +126,8 @@ public static class Tranga AddWorker(baseWorker); } + private static readonly HashSet KnownWorkers = new(); + public static BaseWorker[] GetKnownWorkers() => KnownWorkers.ToArray(); private static readonly ConcurrentDictionary> RunningWorkers = new(); public static BaseWorker[] GetRunningWorkers() => RunningWorkers.Keys.ToArray(); diff --git a/API/openapi/API_v2.json b/API/openapi/API_v2.json index 866ca40..a08d9b9 100644 --- a/API/openapi/API_v2.json +++ b/API/openapi/API_v2.json @@ -5,6 +5,418 @@ "version": "2.0" }, "paths": { + "/v2/Chapters/{MangaId}": { + "get": { + "tags": [ + "Chapters" + ], + "summary": "Returns all API.Schema.MangaContext.Chapter of API.Schema.MangaContext.Manga with MangaId", + "parameters": [ + { + "name": "MangaId", + "in": "path", + "description": "API.Schema.MangaContext.Manga.Key", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "", + "content": { + "application/json; x-version=2.0": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Chapter" + } + } + } + } + }, + "404": { + "description": "API.Schema.MangaContext.Manga with MangaId not found", + "content": { + "text/plain; x-version=2.0": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; x-version=2.0": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "text/json; x-version=2.0": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + } + } + } + }, + "/v2/Chapters/{MangaId}/Downloaded": { + "get": { + "tags": [ + "Chapters" + ], + "summary": "Returns all downloaded API.Controllers.DTOs.Chapter for API.Schema.MangaContext.Manga with MangaId", + "parameters": [ + { + "name": "MangaId", + "in": "path", + "description": "API.Schema.MangaContext.Manga.Key", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "", + "content": { + "application/json; x-version=2.0": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Chapter" + } + } + } + } + }, + "404": { + "description": "API.Schema.MangaContext.Manga with MangaId not found.", + "content": { + "text/plain; x-version=2.0": { + "schema": { + "type": "string" + } + } + } + } + } + } + }, + "/v2/Chapters/{MangaId}/NotDownloaded": { + "get": { + "tags": [ + "Chapters" + ], + "summary": "Returns all API.Controllers.DTOs.Chapter not downloaded for API.Schema.MangaContext.Manga with MangaId", + "parameters": [ + { + "name": "MangaId", + "in": "path", + "description": "API.Schema.MangaContext.Manga.Key", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "", + "content": { + "application/json; x-version=2.0": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Chapter" + } + } + } + } + }, + "404": { + "description": "API.Schema.MangaContext.Manga with MangaId not found.", + "content": { + "text/plain; x-version=2.0": { + "schema": { + "type": "string" + } + } + } + } + } + } + }, + "/v2/Chapters/{MangaId}/LatestAvailable": { + "get": { + "tags": [ + "Chapters" + ], + "summary": "Returns the latest API.Controllers.DTOs.Chapter of requested API.Schema.MangaContext.Manga", + "parameters": [ + { + "name": "MangaId", + "in": "path", + "description": "API.Schema.MangaContext.Manga.Key", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "", + "content": { + "application/json; x-version=2.0": { + "schema": { + "type": "integer", + "format": "int32" + } + } + } + }, + "204": { + "description": "No available chapters" + }, + "404": { + "description": "API.Schema.MangaContext.Manga with MangaId not found.", + "content": { + "text/plain; x-version=2.0": { + "schema": { + "type": "string" + } + } + } + } + } + } + }, + "/v2/Chapters/{MangaId}/LatestDownloaded": { + "get": { + "tags": [ + "Chapters" + ], + "summary": "Returns the latest API.Controllers.DTOs.Chapter of requested API.Schema.MangaContext.Manga that is downloaded", + "parameters": [ + { + "name": "MangaId", + "in": "path", + "description": "API.Schema.MangaContext.Manga.Key", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "", + "content": { + "application/json; x-version=2.0": { + "schema": { + "$ref": "#/components/schemas/Chapter" + } + } + } + }, + "204": { + "description": "No available chapters" + }, + "404": { + "description": "API.Schema.MangaContext.Manga with MangaId not found.", + "content": { + "text/plain; x-version=2.0": { + "schema": { + "type": "string" + } + } + } + }, + "412": { + "description": "Could not retrieve the maximum chapter-number", + "content": { + "text/plain; x-version=2.0": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; x-version=2.0": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "text/json; x-version=2.0": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + }, + "503": { + "description": "Retry after timeout, updating value" + } + } + } + }, + "/v2/Chapters/{MangaId}/IgnoreBefore": { + "patch": { + "tags": [ + "Chapters" + ], + "summary": "Configure the API.Controllers.DTOs.Chapter cut-off for API.Schema.MangaContext.Manga", + "parameters": [ + { + "name": "MangaId", + "in": "path", + "description": "API.Schema.MangaContext.Manga.Key", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "description": "Threshold (API.Controllers.DTOs.Chapter ChapterNumber)", + "content": { + "application/json-patch+json; x-version=2.0": { + "schema": { + "type": "number", + "format": "float" + } + }, + "application/json; x-version=2.0": { + "schema": { + "type": "number", + "format": "float" + } + }, + "text/json; x-version=2.0": { + "schema": { + "type": "number", + "format": "float" + } + }, + "application/*+json; x-version=2.0": { + "schema": { + "type": "number", + "format": "float" + } + } + } + }, + "responses": { + "200": { + "description": "OK" + }, + "404": { + "description": "API.Schema.MangaContext.Manga with MangaId not found.", + "content": { + "text/plain; x-version=2.0": { + "schema": { + "type": "string" + } + } + } + }, + "500": { + "description": "Error during Database Operation", + "content": { + "text/plain; x-version=2.0": { + "schema": { + "type": "string" + } + } + } + }, + "202": { + "description": "" + } + } + } + }, + "/v2/Chapters/{ChapterId}": { + "get": { + "tags": [ + "Chapters" + ], + "summary": "Returns API.Controllers.DTOs.Chapter with ChapterId", + "parameters": [ + { + "name": "ChapterId", + "in": "path", + "description": "API.Controllers.DTOs.Chapter.Key", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "", + "content": { + "application/json; x-version=2.0": { + "schema": { + "$ref": "#/components/schemas/Chapter" + } + } + } + }, + "404": { + "description": "API.Controllers.DTOs.Chapter with ChapterId not found", + "content": { + "text/plain; x-version=2.0": { + "schema": { + "type": "string" + } + } + } + } + } + } + }, + "/v2/Chapters/{MangaConnectorIdId}": { + "get": { + "tags": [ + "Chapters" + ], + "summary": "Returns the API.Schema.MangaContext.MangaConnectorId`1 with API.Schema.MangaContext.MangaConnectorId`1.Key", + "parameters": [ + { + "name": "MangaConnectorIdId", + "in": "path", + "description": "Key of API.Schema.MangaContext.MangaConnectorId`1", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "", + "content": { + "application/json; x-version=2.0": { + "schema": { + "$ref": "#/components/schemas/MangaConnectorId" + } + } + } + }, + "404": { + "description": "API.Schema.MangaContext.MangaConnectorId`1 with MangaConnectorIdId not found", + "content": { + "text/plain; x-version=2.0": { + "schema": { + "type": "string" + } + } + } + } + } + } + }, "/v2/FileLibrary": { "get": { "tags": [ @@ -127,50 +539,6 @@ } } }, - "delete": { - "tags": [ - "FileLibrary" - ], - "summary": "Deletes the !:FileLibraryId.LibraryName with FileLibraryId", - "parameters": [ - { - "name": "FileLibraryId", - "in": "path", - "description": "API.Controllers.DTOs.FileLibrary.Key", - "required": true, - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "" - }, - "404": { - "description": "API.Controllers.DTOs.FileLibrary with FileLibraryId not found.", - "content": { - "text/plain; x-version=2.0": { - "schema": { - "type": "string" - } - } - } - }, - "500": { - "description": "Error during Database Operation", - "content": { - "text/plain; x-version=2.0": { - "schema": { - "type": "string" - } - } - } - } - } - } - }, - "/v2/FileLibrary/{FileLibraryId}/ChangeBasePath": { "patch": { "tags": [ "FileLibrary" @@ -188,26 +556,25 @@ } ], "requestBody": { - "description": "New !:FileLibraryId.BasePath", "content": { "application/json-patch+json; x-version=2.0": { "schema": { - "type": "string" + "$ref": "#/components/schemas/PatchFileLibraryRecord" } }, "application/json; x-version=2.0": { "schema": { - "type": "string" + "$ref": "#/components/schemas/PatchFileLibraryRecord" } }, "text/json; x-version=2.0": { "schema": { - "type": "string" + "$ref": "#/components/schemas/PatchFileLibraryRecord" } }, "application/*+json; x-version=2.0": { "schema": { - "type": "string" + "$ref": "#/components/schemas/PatchFileLibraryRecord" } } } @@ -237,14 +604,12 @@ } } } - } - }, - "/v2/FileLibrary/{FileLibraryId}/ChangeName": { - "patch": { + }, + "delete": { "tags": [ "FileLibrary" ], - "summary": "Changes the !:FileLibraryId.LibraryName with FileLibraryId", + "summary": "Deletes the !:FileLibraryId.LibraryName with FileLibraryId", "parameters": [ { "name": "FileLibraryId", @@ -256,31 +621,6 @@ } } ], - "requestBody": { - "description": "New !:FileLibraryId.LibraryName", - "content": { - "application/json-patch+json; x-version=2.0": { - "schema": { - "type": "string" - } - }, - "application/json; x-version=2.0": { - "schema": { - "type": "string" - } - }, - "text/json; x-version=2.0": { - "schema": { - "type": "string" - } - }, - "application/*+json; x-version=2.0": { - "schema": { - "type": "string" - } - } - } - }, "responses": { "200": { "description": "" @@ -519,32 +859,6 @@ } } }, - "/v2/Manga/Keys": { - "get": { - "tags": [ - "Manga" - ], - "summary": "Returns all cached API.Schema.MangaContext.Manga.Keys", - "responses": { - "200": { - "description": "API.Schema.MangaContext.Manga Keys/IDs", - "content": { - "application/json; x-version=2.0": { - "schema": { - "type": "array", - "items": { - "type": "string" - } - } - } - } - }, - "500": { - "description": "Error during Database Operation" - } - } - } - }, "/v2/Manga/Downloading": { "get": { "tags": [ @@ -571,69 +885,6 @@ } } }, - "/v2/Manga/WithIDs": { - "post": { - "tags": [ - "Manga" - ], - "summary": "Returns all cached API.Schema.MangaContext.Manga with MangaIds", - "requestBody": { - "description": "Array of API.Schema.MangaContext.Manga.Key", - "content": { - "application/json-patch+json; x-version=2.0": { - "schema": { - "type": "array", - "items": { - "type": "string" - } - } - }, - "application/json; x-version=2.0": { - "schema": { - "type": "array", - "items": { - "type": "string" - } - } - }, - "text/json; x-version=2.0": { - "schema": { - "type": "array", - "items": { - "type": "string" - } - } - }, - "application/*+json; x-version=2.0": { - "schema": { - "type": "array", - "items": { - "type": "string" - } - } - } - } - }, - "responses": { - "200": { - "description": "API.Controllers.DTOs.Manga", - "content": { - "application/json; x-version=2.0": { - "schema": { - "type": "array", - "items": { - "$ref": "#/components/schemas/Manga" - } - } - } - } - }, - "500": { - "description": "Error during Database Operation" - } - } - } - }, "/v2/Manga/{MangaId}": { "get": { "tags": [ @@ -718,7 +969,7 @@ } }, "/v2/Manga/{MangaIdFrom}/MergeInto/{MangaIdInto}": { - "patch": { + "post": { "tags": [ "Manga" ], @@ -837,351 +1088,6 @@ } } }, - "/v2/Manga/{MangaId}/Chapters": { - "get": { - "tags": [ - "Manga" - ], - "summary": "Returns all API.Controllers.DTOs.Chapter of API.Controllers.DTOs.Manga with MangaId", - "parameters": [ - { - "name": "MangaId", - "in": "path", - "description": "API.Controllers.DTOs.Manga.Key", - "required": true, - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "", - "content": { - "application/json; x-version=2.0": { - "schema": { - "type": "array", - "items": { - "$ref": "#/components/schemas/Chapter" - } - } - } - } - }, - "404": { - "description": "API.Controllers.DTOs.Manga with MangaId not found", - "content": { - "text/plain; x-version=2.0": { - "schema": { - "$ref": "#/components/schemas/ProblemDetails" - } - }, - "application/json; x-version=2.0": { - "schema": { - "$ref": "#/components/schemas/ProblemDetails" - } - }, - "text/json; x-version=2.0": { - "schema": { - "$ref": "#/components/schemas/ProblemDetails" - } - } - } - } - } - } - }, - "/v2/Manga/{MangaId}/Chapters/Downloaded": { - "get": { - "tags": [ - "Manga" - ], - "summary": "Returns all downloaded API.Controllers.DTOs.Chapter for API.Controllers.DTOs.Manga with MangaId", - "parameters": [ - { - "name": "MangaId", - "in": "path", - "description": "API.Controllers.DTOs.Manga.Key", - "required": true, - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "", - "content": { - "application/json; x-version=2.0": { - "schema": { - "type": "array", - "items": { - "$ref": "#/components/schemas/Chapter" - } - } - } - } - }, - "204": { - "description": "No available chapters" - }, - "404": { - "description": "API.Controllers.DTOs.Manga with MangaId not found.", - "content": { - "text/plain; x-version=2.0": { - "schema": { - "type": "string" - } - } - } - } - } - } - }, - "/v2/Manga/{MangaId}/Chapters/NotDownloaded": { - "get": { - "tags": [ - "Manga" - ], - "summary": "Returns all API.Controllers.DTOs.Chapter not downloaded for API.Controllers.DTOs.Manga with MangaId", - "parameters": [ - { - "name": "MangaId", - "in": "path", - "description": "API.Controllers.DTOs.Manga.Key", - "required": true, - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "", - "content": { - "application/json; x-version=2.0": { - "schema": { - "type": "array", - "items": { - "$ref": "#/components/schemas/Chapter" - } - } - } - } - }, - "204": { - "description": "No available chapters" - }, - "404": { - "description": "API.Controllers.DTOs.Manga with MangaId not found.", - "content": { - "text/plain; x-version=2.0": { - "schema": { - "type": "string" - } - } - } - } - } - } - }, - "/v2/Manga/{MangaId}/Chapter/LatestAvailable": { - "get": { - "tags": [ - "Manga" - ], - "summary": "Returns the latest API.Controllers.DTOs.Chapter of requested API.Controllers.DTOs.Manga available on API.MangaConnectors.MangaConnector", - "parameters": [ - { - "name": "MangaId", - "in": "path", - "description": "API.Controllers.DTOs.Manga.Key", - "required": true, - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "", - "content": { - "application/json; x-version=2.0": { - "schema": { - "type": "integer", - "format": "int32" - } - } - } - }, - "204": { - "description": "No available chapters" - }, - "404": { - "description": "API.Controllers.DTOs.Manga with MangaId not found.", - "content": { - "text/plain; x-version=2.0": { - "schema": { - "type": "string" - } - } - } - }, - "500": { - "description": "Internal Server Error" - }, - "503": { - "description": "Retry after timeout, updating value" - }, - "412": { - "description": "Could not retrieve the maximum chapter-number" - } - } - } - }, - "/v2/Manga/{MangaId}/Chapter/LatestDownloaded": { - "get": { - "tags": [ - "Manga" - ], - "summary": "Returns the latest API.Controllers.DTOs.Chapter of requested API.Controllers.DTOs.Manga that is downloaded", - "parameters": [ - { - "name": "MangaId", - "in": "path", - "description": "API.Controllers.DTOs.Manga.Key", - "required": true, - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "", - "content": { - "application/json; x-version=2.0": { - "schema": { - "$ref": "#/components/schemas/Chapter" - } - } - } - }, - "204": { - "description": "No available chapters" - }, - "404": { - "description": "API.Controllers.DTOs.Manga with MangaId not found.", - "content": { - "text/plain; x-version=2.0": { - "schema": { - "type": "string" - } - } - } - }, - "412": { - "description": "Could not retrieve the maximum chapter-number", - "content": { - "text/plain; x-version=2.0": { - "schema": { - "$ref": "#/components/schemas/ProblemDetails" - } - }, - "application/json; x-version=2.0": { - "schema": { - "$ref": "#/components/schemas/ProblemDetails" - } - }, - "text/json; x-version=2.0": { - "schema": { - "$ref": "#/components/schemas/ProblemDetails" - } - } - } - }, - "503": { - "description": "Retry after timeout, updating value" - } - } - } - }, - "/v2/Manga/{MangaId}/IgnoreChaptersBefore": { - "patch": { - "tags": [ - "Manga" - ], - "summary": "Configure the API.Controllers.DTOs.Chapter cut-off for API.Controllers.DTOs.Manga", - "parameters": [ - { - "name": "MangaId", - "in": "path", - "description": "API.Controllers.DTOs.Manga.Key", - "required": true, - "schema": { - "type": "string" - } - } - ], - "requestBody": { - "description": "Threshold (API.Controllers.DTOs.Chapter ChapterNumber)", - "content": { - "application/json-patch+json; x-version=2.0": { - "schema": { - "type": "number", - "format": "float" - } - }, - "application/json; x-version=2.0": { - "schema": { - "type": "number", - "format": "float" - } - }, - "text/json; x-version=2.0": { - "schema": { - "type": "number", - "format": "float" - } - }, - "application/*+json; x-version=2.0": { - "schema": { - "type": "number", - "format": "float" - } - } - } - }, - "responses": { - "200": { - "description": "OK" - }, - "404": { - "description": "API.Controllers.DTOs.Manga with MangaId not found.", - "content": { - "text/plain; x-version=2.0": { - "schema": { - "type": "string" - } - } - } - }, - "500": { - "description": "Error during Database Operation", - "content": { - "text/plain; x-version=2.0": { - "schema": { - "type": "string" - } - } - } - }, - "202": { - "description": "" - } - } - } - }, "/v2/Manga/{MangaId}/ChangeLibrary/{LibraryId}": { "post": { "tags": [ @@ -1228,8 +1134,8 @@ } } }, - "/v2/Manga/{MangaId}/SetAsDownloadFrom/{MangaConnectorName}/{IsRequested}": { - "post": { + "/v2/Manga/{MangaId}/DownloadFrom/{MangaConnectorName}/{IsRequested}": { + "patch": { "tags": [ "Manga" ], @@ -1310,8 +1216,8 @@ } } }, - "/v2/Manga/{MangaId}/SearchOn/{MangaConnectorName}": { - "post": { + "/v2/Manga/{MangaId}/OnMangaConnector/{MangaConnectorName}": { + "get": { "tags": [ "Manga" ], @@ -1480,6 +1386,94 @@ } } }, + "/v2/Manga/WithSimilarName/{MangaId}": { + "get": { + "tags": [ + "Manga" + ], + "summary": "Returns API.Schema.MangaContext.Manga with names similar to API.Schema.MangaContext.Manga (identified by MangaId)", + "parameters": [ + { + "name": "MangaId", + "in": "path", + "description": "Key of API.Schema.MangaContext.Manga", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "", + "content": { + "application/json; x-version=2.0": { + "schema": { + "type": "array", + "items": { + "type": "string" + } + } + } + } + }, + "404": { + "description": "API.Schema.MangaContext.Manga with MangaId not found", + "content": { + "text/plain; x-version=2.0": { + "schema": { + "type": "string" + } + } + } + }, + "500": { + "description": "Error during Database Operation" + } + } + } + }, + "/v2/Manga/{MangaConnectorIdId}": { + "get": { + "tags": [ + "Manga" + ], + "summary": "Returns the API.Schema.MangaContext.MangaConnectorId`1 with API.Schema.MangaContext.MangaConnectorId`1.Key", + "parameters": [ + { + "name": "MangaConnectorIdId", + "in": "path", + "description": "Key of API.Schema.MangaContext.MangaConnectorId`1", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "", + "content": { + "application/json; x-version=2.0": { + "schema": { + "$ref": "#/components/schemas/MangaConnectorId" + } + } + } + }, + "404": { + "description": "API.Schema.MangaContext.MangaConnectorId`1 with MangaConnectorIdId not found", + "content": { + "text/plain; x-version=2.0": { + "schema": { + "type": "string" + } + } + } + } + } + } + }, "/v2/MangaConnector": { "get": { "tags": [ @@ -1544,35 +1538,22 @@ } } }, - "/v2/MangaConnector/Enabled": { + "/v2/MangaConnector/{Enabled}": { "get": { "tags": [ "MangaConnector" ], - "summary": "Get all enabled API.MangaConnectors.MangaConnector (Scanlation-Sites)", - "responses": { - "200": { - "description": "", - "content": { - "application/json; x-version=2.0": { - "schema": { - "type": "array", - "items": { - "$ref": "#/components/schemas/MangaConnector" - } - } - } + "summary": "Get all API.MangaConnectors.MangaConnector (Scanlation-Sites) with Enabled-Status", + "parameters": [ + { + "name": "Enabled", + "in": "path", + "required": true, + "schema": { + "type": "boolean" } } - } - } - }, - "/v2/MangaConnector/Disabled": { - "get": { - "tags": [ - "MangaConnector" ], - "summary": "Get all disabled API.MangaConnectors.MangaConnector (Scanlation-Sites)", "responses": { "200": { "description": "", @@ -2325,7 +2306,7 @@ } } }, - "/v2/Query/Author/{AuthorId}": { + "/v2/Author/{AuthorId}": { "get": { "tags": [ "Query" @@ -2366,176 +2347,6 @@ } } }, - "/v2/Query/Chapter/{ChapterId}": { - "get": { - "tags": [ - "Query" - ], - "summary": "Returns API.Controllers.DTOs.Chapter with ChapterId", - "parameters": [ - { - "name": "ChapterId", - "in": "path", - "description": "API.Controllers.DTOs.Chapter.Key", - "required": true, - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "", - "content": { - "application/json; x-version=2.0": { - "schema": { - "$ref": "#/components/schemas/Chapter" - } - } - } - }, - "404": { - "description": "API.Controllers.DTOs.Chapter with ChapterId not found", - "content": { - "text/plain; x-version=2.0": { - "schema": { - "type": "string" - } - } - } - } - } - } - }, - "/v2/Query/Manga/MangaConnectorId/{MangaConnectorIdId}": { - "get": { - "tags": [ - "Query" - ], - "summary": "Returns the API.Schema.MangaContext.MangaConnectorId`1 with API.Schema.MangaContext.MangaConnectorId`1.Key", - "parameters": [ - { - "name": "MangaConnectorIdId", - "in": "path", - "description": "Key of API.Schema.MangaContext.MangaConnectorId`1", - "required": true, - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "", - "content": { - "application/json; x-version=2.0": { - "schema": { - "$ref": "#/components/schemas/MangaConnectorId" - } - } - } - }, - "404": { - "description": "API.Schema.MangaContext.MangaConnectorId`1 with MangaConnectorIdId not found", - "content": { - "text/plain; x-version=2.0": { - "schema": { - "type": "string" - } - } - } - } - } - } - }, - "/v2/Query/Manga/{MangaId}/SimilarName": { - "get": { - "tags": [ - "Query" - ], - "summary": "Returns API.Schema.MangaContext.Manga with names similar to API.Schema.MangaContext.Manga (identified by MangaId)", - "parameters": [ - { - "name": "MangaId", - "in": "path", - "description": "Key of API.Schema.MangaContext.Manga", - "required": true, - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "", - "content": { - "application/json; x-version=2.0": { - "schema": { - "type": "array", - "items": { - "type": "string" - } - } - } - } - }, - "404": { - "description": "API.Schema.MangaContext.Manga with MangaId not found", - "content": { - "text/plain; x-version=2.0": { - "schema": { - "type": "string" - } - } - } - }, - "500": { - "description": "Error during Database Operation" - } - } - } - }, - "/v2/Query/Chapter/MangaConnectorId/{MangaConnectorIdId}": { - "get": { - "tags": [ - "Query" - ], - "summary": "Returns the API.Schema.MangaContext.MangaConnectorId`1 with API.Schema.MangaContext.MangaConnectorId`1.Key", - "parameters": [ - { - "name": "MangaConnectorIdId", - "in": "path", - "description": "Key of API.Schema.MangaContext.MangaConnectorId`1", - "required": true, - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "", - "content": { - "application/json; x-version=2.0": { - "schema": { - "$ref": "#/components/schemas/MangaConnectorId" - } - } - } - }, - "404": { - "description": "API.Schema.MangaContext.MangaConnectorId`1 with MangaConnectorIdId not found", - "content": { - "text/plain; x-version=2.0": { - "schema": { - "type": "string" - } - } - } - } - } - } - }, "/v2/Search/{MangaConnectorName}/{Query}": { "get": { "tags": [ @@ -2612,37 +2423,22 @@ } } }, - "/v2/Search/Url": { - "post": { + "/v2/Search": { + "get": { "tags": [ "Search" ], "summary": "Returns API.Schema.MangaContext.Manga from the API.Controllers.DTOs.MangaConnector associated with url", - "requestBody": { - "description": "", - "content": { - "application/json-patch+json; x-version=2.0": { - "schema": { - "type": "string" - } - }, - "application/json; x-version=2.0": { - "schema": { - "type": "string" - } - }, - "text/json; x-version=2.0": { - "schema": { - "type": "string" - } - }, - "application/*+json; x-version=2.0": { - "schema": { - "type": "string" - } + "parameters": [ + { + "name": "url", + "in": "query", + "description": "", + "schema": { + "type": "string" } } - }, + ], "responses": { "200": { "description": "API.Controllers.DTOs.MinimalManga exert of API.Schema.MangaContext.Manga.", @@ -2763,20 +2559,6 @@ } } }, - "/v2/Settings/RequestLimits": { - "patch": { - "tags": [ - "Settings" - ], - "summary": "Update all Request-Limits to new values", - "description": "

NOT IMPLEMENTED

", - "responses": { - "501": { - "description": "Not Implemented" - } - } - } - }, "/v2/Settings/ImageCompressionLevel": { "get": { "tags": [ @@ -2945,7 +2727,7 @@ } }, "/v2/Settings/FlareSolverr/Url": { - "post": { + "patch": { "tags": [ "Settings" ], @@ -3112,29 +2894,6 @@ } } }, - "/v2/Worker/Keys": { - "get": { - "tags": [ - "Worker" - ], - "summary": "Returns all API.Workers.BaseWorker.Keys", - "responses": { - "200": { - "description": "", - "content": { - "application/json; x-version=2.0": { - "schema": { - "type": "array", - "items": { - "type": "string" - } - } - } - } - } - } - } - }, "/v2/Worker/State/{State}": { "get": { "tags": [ @@ -3208,95 +2967,6 @@ } } } - }, - "delete": { - "tags": [ - "Worker" - ], - "summary": "Delete API.Workers.BaseWorker with WorkerId and all child-API.Workers.BaseWorkers", - "parameters": [ - { - "name": "WorkerId", - "in": "path", - "description": "API.Workers.BaseWorker.Key", - "required": true, - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "" - }, - "404": { - "description": "API.Workers.BaseWorker with WorkerId could not be found", - "content": { - "text/plain; x-version=2.0": { - "schema": { - "type": "string" - } - } - } - } - } - } - }, - "/v2/Worker/{WorkerId}/Start": { - "post": { - "tags": [ - "Worker" - ], - "summary": "Starts API.Workers.BaseWorker with WorkerId", - "parameters": [ - { - "name": "WorkerId", - "in": "path", - "description": "API.Workers.BaseWorker.Key", - "required": true, - "schema": { - "type": "string" - } - } - ], - "responses": { - "202": { - "description": "Accepted" - }, - "404": { - "description": "API.Workers.BaseWorker with WorkerId could not be found", - "content": { - "text/plain; x-version=2.0": { - "schema": { - "type": "string" - } - } - } - }, - "412": { - "description": "API.Workers.BaseWorker was already running", - "content": { - "text/plain; x-version=2.0": { - "schema": { - "$ref": "#/components/schemas/ProblemDetails" - } - }, - "application/json; x-version=2.0": { - "schema": { - "$ref": "#/components/schemas/ProblemDetails" - } - }, - "text/json; x-version=2.0": { - "schema": { - "$ref": "#/components/schemas/ProblemDetails" - } - } - } - }, - "200": { - "description": "" - } - } } }, "/v2/Worker/{WorkerId}/Stop": { @@ -4083,6 +3753,26 @@ }, "additionalProperties": false }, + "PatchFileLibraryRecord": { + "required": [ + "name", + "path" + ], + "type": "object", + "properties": { + "path": { + "type": "string", + "description": "Directory Path", + "nullable": true + }, + "name": { + "type": "string", + "description": "Library Name", + "nullable": true + } + }, + "additionalProperties": false + }, "PatchLibraryRefreshRecord": { "required": [ "setting" @@ -4124,9 +3814,16 @@ "instance": { "type": "string", "nullable": true + }, + "extensions": { + "type": "object", + "additionalProperties": { + "nullable": true + }, + "nullable": true } }, - "additionalProperties": { } + "additionalProperties": false }, "TrangaSettings": { "type": "object", diff --git a/docker-compose.local.yaml b/docker-compose.local.yaml index 80c61dd..3d6b3f3 100644 --- a/docker-compose.local.yaml +++ b/docker-compose.local.yaml @@ -15,6 +15,7 @@ services: condition: service_healthy environment: - POSTGRES_HOST=tranga-pg + - CHECK_CHAPTERS_BEFORE_START=false restart: unless-stopped logging: driver: json-file