Add freemium access economy and token store
This commit is contained in:
@@ -19,6 +19,7 @@ public class QuestionsController : ControllerBase
|
||||
private readonly ServiceMetrics _metrics;
|
||||
private readonly QuestionBookmarkStore _bookmarks;
|
||||
private readonly ReviewQueueStore _reviewQueue;
|
||||
private readonly AccessStore _access;
|
||||
|
||||
public QuestionsController(
|
||||
QuestionStore questions,
|
||||
@@ -28,7 +29,8 @@ public class QuestionsController : ControllerBase
|
||||
AnswerStatisticsStore statistics,
|
||||
ServiceMetrics metrics,
|
||||
QuestionBookmarkStore bookmarks,
|
||||
ReviewQueueStore reviewQueue)
|
||||
ReviewQueueStore reviewQueue,
|
||||
AccessStore access)
|
||||
{
|
||||
_questions = questions;
|
||||
_attempts = attempts;
|
||||
@@ -38,6 +40,7 @@ public class QuestionsController : ControllerBase
|
||||
_metrics = metrics;
|
||||
_bookmarks = bookmarks;
|
||||
_reviewQueue = reviewQueue;
|
||||
_access = access;
|
||||
}
|
||||
|
||||
[HttpGet("systems")]
|
||||
@@ -52,6 +55,13 @@ public class QuestionsController : ControllerBase
|
||||
[Authorize(Roles = "USER,SUPER")]
|
||||
public async Task<IActionResult> Search([FromBody] QuestionSearchRequest req)
|
||||
{
|
||||
if (!await _access.CanLoadQuestionAsync(UserId(), User.IsInRole("SUPER")))
|
||||
return StatusCode(StatusCodes.Status403Forbidden, new
|
||||
{
|
||||
code = "qbank_limit_reached",
|
||||
message = "Your free QBank allowance is exhausted. Redeem or activate an access pass to continue.",
|
||||
access = await _access.GetStatusAsync(UserId())
|
||||
});
|
||||
var questions = await _questions.SearchAsync(req, User.IsInRole("SUPER"));
|
||||
var percentages = await _statistics.GetPercentagesByQuestionAsync(questions.Select(q => q.ExternalId));
|
||||
var questionElos = await _ratings.GetQuestionElosAsync(questions.Select(q => q.ExternalId));
|
||||
@@ -117,6 +127,8 @@ public class QuestionsController : ControllerBase
|
||||
return BadRequest("questionExternalId required");
|
||||
if (string.IsNullOrWhiteSpace(req.ChoiceId))
|
||||
return BadRequest("choiceId required");
|
||||
if (string.IsNullOrWhiteSpace(req.CompletionId) || req.CompletionId.Trim().Length > 100)
|
||||
return BadRequest("completionId required and must be 100 characters or fewer");
|
||||
|
||||
var question = await _questions.GetByExternalIdAsync(req.QuestionExternalId.Trim());
|
||||
if (question is null || (!QuestionVisibility.IsVisibleToStudent(question.IsPublished, question.FlaggedForReviewUtc != null) && !User.IsInRole("SUPER")))
|
||||
@@ -146,6 +158,15 @@ public class QuestionsController : ControllerBase
|
||||
?? "";
|
||||
if (string.IsNullOrWhiteSpace(userId))
|
||||
return Unauthorized();
|
||||
var completionId = req.CompletionId.Trim();
|
||||
if (!await _access.ClaimQuestionAsync(userId, completionId, question.ExternalId, User.IsInRole("SUPER")))
|
||||
return StatusCode(StatusCodes.Status403Forbidden, new
|
||||
{
|
||||
code = "qbank_limit_reached",
|
||||
message = "Your free QBank allowance is exhausted.",
|
||||
access = await _access.GetStatusAsync(userId)
|
||||
});
|
||||
var bankCycle = await _access.GetBankCycleAsync(userId);
|
||||
|
||||
var record = await _attempts.RecordFirstAttemptAsync(
|
||||
question.ExternalId,
|
||||
@@ -154,7 +175,9 @@ public class QuestionsController : ControllerBase
|
||||
req.ChoiceId.Trim() == question.CorrectChoiceId,
|
||||
false,
|
||||
presentedChoiceIds,
|
||||
req.DurationSeconds);
|
||||
req.DurationSeconds,
|
||||
bankCycle,
|
||||
completionId);
|
||||
if (record.Inserted)
|
||||
{
|
||||
_metrics.AttemptCreated();
|
||||
@@ -172,6 +195,8 @@ public class QuestionsController : ControllerBase
|
||||
_metrics.AttemptDuplicate();
|
||||
}
|
||||
await _reviewQueue.AddFromFirstAttemptAsync(record.Attempt);
|
||||
if (record.Inserted && !User.IsInRole("SUPER"))
|
||||
await _access.TryAwardCreditAsync(userId, $"qbank-credit:{record.Attempt.Id}");
|
||||
|
||||
var ratingOwner = Guid.NewGuid().ToString("N");
|
||||
var shouldRate = await _attempts.TryAcquireRatingLeaseAsync(
|
||||
@@ -204,7 +229,8 @@ public class QuestionsController : ControllerBase
|
||||
question,
|
||||
await _statistics.GetPercentagesForQuestionAsync(question.ExternalId)),
|
||||
QuestionElo = rating.QuestionElo,
|
||||
Rating = rating
|
||||
Rating = rating,
|
||||
Access = await _access.GetStatusAsync(userId, User.IsInRole("SUPER"))
|
||||
});
|
||||
}
|
||||
|
||||
@@ -252,7 +278,7 @@ public class QuestionsController : ControllerBase
|
||||
|
||||
[HttpPost("{externalId}/skip")]
|
||||
[Authorize(Roles = "USER,SUPER")]
|
||||
public async Task<IActionResult> Skip(string externalId)
|
||||
public async Task<IActionResult> Skip(string externalId, [FromQuery] string completionId)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(externalId))
|
||||
return BadRequest("externalId required");
|
||||
@@ -264,13 +290,28 @@ public class QuestionsController : ControllerBase
|
||||
var userId = CurrentUserId();
|
||||
if (string.IsNullOrWhiteSpace(userId))
|
||||
return Unauthorized();
|
||||
if (string.IsNullOrWhiteSpace(completionId) || completionId.Trim().Length > 100)
|
||||
return BadRequest("completionId required and must be 100 characters or fewer");
|
||||
completionId = completionId.Trim();
|
||||
if (!await _access.ClaimQuestionAsync(userId, completionId, question.ExternalId, User.IsInRole("SUPER")))
|
||||
return StatusCode(StatusCodes.Status403Forbidden, new
|
||||
{
|
||||
code = "qbank_limit_reached",
|
||||
message = "Your free QBank allowance is exhausted.",
|
||||
access = await _access.GetStatusAsync(userId)
|
||||
});
|
||||
var bankCycle = await _access.GetBankCycleAsync(userId);
|
||||
|
||||
var record = await _attempts.RecordFirstAttemptAsync(
|
||||
question.ExternalId,
|
||||
userId,
|
||||
"",
|
||||
false,
|
||||
true);
|
||||
true,
|
||||
null,
|
||||
0,
|
||||
bankCycle,
|
||||
completionId);
|
||||
if (record.Inserted)
|
||||
_metrics.AttemptCreated();
|
||||
else
|
||||
@@ -304,7 +345,8 @@ public class QuestionsController : ControllerBase
|
||||
question,
|
||||
await _statistics.GetPercentagesForQuestionAsync(question.ExternalId)),
|
||||
QuestionElo = rating.QuestionElo,
|
||||
Rating = rating
|
||||
Rating = rating,
|
||||
Access = await _access.GetStatusAsync(userId, User.IsInRole("SUPER"))
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user