using JobTrackerApi.Models; using JobTrackerApi.Services; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Mvc; namespace JobTrackerApi.Controllers; // Phase 5.5 — Interview preparation and follow-up. // // Prep content is the user's; AI suggestions come from the existing /api/jobapplications/{id}/ai // routes and only reach here once the user accepts one (POST with source "ai"). Communication stays // entirely on the existing Correspondence routes — there is no messaging endpoint here. // docs/architecture/application-workspace.md. [ApiController] [Route("api/jobapplications/{jobId:int}")] [Authorize(AuthenticationSchemes = "local")] public sealed class InterviewPrepController : ControllerBase { public sealed record SetFollowUpRequest(DateTime? FollowUpAt, string? NextAction); private readonly UserManager _users; private readonly IInterviewPrepService _prep; public InterviewPrepController(UserManager users, IInterviewPrepService prep) { _users = users; _prep = prep; } [HttpGet("interview-prep")] public async Task> Get(int jobId, CancellationToken ct) { var userId = await CurrentUserIdAsync(); if (userId is null) return Unauthorized(); var result = await _prep.GetAsync(userId, jobId, ct); return result is null ? NotFound() : Ok(result); } [HttpPost("interview-prep")] public async Task> Add(int jobId, [FromBody] InterviewPrepInput input, CancellationToken ct) { var userId = await CurrentUserIdAsync(); if (userId is null) return Unauthorized(); if (string.IsNullOrWhiteSpace(input?.Title)) return BadRequest("Title is required."); var created = await _prep.AddAsync(userId, jobId, input, ct); return created is null ? NotFound() : Ok(created); } [HttpPatch("interview-prep/{itemId:int}")] public async Task> Update(int jobId, int itemId, [FromBody] InterviewPrepInput input, CancellationToken ct) { var userId = await CurrentUserIdAsync(); if (userId is null) return Unauthorized(); var updated = await _prep.UpdateAsync(userId, jobId, itemId, input, ct); return updated is null ? NotFound() : Ok(updated); } [HttpDelete("interview-prep/{itemId:int}")] public async Task Delete(int jobId, int itemId, CancellationToken ct) { var userId = await CurrentUserIdAsync(); if (userId is null) return Unauthorized(); return await _prep.DeleteAsync(userId, jobId, itemId, ct) ? NoContent() : NotFound(); } [HttpGet("follow-up")] public async Task> GetFollowUp(int jobId, CancellationToken ct) { var userId = await CurrentUserIdAsync(); if (userId is null) return Unauthorized(); var result = await _prep.GetFollowUpAsync(userId, jobId, ct); return result is null ? NotFound() : Ok(result); } [HttpPut("follow-up")] public async Task> SetFollowUp(int jobId, [FromBody] SetFollowUpRequest request, CancellationToken ct) { var userId = await CurrentUserIdAsync(); if (userId is null) return Unauthorized(); var result = await _prep.SetFollowUpAsync(userId, jobId, request?.FollowUpAt, request?.NextAction, ct); return result is null ? NotFound() : Ok(result); } private async Task CurrentUserIdAsync() => (await _users.GetUserAsync(User))?.Id; }