Files
jobtrackingapp/JobTrackerApi/Controllers/OperationsController.cs
T

130 lines
5.0 KiB
C#

using JobTrackerApi.Models;
using JobTrackerApi.Services;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
namespace JobTrackerApi.Controllers;
[ApiController]
[Route("api/operations")]
[Authorize(AuthenticationSchemes = "local")]
public sealed class OperationsController(UserOperationStore operations) : ControllerBase
{
[HttpGet]
public async Task<ActionResult<IReadOnlyList<OperationDto>>> List([FromQuery] int limit = 25, CancellationToken cancellationToken = default)
{
if (limit is < 1 or > 100) return BadRequest(new { code = "invalid_limit", message = "Limit must be between 1 and 100." });
return Ok((await operations.ListAsync(limit, cancellationToken)).Select(ToDto).ToList());
}
[HttpGet("{id:guid}")]
public async Task<ActionResult<OperationDto>> Get(Guid id, CancellationToken cancellationToken)
{
var operation = await operations.GetAsync(id, cancellationToken);
return operation is null ? NotFound() : Ok(ToDto(operation));
}
[HttpPost("{id:guid}/cancel")]
public async Task<ActionResult<OperationDto>> Cancel(Guid id, CancellationToken cancellationToken)
{
if (await operations.GetAsync(id, cancellationToken) is null) return NotFound();
if (!await operations.RequestCancellationAsync(id, cancellationToken))
return Conflict(new { code = "operation_not_cancellable", message = "This operation can no longer be cancelled." });
return Ok(ToDto((await operations.GetAsync(id, cancellationToken))!));
}
[HttpPost("{id:guid}/retry")]
public async Task<ActionResult<OperationDto>> Retry(Guid id, CancellationToken cancellationToken)
{
if (await operations.GetAsync(id, cancellationToken) is null) return NotFound();
if (!await operations.RetryAsync(id, cancellationToken))
return Conflict(new { code = "operation_not_retryable", message = "Only failed or cancelled operations can be retried." });
return Ok(ToDto((await operations.GetAsync(id, cancellationToken))!));
}
private static OperationDto ToDto(UserOperation operation) => new(
operation.Id,
operation.TaskType,
operation.Status,
operation.SubjectType,
operation.CreatedAtUtc,
operation.StartedAtUtc,
operation.CompletedAtUtc,
operation.DeadlineAtUtc,
operation.CancellationRequestedAtUtc,
operation.ProgressStage,
operation.ProgressPercent,
operation.FailureCategory,
!OperationStatuses.IsTerminal(operation.Status) && operation.CancellationRequestedAtUtc is null,
operation.Status is OperationStatuses.Failed or OperationStatuses.Cancelled);
}
public sealed record OperationDto(
Guid Id,
string TaskType,
string Status,
string? SubjectType,
DateTime CreatedAtUtc,
DateTime? StartedAtUtc,
DateTime? CompletedAtUtc,
DateTime? DeadlineAtUtc,
DateTime? CancellationRequestedAtUtc,
string? ProgressStage,
int? ProgressPercent,
string? FailureCategory,
bool CanCancel,
bool CanRetry);
[ApiController]
[Route("api/notifications")]
[Authorize(AuthenticationSchemes = "local")]
public sealed class NotificationsController(UserNotificationStore notifications) : ControllerBase
{
[HttpGet]
public async Task<ActionResult<IReadOnlyList<NotificationDto>>> List([FromQuery] int limit = 25, CancellationToken cancellationToken = default)
{
if (limit is < 1 or > 100) return BadRequest(new { code = "invalid_limit", message = "Limit must be between 1 and 100." });
return Ok((await notifications.ListAsync(limit, cancellationToken)).Select(ToDto).ToList());
}
[HttpGet("unread-count")]
public async Task<IActionResult> UnreadCount(CancellationToken cancellationToken) =>
Ok(new { count = await notifications.UnreadCountAsync(cancellationToken) });
[HttpPost("{id:guid}/read")]
public async Task<IActionResult> MarkRead(Guid id, CancellationToken cancellationToken)
{
if (await notifications.GetAsync(id, cancellationToken) is null) return NotFound();
await notifications.MarkReadAsync(id, cancellationToken);
return NoContent();
}
[HttpDelete("{id:guid}")]
public async Task<IActionResult> Dismiss(Guid id, CancellationToken cancellationToken)
{
if (await notifications.GetAsync(id, cancellationToken) is null) return NotFound();
await notifications.DismissAsync(id, cancellationToken);
return NoContent();
}
private static NotificationDto ToDto(UserNotification notification) => new(
notification.Id,
notification.OperationId,
notification.Kind,
notification.Title,
notification.Message,
notification.LinkPath,
notification.CreatedAtUtc,
notification.ReadAtUtc);
}
public sealed record NotificationDto(
Guid Id,
Guid? OperationId,
string Kind,
string Title,
string Message,
string? LinkPath,
DateTime CreatedAtUtc,
DateTime? ReadAtUtc);