using API.Schema; using API.Schema.Jobs; using API.Schema.MangaConnectors; using Asp.Versioning; using Microsoft.AspNetCore.Mvc; using static Microsoft.AspNetCore.Http.StatusCodes; namespace API.Controllers; [ApiVersion(2)] [ApiController] [Route("v{v:apiVersion}/[controller]")] public class SearchController(PgsqlContext context) : Controller { /// /// Initiate a search for a Manga on all Connectors /// /// Name/Title of the Manga /// /// Error during Database Operation [HttpPost("Name")] [ProducesResponseType(Status200OK, "application/json")] [ProducesResponseType(Status500InternalServerError, "text/plain")] public IActionResult SearchMangaGlobal([FromBody]string name) { List<(Manga, List?, List?, List?, List?)> allManga = new(); foreach (MangaConnector contextMangaConnector in context.MangaConnectors.Where(connector => connector.Enabled)) allManga.AddRange(contextMangaConnector.GetManga(name)); List retMangas = new(); foreach ((Manga? manga, List? authors, List? tags, List? links, List? altTitles) in allManga) { try { Manga? add = AddMangaToContext(manga, authors, tags, links, altTitles); if(add is not null) retMangas.Add(add); } catch (Exception e) { return StatusCode(500, e); } } return Ok(retMangas.ToArray()); } /// /// Initiate a search for a Manga on a specific Connector /// /// Manga-Connector-ID /// Name/Title of the Manga /// /// MangaConnector with ID not found /// MangaConnector with ID is disabled /// Error during Database Operation [HttpPost("{MangaConnectorName}")] [ProducesResponseType(Status200OK, "application/json")] [ProducesResponseType(Status404NotFound)] [ProducesResponseType(Status406NotAcceptable)] [ProducesResponseType(Status500InternalServerError, "text/plain")] public IActionResult SearchManga(string MangaConnectorName, [FromBody]string name) { MangaConnector? connector = context.MangaConnectors.Find(MangaConnectorName); if (connector is null) return NotFound(); else if (connector.Enabled is false) return StatusCode(406); (Manga, List?, List?, List?, List?)[] mangas = connector.GetManga(name); List retMangas = new(); foreach ((Manga? manga, List? authors, List? tags, List? links, List? altTitles) in mangas) { try { Manga? add = AddMangaToContext(manga, authors, tags, links, altTitles); if(add is not null) retMangas.Add(add); } catch (Exception e) { return StatusCode(500, e.Message); } } return Ok(retMangas.ToArray()); } /// /// Returns Manga from MangaConnector associated with URL /// /// Manga-Page URL /// /// Multiple connectors found for URL /// No Manga at URL /// No connector found for URL /// Error during Database Operation [HttpPost("Url")] [ProducesResponseType(Status200OK, "application/json")] [ProducesResponseType(Status300MultipleChoices)] [ProducesResponseType(Status400BadRequest)] [ProducesResponseType(Status404NotFound)] [ProducesResponseType(Status500InternalServerError, "text/plain")] public IActionResult GetMangaFromUrl([FromBody]string url) { List connectors = context.MangaConnectors.AsEnumerable().Where(c => c.ValidateUrl(url)).ToList(); if (connectors.Count == 0) return NotFound(); else if (connectors.Count > 1) return StatusCode(Status300MultipleChoices); (Manga manga, List? authors, List? tags, List? links, List? altTitles)? x = connectors.First().GetMangaFromUrl(url); if (x is null) return BadRequest(); try { Manga? add = AddMangaToContext(x.Value.manga, x.Value.authors, x.Value.tags, x.Value.links, x.Value.altTitles); if (add is not null) return Ok(add); return StatusCode(500); } catch (Exception e) { return StatusCode(500, e.Message); } } private Manga? AddMangaToContext(Manga? manga, List? authors, List? tags, List? links, List? altTitles) { if (manga is null) return null; Manga? existing = context.Mangas.Find(manga.MangaId); if (tags is not null) { IEnumerable mergedTags = tags.Select(mt => { MangaTag? inDb = context.Tags.Find(mt.Tag); return inDb ?? mt; }); manga.MangaTags = mergedTags.ToList(); IEnumerable newTags = manga.MangaTags .Where(mt => !context.Tags.Select(t => t.Tag).Contains(mt.Tag)); context.Tags.AddRange(newTags); } if (authors is not null) { IEnumerable mergedAuthors = authors.Select(ma => { Author? inDb = context.Authors.Find(ma.AuthorId); return inDb ?? ma; }); manga.Authors = mergedAuthors.ToList(); IEnumerable newAuthors = manga.Authors .Where(ma => !context.Authors.Select(a => a.AuthorId).Contains(ma.AuthorId)); context.Authors.AddRange(newAuthors); } if (links is not null) { IEnumerable mergedLinks = links.Select(ml => { Link? inDb = context.Links.Find(ml.LinkId); return inDb ?? ml; }); manga.Links = mergedLinks.ToList(); IEnumerable newLinks = manga.Links .Where(ml => !context.Links.Select(l => l.LinkId).Contains(ml.LinkId)); context.Links.AddRange(newLinks); } if (altTitles is not null) { IEnumerable mergedAltTitles = altTitles.Select(mat => { MangaAltTitle? inDb = context.AltTitles.Find(mat.AltTitleId); return inDb ?? mat; }); manga.AltTitles = mergedAltTitles.ToList(); IEnumerable newAltTitles = manga.AltTitles .Where(mat => !context.AltTitles.Select(at => at.AltTitleId).Contains(mat.AltTitleId)); context.AltTitles.AddRange(newAltTitles); } existing?.UpdateWithInfo(manga); if(existing is not null) context.Mangas.Update(existing); else context.Mangas.Add(manga); context.Jobs.Add(new DownloadMangaCoverJob(manga.MangaId)); context.Jobs.Add(new RetrieveChaptersJob(0, manga.MangaId)); context.SaveChanges(); return existing ?? manga; } }