using API.Controllers.DTOs; using API.Schema.MangaContext; using Asp.Versioning; using Microsoft.AspNetCore.Http.HttpResults; using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; using static Microsoft.AspNetCore.Http.StatusCodes; using Chapter = API.Controllers.DTOs.Chapter; // ReSharper disable InconsistentNaming namespace API.Controllers; [ApiVersion(2)] [ApiController] [Route("v{v:apiVersion}/[controller]")] public class ChaptersController(MangaContext context) : Controller { /// /// Returns all of with /// /// .Key /// /// with not found [HttpGet("{MangaId}")] [ProducesResponseType>(Status200OK, "application/json")] [ProducesResponseType(Status404NotFound)] public async Task>, NotFound>> GetChapters(string MangaId) { if(await context.Chapters.Include(ch => ch.MangaConnectorIds) .Where(ch => ch.ParentMangaId == MangaId) .ToListAsync(HttpContext.RequestAborted) is not { } dbChapters) return TypedResults.NotFound(nameof(MangaId)); List chapters = dbChapters.OrderDescending().Select(c => { IEnumerable ids = c.MangaConnectorIds.Select(id => new MangaConnectorId(id.Key, id.MangaConnectorName, id.ObjId, id.WebsiteUrl, id.UseForDownload)); return new Chapter(c.Key, c.ParentMangaId, c.VolumeNumber, c.ChapterNumber, c.Title, ids, c.Downloaded, c.FileName); }).ToList(); return TypedResults.Ok(chapters); } /// /// Returns all downloaded for with /// /// .Key /// /// with not found. [HttpGet("{MangaId}/Downloaded")] [ProducesResponseType(Status200OK, "application/json")] [ProducesResponseType(Status404NotFound, "text/plain")] public async Task>, NotFound>> GetChaptersDownloaded(string MangaId) { if(await context.Chapters.Include(ch => ch.MangaConnectorIds) .Where(ch => ch.ParentMangaId == MangaId && ch.Downloaded) .ToListAsync(HttpContext.RequestAborted) is not { } dbChapters) return TypedResults.NotFound(nameof(MangaId)); List chapters = dbChapters.OrderDescending().Select(c => { IEnumerable ids = c.MangaConnectorIds.Select(id => new MangaConnectorId(id.Key, id.MangaConnectorName, id.ObjId, id.WebsiteUrl, id.UseForDownload)); return new Chapter(c.Key, c.ParentMangaId, c.VolumeNumber, c.ChapterNumber, c.Title, ids, c.Downloaded, c.FileName); }).ToList(); return TypedResults.Ok(chapters); } /// /// Returns all not downloaded for with /// /// .Key /// /// with not found. [HttpGet("{MangaId}/NotDownloaded")] [ProducesResponseType>(Status200OK, "application/json")] [ProducesResponseType(Status404NotFound, "text/plain")] public async Task>, NoContent, NotFound>> GetChaptersNotDownloaded(string MangaId) { if(await context.Chapters.Include(ch => ch.MangaConnectorIds) .Where(ch => ch.ParentMangaId == MangaId && ch.Downloaded == false) .ToListAsync(HttpContext.RequestAborted) is not { } dbChapters) return TypedResults.NotFound(nameof(MangaId)); List chapters = dbChapters.OrderDescending().Select(c => { IEnumerable ids = c.MangaConnectorIds.Select(id => new MangaConnectorId(id.Key, id.MangaConnectorName, id.ObjId, id.WebsiteUrl, id.UseForDownload)); return new Chapter(c.Key, c.ParentMangaId, c.VolumeNumber, c.ChapterNumber, c.Title, ids, c.Downloaded, c.FileName); }).ToList(); return TypedResults.Ok(chapters); } /// /// Returns the latest of requested /// /// .Key /// /// No available chapters /// with not found. [HttpGet("{MangaId}/LatestAvailable")] [ProducesResponseType(Status200OK, "application/json")] [ProducesResponseType(Status204NoContent)] [ProducesResponseType(Status404NotFound, "text/plain")] public async Task, NoContent, NotFound>> GetLatestChapter(string MangaId) { if(await context.Chapters.Include(ch => ch.MangaConnectorIds) .Where(ch => ch.ParentMangaId == MangaId) .ToListAsync(HttpContext.RequestAborted) is not { } dbChapters) return TypedResults.NotFound(nameof(MangaId)); Schema.MangaContext.Chapter? c = dbChapters.Max(); if (c is null) return TypedResults.NoContent(); IEnumerable ids = c.MangaConnectorIds.Select(id => new MangaConnectorId(id.Key, id.MangaConnectorName, id.ObjId, id.WebsiteUrl, id.UseForDownload)); return TypedResults.Ok(new Chapter(c.Key, c.ParentMangaId, c.VolumeNumber, c.ChapterNumber, c.Title, ids, c.Downloaded, c.FileName)); } /// /// Returns the latest of requested that is downloaded /// /// .Key /// /// No available chapters /// with not found. /// Could not retrieve the maximum chapter-number /// Retry after timeout, updating value [HttpGet("{MangaId}/LatestDownloaded")] [ProducesResponseType(Status200OK, "application/json")] [ProducesResponseType(Status204NoContent)] [ProducesResponseType(Status404NotFound, "text/plain")] [ProducesResponseType(Status412PreconditionFailed)] [ProducesResponseType(Status503ServiceUnavailable)] public async Task, NoContent, NotFound, StatusCodeHttpResult>> GetLatestChapterDownloaded(string MangaId) { if(await context.Chapters.Include(ch => ch.MangaConnectorIds) .Where(ch => ch.ParentMangaId == MangaId && ch.Downloaded) .ToListAsync(HttpContext.RequestAborted) is not { } dbChapters) return TypedResults.NotFound(nameof(MangaId)); Schema.MangaContext.Chapter? c = dbChapters.Max(); if (c is null) return TypedResults.NoContent(); IEnumerable ids = c.MangaConnectorIds.Select(id => new MangaConnectorId(id.Key, id.MangaConnectorName, id.ObjId, id.WebsiteUrl, id.UseForDownload)); return TypedResults.Ok(new Chapter(c.Key, c.ParentMangaId, c.VolumeNumber, c.ChapterNumber, c.Title, ids, c.Downloaded, c.FileName)); } /// /// Configure the cut-off for /// /// .Key /// Threshold ( ChapterNumber) /// /// with not found. /// Error during Database Operation [HttpPatch("{MangaId}/IgnoreBefore")] [ProducesResponseType(Status200OK)] [ProducesResponseType(Status404NotFound, "text/plain")] [ProducesResponseType(Status500InternalServerError, "text/plain")] public async Task, InternalServerError>> IgnoreChaptersBefore(string MangaId, [FromBody]float chapterThreshold) { if (await context.Mangas.FirstOrDefaultAsync(m => m.Key == MangaId, HttpContext.RequestAborted) is not { } manga) return TypedResults.NotFound(nameof(MangaId)); manga.IgnoreChaptersBefore = chapterThreshold; if(await context.Sync(HttpContext.RequestAborted, GetType(), System.Reflection.MethodBase.GetCurrentMethod()?.Name) is { success: false } result) return TypedResults.InternalServerError(result.exceptionMessage); return TypedResults.Ok(); } /// /// Returns with /// /// .Key /// /// with not found [HttpGet("WithId/{ChapterId}")] [ProducesResponseType(Status200OK, "application/json")] [ProducesResponseType(Status404NotFound, "text/plain")] public async Task, NotFound>> GetChapter (string ChapterId) { if (await context.Chapters.FirstOrDefaultAsync(c => c.Key == ChapterId, HttpContext.RequestAborted) is not { } chapter) return TypedResults.NotFound(nameof(ChapterId)); IEnumerable ids = chapter.MangaConnectorIds.Select(id => new MangaConnectorId(id.Key, id.MangaConnectorName, id.ObjId, id.WebsiteUrl, id.UseForDownload)); return TypedResults.Ok(new Chapter(chapter.Key, chapter.ParentMangaId, chapter.VolumeNumber, chapter.ChapterNumber, chapter.Title,ids, chapter.Downloaded, chapter.FileName)); } /// /// Returns the with .Key /// /// Key of /// /// with not found [HttpGet("ConnectorId/{MangaConnectorIdId}")] [ProducesResponseType(Status200OK, "application/json")] [ProducesResponseType(Status404NotFound, "text/plain")] public async Task, NotFound>> GetChapterMangaConnectorId (string MangaConnectorIdId) { if (await context.MangaConnectorToChapter.FirstOrDefaultAsync(c => c.Key == MangaConnectorIdId, HttpContext.RequestAborted) is not { } mcIdManga) return TypedResults.NotFound(nameof(MangaConnectorIdId)); MangaConnectorId result = new (mcIdManga.Key, mcIdManga.MangaConnectorName, mcIdManga.ObjId, mcIdManga.WebsiteUrl, mcIdManga.UseForDownload); return TypedResults.Ok(result); } }