Files
step3/api/QuestionsApi/Services/QuestionReportStore.cs
T

175 lines
6.5 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
},
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 });
}
}