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; } /// Canonical ordered pipeline stages so the UI renders one source of truth. [HttpGet("pipeline")] public ActionResult> GetPipeline() => Ok(JobPipeline.Stages.Select(stage => new PipelineStageDto( stage.Key, stage.Order, stage.Category.ToString(), stage.Group.ToString()))); [HttpDelete("{id:int}")] public async Task 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 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 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>> 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); } }