Add synced question bookmarks
This commit is contained in:
@@ -0,0 +1,117 @@
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using QuestionsApi.Models;
|
||||
using QuestionsApi.Services;
|
||||
using System.Security.Claims;
|
||||
|
||||
namespace QuestionsApi.Controllers;
|
||||
|
||||
[ApiController, Route("api/bookmarks"), Authorize(Roles = "USER,SUPER")]
|
||||
public sealed class BookmarksController(
|
||||
QuestionBookmarkStore bookmarks,
|
||||
QuestionStore questions,
|
||||
AnswerStatisticsStore statistics,
|
||||
RatingStore ratings) : ControllerBase
|
||||
{
|
||||
[HttpGet]
|
||||
public async Task<ActionResult<QuestionBookmarkPageResponse>> List(
|
||||
[FromQuery] string query = "",
|
||||
[FromQuery] string system = "",
|
||||
[FromQuery] string discipline = "",
|
||||
[FromQuery] string sort = "newest",
|
||||
[FromQuery] string? cursor = null,
|
||||
[FromQuery] int limit = 20)
|
||||
{
|
||||
if (query.Length > 200) return BadRequest("query must be 200 characters or fewer");
|
||||
if (system.Length > 120 || discipline.Length > 120)
|
||||
return BadRequest("filter values must be 120 characters or fewer");
|
||||
try
|
||||
{
|
||||
var page = await bookmarks.SearchAsync(
|
||||
UserId(), query, system, discipline, sort, cursor, limit, User.IsInRole("SUPER"));
|
||||
var facets = await bookmarks.GetFacetsAsync(UserId(), User.IsInRole("SUPER"));
|
||||
return Ok(new QuestionBookmarkPageResponse
|
||||
{
|
||||
Items = page.Items.Select(item => new QuestionBookmarkSummary
|
||||
{
|
||||
QuestionExternalId = item.Question.ExternalId,
|
||||
Stem = item.Question.Stem,
|
||||
LeadIn = item.Question.LeadIn,
|
||||
System = item.Question.System,
|
||||
Discipline = item.Question.Discipline,
|
||||
Difficulty = item.Question.Difficulty,
|
||||
HasMedia = item.Question.Media.Count > 0,
|
||||
Tags = item.Question.Tags,
|
||||
CreatedUtc = item.Bookmark.CreatedUtc
|
||||
}).ToList(),
|
||||
NextCursor = page.NextCursor,
|
||||
HasMore = page.HasMore,
|
||||
Systems = facets.Systems,
|
||||
Disciplines = facets.Disciplines
|
||||
});
|
||||
}
|
||||
catch (ArgumentException exception)
|
||||
{
|
||||
return BadRequest(exception.Message);
|
||||
}
|
||||
}
|
||||
|
||||
[HttpPut("{externalId}")]
|
||||
public async Task<ActionResult<QuestionBookmarkMutationResponse>> Add(string externalId)
|
||||
{
|
||||
var normalized = NormalizeExternalId(externalId);
|
||||
if (normalized is null) return BadRequest("externalId is invalid");
|
||||
var question = await questions.GetByExternalIdAsync(normalized);
|
||||
if (question is null || (!question.IsPublished && !User.IsInRole("SUPER"))) return NotFound();
|
||||
await bookmarks.AddAsync(UserId(), question.ExternalId);
|
||||
return Ok(new QuestionBookmarkMutationResponse
|
||||
{
|
||||
QuestionExternalId = question.ExternalId,
|
||||
IsBookmarked = true
|
||||
});
|
||||
}
|
||||
|
||||
[HttpDelete("{externalId}")]
|
||||
public async Task<ActionResult<QuestionBookmarkMutationResponse>> Remove(string externalId)
|
||||
{
|
||||
var normalized = NormalizeExternalId(externalId);
|
||||
if (normalized is null) return BadRequest("externalId is invalid");
|
||||
await bookmarks.RemoveAsync(UserId(), normalized);
|
||||
return Ok(new QuestionBookmarkMutationResponse
|
||||
{
|
||||
QuestionExternalId = normalized,
|
||||
IsBookmarked = false
|
||||
});
|
||||
}
|
||||
|
||||
[HttpGet("{externalId}/question")]
|
||||
public async Task<ActionResult<QuestionResponse>> Question(string externalId)
|
||||
{
|
||||
var normalized = NormalizeExternalId(externalId);
|
||||
if (normalized is null) return BadRequest("externalId is invalid");
|
||||
if (!await bookmarks.IsBookmarkedAsync(UserId(), normalized)) return NotFound();
|
||||
var question = await questions.GetByExternalIdAsync(normalized);
|
||||
if (question is null || (!question.IsPublished && !User.IsInRole("SUPER")))
|
||||
{
|
||||
await bookmarks.DeleteStaleAsync(UserId(), normalized);
|
||||
return NotFound();
|
||||
}
|
||||
var response = QuestionResponse.FromModel(
|
||||
question,
|
||||
await statistics.GetPercentagesForQuestionAsync(normalized),
|
||||
await ratings.GetQuestionEloAsync(normalized));
|
||||
response.IsBookmarked = true;
|
||||
return Ok(response);
|
||||
}
|
||||
|
||||
private string UserId() => User.FindFirstValue(ClaimTypes.NameIdentifier)
|
||||
?? User.FindFirstValue(ClaimTypes.Name)
|
||||
?? User.Identity?.Name
|
||||
?? "";
|
||||
|
||||
private static string? NormalizeExternalId(string value)
|
||||
{
|
||||
var normalized = value.Trim();
|
||||
return normalized.Length is > 0 and <= 200 ? normalized : null;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user