mirror of
https://github.com/C9Glax/tranga.git
synced 2025-10-15 01:40:44 +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="200"></response>
|
||||||
/// <response code="404"><see cref="FileLibrary"/> with <paramref name="FileLibraryId"/> not found.</response>
|
/// <response code="404"><see cref="FileLibrary"/> with <paramref name="FileLibraryId"/> not found.</response>
|
||||||
/// <response code="500">Error during Database Operation</response>
|
/// <response code="500">Error during Database Operation</response>
|
||||||
[HttpPatch("{FileLibraryId}/ChangeBasePath")]
|
[HttpPatch("{FileLibraryId}")]
|
||||||
[ProducesResponseType(Status200OK)]
|
[ProducesResponseType(Status200OK)]
|
||||||
[ProducesResponseType<string>(Status404NotFound, "text/plain")]
|
[ProducesResponseType<string>(Status404NotFound, "text/plain")]
|
||||||
[ProducesResponseType<string>(Status500InternalServerError, "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)
|
if(await context.FileLibraries.FirstOrDefaultAsync(l => l.Key == FileLibraryId, HttpContext.RequestAborted) is not { } library)
|
||||||
return TypedResults.NotFound(nameof(FileLibraryId));
|
return TypedResults.NotFound(nameof(FileLibraryId));
|
||||||
|
|
||||||
//TODO Path check
|
if (requestData.Path is { } path)
|
||||||
library.BasePath = newBasePath;
|
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)
|
if(await context.Sync(HttpContext.RequestAborted, GetType(), System.Reflection.MethodBase.GetCurrentMethod()?.Name) is { success: false } result)
|
||||||
return TypedResults.InternalServerError(result.exceptionMessage);
|
return TypedResults.InternalServerError(result.exceptionMessage);
|
||||||
return TypedResults.Ok();
|
return TypedResults.Ok();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
public record PatchFileLibraryRecord(string? Path, string? Name)
|
||||||
/// 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)
|
|
||||||
{
|
{
|
||||||
if(await context.FileLibraries.FirstOrDefaultAsync(l => l.Key == FileLibraryId, HttpContext.RequestAborted) is not { } library)
|
/// <summary>
|
||||||
return TypedResults.NotFound(nameof(FileLibraryId));
|
/// Directory Path
|
||||||
|
/// </summary>
|
||||||
//TODO Name check
|
public required string? Path { get; init; } = Path;
|
||||||
library.LibraryName = newName;
|
/// <summary>
|
||||||
|
/// Library Name
|
||||||
if(await context.Sync(HttpContext.RequestAborted, GetType(), System.Reflection.MethodBase.GetCurrentMethod()?.Name) is { success: false } result)
|
/// </summary>
|
||||||
return TypedResults.InternalServerError(result.exceptionMessage);
|
public required string? Name { get; init; } = Name;
|
||||||
return TypedResults.Ok();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@@ -44,29 +44,15 @@ public class MangaConnectorController(MangaContext context) : Controller
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <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>
|
/// </summary>
|
||||||
/// <response code="200"></response>
|
/// <response code="200"></response>
|
||||||
[HttpGet("Enabled")]
|
[HttpGet("{Enabled}")]
|
||||||
[ProducesResponseType<List<MangaConnector>>(Status200OK, "application/json")]
|
[ProducesResponseType<List<MangaConnector>>(Status200OK, "application/json")]
|
||||||
public Ok<List<MangaConnector>> GetEnabledConnectors()
|
public Ok<List<MangaConnector>> GetEnabledConnectors(bool Enabled)
|
||||||
{
|
{
|
||||||
return TypedResults.Ok(Tranga.MangaConnectors
|
return TypedResults.Ok(Tranga.MangaConnectors
|
||||||
.Where(c => c.Enabled)
|
.Where(c => c.Enabled == 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)
|
|
||||||
.Select(c => new MangaConnector(c.Name, c.Enabled, c.IconUrl, c.SupportedLanguages))
|
.Select(c => new MangaConnector(c.Name, c.Enabled, c.IconUrl, c.SupportedLanguages))
|
||||||
.ToList());
|
.ToList());
|
||||||
}
|
}
|
||||||
|
@@ -6,12 +6,11 @@ using Asp.Versioning;
|
|||||||
using Microsoft.AspNetCore.Http.HttpResults;
|
using Microsoft.AspNetCore.Http.HttpResults;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using Microsoft.EntityFrameworkCore.ChangeTracking;
|
|
||||||
using Microsoft.Net.Http.Headers;
|
using Microsoft.Net.Http.Headers;
|
||||||
|
using Soenneker.Utils.String.NeedlemanWunsch;
|
||||||
using static Microsoft.AspNetCore.Http.StatusCodes;
|
using static Microsoft.AspNetCore.Http.StatusCodes;
|
||||||
using AltTitle = API.Controllers.DTOs.AltTitle;
|
using AltTitle = API.Controllers.DTOs.AltTitle;
|
||||||
using Author = API.Controllers.DTOs.Author;
|
using Author = API.Controllers.DTOs.Author;
|
||||||
using Chapter = API.Controllers.DTOs.Chapter;
|
|
||||||
using Link = API.Controllers.DTOs.Link;
|
using Link = API.Controllers.DTOs.Link;
|
||||||
using Manga = API.Controllers.DTOs.Manga;
|
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);
|
return new MinimalManga(m.Key, m.Name, m.Description, m.ReleaseStatus, ids);
|
||||||
}).ToList());
|
}).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>
|
/// <summary>
|
||||||
/// Return <see cref="Schema.MangaContext.Manga"/> with <paramref name="MangaId"/>
|
/// 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>
|
/// <param name="MangaIdInto"><see cref="Manga"/>.Key of <see cref="Manga"/> merging data into</param>
|
||||||
/// <response code="200"></response>
|
/// <response code="200"></response>
|
||||||
/// <response code="404"><see cref="Manga"/> with <paramref name="MangaIdFrom"/> or <paramref name="MangaIdInto"/> not found</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(Status200OK)]
|
||||||
[ProducesResponseType<string>(Status404NotFound, "text/plain")]
|
[ProducesResponseType<string>(Status404NotFound, "text/plain")]
|
||||||
public async Task<Results<Ok, NotFound<string>>> MergeIntoManga (string MangaIdFrom, string MangaIdInto)
|
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 }
|
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>
|
/// <summary>
|
||||||
/// Move <see cref="Manga"/> to different <see cref="DTOs.FileLibrary"/>
|
/// Move <see cref="Manga"/> to different <see cref="DTOs.FileLibrary"/>
|
||||||
/// </summary>
|
/// </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="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="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>
|
/// <response code="500">Error during Database Operation</response>
|
||||||
[HttpPost("{MangaId}/SetAsDownloadFrom/{MangaConnectorName}/{IsRequested}")]
|
[HttpPatch("{MangaId}/DownloadFrom/{MangaConnectorName}/{IsRequested}")]
|
||||||
[ProducesResponseType(Status200OK)]
|
[ProducesResponseType(Status200OK)]
|
||||||
[ProducesResponseType<string>(Status404NotFound, "text/plain")]
|
[ProducesResponseType<string>(Status404NotFound, "text/plain")]
|
||||||
[ProducesResponseType<string>(Status412PreconditionFailed, "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="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="404"><see cref="API.MangaConnectors.MangaConnector"/> with Name not found</response>
|
||||||
/// <response code="412"><see cref="API.MangaConnectors.MangaConnector"/> with Name is disabled</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<List<MinimalManga>>(Status200OK, "application/json")]
|
||||||
[ProducesResponseType<string>(Status404NotFound, "text/plain")]
|
[ProducesResponseType<string>(Status404NotFound, "text/plain")]
|
||||||
[ProducesResponseType(Status406NotAcceptable)]
|
[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);
|
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());
|
}).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)]
|
[ApiVersion(2)]
|
||||||
[ApiController]
|
[ApiController]
|
||||||
[Route("v{v:apiVersion}/[controller]")]
|
[Route("v{v:apiVersion}/")]
|
||||||
public class QueryController(MangaContext context) : Controller
|
public class QueryController(MangaContext context) : Controller
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -34,91 +34,4 @@ public class QueryController(MangaContext context) : Controller
|
|||||||
|
|
||||||
return TypedResults.Ok(new Author(author.Key, author.AuthorName));
|
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="200"><see cref="MinimalManga"/> exert of <see cref="Schema.MangaContext.Manga"/>.</response>
|
||||||
/// <response code="404"><see cref="Manga"/> not found</response>
|
/// <response code="404"><see cref="Manga"/> not found</response>
|
||||||
/// <response code="500">Error during Database Operation</response>
|
/// <response code="500">Error during Database Operation</response>
|
||||||
[HttpPost("Url")]
|
[HttpGet]
|
||||||
[ProducesResponseType<MinimalManga>(Status200OK, "application/json")]
|
[ProducesResponseType<MinimalManga>(Status200OK, "application/json")]
|
||||||
[ProducesResponseType<string>(Status404NotFound, "text/plain")]
|
[ProducesResponseType<string>(Status404NotFound, "text/plain")]
|
||||||
[ProducesResponseType<string>(Status500InternalServerError, "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)
|
if(Tranga.MangaConnectors.FirstOrDefault(c => c.Name.Equals("Global", StringComparison.InvariantCultureIgnoreCase)) is not { } connector)
|
||||||
return TypedResults.InternalServerError("Could not find Global Connector.");
|
return TypedResults.InternalServerError("Could not find Global Connector.");
|
||||||
|
@@ -60,17 +60,6 @@ public class SettingsController() : Controller
|
|||||||
return TypedResults.Ok();
|
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>
|
/// <summary>
|
||||||
/// Returns Level of Image-Compression for Images
|
/// Returns Level of Image-Compression for Images
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -179,7 +168,7 @@ public class SettingsController() : Controller
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="flareSolverrUrl">URL of FlareSolverr-Instance</param>
|
/// <param name="flareSolverrUrl">URL of FlareSolverr-Instance</param>
|
||||||
/// <response code="200"></response>
|
/// <response code="200"></response>
|
||||||
[HttpPost("FlareSolverr/Url")]
|
[HttpPatch("FlareSolverr/Url")]
|
||||||
[ProducesResponseType(Status200OK)]
|
[ProducesResponseType(Status200OK)]
|
||||||
public Ok SetFlareSolverrUrl([FromBody]string flareSolverrUrl)
|
public Ok SetFlareSolverrUrl([FromBody]string flareSolverrUrl)
|
||||||
{
|
{
|
||||||
@@ -212,7 +201,7 @@ public class SettingsController() : Controller
|
|||||||
const string knownProtectedUrl = "https://prowlarr.servarr.com/v1/ping";
|
const string knownProtectedUrl = "https://prowlarr.servarr.com/v1/ping";
|
||||||
FlareSolverrDownloadClient client = new(new ());
|
FlareSolverrDownloadClient client = new(new ());
|
||||||
HttpResponseMessage result = await client.MakeRequest(knownProtectedUrl, RequestType.Default);
|
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>
|
/// <summary>
|
||||||
|
@@ -21,21 +21,10 @@ public class WorkerController : Controller
|
|||||||
[ProducesResponseType<List<Worker>>(Status200OK, "application/json")]
|
[ProducesResponseType<List<Worker>>(Status200OK, "application/json")]
|
||||||
public Ok<List<Worker>> GetWorkers()
|
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));
|
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());
|
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>
|
/// <summary>
|
||||||
/// Get all <see cref="BaseWorker"/> in requested <see cref="WorkerExecutionState"/>
|
/// Get all <see cref="BaseWorker"/> in requested <see cref="WorkerExecutionState"/>
|
||||||
@@ -46,7 +35,7 @@ public class WorkerController : Controller
|
|||||||
[ProducesResponseType<List<Worker>>(Status200OK, "application/json")]
|
[ProducesResponseType<List<Worker>>(Status200OK, "application/json")]
|
||||||
public Ok<List<Worker>> GetWorkersInState(WorkerExecutionState State)
|
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));
|
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());
|
return TypedResults.Ok(result.ToList());
|
||||||
}
|
}
|
||||||
@@ -62,7 +51,7 @@ public class WorkerController : Controller
|
|||||||
[ProducesResponseType<string>(Status404NotFound, "text/plain")]
|
[ProducesResponseType<string>(Status404NotFound, "text/plain")]
|
||||||
public Results<Ok<Worker>, NotFound<string>> GetWorker(string WorkerId)
|
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));
|
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);
|
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);
|
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>
|
/// <summary>
|
||||||
/// Stops <see cref="BaseWorker"/> with <paramref name="WorkerId"/>
|
/// Stops <see cref="BaseWorker"/> with <paramref name="WorkerId"/>
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@@ -77,7 +77,11 @@ public static class Tranga
|
|||||||
public static void AddWorker(BaseWorker worker)
|
public static void AddWorker(BaseWorker worker)
|
||||||
{
|
{
|
||||||
Log.Debug($"Adding Worker {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)
|
if(worker is IPeriodic periodic)
|
||||||
AddPeriodicWorker(worker, periodic);
|
AddPeriodicWorker(worker, periodic);
|
||||||
}
|
}
|
||||||
@@ -109,6 +113,12 @@ public static class Tranga
|
|||||||
PeriodicWorkers.AddOrUpdate((worker as IPeriodic)!, periodicTask, (_, _) => periodicTask);
|
PeriodicWorkers.AddOrUpdate((worker as IPeriodic)!, periodicTask, (_, _) => periodicTask);
|
||||||
periodicTask.Start();
|
periodicTask.Start();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
private static Action RemoveFromKnownWorkers(BaseWorker worker) => () =>
|
||||||
|
{
|
||||||
|
if (KnownWorkers.Contains(worker))
|
||||||
|
KnownWorkers.Remove(worker);
|
||||||
|
};
|
||||||
|
|
||||||
public static void AddWorkers(IEnumerable<BaseWorker> workers)
|
public static void AddWorkers(IEnumerable<BaseWorker> workers)
|
||||||
{
|
{
|
||||||
@@ -116,6 +126,8 @@ public static class Tranga
|
|||||||
AddWorker(baseWorker);
|
AddWorker(baseWorker);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static readonly HashSet<BaseWorker> KnownWorkers = new();
|
||||||
|
public static BaseWorker[] GetKnownWorkers() => KnownWorkers.ToArray();
|
||||||
private static readonly ConcurrentDictionary<BaseWorker, Task<BaseWorker[]>> RunningWorkers = new();
|
private static readonly ConcurrentDictionary<BaseWorker, Task<BaseWorker[]>> RunningWorkers = new();
|
||||||
public static BaseWorker[] GetRunningWorkers() => RunningWorkers.Keys.ToArray();
|
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
|
condition: service_healthy
|
||||||
environment:
|
environment:
|
||||||
- POSTGRES_HOST=tranga-pg
|
- POSTGRES_HOST=tranga-pg
|
||||||
|
- CHECK_CHAPTERS_BEFORE_START=false
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
logging:
|
logging:
|
||||||
driver: json-file
|
driver: json-file
|
||||||
|
Reference in New Issue
Block a user