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

145 lines
5.5 KiB
C#

using MongoDB.Driver;
using QuestionsApi.Models;
namespace QuestionsApi.Services;
public class QuestionStore
{
private readonly IMongoCollection<Question> _col;
public QuestionStore(IConfiguration cfg)
{
var cs = cfg["MongoDB:ConnectionString"] ?? "mongodb://127.0.0.1:27017";
var dbName = cfg["MongoDB:DatabaseName"] ?? "GameDb";
var client = new MongoClient(cs);
var db = client.GetDatabase(dbName);
_col = db.GetCollection<Question>("Questions");
var externalIdIndex = Builders<Question>.IndexKeys.Ascending(q => q.ExternalId);
_col.Indexes.CreateOne(new CreateIndexModel<Question>(externalIdIndex, new CreateIndexOptions { Unique = true }));
var systemIndex = Builders<Question>.IndexKeys.Ascending(q => q.System).Ascending(q => q.IsPublished);
_col.Indexes.CreateOne(new CreateIndexModel<Question>(systemIndex));
var tagsIndex = Builders<Question>.IndexKeys.Ascending(q => q.Tags);
_col.Indexes.CreateOne(new CreateIndexModel<Question>(tagsIndex));
}
public async Task<List<Question>> SearchAsync(QuestionSearchRequest req, bool includeUnpublished)
{
var builder = Builders<Question>.Filter;
var filters = new List<FilterDefinition<Question>>();
if (!includeUnpublished)
filters.Add(builder.Eq(q => q.IsPublished, true));
if (!string.IsNullOrWhiteSpace(req.System) && req.System != "All Systems")
filters.Add(builder.Eq(q => q.System, req.System.Trim()));
if (!string.IsNullOrWhiteSpace(req.Discipline))
filters.Add(builder.Eq(q => q.Discipline, req.Discipline.Trim()));
if (req.Tags.Count > 0)
filters.Add(builder.All(q => q.Tags, req.Tags.Where(t => !string.IsNullOrWhiteSpace(t)).Select(t => t.Trim())));
if (req.DifficultyMin is not null)
filters.Add(builder.Gte(q => q.Difficulty, req.DifficultyMin.Value));
if (req.DifficultyMax is not null)
filters.Add(builder.Lte(q => q.Difficulty, req.DifficultyMax.Value));
var filter = filters.Count == 0 ? builder.Empty : builder.And(filters);
var limit = Math.Clamp(req.Limit, 1, 80);
var questions = await _col.Find(filter).Limit(limit).ToListAsync();
return questions.OrderBy(_ => Guid.NewGuid()).ToList();
}
public async Task<Question?> GetByIdAsync(string id) =>
await _col.Find(q => q.Id == id).FirstOrDefaultAsync();
public async Task<Question?> CreateAsync(UpsertQuestionRequest req)
{
var question = FromRequest(req);
question.CreatedUtc = DateTime.UtcNow;
question.UpdatedUtc = DateTime.UtcNow;
try
{
await _col.InsertOneAsync(question);
return question;
}
catch (MongoWriteException ex) when (ex.WriteError.Category == ServerErrorCategory.DuplicateKey)
{
return null;
}
}
public async Task<Question?> UpdateAsync(string id, UpsertQuestionRequest req)
{
var question = FromRequest(req);
question.Id = id;
question.UpdatedUtc = DateTime.UtcNow;
var existing = await GetByIdAsync(id);
if (existing is null)
return null;
question.CreatedUtc = existing.CreatedUtc;
var result = await _col.ReplaceOneAsync(q => q.Id == id, question);
return result.MatchedCount == 0 ? null : question;
}
public async Task<int> UpsertManyByExternalIdAsync(IEnumerable<UpsertQuestionRequest> requests)
{
var count = 0;
foreach (var req in requests)
{
var question = FromRequest(req);
question.UpdatedUtc = DateTime.UtcNow;
var filter = Builders<Question>.Filter.Eq(q => q.ExternalId, question.ExternalId);
var existing = await _col.Find(filter).FirstOrDefaultAsync();
if (existing is not null)
{
question.Id = existing.Id;
question.CreatedUtc = existing.CreatedUtc;
}
else
{
question.CreatedUtc = DateTime.UtcNow;
}
await _col.ReplaceOneAsync(filter, question, new ReplaceOptions { IsUpsert = true });
count++;
}
return count;
}
public async Task<bool> DeleteAsync(string id)
{
var result = await _col.DeleteOneAsync(q => q.Id == id);
return result.DeletedCount > 0;
}
public async Task<List<string>> GetSystemsAsync(bool includeUnpublished)
{
var filter = includeUnpublished
? Builders<Question>.Filter.Empty
: Builders<Question>.Filter.Eq(q => q.IsPublished, true);
var systems = await _col.Distinct(q => q.System, filter).ToListAsync();
return systems.Where(s => !string.IsNullOrWhiteSpace(s)).OrderBy(s => s).ToList();
}
private static Question FromRequest(UpsertQuestionRequest req) => new()
{
ExternalId = req.ExternalId.Trim(),
Stem = req.Stem.Trim(),
LeadIn = req.LeadIn.Trim(),
Choices = req.Choices,
CorrectChoiceId = req.CorrectChoiceId.Trim(),
Explanation = req.Explanation.Trim(),
ChoiceExplanations = req.ChoiceExplanations,
Discipline = req.Discipline.Trim(),
System = req.System.Trim(),
Difficulty = req.Difficulty,
Labs = req.Labs,
Media = req.Media,
Tags = req.Tags.Where(t => !string.IsNullOrWhiteSpace(t)).Select(t => t.Trim()).Distinct(StringComparer.OrdinalIgnoreCase).ToList(),
IsPublished = req.IsPublished
};
}