refactor(jobs): extract lifecycle endpoints
This commit is contained in:
@@ -0,0 +1,122 @@
|
||||
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")]
|
||||
[Authorize(AuthenticationSchemes = "local")]
|
||||
public sealed class JobApplicationLifecycleController : ControllerBase
|
||||
{
|
||||
private readonly JobTrackerContext _db;
|
||||
|
||||
public JobApplicationLifecycleController(JobTrackerContext db)
|
||||
{
|
||||
_db = db;
|
||||
}
|
||||
|
||||
/// <summary>Canonical ordered pipeline stages so the UI renders one source of truth.</summary>
|
||||
[HttpGet("pipeline")]
|
||||
public ActionResult<IEnumerable<PipelineStageDto>> GetPipeline()
|
||||
=> Ok(JobPipeline.Stages.Select(stage => new PipelineStageDto(
|
||||
stage.Key,
|
||||
stage.Order,
|
||||
stage.Category.ToString(),
|
||||
stage.Group.ToString())));
|
||||
|
||||
[HttpDelete("{id:int}")]
|
||||
public async Task<IActionResult> SoftDelete([FromRoute] int id, CancellationToken cancellationToken)
|
||||
{
|
||||
var job = await _db.JobApplications.FirstOrDefaultAsync(item => item.Id == id, cancellationToken);
|
||||
if (job is null) return NotFound();
|
||||
|
||||
if (!job.IsDeleted)
|
||||
{
|
||||
job.IsDeleted = true;
|
||||
job.DeletedAt = DateTime.Now;
|
||||
_db.JobEvents.Add(new JobEvent
|
||||
{
|
||||
JobApplicationId = job.Id,
|
||||
Type = "Deleted",
|
||||
At = DateTime.Now
|
||||
});
|
||||
await _db.SaveChangesAsync(cancellationToken);
|
||||
}
|
||||
|
||||
return NoContent();
|
||||
}
|
||||
|
||||
[HttpPost("{id:int}/restore")]
|
||||
public async Task<IActionResult> Restore([FromRoute] int id, CancellationToken cancellationToken)
|
||||
{
|
||||
var job = await _db.JobApplications.FirstOrDefaultAsync(item => item.Id == id, cancellationToken);
|
||||
if (job is null) return NotFound();
|
||||
|
||||
if (job.IsDeleted)
|
||||
{
|
||||
job.IsDeleted = false;
|
||||
job.DeletedAt = null;
|
||||
_db.JobEvents.Add(new JobEvent
|
||||
{
|
||||
JobApplicationId = job.Id,
|
||||
Type = "Restored",
|
||||
At = DateTime.Now
|
||||
});
|
||||
await _db.SaveChangesAsync(cancellationToken);
|
||||
}
|
||||
|
||||
return NoContent();
|
||||
}
|
||||
|
||||
[HttpPatch("{id:int}/followup")]
|
||||
public async Task<IActionResult> SetFollowUp(
|
||||
[FromRoute] int id,
|
||||
[FromBody] FollowUpRequest request,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
var job = await _db.JobApplications.FirstOrDefaultAsync(item => item.Id == id, cancellationToken);
|
||||
if (job is null) return NotFound();
|
||||
|
||||
var old = job.FollowUpAt?.ToString("o");
|
||||
job.FollowUpAt = request.FollowUpAt;
|
||||
_db.JobEvents.Add(new JobEvent
|
||||
{
|
||||
JobApplicationId = job.Id,
|
||||
Type = "FollowUpSet",
|
||||
OldValue = old,
|
||||
NewValue = request.FollowUpAt?.ToString("o"),
|
||||
At = DateTime.Now
|
||||
});
|
||||
|
||||
await _db.SaveChangesAsync(cancellationToken);
|
||||
return NoContent();
|
||||
}
|
||||
|
||||
[HttpGet("{id:int}/history")]
|
||||
public async Task<ActionResult<List<JobEventDto>>> GetHistory(
|
||||
[FromRoute] int id,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
var exists = await _db.JobApplications.AnyAsync(item => item.Id == id, cancellationToken);
|
||||
if (!exists) return NotFound();
|
||||
|
||||
var items = await _db.JobEvents
|
||||
.AsNoTracking()
|
||||
.Where(item => item.JobApplicationId == id)
|
||||
.OrderByDescending(item => item.At)
|
||||
.Select(item => new JobEventDto(
|
||||
item.Id,
|
||||
item.Type,
|
||||
item.OldValue,
|
||||
item.NewValue,
|
||||
item.Note,
|
||||
item.At))
|
||||
.ToListAsync(cancellationToken);
|
||||
|
||||
return Ok(items);
|
||||
}
|
||||
}
|
||||
@@ -955,11 +955,6 @@ Canonical profile:
|
||||
return string.Join(' ', words);
|
||||
}
|
||||
|
||||
/// <summary>Canonical ordered pipeline stages so the UI renders one source of truth.</summary>
|
||||
[HttpGet("pipeline")]
|
||||
public ActionResult<IEnumerable<PipelineStageDto>> GetPipeline()
|
||||
=> Ok(JobPipeline.Stages.Select(s => new PipelineStageDto(s.Key, s.Order, s.Category.ToString(), s.Group.ToString())));
|
||||
|
||||
[HttpPatch("{id:int}/status")]
|
||||
public async Task<IActionResult> UpdateStatus([FromRoute] int id, [FromBody] UpdateStatusRequest request, CancellationToken cancellationToken)
|
||||
{
|
||||
@@ -1094,87 +1089,6 @@ Canonical profile:
|
||||
return Ok(BuildJobApplicationDto(job, followUp));
|
||||
}
|
||||
|
||||
[HttpDelete("{id:int}")]
|
||||
public async Task<IActionResult> SoftDelete([FromRoute] int id, CancellationToken cancellationToken)
|
||||
{
|
||||
var job = await _db.JobApplications.FirstOrDefaultAsync(j => j.Id == id, cancellationToken);
|
||||
if (job is null) return NotFound();
|
||||
|
||||
if (!job.IsDeleted)
|
||||
{
|
||||
job.IsDeleted = true;
|
||||
job.DeletedAt = DateTime.Now;
|
||||
_db.JobEvents.Add(new JobEvent
|
||||
{
|
||||
JobApplicationId = job.Id,
|
||||
Type = "Deleted",
|
||||
At = DateTime.Now
|
||||
});
|
||||
await _db.SaveChangesAsync(cancellationToken);
|
||||
}
|
||||
|
||||
return NoContent();
|
||||
}
|
||||
|
||||
[HttpPost("{id:int}/restore")]
|
||||
public async Task<IActionResult> Restore([FromRoute] int id, CancellationToken cancellationToken)
|
||||
{
|
||||
var job = await _db.JobApplications.FirstOrDefaultAsync(j => j.Id == id, cancellationToken);
|
||||
if (job is null) return NotFound();
|
||||
|
||||
if (job.IsDeleted)
|
||||
{
|
||||
job.IsDeleted = false;
|
||||
job.DeletedAt = null;
|
||||
_db.JobEvents.Add(new JobEvent
|
||||
{
|
||||
JobApplicationId = job.Id,
|
||||
Type = "Restored",
|
||||
At = DateTime.Now
|
||||
});
|
||||
await _db.SaveChangesAsync(cancellationToken);
|
||||
}
|
||||
|
||||
return NoContent();
|
||||
}
|
||||
|
||||
[HttpPatch("{id:int}/followup")]
|
||||
public async Task<IActionResult> SetFollowUp([FromRoute] int id, [FromBody] FollowUpRequest request, CancellationToken cancellationToken)
|
||||
{
|
||||
var job = await _db.JobApplications.FirstOrDefaultAsync(j => j.Id == id, cancellationToken);
|
||||
if (job is null) return NotFound();
|
||||
|
||||
var old = job.FollowUpAt?.ToString("o");
|
||||
job.FollowUpAt = request.FollowUpAt;
|
||||
_db.JobEvents.Add(new JobEvent
|
||||
{
|
||||
JobApplicationId = job.Id,
|
||||
Type = "FollowUpSet",
|
||||
OldValue = old,
|
||||
NewValue = request.FollowUpAt?.ToString("o"),
|
||||
At = DateTime.Now
|
||||
});
|
||||
|
||||
await _db.SaveChangesAsync(cancellationToken);
|
||||
return NoContent();
|
||||
}
|
||||
|
||||
[HttpGet("{id:int}/history")]
|
||||
public async Task<ActionResult<List<JobEventDto>>> GetHistory([FromRoute] int id, CancellationToken cancellationToken)
|
||||
{
|
||||
var exists = await _db.JobApplications.AnyAsync(j => j.Id == id, cancellationToken);
|
||||
if (!exists) return NotFound();
|
||||
|
||||
var items = await _db.JobEvents
|
||||
.AsNoTracking()
|
||||
.Where(e => e.JobApplicationId == id)
|
||||
.OrderByDescending(e => e.At)
|
||||
.Select(e => new JobEventDto(e.Id, e.Type, e.OldValue, e.NewValue, e.Note, e.At))
|
||||
.ToListAsync(cancellationToken);
|
||||
|
||||
return Ok(items);
|
||||
}
|
||||
|
||||
private static string BuildPackageModeInstruction(string? mode)
|
||||
{
|
||||
return (mode ?? string.Empty).Trim().ToLowerInvariant() switch
|
||||
|
||||
Reference in New Issue
Block a user