Files
MilkyShots/Lactose/Controllers/JobsController.cs

155 lines
6.8 KiB
C#

using Butter.Dtos;
using Butter.Dtos.Jobs;
using Butter.Types;
using Lactose.Jobs;
using Lactose.Services;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
namespace Lactose.Controllers;
/// <summary>
/// Manages job lifecycle — listing active/past jobs, cancelling, and enqueuing new jobs.
/// </summary>
/// <param name="jobManager">The job manager for job lifecycle.</param>
/// <param name="jobScheduler">The job scheduler for queueing jobs.</param>
/// <param name="authService">The authentication service.</param>
[ApiController]
[Authorize(Roles = "Admin")]
[Route("api/[controller]")]
public class JobsController(JobManager jobManager, JobScheduler jobScheduler, LactoseAuthService authService) : ControllerBase {
/// <summary>
/// Returns active root jobs only — no children, no past jobs. Lightweight, polled frequently.
/// </summary>
[HttpGet]
[Authorize(Roles = "Admin")]
public ActionResult<List<JobStatusDto>> GetActiveRoots() {
var accessLevel = authService.GetUserData(User)?.AccessLevel ?? EAccessLevel.User;
if (accessLevel != EAccessLevel.Admin) { return Unauthorized(); }
return Ok(jobManager.GetActiveRootJobs());
}
/// <summary>
/// Returns a page of past root jobs with total count. Polled less frequently or on demand.
/// </summary>
[HttpGet("past")]
[Authorize(Roles = "Admin")]
public ActionResult<PastJobsResponse> GetPast([FromQuery] int page = 0, [FromQuery] int pageSize = 5) {
if (page < 0 || pageSize < 1 || pageSize > PagedParametersDto.MaxPageSize) { return BadRequest(); }
var accessLevel = authService.GetUserData(User)?.AccessLevel ?? EAccessLevel.User;
if (accessLevel != EAccessLevel.Admin) { return Unauthorized(); }
var (jobs, total) = jobManager.GetPastRootJobs(page, pageSize);
return Ok(new PastJobsResponse { Jobs = jobs, Total = total });
}
/// <summary>
/// Returns children of a specific parent job (active + past).
/// Supports delta sync — pass <paramref name="since"/> to receive only children changed after that timestamp.
/// The <see cref="ChildrenResponse.Since"/> field contains the timestamp to use for the next delta poll.
/// </summary>
[HttpGet("{parentId}/children")]
[Authorize(Roles = "Admin")]
public ActionResult<ChildrenResponse> GetChildren(Guid parentId, [FromQuery] DateTime? since) {
var accessLevel = authService.GetUserData(User)?.AccessLevel ?? EAccessLevel.User;
if (accessLevel != EAccessLevel.Admin) { return Unauthorized(); }
var children = jobManager.GetChildren(parentId, since);
return Ok(new ChildrenResponse { Children = children, Since = DateTime.UtcNow });
}
/// <summary>
/// Returns the status of a specific job by its ID.
/// </summary>
/// <param name="jobId">The ID of the job.</param>
/// <returns>The job status, or 404 if not found.</returns>
[HttpGet("{jobId}")]
[Authorize(Roles = "Admin")]
public ActionResult<JobStatusDto> Get(Guid jobId) {
var accessLevel = authService.GetUserData(User)?.AccessLevel ?? EAccessLevel.User;
if (accessLevel != EAccessLevel.Admin) { return Unauthorized(); }
var job = jobManager.GetJobStatus(jobId);
if (job == null) { return NotFound(); }
return Ok(job);
}
/// <summary>
/// Cancels a running or queued job.
/// </summary>
/// <param name="jobId">The ID of the job to cancel.</param>
/// <returns>200 if cancelled, 404 if not found.</returns>
[HttpDelete("{jobId}")]
[Authorize(Roles = "Admin")]
public ActionResult Cancel(Guid jobId) {
var accessLevel = authService.GetUserData(User)?.AccessLevel ?? EAccessLevel.User;
if (accessLevel != EAccessLevel.Admin) { return Unauthorized(); }
var job = jobManager.GetJob(jobId);
if (job == null) { return NotFound(); }
job.Cancel();
return Ok();
}
/// <summary>
/// Enqueues a new job of the specified type.
/// </summary>
/// <param name="jobRequest">The job request containing the job type and optional parameters.</param>
/// <returns>200 if enqueued, 400 if invalid request, 501 if not implemented.</returns>
[HttpPut]
[Authorize(Roles = "Admin")]
public ActionResult Enqueue([FromBody] JobRequestDto jobRequest) {
var accessLevel = authService.GetUserData(User)?.AccessLevel ?? EAccessLevel.User;
if (accessLevel != EAccessLevel.Admin) { return Unauthorized(); }
switch (jobRequest.JobType) {
case EJobType.FileSystemScan:
if (jobRequest.Parameters.Count != 0)
return BadRequest("FullFileSystemScan job does not require any parameters.");
jobScheduler.QueueFileSystemCrawl(null);
return Ok();
case EJobType.ThumbnailGeneration:
if (jobRequest.Parameters.Count != 0)
return BadRequest("ThumbnailGeneration job does not require any parameters.");
jobScheduler.QueueThumbnailGeneration(null);
return Ok();
case EJobType.PreviewGeneration:
if (jobRequest.Parameters.Count != 0)
return BadRequest("PreviewGeneration job does not require any parameters.");
jobScheduler.QueuePreviewGeneration(null);
return Ok();
case EJobType.MetadataExtraction:
if (jobRequest.Parameters.Count != 0)
return BadRequest("MetadataExtraction job does not require any parameters.");
jobScheduler.QueueMetadataExtraction(null);
return Ok();
case EJobType.IntegrityCheck:
if (jobRequest.Parameters.Count != 0)
return BadRequest("IntegrityCheck job does not require any parameters.");
jobScheduler.QueueIntegrityCheck(null);
return Ok();
case EJobType.CreatePersons:
if (jobRequest.Parameters.Count != 0)
return BadRequest("CreatePersons job does not require any parameters.");
jobScheduler.QueueCreatePersons(null);
return Ok();
case EJobType.CreateAlbums:
if (jobRequest.Parameters.Count != 0)
return BadRequest("CreateAlbums job does not require any parameters.");
jobScheduler.QueueCreateAlbums(null);
return Ok();
case EJobType.PHashGeneration:
if (jobRequest.Parameters.Count > 1)
return BadRequest("PHashGeneration job takes zero or one parameter (the asset ID).");
jobScheduler.QueuePHashGeneration(jobRequest.Parameters.FirstOrDefault());
return Ok();
default: return BadRequest("Invalid job type.");
}
}
}