using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using QuestionsApi.Models; using QuestionsApi.Services; using System.Security.Claims; namespace QuestionsApi.Controllers; [ApiController] [Route("api/exam-simulations")] [Authorize(Roles = "USER,SUPER")] public class ExamSimulationsController(ExamSimulationStore simulations) : ControllerBase { [HttpGet("readiness")] public async Task Readiness() => Ok(await simulations.GetReadinessAsync(UserId())); [HttpPost] public async Task Create() { try { var simulation = await simulations.CreateAsync(UserId()); return Ok(await simulations.ToResponseAsync(simulation)); } catch (InvalidOperationException exception) { return Conflict(exception.Message); } } [HttpGet("preview-results")] [Authorize(Roles = "SUPER")] public async Task PreviewResults() { try { return Ok(await simulations.BuildResultsPreviewAsync(UserId())); } catch (InvalidOperationException exception) { return Conflict(exception.Message); } } [HttpPost("sample-completed")] [Authorize(Roles = "SUPER")] public async Task CreateSampleCompleted() { try { return Ok(ExamSimulationStore.ToHistorySummary(await simulations.CreateSampleCompletedAsync(UserId()))); } catch (InvalidOperationException exception) { return Conflict(exception.Message); } } [HttpGet("active")] public async Task Active() { var simulation = await simulations.FindActiveAsync(UserId()); return simulation is null ? NoContent() : Ok(await simulations.ToResponseAsync(simulation)); } [HttpGet("completed")] public async Task Completed([FromQuery] int limit = 20, [FromQuery] string? before = null) { DateTime? cursor = null; if (!string.IsNullOrWhiteSpace(before)) { if (!DateTime.TryParse(before, null, System.Globalization.DateTimeStyles.RoundtripKind, out var parsed)) return BadRequest("The completed-simulation cursor is invalid."); cursor = parsed; } return Ok(await simulations.GetCompletedAsync(UserId(), limit, cursor)); } [HttpGet("history")] public async Task History([FromQuery] int limit = 20, [FromQuery] string? before = null) { DateTime? cursor = null; if (!string.IsNullOrWhiteSpace(before)) { if (!DateTime.TryParse(before, null, System.Globalization.DateTimeStyles.RoundtripKind, out var parsed)) return BadRequest("The simulation-history cursor is invalid."); cursor = parsed; } return Ok(await simulations.GetHistoryAsync(UserId(), limit, cursor)); } [HttpGet("{id}")] public async Task Get(string id) { var simulation = await simulations.GetOwnedAsync(id, UserId()); return simulation is null ? NotFound() : Ok(await simulations.ToResponseAsync(simulation)); } [HttpPost("{id}/blocks/start")] public async Task StartBlock(string id) { var simulation = await simulations.GetOwnedAsync(id, UserId()); if (simulation is null) return NotFound(); try { return Ok(await simulations.ToResponseAsync(await simulations.StartNextBlockAsync(simulation))); } catch (InvalidOperationException exception) { return Conflict(exception.Message); } } [HttpPut("{id}/responses")] public async Task SaveResponse(string id, SaveSimulationResponseRequest request) { var simulation = await simulations.GetOwnedAsync(id, UserId()); if (simulation is null) return NotFound(); try { return Ok(await simulations.ToResponseAsync(await simulations.SaveResponseAsync(simulation, request))); } catch (ArgumentException exception) { return BadRequest(exception.Message); } catch (InvalidOperationException exception) { return Conflict(exception.Message); } } [HttpPost("{id}/blocks/submit")] public async Task SubmitBlock(string id) { var simulation = await simulations.GetOwnedAsync(id, UserId()); if (simulation is null) return NotFound(); try { return Ok(await simulations.ToResponseAsync(await simulations.SubmitBlockAsync(simulation))); } catch (InvalidOperationException exception) { return Conflict(exception.Message); } } [HttpPost("{id}/abandon")] public async Task Abandon(string id) { var simulation = await simulations.GetOwnedAsync(id, UserId()); if (simulation is null) return NotFound(); await simulations.AbandonAsync(simulation); return NoContent(); } private string UserId() => User.FindFirstValue(ClaimTypes.NameIdentifier) ?? User.FindFirstValue(ClaimTypes.Name) ?? User.Identity?.Name ?? throw new UnauthorizedAccessException(); } [ApiController] [Route("api/step3-outcomes")] [Authorize(Roles = "USER,SUPER")] public class Step3OutcomesController(ExamSimulationStore simulations) : ControllerBase { [HttpGet("me")] public async Task Get() { var outcome = await simulations.GetOutcomeAsync(UserId()); return outcome is null ? NoContent() : Ok(outcome); } [HttpPut("me")] public async Task Put(UpsertStep3OutcomeRequest request) { try { return Ok(await simulations.UpsertOutcomeAsync(UserId(), request)); } catch (ArgumentException exception) { return BadRequest(exception.Message); } } [HttpDelete("me")] public async Task Delete() { await simulations.DeleteOutcomeAsync(UserId()); return NoContent(); } private string UserId() => User.FindFirstValue(ClaimTypes.NameIdentifier) ?? User.FindFirstValue(ClaimTypes.Name) ?? User.Identity?.Name ?? throw new UnauthorizedAccessException(); }