From d5b7fa5ae758e94472f83e1b0d2cc28ddc2dddf8 Mon Sep 17 00:00:00 2001 From: Laura Hausmann Date: Mon, 18 Mar 2024 16:23:52 +0100 Subject: [PATCH] [backend/api] Correctly render replies/renotes/quotes (ISH-205) --- .../Controllers/Renderers/NoteRenderer.cs | 47 +++++++++++++++++-- .../Controllers/Schemas/NoteResponse.cs | 14 +++--- .../Core/Database/Tables/Note.cs | 4 +- .../Core/Database/Tables/Notification.cs | 4 +- .../Core/Extensions/QueryableExtensions.cs | 27 ++++++++++- 5 files changed, 82 insertions(+), 14 deletions(-) diff --git a/Iceshrimp.Backend/Controllers/Renderers/NoteRenderer.cs b/Iceshrimp.Backend/Controllers/Renderers/NoteRenderer.cs index ca0abbbd..f5e03524 100644 --- a/Iceshrimp.Backend/Controllers/Renderers/NoteRenderer.cs +++ b/Iceshrimp.Backend/Controllers/Renderers/NoteRenderer.cs @@ -10,6 +10,38 @@ namespace Iceshrimp.Backend.Controllers.Renderers; public class NoteRenderer(UserRenderer userRenderer, DatabaseContext db, EmojiService emojiSvc) { public async Task RenderOne(Note note, User? localUser, NoteRendererDto? data = null) + { + var res = await RenderBaseInternal(note, localUser, data); + + var renote = note.Renote is { IsPureRenote: true } ? await RenderRenote(note.Renote, localUser, data) : null; + var quote = note.Renote is { IsPureRenote: false } ? await RenderBase(note.Renote, localUser, data) : null; + var reply = note.Reply != null ? await RenderBase(note.Reply, localUser, data) : null; + + res.Renote = renote; + res.RenoteId = note.RenoteId; + res.Quote = quote; + res.QuoteId = note.RenoteId; + res.Reply = reply; + res.ReplyId = note.ReplyId; + + return res; + } + + private async Task RenderRenote(Note note, User? localUser, NoteRendererDto? data = null) + { + var res = await RenderBaseInternal(note, localUser, data); + var quote = note.Renote is { IsPureRenote: false } ? await RenderBase(note.Renote, localUser, data) : null; + + res.Quote = quote; + res.QuoteId = note.RenoteId; + + return res; + } + + private async Task RenderBase(Note note, User? localUser, NoteRendererDto? data = null) + => await RenderBaseInternal(note, localUser, data); + + private async Task RenderBaseInternal(Note note, User? localUser, NoteRendererDto? data = null) { var user = (data?.Users ?? await GetUsers([note])).First(p => p.Id == note.User.Id); var attachments = (data?.Attachments ?? await GetAttachments([note])).Where(p => note.FileIds.Contains(p.Id)); @@ -87,14 +119,23 @@ public class NoteRenderer(UserRenderer userRenderer, DatabaseContext db, EmojiSe return res; } + private static List GetAllNotes(IEnumerable notes) + { + return notes.SelectMany(p => [p, p.Reply, p.Renote, p.Renote?.Renote]) + .OfType() + .Distinct() + .ToList(); + } + public async Task> RenderMany(IEnumerable notes, User? user) { var notesList = notes.ToList(); + var allNotes = GetAllNotes(notesList); var data = new NoteRendererDto { - Users = await GetUsers(notesList), - Attachments = await GetAttachments(notesList), - Reactions = await GetReactions(notesList, user) + Users = await GetUsers(allNotes), + Attachments = await GetAttachments(allNotes), + Reactions = await GetReactions(allNotes, user) }; return await notesList.Select(p => RenderOne(p, user, data)).AwaitAllAsync(); diff --git a/Iceshrimp.Backend/Controllers/Schemas/NoteResponse.cs b/Iceshrimp.Backend/Controllers/Schemas/NoteResponse.cs index 0ae6f4b7..bc649ee9 100644 --- a/Iceshrimp.Backend/Controllers/Schemas/NoteResponse.cs +++ b/Iceshrimp.Backend/Controllers/Schemas/NoteResponse.cs @@ -3,16 +3,18 @@ using JI = System.Text.Json.Serialization.JsonIgnoreAttribute; namespace Iceshrimp.Backend.Controllers.Schemas; -public class NoteResponse : NoteBase +public class NoteResponse : NoteWithQuote { - [J("reply")] public NoteBase? Reply { get; set; } - [J("renote")] public NoteRenote? Renote { get; set; } - [J("quote")] public NoteBase? Quote { get; set; } + [J("reply")] public NoteBase? Reply { get; set; } + [J("replyId")] public string? ReplyId { get; set; } + [J("renote")] public NoteWithQuote? Renote { get; set; } + [J("renoteId")] public string? RenoteId { get; set; } } -public class NoteRenote : NoteBase +public class NoteWithQuote : NoteBase { - [J("quote")] public NoteBase? Quote { get; set; } + [J("quote")] public NoteBase? Quote { get; set; } + [J("quoteId")] public string? QuoteId { get; set; } } public class NoteBase diff --git a/Iceshrimp.Backend/Core/Database/Tables/Note.cs b/Iceshrimp.Backend/Core/Database/Tables/Note.cs index 64150894..42b3db57 100644 --- a/Iceshrimp.Backend/Core/Database/Tables/Note.cs +++ b/Iceshrimp.Backend/Core/Database/Tables/Note.cs @@ -280,12 +280,14 @@ public class Note : IEntity (Visibility == NoteVisibility.Followers && (User.IsFollowedBy(user) || ReplyUserId == user.Id)); - public Note WithPrecomputedVisibilities(bool reply, bool renote) + public Note WithPrecomputedVisibilities(bool reply, bool renote, bool renoteRenote) { if (Reply != null) PrecomputedIsReplyVisible = reply; if (Renote != null) PrecomputedIsRenoteVisible = renote; + if (Renote?.Renote != null) + Renote.PrecomputedIsRenoteVisible = renoteRenote; return this; } diff --git a/Iceshrimp.Backend/Core/Database/Tables/Notification.cs b/Iceshrimp.Backend/Core/Database/Tables/Notification.cs index db0dea5d..3d532cfe 100644 --- a/Iceshrimp.Backend/Core/Database/Tables/Notification.cs +++ b/Iceshrimp.Backend/Core/Database/Tables/Notification.cs @@ -126,9 +126,9 @@ public class Notification : IEntity [StringLength(32)] public string Id { get; set; } = null!; - public Notification WithPrecomputedNoteVisibilities(bool reply, bool renote) + public Notification WithPrecomputedNoteVisibilities(bool reply, bool renote, bool renoteRenote) { - Note = Note?.WithPrecomputedVisibilities(reply, renote); + Note = Note?.WithPrecomputedVisibilities(reply, renote, renoteRenote); return this; } } \ No newline at end of file diff --git a/Iceshrimp.Backend/Core/Extensions/QueryableExtensions.cs b/Iceshrimp.Backend/Core/Extensions/QueryableExtensions.cs index 9bb79266..8b748ff9 100644 --- a/Iceshrimp.Backend/Core/Extensions/QueryableExtensions.cs +++ b/Iceshrimp.Backend/Core/Extensions/QueryableExtensions.cs @@ -225,7 +225,10 @@ public static class QueryableExtensions { return query.Select(p => p.WithPrecomputedVisibilities(p.Reply != null && p.Reply.IsVisibleFor(user), p.Renote != null && - p.Renote.IsVisibleFor(user))); + p.Renote.IsVisibleFor(user), + p.Renote != null && + p.Renote.Renote != null && + p.Renote.Renote.IsVisibleFor(user))); } public static IQueryable PrecomputeNoteVisibilities(this IQueryable query, User user) @@ -235,7 +238,11 @@ public static class QueryableExtensions p.Note.Reply.IsVisibleFor(user), p.Note != null && p.Note.Renote != null && - p.Note.Renote.IsVisibleFor(user))); + p.Note.Renote.IsVisibleFor(user), + p.Note != null && + p.Note.Renote != null && + p.Note.Renote.Renote != null && + p.Note.Renote.Renote.IsVisibleFor(user))); } public static IQueryable PrecomputeRelationshipData(this IQueryable query, User user) @@ -251,6 +258,10 @@ public static class QueryableExtensions return query.Where(note => !note.User.IsBlocking(user) && !note.User.IsBlockedBy(user)) .Where(note => note.Renote == null || (!note.Renote.User.IsBlockedBy(user) && !note.Renote.User.IsBlocking(user))) + .Where(note => note.Renote == null || + note.Renote.Renote == null || + (!note.Renote.Renote.User.IsBlockedBy(user) && + !note.Renote.Renote.User.IsBlocking(user))) .Where(note => note.Reply == null || (!note.Reply.User.IsBlockedBy(user) && !note.Reply.User.IsBlocking(user))); } @@ -275,6 +286,10 @@ public static class QueryableExtensions (note.Renote == null || (!note.Renote.User.IsBlockedBy(user) && !note.Renote.User.IsBlocking(user))) && + (note.Renote == null || + note.Renote.Renote == null || + (!note.Renote.Renote.User.IsBlockedBy(user) && + !note.Renote.Renote.User.IsBlocking(user))) && (note.Reply == null || (!note.Reply.User.IsBlockedBy(user) && !note.Reply.User.IsBlocking(user)))))); @@ -286,6 +301,9 @@ public static class QueryableExtensions return query.Where(note => !note.User.IsMuting(user)) .Where(note => note.Renote == null || !note.Renote.User.IsMuting(user)) + .Where(note => note.Renote == null || + note.Renote.Renote == null || + !note.Renote.Renote.User.IsMuting(user)) .Where(note => note.Reply == null || !note.Reply.User.IsMuting(user)); } @@ -315,6 +333,8 @@ public static class QueryableExtensions note.Reply = null; if (!(note.PrecomputedIsRenoteVisible ?? false)) note.Renote = null; + if (note.Renote?.Renote != null && !(note.Renote.PrecomputedIsRenoteVisible ?? false)) + note.Renote.Renote = null; return note; } @@ -332,6 +352,8 @@ public static class QueryableExtensions note.Reply = null; if (!(note.PrecomputedIsRenoteVisible ?? false)) note.Renote = null; + if (note.Renote?.Renote != null && !(note.Renote.PrecomputedIsRenoteVisible ?? false)) + note.Renote.Renote = null; return source; } @@ -444,6 +466,7 @@ public static class QueryableExtensions { return query.Include(p => p.User.UserProfile) .Include(p => p.Renote.User.UserProfile) + .Include(p => p.Renote.Renote.User.UserProfile) .Include(p => p.Reply.User.UserProfile); }