using MongoDB.Driver; using QuestionsApi.Models; namespace QuestionsApi.Services; public class QuestionAttemptStore { private readonly IMongoCollection _col; public QuestionAttemptStore(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("QuestionAttempts"); var firstAttemptIndex = Builders.IndexKeys .Ascending(a => a.QuestionExternalId) .Ascending(a => a.UserId); _col.Indexes.CreateOne(new CreateIndexModel( firstAttemptIndex, new CreateIndexOptions { Unique = true })); var questionIndex = Builders.IndexKeys.Ascending(a => a.QuestionExternalId); _col.Indexes.CreateOne(new CreateIndexModel(questionIndex)); } public async Task RecordFirstAttemptAsync(string questionExternalId, string userId, string choiceId, bool isCorrect, bool isSkip = false) { var attempt = new QuestionAttempt { QuestionExternalId = questionExternalId, UserId = userId, ChoiceId = choiceId, IsCorrect = isCorrect, IsSkip = isSkip, CreatedUtc = DateTime.UtcNow }; try { await _col.InsertOneAsync(attempt); return true; } catch (MongoWriteException ex) when (ex.WriteError.Category == ServerErrorCategory.DuplicateKey) { return false; } } public async Task> GetPercentagesForQuestionAsync(string questionExternalId) { var results = await GetPercentagesByQuestionAsync([questionExternalId]); return results.GetValueOrDefault(questionExternalId, []); } public async Task>> GetPercentagesByQuestionAsync(IEnumerable questionExternalIds) { var ids = questionExternalIds.Where(id => !string.IsNullOrWhiteSpace(id)).Distinct().ToList(); if (ids.Count == 0) return []; var attempts = await _col.Find(a => ids.Contains(a.QuestionExternalId) && !a.IsSkip).ToListAsync(); var output = new Dictionary>(); foreach (var group in attempts.GroupBy(a => a.QuestionExternalId)) { var total = group.Count(); if (total == 0) continue; output[group.Key] = group .GroupBy(a => a.ChoiceId) .ToDictionary( choiceGroup => choiceGroup.Key, choiceGroup => (int)Math.Round(choiceGroup.Count() * 100.0 / total)); } return output; } }