179 lines
6.7 KiB
C#
179 lines
6.7 KiB
C#
using MongoDB.Bson;
|
|
using MongoDB.Driver;
|
|
using QuestionsApi.Models;
|
|
|
|
namespace QuestionsApi.Services;
|
|
|
|
public class QuestionReportStore
|
|
{
|
|
private readonly IMongoCollection<QuestionReport> _reports;
|
|
|
|
public QuestionReportStore(MongoDatabaseProvider mongo)
|
|
{
|
|
_reports = mongo.Database.GetCollection<QuestionReport>("QuestionReports");
|
|
|
|
_reports.Indexes.CreateMany(
|
|
[
|
|
new CreateIndexModel<QuestionReport>(
|
|
Builders<QuestionReport>.IndexKeys
|
|
.Ascending(r => r.Status)
|
|
.Descending(r => r.CreatedUtc)),
|
|
new CreateIndexModel<QuestionReport>(
|
|
Builders<QuestionReport>.IndexKeys.Ascending(r => r.QuestionExternalId)),
|
|
new CreateIndexModel<QuestionReport>(
|
|
Builders<QuestionReport>.IndexKeys.Ascending(r => r.UserId))
|
|
]);
|
|
}
|
|
|
|
public async Task<QuestionReport> CreateAsync(Question question, string userId, string category, string details)
|
|
{
|
|
var now = DateTime.UtcNow;
|
|
var report = new QuestionReport
|
|
{
|
|
QuestionExternalId = question.ExternalId,
|
|
QuestionMongoId = question.Id ?? "",
|
|
UserId = userId,
|
|
Category = category,
|
|
Details = details,
|
|
Status = QuestionReportStatus.Open,
|
|
QuestionSnapshot = new QuestionReportSnapshot
|
|
{
|
|
Stem = question.Stem,
|
|
LeadIn = question.LeadIn,
|
|
Choices = question.Choices
|
|
.Select(choice => new QuestionChoice { Id = choice.Id, Text = choice.Text })
|
|
.ToList(),
|
|
CorrectChoiceId = question.CorrectChoiceId,
|
|
Explanation = question.Explanation,
|
|
KeyTakeaway = question.KeyTakeaway,
|
|
UsmlePearl = question.UsmlePearl,
|
|
ReviewFollowups = question.ReviewFollowups,
|
|
ChoiceExplanations = new Dictionary<string, string>(question.ChoiceExplanations),
|
|
WrongAnswerFollowups = question.WrongAnswerFollowups.ToDictionary(
|
|
item => item.Key,
|
|
item => new WrongAnswerFollowup
|
|
{
|
|
Prompt = item.Value.Prompt,
|
|
Choices = item.Value.Choices
|
|
.Select(choice => new QuestionChoice { Id = choice.Id, Text = choice.Text })
|
|
.ToList(),
|
|
CorrectChoiceId = item.Value.CorrectChoiceId,
|
|
TeachingPoint = item.Value.TeachingPoint
|
|
}),
|
|
Labs = question.Labs
|
|
.Select(lab => new QuestionLab
|
|
{
|
|
Name = lab.Name,
|
|
Value = lab.Value,
|
|
Unit = lab.Unit,
|
|
Reference = lab.Reference
|
|
})
|
|
.ToList(),
|
|
Media = [.. question.Media]
|
|
},
|
|
CreatedUtc = now,
|
|
UpdatedUtc = now
|
|
};
|
|
|
|
await _reports.InsertOneAsync(report);
|
|
return report;
|
|
}
|
|
|
|
public async Task<QuestionReport> CreateDynamicAsync(
|
|
DynamicGenerationClaims claims,
|
|
string userId,
|
|
string category,
|
|
string details)
|
|
{
|
|
var now = DateTime.UtcNow;
|
|
var report = new QuestionReport
|
|
{
|
|
QuestionExternalId = $"dynamic:{claims.BlueprintExternalId}:v{claims.BlueprintVersion}",
|
|
UserId = userId,
|
|
Category = category,
|
|
Details = details,
|
|
Status = QuestionReportStatus.Open,
|
|
SourceType = "dynamic",
|
|
DynamicGeneration = new DynamicGenerationDescriptor
|
|
{
|
|
BlueprintExternalId = claims.BlueprintExternalId,
|
|
BlueprintVersion = claims.BlueprintVersion,
|
|
GeneratorVersion = claims.GeneratorVersion,
|
|
Seed = claims.Seed,
|
|
CaseFamilyId = claims.CaseFamilyId,
|
|
DifficultyTier = claims.DifficultyTier,
|
|
FamilyElo = claims.FamilyElo,
|
|
EffectiveAbilityElo = claims.EffectiveAbilityElo
|
|
},
|
|
CreatedUtc = now,
|
|
UpdatedUtc = now
|
|
};
|
|
await _reports.InsertOneAsync(report);
|
|
return report;
|
|
}
|
|
|
|
public async Task<QuestionReport?> GetByIdAsync(string id)
|
|
{
|
|
if (!ObjectId.TryParse(id, out _))
|
|
return null;
|
|
return await _reports.Find(r => r.Id == id).FirstOrDefaultAsync();
|
|
}
|
|
|
|
public async Task<QuestionReportListResponse> SearchAsync(
|
|
string status,
|
|
string category,
|
|
string questionExternalId,
|
|
string userId,
|
|
int skip,
|
|
int limit)
|
|
{
|
|
var filter = Builders<QuestionReport>.Filter.Empty;
|
|
if (!string.Equals(status, "all", StringComparison.OrdinalIgnoreCase))
|
|
filter &= Builders<QuestionReport>.Filter.Eq(r => r.Status, status);
|
|
if (!string.IsNullOrWhiteSpace(category))
|
|
filter &= Builders<QuestionReport>.Filter.Eq(r => r.Category, category);
|
|
if (!string.IsNullOrWhiteSpace(questionExternalId))
|
|
filter &= Builders<QuestionReport>.Filter.Eq(r => r.QuestionExternalId, questionExternalId);
|
|
if (!string.IsNullOrWhiteSpace(userId))
|
|
filter &= Builders<QuestionReport>.Filter.Eq(r => r.UserId, userId);
|
|
|
|
var total = await _reports.CountDocumentsAsync(filter);
|
|
var items = await _reports
|
|
.Find(filter)
|
|
.SortByDescending(r => r.CreatedUtc)
|
|
.Skip(skip)
|
|
.Limit(limit)
|
|
.ToListAsync();
|
|
|
|
return new QuestionReportListResponse
|
|
{
|
|
Items = items,
|
|
Total = total,
|
|
Skip = skip,
|
|
Limit = limit
|
|
};
|
|
}
|
|
|
|
public async Task<QuestionReport?> CloseAsync(string id, string status, string reviewerUserId, string reviewerNote)
|
|
{
|
|
if (!ObjectId.TryParse(id, out _))
|
|
return null;
|
|
|
|
var now = DateTime.UtcNow;
|
|
var filter = Builders<QuestionReport>.Filter.And(
|
|
Builders<QuestionReport>.Filter.Eq(r => r.Id, id),
|
|
Builders<QuestionReport>.Filter.Eq(r => r.Status, QuestionReportStatus.Open));
|
|
var update = Builders<QuestionReport>.Update
|
|
.Set(r => r.Status, status)
|
|
.Set(r => r.ReviewerNote, reviewerNote)
|
|
.Set(r => r.ReviewedByUserId, reviewerUserId)
|
|
.Set(r => r.ReviewedUtc, now)
|
|
.Set(r => r.UpdatedUtc, now);
|
|
|
|
return await _reports.FindOneAndUpdateAsync(
|
|
filter,
|
|
update,
|
|
new FindOneAndUpdateOptions<QuestionReport> { ReturnDocument = ReturnDocument.After });
|
|
}
|
|
}
|