using MongoDB.Driver; using MongoDB.Entities; using PrivaPub.Models.Group; using PrivaPub.Models.Social; namespace PrivaPub.Domain.Statuses { // A direct conversation's newest post (DmGroup.LastPostId, which pages the conversations list) and each persona's // read and removed marks on it (ConversationState). A conversation is unread for a persona when its newest post is // someone else's and newer than what the persona read; writing in it reads it. public static class ConversationStates { public static async Task Posted(string conversationId, string postId, string authorAvatarId, CancellationToken token) { await DB.Default.Update().MatchID(conversationId) .Modify(g => g.UpdatedAt, DateTime.UtcNow) .Modify(g => g.LastPostId, postId) .ExecuteAsync(token); if (authorAvatarId != default) await Read(authorAvatarId, conversationId, postId, token); } public static Task Read(string avatarId, string conversationId, string postId, CancellationToken token) => Mark(avatarId, conversationId, s => s.LastReadPostId, postId, token); public static Task Hide(string avatarId, string conversationId, string postId, CancellationToken token) => Mark(avatarId, conversationId, s => s.HiddenUpTo, postId, token); public static bool IsUnread(ConversationState state, string lastPostId, string lastAuthorId, string avatarId) => lastPostId != default && lastAuthorId != avatarId && string.CompareOrdinal(lastPostId, state?.LastReadPostId) > 0; public static bool IsHidden(ConversationState state, string lastPostId) => state?.HiddenUpTo != default && string.CompareOrdinal(lastPostId, state.HiddenUpTo) <= 0; // the mark only moves forward; the first mark makes the state (two at once: the unique index keeps one, and the // loser marks again) static async Task Mark(string avatarId, string conversationId, System.Linq.Expressions.Expression> field, string postId, CancellationToken token) { if (postId == default) return; Task Write() => DB.Default.Update() .Match(s => s.AvatarId == avatarId && s.ConversationId == conversationId) .Modify(b => b.Max(field, postId)) .Modify(s => s.UpdatedAt, DateTime.UtcNow) .Option(o => o.IsUpsert = true) .ExecuteAsync(token); try { await Write(); } catch (MongoWriteException ex) when (ex.WriteError?.Category == ServerErrorCategory.DuplicateKey) { await Write(); } } } }