mirror of
https://github.com/C9Glax/tranga.git
synced 2025-09-10 03:48:19 +02:00
Use DTOs to return API requests instead of Database Schema types.
Make use of IHttpStatusCodeResults
This commit is contained in:
24
API/Controllers/DTOs/AltTitle.cs
Normal file
24
API/Controllers/DTOs/AltTitle.cs
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
using System.ComponentModel;
|
||||||
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
|
||||||
|
namespace API.Controllers.DTOs;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// <see cref="API.Schema.MangaContext.AltTitle"/> DTO
|
||||||
|
/// </summary>
|
||||||
|
public sealed record AltTitle(string Language, string Title)
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Language of the Title
|
||||||
|
/// </summary>
|
||||||
|
[Required]
|
||||||
|
[Description("Language of the Title")]
|
||||||
|
public string Language { get; init; } = Language;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Title
|
||||||
|
/// </summary>
|
||||||
|
[Required]
|
||||||
|
[Description("Title")]
|
||||||
|
public string Title { get; init; } = Title;
|
||||||
|
}
|
17
API/Controllers/DTOs/Author.cs
Normal file
17
API/Controllers/DTOs/Author.cs
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
using System.ComponentModel;
|
||||||
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
|
||||||
|
namespace API.Controllers.DTOs;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The <see cref="API.Schema.MangaContext.Author"/> DTO
|
||||||
|
/// </summary>
|
||||||
|
public record Author(string Key, string Name) : Identifiable(Key)
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Name of the Author.
|
||||||
|
/// </summary>
|
||||||
|
[Required]
|
||||||
|
[Description("Name of the Author.")]
|
||||||
|
public string Name { get; init; } = Name;
|
||||||
|
}
|
52
API/Controllers/DTOs/Chapter.cs
Normal file
52
API/Controllers/DTOs/Chapter.cs
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
using System.ComponentModel;
|
||||||
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
|
||||||
|
namespace API.Controllers.DTOs;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// <see cref="API.Schema.MangaContext.Chapter"/> DTO
|
||||||
|
/// </summary>
|
||||||
|
public record Chapter(string Key, string MangaId, int? Volume, string ChapterNumber, string? Title, IEnumerable<MangaConnectorId> MangaConnectorIds, bool Downloaded) : Identifiable(Key)
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Identifier of the Manga this Chapter belongs to
|
||||||
|
/// </summary>
|
||||||
|
[Required]
|
||||||
|
[Description("Identifier of the Manga this Chapter belongs to")]
|
||||||
|
public string MangaId { get; init; } = MangaId;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Volume number
|
||||||
|
/// </summary>
|
||||||
|
[Required]
|
||||||
|
[Description("Volume number")]
|
||||||
|
public int? Volume { get; init; } = Volume;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Chapter number
|
||||||
|
/// </summary>
|
||||||
|
[Required]
|
||||||
|
[Description("Chapter number")]
|
||||||
|
public string ChapterNumber { get; init; } = ChapterNumber;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Title of the Chapter
|
||||||
|
/// </summary>
|
||||||
|
[Required]
|
||||||
|
[Description("Title of the Chapter")]
|
||||||
|
public string? Title { get; init; } = Title;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Whether Chapter is Downloaded (on disk)
|
||||||
|
/// </summary>
|
||||||
|
[Required]
|
||||||
|
[Description("Whether Chapter is Downloaded (on disk)")]
|
||||||
|
public bool Downloaded { get; init; } = Downloaded;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Ids of the Manga on MangaConnectors
|
||||||
|
/// </summary>
|
||||||
|
[Required]
|
||||||
|
[Description("Ids of the Manga on MangaConnectors")]
|
||||||
|
public IEnumerable<MangaConnectorId> MangaConnectorIds { get; init; } = MangaConnectorIds;
|
||||||
|
}
|
18
API/Controllers/DTOs/Identifiable.cs
Normal file
18
API/Controllers/DTOs/Identifiable.cs
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
using System.ComponentModel;
|
||||||
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
|
||||||
|
namespace API.Controllers.DTOs;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// <see cref="API.Schema.Identifiable"/>
|
||||||
|
/// </summary>
|
||||||
|
public record Identifiable(string Key)
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Unique Identifier of the DTO
|
||||||
|
/// </summary>
|
||||||
|
[Required]
|
||||||
|
[Description("Unique Identifier of the DTO")]
|
||||||
|
[StringLength(TokenGen.MaximumLength, MinimumLength = TokenGen.MinimumLength)]
|
||||||
|
public string Key { get; init; } = Key;
|
||||||
|
}
|
25
API/Controllers/DTOs/Link.cs
Normal file
25
API/Controllers/DTOs/Link.cs
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
using System.ComponentModel;
|
||||||
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
|
||||||
|
namespace API.Controllers.DTOs;
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// <see cref="API.Schema.MangaContext.Link"/> DTO
|
||||||
|
/// </summary>
|
||||||
|
public sealed record Link(string Key, string Provider, string Url) : Identifiable(Key)
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Name of the Provider
|
||||||
|
/// </summary>
|
||||||
|
[Required]
|
||||||
|
[Description("Name of the Provider")]
|
||||||
|
public string Provider { get; init; } = Provider;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Url
|
||||||
|
/// </summary>
|
||||||
|
[Required]
|
||||||
|
[Description("Url")]
|
||||||
|
public string Url { get; init; } = Url;
|
||||||
|
}
|
66
API/Controllers/DTOs/Manga.cs
Normal file
66
API/Controllers/DTOs/Manga.cs
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
using System.ComponentModel;
|
||||||
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
using API.Schema.MangaContext;
|
||||||
|
|
||||||
|
namespace API.Controllers.DTOs;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// <see cref="Schema.MangaContext.Manga"/> DTO
|
||||||
|
/// </summary>
|
||||||
|
public record Manga(string Key, string Name, string Description, MangaReleaseStatus ReleaseStatus, IEnumerable<MangaConnectorId> MangaConnectorIds, float IgnoreChaptersBefore, uint? Year, string? OriginalLanguage, IEnumerable<string> ChapterIds, IEnumerable<Author> Authors, IEnumerable<string> Tags, IEnumerable<Link> Links, IEnumerable<AltTitle> AltTitles)
|
||||||
|
: MinimalManga(Key, Name, Description, ReleaseStatus, MangaConnectorIds)
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Chapter cutoff for Downloads (Chapters before this will not be downloaded)
|
||||||
|
/// </summary>
|
||||||
|
[Required]
|
||||||
|
[Description("Chapter cutoff for Downloads (Chapters before this will not be downloaded)")]
|
||||||
|
public float IgnoreChaptersBefore { get; init; } = IgnoreChaptersBefore;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Release Year
|
||||||
|
/// </summary>
|
||||||
|
[Description("Release Year")]
|
||||||
|
public uint? Year { get; init; } = Year;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Release Language
|
||||||
|
/// </summary>
|
||||||
|
[Description("Release Language")]
|
||||||
|
public string? OriginalLanguage { get; init; } = OriginalLanguage;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Keys of ChapterDTOs
|
||||||
|
/// </summary>
|
||||||
|
[Required]
|
||||||
|
[Description("Keys of ChapterDTOs")]
|
||||||
|
public IEnumerable<string> ChapterIds { get; init; } = ChapterIds;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Author-names
|
||||||
|
/// </summary>
|
||||||
|
[Required]
|
||||||
|
[Description("Author-names")]
|
||||||
|
public IEnumerable<Author> Authors { get; init; } = Authors;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Manga Tags
|
||||||
|
/// </summary>
|
||||||
|
[Required]
|
||||||
|
[Description("Manga Tags")]
|
||||||
|
public IEnumerable<string> Tags { get; init; } = Tags;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Links for more Metadata
|
||||||
|
/// </summary>
|
||||||
|
[Required]
|
||||||
|
[Description("Links for more Metadata")]
|
||||||
|
public IEnumerable<Link> Links { get; init; } = Links;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Alt Titles of Manga
|
||||||
|
/// </summary>
|
||||||
|
[Required]
|
||||||
|
[Description("Alt Titles of Manga")]
|
||||||
|
public IEnumerable<AltTitle> AltTitles { get; init; } = AltTitles;
|
||||||
|
}
|
38
API/Controllers/DTOs/MangaConnectorId.cs
Normal file
38
API/Controllers/DTOs/MangaConnectorId.cs
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
using System.ComponentModel;
|
||||||
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
using API.Schema.MangaContext;
|
||||||
|
|
||||||
|
namespace API.Controllers.DTOs;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// <see cref="MangaConnectorId{T}"/> DTO
|
||||||
|
/// </summary>
|
||||||
|
public sealed record MangaConnectorId(string Key, string MangaConnectorName, string ForeignKey, string? WebsiteUrl, bool UseForDownload) : Identifiable(Key)
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Name of the Connector
|
||||||
|
/// </summary>
|
||||||
|
[Required]
|
||||||
|
[Description("Name of the Connector")]
|
||||||
|
public string MangaConnectorName { get; init; } = MangaConnectorName;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Key of the referenced DTO
|
||||||
|
/// </summary>
|
||||||
|
[Required]
|
||||||
|
[Description("Key of the referenced DTO")]
|
||||||
|
public string ForeignKey { get; init; } = ForeignKey;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Website Link for reference, if any
|
||||||
|
/// </summary>
|
||||||
|
[Description("Website Link for reference, if any")]
|
||||||
|
public string? WebsiteUrl { get; init; } = WebsiteUrl;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Whether this Link is used for downloads
|
||||||
|
/// </summary>
|
||||||
|
[Required]
|
||||||
|
[Description("Whether this Link is used for downloads")]
|
||||||
|
public bool UseForDownload { get; init; } = UseForDownload;
|
||||||
|
}
|
@@ -1,21 +1,39 @@
|
|||||||
|
using System.ComponentModel;
|
||||||
using System.ComponentModel.DataAnnotations;
|
using System.ComponentModel.DataAnnotations;
|
||||||
using API.Schema.MangaContext;
|
using API.Schema.MangaContext;
|
||||||
using Newtonsoft.Json;
|
|
||||||
|
|
||||||
namespace API.Controllers.DTOs;
|
namespace API.Controllers.DTOs;
|
||||||
|
|
||||||
public sealed record MinimalManga(string Key, string Name, string Description, MangaReleaseStatus ReleaseStatus, IEnumerable<MangaConnectorId<Manga>>? MangaConnectorIds = null)
|
/// <summary>
|
||||||
|
/// Shortened Version of <see cref="Manga"/>
|
||||||
|
/// </summary>
|
||||||
|
public record MinimalManga(string Key, string Name, string Description, MangaReleaseStatus ReleaseStatus, IEnumerable<MangaConnectorId> MangaConnectorIds) : Identifiable(Key)
|
||||||
{
|
{
|
||||||
[Required] [StringLength(TokenGen.MaximumLength, MinimumLength = TokenGen.MinimumLength)]
|
/// <summary>
|
||||||
public string Key { get; init; } = Key;
|
/// Name of the Manga
|
||||||
|
/// </summary>
|
||||||
[Required]
|
[Required]
|
||||||
[JsonRequired]
|
[Description("Name of the Manga")]
|
||||||
public string Name { get; init; } = Name;
|
public string Name { get; init; } = Name;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Description of the Manga
|
||||||
|
/// </summary>
|
||||||
[Required]
|
[Required]
|
||||||
[JsonRequired]
|
[Description("Description of the Manga")]
|
||||||
public string Description { get; init; } = Description;
|
public string Description { get; init; } = Description;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// ReleaseStatus of the Manga
|
||||||
|
/// </summary>
|
||||||
[Required]
|
[Required]
|
||||||
[JsonRequired]
|
[Description("ReleaseStatus of the Manga")]
|
||||||
public MangaReleaseStatus ReleaseStatus { get; init; } = ReleaseStatus;
|
public MangaReleaseStatus ReleaseStatus { get; init; } = ReleaseStatus;
|
||||||
public IEnumerable<MangaConnectorId<Manga>>? MangaConnectorIds { get; init; } = MangaConnectorIds;
|
|
||||||
|
/// <summary>
|
||||||
|
/// Ids of the Manga on MangaConnectors
|
||||||
|
/// </summary>
|
||||||
|
[Required]
|
||||||
|
[Description("Ids of the Manga on MangaConnectors")]
|
||||||
|
public IEnumerable<MangaConnectorId> MangaConnectorIds { get; init; } = MangaConnectorIds;
|
||||||
}
|
}
|
33
API/Controllers/DTOs/PeriodicWorker.cs
Normal file
33
API/Controllers/DTOs/PeriodicWorker.cs
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
using System.ComponentModel;
|
||||||
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
using API.Workers;
|
||||||
|
|
||||||
|
namespace API.Controllers.DTOs;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// <see cref="IPeriodic"/> DTO (<seealso cref="Worker"/> <seealso cref="BaseWorker"/>)
|
||||||
|
/// </summary>
|
||||||
|
public sealed record PeriodicWorker(string Key, IEnumerable<string> Dependencies, IEnumerable<string> MissingDependencies, bool DependenciesFulfilled, WorkerExecutionState State, DateTime LastExecution, TimeSpan Interval, DateTime NextExecution)
|
||||||
|
: Worker(Key, Dependencies, MissingDependencies, DependenciesFulfilled, State)
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Timestamp when Worker executed last.
|
||||||
|
/// </summary>
|
||||||
|
[Required]
|
||||||
|
[Description("Timestamp when Worker executed last.")]
|
||||||
|
public DateTime LastExecution { get; init; } = LastExecution;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Interval at which Worker runs.
|
||||||
|
/// </summary>
|
||||||
|
[Required]
|
||||||
|
[Description("Interval at which Worker runs.")]
|
||||||
|
public TimeSpan Interval { get; init; } = Interval;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Timestamp when Worker is scheduled to execute next.
|
||||||
|
/// </summary>
|
||||||
|
[Required]
|
||||||
|
[Description("Timestamp when Worker is scheduled to execute next.")]
|
||||||
|
public DateTime NextExecution { get; init; } = LastExecution;
|
||||||
|
}
|
39
API/Controllers/DTOs/Worker.cs
Normal file
39
API/Controllers/DTOs/Worker.cs
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
using System.ComponentModel;
|
||||||
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
using API.Workers;
|
||||||
|
|
||||||
|
namespace API.Controllers.DTOs;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// <see cref="BaseWorker"/> DTO
|
||||||
|
/// </summary>
|
||||||
|
public record Worker(string Key, IEnumerable<string> Dependencies, IEnumerable<string> MissingDependencies, bool DependenciesFulfilled, WorkerExecutionState State) : Identifiable(Key)
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Workers this worker depends on having ran.
|
||||||
|
/// </summary>
|
||||||
|
[Required]
|
||||||
|
[Description("Workers this worker depends on having ran.")]
|
||||||
|
public IEnumerable<string> Dependencies { get; init; } = Dependencies;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Workers that have not yet ran, that need to run for this Worker to run.
|
||||||
|
/// </summary>
|
||||||
|
[Required]
|
||||||
|
[Description("Workers that have not yet ran, that need to run for this Worker to run.")]
|
||||||
|
public IEnumerable<string> MissingDependencies { get; init; } = MissingDependencies;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Worker can run.
|
||||||
|
/// </summary>
|
||||||
|
[Required]
|
||||||
|
[Description("Worker can run.")]
|
||||||
|
public bool DependenciesFulfilled { get; init; } = DependenciesFulfilled;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Execution state of the Worker.
|
||||||
|
/// </summary>
|
||||||
|
[Required]
|
||||||
|
[Description("Execution state of the Worker.")]
|
||||||
|
public WorkerExecutionState State { get; init; } = State;
|
||||||
|
}
|
@@ -1,5 +1,6 @@
|
|||||||
using API.Schema.MangaContext;
|
using API.Schema.MangaContext;
|
||||||
using Asp.Versioning;
|
using Asp.Versioning;
|
||||||
|
using Microsoft.AspNetCore.Http.HttpResults;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using static Microsoft.AspNetCore.Http.StatusCodes;
|
using static Microsoft.AspNetCore.Http.StatusCodes;
|
||||||
@@ -19,11 +20,12 @@ public class FileLibraryController(MangaContext context) : Controller
|
|||||||
/// <response code="500">Error during Database Operation</response>
|
/// <response code="500">Error during Database Operation</response>
|
||||||
[HttpGet]
|
[HttpGet]
|
||||||
[ProducesResponseType<FileLibrary[]>(Status200OK, "application/json")]
|
[ProducesResponseType<FileLibrary[]>(Status200OK, "application/json")]
|
||||||
public async Task<IActionResult> GetFileLibraries ()
|
[ProducesResponseType(Status500InternalServerError)]
|
||||||
|
public async Task<Results<Ok<List<FileLibrary>>, InternalServerError>> GetFileLibraries ()
|
||||||
{
|
{
|
||||||
if(await context.FileLibraries.ToArrayAsync(HttpContext.RequestAborted) is not { } result)
|
if (await context.FileLibraries.ToListAsync(HttpContext.RequestAborted) is not { } result)
|
||||||
return StatusCode(Status500InternalServerError);
|
return TypedResults.InternalServerError();
|
||||||
return Ok(result);
|
return TypedResults.Ok(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -34,13 +36,13 @@ public class FileLibraryController(MangaContext context) : Controller
|
|||||||
/// <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>
|
||||||
[HttpGet("{FileLibraryId}")]
|
[HttpGet("{FileLibraryId}")]
|
||||||
[ProducesResponseType<FileLibrary>(Status200OK, "application/json")]
|
[ProducesResponseType<FileLibrary>(Status200OK, "application/json")]
|
||||||
[ProducesResponseType(Status404NotFound)]
|
[ProducesResponseType<string>(Status404NotFound, "text/plain")]
|
||||||
public async Task<IActionResult> GetFileLibrary (string FileLibraryId)
|
public async Task<Results<Ok<FileLibrary>, NotFound<string>>> GetFileLibrary (string FileLibraryId)
|
||||||
{
|
{
|
||||||
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 NotFound();
|
return TypedResults.NotFound(nameof(FileLibraryId));
|
||||||
|
|
||||||
return Ok(library);
|
return TypedResults.Ok(library);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -53,19 +55,19 @@ public class FileLibraryController(MangaContext context) : Controller
|
|||||||
/// <response code="500">Error during Database Operation</response>
|
/// <response code="500">Error during Database Operation</response>
|
||||||
[HttpPatch("{FileLibraryId}/ChangeBasePath")]
|
[HttpPatch("{FileLibraryId}/ChangeBasePath")]
|
||||||
[ProducesResponseType(Status200OK)]
|
[ProducesResponseType(Status200OK)]
|
||||||
[ProducesResponseType(Status404NotFound)]
|
[ProducesResponseType<string>(Status404NotFound, "text/plain")]
|
||||||
[ProducesResponseType<string>(Status500InternalServerError, "text/plain")]
|
[ProducesResponseType<string>(Status500InternalServerError, "text/plain")]
|
||||||
public async Task<IActionResult> ChangeLibraryBasePath (string FileLibraryId, [FromBody]string newBasePath)
|
public async Task<Results<Ok, NotFound<string>, InternalServerError<string>>> ChangeLibraryBasePath (string FileLibraryId, [FromBody]string newBasePath)
|
||||||
{
|
{
|
||||||
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 NotFound();
|
return TypedResults.NotFound(nameof(FileLibraryId));
|
||||||
|
|
||||||
//TODO Path check
|
//TODO Path check
|
||||||
library.BasePath = newBasePath;
|
library.BasePath = newBasePath;
|
||||||
|
|
||||||
if(await context.Sync(HttpContext.RequestAborted) is { success: false } result)
|
if(await context.Sync(HttpContext.RequestAborted) is { success: false } result)
|
||||||
return StatusCode(Status500InternalServerError, result.exceptionMessage);
|
return TypedResults.InternalServerError(result.exceptionMessage);
|
||||||
return Ok();
|
return TypedResults.Ok();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -78,39 +80,39 @@ public class FileLibraryController(MangaContext context) : Controller
|
|||||||
/// <response code="500">Error during Database Operation</response>
|
/// <response code="500">Error during Database Operation</response>
|
||||||
[HttpPatch("{FileLibraryId}/ChangeName")]
|
[HttpPatch("{FileLibraryId}/ChangeName")]
|
||||||
[ProducesResponseType(Status200OK)]
|
[ProducesResponseType(Status200OK)]
|
||||||
[ProducesResponseType(Status404NotFound)]
|
[ProducesResponseType<string>(Status404NotFound, "text/plain")]
|
||||||
[ProducesResponseType(Status400BadRequest)]
|
|
||||||
[ProducesResponseType<string>(Status500InternalServerError, "text/plain")]
|
[ProducesResponseType<string>(Status500InternalServerError, "text/plain")]
|
||||||
public async Task<IActionResult> ChangeLibraryName (string FileLibraryId, [FromBody] string newName)
|
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)
|
if(await context.FileLibraries.FirstOrDefaultAsync(l => l.Key == FileLibraryId, HttpContext.RequestAborted) is not { } library)
|
||||||
return NotFound();
|
return TypedResults.NotFound(nameof(FileLibraryId));
|
||||||
|
|
||||||
//TODO Name check
|
//TODO Name check
|
||||||
library.LibraryName = newName;
|
library.LibraryName = newName;
|
||||||
|
|
||||||
if(await context.Sync(HttpContext.RequestAborted) is { success: false } result)
|
if(await context.Sync(HttpContext.RequestAborted) is { success: false } result)
|
||||||
return StatusCode(Status500InternalServerError, result.exceptionMessage);
|
return TypedResults.InternalServerError(result.exceptionMessage);
|
||||||
return Ok();
|
return TypedResults.Ok();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Creates new <see cref="FileLibrary"/>
|
/// Creates new <see cref="FileLibrary"/>
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="library">New <see cref="FileLibrary"/> to add</param>
|
/// <param name="library">New <see cref="FileLibrary"/> to add</param>
|
||||||
/// <response code="200"></response>
|
/// <response code="200">Key of new Library</response>
|
||||||
/// <response code="500">Error during Database Operation</response>
|
/// <response code="500">Error during Database Operation</response>
|
||||||
[HttpPut]
|
[HttpPut]
|
||||||
[ProducesResponseType(Status201Created)]
|
[ProducesResponseType<string>(Status201Created, "text/plain")]
|
||||||
[ProducesResponseType<string>(Status500InternalServerError, "text/plain")]
|
[ProducesResponseType<string>(Status500InternalServerError, "text/plain")]
|
||||||
public async Task<IActionResult> CreateNewLibrary ([FromBody]FileLibrary library)
|
public async Task<Results<Created<string>, InternalServerError<string>>> CreateNewLibrary ([FromBody]FileLibrary library)
|
||||||
{
|
{
|
||||||
//TODO Parameter check
|
//TODO Parameter check
|
||||||
context.FileLibraries.Add(library);
|
context.FileLibraries.Add(library);
|
||||||
|
|
||||||
if(await context.Sync(HttpContext.RequestAborted) is { success: false } result)
|
if(await context.Sync(HttpContext.RequestAborted) is { success: false } result)
|
||||||
return StatusCode(Status500InternalServerError, result.exceptionMessage);
|
return TypedResults.InternalServerError(result.exceptionMessage);
|
||||||
return Created();
|
|
||||||
|
return TypedResults.Created(string.Empty, library.Key);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -118,20 +120,21 @@ public class FileLibraryController(MangaContext context) : Controller
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="FileLibraryId"><see cref="FileLibrary"/>.Key</param>
|
/// <param name="FileLibraryId"><see cref="FileLibrary"/>.Key</param>
|
||||||
/// <response code="200"></response>
|
/// <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>
|
/// <response code="500">Error during Database Operation</response>
|
||||||
[HttpDelete("{FileLibraryId}")]
|
[HttpDelete("{FileLibraryId}")]
|
||||||
[ProducesResponseType(Status200OK)]
|
[ProducesResponseType(Status200OK)]
|
||||||
[ProducesResponseType(Status404NotFound)]
|
[ProducesResponseType<string>(Status404NotFound, "text/plain")]
|
||||||
[ProducesResponseType<string>(Status500InternalServerError, "text/plain")]
|
[ProducesResponseType<string>(Status500InternalServerError, "text/plain")]
|
||||||
public async Task<IActionResult> DeleteLocalLibrary (string FileLibraryId)
|
public async Task<Results<Ok, NotFound<string>, InternalServerError<string>>> DeleteLocalLibrary (string FileLibraryId)
|
||||||
{
|
{
|
||||||
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 NotFound();
|
return TypedResults.NotFound(nameof(FileLibraryId));
|
||||||
|
|
||||||
context.FileLibraries.Remove(library);
|
context.FileLibraries.Remove(library);
|
||||||
|
|
||||||
if(await context.Sync(HttpContext.RequestAborted) is { success: false } result)
|
if(await context.Sync(HttpContext.RequestAborted) is { success: false } result)
|
||||||
return StatusCode(Status500InternalServerError, result.exceptionMessage);
|
return TypedResults.InternalServerError(result.exceptionMessage);
|
||||||
return Ok();
|
return TypedResults.Ok();
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -1,6 +1,7 @@
|
|||||||
using API.Schema.LibraryContext;
|
using API.Schema.LibraryContext;
|
||||||
using API.Schema.LibraryContext.LibraryConnectors;
|
using API.Schema.LibraryContext.LibraryConnectors;
|
||||||
using Asp.Versioning;
|
using Asp.Versioning;
|
||||||
|
using Microsoft.AspNetCore.Http.HttpResults;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using static Microsoft.AspNetCore.Http.StatusCodes;
|
using static Microsoft.AspNetCore.Http.StatusCodes;
|
||||||
@@ -19,13 +20,13 @@ public class LibraryConnectorController(LibraryContext context) : Controller
|
|||||||
/// <response code="200"></response>
|
/// <response code="200"></response>
|
||||||
/// <response code="500">Error during Database Operation</response>
|
/// <response code="500">Error during Database Operation</response>
|
||||||
[HttpGet]
|
[HttpGet]
|
||||||
[ProducesResponseType<LibraryConnector[]>(Status200OK, "application/json")]
|
[ProducesResponseType<List<LibraryConnector>>(Status200OK, "application/json")]
|
||||||
public async Task<IActionResult> GetAllConnectors ()
|
public async Task<Results<Ok<List<LibraryConnector>>, InternalServerError>> GetAllConnectors ()
|
||||||
{
|
{
|
||||||
if (await context.LibraryConnectors.ToArrayAsync(HttpContext.RequestAborted) is not { } connectors)
|
if (await context.LibraryConnectors.ToListAsync(HttpContext.RequestAborted) is not { } connectors)
|
||||||
return StatusCode(Status500InternalServerError);
|
return TypedResults.InternalServerError();
|
||||||
|
|
||||||
return Ok(connectors);
|
return TypedResults.Ok(connectors);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -36,13 +37,13 @@ public class LibraryConnectorController(LibraryContext context) : Controller
|
|||||||
/// <response code="404"><see cref="LibraryConnector"/> with <paramref name="LibraryConnectorId"/> not found.</response>
|
/// <response code="404"><see cref="LibraryConnector"/> with <paramref name="LibraryConnectorId"/> not found.</response>
|
||||||
[HttpGet("{LibraryConnectorId}")]
|
[HttpGet("{LibraryConnectorId}")]
|
||||||
[ProducesResponseType<LibraryConnector>(Status200OK, "application/json")]
|
[ProducesResponseType<LibraryConnector>(Status200OK, "application/json")]
|
||||||
[ProducesResponseType(Status404NotFound)]
|
[ProducesResponseType<string>(Status404NotFound, "text/plain")]
|
||||||
public async Task<IActionResult> GetConnector (string LibraryConnectorId)
|
public async Task<Results<Ok<LibraryConnector>, NotFound<string>>> GetConnector (string LibraryConnectorId)
|
||||||
{
|
{
|
||||||
if (await context.LibraryConnectors.FirstOrDefaultAsync(l => l.Key == LibraryConnectorId) is not { } connector)
|
if (await context.LibraryConnectors.FirstOrDefaultAsync(l => l.Key == LibraryConnectorId) is not { } connector)
|
||||||
return NotFound();
|
return TypedResults.NotFound(nameof(LibraryConnectorId));
|
||||||
|
|
||||||
return Ok(connector);
|
return TypedResults.Ok(connector);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -52,16 +53,15 @@ public class LibraryConnectorController(LibraryContext context) : Controller
|
|||||||
/// <response code="201"></response>
|
/// <response code="201"></response>
|
||||||
/// <response code="500">Error during Database Operation</response>
|
/// <response code="500">Error during Database Operation</response>
|
||||||
[HttpPut]
|
[HttpPut]
|
||||||
[ProducesResponseType(Status201Created)]
|
[ProducesResponseType<string>(Status201Created, "text/plain")]
|
||||||
[ProducesResponseType<string>(Status500InternalServerError, "text/plain")]
|
[ProducesResponseType<string>(Status500InternalServerError, "text/plain")]
|
||||||
public async Task<IActionResult> CreateConnector ([FromBody]LibraryConnector libraryConnector)
|
public async Task<Results<Created<string>, InternalServerError<string>>> CreateConnector ([FromBody]LibraryConnector libraryConnector)
|
||||||
{
|
{
|
||||||
|
|
||||||
context.LibraryConnectors.Add(libraryConnector);
|
context.LibraryConnectors.Add(libraryConnector);
|
||||||
|
|
||||||
if(await context.Sync(HttpContext.RequestAborted) is { success: false } result)
|
if(await context.Sync(HttpContext.RequestAborted) is { success: false } result)
|
||||||
return StatusCode(Status500InternalServerError, result.exceptionMessage);
|
return TypedResults.InternalServerError(result.exceptionMessage);
|
||||||
return Created();
|
return TypedResults.Created(string.Empty, libraryConnector.Key);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -73,17 +73,17 @@ public class LibraryConnectorController(LibraryContext context) : Controller
|
|||||||
/// <response code="500">Error during Database Operation</response>
|
/// <response code="500">Error during Database Operation</response>
|
||||||
[HttpDelete("{LibraryConnectorId}")]
|
[HttpDelete("{LibraryConnectorId}")]
|
||||||
[ProducesResponseType(Status200OK)]
|
[ProducesResponseType(Status200OK)]
|
||||||
[ProducesResponseType(Status404NotFound)]
|
[ProducesResponseType<string>(Status404NotFound, "text/plain")]
|
||||||
[ProducesResponseType<string>(Status500InternalServerError, "text/plain")]
|
[ProducesResponseType<string>(Status500InternalServerError, "text/plain")]
|
||||||
public async Task<IActionResult> DeleteConnector (string LibraryConnectorId)
|
public async Task<Results<Ok, NotFound<string>, InternalServerError<string>>> DeleteConnector (string LibraryConnectorId)
|
||||||
{
|
{
|
||||||
if (await context.LibraryConnectors.FirstOrDefaultAsync(l => l.Key == LibraryConnectorId) is not { } connector)
|
if (await context.LibraryConnectors.FirstOrDefaultAsync(l => l.Key == LibraryConnectorId) is not { } connector)
|
||||||
return NotFound();
|
return TypedResults.NotFound(nameof(LibraryConnectorId));
|
||||||
|
|
||||||
context.LibraryConnectors.Remove(connector);
|
context.LibraryConnectors.Remove(connector);
|
||||||
|
|
||||||
if(await context.Sync(HttpContext.RequestAborted) is { success: false } result)
|
if(await context.Sync(HttpContext.RequestAborted) is { success: false } result)
|
||||||
return StatusCode(Status500InternalServerError, result.exceptionMessage);
|
return TypedResults.InternalServerError(result.exceptionMessage);
|
||||||
return Ok();
|
return TypedResults.Ok();
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -1,6 +1,7 @@
|
|||||||
using API.MangaConnectors;
|
using API.MangaConnectors;
|
||||||
using API.Schema.MangaContext;
|
using API.Schema.MangaContext;
|
||||||
using Asp.Versioning;
|
using Asp.Versioning;
|
||||||
|
using Microsoft.AspNetCore.Http.HttpResults;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using static Microsoft.AspNetCore.Http.StatusCodes;
|
using static Microsoft.AspNetCore.Http.StatusCodes;
|
||||||
@@ -21,18 +22,18 @@ public class MaintenanceController(MangaContext mangaContext) : Controller
|
|||||||
[HttpPost("CleanupNoDownloadManga")]
|
[HttpPost("CleanupNoDownloadManga")]
|
||||||
[ProducesResponseType(Status200OK)]
|
[ProducesResponseType(Status200OK)]
|
||||||
[ProducesResponseType<string>(Status500InternalServerError, "text/plain")]
|
[ProducesResponseType<string>(Status500InternalServerError, "text/plain")]
|
||||||
public async Task<IActionResult> CleanupNoDownloadManga()
|
public async Task<Results<Ok, InternalServerError<string>>> CleanupNoDownloadManga()
|
||||||
{
|
{
|
||||||
if (await mangaContext.Mangas
|
if (await mangaContext.Mangas
|
||||||
.Include(m => m.MangaConnectorIds)
|
.Include(m => m.MangaConnectorIds)
|
||||||
.Where(m => !m.MangaConnectorIds.Any(id => id.UseForDownload))
|
.Where(m => !m.MangaConnectorIds.Any(id => id.UseForDownload))
|
||||||
.ToArrayAsync(HttpContext.RequestAborted) is not { } noDownloads)
|
.ToArrayAsync(HttpContext.RequestAborted) is not { } noDownloads)
|
||||||
return StatusCode(Status500InternalServerError);
|
return TypedResults.InternalServerError("Could not fetch Manga");
|
||||||
|
|
||||||
mangaContext.Mangas.RemoveRange(noDownloads);
|
mangaContext.Mangas.RemoveRange(noDownloads);
|
||||||
|
|
||||||
if(await mangaContext.Sync(HttpContext.RequestAborted) is { success: false } result)
|
if(await mangaContext.Sync(HttpContext.RequestAborted) is { success: false } result)
|
||||||
return StatusCode(Status500InternalServerError, result.exceptionMessage);
|
return TypedResults.InternalServerError(result.exceptionMessage);
|
||||||
return Ok();
|
return TypedResults.Ok();
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -1,6 +1,7 @@
|
|||||||
using API.MangaConnectors;
|
using API.MangaConnectors;
|
||||||
using API.Schema.MangaContext;
|
using API.Schema.MangaContext;
|
||||||
using Asp.Versioning;
|
using Asp.Versioning;
|
||||||
|
using Microsoft.AspNetCore.Http.HttpResults;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using static Microsoft.AspNetCore.Http.StatusCodes;
|
using static Microsoft.AspNetCore.Http.StatusCodes;
|
||||||
// ReSharper disable InconsistentNaming
|
// ReSharper disable InconsistentNaming
|
||||||
@@ -17,10 +18,10 @@ public class MangaConnectorController(MangaContext context) : Controller
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <response code="200">Names of <see cref="MangaConnector"/> (Scanlation-Sites)</response>
|
/// <response code="200">Names of <see cref="MangaConnector"/> (Scanlation-Sites)</response>
|
||||||
[HttpGet]
|
[HttpGet]
|
||||||
[ProducesResponseType<MangaConnector[]>(Status200OK, "application/json")]
|
[ProducesResponseType<List<MangaConnector>>(Status200OK, "application/json")]
|
||||||
public IActionResult GetConnectors()
|
public Ok<List<MangaConnector>> GetConnectors()
|
||||||
{
|
{
|
||||||
return Ok(Tranga.MangaConnectors.ToArray());
|
return TypedResults.Ok(Tranga.MangaConnectors.ToList());
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -31,13 +32,13 @@ public class MangaConnectorController(MangaContext context) : Controller
|
|||||||
/// <response code="404"><see cref="MangaConnector"/> (Scanlation-Sites) with Name not found.</response>
|
/// <response code="404"><see cref="MangaConnector"/> (Scanlation-Sites) with Name not found.</response>
|
||||||
[HttpGet("{MangaConnectorName}")]
|
[HttpGet("{MangaConnectorName}")]
|
||||||
[ProducesResponseType<MangaConnector>(Status200OK, "application/json")]
|
[ProducesResponseType<MangaConnector>(Status200OK, "application/json")]
|
||||||
[ProducesResponseType(Status404NotFound)]
|
[ProducesResponseType<string>(Status404NotFound, "text/plain")]
|
||||||
public IActionResult GetConnector(string MangaConnectorName)
|
public Results<Ok<MangaConnector>, NotFound<string>> GetConnector(string MangaConnectorName)
|
||||||
{
|
{
|
||||||
if(Tranga.MangaConnectors.FirstOrDefault(c => c.Name.Equals(MangaConnectorName, StringComparison.InvariantCultureIgnoreCase)) is not { } connector)
|
if(Tranga.MangaConnectors.FirstOrDefault(c => c.Name.Equals(MangaConnectorName, StringComparison.InvariantCultureIgnoreCase)) is not { } connector)
|
||||||
return NotFound();
|
return TypedResults.NotFound(nameof(MangaConnectorName));
|
||||||
|
|
||||||
return Ok(connector);
|
return TypedResults.Ok(connector);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -45,10 +46,10 @@ public class MangaConnectorController(MangaContext context) : Controller
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <response code="200"></response>
|
/// <response code="200"></response>
|
||||||
[HttpGet("Enabled")]
|
[HttpGet("Enabled")]
|
||||||
[ProducesResponseType<MangaConnector[]>(Status200OK, "application/json")]
|
[ProducesResponseType<List<MangaConnector>>(Status200OK, "application/json")]
|
||||||
public IActionResult GetEnabledConnectors()
|
public Ok<List<MangaConnector>> GetEnabledConnectors()
|
||||||
{
|
{
|
||||||
return Ok(Tranga.MangaConnectors.Where(c => c.Enabled).ToArray());
|
return TypedResults.Ok(Tranga.MangaConnectors.Where(c => c.Enabled).ToList());
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -56,11 +57,11 @@ public class MangaConnectorController(MangaContext context) : Controller
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <response code="200"></response>
|
/// <response code="200"></response>
|
||||||
[HttpGet("Disabled")]
|
[HttpGet("Disabled")]
|
||||||
[ProducesResponseType<MangaConnector[]>(Status200OK, "application/json")]
|
[ProducesResponseType<List<MangaConnector>>(Status200OK, "application/json")]
|
||||||
public IActionResult GetDisabledConnectors()
|
public Ok<List<MangaConnector>> GetDisabledConnectors()
|
||||||
{
|
{
|
||||||
|
|
||||||
return Ok(Tranga.MangaConnectors.Where(c => c.Enabled == false).ToArray());
|
return TypedResults.Ok(Tranga.MangaConnectors.Where(c => c.Enabled == false).ToList());
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -68,22 +69,22 @@ public class MangaConnectorController(MangaContext context) : Controller
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="MangaConnectorName"><see cref="MangaConnector"/>.Name</param>
|
/// <param name="MangaConnectorName"><see cref="MangaConnector"/>.Name</param>
|
||||||
/// <param name="Enabled">Set true to enable, false to disable</param>
|
/// <param name="Enabled">Set true to enable, false to disable</param>
|
||||||
/// <response code="202"></response>
|
/// <response code="200"></response>
|
||||||
/// <response code="404"><see cref="MangaConnector"/> (Scanlation-Sites) with Name not found.</response>
|
/// <response code="404"><see cref="MangaConnector"/> (Scanlation-Sites) with Name not found.</response>
|
||||||
/// <response code="500">Error during Database Operation</response>
|
/// <response code="500">Error during Database Operation</response>
|
||||||
[HttpPatch("{MangaConnectorName}/SetEnabled/{Enabled}")]
|
[HttpPatch("{MangaConnectorName}/SetEnabled/{Enabled}")]
|
||||||
[ProducesResponseType(Status202Accepted)]
|
[ProducesResponseType(Status200OK)]
|
||||||
[ProducesResponseType(Status404NotFound)]
|
[ProducesResponseType<string>(Status404NotFound, "text/plain")]
|
||||||
[ProducesResponseType<string>(Status500InternalServerError, "text/plain")]
|
[ProducesResponseType<string>(Status500InternalServerError, "text/plain")]
|
||||||
public async Task<IActionResult> SetEnabled(string MangaConnectorName, bool Enabled)
|
public async Task<Results<Ok, NotFound<string>, InternalServerError<string>>> SetEnabled(string MangaConnectorName, bool Enabled)
|
||||||
{
|
{
|
||||||
if(Tranga.MangaConnectors.FirstOrDefault(c => c.Name.Equals(MangaConnectorName, StringComparison.InvariantCultureIgnoreCase)) is not { } connector)
|
if(Tranga.MangaConnectors.FirstOrDefault(c => c.Name.Equals(MangaConnectorName, StringComparison.InvariantCultureIgnoreCase)) is not { } connector)
|
||||||
return NotFound();
|
return TypedResults.NotFound(nameof(MangaConnectorName));
|
||||||
|
|
||||||
connector.Enabled = Enabled;
|
connector.Enabled = Enabled;
|
||||||
|
|
||||||
if(await context.Sync(HttpContext.RequestAborted) is { success: false } result)
|
if(await context.Sync(HttpContext.RequestAborted) is { success: false } result)
|
||||||
return StatusCode(Status500InternalServerError, result.exceptionMessage);
|
return TypedResults.InternalServerError(result.exceptionMessage);
|
||||||
return Accepted();
|
return TypedResults.Ok();
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -3,6 +3,7 @@ using API.MangaConnectors;
|
|||||||
using API.Schema.MangaContext;
|
using API.Schema.MangaContext;
|
||||||
using API.Workers;
|
using API.Workers;
|
||||||
using Asp.Versioning;
|
using Asp.Versioning;
|
||||||
|
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.EntityFrameworkCore.ChangeTracking;
|
||||||
@@ -12,6 +13,12 @@ using SixLabors.ImageSharp.Formats.Jpeg;
|
|||||||
using SixLabors.ImageSharp.Processing;
|
using SixLabors.ImageSharp.Processing;
|
||||||
using SixLabors.ImageSharp.Processing.Processors.Transforms;
|
using SixLabors.ImageSharp.Processing.Processors.Transforms;
|
||||||
using static Microsoft.AspNetCore.Http.StatusCodes;
|
using static Microsoft.AspNetCore.Http.StatusCodes;
|
||||||
|
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;
|
||||||
|
|
||||||
// ReSharper disable InconsistentNaming
|
// ReSharper disable InconsistentNaming
|
||||||
|
|
||||||
namespace API.Controllers;
|
namespace API.Controllers;
|
||||||
@@ -23,86 +30,114 @@ public class MangaController(MangaContext context) : Controller
|
|||||||
{
|
{
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Returns all cached <see cref="Manga"/>
|
/// Returns all cached <see cref="DTOs.Manga"/>
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <response code="200"><see cref="MinimalManga"/> exert of <see cref="Manga"/>. Use <see cref="GetManga"/> for more information</response>
|
/// <response code="200"><see cref="MinimalManga"/> exert of <see cref="Schema.MangaContext.Manga"/>. Use <see cref="GetManga"/> for more information</response>
|
||||||
/// <response code="500">Error during Database Operation</response>
|
/// <response code="500">Error during Database Operation</response>
|
||||||
[HttpGet]
|
[HttpGet]
|
||||||
[ProducesResponseType<MinimalManga[]>(Status200OK, "application/json")]
|
[ProducesResponseType<List<MinimalManga>>(Status200OK, "application/json")]
|
||||||
public async Task<IActionResult> GetAllManga ()
|
[ProducesResponseType(Status500InternalServerError)]
|
||||||
|
public async Task<Results<Ok<List<MinimalManga>>, InternalServerError>> GetAllManga ()
|
||||||
{
|
{
|
||||||
if(await context.Mangas.ToArrayAsync(HttpContext.RequestAborted) is not { } result)
|
if (await context.Mangas.Include(m => m.MangaConnectorIds).ToArrayAsync(HttpContext.RequestAborted) is not
|
||||||
return StatusCode(Status500InternalServerError);
|
{ } result)
|
||||||
|
return TypedResults.InternalServerError();
|
||||||
|
|
||||||
return Ok(result.Select(m => new MinimalManga(m.Key, m.Name, m.Description, m.ReleaseStatus)));
|
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));
|
||||||
|
return new MinimalManga(m.Key, m.Name, m.Description, m.ReleaseStatus, ids);
|
||||||
|
}).ToList());
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Returns all cached <see cref="Manga"/>.Keys
|
/// Returns all cached <see cref="Schema.MangaContext.Manga"/>.Keys
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <response code="200"><see cref="Manga"/> Keys/IDs</response>
|
/// <response code="200"><see cref="Schema.MangaContext.Manga"/> Keys/IDs</response>
|
||||||
/// <response code="500">Error during Database Operation</response>
|
/// <response code="500">Error during Database Operation</response>
|
||||||
[HttpGet("Keys")]
|
[HttpGet("Keys")]
|
||||||
[ProducesResponseType<string[]>(Status200OK, "application/json")]
|
[ProducesResponseType<string[]>(Status200OK, "application/json")]
|
||||||
public async Task<IActionResult> GetAllMangaKeys ()
|
[ProducesResponseType(Status500InternalServerError)]
|
||||||
|
public async Task<Results<Ok<string[]>, InternalServerError>> GetAllMangaKeys ()
|
||||||
{
|
{
|
||||||
if(await context.Mangas.Select(m => m.Key).ToArrayAsync(HttpContext.RequestAborted) is not { } result)
|
if (await context.Mangas.Select(m => m.Key).ToArrayAsync(HttpContext.RequestAborted) is not { } result)
|
||||||
return StatusCode(Status500InternalServerError);
|
return TypedResults.InternalServerError();
|
||||||
|
|
||||||
return Ok(result);
|
return TypedResults.Ok(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Returns all <see cref="Manga"/> that are being downloaded from at least one <see cref="MangaConnector"/>
|
/// Returns all <see cref="Schema.MangaContext.Manga"/> that are being downloaded from at least one <see cref="MangaConnector"/>
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <response code="200"><see cref="MinimalManga"/> exert of <see cref="Manga"/>. Use <see cref="GetManga"/> for more information</response>
|
/// <response code="200"><see cref="MinimalManga"/> exert of <see cref="Schema.MangaContext.Manga"/>. Use <see cref="GetManga"/> for more information</response>
|
||||||
/// <response code="500">Error during Database Operation</response>
|
/// <response code="500">Error during Database Operation</response>
|
||||||
[HttpGet("Downloading")]
|
[HttpGet("Downloading")]
|
||||||
[ProducesResponseType<MinimalManga[]>(Status200OK, "application/json")]
|
[ProducesResponseType<MinimalManga[]>(Status200OK, "application/json")]
|
||||||
public async Task<IActionResult> GetMangaDownloading ()
|
[ProducesResponseType(Status500InternalServerError)]
|
||||||
|
public async Task<Results<Ok<List<MinimalManga>>, InternalServerError>> GetMangaDownloading()
|
||||||
{
|
{
|
||||||
if(await context.Mangas
|
if (await context.Mangas
|
||||||
.Include(m => m.MangaConnectorIds)
|
.Include(m => m.MangaConnectorIds)
|
||||||
.Where(m => m.MangaConnectorIds.Any(id => id.UseForDownload))
|
.Where(m => m.MangaConnectorIds.Any(id => id.UseForDownload))
|
||||||
.ToArrayAsync(HttpContext.RequestAborted) is not { } result)
|
.ToArrayAsync(HttpContext.RequestAborted) is not { } result)
|
||||||
return StatusCode(Status500InternalServerError);
|
return TypedResults.InternalServerError();
|
||||||
|
|
||||||
return Ok(result.Select(m => new MinimalManga(m.Key, m.Name, m.Description, m.ReleaseStatus, m.MangaConnectorIds)));
|
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));
|
||||||
|
return new MinimalManga(m.Key, m.Name, m.Description, m.ReleaseStatus, ids);
|
||||||
|
}).ToList());
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Returns all cached <see cref="Manga"/> with <paramref name="MangaIds"/>
|
/// Returns all cached <see cref="Schema.MangaContext.Manga"/> with <paramref name="MangaIds"/>
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="MangaIds">Array of <see cref="Manga"/>.Key</param>
|
/// <param name="MangaIds">Array of <see cref="Schema.MangaContext.Manga"/>.Key</param>
|
||||||
/// <response code="200"></response>
|
/// <response code="200"><see cref="DTOs.Manga"/></response>
|
||||||
/// <response code="500">Error during Database Operation</response>
|
/// <response code="500">Error during Database Operation</response>
|
||||||
[HttpPost("WithIDs")]
|
[HttpPost("WithIDs")]
|
||||||
[ProducesResponseType<Manga[]>(Status200OK, "application/json")]
|
[ProducesResponseType<List<Manga>>(Status200OK, "application/json")]
|
||||||
public async Task<IActionResult> GetMangaWithIds ([FromBody]string[] MangaIds)
|
[ProducesResponseType(Status500InternalServerError)]
|
||||||
|
public async Task<Results<Ok<List<Manga>>, InternalServerError>> GetMangaWithIds ([FromBody]string[] MangaIds)
|
||||||
{
|
{
|
||||||
if(await context.MangaIncludeAll()
|
if (await context.MangaIncludeAll()
|
||||||
.Where(m => MangaIds.Contains(m.Key))
|
.Where(m => MangaIds.Contains(m.Key))
|
||||||
.ToArrayAsync(HttpContext.RequestAborted) is not { } result)
|
.ToArrayAsync(HttpContext.RequestAborted) is not { } result)
|
||||||
return StatusCode(Status500InternalServerError);
|
return TypedResults.InternalServerError();
|
||||||
|
|
||||||
return Ok(result);
|
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);
|
||||||
|
}).ToList());
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Return <see cref="Manga"/> with <paramref name="MangaId"/>
|
/// Return <see cref="Schema.MangaContext.Manga"/> with <paramref name="MangaId"/>
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="MangaId"><see cref="Manga"/>.Key</param>
|
/// <param name="MangaId"><see cref="Schema.MangaContext.Manga"/>.Key</param>
|
||||||
/// <response code="200"></response>
|
/// <response code="200"></response>
|
||||||
/// <response code="404"><see cref="Manga"/> with <paramref name="MangaId"/> not found</response>
|
/// <response code="404"><see cref="Manga"/> with <paramref name="MangaId"/> not found</response>
|
||||||
[HttpGet("{MangaId}")]
|
[HttpGet("{MangaId}")]
|
||||||
[ProducesResponseType<Manga>(Status200OK, "application/json")]
|
[ProducesResponseType<Manga>(Status200OK, "application/json")]
|
||||||
[ProducesResponseType(Status404NotFound)]
|
[ProducesResponseType<string>(Status404NotFound, "text/plain")]
|
||||||
public async Task<IActionResult> GetManga (string MangaId)
|
public async Task<Results<Ok<Manga>, NotFound<string>>> GetManga (string MangaId)
|
||||||
{
|
{
|
||||||
if (await context.MangaIncludeAll().FirstOrDefaultAsync(m => m.Key == MangaId, HttpContext.RequestAborted) is not { } manga)
|
if (await context.MangaIncludeAll().FirstOrDefaultAsync(m => m.Key == MangaId, HttpContext.RequestAborted) is not { } manga)
|
||||||
return NotFound(nameof(MangaId));
|
return TypedResults.NotFound(nameof(MangaId));
|
||||||
|
|
||||||
return Ok(manga);
|
IEnumerable<MangaConnectorId> ids = manga.MangaConnectorIds.Select(id => new MangaConnectorId(id.Key, id.MangaConnectorName, id.ObjId, id.WebsiteUrl, id.UseForDownload));
|
||||||
|
IEnumerable<Author> authors = manga.Authors.Select(a => new Author(a.Key, a.AuthorName));
|
||||||
|
IEnumerable<string> tags = manga.MangaTags.Select(t => t.Tag);
|
||||||
|
IEnumerable<Link> links = manga.Links.Select(l => new Link(l.Key, l.LinkProvider, l.LinkUrl));
|
||||||
|
IEnumerable<AltTitle> altTitles = manga.AltTitles.Select(a => new AltTitle(a.Language, a.Title));
|
||||||
|
Manga result = new (manga.Key, manga.Name, manga.Description, manga.ReleaseStatus, ids, manga.IgnoreChaptersBefore, manga.Year, manga.OriginalLanguage, manga.ChapterIds, authors, tags, links, altTitles);
|
||||||
|
|
||||||
|
return TypedResults.Ok(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -114,18 +149,18 @@ public class MangaController(MangaContext context) : Controller
|
|||||||
/// <response code="500">Error during Database Operation</response>
|
/// <response code="500">Error during Database Operation</response>
|
||||||
[HttpDelete("{MangaId}")]
|
[HttpDelete("{MangaId}")]
|
||||||
[ProducesResponseType(Status200OK)]
|
[ProducesResponseType(Status200OK)]
|
||||||
[ProducesResponseType(Status404NotFound)]
|
[ProducesResponseType<string>(Status404NotFound, "text/plain")]
|
||||||
[ProducesResponseType<string>(Status500InternalServerError, "text/plain")]
|
[ProducesResponseType<string>(Status500InternalServerError, "text/plain")]
|
||||||
public async Task<IActionResult> DeleteManga (string MangaId)
|
public async Task<Results<Ok, NotFound<string>, InternalServerError<string>>> DeleteManga (string MangaId)
|
||||||
{
|
{
|
||||||
if (await context.Mangas.FirstOrDefaultAsync(m => m.Key == MangaId, HttpContext.RequestAborted) is not { } manga)
|
if (await context.Mangas.FirstOrDefaultAsync(m => m.Key == MangaId, HttpContext.RequestAborted) is not { } manga)
|
||||||
return NotFound(nameof(MangaId));
|
return TypedResults.NotFound(nameof(MangaId));
|
||||||
|
|
||||||
context.Mangas.Remove(manga);
|
context.Mangas.Remove(manga);
|
||||||
|
|
||||||
if(await context.Sync(HttpContext.RequestAborted) is { success: false } result)
|
if(await context.Sync(HttpContext.RequestAborted) is { success: false } result)
|
||||||
return StatusCode(Status500InternalServerError, result.exceptionMessage);
|
return TypedResults.InternalServerError(result.exceptionMessage);
|
||||||
return Ok();
|
return TypedResults.Ok();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -137,27 +172,19 @@ public class MangaController(MangaContext context) : Controller
|
|||||||
/// <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}")]
|
[HttpPatch("{MangaIdFrom}/MergeInto/{MangaIdInto}")]
|
||||||
[ProducesResponseType<byte[]>(Status200OK,"image/jpeg")]
|
[ProducesResponseType(Status200OK)]
|
||||||
[ProducesResponseType(Status404NotFound)]
|
[ProducesResponseType<string>(Status404NotFound, "text/plain")]
|
||||||
public async Task<IActionResult> MergeIntoManga (string MangaIdFrom, string MangaIdInto)
|
public async Task<Results<Ok, NotFound<string>>> MergeIntoManga (string MangaIdFrom, string MangaIdInto)
|
||||||
{
|
{
|
||||||
if (await context.Mangas.FirstOrDefaultAsync(m => m.Key == MangaIdFrom, HttpContext.RequestAborted) is not { } from)
|
if (await context.MangaIncludeAll().FirstOrDefaultAsync(m => m.Key == MangaIdFrom, HttpContext.RequestAborted) is not { } from)
|
||||||
return NotFound(nameof(MangaIdFrom));
|
return TypedResults.NotFound(nameof(MangaIdFrom));
|
||||||
if (await context.Mangas.FirstOrDefaultAsync(m => m.Key == MangaIdInto, HttpContext.RequestAborted) is not { } into)
|
if (await context.MangaIncludeAll().FirstOrDefaultAsync(m => m.Key == MangaIdInto, HttpContext.RequestAborted) is not { } into)
|
||||||
return NotFound(nameof(MangaIdInto));
|
return TypedResults.NotFound(nameof(MangaIdInto));
|
||||||
|
|
||||||
foreach (CollectionEntry collectionEntry in context.Entry(from).Collections)
|
|
||||||
await collectionEntry.LoadAsync(HttpContext.RequestAborted);
|
|
||||||
await context.Entry(from).Navigation(nameof(Manga.Library)).LoadAsync(HttpContext.RequestAborted);
|
|
||||||
|
|
||||||
foreach (CollectionEntry collectionEntry in context.Entry(into).Collections)
|
|
||||||
await collectionEntry.LoadAsync(HttpContext.RequestAborted);
|
|
||||||
await context.Entry(into).Navigation(nameof(Manga.Library)).LoadAsync(HttpContext.RequestAborted);
|
|
||||||
|
|
||||||
BaseWorker[] newJobs = into.MergeFrom(from, context);
|
BaseWorker[] newJobs = into.MergeFrom(from, context);
|
||||||
Tranga.AddWorkers(newJobs);
|
Tranga.AddWorkers(newJobs);
|
||||||
|
|
||||||
return Ok();
|
return TypedResults.Ok();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -175,22 +202,22 @@ public class MangaController(MangaContext context) : Controller
|
|||||||
[ProducesResponseType<byte[]>(Status200OK,"image/jpeg")]
|
[ProducesResponseType<byte[]>(Status200OK,"image/jpeg")]
|
||||||
[ProducesResponseType(Status204NoContent)]
|
[ProducesResponseType(Status204NoContent)]
|
||||||
[ProducesResponseType(Status400BadRequest)]
|
[ProducesResponseType(Status400BadRequest)]
|
||||||
[ProducesResponseType(Status404NotFound)]
|
[ProducesResponseType<string>(Status404NotFound, "text/plain")]
|
||||||
[ProducesResponseType<int>(Status503ServiceUnavailable, "text/plain")]
|
[ProducesResponseType(Status503ServiceUnavailable)]
|
||||||
public async Task<IActionResult> GetCover (string MangaId, [FromQuery]int? width, [FromQuery]int? height)
|
public async Task<Results<FileContentHttpResult, NoContent, BadRequest, NotFound<string>, StatusCodeHttpResult>> GetCover (string MangaId, [FromQuery]int? width, [FromQuery]int? height)
|
||||||
{
|
{
|
||||||
if (await context.Mangas.FirstOrDefaultAsync(m => m.Key == MangaId, HttpContext.RequestAborted) is not { } manga)
|
if (await context.Mangas.FirstOrDefaultAsync(m => m.Key == MangaId, HttpContext.RequestAborted) is not { } manga)
|
||||||
return NotFound(nameof(MangaId));
|
return TypedResults.NotFound(nameof(MangaId));
|
||||||
|
|
||||||
if (!System.IO.File.Exists(manga.CoverFileNameInCache))
|
if (!System.IO.File.Exists(manga.CoverFileNameInCache))
|
||||||
{
|
{
|
||||||
if (Tranga.GetRunningWorkers().Any(worker => worker is DownloadCoverFromMangaconnectorWorker w && context.MangaConnectorToManga.Find(w.MangaConnectorIdId)?.ObjId == MangaId))
|
if (Tranga.GetRunningWorkers().Any(worker => worker is DownloadCoverFromMangaconnectorWorker w && context.MangaConnectorToManga.Find(w.MangaConnectorIdId)?.ObjId == MangaId))
|
||||||
{
|
{
|
||||||
Response.Headers.Append("Retry-After", $"{Tranga.Settings.WorkCycleTimeoutMs * 2 / 1000:D}");
|
Response.Headers.Append("Retry-After", $"{Tranga.Settings.WorkCycleTimeoutMs * 2 / 1000:D}");
|
||||||
return StatusCode(Status503ServiceUnavailable, Tranga.Settings.WorkCycleTimeoutMs * 2 / 1000);
|
return TypedResults.StatusCode(Status503ServiceUnavailable);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
return NoContent();
|
return TypedResults.NoContent();
|
||||||
}
|
}
|
||||||
|
|
||||||
Image image = await Image.LoadAsync(manga.CoverFileNameInCache, HttpContext.RequestAborted);
|
Image image = await Image.LoadAsync(manga.CoverFileNameInCache, HttpContext.RequestAborted);
|
||||||
@@ -198,7 +225,7 @@ public class MangaController(MangaContext context) : Controller
|
|||||||
if (width is { } w && height is { } h)
|
if (width is { } w && height is { } h)
|
||||||
{
|
{
|
||||||
if (width < 10 || height < 10 || width > 65535 || height > 65535)
|
if (width < 10 || height < 10 || width > 65535 || height > 65535)
|
||||||
return BadRequest();
|
return TypedResults.BadRequest();
|
||||||
image.Mutate(i => i.ApplyProcessor(new ResizeProcessor(new ResizeOptions()
|
image.Mutate(i => i.ApplyProcessor(new ResizeProcessor(new ResizeOptions()
|
||||||
{
|
{
|
||||||
Mode = ResizeMode.Max,
|
Mode = ResizeMode.Max,
|
||||||
@@ -210,7 +237,7 @@ public class MangaController(MangaContext context) : Controller
|
|||||||
await image.SaveAsync(ms, new JpegEncoder(){Quality = 100}, HttpContext.RequestAborted);
|
await image.SaveAsync(ms, new JpegEncoder(){Quality = 100}, HttpContext.RequestAborted);
|
||||||
DateTime lastModified = new FileInfo(manga.CoverFileNameInCache).LastWriteTime;
|
DateTime lastModified = new FileInfo(manga.CoverFileNameInCache).LastWriteTime;
|
||||||
HttpContext.Response.Headers.CacheControl = "public";
|
HttpContext.Response.Headers.CacheControl = "public";
|
||||||
return File(ms.GetBuffer(), "image/jpeg", new DateTimeOffset(lastModified), EntityTagHeaderValue.Parse($"\"{lastModified.Ticks}\""));
|
return TypedResults.File(ms.GetBuffer(), "image/jpeg", lastModified: new DateTimeOffset(lastModified), entityTag: EntityTagHeaderValue.Parse($"\"{lastModified.Ticks}\""));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -220,17 +247,20 @@ public class MangaController(MangaContext context) : Controller
|
|||||||
/// <response code="200"></response>
|
/// <response code="200"></response>
|
||||||
/// <response code="404"><see cref="Manga"/> with <paramref name="MangaId"/> not found</response>
|
/// <response code="404"><see cref="Manga"/> with <paramref name="MangaId"/> not found</response>
|
||||||
[HttpGet("{MangaId}/Chapters")]
|
[HttpGet("{MangaId}/Chapters")]
|
||||||
[ProducesResponseType<Chapter[]>(Status200OK, "application/json")]
|
[ProducesResponseType<List<Chapter>>(Status200OK, "application/json")]
|
||||||
[ProducesResponseType(Status404NotFound)]
|
[ProducesResponseType(Status404NotFound)]
|
||||||
public async Task<IActionResult> GetChapters (string MangaId)
|
public async Task<Results<Ok<List<Chapter>>, NotFound<string>>> GetChapters(string MangaId)
|
||||||
{
|
{
|
||||||
if (await context.Mangas.FirstOrDefaultAsync(m => m.Key == MangaId, HttpContext.RequestAborted) is not { } manga)
|
if (await context.Mangas.Include(m => m.Chapters).ThenInclude(c => c.MangaConnectorIds).FirstOrDefaultAsync(m => m.Key == MangaId, HttpContext.RequestAborted) is not { } manga)
|
||||||
return NotFound(nameof(MangaId));
|
return TypedResults.NotFound(nameof(MangaId));
|
||||||
|
|
||||||
await context.Entry(manga).Collection(m => m.Chapters).LoadAsync();
|
List<Chapter> chapters = manga.Chapters.Select(c =>
|
||||||
|
{
|
||||||
Chapter[] chapters = manga.Chapters.ToArray();
|
IEnumerable<MangaConnectorId> ids = c.MangaConnectorIds.Select(id =>
|
||||||
return Ok(chapters);
|
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);
|
||||||
|
}).ToList();
|
||||||
|
return TypedResults.Ok(chapters);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -243,19 +273,22 @@ public class MangaController(MangaContext context) : Controller
|
|||||||
[HttpGet("{MangaId}/Chapters/Downloaded")]
|
[HttpGet("{MangaId}/Chapters/Downloaded")]
|
||||||
[ProducesResponseType<Chapter[]>(Status200OK, "application/json")]
|
[ProducesResponseType<Chapter[]>(Status200OK, "application/json")]
|
||||||
[ProducesResponseType(Status204NoContent)]
|
[ProducesResponseType(Status204NoContent)]
|
||||||
[ProducesResponseType(Status404NotFound)]
|
[ProducesResponseType<string>(Status404NotFound, "text/plain")]
|
||||||
public async Task<IActionResult> GetChaptersDownloaded (string MangaId)
|
public async Task<Results<Ok<List<Chapter>>, NoContent, NotFound<string>>> GetChaptersDownloaded(string MangaId)
|
||||||
{
|
{
|
||||||
if (await context.Mangas.FirstOrDefaultAsync(m => m.Key == MangaId, HttpContext.RequestAborted) is not { } manga)
|
if (await context.Mangas.Include(m => m.Chapters).FirstOrDefaultAsync(m => m.Key == MangaId, HttpContext.RequestAborted) is not { } manga)
|
||||||
return NotFound(nameof(MangaId));
|
return TypedResults.NotFound(nameof(MangaId));
|
||||||
|
|
||||||
await context.Entry(manga).Collection(m => m.Chapters).LoadAsync();
|
List<Chapter> chapters = manga.Chapters.Where(c => c.Downloaded).Select(c =>
|
||||||
|
{
|
||||||
List<Chapter> chapters = manga.Chapters.Where(c => c.Downloaded).ToList();
|
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);
|
||||||
|
}).ToList();
|
||||||
if (chapters.Count == 0)
|
if (chapters.Count == 0)
|
||||||
return NoContent();
|
return TypedResults.NoContent();
|
||||||
|
|
||||||
return Ok(chapters);
|
return TypedResults.Ok(chapters);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -266,21 +299,24 @@ public class MangaController(MangaContext context) : Controller
|
|||||||
/// <response code="204">No available chapters</response>
|
/// <response code="204">No available chapters</response>
|
||||||
/// <response code="404"><see cref="Manga"/> with <paramref name="MangaId"/> not found.</response>
|
/// <response code="404"><see cref="Manga"/> with <paramref name="MangaId"/> not found.</response>
|
||||||
[HttpGet("{MangaId}/Chapters/NotDownloaded")]
|
[HttpGet("{MangaId}/Chapters/NotDownloaded")]
|
||||||
[ProducesResponseType<Chapter[]>(Status200OK, "application/json")]
|
[ProducesResponseType<List<Chapter>>(Status200OK, "application/json")]
|
||||||
[ProducesResponseType(Status204NoContent)]
|
[ProducesResponseType(Status204NoContent)]
|
||||||
[ProducesResponseType(Status404NotFound)]
|
[ProducesResponseType<string>(Status404NotFound, "text/plain")]
|
||||||
public async Task<IActionResult> GetChaptersNotDownloaded (string MangaId)
|
public async Task<Results<Ok<List<Chapter>>, NoContent, NotFound<string>>> GetChaptersNotDownloaded(string MangaId)
|
||||||
{
|
{
|
||||||
if (await context.Mangas.FirstOrDefaultAsync(m => m.Key == MangaId, HttpContext.RequestAborted) is not { } manga)
|
if (await context.Mangas.Include(m => m.Chapters).FirstOrDefaultAsync(m => m.Key == MangaId, HttpContext.RequestAborted) is not { } manga)
|
||||||
return NotFound(nameof(MangaId));
|
return TypedResults.NotFound(nameof(MangaId));
|
||||||
|
|
||||||
await context.Entry(manga).Collection(m => m.Chapters).LoadAsync(HttpContext.RequestAborted);
|
List<Chapter> chapters = manga.Chapters.Where(c => c.Downloaded == false).Select(c =>
|
||||||
|
{
|
||||||
List<Chapter> chapters = manga.Chapters.Where(c => c.Downloaded == false).ToList();
|
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);
|
||||||
|
}).ToList();
|
||||||
if (chapters.Count == 0)
|
if (chapters.Count == 0)
|
||||||
return NoContent();
|
return TypedResults.NoContent();
|
||||||
|
|
||||||
return Ok(chapters);
|
return TypedResults.Ok(chapters);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -293,38 +329,41 @@ public class MangaController(MangaContext context) : Controller
|
|||||||
/// <response code="412">Could not retrieve the maximum chapter-number</response>
|
/// <response code="412">Could not retrieve the maximum chapter-number</response>
|
||||||
/// <response code="503">Retry after timeout, updating value</response>
|
/// <response code="503">Retry after timeout, updating value</response>
|
||||||
[HttpGet("{MangaId}/Chapter/LatestAvailable")]
|
[HttpGet("{MangaId}/Chapter/LatestAvailable")]
|
||||||
[ProducesResponseType<Chapter>(Status200OK, "application/json")]
|
[ProducesResponseType<int>(Status200OK, "application/json")]
|
||||||
[ProducesResponseType(Status204NoContent)]
|
[ProducesResponseType(Status204NoContent)]
|
||||||
[ProducesResponseType<string>(Status404NotFound, "text/plain")]
|
[ProducesResponseType<string>(Status404NotFound, "text/plain")]
|
||||||
[ProducesResponseType<string>(Status500InternalServerError, "text/plain")]
|
[ProducesResponseType(Status500InternalServerError)]
|
||||||
[ProducesResponseType<int>(Status503ServiceUnavailable, "text/plain")]
|
[ProducesResponseType(Status503ServiceUnavailable)]
|
||||||
public async Task<IActionResult> GetLatestChapter (string MangaId)
|
public async Task<Results<Ok<Chapter>, NoContent, InternalServerError, NotFound<string>, StatusCodeHttpResult>> GetLatestChapter(string MangaId)
|
||||||
{
|
{
|
||||||
if (await context.Mangas.FirstOrDefaultAsync(m => m.Key == MangaId, HttpContext.RequestAborted) is not { } manga)
|
if (await context.Mangas.Include(m => m.Chapters).FirstOrDefaultAsync(m => m.Key == MangaId, HttpContext.RequestAborted) is not { } manga)
|
||||||
return NotFound(nameof(MangaId));
|
return TypedResults.NotFound(nameof(MangaId));
|
||||||
|
|
||||||
await context.Entry(manga).Collection(m => m.Chapters).LoadAsync(HttpContext.RequestAborted);
|
List<API.Schema.MangaContext.Chapter> chapters = manga.Chapters.ToList();
|
||||||
|
|
||||||
List<Chapter> chapters = manga.Chapters.ToList();
|
|
||||||
if (chapters.Count == 0)
|
if (chapters.Count == 0)
|
||||||
{
|
{
|
||||||
if (Tranga.GetRunningWorkers().Any(worker => worker is RetrieveMangaChaptersFromMangaconnectorWorker w && context.MangaConnectorToManga.Find(w.MangaConnectorIdId)?.ObjId == MangaId && w.State < WorkerExecutionState.Completed))
|
if (Tranga.GetRunningWorkers().Any(worker =>
|
||||||
|
worker is RetrieveMangaChaptersFromMangaconnectorWorker w &&
|
||||||
|
context.MangaConnectorToManga.Find(w.MangaConnectorIdId)?.ObjId == MangaId &&
|
||||||
|
w.State < WorkerExecutionState.Completed))
|
||||||
{
|
{
|
||||||
Response.Headers.Append("Retry-After", $"{Tranga.Settings.WorkCycleTimeoutMs * 2 / 1000:D}");
|
Response.Headers.Append("Retry-After", $"{Tranga.Settings.WorkCycleTimeoutMs * 2 / 1000:D}");
|
||||||
return StatusCode(Status503ServiceUnavailable, Tranga.Settings.WorkCycleTimeoutMs * 2/ 1000);
|
return TypedResults.StatusCode(Status503ServiceUnavailable);
|
||||||
}else
|
}
|
||||||
return Ok(0);
|
else
|
||||||
|
return TypedResults.NoContent();
|
||||||
}
|
}
|
||||||
|
|
||||||
Chapter? max = chapters.Max();
|
API.Schema.MangaContext.Chapter? max = chapters.Max();
|
||||||
if (max is null)
|
if (max is null)
|
||||||
return StatusCode(Status500InternalServerError, "Max chapter could not be found");
|
return TypedResults.InternalServerError();
|
||||||
|
|
||||||
foreach (CollectionEntry collectionEntry in context.Entry(max).Collections)
|
foreach (CollectionEntry collectionEntry in context.Entry(max).Collections)
|
||||||
await collectionEntry.LoadAsync(HttpContext.RequestAborted);
|
await collectionEntry.LoadAsync(HttpContext.RequestAborted);
|
||||||
await context.Entry(max).Navigation(nameof(Chapter.ParentManga)).LoadAsync(HttpContext.RequestAborted);
|
|
||||||
|
|
||||||
return Ok(max);
|
IEnumerable<MangaConnectorId> ids = max.MangaConnectorIds.Select(id =>
|
||||||
|
new MangaConnectorId(id.Key, id.MangaConnectorName, id.ObjId, id.WebsiteUrl, id.UseForDownload));
|
||||||
|
return TypedResults.Ok(new Chapter(max.Key, max.ParentMangaId, max.VolumeNumber, max.ChapterNumber, max.Title,ids, max.Downloaded));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -339,36 +378,34 @@ public class MangaController(MangaContext context) : Controller
|
|||||||
[HttpGet("{MangaId}/Chapter/LatestDownloaded")]
|
[HttpGet("{MangaId}/Chapter/LatestDownloaded")]
|
||||||
[ProducesResponseType<Chapter>(Status200OK, "application/json")]
|
[ProducesResponseType<Chapter>(Status200OK, "application/json")]
|
||||||
[ProducesResponseType(Status204NoContent)]
|
[ProducesResponseType(Status204NoContent)]
|
||||||
[ProducesResponseType(Status404NotFound)]
|
[ProducesResponseType<string>(Status404NotFound, "text/plain")]
|
||||||
[ProducesResponseType<string>(Status412PreconditionFailed, "text/plain")]
|
[ProducesResponseType(Status412PreconditionFailed)]
|
||||||
[ProducesResponseType<int>(Status503ServiceUnavailable, "text/plain")]
|
[ProducesResponseType(Status503ServiceUnavailable)]
|
||||||
public async Task<IActionResult> GetLatestChapterDownloaded (string MangaId)
|
public async Task<Results<Ok<Chapter>, NoContent, NotFound<string>, StatusCodeHttpResult>> GetLatestChapterDownloaded(string MangaId)
|
||||||
{
|
{
|
||||||
if (await context.Mangas.FirstOrDefaultAsync(m => m.Key == MangaId, HttpContext.RequestAborted) is not { } manga)
|
if (await context.Mangas.FirstOrDefaultAsync(m => m.Key == MangaId, HttpContext.RequestAborted) is not { } manga)
|
||||||
return NotFound(nameof(MangaId));
|
return TypedResults.NotFound(nameof(MangaId));
|
||||||
|
|
||||||
await context.Entry(manga).Collection(m => m.Chapters).LoadAsync(HttpContext.RequestAborted);
|
await context.Entry(manga).Collection(m => m.Chapters).LoadAsync(HttpContext.RequestAborted);
|
||||||
|
|
||||||
List<Chapter> chapters = manga.Chapters.ToList();
|
List<API.Schema.MangaContext.Chapter> chapters = manga.Chapters.ToList();
|
||||||
if (chapters.Count == 0)
|
if (chapters.Count == 0)
|
||||||
{
|
{
|
||||||
if (Tranga.GetRunningWorkers().Any(worker => worker is RetrieveMangaChaptersFromMangaconnectorWorker w && context.MangaConnectorToManga.Find(w.MangaConnectorIdId)?.ObjId == MangaId && w.State < WorkerExecutionState.Completed))
|
if (Tranga.GetRunningWorkers().Any(worker => worker is RetrieveMangaChaptersFromMangaconnectorWorker w && context.MangaConnectorToManga.Find(w.MangaConnectorIdId)?.ObjId == MangaId && w.State < WorkerExecutionState.Completed))
|
||||||
{
|
{
|
||||||
Response.Headers.Append("Retry-After", $"{Tranga.Settings.WorkCycleTimeoutMs * 2 / 1000:D}");
|
Response.Headers.Append("Retry-After", $"{Tranga.Settings.WorkCycleTimeoutMs * 2 / 1000:D}");
|
||||||
return StatusCode(Status503ServiceUnavailable, Tranga.Settings.WorkCycleTimeoutMs * 2/ 1000);
|
return TypedResults.StatusCode(Status503ServiceUnavailable);
|
||||||
}else
|
}else
|
||||||
return NoContent();
|
return TypedResults.NoContent();
|
||||||
}
|
}
|
||||||
|
|
||||||
Chapter? max = chapters.Max();
|
API.Schema.MangaContext.Chapter? max = chapters.Max();
|
||||||
if (max is null)
|
if (max is null)
|
||||||
return StatusCode(Status412PreconditionFailed, "Max chapter could not be found");
|
return TypedResults.StatusCode(Status412PreconditionFailed);
|
||||||
|
|
||||||
foreach (CollectionEntry collectionEntry in context.Entry(max).Collections)
|
IEnumerable<MangaConnectorId> ids = max.MangaConnectorIds.Select(id =>
|
||||||
await collectionEntry.LoadAsync(HttpContext.RequestAborted);
|
new MangaConnectorId(id.Key, id.MangaConnectorName, id.ObjId, id.WebsiteUrl, id.UseForDownload));
|
||||||
await context.Entry(max).Navigation(nameof(Chapter.ParentManga)).LoadAsync(HttpContext.RequestAborted);
|
return TypedResults.Ok(new Chapter(max.Key, max.ParentMangaId, max.VolumeNumber, max.ChapterNumber, max.Title,ids, max.Downloaded));
|
||||||
|
|
||||||
return Ok(max);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -380,19 +417,19 @@ public class MangaController(MangaContext context) : Controller
|
|||||||
/// <response code="404"><see cref="Manga"/> with <paramref name="MangaId"/> not found.</response>
|
/// <response code="404"><see cref="Manga"/> with <paramref name="MangaId"/> not found.</response>
|
||||||
/// <response code="500">Error during Database Operation</response>
|
/// <response code="500">Error during Database Operation</response>
|
||||||
[HttpPatch("{MangaId}/IgnoreChaptersBefore")]
|
[HttpPatch("{MangaId}/IgnoreChaptersBefore")]
|
||||||
[ProducesResponseType(Status202Accepted)]
|
[ProducesResponseType(Status200OK)]
|
||||||
[ProducesResponseType(Status404NotFound)]
|
[ProducesResponseType<string>(Status404NotFound, "text/plain")]
|
||||||
[ProducesResponseType<string>(Status500InternalServerError, "text/plain")]
|
[ProducesResponseType<string>(Status500InternalServerError, "text/plain")]
|
||||||
public async Task<IActionResult> IgnoreChaptersBefore (string MangaId, [FromBody]float chapterThreshold)
|
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)
|
if (await context.Mangas.FirstOrDefaultAsync(m => m.Key == MangaId, HttpContext.RequestAborted) is not { } manga)
|
||||||
return NotFound(nameof(MangaId));
|
return TypedResults.NotFound(nameof(MangaId));
|
||||||
|
|
||||||
manga.IgnoreChaptersBefore = chapterThreshold;
|
manga.IgnoreChaptersBefore = chapterThreshold;
|
||||||
if(await context.Sync(HttpContext.RequestAborted) is { success: false } result)
|
if(await context.Sync(HttpContext.RequestAborted) is { success: false } result)
|
||||||
return StatusCode(Status500InternalServerError, result.exceptionMessage);
|
return TypedResults.InternalServerError(result.exceptionMessage);
|
||||||
|
|
||||||
return Accepted();
|
return TypedResults.Ok();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -403,24 +440,20 @@ public class MangaController(MangaContext context) : Controller
|
|||||||
/// <response code="202">Folder is going to be moved</response>
|
/// <response code="202">Folder is going to be moved</response>
|
||||||
/// <response code="404"><paramref name="MangaId"/> or <paramref name="LibraryId"/> not found</response>
|
/// <response code="404"><paramref name="MangaId"/> or <paramref name="LibraryId"/> not found</response>
|
||||||
[HttpPost("{MangaId}/ChangeLibrary/{LibraryId}")]
|
[HttpPost("{MangaId}/ChangeLibrary/{LibraryId}")]
|
||||||
[ProducesResponseType(Status202Accepted)]
|
[ProducesResponseType(Status200OK)]
|
||||||
[ProducesResponseType(Status404NotFound)]
|
[ProducesResponseType<string>(Status404NotFound, "text/plain")]
|
||||||
public async Task<IActionResult> ChangeLibrary (string MangaId, string LibraryId)
|
public async Task<Results<Ok, NotFound<string>>> ChangeLibrary(string MangaId, string LibraryId)
|
||||||
{
|
{
|
||||||
if (await context.Mangas.FirstOrDefaultAsync(m => m.Key == MangaId, HttpContext.RequestAborted) is not { } manga)
|
if (await context.MangaIncludeAll().FirstOrDefaultAsync(m => m.Key == MangaId, HttpContext.RequestAborted) is not { } manga)
|
||||||
return NotFound(nameof(MangaId));
|
return TypedResults.NotFound(nameof(MangaId));
|
||||||
if (await context.FileLibraries.FirstOrDefaultAsync(l => l.Key == LibraryId, HttpContext.RequestAborted) is not { } library)
|
if (await context.FileLibraries.FirstOrDefaultAsync(l => l.Key == LibraryId, HttpContext.RequestAborted) is not { } library)
|
||||||
return NotFound(nameof(LibraryId));
|
return TypedResults.NotFound(nameof(LibraryId));
|
||||||
|
|
||||||
foreach (CollectionEntry collectionEntry in context.Entry(manga).Collections)
|
|
||||||
await collectionEntry.LoadAsync(HttpContext.RequestAborted);
|
|
||||||
await context.Entry(manga).Navigation(nameof(Manga.Library)).LoadAsync(HttpContext.RequestAborted);
|
|
||||||
|
|
||||||
MoveMangaLibraryWorker moveLibrary = new(manga, library);
|
MoveMangaLibraryWorker moveLibrary = new(manga, library);
|
||||||
|
|
||||||
Tranga.AddWorkers([moveLibrary]);
|
Tranga.AddWorkers([moveLibrary]);
|
||||||
|
|
||||||
return Accepted();
|
return TypedResults.Ok();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -440,51 +473,50 @@ public class MangaController(MangaContext context) : Controller
|
|||||||
[ProducesResponseType<string>(Status412PreconditionFailed, "text/plain")]
|
[ProducesResponseType<string>(Status412PreconditionFailed, "text/plain")]
|
||||||
[ProducesResponseType<string>(Status428PreconditionRequired, "text/plain")]
|
[ProducesResponseType<string>(Status428PreconditionRequired, "text/plain")]
|
||||||
[ProducesResponseType<string>(Status500InternalServerError, "text/plain")]
|
[ProducesResponseType<string>(Status500InternalServerError, "text/plain")]
|
||||||
public async Task<IActionResult> MarkAsRequested (string MangaId, string MangaConnectorName, bool IsRequested)
|
public async Task<Results<Ok, NotFound<string>, StatusCodeHttpResult, InternalServerError<string>>> MarkAsRequested(string MangaId, string MangaConnectorName, bool IsRequested)
|
||||||
{
|
{
|
||||||
if (await context.Mangas.FirstOrDefaultAsync(m => m.Key == MangaId, HttpContext.RequestAborted) is not { } _)
|
if (await context.Mangas.FirstOrDefaultAsync(m => m.Key == MangaId, HttpContext.RequestAborted) is not { } _)
|
||||||
return NotFound(nameof(MangaId));
|
return TypedResults.NotFound(nameof(MangaId));
|
||||||
if(!Tranga.TryGetMangaConnector(MangaConnectorName, out MangaConnector? _))
|
if(!Tranga.TryGetMangaConnector(MangaConnectorName, out MangaConnector? _))
|
||||||
return NotFound(nameof(MangaConnectorName));
|
return TypedResults.NotFound(nameof(MangaConnectorName));
|
||||||
|
|
||||||
if (context.MangaConnectorToManga
|
if (context.MangaConnectorToManga
|
||||||
.FirstOrDefault(id => id.MangaConnectorName == MangaConnectorName && id.ObjId == MangaId)
|
.FirstOrDefault(id => id.MangaConnectorName == MangaConnectorName && id.ObjId == MangaId)
|
||||||
is not { } mcId)
|
is not { } mcId)
|
||||||
{
|
{
|
||||||
if(IsRequested)
|
if(IsRequested)
|
||||||
return StatusCode(Status428PreconditionRequired, "Don't know how to download this Manga from MangaConnector");
|
return TypedResults.StatusCode(Status428PreconditionRequired);
|
||||||
else
|
else
|
||||||
return StatusCode(Status412PreconditionFailed, "Not linked anyways.");
|
return TypedResults.StatusCode(Status412PreconditionFailed);
|
||||||
}
|
}
|
||||||
|
|
||||||
mcId.UseForDownload = IsRequested;
|
mcId.UseForDownload = IsRequested;
|
||||||
if(await context.Sync(HttpContext.RequestAborted) is { success: false } result)
|
if(await context.Sync(HttpContext.RequestAborted) is { success: false } result)
|
||||||
return StatusCode(Status500InternalServerError, result.exceptionMessage);
|
return TypedResults.InternalServerError(result.exceptionMessage);
|
||||||
|
|
||||||
|
|
||||||
DownloadCoverFromMangaconnectorWorker downloadCover = new(mcId);
|
DownloadCoverFromMangaconnectorWorker downloadCover = new(mcId);
|
||||||
RetrieveMangaChaptersFromMangaconnectorWorker retrieveChapters = new(mcId, Tranga.Settings.DownloadLanguage);
|
RetrieveMangaChaptersFromMangaconnectorWorker retrieveChapters = new(mcId, Tranga.Settings.DownloadLanguage);
|
||||||
Tranga.AddWorkers([downloadCover, retrieveChapters]);
|
Tranga.AddWorkers([downloadCover, retrieveChapters]);
|
||||||
|
|
||||||
return Ok();
|
return TypedResults.Ok();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initiate a search for <see cref="Manga"/> on a different <see cref="MangaConnector"/>
|
/// Initiate a search for <see cref="API.Schema.MangaContext.Manga"/> on a different <see cref="MangaConnector"/>
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="MangaId"><see cref="Manga"/> with <paramref name="MangaId"/></param>
|
/// <param name="MangaId"><see cref="API.Schema.MangaContext.Manga"/> with <paramref name="MangaId"/></param>
|
||||||
/// <param name="MangaConnectorName"><see cref="MangaConnector"/>.Name</param>
|
/// <param name="MangaConnectorName"><see cref="MangaConnector"/>.Name</param>
|
||||||
/// <response code="200"></response>
|
/// <response code="200"><see cref="MinimalManga"/> exert of <see cref="Schema.MangaContext.Manga"/></response>
|
||||||
/// <response code="404"><see cref="MangaConnector"/> with Name not found</response>
|
/// <response code="404"><see cref="MangaConnector"/> with Name not found</response>
|
||||||
/// <response code="412"><see cref="MangaConnector"/> with Name is disabled</response>
|
/// <response code="412"><see cref="MangaConnector"/> with Name is disabled</response>
|
||||||
[HttpPost("{MangaId}/SearchOn/{MangaConnectorName}")]
|
[HttpPost("{MangaId}/SearchOn/{MangaConnectorName}")]
|
||||||
[ProducesResponseType<Manga[]>(Status200OK, "application/json")]
|
[ProducesResponseType<List<MinimalManga>>(Status200OK, "application/json")]
|
||||||
[ProducesResponseType(Status404NotFound)]
|
[ProducesResponseType<string>(Status404NotFound, "text/plain")]
|
||||||
[ProducesResponseType(Status406NotAcceptable)]
|
[ProducesResponseType(Status406NotAcceptable)]
|
||||||
public async Task<IActionResult> SearchOnDifferentConnector (string MangaId, string MangaConnectorName)
|
public async Task<Results<Ok<List<MinimalManga>>, NotFound<string>, StatusCodeHttpResult>> SearchOnDifferentConnector (string MangaId, string MangaConnectorName)
|
||||||
{
|
{
|
||||||
if (await context.Mangas.FirstOrDefaultAsync(m => m.Key == MangaId, HttpContext.RequestAborted) is not { } manga)
|
if (await context.Mangas.FirstOrDefaultAsync(m => m.Key == MangaId, HttpContext.RequestAborted) is not { } manga)
|
||||||
return NotFound(nameof(MangaId));
|
return TypedResults.NotFound(nameof(MangaId));
|
||||||
|
|
||||||
return new SearchController(context).SearchManga(MangaConnectorName, manga.Name);
|
return new SearchController(context).SearchManga(MangaConnectorName, manga.Name);
|
||||||
}
|
}
|
||||||
@@ -495,14 +527,27 @@ public class MangaController(MangaContext context) : Controller
|
|||||||
/// <param name="AuthorId"><see cref="Author"/>.Key</param>
|
/// <param name="AuthorId"><see cref="Author"/>.Key</param>
|
||||||
/// <response code="200"></response>
|
/// <response code="200"></response>
|
||||||
/// <response code="404"><see cref="Author"/> with <paramref name="AuthorId"/></response>
|
/// <response code="404"><see cref="Author"/> with <paramref name="AuthorId"/></response>
|
||||||
|
/// /// <response code="500">Error during Database Operation</response>
|
||||||
[HttpGet("WithAuthorId/{AuthorId}")]
|
[HttpGet("WithAuthorId/{AuthorId}")]
|
||||||
[ProducesResponseType<Manga[]>(Status200OK, "application/json")]
|
[ProducesResponseType<List<Manga>>(Status200OK, "application/json")]
|
||||||
public async Task<IActionResult> GetMangaWithAuthorIds (string AuthorId)
|
[ProducesResponseType<string>(Status404NotFound, "text/plain")]
|
||||||
|
public async Task<Results<Ok<List<Manga>>, NotFound<string>, InternalServerError>> GetMangaWithAuthorIds (string AuthorId)
|
||||||
{
|
{
|
||||||
if (await context.Authors.FirstOrDefaultAsync(a => a.Key == AuthorId, HttpContext.RequestAborted) is not { } author)
|
if (await context.Authors.FirstOrDefaultAsync(a => a.Key == AuthorId, HttpContext.RequestAborted) is not { } _)
|
||||||
return NotFound(nameof(AuthorId));
|
return TypedResults.NotFound(nameof(AuthorId));
|
||||||
|
|
||||||
return Ok(context.Mangas.Where(m => m.Authors.Contains(author)));
|
if (await context.MangaIncludeAll().Where(m => m.Authors.Any(a => a.Key == AuthorId)).ToListAsync(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);
|
||||||
|
}).ToList());
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -511,13 +556,28 @@ public class MangaController(MangaContext context) : Controller
|
|||||||
/// <param name="Tag"><see cref="Tag"/>.Tag</param>
|
/// <param name="Tag"><see cref="Tag"/>.Tag</param>
|
||||||
/// <response code="200"></response>
|
/// <response code="200"></response>
|
||||||
/// <response code="404"><see cref="Tag"/> not found</response>
|
/// <response code="404"><see cref="Tag"/> not found</response>
|
||||||
|
/// <response code="500">Error during Database Operation</response>
|
||||||
[HttpGet("WithTag/{Tag}")]
|
[HttpGet("WithTag/{Tag}")]
|
||||||
[ProducesResponseType<Manga[]>(Status200OK, "application/json")]
|
[ProducesResponseType<Manga[]>(Status200OK, "application/json")]
|
||||||
public async Task<IActionResult> GetMangasWithTag (string Tag)
|
[ProducesResponseType<string>(Status404NotFound, "text/plain")]
|
||||||
|
[ProducesResponseType(Status500InternalServerError)]
|
||||||
|
public async Task<Results<Ok<List<Manga>>, NotFound<string>, InternalServerError>> GetMangasWithTag (string Tag)
|
||||||
{
|
{
|
||||||
if (await context.Tags.FirstOrDefaultAsync(t => t.Tag == Tag, HttpContext.RequestAborted) is not { } tag)
|
if (await context.Tags.FirstOrDefaultAsync(t => t.Tag == Tag, HttpContext.RequestAborted) is not { } tag)
|
||||||
return NotFound(nameof(Tag));
|
return TypedResults.NotFound(nameof(Tag));
|
||||||
|
|
||||||
return Ok(context.Mangas.Where(m => m.MangaTags.Contains(tag)));
|
|
||||||
|
if (await context.MangaIncludeAll().Where(m => m.MangaTags.Any(t => t.Tag.Equals(tag))) .ToListAsync(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);
|
||||||
|
}).ToList());
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -1,6 +1,7 @@
|
|||||||
using API.Schema.MangaContext;
|
using API.Schema.MangaContext;
|
||||||
using API.Schema.MangaContext.MetadataFetchers;
|
using API.Schema.MangaContext.MetadataFetchers;
|
||||||
using Asp.Versioning;
|
using Asp.Versioning;
|
||||||
|
using Microsoft.AspNetCore.Http.HttpResults;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using Microsoft.AspNetCore.Mvc.ModelBinding;
|
using Microsoft.AspNetCore.Mvc.ModelBinding;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
@@ -19,10 +20,10 @@ public class MetadataFetcherController(MangaContext context) : Controller
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <response code="200">Names of <see cref="MetadataFetcher"/> (Metadata-Sites)</response>
|
/// <response code="200">Names of <see cref="MetadataFetcher"/> (Metadata-Sites)</response>
|
||||||
[HttpGet]
|
[HttpGet]
|
||||||
[ProducesResponseType<string[]>(Status200OK, "application/json")]
|
[ProducesResponseType<List<string>>(Status200OK, "application/json")]
|
||||||
public IActionResult GetConnectors ()
|
public Ok<List<string>> GetConnectors ()
|
||||||
{
|
{
|
||||||
return Ok(Tranga.MetadataFetchers.Select(m => m.Name).ToArray());
|
return TypedResults.Ok(Tranga.MetadataFetchers.Select(m => m.Name).ToList());
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -31,13 +32,14 @@ public class MetadataFetcherController(MangaContext context) : Controller
|
|||||||
/// <response code="200"></response>
|
/// <response code="200"></response>
|
||||||
/// <response code="500">Error during Database Operation</response>
|
/// <response code="500">Error during Database Operation</response>
|
||||||
[HttpGet("Links")]
|
[HttpGet("Links")]
|
||||||
[ProducesResponseType<MetadataEntry[]>(Status200OK, "application/json")]
|
[ProducesResponseType<List<MetadataEntry>>(Status200OK, "application/json")]
|
||||||
public async Task<IActionResult> GetLinkedEntries ()
|
[ProducesResponseType(Status500InternalServerError)]
|
||||||
|
public async Task<Results<Ok<List<MetadataEntry>>, InternalServerError>> GetLinkedEntries ()
|
||||||
{
|
{
|
||||||
if (await context.MetadataEntries.ToArrayAsync() is not { } result)
|
if (await context.MetadataEntries.ToListAsync() is not { } result)
|
||||||
return StatusCode(Status500InternalServerError);
|
return TypedResults.InternalServerError();
|
||||||
|
|
||||||
return Ok(result);
|
return TypedResults.Ok(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -52,16 +54,16 @@ public class MetadataFetcherController(MangaContext context) : Controller
|
|||||||
[HttpPost("{MetadataFetcherName}/SearchManga/{MangaId}")]
|
[HttpPost("{MetadataFetcherName}/SearchManga/{MangaId}")]
|
||||||
[ProducesResponseType<MetadataSearchResult[]>(Status200OK, "application/json")]
|
[ProducesResponseType<MetadataSearchResult[]>(Status200OK, "application/json")]
|
||||||
[ProducesResponseType(Status400BadRequest)]
|
[ProducesResponseType(Status400BadRequest)]
|
||||||
[ProducesResponseType(Status404NotFound)]
|
[ProducesResponseType<string>(Status404NotFound, "text/plain")]
|
||||||
public async Task<IActionResult> SearchMangaMetadata(string MangaId, string MetadataFetcherName, [FromBody (EmptyBodyBehavior = EmptyBodyBehavior.Allow)]string? searchTerm = null)
|
public async Task<Results<Ok<List<MetadataSearchResult>>, BadRequest, NotFound<string>>> SearchMangaMetadata(string MangaId, string MetadataFetcherName, [FromBody (EmptyBodyBehavior = EmptyBodyBehavior.Allow)]string? searchTerm = null)
|
||||||
{
|
{
|
||||||
if (await context.Mangas.FirstOrDefaultAsync(m => m.Key == MangaId, HttpContext.RequestAborted) is not { } manga)
|
if (await context.Mangas.FirstOrDefaultAsync(m => m.Key == MangaId, HttpContext.RequestAborted) is not { } manga)
|
||||||
return NotFound(nameof(MangaId));
|
return TypedResults.NotFound(nameof(MangaId));
|
||||||
if(Tranga.MetadataFetchers.FirstOrDefault(f => f.Name == MetadataFetcherName) is not { } fetcher)
|
if(Tranga.MetadataFetchers.FirstOrDefault(f => f.Name == MetadataFetcherName) is not { } fetcher)
|
||||||
return BadRequest();
|
return TypedResults.BadRequest();
|
||||||
|
|
||||||
MetadataSearchResult[] searchResults = searchTerm is null ? fetcher.SearchMetadataEntry(manga) : fetcher.SearchMetadataEntry(searchTerm);
|
MetadataSearchResult[] searchResults = searchTerm is null ? fetcher.SearchMetadataEntry(manga) : fetcher.SearchMetadataEntry(searchTerm);
|
||||||
return Ok(searchResults);
|
return TypedResults.Ok(searchResults.ToList());
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -77,21 +79,21 @@ public class MetadataFetcherController(MangaContext context) : Controller
|
|||||||
[HttpPost("{MetadataFetcherName}/Link/{MangaId}")]
|
[HttpPost("{MetadataFetcherName}/Link/{MangaId}")]
|
||||||
[ProducesResponseType<MetadataEntry>(Status200OK, "application/json")]
|
[ProducesResponseType<MetadataEntry>(Status200OK, "application/json")]
|
||||||
[ProducesResponseType(Status400BadRequest)]
|
[ProducesResponseType(Status400BadRequest)]
|
||||||
[ProducesResponseType(Status404NotFound)]
|
[ProducesResponseType<string>(Status404NotFound, "text/plain")]
|
||||||
[ProducesResponseType<string>(Status500InternalServerError, "text/plain")]
|
[ProducesResponseType<string>(Status500InternalServerError, "text/plain")]
|
||||||
public async Task<IActionResult> LinkMangaMetadata (string MangaId, string MetadataFetcherName, [FromBody]string Identifier)
|
public async Task<Results<Ok<MetadataEntry>, BadRequest, NotFound<string>, InternalServerError<string>>> LinkMangaMetadata (string MangaId, string MetadataFetcherName, [FromBody]string Identifier)
|
||||||
{
|
{
|
||||||
if (await context.Mangas.FirstOrDefaultAsync(m => m.Key == MangaId, HttpContext.RequestAborted) is not { } manga)
|
if (await context.Mangas.FirstOrDefaultAsync(m => m.Key == MangaId, HttpContext.RequestAborted) is not { } manga)
|
||||||
return NotFound(nameof(MangaId));
|
return TypedResults.NotFound(nameof(MangaId));
|
||||||
if(Tranga.MetadataFetchers.FirstOrDefault(f => f.Name == MetadataFetcherName) is not { } fetcher)
|
if(Tranga.MetadataFetchers.FirstOrDefault(f => f.Name == MetadataFetcherName) is not { } fetcher)
|
||||||
return BadRequest();
|
return TypedResults.BadRequest();
|
||||||
|
|
||||||
MetadataEntry entry = fetcher.CreateMetadataEntry(manga, Identifier);
|
MetadataEntry entry = fetcher.CreateMetadataEntry(manga, Identifier);
|
||||||
context.MetadataEntries.Add(entry);
|
context.MetadataEntries.Add(entry);
|
||||||
|
|
||||||
if(await context.Sync(HttpContext.RequestAborted) is { success: false } result)
|
if(await context.Sync(HttpContext.RequestAborted) is { success: false } result)
|
||||||
return StatusCode(Status500InternalServerError, result.exceptionMessage);
|
return TypedResults.InternalServerError(result.exceptionMessage);
|
||||||
return Ok(entry);
|
return TypedResults.Ok(entry);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -105,22 +107,23 @@ public class MetadataFetcherController(MangaContext context) : Controller
|
|||||||
[HttpPost("{MetadataFetcherName}/Unlink/{MangaId}")]
|
[HttpPost("{MetadataFetcherName}/Unlink/{MangaId}")]
|
||||||
[ProducesResponseType(Status200OK)]
|
[ProducesResponseType(Status200OK)]
|
||||||
[ProducesResponseType(Status400BadRequest)]
|
[ProducesResponseType(Status400BadRequest)]
|
||||||
[ProducesResponseType(Status404NotFound)]
|
[ProducesResponseType<string>(Status404NotFound, "text/plain")]
|
||||||
[ProducesResponseType<string>(Status412PreconditionFailed, "text/plain")]
|
[ProducesResponseType(Status412PreconditionFailed)]
|
||||||
[ProducesResponseType<string>(Status500InternalServerError, "text/plain")]
|
[ProducesResponseType<string>(Status500InternalServerError, "text/plain")]
|
||||||
public async Task<IActionResult> UnlinkMangaMetadata (string MangaId, string MetadataFetcherName)
|
public async Task<Results<Ok, BadRequest, NotFound<string>, InternalServerError<string>, StatusCodeHttpResult>> UnlinkMangaMetadata (string MangaId, string MetadataFetcherName)
|
||||||
{
|
{
|
||||||
if (await context.Mangas.FirstOrDefaultAsync(m => m.Key == MangaId, HttpContext.RequestAborted) is not { } _)
|
if (await context.Mangas.FirstOrDefaultAsync(m => m.Key == MangaId, HttpContext.RequestAborted) is not { } _)
|
||||||
return NotFound(nameof(MangaId));
|
return TypedResults.NotFound(nameof(MangaId));
|
||||||
if(Tranga.MetadataFetchers.FirstOrDefault(f => f.Name == MetadataFetcherName) is null)
|
if(Tranga.MetadataFetchers.FirstOrDefault(f => f.Name == MetadataFetcherName) is null)
|
||||||
return BadRequest();
|
return TypedResults.BadRequest();
|
||||||
if(context.MetadataEntries.FirstOrDefault(e => e.MangaId == MangaId && e.MetadataFetcherName == MetadataFetcherName) is not { } entry)
|
if (context.MetadataEntries.FirstOrDefault(e =>
|
||||||
return StatusCode(Status412PreconditionFailed, "No entry found");
|
e.MangaId == MangaId && e.MetadataFetcherName == MetadataFetcherName) is not { } entry)
|
||||||
|
return TypedResults.StatusCode(Status412PreconditionFailed);
|
||||||
|
|
||||||
context.Remove(entry);
|
context.Remove(entry);
|
||||||
|
|
||||||
if(await context.Sync(HttpContext.RequestAborted) is { success: false } result)
|
if(await context.Sync(HttpContext.RequestAborted) is { success: false } result)
|
||||||
return StatusCode(Status500InternalServerError, result.exceptionMessage);
|
return TypedResults.InternalServerError(result.exceptionMessage);
|
||||||
return Ok();
|
return TypedResults.Ok();
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -3,6 +3,7 @@ using API.APIEndpointRecords;
|
|||||||
using API.Schema.NotificationsContext;
|
using API.Schema.NotificationsContext;
|
||||||
using API.Schema.NotificationsContext.NotificationConnectors;
|
using API.Schema.NotificationsContext.NotificationConnectors;
|
||||||
using Asp.Versioning;
|
using Asp.Versioning;
|
||||||
|
using Microsoft.AspNetCore.Http.HttpResults;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using static Microsoft.AspNetCore.Http.StatusCodes;
|
using static Microsoft.AspNetCore.Http.StatusCodes;
|
||||||
@@ -22,13 +23,14 @@ public class NotificationConnectorController(NotificationsContext context) : Con
|
|||||||
/// <response code="200"></response>
|
/// <response code="200"></response>
|
||||||
/// <response code="500">Error during Database Operation</response>
|
/// <response code="500">Error during Database Operation</response>
|
||||||
[HttpGet]
|
[HttpGet]
|
||||||
[ProducesResponseType<NotificationConnector[]>(Status200OK, "application/json")]
|
[ProducesResponseType<List<NotificationConnector>>(Status200OK, "application/json")]
|
||||||
public async Task<IActionResult> GetAllConnectors ()
|
[ProducesResponseType(Status500InternalServerError)]
|
||||||
|
public async Task<Results<Ok<List<NotificationConnector>>, InternalServerError>> GetAllConnectors ()
|
||||||
{
|
{
|
||||||
if(await context.NotificationConnectors.ToArrayAsync(HttpContext.RequestAborted) is not { } result)
|
if(await context.NotificationConnectors.ToListAsync(HttpContext.RequestAborted) is not { } result)
|
||||||
return StatusCode(Status500InternalServerError);
|
return TypedResults.InternalServerError();
|
||||||
|
|
||||||
return Ok(result);
|
return TypedResults.Ok(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -39,13 +41,13 @@ public class NotificationConnectorController(NotificationsContext context) : Con
|
|||||||
/// <response code="404"><see cref="NotificationConnector"/> with <paramref name="Name"/> not found</response>
|
/// <response code="404"><see cref="NotificationConnector"/> with <paramref name="Name"/> not found</response>
|
||||||
[HttpGet("{Name}")]
|
[HttpGet("{Name}")]
|
||||||
[ProducesResponseType<NotificationConnector>(Status200OK, "application/json")]
|
[ProducesResponseType<NotificationConnector>(Status200OK, "application/json")]
|
||||||
[ProducesResponseType(Status404NotFound)]
|
[ProducesResponseType<string>(Status404NotFound, "text/plain")]
|
||||||
public async Task<IActionResult> GetConnector (string Name)
|
public async Task<Results<Ok<NotificationConnector>, NotFound<string>>> GetConnector (string Name)
|
||||||
{
|
{
|
||||||
if (await context.NotificationConnectors.FirstOrDefaultAsync(c => c.Name == Name, HttpContext.RequestAborted) is not { } connector)
|
if (await context.NotificationConnectors.FirstOrDefaultAsync(c => c.Name == Name, HttpContext.RequestAborted) is not { } connector)
|
||||||
return NotFound(nameof(Name));
|
return TypedResults.NotFound(nameof(Name));
|
||||||
|
|
||||||
return Ok(connector);
|
return TypedResults.Ok(connector);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -57,14 +59,14 @@ public class NotificationConnectorController(NotificationsContext context) : Con
|
|||||||
[HttpPut]
|
[HttpPut]
|
||||||
[ProducesResponseType<string>(Status200OK, "text/plain")]
|
[ProducesResponseType<string>(Status200OK, "text/plain")]
|
||||||
[ProducesResponseType<string>(Status500InternalServerError, "text/plain")]
|
[ProducesResponseType<string>(Status500InternalServerError, "text/plain")]
|
||||||
public async Task<IActionResult> CreateConnector ([FromBody]NotificationConnector notificationConnector)
|
public async Task<Results<Ok<string>, InternalServerError<string>>> CreateConnector ([FromBody]NotificationConnector notificationConnector)
|
||||||
{
|
{
|
||||||
context.NotificationConnectors.Add(notificationConnector);
|
context.NotificationConnectors.Add(notificationConnector);
|
||||||
context.Notifications.Add(new ("Added new Notification Connector!", notificationConnector.Name, NotificationUrgency.High));
|
context.Notifications.Add(new ("Added new Notification Connector!", notificationConnector.Name, NotificationUrgency.High));
|
||||||
|
|
||||||
if(await context.Sync(HttpContext.RequestAborted) is { success: false } result)
|
if(await context.Sync(HttpContext.RequestAborted) is { success: false } result)
|
||||||
return StatusCode(Status500InternalServerError, result.exceptionMessage);
|
return TypedResults.InternalServerError(result.exceptionMessage);
|
||||||
return Ok(notificationConnector.Name);
|
return TypedResults.Ok(notificationConnector.Name);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -76,7 +78,7 @@ public class NotificationConnectorController(NotificationsContext context) : Con
|
|||||||
[HttpPut("Gotify")]
|
[HttpPut("Gotify")]
|
||||||
[ProducesResponseType<string>(Status200OK, "text/plain")]
|
[ProducesResponseType<string>(Status200OK, "text/plain")]
|
||||||
[ProducesResponseType<string>(Status500InternalServerError, "text/plain")]
|
[ProducesResponseType<string>(Status500InternalServerError, "text/plain")]
|
||||||
public async Task<IActionResult> CreateGotifyConnector ([FromBody]GotifyRecord gotifyData)
|
public async Task<Results<Ok<string>, InternalServerError<string>>> CreateGotifyConnector ([FromBody]GotifyRecord gotifyData)
|
||||||
{
|
{
|
||||||
//TODO Validate Data
|
//TODO Validate Data
|
||||||
|
|
||||||
@@ -97,7 +99,7 @@ public class NotificationConnectorController(NotificationsContext context) : Con
|
|||||||
[HttpPut("Ntfy")]
|
[HttpPut("Ntfy")]
|
||||||
[ProducesResponseType<string>(Status200OK, "text/plain")]
|
[ProducesResponseType<string>(Status200OK, "text/plain")]
|
||||||
[ProducesResponseType<string>(Status500InternalServerError, "text/plain")]
|
[ProducesResponseType<string>(Status500InternalServerError, "text/plain")]
|
||||||
public async Task<IActionResult> CreateNtfyConnector ([FromBody]NtfyRecord ntfyRecord)
|
public async Task<Results<Ok<string>, InternalServerError<string>>> CreateNtfyConnector ([FromBody]NtfyRecord ntfyRecord)
|
||||||
{
|
{
|
||||||
//TODO Validate Data
|
//TODO Validate Data
|
||||||
|
|
||||||
@@ -124,7 +126,7 @@ public class NotificationConnectorController(NotificationsContext context) : Con
|
|||||||
[HttpPut("Pushover")]
|
[HttpPut("Pushover")]
|
||||||
[ProducesResponseType<string>(Status200OK, "text/plain")]
|
[ProducesResponseType<string>(Status200OK, "text/plain")]
|
||||||
[ProducesResponseType<string>(Status500InternalServerError, "text/plain")]
|
[ProducesResponseType<string>(Status500InternalServerError, "text/plain")]
|
||||||
public async Task<IActionResult> CreatePushoverConnector ([FromBody]PushoverRecord pushoverRecord)
|
public async Task<Results<Ok<string>, InternalServerError<string>>> CreatePushoverConnector ([FromBody]PushoverRecord pushoverRecord)
|
||||||
{
|
{
|
||||||
//TODO Validate Data
|
//TODO Validate Data
|
||||||
|
|
||||||
@@ -145,17 +147,17 @@ public class NotificationConnectorController(NotificationsContext context) : Con
|
|||||||
/// <response code="500">Error during Database Operation</response>
|
/// <response code="500">Error during Database Operation</response>
|
||||||
[HttpDelete("{Name}")]
|
[HttpDelete("{Name}")]
|
||||||
[ProducesResponseType(Status200OK)]
|
[ProducesResponseType(Status200OK)]
|
||||||
[ProducesResponseType(Status404NotFound)]
|
[ProducesResponseType<string>(Status404NotFound, "text/plain")]
|
||||||
[ProducesResponseType<string>(Status500InternalServerError, "text/plain")]
|
[ProducesResponseType<string>(Status500InternalServerError, "text/plain")]
|
||||||
public async Task<IActionResult> DeleteConnector (string Name)
|
public async Task<Results<Ok, NotFound<string>, InternalServerError<string>>> DeleteConnector (string Name)
|
||||||
{
|
{
|
||||||
if (await context.NotificationConnectors.FirstOrDefaultAsync(c => c.Name == Name, HttpContext.RequestAborted) is not { } connector)
|
if (await context.NotificationConnectors.FirstOrDefaultAsync(c => c.Name == Name, HttpContext.RequestAborted) is not { } connector)
|
||||||
return NotFound(nameof(Name));
|
return TypedResults.NotFound(nameof(Name));
|
||||||
|
|
||||||
context.NotificationConnectors.Remove(connector);
|
context.NotificationConnectors.Remove(connector);
|
||||||
|
|
||||||
if(await context.Sync(HttpContext.RequestAborted) is { success: false } result)
|
if(await context.Sync(HttpContext.RequestAborted) is { success: false } result)
|
||||||
return StatusCode(Status500InternalServerError, result.exceptionMessage);
|
return TypedResults.InternalServerError(result.exceptionMessage);
|
||||||
return Ok();
|
return TypedResults.Ok();
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -1,9 +1,14 @@
|
|||||||
using API.Schema.MangaContext;
|
using API.Controllers.DTOs;
|
||||||
|
using API.Schema.MangaContext;
|
||||||
using Asp.Versioning;
|
using Asp.Versioning;
|
||||||
|
using Microsoft.AspNetCore.Http.HttpResults;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using Soenneker.Utils.String.NeedlemanWunsch;
|
using Soenneker.Utils.String.NeedlemanWunsch;
|
||||||
using static Microsoft.AspNetCore.Http.StatusCodes;
|
using static Microsoft.AspNetCore.Http.StatusCodes;
|
||||||
|
using Author = API.Controllers.DTOs.Author;
|
||||||
|
using Chapter = API.Controllers.DTOs.Chapter;
|
||||||
|
|
||||||
// ReSharper disable InconsistentNaming
|
// ReSharper disable InconsistentNaming
|
||||||
|
|
||||||
namespace API.Controllers;
|
namespace API.Controllers;
|
||||||
@@ -21,13 +26,13 @@ public class QueryController(MangaContext context) : Controller
|
|||||||
/// <response code="404"><see cref="Author"/> with <paramref name="AuthorId"/> not found</response>
|
/// <response code="404"><see cref="Author"/> with <paramref name="AuthorId"/> not found</response>
|
||||||
[HttpGet("Author/{AuthorId}")]
|
[HttpGet("Author/{AuthorId}")]
|
||||||
[ProducesResponseType<Author>(Status200OK, "application/json")]
|
[ProducesResponseType<Author>(Status200OK, "application/json")]
|
||||||
[ProducesResponseType(Status404NotFound)]
|
[ProducesResponseType<string>(Status404NotFound, "text/plain")]
|
||||||
public async Task<IActionResult> GetAuthor (string AuthorId)
|
public async Task<Results<Ok<Author>, NotFound<string>>> GetAuthor (string AuthorId)
|
||||||
{
|
{
|
||||||
if (await context.Authors.FirstOrDefaultAsync(a => a.Key == AuthorId, HttpContext.RequestAborted) is not { } author)
|
if (await context.Authors.FirstOrDefaultAsync(a => a.Key == AuthorId, HttpContext.RequestAborted) is not { } author)
|
||||||
return NotFound(nameof(AuthorId));
|
return TypedResults.NotFound(nameof(AuthorId));
|
||||||
|
|
||||||
return Ok(author);
|
return TypedResults.Ok(new Author(author.Key, author.AuthorName));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -38,13 +43,15 @@ public class QueryController(MangaContext context) : Controller
|
|||||||
/// <response code="404"><see cref="Chapter"/> with <paramref name="ChapterId"/> not found</response>
|
/// <response code="404"><see cref="Chapter"/> with <paramref name="ChapterId"/> not found</response>
|
||||||
[HttpGet("Chapter/{ChapterId}")]
|
[HttpGet("Chapter/{ChapterId}")]
|
||||||
[ProducesResponseType<Chapter>(Status200OK, "application/json")]
|
[ProducesResponseType<Chapter>(Status200OK, "application/json")]
|
||||||
[ProducesResponseType(Status404NotFound)]
|
[ProducesResponseType<string>(Status404NotFound, "text/plain")]
|
||||||
public async Task<IActionResult> GetChapter (string ChapterId)
|
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)
|
if (await context.Chapters.FirstOrDefaultAsync(c => c.Key == ChapterId, HttpContext.RequestAborted) is not { } chapter)
|
||||||
return NotFound(nameof(ChapterId));
|
return TypedResults.NotFound(nameof(ChapterId));
|
||||||
|
|
||||||
return Ok(chapter);
|
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));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -54,41 +61,46 @@ public class QueryController(MangaContext context) : Controller
|
|||||||
/// <response code="200"></response>
|
/// <response code="200"></response>
|
||||||
/// <response code="404"><see cref="MangaConnectorId{Manga}"/> with <paramref name="MangaConnectorIdId"/> not found</response>
|
/// <response code="404"><see cref="MangaConnectorId{Manga}"/> with <paramref name="MangaConnectorIdId"/> not found</response>
|
||||||
[HttpGet("Manga/MangaConnectorId/{MangaConnectorIdId}")]
|
[HttpGet("Manga/MangaConnectorId/{MangaConnectorIdId}")]
|
||||||
[ProducesResponseType<MangaConnectorId<Manga>>(Status200OK, "application/json")]
|
[ProducesResponseType<MangaConnectorId>(Status200OK, "application/json")]
|
||||||
[ProducesResponseType(Status404NotFound)]
|
[ProducesResponseType<string>(Status404NotFound, "text/plain")]
|
||||||
public async Task<IActionResult> GetMangaMangaConnectorId (string MangaConnectorIdId)
|
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)
|
if (await context.MangaConnectorToManga.FirstOrDefaultAsync(c => c.Key == MangaConnectorIdId, HttpContext.RequestAborted) is not { } mcIdManga)
|
||||||
return NotFound(nameof(MangaConnectorIdId));
|
return TypedResults.NotFound(nameof(MangaConnectorIdId));
|
||||||
|
|
||||||
|
MangaConnectorId result = new (mcIdManga.Key, mcIdManga.MangaConnectorName, mcIdManga.ObjId, mcIdManga.WebsiteUrl, mcIdManga.UseForDownload);
|
||||||
|
|
||||||
return Ok(mcIdManga);
|
return TypedResults.Ok(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Returns <see cref="Manga"/> with names similar to <see cref="Manga"/> (identified by <paramref name="MangaId"/>)
|
/// Returns <see cref="Schema.MangaContext.Manga"/> with names similar to <see cref="Schema.MangaContext.Manga"/> (identified by <paramref name="MangaId"/>)
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="MangaId">Key of <see cref="Manga"/></param>
|
/// <param name="MangaId">Key of <see cref="Schema.MangaContext.Manga"/></param>
|
||||||
/// <response code="200"></response>
|
/// <response code="200"></response>
|
||||||
/// <response code="404"><see cref="Manga"/> with <paramref name="MangaId"/> not found</response>
|
/// <response code="404"><see cref="Schema.MangaContext.Manga"/> with <paramref name="MangaId"/> not found</response>
|
||||||
/// <response code="500">Error during Database Operation</response>
|
/// <response code="500">Error during Database Operation</response>
|
||||||
[HttpGet("Manga/{MangaId}/SimilarName")]
|
[HttpGet("Manga/{MangaId}/SimilarName")]
|
||||||
[ProducesResponseType<string[]>(Status200OK, "application/json")]
|
[ProducesResponseType<List<string>>(Status200OK, "application/json")]
|
||||||
[ProducesResponseType(Status404NotFound)]
|
[ProducesResponseType<string>(Status404NotFound, "text/plain")]
|
||||||
public async Task<IActionResult> GetSimilarManga (string MangaId)
|
[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)
|
if (await context.Mangas.FirstOrDefaultAsync(m => m.Key == MangaId, HttpContext.RequestAborted) is not { } manga)
|
||||||
return NotFound(nameof(MangaId));
|
return TypedResults.NotFound(nameof(MangaId));
|
||||||
|
|
||||||
string name = manga.Name;
|
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)
|
if (await context.Mangas.Where(m => m.Key != MangaId)
|
||||||
return StatusCode(Status500InternalServerError);
|
.ToDictionaryAsync(m => m.Key, m => m.Name, HttpContext.RequestAborted) is not { } mangaNames)
|
||||||
|
return TypedResults.InternalServerError();
|
||||||
string[] similarIds = mangaNames
|
|
||||||
|
List<string> similarIds = mangaNames
|
||||||
.Where(kv => NeedlemanWunschStringUtil.CalculateSimilarityPercentage(name, kv.Value) > 0.8)
|
.Where(kv => NeedlemanWunschStringUtil.CalculateSimilarityPercentage(name, kv.Value) > 0.8)
|
||||||
.Select(kv => kv.Key).ToArray();
|
.Select(kv => kv.Key)
|
||||||
|
.ToList();
|
||||||
|
|
||||||
return Ok(similarIds);
|
return TypedResults.Ok(similarIds);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -98,13 +110,15 @@ public class QueryController(MangaContext context) : Controller
|
|||||||
/// <response code="200"></response>
|
/// <response code="200"></response>
|
||||||
/// <response code="404"><see cref="MangaConnectorId{Chapter}"/> with <paramref name="MangaConnectorIdId"/> not found</response>
|
/// <response code="404"><see cref="MangaConnectorId{Chapter}"/> with <paramref name="MangaConnectorIdId"/> not found</response>
|
||||||
[HttpGet("Chapter/MangaConnectorId/{MangaConnectorIdId}")]
|
[HttpGet("Chapter/MangaConnectorId/{MangaConnectorIdId}")]
|
||||||
[ProducesResponseType<MangaConnectorId<Chapter>>(Status200OK, "application/json")]
|
[ProducesResponseType<MangaConnectorId>(Status200OK, "application/json")]
|
||||||
[ProducesResponseType(Status404NotFound)]
|
[ProducesResponseType<string>(Status404NotFound, "text/plain")]
|
||||||
public async Task<IActionResult> GetChapterMangaConnectorId (string MangaConnectorIdId)
|
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)
|
if (await context.MangaConnectorToManga.FirstOrDefaultAsync(c => c.Key == MangaConnectorIdId, HttpContext.RequestAborted) is not { } mcIdChapter)
|
||||||
return NotFound(nameof(MangaConnectorIdId));
|
return TypedResults.NotFound(nameof(MangaConnectorIdId));
|
||||||
|
|
||||||
return Ok(mcIdChapter);
|
MangaConnectorId result = new(mcIdChapter.Key, mcIdChapter.MangaConnectorName, mcIdChapter.ObjId, mcIdChapter.WebsiteUrl, mcIdChapter.UseForDownload);
|
||||||
|
|
||||||
|
return TypedResults.Ok(result);
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -2,8 +2,11 @@ using API.Controllers.DTOs;
|
|||||||
using API.MangaConnectors;
|
using API.MangaConnectors;
|
||||||
using API.Schema.MangaContext;
|
using API.Schema.MangaContext;
|
||||||
using Asp.Versioning;
|
using Asp.Versioning;
|
||||||
|
using Microsoft.AspNetCore.Http.HttpResults;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using static Microsoft.AspNetCore.Http.StatusCodes;
|
using static Microsoft.AspNetCore.Http.StatusCodes;
|
||||||
|
using Manga = API.Schema.MangaContext.Manga;
|
||||||
|
|
||||||
// ReSharper disable InconsistentNaming
|
// ReSharper disable InconsistentNaming
|
||||||
|
|
||||||
namespace API.Controllers;
|
namespace API.Controllers;
|
||||||
@@ -14,58 +17,62 @@ namespace API.Controllers;
|
|||||||
public class SearchController(MangaContext context) : Controller
|
public class SearchController(MangaContext context) : Controller
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initiate a search for a <see cref="Manga"/> on <see cref="MangaConnector"/> with searchTerm
|
/// Initiate a search for a <see cref="Schema.MangaContext.Manga"/> on <see cref="MangaConnector"/> with searchTerm
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="MangaConnectorName"><see cref="MangaConnector"/>.Name</param>
|
/// <param name="MangaConnectorName"><see cref="MangaConnector"/>.Name</param>
|
||||||
/// <param name="Query">searchTerm</param>
|
/// <param name="Query">searchTerm</param>
|
||||||
/// <response code="200"><see cref="MinimalManga"/> exert of <see cref="Manga"/>. Use <see cref="GetManga"/> for more information</response>
|
/// <response code="200"><see cref="MinimalManga"/> exert of <see cref="Schema.MangaContext.Manga"/></response>
|
||||||
/// <response code="404"><see cref="MangaConnector"/> with Name not found</response>
|
/// <response code="404"><see cref="MangaConnector"/> with Name not found</response>
|
||||||
/// <response code="412"><see cref="MangaConnector"/> with Name is disabled</response>
|
/// <response code="412"><see cref="MangaConnector"/> with Name is disabled</response>
|
||||||
[HttpGet("{MangaConnectorName}/{Query}")]
|
[HttpGet("{MangaConnectorName}/{Query}")]
|
||||||
[ProducesResponseType<MinimalManga[]>(Status200OK, "application/json")]
|
[ProducesResponseType<List<MinimalManga>>(Status200OK, "application/json")]
|
||||||
[ProducesResponseType(Status404NotFound)]
|
[ProducesResponseType<string>(Status404NotFound, "text/plain")]
|
||||||
[ProducesResponseType(Status406NotAcceptable)]
|
[ProducesResponseType(Status406NotAcceptable)]
|
||||||
public IActionResult SearchManga (string MangaConnectorName, string Query)
|
public Results<Ok<List<MinimalManga>>, NotFound<string>, StatusCodeHttpResult> SearchManga (string MangaConnectorName, string Query)
|
||||||
{
|
{
|
||||||
if(Tranga.MangaConnectors.FirstOrDefault(c => c.Name.Equals(MangaConnectorName, StringComparison.InvariantCultureIgnoreCase)) is not { } connector)
|
if(Tranga.MangaConnectors.FirstOrDefault(c => c.Name.Equals(MangaConnectorName, StringComparison.InvariantCultureIgnoreCase)) is not { } connector)
|
||||||
return NotFound();
|
return TypedResults.NotFound(nameof(MangaConnectorName));
|
||||||
if (connector.Enabled is false)
|
if (connector.Enabled is false)
|
||||||
return StatusCode(Status412PreconditionFailed);
|
return TypedResults.StatusCode(Status412PreconditionFailed);
|
||||||
|
|
||||||
(Manga, MangaConnectorId<Manga>)[] mangas = connector.SearchManga(Query);
|
(Manga manga, MangaConnectorId<Manga> id)[] mangas = connector.SearchManga(Query);
|
||||||
List<Manga> retMangas = new();
|
|
||||||
foreach ((Manga manga, MangaConnectorId<Manga> mcId) manga in mangas)
|
|
||||||
{
|
|
||||||
if(Tranga.AddMangaToContext(manga, context, out Manga? add, HttpContext.RequestAborted))
|
|
||||||
retMangas.Add(add);
|
|
||||||
}
|
|
||||||
|
|
||||||
return Ok(retMangas.Select(m => new MinimalManga(m.Key, m.Name, m.Description, m.ReleaseStatus, m.MangaConnectorIds)));
|
IEnumerable<MinimalManga> result = mangas.Select(manga => manga.manga).Select(m =>
|
||||||
|
{
|
||||||
|
IEnumerable<MangaConnectorId> ids = m.MangaConnectorIds.Select(id =>
|
||||||
|
new MangaConnectorId(id.Key, id.MangaConnectorName, id.ObjId, id.WebsiteUrl, id.UseForDownload));
|
||||||
|
return new MinimalManga(m.Key, m.Name, m.Description, m.ReleaseStatus, ids);
|
||||||
|
});
|
||||||
|
|
||||||
|
return TypedResults.Ok(result.ToList());
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Returns <see cref="Manga"/> from the <see cref="MangaConnector"/> associated with <paramref name="url"/>
|
/// Returns <see cref="Schema.MangaContext.Manga"/> from the <see cref="MangaConnector"/> associated with <paramref name="url"/>
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="url"></param>
|
/// <param name="url"></param>
|
||||||
/// <response code="200"><see cref="MinimalManga"/> exert of <see cref="Manga"/>. Use <see cref="GetManga"/> for more information</response>
|
/// <response code="200"><see cref="MinimalManga"/> exert of <see cref="Schema.MangaContext.Manga"/>.</response>
|
||||||
/// <response code="300">Multiple <see cref="MangaConnector"/> found for URL</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")]
|
[HttpPost("Url")]
|
||||||
[ProducesResponseType<MinimalManga>(Status200OK, "application/json")]
|
[ProducesResponseType<MinimalManga>(Status200OK, "application/json")]
|
||||||
[ProducesResponseType(Status404NotFound)]
|
[ProducesResponseType<string>(Status404NotFound, "text/plain")]
|
||||||
[ProducesResponseType(Status500InternalServerError)]
|
[ProducesResponseType<string>(Status500InternalServerError, "text/plain")]
|
||||||
public IActionResult GetMangaFromUrl ([FromBody]string url)
|
public Results<Ok<MinimalManga>, NotFound<string>, InternalServerError<string>> GetMangaFromUrl ([FromBody]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 StatusCode(Status500InternalServerError, "Could not find Global Connector.");
|
return TypedResults.InternalServerError("Could not find Global Connector.");
|
||||||
|
|
||||||
if(connector.GetMangaFromUrl(url) is not { } manga)
|
if(connector.GetMangaFromUrl(url) is not { } manga)
|
||||||
return NotFound();
|
return TypedResults.NotFound("Could not retrieve Manga");
|
||||||
|
|
||||||
if(Tranga.AddMangaToContext(manga, context, out Manga? add, HttpContext.RequestAborted) == false)
|
if(Tranga.AddMangaToContext(manga, context, out Manga? m, HttpContext.RequestAborted) == false)
|
||||||
return StatusCode(Status500InternalServerError);
|
return TypedResults.InternalServerError("Could not add Manga to context");
|
||||||
|
|
||||||
return Ok(new MinimalManga(add.Key, add.Name, add.Description, add.ReleaseStatus, add.MangaConnectorIds));
|
IEnumerable<MangaConnectorId> ids = m.MangaConnectorIds.Select(id =>
|
||||||
|
new MangaConnectorId(id.Key, id.MangaConnectorName, id.ObjId, id.WebsiteUrl, id.UseForDownload));
|
||||||
|
MinimalManga result = new (m.Key, m.Name, m.Description, m.ReleaseStatus, ids);
|
||||||
|
|
||||||
|
return TypedResults.Ok(result);
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -1,5 +1,6 @@
|
|||||||
using API.MangaDownloadClients;
|
using API.MangaDownloadClients;
|
||||||
using Asp.Versioning;
|
using Asp.Versioning;
|
||||||
|
using Microsoft.AspNetCore.Http.HttpResults;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using static Microsoft.AspNetCore.Http.StatusCodes;
|
using static Microsoft.AspNetCore.Http.StatusCodes;
|
||||||
// ReSharper disable InconsistentNaming
|
// ReSharper disable InconsistentNaming
|
||||||
@@ -17,9 +18,9 @@ public class SettingsController() : Controller
|
|||||||
/// <response code="200"></response>
|
/// <response code="200"></response>
|
||||||
[HttpGet]
|
[HttpGet]
|
||||||
[ProducesResponseType<TrangaSettings>(Status200OK, "application/json")]
|
[ProducesResponseType<TrangaSettings>(Status200OK, "application/json")]
|
||||||
public IActionResult GetSettings()
|
public Ok<TrangaSettings> GetSettings()
|
||||||
{
|
{
|
||||||
return Ok(Tranga.Settings);
|
return TypedResults.Ok(Tranga.Settings);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -28,9 +29,9 @@ public class SettingsController() : Controller
|
|||||||
/// <response code="200"></response>
|
/// <response code="200"></response>
|
||||||
[HttpGet("UserAgent")]
|
[HttpGet("UserAgent")]
|
||||||
[ProducesResponseType<string>(Status200OK, "text/plain")]
|
[ProducesResponseType<string>(Status200OK, "text/plain")]
|
||||||
public IActionResult GetUserAgent()
|
public Ok<string> GetUserAgent()
|
||||||
{
|
{
|
||||||
return Ok(Tranga.Settings.UserAgent);
|
return TypedResults.Ok(Tranga.Settings.UserAgent);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -39,11 +40,11 @@ public class SettingsController() : Controller
|
|||||||
/// <response code="200"></response>
|
/// <response code="200"></response>
|
||||||
[HttpPatch("UserAgent")]
|
[HttpPatch("UserAgent")]
|
||||||
[ProducesResponseType(Status200OK)]
|
[ProducesResponseType(Status200OK)]
|
||||||
public IActionResult SetUserAgent([FromBody]string userAgent)
|
public Ok SetUserAgent([FromBody]string userAgent)
|
||||||
{
|
{
|
||||||
//TODO Validate
|
//TODO Validate
|
||||||
Tranga.Settings.SetUserAgent(userAgent);
|
Tranga.Settings.SetUserAgent(userAgent);
|
||||||
return Ok();
|
return TypedResults.Ok();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -52,10 +53,10 @@ public class SettingsController() : Controller
|
|||||||
/// <response code="200"></response>
|
/// <response code="200"></response>
|
||||||
[HttpDelete("UserAgent")]
|
[HttpDelete("UserAgent")]
|
||||||
[ProducesResponseType(Status200OK)]
|
[ProducesResponseType(Status200OK)]
|
||||||
public IActionResult ResetUserAgent()
|
public Ok ResetUserAgent()
|
||||||
{
|
{
|
||||||
Tranga.Settings.SetUserAgent(TrangaSettings.DefaultUserAgent);
|
Tranga.Settings.SetUserAgent(TrangaSettings.DefaultUserAgent);
|
||||||
return Ok();
|
return TypedResults.Ok();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -64,9 +65,9 @@ public class SettingsController() : Controller
|
|||||||
/// <response code="200"></response>
|
/// <response code="200"></response>
|
||||||
[HttpGet("RequestLimits")]
|
[HttpGet("RequestLimits")]
|
||||||
[ProducesResponseType<Dictionary<RequestType,int>>(Status200OK, "application/json")]
|
[ProducesResponseType<Dictionary<RequestType,int>>(Status200OK, "application/json")]
|
||||||
public IActionResult GetRequestLimits()
|
public Ok<Dictionary<RequestType,int>> GetRequestLimits()
|
||||||
{
|
{
|
||||||
return Ok(Tranga.Settings.RequestLimits);
|
return TypedResults.Ok(Tranga.Settings.RequestLimits);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -75,9 +76,9 @@ public class SettingsController() : Controller
|
|||||||
/// <remarks><h1>NOT IMPLEMENTED</h1></remarks>
|
/// <remarks><h1>NOT IMPLEMENTED</h1></remarks>
|
||||||
[HttpPatch("RequestLimits")]
|
[HttpPatch("RequestLimits")]
|
||||||
[ProducesResponseType(Status501NotImplemented)]
|
[ProducesResponseType(Status501NotImplemented)]
|
||||||
public IActionResult SetRequestLimits()
|
public StatusCodeHttpResult SetRequestLimits()
|
||||||
{
|
{
|
||||||
return StatusCode(501);
|
return TypedResults.StatusCode(Status501NotImplemented);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -90,12 +91,12 @@ public class SettingsController() : Controller
|
|||||||
[HttpPatch("RequestLimits/{RequestType}")]
|
[HttpPatch("RequestLimits/{RequestType}")]
|
||||||
[ProducesResponseType(Status200OK)]
|
[ProducesResponseType(Status200OK)]
|
||||||
[ProducesResponseType(Status400BadRequest)]
|
[ProducesResponseType(Status400BadRequest)]
|
||||||
public IActionResult SetRequestLimit(RequestType RequestType, [FromBody]int requestLimit)
|
public Results<Ok, BadRequest> SetRequestLimit(RequestType RequestType, [FromBody]int requestLimit)
|
||||||
{
|
{
|
||||||
if (requestLimit <= 0)
|
if (requestLimit <= 0)
|
||||||
return BadRequest();
|
return TypedResults.BadRequest();
|
||||||
Tranga.Settings.SetRequestLimit(RequestType, requestLimit);
|
Tranga.Settings.SetRequestLimit(RequestType, requestLimit);
|
||||||
return Ok();
|
return TypedResults.Ok();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -104,10 +105,10 @@ public class SettingsController() : Controller
|
|||||||
/// <response code="200"></response>
|
/// <response code="200"></response>
|
||||||
[HttpDelete("RequestLimits/{RequestType}")]
|
[HttpDelete("RequestLimits/{RequestType}")]
|
||||||
[ProducesResponseType<string>(Status200OK)]
|
[ProducesResponseType<string>(Status200OK)]
|
||||||
public IActionResult ResetRequestLimits(RequestType RequestType)
|
public Ok ResetRequestLimits(RequestType RequestType)
|
||||||
{
|
{
|
||||||
Tranga.Settings.SetRequestLimit(RequestType, TrangaSettings.DefaultRequestLimits[RequestType]);
|
Tranga.Settings.SetRequestLimit(RequestType, TrangaSettings.DefaultRequestLimits[RequestType]);
|
||||||
return Ok();
|
return TypedResults.Ok();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -116,10 +117,10 @@ public class SettingsController() : Controller
|
|||||||
/// <response code="200"></response>
|
/// <response code="200"></response>
|
||||||
[HttpDelete("RequestLimits")]
|
[HttpDelete("RequestLimits")]
|
||||||
[ProducesResponseType<string>(Status200OK)]
|
[ProducesResponseType<string>(Status200OK)]
|
||||||
public IActionResult ResetRequestLimits()
|
public Ok ResetRequestLimits()
|
||||||
{
|
{
|
||||||
Tranga.Settings.ResetRequestLimits();
|
Tranga.Settings.ResetRequestLimits();
|
||||||
return Ok();
|
return TypedResults.Ok();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -128,9 +129,9 @@ public class SettingsController() : Controller
|
|||||||
/// <response code="200">JPEG ImageCompression-level as Integer</response>
|
/// <response code="200">JPEG ImageCompression-level as Integer</response>
|
||||||
[HttpGet("ImageCompressionLevel")]
|
[HttpGet("ImageCompressionLevel")]
|
||||||
[ProducesResponseType<int>(Status200OK, "text/plain")]
|
[ProducesResponseType<int>(Status200OK, "text/plain")]
|
||||||
public IActionResult GetImageCompression()
|
public Ok<int> GetImageCompression()
|
||||||
{
|
{
|
||||||
return Ok(Tranga.Settings.ImageCompression);
|
return TypedResults.Ok(Tranga.Settings.ImageCompression);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -142,12 +143,12 @@ public class SettingsController() : Controller
|
|||||||
[HttpPatch("ImageCompressionLevel/{level}")]
|
[HttpPatch("ImageCompressionLevel/{level}")]
|
||||||
[ProducesResponseType(Status200OK)]
|
[ProducesResponseType(Status200OK)]
|
||||||
[ProducesResponseType(Status400BadRequest)]
|
[ProducesResponseType(Status400BadRequest)]
|
||||||
public IActionResult SetImageCompression(int level)
|
public Results<Ok, BadRequest> SetImageCompression(int level)
|
||||||
{
|
{
|
||||||
if (level < 1 || level > 100)
|
if (level < 1 || level > 100)
|
||||||
return BadRequest();
|
return TypedResults.BadRequest();
|
||||||
Tranga.Settings.UpdateImageCompression(level);
|
Tranga.Settings.UpdateImageCompression(level);
|
||||||
return Ok();
|
return TypedResults.Ok();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -156,9 +157,9 @@ public class SettingsController() : Controller
|
|||||||
/// <response code="200">True if enabled</response>
|
/// <response code="200">True if enabled</response>
|
||||||
[HttpGet("BWImages")]
|
[HttpGet("BWImages")]
|
||||||
[ProducesResponseType<bool>(Status200OK, "text/plain")]
|
[ProducesResponseType<bool>(Status200OK, "text/plain")]
|
||||||
public IActionResult GetBwImagesToggle()
|
public Ok<bool> GetBwImagesToggle()
|
||||||
{
|
{
|
||||||
return Ok(Tranga.Settings.BlackWhiteImages);
|
return TypedResults.Ok(Tranga.Settings.BlackWhiteImages);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -168,10 +169,10 @@ public class SettingsController() : Controller
|
|||||||
/// <response code="200"></response>
|
/// <response code="200"></response>
|
||||||
[HttpPatch("BWImages/{enabled}")]
|
[HttpPatch("BWImages/{enabled}")]
|
||||||
[ProducesResponseType(Status200OK)]
|
[ProducesResponseType(Status200OK)]
|
||||||
public IActionResult SetBwImagesToggle(bool enabled)
|
public Ok SetBwImagesToggle(bool enabled)
|
||||||
{
|
{
|
||||||
Tranga.Settings.SetBlackWhiteImageEnabled(enabled);
|
Tranga.Settings.SetBlackWhiteImageEnabled(enabled);
|
||||||
return Ok();
|
return TypedResults.Ok();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -194,9 +195,9 @@ public class SettingsController() : Controller
|
|||||||
/// <response code="200"></response>
|
/// <response code="200"></response>
|
||||||
[HttpGet("ChapterNamingScheme")]
|
[HttpGet("ChapterNamingScheme")]
|
||||||
[ProducesResponseType<string>(Status200OK, "text/plain")]
|
[ProducesResponseType<string>(Status200OK, "text/plain")]
|
||||||
public IActionResult GetCustomNamingScheme()
|
public Ok<string> GetCustomNamingScheme()
|
||||||
{
|
{
|
||||||
return Ok(Tranga.Settings.ChapterNamingScheme);
|
return TypedResults.Ok(Tranga.Settings.ChapterNamingScheme);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -217,12 +218,12 @@ public class SettingsController() : Controller
|
|||||||
/// <response code="200"></response>
|
/// <response code="200"></response>
|
||||||
[HttpPatch("ChapterNamingScheme")]
|
[HttpPatch("ChapterNamingScheme")]
|
||||||
[ProducesResponseType(Status200OK)]
|
[ProducesResponseType(Status200OK)]
|
||||||
public IActionResult SetCustomNamingScheme([FromBody]string namingScheme)
|
public Ok SetCustomNamingScheme([FromBody]string namingScheme)
|
||||||
{
|
{
|
||||||
//TODO Move old Chapters
|
//TODO Move old Chapters
|
||||||
Tranga.Settings.SetChapterNamingScheme(namingScheme);
|
Tranga.Settings.SetChapterNamingScheme(namingScheme);
|
||||||
|
|
||||||
return Ok();
|
return TypedResults.Ok();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -232,10 +233,10 @@ public class SettingsController() : Controller
|
|||||||
/// <response code="200"></response>
|
/// <response code="200"></response>
|
||||||
[HttpPost("FlareSolverr/Url")]
|
[HttpPost("FlareSolverr/Url")]
|
||||||
[ProducesResponseType(Status200OK)]
|
[ProducesResponseType(Status200OK)]
|
||||||
public IActionResult SetFlareSolverrUrl([FromBody]string flareSolverrUrl)
|
public Ok SetFlareSolverrUrl([FromBody]string flareSolverrUrl)
|
||||||
{
|
{
|
||||||
Tranga.Settings.SetFlareSolverrUrl(flareSolverrUrl);
|
Tranga.Settings.SetFlareSolverrUrl(flareSolverrUrl);
|
||||||
return Ok();
|
return TypedResults.Ok();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -244,10 +245,10 @@ public class SettingsController() : Controller
|
|||||||
/// <response code="200"></response>
|
/// <response code="200"></response>
|
||||||
[HttpDelete("FlareSolverr/Url")]
|
[HttpDelete("FlareSolverr/Url")]
|
||||||
[ProducesResponseType(Status200OK)]
|
[ProducesResponseType(Status200OK)]
|
||||||
public IActionResult ClearFlareSolverrUrl()
|
public Ok ClearFlareSolverrUrl()
|
||||||
{
|
{
|
||||||
Tranga.Settings.SetFlareSolverrUrl(string.Empty);
|
Tranga.Settings.SetFlareSolverrUrl(string.Empty);
|
||||||
return Ok();
|
return TypedResults.Ok();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -258,12 +259,12 @@ public class SettingsController() : Controller
|
|||||||
[HttpPost("FlareSolverr/Test")]
|
[HttpPost("FlareSolverr/Test")]
|
||||||
[ProducesResponseType(Status200OK)]
|
[ProducesResponseType(Status200OK)]
|
||||||
[ProducesResponseType(Status500InternalServerError)]
|
[ProducesResponseType(Status500InternalServerError)]
|
||||||
public IActionResult TestFlareSolverrReachable()
|
public Results<Ok, InternalServerError> TestFlareSolverrReachable()
|
||||||
{
|
{
|
||||||
const string knownProtectedUrl = "https://prowlarr.servarr.com/v1/ping";
|
const string knownProtectedUrl = "https://prowlarr.servarr.com/v1/ping";
|
||||||
FlareSolverrDownloadClient client = new();
|
FlareSolverrDownloadClient client = new();
|
||||||
RequestResult result = client.MakeRequestInternal(knownProtectedUrl);
|
RequestResult result = client.MakeRequestInternal(knownProtectedUrl);
|
||||||
return (int)result.statusCode >= 200 && (int)result.statusCode < 300 ? Ok() : StatusCode(500, result.statusCode);
|
return (int)result.statusCode >= 200 && (int)result.statusCode < 300 ? TypedResults.Ok() : TypedResults.InternalServerError();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -272,9 +273,9 @@ public class SettingsController() : Controller
|
|||||||
/// <response code="200"></response>
|
/// <response code="200"></response>
|
||||||
[HttpGet("DownloadLanguage")]
|
[HttpGet("DownloadLanguage")]
|
||||||
[ProducesResponseType<string>(Status200OK, "text/plain")]
|
[ProducesResponseType<string>(Status200OK, "text/plain")]
|
||||||
public IActionResult GetDownloadLanguage()
|
public Ok<string> GetDownloadLanguage()
|
||||||
{
|
{
|
||||||
return Ok(Tranga.Settings.DownloadLanguage);
|
return TypedResults.Ok(Tranga.Settings.DownloadLanguage);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -283,10 +284,10 @@ public class SettingsController() : Controller
|
|||||||
/// <response code="200"></response>
|
/// <response code="200"></response>
|
||||||
[HttpPatch("DownloadLanguage/{Language}")]
|
[HttpPatch("DownloadLanguage/{Language}")]
|
||||||
[ProducesResponseType(Status200OK)]
|
[ProducesResponseType(Status200OK)]
|
||||||
public IActionResult SetDownloadLanguage(string Language)
|
public Ok SetDownloadLanguage(string Language)
|
||||||
{
|
{
|
||||||
//TODO Validation
|
//TODO Validation
|
||||||
Tranga.Settings.SetDownloadLanguage(Language);
|
Tranga.Settings.SetDownloadLanguage(Language);
|
||||||
return Ok();
|
return TypedResults.Ok();
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -1,5 +1,7 @@
|
|||||||
using API.Workers;
|
using API.Controllers.DTOs;
|
||||||
|
using API.Workers;
|
||||||
using Asp.Versioning;
|
using Asp.Versioning;
|
||||||
|
using Microsoft.AspNetCore.Http.HttpResults;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using static Microsoft.AspNetCore.Http.StatusCodes;
|
using static Microsoft.AspNetCore.Http.StatusCodes;
|
||||||
// ReSharper disable InconsistentNaming
|
// ReSharper disable InconsistentNaming
|
||||||
@@ -14,12 +16,14 @@ public class WorkerController() : Controller
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Returns all <see cref="BaseWorker"/>
|
/// Returns all <see cref="BaseWorker"/>
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <response code="200"></response>
|
/// <response code="200"><see cref="Worker"/></response>
|
||||||
[HttpGet]
|
[HttpGet]
|
||||||
[ProducesResponseType<BaseWorker[]>(Status200OK, "application/json")]
|
[ProducesResponseType<List<Worker>>(Status200OK, "application/json")]
|
||||||
public IActionResult GetWorkers()
|
public Ok<List<Worker>> GetWorkers()
|
||||||
{
|
{
|
||||||
return Ok(Tranga.GetRunningWorkers().ToArray());
|
IEnumerable<Worker> result = Tranga.GetRunningWorkers().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>
|
/// <summary>
|
||||||
@@ -28,9 +32,9 @@ public class WorkerController() : Controller
|
|||||||
/// <response code="200"></response>
|
/// <response code="200"></response>
|
||||||
[HttpGet("Keys")]
|
[HttpGet("Keys")]
|
||||||
[ProducesResponseType<string[]>(Status200OK, "application/json")]
|
[ProducesResponseType<string[]>(Status200OK, "application/json")]
|
||||||
public IActionResult GetWorkerIds()
|
public Ok<List<string>> GetWorkerIds()
|
||||||
{
|
{
|
||||||
return Ok(Tranga.GetRunningWorkers().Select(w => w.Key).ToArray());
|
return TypedResults.Ok(Tranga.GetRunningWorkers().Select(w => w.Key).ToList());
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -39,10 +43,12 @@ public class WorkerController() : Controller
|
|||||||
/// <param name="State">Requested <see cref="WorkerExecutionState"/></param>
|
/// <param name="State">Requested <see cref="WorkerExecutionState"/></param>
|
||||||
/// <response code="200"></response>
|
/// <response code="200"></response>
|
||||||
[HttpGet("State/{State}")]
|
[HttpGet("State/{State}")]
|
||||||
[ProducesResponseType<BaseWorker[]>(Status200OK, "application/json")]
|
[ProducesResponseType<List<Worker>>(Status200OK, "application/json")]
|
||||||
public IActionResult GetWorkersInState(WorkerExecutionState State)
|
public Ok<List<Worker>> GetWorkersInState(WorkerExecutionState State)
|
||||||
{
|
{
|
||||||
return Ok(Tranga.GetRunningWorkers().Where(worker => worker.State == State).ToArray());
|
IEnumerable<Worker> result = Tranga.GetRunningWorkers().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());
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -52,13 +58,16 @@ public class WorkerController() : Controller
|
|||||||
/// <response code="200"></response>
|
/// <response code="200"></response>
|
||||||
/// <response code="404"><see cref="BaseWorker"/> with <paramref name="WorkerId"/> could not be found</response>
|
/// <response code="404"><see cref="BaseWorker"/> with <paramref name="WorkerId"/> could not be found</response>
|
||||||
[HttpGet("{WorkerId}")]
|
[HttpGet("{WorkerId}")]
|
||||||
[ProducesResponseType<BaseWorker>(Status200OK, "application/json")]
|
[ProducesResponseType<Worker>(Status200OK, "application/json")]
|
||||||
[ProducesResponseType(Status404NotFound)]
|
[ProducesResponseType<string>(Status404NotFound, "text/plain")]
|
||||||
public IActionResult GetWorker(string WorkerId)
|
public Results<Ok<Worker>, NotFound<string>> GetWorker(string WorkerId)
|
||||||
{
|
{
|
||||||
if(Tranga.GetRunningWorkers().FirstOrDefault(w => w.Key == WorkerId) is not { } worker)
|
if(Tranga.GetRunningWorkers().FirstOrDefault(w => w.Key == WorkerId) is not { } w)
|
||||||
return NotFound(nameof(WorkerId));
|
return TypedResults.NotFound(nameof(WorkerId));
|
||||||
return Ok(worker);
|
|
||||||
|
Worker result = new (w.Key, w.AllDependencies.Select(d => d.Key), w.MissingDependencies.Select(d => d.Key), w.AllDependenciesFulfilled, w.State);
|
||||||
|
|
||||||
|
return TypedResults.Ok(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -69,13 +78,13 @@ public class WorkerController() : Controller
|
|||||||
/// <response code="404"><see cref="BaseWorker"/> with <paramref name="WorkerId"/> could not be found</response>
|
/// <response code="404"><see cref="BaseWorker"/> with <paramref name="WorkerId"/> could not be found</response>
|
||||||
[HttpDelete("{WorkerId}")]
|
[HttpDelete("{WorkerId}")]
|
||||||
[ProducesResponseType(Status200OK)]
|
[ProducesResponseType(Status200OK)]
|
||||||
[ProducesResponseType(Status404NotFound)]
|
[ProducesResponseType<string>(Status404NotFound, "text/plain")]
|
||||||
public IActionResult DeleteWorker(string WorkerId)
|
public Results<Ok, NotFound<string>> DeleteWorker(string WorkerId)
|
||||||
{
|
{
|
||||||
if(Tranga.GetRunningWorkers().FirstOrDefault(w => w.Key == WorkerId) is not { } worker)
|
if(Tranga.GetRunningWorkers().FirstOrDefault(w => w.Key == WorkerId) is not { } worker)
|
||||||
return NotFound(nameof(WorkerId));
|
return TypedResults.NotFound(nameof(WorkerId));
|
||||||
Tranga.StopWorker(worker);
|
Tranga.StopWorker(worker);
|
||||||
return Ok();
|
return TypedResults.Ok();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -87,18 +96,18 @@ public class WorkerController() : Controller
|
|||||||
/// <response code="412"><see cref="BaseWorker"/> was already running</response>
|
/// <response code="412"><see cref="BaseWorker"/> was already running</response>
|
||||||
[HttpPost("{WorkerId}/Start")]
|
[HttpPost("{WorkerId}/Start")]
|
||||||
[ProducesResponseType(Status202Accepted)]
|
[ProducesResponseType(Status202Accepted)]
|
||||||
[ProducesResponseType(Status404NotFound)]
|
[ProducesResponseType<string>(Status404NotFound, "text/plain")]
|
||||||
[ProducesResponseType<string>(Status412PreconditionFailed, "text/plain")]
|
[ProducesResponseType(Status412PreconditionFailed)]
|
||||||
public IActionResult StartWorker(string WorkerId)
|
public Results<Ok, NotFound<string>, StatusCodeHttpResult> StartWorker(string WorkerId)
|
||||||
{
|
{
|
||||||
if(Tranga.GetRunningWorkers().FirstOrDefault(w => w.Key == WorkerId) is not { } worker)
|
if(Tranga.GetRunningWorkers().FirstOrDefault(w => w.Key == WorkerId) is not { } worker)
|
||||||
return NotFound(nameof(WorkerId));
|
return TypedResults.NotFound(nameof(WorkerId));
|
||||||
|
|
||||||
if (worker.State >= WorkerExecutionState.Waiting)
|
if (worker.State >= WorkerExecutionState.Waiting)
|
||||||
return StatusCode(Status412PreconditionFailed, "Already running");
|
return TypedResults.StatusCode(Status412PreconditionFailed);
|
||||||
|
|
||||||
Tranga.StartWorker(worker);
|
Tranga.StartWorker(worker);
|
||||||
return Ok();
|
return TypedResults.Ok();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -107,18 +116,20 @@ public class WorkerController() : Controller
|
|||||||
/// <param name="WorkerId"><see cref="BaseWorker"/>.Key</param>
|
/// <param name="WorkerId"><see cref="BaseWorker"/>.Key</param>
|
||||||
/// <response code="200"></response>
|
/// <response code="200"></response>
|
||||||
/// <response code="404"><see cref="BaseWorker"/> with <paramref name="WorkerId"/> could not be found</response>
|
/// <response code="404"><see cref="BaseWorker"/> with <paramref name="WorkerId"/> could not be found</response>
|
||||||
/// <response code="208"><see cref="BaseWorker"/> was not running</response>
|
/// <response code="412"><see cref="BaseWorker"/> was already not running</response>
|
||||||
[HttpPost("{WorkerId}/Stop")]
|
[HttpPost("{WorkerId}/Stop")]
|
||||||
[ProducesResponseType(Status501NotImplemented)]
|
[ProducesResponseType(Status202Accepted)]
|
||||||
public IActionResult StopWorker(string WorkerId)
|
[ProducesResponseType<string>(Status404NotFound, "text/plain")]
|
||||||
|
[ProducesResponseType(Status412PreconditionFailed)]
|
||||||
|
public Results<Ok, NotFound<string>, StatusCodeHttpResult> StopWorker(string WorkerId)
|
||||||
{
|
{
|
||||||
if(Tranga.GetRunningWorkers().FirstOrDefault(w => w.Key == WorkerId) is not { } worker)
|
if(Tranga.GetRunningWorkers().FirstOrDefault(w => w.Key == WorkerId) is not { } worker)
|
||||||
return NotFound(nameof(WorkerId));
|
return TypedResults.NotFound(nameof(WorkerId));
|
||||||
|
|
||||||
if(worker.State is < WorkerExecutionState.Running or >= WorkerExecutionState.Completed)
|
if(worker.State is < WorkerExecutionState.Running or >= WorkerExecutionState.Completed)
|
||||||
return StatusCode(Status208AlreadyReported, "Not running");
|
return TypedResults.StatusCode(Status412PreconditionFailed);
|
||||||
|
|
||||||
Tranga.StopWorker(worker);
|
Tranga.StopWorker(worker);
|
||||||
return Ok();
|
return TypedResults.Ok();
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -7,7 +7,6 @@ namespace API.Schema.MangaContext;
|
|||||||
public class Author(string authorName) : Identifiable(TokenGen.CreateToken(typeof(Author), authorName))
|
public class Author(string authorName) : Identifiable(TokenGen.CreateToken(typeof(Author), authorName))
|
||||||
{
|
{
|
||||||
[StringLength(128)]
|
[StringLength(128)]
|
||||||
[Required]
|
|
||||||
public string AuthorName { get; init; } = authorName;
|
public string AuthorName { get; init; } = authorName;
|
||||||
|
|
||||||
public override string ToString() => $"{base.ToString()} {AuthorName}";
|
public override string ToString() => $"{base.ToString()} {AuthorName}";
|
||||||
|
@@ -4,28 +4,27 @@ using System.Text;
|
|||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
using System.Xml.Linq;
|
using System.Xml.Linq;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using Newtonsoft.Json;
|
|
||||||
|
|
||||||
namespace API.Schema.MangaContext;
|
namespace API.Schema.MangaContext;
|
||||||
|
|
||||||
[PrimaryKey("Key")]
|
[PrimaryKey("Key")]
|
||||||
public class Chapter : Identifiable, IComparable<Chapter>
|
public class Chapter : Identifiable, IComparable<Chapter>
|
||||||
{
|
{
|
||||||
[StringLength(64)] [Required] public string ParentMangaId { get; init; } = null!;
|
[StringLength(64)] public string ParentMangaId { get; init; } = null!;
|
||||||
[JsonIgnore] public Manga ParentManga = null!;
|
public Manga ParentManga = null!;
|
||||||
|
|
||||||
[NotMapped] public Dictionary<string, string> IdsOnMangaConnectors =>
|
[NotMapped] public Dictionary<string, string> IdsOnMangaConnectors =>
|
||||||
MangaConnectorIds.ToDictionary(id => id.MangaConnectorName, id => id.IdOnConnectorSite);
|
MangaConnectorIds.ToDictionary(id => id.MangaConnectorName, id => id.IdOnConnectorSite);
|
||||||
[JsonIgnore] public ICollection<MangaConnectorId<Chapter>> MangaConnectorIds = null!;
|
public ICollection<MangaConnectorId<Chapter>> MangaConnectorIds = null!;
|
||||||
|
|
||||||
public int? VolumeNumber { get; private set; }
|
public int? VolumeNumber { get; private set; }
|
||||||
[StringLength(10)] [Required] public string ChapterNumber { get; private set; }
|
[StringLength(10)] public string ChapterNumber { get; private set; }
|
||||||
|
|
||||||
[StringLength(256)] public string? Title { get; private set; }
|
[StringLength(256)] public string? Title { get; private set; }
|
||||||
|
|
||||||
[StringLength(256)] [Required] public string FileName { get; private set; }
|
[StringLength(256)] public string FileName { get; private set; }
|
||||||
|
|
||||||
[Required] public bool Downloaded { get; internal set; }
|
public bool Downloaded { get; internal set; }
|
||||||
[NotMapped] public string FullArchiveFilePath => Path.Join(ParentManga.FullDirectoryPath, FileName);
|
[NotMapped] public string FullArchiveFilePath => Path.Join(ParentManga.FullDirectoryPath, FileName);
|
||||||
|
|
||||||
public Chapter(Manga parentManga, string chapterNumber,
|
public Chapter(Manga parentManga, string chapterNumber,
|
||||||
|
@@ -7,10 +7,8 @@ namespace API.Schema.MangaContext;
|
|||||||
public class Link(string linkProvider, string linkUrl) : Identifiable(TokenGen.CreateToken(typeof(Link), linkProvider, linkUrl))
|
public class Link(string linkProvider, string linkUrl) : Identifiable(TokenGen.CreateToken(typeof(Link), linkProvider, linkUrl))
|
||||||
{
|
{
|
||||||
[StringLength(64)]
|
[StringLength(64)]
|
||||||
[Required]
|
|
||||||
public string LinkProvider { get; init; } = linkProvider;
|
public string LinkProvider { get; init; } = linkProvider;
|
||||||
[StringLength(2048)]
|
[StringLength(2048)]
|
||||||
[Required]
|
|
||||||
[Url]
|
[Url]
|
||||||
public string LinkUrl { get; init; } = linkUrl;
|
public string LinkUrl { get; init; } = linkUrl;
|
||||||
|
|
||||||
|
@@ -4,8 +4,6 @@ using System.Runtime.InteropServices;
|
|||||||
using System.Text;
|
using System.Text;
|
||||||
using API.Workers;
|
using API.Workers;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
|
||||||
using Newtonsoft.Json;
|
|
||||||
using static System.IO.UnixFileMode;
|
using static System.IO.UnixFileMode;
|
||||||
|
|
||||||
namespace API.Schema.MangaContext;
|
namespace API.Schema.MangaContext;
|
||||||
@@ -13,34 +11,31 @@ namespace API.Schema.MangaContext;
|
|||||||
[PrimaryKey("Key")]
|
[PrimaryKey("Key")]
|
||||||
public class Manga : Identifiable
|
public class Manga : Identifiable
|
||||||
{
|
{
|
||||||
[StringLength(512)] [Required] public string Name { get; internal set; }
|
[StringLength(512)] public string Name { get; internal set; }
|
||||||
[Required] public string Description { get; internal set; }
|
[Required] public string Description { get; internal set; }
|
||||||
[JsonIgnore] [Url] [StringLength(512)] public string CoverUrl { get; internal set; }
|
[Url] [StringLength(512)] public string CoverUrl { get; internal set; }
|
||||||
[Required] public MangaReleaseStatus ReleaseStatus { get; internal set; }
|
public MangaReleaseStatus ReleaseStatus { get; internal set; }
|
||||||
[StringLength(64)] public string? LibraryId { get; private set; }
|
[StringLength(64)] public string? LibraryId { get; private set; }
|
||||||
[JsonIgnore] public FileLibrary? Library = null!;
|
public FileLibrary? Library = null!;
|
||||||
|
|
||||||
public ICollection<Author> Authors { get; internal set; } = null!;
|
public ICollection<Author> Authors { get; internal set; } = null!;
|
||||||
public ICollection<MangaTag> MangaTags { get; internal set; } = null!;
|
public ICollection<MangaTag> MangaTags { get; internal set; } = null!;
|
||||||
public ICollection<Link> Links { get; internal set; } = null!;
|
public ICollection<Link> Links { get; internal set; } = null!;
|
||||||
public ICollection<AltTitle> AltTitles { get; internal set; } = null!;
|
public ICollection<AltTitle> AltTitles { get; internal set; } = null!;
|
||||||
[Required] public float IgnoreChaptersBefore { get; internal set; }
|
public float IgnoreChaptersBefore { get; internal set; }
|
||||||
[StringLength(1024)] [Required] public string DirectoryName { get; private set; }
|
[StringLength(1024)] [Required] public string DirectoryName { get; private set; }
|
||||||
[JsonIgnore] [StringLength(512)] public string? CoverFileNameInCache { get; internal set; }
|
[StringLength(512)] public string? CoverFileNameInCache { get; internal set; }
|
||||||
public uint? Year { get; internal init; }
|
public uint? Year { get; internal init; }
|
||||||
[StringLength(8)] public string? OriginalLanguage { get; internal init; }
|
[StringLength(8)] public string? OriginalLanguage { get; internal init; }
|
||||||
|
|
||||||
[JsonIgnore]
|
[NotMapped] public string? FullDirectoryPath => Library is not null ? Path.Join(Library.BasePath, DirectoryName) : null;
|
||||||
[NotMapped]
|
|
||||||
public string? FullDirectoryPath => Library is not null ? Path.Join(Library.BasePath, DirectoryName) : null;
|
|
||||||
|
|
||||||
[NotMapped] public ICollection<string> ChapterIds => Chapters.Select(c => c.Key).ToList();
|
[NotMapped] public ICollection<string> ChapterIds => Chapters.Select(c => c.Key).ToList();
|
||||||
[JsonIgnore] public ICollection<Chapter> Chapters = null!;
|
public ICollection<Chapter> Chapters = null!;
|
||||||
|
|
||||||
[NotMapped] public Dictionary<string, string> IdsOnMangaConnectors =>
|
[NotMapped] public Dictionary<string, string> IdsOnMangaConnectors => MangaConnectorIds.ToDictionary(id => id.MangaConnectorName, id => id.IdOnConnectorSite);
|
||||||
MangaConnectorIds.ToDictionary(id => id.MangaConnectorName, id => id.IdOnConnectorSite);
|
|
||||||
[NotMapped] public ICollection<string> MangaConnectorIdsIds => MangaConnectorIds.Select(id => id.Key).ToList();
|
[NotMapped] public ICollection<string> MangaConnectorIdsIds => MangaConnectorIds.Select(id => id.Key).ToList();
|
||||||
[JsonIgnore] public ICollection<MangaConnectorId<Manga>> MangaConnectorIds = null!;
|
public ICollection<MangaConnectorId<Manga>> MangaConnectorIds = null!;
|
||||||
|
|
||||||
public Manga(string name, string description, string coverUrl, MangaReleaseStatus releaseStatus,
|
public Manga(string name, string description, string coverUrl, MangaReleaseStatus releaseStatus,
|
||||||
ICollection<Author> authors, ICollection<MangaTag> mangaTags, ICollection<Link> links, ICollection<AltTitle> altTitles,
|
ICollection<Author> authors, ICollection<MangaTag> mangaTags, ICollection<Link> links, ICollection<AltTitle> altTitles,
|
||||||
|
@@ -1,19 +1,18 @@
|
|||||||
using System.ComponentModel.DataAnnotations;
|
using System.ComponentModel.DataAnnotations;
|
||||||
using API.MangaConnectors;
|
using API.MangaConnectors;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using Newtonsoft.Json;
|
|
||||||
|
|
||||||
namespace API.Schema.MangaContext;
|
namespace API.Schema.MangaContext;
|
||||||
|
|
||||||
[PrimaryKey("Key")]
|
[PrimaryKey("Key")]
|
||||||
public class MangaConnectorId<T> : Identifiable where T : Identifiable
|
public class MangaConnectorId<T> : Identifiable where T : Identifiable
|
||||||
{
|
{
|
||||||
[StringLength(64)] [Required] public string ObjId { get; internal set; }
|
public T Obj = null!;
|
||||||
[JsonIgnore] public T Obj = null!;
|
[StringLength(64)] public string ObjId { get; internal set; }
|
||||||
|
|
||||||
[StringLength(32)] [Required] public string MangaConnectorName { get; private set; }
|
[StringLength(32)] public string MangaConnectorName { get; private set; }
|
||||||
|
|
||||||
[StringLength(256)] [Required] public string IdOnConnectorSite { get; init; }
|
[StringLength(256)] public string IdOnConnectorSite { get; init; }
|
||||||
[Url] [StringLength(512)] public string? WebsiteUrl { get; internal init; }
|
[Url] [StringLength(512)] public string? WebsiteUrl { get; internal init; }
|
||||||
public bool UseForDownload { get; internal set; }
|
public bool UseForDownload { get; internal set; }
|
||||||
|
|
||||||
|
@@ -1,6 +1,5 @@
|
|||||||
using API.Schema;
|
using API.Schema;
|
||||||
using log4net;
|
using log4net;
|
||||||
using Newtonsoft.Json;
|
|
||||||
|
|
||||||
namespace API.Workers;
|
namespace API.Workers;
|
||||||
|
|
||||||
@@ -9,21 +8,19 @@ public abstract class BaseWorker : Identifiable
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Workers this Worker depends on being completed before running.
|
/// Workers this Worker depends on being completed before running.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public BaseWorker[] DependsOn { get; init; }
|
private BaseWorker[] DependsOn { get; init; }
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Dependencies and dependencies of dependencies. See also <see cref="DependsOn"/>.
|
/// Dependencies and dependencies of dependencies. See also <see cref="DependsOn"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[JsonIgnore]
|
internal IEnumerable<BaseWorker> AllDependencies => DependsOn.Select(d => d.AllDependencies).SelectMany(x => x);
|
||||||
public IEnumerable<BaseWorker> AllDependencies => DependsOn.Select(d => d.AllDependencies).SelectMany(x => x);
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// <see cref="AllDependencies"/> and Self.
|
/// <see cref="AllDependencies"/> and Self.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[JsonIgnore]
|
internal IEnumerable<BaseWorker> DependenciesAndSelf => AllDependencies.Append(this);
|
||||||
public IEnumerable<BaseWorker> DependenciesAndSelf => AllDependencies.Append(this);
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// <see cref="DependsOn"/> where <see cref="WorkerExecutionState"/> is less than Completed.
|
/// <see cref="DependsOn"/> where <see cref="WorkerExecutionState"/> is less than Completed.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public IEnumerable<BaseWorker> MissingDependencies => DependsOn.Where(d => d.State < WorkerExecutionState.Completed);
|
internal IEnumerable<BaseWorker> MissingDependencies => DependsOn.Where(d => d.State < WorkerExecutionState.Completed);
|
||||||
public bool AllDependenciesFulfilled => DependsOn.All(d => d.State >= WorkerExecutionState.Completed);
|
public bool AllDependenciesFulfilled => DependsOn.All(d => d.State >= WorkerExecutionState.Completed);
|
||||||
internal WorkerExecutionState State { get; private set; }
|
internal WorkerExecutionState State { get; private set; }
|
||||||
private CancellationTokenSource _cancellationTokenSource = new ();
|
private CancellationTokenSource _cancellationTokenSource = new ();
|
||||||
@@ -50,7 +47,7 @@ public abstract class BaseWorker : Identifiable
|
|||||||
_cancellationTokenSource.Cancel();
|
_cancellationTokenSource.Cancel();
|
||||||
}
|
}
|
||||||
|
|
||||||
public BaseWorker(IEnumerable<BaseWorker>? dependsOn = null)
|
protected BaseWorker(IEnumerable<BaseWorker>? dependsOn = null)
|
||||||
{
|
{
|
||||||
this.DependsOn = dependsOn?.ToArray() ?? [];
|
this.DependsOn = dependsOn?.ToArray() ?? [];
|
||||||
this.Log = LogManager.GetLogger(GetType());
|
this.Log = LogManager.GetLogger(GetType());
|
||||||
|
Reference in New Issue
Block a user