Embed question verification references
This commit is contained in:
@@ -151,6 +151,32 @@ public class QuestionStore
|
||||
return systems.Where(s => !string.IsNullOrWhiteSpace(s)).OrderBy(s => s).ToList();
|
||||
}
|
||||
|
||||
public async Task<List<string>> GetMissingExternalIdsAsync(IEnumerable<string> externalIds)
|
||||
{
|
||||
var requested = externalIds.Distinct(StringComparer.Ordinal).ToList();
|
||||
var existing = await _col.Find(Builders<Question>.Filter.In(q => q.ExternalId, requested))
|
||||
.Project(q => q.ExternalId)
|
||||
.ToListAsync();
|
||||
return requested.Except(existing, StringComparer.Ordinal).OrderBy(id => id).ToList();
|
||||
}
|
||||
|
||||
public async Task<int> UpdateReferencesByExternalIdAsync(IEnumerable<QuestionReferencesUpdate> updates)
|
||||
{
|
||||
var now = DateTime.UtcNow;
|
||||
var writes = updates.Select(update =>
|
||||
new UpdateOneModel<Question>(
|
||||
Builders<Question>.Filter.Eq(question => question.ExternalId, update.ExternalId.Trim()),
|
||||
Builders<Question>.Update
|
||||
.Set(question => question.References, NormalizeReferences(update.References))
|
||||
.Set(question => question.UpdatedUtc, now)))
|
||||
.ToList();
|
||||
if (writes.Count == 0)
|
||||
return 0;
|
||||
|
||||
var result = await _col.BulkWriteAsync(writes, new BulkWriteOptions { IsOrdered = true });
|
||||
return (int)result.MatchedCount;
|
||||
}
|
||||
|
||||
private static Question FromRequest(UpsertQuestionRequest req) => new()
|
||||
{
|
||||
ExternalId = req.ExternalId.Trim(),
|
||||
@@ -169,6 +195,18 @@ public class QuestionStore
|
||||
Labs = req.Labs,
|
||||
Media = req.Media,
|
||||
Tags = req.Tags.Where(t => !string.IsNullOrWhiteSpace(t)).Select(t => t.Trim()).Distinct(StringComparer.OrdinalIgnoreCase).ToList(),
|
||||
References = NormalizeReferences(req.References),
|
||||
IsPublished = req.IsPublished
|
||||
};
|
||||
|
||||
private static List<QuestionReference> NormalizeReferences(IEnumerable<QuestionReference>? references) =>
|
||||
(references ?? []).Select(reference => new QuestionReference
|
||||
{
|
||||
Title = reference.Title.Trim(),
|
||||
Publisher = reference.Publisher.Trim(),
|
||||
Url = reference.Url.Trim(),
|
||||
Supports = reference.Supports.Trim(),
|
||||
AccessedOn = reference.AccessedOn.Trim(),
|
||||
Reviewer = reference.Reviewer.Trim()
|
||||
}).ToList();
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using QuestionsApi.Models;
|
||||
using System.Globalization;
|
||||
|
||||
namespace QuestionsApi.Services;
|
||||
|
||||
@@ -52,6 +53,45 @@ public static class QuestionValidator
|
||||
if (string.IsNullOrWhiteSpace(req.System)) return "system required";
|
||||
if (string.IsNullOrWhiteSpace(req.Discipline)) return "discipline required";
|
||||
if (req.Difficulty is < 1 or > 5) return "difficulty must be between 1 and 5";
|
||||
var referenceValidation = ValidateReferences(req.References, req.IsPublished);
|
||||
if (referenceValidation is not null) return referenceValidation;
|
||||
return null;
|
||||
}
|
||||
|
||||
public static string? ValidateReferences(IReadOnlyCollection<QuestionReference>? references, bool required)
|
||||
{
|
||||
if (references is null)
|
||||
return "verification references must be an array";
|
||||
if (required && references.Count == 0)
|
||||
return "published questions require at least one verification reference";
|
||||
if (references.Count > 20)
|
||||
return "questions support at most twenty verification references";
|
||||
|
||||
var urls = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
||||
foreach (var reference in references)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(reference.Title)
|
||||
|| string.IsNullOrWhiteSpace(reference.Publisher)
|
||||
|| string.IsNullOrWhiteSpace(reference.Url)
|
||||
|| string.IsNullOrWhiteSpace(reference.Supports)
|
||||
|| string.IsNullOrWhiteSpace(reference.AccessedOn)
|
||||
|| string.IsNullOrWhiteSpace(reference.Reviewer))
|
||||
return "verification reference fields are required";
|
||||
|
||||
var url = reference.Url.Trim();
|
||||
if (!Uri.TryCreate(url, UriKind.Absolute, out var uri) || uri.Scheme != Uri.UriSchemeHttps)
|
||||
return "verification reference urls must be absolute HTTPS urls";
|
||||
if (!urls.Add(url))
|
||||
return "verification reference urls must be unique within a question";
|
||||
if (!DateOnly.TryParseExact(
|
||||
reference.AccessedOn.Trim(),
|
||||
"yyyy-MM-dd",
|
||||
CultureInfo.InvariantCulture,
|
||||
DateTimeStyles.None,
|
||||
out _))
|
||||
return "verification reference accessedOn must use YYYY-MM-DD";
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user