using API.APIEndpointRecords; using API.Schema; using API.Schema.Jobs; using Asp.Versioning; using Microsoft.AspNetCore.Mvc; using static Microsoft.AspNetCore.Http.StatusCodes; namespace API.Controllers; [ApiVersion(2)] [ApiController] [Route("v{version:apiVersion}/[controller]")] public class JobController(PgsqlContext context) : Controller { /// /// Returns all Jobs /// /// [HttpGet] [ProducesResponseType(Status200OK, "application/json")] public IActionResult GetAllJobs() { Job[] ret = context.Jobs.ToArray(); return Ok(ret); } /// /// Returns Jobs with requested Job-IDs /// /// Array of Job-IDs /// [HttpPost("WithIDs")] [ProducesResponseType(Status200OK, "application/json")] public IActionResult GetJobs([FromBody]string[] ids) { Job[] ret = context.Jobs.Where(job => ids.Contains(job.JobId)).ToArray(); return Ok(ret); } /// /// Get all Jobs in requested State /// /// Requested Job-State /// [HttpGet("State/{JobState}")] [ProducesResponseType(Status200OK, "application/json")] public IActionResult GetJobsInState(JobState JobState) { Job[] jobsInState = context.Jobs.Where(job => job.state == JobState).ToArray(); return Ok(jobsInState); } /// /// Returns all Jobs of requested Type /// /// Requested Job-Type /// [HttpGet("Type/{JobType}")] [ProducesResponseType(Status200OK, "application/json")] public IActionResult GetJobsOfType(JobType JobType) { Job[] jobsOfType = context.Jobs.Where(job => job.JobType == JobType).ToArray(); return Ok(jobsOfType); } /// /// Returns all Jobs of requested Type and State /// /// Requested Job-Type /// Requested Job-State /// [HttpGet("TypeAndState/{JobType}/{JobState}")] [ProducesResponseType(Status200OK, "application/json")] public IActionResult GetJobsOfType(JobType JobType, JobState JobState) { Job[] jobsOfType = context.Jobs.Where(job => job.JobType == JobType && job.state == JobState).ToArray(); return Ok(jobsOfType); } /// /// Return Job with ID /// /// Job-ID /// /// Job with ID could not be found [HttpGet("{JobId}")] [ProducesResponseType(Status200OK, "application/json")] [ProducesResponseType(Status404NotFound)] public IActionResult GetJob(string JobId) { Job? ret = context.Jobs.Find(JobId); return (ret is not null) switch { true => Ok(ret), false => NotFound() }; } /// /// Create a new DownloadAvailableChaptersJob /// /// ID of Manga /// Job-Configuration /// Job-IDs /// Could not find Library with ID /// Could not find Manga with ID /// Error during Database Operation [HttpPut("DownloadAvailableChaptersJob/{MangaId}")] [ProducesResponseType(Status201Created, "application/json")] [ProducesResponseType(Status400BadRequest)] [ProducesResponseType(Status404NotFound)] [ProducesResponseType(Status500InternalServerError, "text/plain")] public IActionResult CreateDownloadAvailableChaptersJob(string MangaId, [FromBody]DownloadAvailableJobsRecord record) { if (context.Mangas.Find(MangaId) is not { } m) return NotFound(); else { try { LocalLibrary? l = context.LocalLibraries.Find(record.localLibraryId); if (l is null) return BadRequest(); m.Library = l; context.SaveChanges(); } catch (Exception e) { return StatusCode(500, e.Message); } } Job dep = new RetrieveChaptersJob(record.recurrenceTimeMs, MangaId); Job job = new DownloadAvailableChaptersJob(record.recurrenceTimeMs, MangaId, null, [dep.JobId]); return AddJobs([dep, job]); } /// /// Create a new DownloadSingleChapterJob /// /// ID of the Chapter /// Job-IDs /// Could not find Chapter with ID /// Error during Database Operation [HttpPut("DownloadSingleChapterJob/{ChapterId}")] [ProducesResponseType(Status201Created, "application/json")] [ProducesResponseType(Status404NotFound)] [ProducesResponseType(Status500InternalServerError, "text/plain")] public IActionResult CreateNewDownloadChapterJob(string ChapterId) { if(context.Chapters.Find(ChapterId) is null) return NotFound(); Job job = new DownloadSingleChapterJob(ChapterId); return AddJobs([job]); } /// /// Create a new UpdateFilesDownloadedJob /// /// ID of the Manga /// Job-IDs /// Could not find Manga with ID /// Error during Database Operation [HttpPut("UpdateFilesJob/{MangaId}")] [ProducesResponseType(Status201Created, "application/json")] [ProducesResponseType(Status404NotFound)] [ProducesResponseType(Status500InternalServerError, "text/plain")] public IActionResult CreateUpdateFilesDownloadedJob(string MangaId) { if(context.Mangas.Find(MangaId) is null) return NotFound(); Job job = new UpdateFilesDownloadedJob(0, MangaId); return AddJobs([job]); } /// /// Create a new UpdateMetadataJob for all Manga /// /// Job-IDs /// Error during Database Operation [HttpPut("UpdateAllFilesJob")] [ProducesResponseType(Status201Created, "application/json")] [ProducesResponseType(Status500InternalServerError, "text/plain")] public IActionResult CreateUpdateAllFilesDownloadedJob() { List ids = context.Mangas.Select(m => m.MangaId).ToList(); List jobs = ids.Select(id => new UpdateFilesDownloadedJob(0, id)).ToList(); try { context.Jobs.AddRange(jobs); context.SaveChanges(); return Created(); } catch (Exception e) { return StatusCode(500, e.Message); } } /// /// Create a new UpdateMetadataJob /// /// ID of the Manga /// Job-IDs /// Could not find Manga with ID /// Error during Database Operation [HttpPut("UpdateMetadataJob/{MangaId}")] [ProducesResponseType(Status201Created, "application/json")] [ProducesResponseType(Status404NotFound)] [ProducesResponseType(Status500InternalServerError, "text/plain")] public IActionResult CreateUpdateMetadataJob(string MangaId) { if(context.Mangas.Find(MangaId) is null) return NotFound(); Job job = new UpdateMetadataJob(0, MangaId); return AddJobs([job]); } /// /// Create a new UpdateMetadataJob for all Manga /// /// Job-IDs /// Error during Database Operation [HttpPut("UpdateAllMetadataJob")] [ProducesResponseType(Status201Created, "application/json")] [ProducesResponseType(Status500InternalServerError, "text/plain")] public IActionResult CreateUpdateAllMetadataJob() { List ids = context.Mangas.Select(m => m.MangaId).ToList(); List jobs = ids.Select(id => new UpdateMetadataJob(0, id)).ToList(); try { context.Jobs.AddRange(jobs); context.SaveChanges(); return Created(); } catch (Exception e) { return StatusCode(500, e.Message); } } private IActionResult AddJobs(Job[] jobs) { try { context.Jobs.AddRange(jobs); context.SaveChanges(); return new CreatedResult((string?)null, jobs.Select(j => j.JobId).ToArray()); } catch (Exception e) { return StatusCode(500, e.Message); } } /// /// Delete Job with ID and all children /// /// Job-ID /// Job(s) deleted /// Job could not be found /// Error during Database Operation [HttpDelete("{JobId}")] [ProducesResponseType(Status200OK, "application/json")] [ProducesResponseType(Status404NotFound)] [ProducesResponseType(Status500InternalServerError, "text/plain")] public IActionResult DeleteJob(string JobId) { try { Job? ret = context.Jobs.Find(JobId); if(ret is null) return NotFound(); IQueryable children = GetChildJobs(JobId); context.RemoveRange(children); context.Remove(ret); context.SaveChanges(); return new OkObjectResult(children.Select(x => x.JobId).Append(ret.JobId).ToArray()); } catch (Exception e) { return StatusCode(500, e.Message); } } private IQueryable GetChildJobs(string parentJobId) { IQueryable children = context.Jobs.Where(j => j.ParentJobId == parentJobId); foreach (Job child in children) foreach (Job grandChild in GetChildJobs(child.JobId)) children.Append(grandChild); return children; } /// /// Modify Job with ID /// /// Job-ID /// Fields to modify, set to null to keep previous value /// Job modified /// Malformed request /// Job with ID not found /// Error during Database Operation [HttpPatch("{JobId}")] [ProducesResponseType(Status202Accepted, "application/json")] [ProducesResponseType(Status400BadRequest)] [ProducesResponseType(Status404NotFound)] [ProducesResponseType(Status500InternalServerError, "text/plain")] public IActionResult ModifyJob(string JobId, [FromBody]ModifyJobRecord modifyJobRecord) { try { Job? ret = context.Jobs.Find(JobId); if(ret is null) return NotFound(); ret.RecurrenceMs = modifyJobRecord.RecurrenceMs ?? ret.RecurrenceMs; ret.Enabled = modifyJobRecord.Enabled ?? ret.Enabled; context.SaveChanges(); return new AcceptedResult(ret.JobId, ret); } catch (Exception e) { return StatusCode(500, e.Message); } } /// /// Starts the Job with the requested ID /// /// Job-ID /// Job started /// Job with ID not found /// Job was already running /// Error during Database Operation [HttpPost("{JobId}/Start")] [ProducesResponseType(Status202Accepted)] [ProducesResponseType(Status404NotFound)] [ProducesResponseType(Status409Conflict)] [ProducesResponseType(Status500InternalServerError, "text/plain")] public IActionResult StartJob(string JobId) { Job? ret = context.Jobs.Find(JobId); if (ret is null) return NotFound(); try { if (ret.state >= JobState.Running && ret.state < JobState.Completed) return new ConflictResult(); ret.LastExecution = DateTime.UnixEpoch; context.SaveChanges(); return Accepted(); } catch (Exception e) { return StatusCode(500, e.Message); } } /// /// Stops the Job with the requested ID /// /// Job-ID ///

NOT IMPLEMENTED

[HttpPost("{JobId}/Stop")] [ProducesResponseType(Status501NotImplemented)] public IActionResult StopJob(string JobId) { return StatusCode(501); } }