using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using QuestionsApi.Models; using QuestionsApi.Services; using System.Security.Claims; namespace QuestionsApi.Controllers; [ApiController] [Route("api/access")] [Authorize(Roles = "USER,SUPER")] public sealed class AccessController( AccessStore access, ExamSimulationStore simulations, RatingStore ratings, TimeProvider clock) : ControllerBase { [HttpGet] public async Task Get() => Ok(await access.GetStatusAsync(UserId(), User.IsInRole("SUPER"))); [HttpPost("redeem-credits")] public async Task RedeemCredits(RedeemCreditsRequest request) { if (!ValidOperationId(request.OperationId)) return BadRequest("A valid operationId is required."); var status = await access.RedeemCreditsAsync(UserId(), request.OperationId.Trim()); return status is null ? Conflict(new { code = "insufficient_credits", message = "100 credits are required to redeem a day pass." }) : Ok(status); } [HttpPost("activate-token")] public async Task ActivateToken(ActivateAccessTokenRequest request) { if (!ValidOperationId(request.OperationId)) return BadRequest("A valid operationId is required."); if (request.Token.Trim().ToLowerInvariant() is not ("day" or "week" or "month")) return BadRequest("token must be day, week, or month"); var status = await access.ActivateTokenAsync(UserId(), request.OperationId.Trim(), request.Token); return status is null ? Conflict(new { code = "token_unavailable", message = "That access token is not available." }) : Ok(status); } [HttpPost("qbank-reset")] public async Task ResetQBank(ResetQBankRequest request) { if (request.Confirmation != "RESET_QBANK") return BadRequest("Type RESET_QBANK to confirm."); if (!ValidOperationId(request.OperationId)) return BadRequest("A valid operationId is required."); if (await simulations.FindActiveAsync(UserId()) is not null) return Conflict(new { code = "active_exam", message = "Finish or abandon the active exam simulation before resetting the QBank." }); var cycle = await access.ApplyResetAsync(UserId(), request.OperationId.Trim()); if (cycle is null) return Conflict(new { code = "reset_token_unavailable", message = "A QBank reset token is required." }); await ratings.ResetUserAsync(UserId(), clock.GetUtcNow().UtcDateTime); return Ok(await access.GetStatusAsync(UserId(), User.IsInRole("SUPER"))); } [HttpPost("steam-checkouts")] public IActionResult SteamCheckout() => Problem( statusCode: StatusCodes.Status503ServiceUnavailable, title: "Steam Wallet checkout is not configured", detail: "Steam purchases will be enabled after the Steamworks application and product catalog are configured."); private string UserId() => User.FindFirstValue(ClaimTypes.NameIdentifier) ?? User.FindFirstValue(ClaimTypes.Name) ?? User.Identity?.Name ?? throw new UnauthorizedAccessException(); private static bool ValidOperationId(string value) => !string.IsNullOrWhiteSpace(value) && value.Trim().Length <= 100; }