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);
}
}