mirror of
https://github.com/C9Glax/tranga.git
synced 2025-10-14 17:30:45 +02:00
Update Endpoints
This commit is contained in:
220
API/Controllers/ChaptersController.cs
Normal file
220
API/Controllers/ChaptersController.cs
Normal file
@@ -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
|
||||
{
|
||||
/// <summary>
|
||||
/// Returns all <see cref="Schema.MangaContext.Chapter"/> of <see cref="Schema.MangaContext.Manga"/> with <paramref name="MangaId"/>
|
||||
/// </summary>
|
||||
/// <param name="MangaId"><see cref="Schema.MangaContext.Manga"/>.Key</param>
|
||||
/// <response code="200"></response>
|
||||
/// <response code="404"><see cref="Schema.MangaContext.Manga"/> with <paramref name="MangaId"/> not found</response>
|
||||
[HttpGet("{MangaId}")]
|
||||
[ProducesResponseType<List<Chapter>>(Status200OK, "application/json")]
|
||||
[ProducesResponseType(Status404NotFound)]
|
||||
public async Task<Results<Ok<List<Chapter>>, NotFound<string>>> 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<Chapter> chapters = dbChapters.OrderDescending().Select(c =>
|
||||
{
|
||||
IEnumerable<MangaConnectorId> 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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns all downloaded <see cref="Chapter"/> for <see cref="Schema.MangaContext.Manga"/> with <paramref name="MangaId"/>
|
||||
/// </summary>
|
||||
/// <param name="MangaId"><see cref="Schema.MangaContext.Manga"/>.Key</param>
|
||||
/// <response code="200"></response>
|
||||
/// <response code="404"><see cref="Schema.MangaContext.Manga"/> with <paramref name="MangaId"/> not found.</response>
|
||||
[HttpGet("{MangaId}/Downloaded")]
|
||||
[ProducesResponseType<Chapter[]>(Status200OK, "application/json")]
|
||||
[ProducesResponseType<string>(Status404NotFound, "text/plain")]
|
||||
public async Task<Results<Ok<List<Chapter>>, NotFound<string>>> 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<Chapter> chapters = dbChapters.OrderDescending().Select(c =>
|
||||
{
|
||||
IEnumerable<MangaConnectorId> 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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns all <see cref="Chapter"/> not downloaded for <see cref="Schema.MangaContext.Manga"/> with <paramref name="MangaId"/>
|
||||
/// </summary>
|
||||
/// <param name="MangaId"><see cref="Schema.MangaContext.Manga"/>.Key</param>
|
||||
/// <response code="200"></response>
|
||||
/// <response code="404"><see cref="Schema.MangaContext.Manga"/> with <paramref name="MangaId"/> not found.</response>
|
||||
[HttpGet("{MangaId}/NotDownloaded")]
|
||||
[ProducesResponseType<List<Chapter>>(Status200OK, "application/json")]
|
||||
[ProducesResponseType<string>(Status404NotFound, "text/plain")]
|
||||
public async Task<Results<Ok<List<Chapter>>, NoContent, NotFound<string>>> 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<Chapter> chapters = dbChapters.OrderDescending().Select(c =>
|
||||
{
|
||||
IEnumerable<MangaConnectorId> 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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the latest <see cref="Chapter"/> of requested <see cref="Schema.MangaContext.Manga"/>
|
||||
/// </summary>
|
||||
/// <param name="MangaId"><see cref="Schema.MangaContext.Manga"/>.Key</param>
|
||||
/// <response code="200"></response>
|
||||
/// <response code="204">No available chapters</response>
|
||||
/// <response code="404"><see cref="Schema.MangaContext.Manga"/> with <paramref name="MangaId"/> not found.</response>
|
||||
[HttpGet("{MangaId}/LatestAvailable")]
|
||||
[ProducesResponseType<int>(Status200OK, "application/json")]
|
||||
[ProducesResponseType(Status204NoContent)]
|
||||
[ProducesResponseType<string>(Status404NotFound, "text/plain")]
|
||||
public async Task<Results<Ok<Chapter>, NoContent, NotFound<string>>> 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<MangaConnectorId> 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));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the latest <see cref="Chapter"/> of requested <see cref="Schema.MangaContext.Manga"/> that is downloaded
|
||||
/// </summary>
|
||||
/// <param name="MangaId"><see cref="Schema.MangaContext.Manga"/>.Key</param>
|
||||
/// <response code="200"></response>
|
||||
/// <response code="204">No available chapters</response>
|
||||
/// <response code="404"><see cref="Schema.MangaContext.Manga"/> with <paramref name="MangaId"/> not found.</response>
|
||||
/// <response code="412">Could not retrieve the maximum chapter-number</response>
|
||||
/// <response code="503">Retry after timeout, updating value</response>
|
||||
[HttpGet("{MangaId}/LatestDownloaded")]
|
||||
[ProducesResponseType<Chapter>(Status200OK, "application/json")]
|
||||
[ProducesResponseType(Status204NoContent)]
|
||||
[ProducesResponseType<string>(Status404NotFound, "text/plain")]
|
||||
[ProducesResponseType(Status412PreconditionFailed)]
|
||||
[ProducesResponseType(Status503ServiceUnavailable)]
|
||||
public async Task<Results<Ok<Chapter>, NoContent, NotFound<string>, 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<MangaConnectorId> 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));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Configure the <see cref="Chapter"/> cut-off for <see cref="Schema.MangaContext.Manga"/>
|
||||
/// </summary>
|
||||
/// <param name="MangaId"><see cref="Schema.MangaContext.Manga"/>.Key</param>
|
||||
/// <param name="chapterThreshold">Threshold (<see cref="Chapter"/> ChapterNumber)</param>
|
||||
/// <response code="202"></response>
|
||||
/// <response code="404"><see cref="Schema.MangaContext.Manga"/> with <paramref name="MangaId"/> not found.</response>
|
||||
/// <response code="500">Error during Database Operation</response>
|
||||
[HttpPatch("{MangaId}/IgnoreBefore")]
|
||||
[ProducesResponseType(Status200OK)]
|
||||
[ProducesResponseType<string>(Status404NotFound, "text/plain")]
|
||||
[ProducesResponseType<string>(Status500InternalServerError, "text/plain")]
|
||||
public async Task<Results<Ok, NotFound<string>, InternalServerError<string>>> 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();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns <see cref="Chapter"/> with <paramref name="ChapterId"/>
|
||||
/// </summary>
|
||||
/// <param name="ChapterId"><see cref="Chapter"/>.Key</param>
|
||||
/// <response code="200"></response>
|
||||
/// <response code="404"><see cref="Chapter"/> with <paramref name="ChapterId"/> not found</response>
|
||||
[HttpGet("{ChapterId}")]
|
||||
[ProducesResponseType<Chapter>(Status200OK, "application/json")]
|
||||
[ProducesResponseType<string>(Status404NotFound, "text/plain")]
|
||||
public async Task<Results<Ok<Chapter>, NotFound<string>>> GetChapter (string ChapterId)
|
||||
{
|
||||
if (await context.Chapters.FirstOrDefaultAsync(c => c.Key == ChapterId, HttpContext.RequestAborted) is not { } chapter)
|
||||
return TypedResults.NotFound(nameof(ChapterId));
|
||||
|
||||
IEnumerable<MangaConnectorId> 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));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the <see cref="MangaConnectorId{Chapter}"/> with <see cref="MangaConnectorId{Chapter}"/>.Key
|
||||
/// </summary>
|
||||
/// <param name="MangaConnectorIdId">Key of <see cref="MangaConnectorId{Chapter}"/></param>
|
||||
/// <response code="200"></response>
|
||||
/// <response code="404"><see cref="MangaConnectorId{Manga}"/> with <paramref name="MangaConnectorIdId"/> not found</response>
|
||||
[HttpGet("{MangaConnectorIdId}")]
|
||||
[ProducesResponseType<MangaConnectorId>(Status200OK, "application/json")]
|
||||
[ProducesResponseType<string>(Status404NotFound, "text/plain")]
|
||||
public async Task<Results<Ok<MangaConnectorId>, NotFound<string>>> 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);
|
||||
}
|
||||
}
|
@@ -59,46 +59,35 @@ public class FileLibraryController(MangaContext context) : Controller
|
||||
/// <response code="200"></response>
|
||||
/// <response code="404"><see cref="FileLibrary"/> with <paramref name="FileLibraryId"/> not found.</response>
|
||||
/// <response code="500">Error during Database Operation</response>
|
||||
[HttpPatch("{FileLibraryId}/ChangeBasePath")]
|
||||
[HttpPatch("{FileLibraryId}")]
|
||||
[ProducesResponseType(Status200OK)]
|
||||
[ProducesResponseType<string>(Status404NotFound, "text/plain")]
|
||||
[ProducesResponseType<string>(Status500InternalServerError, "text/plain")]
|
||||
public async Task<Results<Ok, NotFound<string>, InternalServerError<string>>> ChangeLibraryBasePath (string FileLibraryId, [FromBody]string newBasePath)
|
||||
public async Task<Results<Ok, NotFound<string>, InternalServerError<string>>> 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();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Changes the <see cref="FileLibraryId"/>.LibraryName with <paramref name="FileLibraryId"/>
|
||||
/// </summary>
|
||||
/// <param name="FileLibraryId"><see cref="FileLibrary"/>.Key</param>
|
||||
/// <param name="newName">New <see cref="FileLibraryId"/>.LibraryName</param>
|
||||
/// <response code="200"></response>
|
||||
/// <response code="404"><see cref="FileLibrary"/> with <paramref name="FileLibraryId"/> not found.</response>
|
||||
/// <response code="500">Error during Database Operation</response>
|
||||
[HttpPatch("{FileLibraryId}/ChangeName")]
|
||||
[ProducesResponseType(Status200OK)]
|
||||
[ProducesResponseType<string>(Status404NotFound, "text/plain")]
|
||||
[ProducesResponseType<string>(Status500InternalServerError, "text/plain")]
|
||||
public async Task<Results<Ok, NotFound<string>, InternalServerError<string>>> 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();
|
||||
/// <summary>
|
||||
/// Directory Path
|
||||
/// </summary>
|
||||
public required string? Path { get; init; } = Path;
|
||||
/// <summary>
|
||||
/// Library Name
|
||||
/// </summary>
|
||||
public required string? Name { get; init; } = Name;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@@ -44,29 +44,15 @@ public class MangaConnectorController(MangaContext context) : Controller
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get all enabled <see cref="API.MangaConnectors.MangaConnector"/> (Scanlation-Sites)
|
||||
/// Get all <see cref="API.MangaConnectors.MangaConnector"/> (Scanlation-Sites) with <paramref name="Enabled"/>-Status
|
||||
/// </summary>
|
||||
/// <response code="200"></response>
|
||||
[HttpGet("Enabled")]
|
||||
[HttpGet("{Enabled}")]
|
||||
[ProducesResponseType<List<MangaConnector>>(Status200OK, "application/json")]
|
||||
public Ok<List<MangaConnector>> GetEnabledConnectors()
|
||||
public Ok<List<MangaConnector>> 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());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get all disabled <see cref="API.MangaConnectors.MangaConnector"/> (Scanlation-Sites)
|
||||
/// </summary>
|
||||
/// <response code="200"></response>
|
||||
[HttpGet("Disabled")]
|
||||
[ProducesResponseType<List<MangaConnector>>(Status200OK, "application/json")]
|
||||
public Ok<List<MangaConnector>> 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());
|
||||
}
|
||||
|
@@ -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());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns all cached <see cref="Schema.MangaContext.Manga"/> with <paramref name="MangaIds"/>
|
||||
/// </summary>
|
||||
/// <param name="MangaIds">Array of <see cref="Schema.MangaContext.Manga"/>.Key</param>
|
||||
/// <response code="200"><see cref="DTOs.Manga"/></response>
|
||||
/// <response code="500">Error during Database Operation</response>
|
||||
[HttpPost("WithIDs")]
|
||||
[ProducesResponseType<List<Manga>>(Status200OK, "application/json")]
|
||||
[ProducesResponseType(Status500InternalServerError)]
|
||||
public async Task<Results<Ok<List<Manga>>, 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<MangaConnectorId> ids = m.MangaConnectorIds.Select(id => new MangaConnectorId(id.Key, id.MangaConnectorName, id.ObjId, id.WebsiteUrl, id.UseForDownload));
|
||||
IEnumerable<Author> authors = m.Authors.Select(a => new Author(a.Key, a.AuthorName));
|
||||
IEnumerable<string> tags = m.MangaTags.Select(t => t.Tag);
|
||||
IEnumerable<Link> links = m.Links.Select(l => new Link(l.Key, l.LinkProvider, l.LinkUrl));
|
||||
IEnumerable<AltTitle> 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());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Return <see cref="Schema.MangaContext.Manga"/> with <paramref name="MangaId"/>
|
||||
@@ -152,7 +124,7 @@ public class MangaController(MangaContext context) : Controller
|
||||
/// <param name="MangaIdInto"><see cref="Manga"/>.Key of <see cref="Manga"/> merging data into</param>
|
||||
/// <response code="200"></response>
|
||||
/// <response code="404"><see cref="Manga"/> with <paramref name="MangaIdFrom"/> or <paramref name="MangaIdInto"/> not found</response>
|
||||
[HttpPatch("{MangaIdFrom}/MergeInto/{MangaIdInto}")]
|
||||
[HttpPost("{MangaIdFrom}/MergeInto/{MangaIdInto}")]
|
||||
[ProducesResponseType(Status200OK)]
|
||||
[ProducesResponseType<string>(Status404NotFound, "text/plain")]
|
||||
public async Task<Results<Ok, NotFound<string>>> MergeIntoManga (string MangaIdFrom, string MangaIdInto)
|
||||
@@ -219,171 +191,6 @@ public class MangaController(MangaContext context) : Controller
|
||||
}
|
||||
public enum CoverSize { Original, Large, Medium, Small }
|
||||
|
||||
/// <summary>
|
||||
/// Returns all <see cref="Chapter"/> of <see cref="Manga"/> with <paramref name="MangaId"/>
|
||||
/// </summary>
|
||||
/// <param name="MangaId"><see cref="Manga"/>.Key</param>
|
||||
/// <response code="200"></response>
|
||||
/// <response code="404"><see cref="Manga"/> with <paramref name="MangaId"/> not found</response>
|
||||
[HttpGet("{MangaId}/Chapters")]
|
||||
[ProducesResponseType<List<Chapter>>(Status200OK, "application/json")]
|
||||
[ProducesResponseType(Status404NotFound)]
|
||||
public async Task<Results<Ok<List<Chapter>>, NotFound<string>>> 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<Chapter> chapters = dbChapters.OrderDescending().Select(c =>
|
||||
{
|
||||
IEnumerable<MangaConnectorId> 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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns all downloaded <see cref="Chapter"/> for <see cref="Manga"/> with <paramref name="MangaId"/>
|
||||
/// </summary>
|
||||
/// <param name="MangaId"><see cref="Manga"/>.Key</param>
|
||||
/// <response code="200"></response>
|
||||
/// <response code="404"><see cref="Manga"/> with <paramref name="MangaId"/> not found.</response>
|
||||
[HttpGet("{MangaId}/Chapters/Downloaded")]
|
||||
[ProducesResponseType<Chapter[]>(Status200OK, "application/json")]
|
||||
[ProducesResponseType<string>(Status404NotFound, "text/plain")]
|
||||
public async Task<Results<Ok<List<Chapter>>, NotFound<string>>> 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<Chapter> chapters = dbChapters.OrderDescending().Select(c =>
|
||||
{
|
||||
IEnumerable<MangaConnectorId> 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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns all <see cref="Chapter"/> not downloaded for <see cref="Manga"/> with <paramref name="MangaId"/>
|
||||
/// </summary>
|
||||
/// <param name="MangaId"><see cref="Manga"/>.Key</param>
|
||||
/// <response code="200"></response>
|
||||
/// <response code="404"><see cref="Manga"/> with <paramref name="MangaId"/> not found.</response>
|
||||
[HttpGet("{MangaId}/Chapters/NotDownloaded")]
|
||||
[ProducesResponseType<List<Chapter>>(Status200OK, "application/json")]
|
||||
[ProducesResponseType<string>(Status404NotFound, "text/plain")]
|
||||
public async Task<Results<Ok<List<Chapter>>, NoContent, NotFound<string>>> 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<Chapter> chapters = dbChapters.OrderDescending().Select(c =>
|
||||
{
|
||||
IEnumerable<MangaConnectorId> 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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the latest <see cref="Chapter"/> of requested <see cref="Manga"/>
|
||||
/// </summary>
|
||||
/// <param name="MangaId"><see cref="Manga"/>.Key</param>
|
||||
/// <response code="200"></response>
|
||||
/// <response code="204">No available chapters</response>
|
||||
/// <response code="404"><see cref="Manga"/> with <paramref name="MangaId"/> not found.</response>
|
||||
[HttpGet("{MangaId}/Chapter/LatestAvailable")]
|
||||
[ProducesResponseType<int>(Status200OK, "application/json")]
|
||||
[ProducesResponseType(Status204NoContent)]
|
||||
[ProducesResponseType<string>(Status404NotFound, "text/plain")]
|
||||
public async Task<Results<Ok<Chapter>, NoContent, NotFound<string>>> 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<MangaConnectorId> 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));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the latest <see cref="Chapter"/> of requested <see cref="Manga"/> that is downloaded
|
||||
/// </summary>
|
||||
/// <param name="MangaId"><see cref="Manga"/>.Key</param>
|
||||
/// <response code="200"></response>
|
||||
/// <response code="204">No available chapters</response>
|
||||
/// <response code="404"><see cref="Manga"/> with <paramref name="MangaId"/> not found.</response>
|
||||
/// <response code="412">Could not retrieve the maximum chapter-number</response>
|
||||
/// <response code="503">Retry after timeout, updating value</response>
|
||||
[HttpGet("{MangaId}/Chapter/LatestDownloaded")]
|
||||
[ProducesResponseType<Chapter>(Status200OK, "application/json")]
|
||||
[ProducesResponseType(Status204NoContent)]
|
||||
[ProducesResponseType<string>(Status404NotFound, "text/plain")]
|
||||
[ProducesResponseType(Status412PreconditionFailed)]
|
||||
[ProducesResponseType(Status503ServiceUnavailable)]
|
||||
public async Task<Results<Ok<Chapter>, NoContent, NotFound<string>, 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<MangaConnectorId> 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));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Configure the <see cref="Chapter"/> cut-off for <see cref="Manga"/>
|
||||
/// </summary>
|
||||
/// <param name="MangaId"><see cref="Manga"/>.Key</param>
|
||||
/// <param name="chapterThreshold">Threshold (<see cref="Chapter"/> ChapterNumber)</param>
|
||||
/// <response code="202"></response>
|
||||
/// <response code="404"><see cref="Manga"/> with <paramref name="MangaId"/> not found.</response>
|
||||
/// <response code="500">Error during Database Operation</response>
|
||||
[HttpPatch("{MangaId}/IgnoreChaptersBefore")]
|
||||
[ProducesResponseType(Status200OK)]
|
||||
[ProducesResponseType<string>(Status404NotFound, "text/plain")]
|
||||
[ProducesResponseType<string>(Status500InternalServerError, "text/plain")]
|
||||
public async Task<Results<Ok, NotFound<string>, InternalServerError<string>>> 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();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Move <see cref="Manga"/> to different <see cref="DTOs.FileLibrary"/>
|
||||
/// </summary>
|
||||
@@ -422,7 +229,7 @@ public class MangaController(MangaContext context) : Controller
|
||||
/// <response code="412"><see cref="Manga"/> was not linked to <see cref="API.MangaConnectors.MangaConnector"/>, so nothing changed</response>
|
||||
/// <response code="428"><see cref="Manga"/> is not linked to <see cref="API.MangaConnectors.MangaConnector"/> yet. Search for <see cref="Manga"/> on <see cref="API.MangaConnectors.MangaConnector"/> first (to create a <see cref="MangaConnectorId{T}"/>).</response>
|
||||
/// <response code="500">Error during Database Operation</response>
|
||||
[HttpPost("{MangaId}/SetAsDownloadFrom/{MangaConnectorName}/{IsRequested}")]
|
||||
[HttpPatch("{MangaId}/DownloadFrom/{MangaConnectorName}/{IsRequested}")]
|
||||
[ProducesResponseType(Status200OK)]
|
||||
[ProducesResponseType<string>(Status404NotFound, "text/plain")]
|
||||
[ProducesResponseType<string>(Status412PreconditionFailed, "text/plain")]
|
||||
@@ -464,7 +271,7 @@ public class MangaController(MangaContext context) : Controller
|
||||
/// <response code="200"><see cref="MinimalManga"/> exert of <see cref="Schema.MangaContext.Manga"/></response>
|
||||
/// <response code="404"><see cref="API.MangaConnectors.MangaConnector"/> with Name not found</response>
|
||||
/// <response code="412"><see cref="API.MangaConnectors.MangaConnector"/> with Name is disabled</response>
|
||||
[HttpPost("{MangaId}/SearchOn/{MangaConnectorName}")]
|
||||
[HttpGet("{MangaId}/OnMangaConnector/{MangaConnectorName}")]
|
||||
[ProducesResponseType<List<MinimalManga>>(Status200OK, "application/json")]
|
||||
[ProducesResponseType<string>(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());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns <see cref="Schema.MangaContext.Manga"/> with names similar to <see cref="Schema.MangaContext.Manga"/> (identified by <paramref name="MangaId"/>)
|
||||
/// </summary>
|
||||
/// <param name="MangaId">Key of <see cref="Schema.MangaContext.Manga"/></param>
|
||||
/// <response code="200"></response>
|
||||
/// <response code="404"><see cref="Schema.MangaContext.Manga"/> with <paramref name="MangaId"/> not found</response>
|
||||
/// <response code="500">Error during Database Operation</response>
|
||||
[HttpGet("WithSimilarName/{MangaId}")]
|
||||
[ProducesResponseType<List<string>>(Status200OK, "application/json")]
|
||||
[ProducesResponseType<string>(Status404NotFound, "text/plain")]
|
||||
[ProducesResponseType(Status500InternalServerError)]
|
||||
public async Task<Results<Ok<List<string>>, NotFound<string>, 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<string> similarIds = mangaNames
|
||||
.Where(kv => NeedlemanWunschStringUtil.CalculateSimilarityPercentage(name, kv.Value) > 0.8)
|
||||
.Select(kv => kv.Key)
|
||||
.ToList();
|
||||
|
||||
return TypedResults.Ok(similarIds);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the <see cref="MangaConnectorId{Manga}"/> with <see cref="MangaConnectorId{Manga}"/>.Key
|
||||
/// </summary>
|
||||
/// <param name="MangaConnectorIdId">Key of <see cref="MangaConnectorId{Manga}"/></param>
|
||||
/// <response code="200"></response>
|
||||
/// <response code="404"><see cref="MangaConnectorId{Manga}"/> with <paramref name="MangaConnectorIdId"/> not found</response>
|
||||
[HttpGet("{MangaConnectorIdId}")]
|
||||
[ProducesResponseType<MangaConnectorId>(Status200OK, "application/json")]
|
||||
[ProducesResponseType<string>(Status404NotFound, "text/plain")]
|
||||
public async Task<Results<Ok<MangaConnectorId>, NotFound<string>>> 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);
|
||||
}
|
||||
}
|
@@ -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
|
||||
{
|
||||
/// <summary>
|
||||
@@ -34,91 +34,4 @@ public class QueryController(MangaContext context) : Controller
|
||||
|
||||
return TypedResults.Ok(new Author(author.Key, author.AuthorName));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns <see cref="Chapter"/> with <paramref name="ChapterId"/>
|
||||
/// </summary>
|
||||
/// <param name="ChapterId"><see cref="Chapter"/>.Key</param>
|
||||
/// <response code="200"></response>
|
||||
/// <response code="404"><see cref="Chapter"/> with <paramref name="ChapterId"/> not found</response>
|
||||
[HttpGet("Chapter/{ChapterId}")]
|
||||
[ProducesResponseType<Chapter>(Status200OK, "application/json")]
|
||||
[ProducesResponseType<string>(Status404NotFound, "text/plain")]
|
||||
public async Task<Results<Ok<Chapter>, NotFound<string>>> GetChapter (string ChapterId)
|
||||
{
|
||||
if (await context.Chapters.FirstOrDefaultAsync(c => c.Key == ChapterId, HttpContext.RequestAborted) is not { } chapter)
|
||||
return TypedResults.NotFound(nameof(ChapterId));
|
||||
|
||||
IEnumerable<MangaConnectorId> 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));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the <see cref="MangaConnectorId{Manga}"/> with <see cref="MangaConnectorId{Manga}"/>.Key
|
||||
/// </summary>
|
||||
/// <param name="MangaConnectorIdId">Key of <see cref="MangaConnectorId{Manga}"/></param>
|
||||
/// <response code="200"></response>
|
||||
/// <response code="404"><see cref="MangaConnectorId{Manga}"/> with <paramref name="MangaConnectorIdId"/> not found</response>
|
||||
[HttpGet("Manga/MangaConnectorId/{MangaConnectorIdId}")]
|
||||
[ProducesResponseType<MangaConnectorId>(Status200OK, "application/json")]
|
||||
[ProducesResponseType<string>(Status404NotFound, "text/plain")]
|
||||
public async Task<Results<Ok<MangaConnectorId>, NotFound<string>>> 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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns <see cref="Schema.MangaContext.Manga"/> with names similar to <see cref="Schema.MangaContext.Manga"/> (identified by <paramref name="MangaId"/>)
|
||||
/// </summary>
|
||||
/// <param name="MangaId">Key of <see cref="Schema.MangaContext.Manga"/></param>
|
||||
/// <response code="200"></response>
|
||||
/// <response code="404"><see cref="Schema.MangaContext.Manga"/> with <paramref name="MangaId"/> not found</response>
|
||||
/// <response code="500">Error during Database Operation</response>
|
||||
[HttpGet("Manga/{MangaId}/SimilarName")]
|
||||
[ProducesResponseType<List<string>>(Status200OK, "application/json")]
|
||||
[ProducesResponseType<string>(Status404NotFound, "text/plain")]
|
||||
[ProducesResponseType(Status500InternalServerError)]
|
||||
public async Task<Results<Ok<List<string>>, NotFound<string>, 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<string> similarIds = mangaNames
|
||||
.Where(kv => NeedlemanWunschStringUtil.CalculateSimilarityPercentage(name, kv.Value) > 0.8)
|
||||
.Select(kv => kv.Key)
|
||||
.ToList();
|
||||
|
||||
return TypedResults.Ok(similarIds);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the <see cref="MangaConnectorId{Chapter}"/> with <see cref="MangaConnectorId{Chapter}"/>.Key
|
||||
/// </summary>
|
||||
/// <param name="MangaConnectorIdId">Key of <see cref="MangaConnectorId{Manga}"/></param>
|
||||
/// <response code="200"></response>
|
||||
/// <response code="404"><see cref="MangaConnectorId{Chapter}"/> with <paramref name="MangaConnectorIdId"/> not found</response>
|
||||
[HttpGet("Chapter/MangaConnectorId/{MangaConnectorIdId}")]
|
||||
[ProducesResponseType<MangaConnectorId>(Status200OK, "application/json")]
|
||||
[ProducesResponseType<string>(Status404NotFound, "text/plain")]
|
||||
public async Task<Results<Ok<MangaConnectorId>, NotFound<string>>> 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);
|
||||
}
|
||||
}
|
@@ -58,11 +58,11 @@ public class SearchController(MangaContext context) : Controller
|
||||
/// <response code="200"><see cref="MinimalManga"/> exert of <see cref="Schema.MangaContext.Manga"/>.</response>
|
||||
/// <response code="404"><see cref="Manga"/> not found</response>
|
||||
/// <response code="500">Error during Database Operation</response>
|
||||
[HttpPost("Url")]
|
||||
[HttpGet]
|
||||
[ProducesResponseType<MinimalManga>(Status200OK, "application/json")]
|
||||
[ProducesResponseType<string>(Status404NotFound, "text/plain")]
|
||||
[ProducesResponseType<string>(Status500InternalServerError, "text/plain")]
|
||||
public async Task<Results<Ok<MinimalManga>, NotFound<string>, InternalServerError<string>>> GetMangaFromUrl([FromBody]string url)
|
||||
public async Task<Results<Ok<MinimalManga>, NotFound<string>, InternalServerError<string>>> 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.");
|
||||
|
@@ -60,17 +60,6 @@ public class SettingsController() : Controller
|
||||
return TypedResults.Ok();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Update all Request-Limits to new values
|
||||
/// </summary>
|
||||
/// <remarks><h1>NOT IMPLEMENTED</h1></remarks>
|
||||
[HttpPatch("RequestLimits")]
|
||||
[ProducesResponseType(Status501NotImplemented)]
|
||||
public StatusCodeHttpResult SetRequestLimits()
|
||||
{
|
||||
return TypedResults.StatusCode(Status501NotImplemented);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns Level of Image-Compression for Images
|
||||
/// </summary>
|
||||
@@ -179,7 +168,7 @@ public class SettingsController() : Controller
|
||||
/// </summary>
|
||||
/// <param name="flareSolverrUrl">URL of FlareSolverr-Instance</param>
|
||||
/// <response code="200"></response>
|
||||
[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();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@@ -21,21 +21,10 @@ public class WorkerController : Controller
|
||||
[ProducesResponseType<List<Worker>>(Status200OK, "application/json")]
|
||||
public Ok<List<Worker>> GetWorkers()
|
||||
{
|
||||
IEnumerable<Worker> result = Tranga.GetRunningWorkers().Select(w =>
|
||||
IEnumerable<Worker> 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());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns all <see cref="BaseWorker"/>.Keys
|
||||
/// </summary>
|
||||
/// <response code="200"></response>
|
||||
[HttpGet("Keys")]
|
||||
[ProducesResponseType<string[]>(Status200OK, "application/json")]
|
||||
public Ok<List<string>> GetWorkerIds()
|
||||
{
|
||||
return TypedResults.Ok(Tranga.GetRunningWorkers().Select(w => w.Key).ToList());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get all <see cref="BaseWorker"/> in requested <see cref="WorkerExecutionState"/>
|
||||
@@ -46,7 +35,7 @@ public class WorkerController : Controller
|
||||
[ProducesResponseType<List<Worker>>(Status200OK, "application/json")]
|
||||
public Ok<List<Worker>> GetWorkersInState(WorkerExecutionState State)
|
||||
{
|
||||
IEnumerable<Worker> result = Tranga.GetRunningWorkers().Where(worker => worker.State == State).Select(w =>
|
||||
IEnumerable<Worker> 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<string>(Status404NotFound, "text/plain")]
|
||||
public Results<Ok<Worker>, NotFound<string>> 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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Delete <see cref="BaseWorker"/> with <paramref name="WorkerId"/> and all child-<see cref="BaseWorker"/>s
|
||||
/// </summary>
|
||||
/// <param name="WorkerId"><see cref="BaseWorker"/>.Key</param>
|
||||
/// <response code="200"></response>
|
||||
/// <response code="404"><see cref="BaseWorker"/> with <paramref name="WorkerId"/> could not be found</response>
|
||||
[HttpDelete("{WorkerId}")]
|
||||
[ProducesResponseType(Status200OK)]
|
||||
[ProducesResponseType<string>(Status404NotFound, "text/plain")]
|
||||
public Results<Ok, NotFound<string>> 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();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Starts <see cref="BaseWorker"/> with <paramref name="WorkerId"/>
|
||||
/// </summary>
|
||||
/// <param name="WorkerId"><see cref="BaseWorker"/>.Key</param>
|
||||
/// <response code="200"></response>
|
||||
/// <response code="404"><see cref="BaseWorker"/> with <paramref name="WorkerId"/> could not be found</response>
|
||||
/// <response code="412"><see cref="BaseWorker"/> was already running</response>
|
||||
[HttpPost("{WorkerId}/Start")]
|
||||
[ProducesResponseType(Status202Accepted)]
|
||||
[ProducesResponseType<string>(Status404NotFound, "text/plain")]
|
||||
[ProducesResponseType(Status412PreconditionFailed)]
|
||||
public Results<Ok, NotFound<string>, 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();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Stops <see cref="BaseWorker"/> with <paramref name="WorkerId"/>
|
||||
/// </summary>
|
||||
|
@@ -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<BaseWorker> workers)
|
||||
{
|
||||
@@ -116,6 +126,8 @@ public static class Tranga
|
||||
AddWorker(baseWorker);
|
||||
}
|
||||
|
||||
private static readonly HashSet<BaseWorker> KnownWorkers = new();
|
||||
public static BaseWorker[] GetKnownWorkers() => KnownWorkers.ToArray();
|
||||
private static readonly ConcurrentDictionary<BaseWorker, Task<BaseWorker[]>> RunningWorkers = new();
|
||||
public static BaseWorker[] GetRunningWorkers() => RunningWorkers.Keys.ToArray();
|
||||
|
||||
|
File diff suppressed because it is too large
Load Diff
@@ -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
|
||||
|
Reference in New Issue
Block a user