87 lines
4.6 KiB
C#
87 lines
4.6 KiB
C#
using JobTrackerApi.Data;
|
|
using JobTrackerApi.Models;
|
|
using JobTrackerApi.Services;
|
|
using Microsoft.AspNetCore.Authorization;
|
|
using Microsoft.AspNetCore.Mvc;
|
|
using Microsoft.EntityFrameworkCore;
|
|
|
|
namespace JobTrackerApi.Controllers;
|
|
|
|
[ApiController]
|
|
[Route("api/jobapplications/{jobId:int}/focus-plan")]
|
|
[Authorize(AuthenticationSchemes = "local", Policy = ProEntitlement.Policy)]
|
|
public sealed class StrategySnapshotController(
|
|
JobTrackerContext db,
|
|
AiOperationAdmission admission,
|
|
StrategySnapshotService snapshots) : ControllerBase
|
|
{
|
|
[HttpGet]
|
|
public async Task<ActionResult<FocusPlanDto>> Get(int jobId, [FromQuery] string? attachmentIds, CancellationToken cancellationToken)
|
|
{
|
|
IReadOnlyList<int> ids;
|
|
try { ids = StrategySnapshotService.ParseAttachmentIds(attachmentIds); }
|
|
catch (StrategySnapshotValidationException exception) { return Problem(exception); }
|
|
var result = await snapshots.GetCachedAsync(jobId, StrategySnapshotService.NormalizeAttachmentIds(ids), cancellationToken);
|
|
return result is null
|
|
? NotFound(new { code = "strategy_not_generated", message = "No strategy snapshot has been generated for this context." })
|
|
: Ok(result);
|
|
}
|
|
|
|
[HttpPost("operations")]
|
|
public async Task<IActionResult> Enqueue(int jobId, [FromBody] StrategySnapshotRequest? request, CancellationToken cancellationToken)
|
|
{
|
|
try
|
|
{
|
|
var ids = StrategySnapshotService.ParseAttachmentIds(request?.AttachmentIds);
|
|
await snapshots.ValidateRequestAsync(jobId, ids, cancellationToken);
|
|
var signature = StrategySnapshotService.NormalizeAttachmentIds(ids);
|
|
var subject = StrategySnapshotService.EncodeSubject(jobId, ids);
|
|
var activeStatuses = new[] { OperationStatuses.Queued, OperationStatuses.Running, OperationStatuses.WaitingForRetry, OperationStatuses.WaitingForExternalFallback };
|
|
var active = await db.UserOperations.AsNoTracking()
|
|
.Where(item => item.TaskType == StrategySnapshotService.TaskType && item.SubjectId == subject && activeStatuses.Contains(item.Status))
|
|
.OrderByDescending(item => item.CreatedAtUtc)
|
|
.FirstOrDefaultAsync(cancellationToken);
|
|
if (active is not null)
|
|
{
|
|
var statusUrl = $"/api/operations/{active.Id:D}";
|
|
return Accepted(statusUrl, new StrategySnapshotOperationResponse(OperationDto.From(active), statusUrl, false));
|
|
}
|
|
var key = await snapshots.BuildIdempotencyKeyAsync(jobId, signature, cancellationToken);
|
|
var result = await admission.EnqueueAsync(
|
|
StrategySnapshotService.TaskType,
|
|
key,
|
|
"job_strategy",
|
|
subject,
|
|
AiOperationPriorities.Interactive,
|
|
cancellationToken);
|
|
return Accepted(result.StatusUrl, new StrategySnapshotOperationResponse(OperationDto.From(result.Operation), result.StatusUrl, result.Created));
|
|
}
|
|
catch (StrategySnapshotValidationException exception) { return Problem(exception); }
|
|
catch (AiOperationAdmissionException exception)
|
|
{
|
|
if (exception.RetryAfterSeconds is int seconds) Response.Headers.RetryAfter = seconds.ToString();
|
|
return StatusCode(exception.StatusCode, new { code = exception.Code, message = exception.Message });
|
|
}
|
|
}
|
|
|
|
[HttpGet("operation")]
|
|
public async Task<ActionResult<OperationDto>> LatestOperation(int jobId, [FromQuery] string? attachmentIds, CancellationToken cancellationToken)
|
|
{
|
|
IReadOnlyList<int> ids;
|
|
try { ids = StrategySnapshotService.ParseAttachmentIds(attachmentIds); }
|
|
catch (StrategySnapshotValidationException exception) { return Problem(exception); }
|
|
var subject = StrategySnapshotService.EncodeSubject(jobId, ids);
|
|
var operation = await db.UserOperations.AsNoTracking()
|
|
.Where(item => item.TaskType == StrategySnapshotService.TaskType && item.SubjectId == subject)
|
|
.OrderByDescending(item => item.CreatedAtUtc)
|
|
.FirstOrDefaultAsync(cancellationToken);
|
|
return operation is null ? NotFound() : Ok(OperationDto.From(operation));
|
|
}
|
|
|
|
private ObjectResult Problem(StrategySnapshotValidationException exception) =>
|
|
StatusCode(exception.StatusCode, new { code = exception.Code, message = exception.Message });
|
|
}
|
|
|
|
public sealed record StrategySnapshotRequest(string? AttachmentIds);
|
|
public sealed record StrategySnapshotOperationResponse(OperationDto Operation, string StatusUrl, bool Created);
|