using MongoDB.Driver; using QuestionsApi.Models; namespace QuestionsApi.Services; public sealed record UserNotepadUpdateResult(UserNotepad? Note, UserNotepad? Current) { public bool Succeeded => Note is not null; } public sealed class UserNotepadStore { private readonly IMongoCollection _notes; public UserNotepadStore(MongoDatabaseProvider mongo) { _notes = mongo.Database.GetCollection("UserNotepads"); _notes.Indexes.CreateOne(new CreateIndexModel( Builders.IndexKeys.Ascending(note => note.UserId), new CreateIndexOptions { Unique = true, Name = "user_notepad_unique_user" })); } public async Task GetAsync(string userId) => await _notes.Find(note => note.UserId == userId).FirstOrDefaultAsync(); public async Task UpdateAsync( string userId, string content, long expectedRevision) { var now = DateTime.UtcNow; if (expectedRevision == 0) { var created = new UserNotepad { UserId = userId, Content = content, Revision = 1, CreatedUtc = now, UpdatedUtc = now }; try { await _notes.InsertOneAsync(created); return new UserNotepadUpdateResult(created, null); } catch (MongoWriteException exception) when ( exception.WriteError?.Category == ServerErrorCategory.DuplicateKey) { return new UserNotepadUpdateResult(null, await GetAsync(userId)); } } var updated = await _notes.FindOneAndUpdateAsync( note => note.UserId == userId && note.Revision == expectedRevision, Builders.Update .Set(note => note.Content, content) .Set(note => note.UpdatedUtc, now) .Inc(note => note.Revision, 1), new FindOneAndUpdateOptions { ReturnDocument = ReturnDocument.After }); return updated is not null ? new UserNotepadUpdateResult(updated, null) : new UserNotepadUpdateResult(null, await GetAsync(userId)); } }