using Microsoft.Extensions.Localization; using MongoDB.Entities; using PrivaPub.ClientModels; using PrivaPub.ClientModels.Post; using PrivaPub.ClientModels.Social; using PrivaPub.Domain.Privacy; using PrivaPub.Models.Post; using PrivaPub.Models.Social; using PrivaPub.Resources; using PrivaPub.StaticServices; namespace PrivaPub.Domain.Timelines { public interface ITimelineService { Task Home(string rootUserId, string avatarId, string maxId, int limit, CancellationToken token); Task Notifications(string rootUserId, string avatarId, string maxId, int limit, CancellationToken token); Task MarkNotificationsRead(string rootUserId, string avatarId, CancellationToken token); } public class TimelineService : ITimelineService { const int MaxLimit = 40; readonly DbEntities _dbEntities; readonly IStringLocalizer _localizer; public TimelineService(DbEntities dbEntities, IStringLocalizer localizer) { _dbEntities = dbEntities; _localizer = localizer; } public async Task Home(string rootUserId, string avatarId, string maxId, int limit, CancellationToken token) { var result = new WebResult(); if (!await Owns(rootUserId, avatarId, token)) return result.Invalidate(_localizer["Avatar not found."], StatusCodes.Status404NotFound); var query = _dbEntities.TimelineEntries.Match(e => e.AvatarId == avatarId); if (!string.IsNullOrEmpty(maxId)) query.Match(f => f.Lt(e => e.PostId, maxId)); var entries = await query.Sort(e => e.PostId, Order.Descending).Limit(Math.Clamp(limit, 1, MaxLimit)).ExecuteAsync(token); var ids = entries.Select(e => e.PostId).Concat(entries.Where(e => e.ReblogOfPostId != default).Select(e => e.ReblogOfPostId)).Distinct().ToList(); var posts = ids.Count == 0 ? new Dictionary() : (await _dbEntities.Posts.Match(p => ids.Contains(p.ID)).Match(VisibilityPolicy.IsShown).ExecuteAsync(token)).ToDictionary(p => p.ID); result.Data = entries .Where(e => posts.ContainsKey(e.PostId) && (e.ReblogOfPostId == default || posts.ContainsKey(e.ReblogOfPostId))) .Select(e => { var view = ToView(posts[e.PostId]); if (e.ReblogOfPostId != default) view.ReblogOf = ToView(posts[e.ReblogOfPostId]); return view; }) .ToList(); return result; } public async Task Notifications(string rootUserId, string avatarId, string maxId, int limit, CancellationToken token) { var result = new WebResult(); if (!await Owns(rootUserId, avatarId, token)) return result.Invalidate(_localizer["Avatar not found."], StatusCodes.Status404NotFound); var query = _dbEntities.Notifications.Match(n => n.AvatarId == avatarId); if (!string.IsNullOrEmpty(maxId)) query.Match(f => f.Lt(n => n.ID, maxId)); result.Data = (await query.Sort(n => n.ID, Order.Descending).Limit(Math.Clamp(limit, 1, MaxLimit)).ExecuteAsync(token)) .Select(n => new ViewNotification { Id = n.ID, Type = n.Type.ToString(), FromAccountId = n.FromAccountId, FromActorURI = n.FromActorURI, PostId = n.PostId, IsRead = n.IsRead, CreatedAt = n.CreatedAt }) .ToList(); return result; } public async Task MarkNotificationsRead(string rootUserId, string avatarId, CancellationToken token) { var result = new WebResult(); if (!await Owns(rootUserId, avatarId, token)) return result.Invalidate(_localizer["Avatar not found."], StatusCodes.Status404NotFound); await DB.Default.Update().Match(n => n.AvatarId == avatarId && !n.IsRead).Modify(n => n.IsRead, true).ExecuteAsync(token); return result; } async Task Owns(string rootUserId, string avatarId, CancellationToken token) => !string.IsNullOrEmpty(rootUserId) && !string.IsNullOrEmpty(avatarId) && await _dbEntities.RootToAvatars.Match(ra => ra.RootId == rootUserId && ra.AvatarId == avatarId).ExecuteAnyAsync(token); static ViewPost ToView(Post post) => new() { Id = post.ID, ObjectURI = post.ObjectURI, AuthorAvatarId = post.IsFederatedCopy ? default : post.GroupUserId, AuthorActorURI = post.ActorURI, GroupId = post.GroupId, DmGroupId = post.ConversationId, Visibility = post.Visibility.ToString(), AnsweringToPostId = post.AnsweringToPostId, Title = post.Title, Text = post.Text, ContentHtml = post.ContentHtml, HasContentWarning = post.HasContentWarning, IsFederatedCopy = post.IsFederatedCopy, CreationDate = post.CreationDate }; } }