[backend/api] Correctly render replies/renotes/quotes (ISH-205)

This commit is contained in:
Laura Hausmann 2024-03-18 16:23:52 +01:00
parent f5aa7f6ff6
commit d5b7fa5ae7
No known key found for this signature in database
GPG key ID: D044E84C5BE01605
5 changed files with 82 additions and 14 deletions

View file

@ -10,6 +10,38 @@ namespace Iceshrimp.Backend.Controllers.Renderers;
public class NoteRenderer(UserRenderer userRenderer, DatabaseContext db, EmojiService emojiSvc) public class NoteRenderer(UserRenderer userRenderer, DatabaseContext db, EmojiService emojiSvc)
{ {
public async Task<NoteResponse> RenderOne(Note note, User? localUser, NoteRendererDto? data = null) public async Task<NoteResponse> 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<NoteWithQuote> 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<NoteBase> RenderBase(Note note, User? localUser, NoteRendererDto? data = null)
=> await RenderBaseInternal(note, localUser, data);
private async Task<NoteResponse> RenderBaseInternal(Note note, User? localUser, NoteRendererDto? data = null)
{ {
var user = (data?.Users ?? await GetUsers([note])).First(p => p.Id == note.User.Id); 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)); 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; return res;
} }
private static List<Note> GetAllNotes(IEnumerable<Note> notes)
{
return notes.SelectMany<Note, Note?>(p => [p, p.Reply, p.Renote, p.Renote?.Renote])
.OfType<Note>()
.Distinct()
.ToList();
}
public async Task<IEnumerable<NoteResponse>> RenderMany(IEnumerable<Note> notes, User? user) public async Task<IEnumerable<NoteResponse>> RenderMany(IEnumerable<Note> notes, User? user)
{ {
var notesList = notes.ToList(); var notesList = notes.ToList();
var allNotes = GetAllNotes(notesList);
var data = new NoteRendererDto var data = new NoteRendererDto
{ {
Users = await GetUsers(notesList), Users = await GetUsers(allNotes),
Attachments = await GetAttachments(notesList), Attachments = await GetAttachments(allNotes),
Reactions = await GetReactions(notesList, user) Reactions = await GetReactions(allNotes, user)
}; };
return await notesList.Select(p => RenderOne(p, user, data)).AwaitAllAsync(); return await notesList.Select(p => RenderOne(p, user, data)).AwaitAllAsync();

View file

@ -3,16 +3,18 @@ using JI = System.Text.Json.Serialization.JsonIgnoreAttribute;
namespace Iceshrimp.Backend.Controllers.Schemas; namespace Iceshrimp.Backend.Controllers.Schemas;
public class NoteResponse : NoteBase public class NoteResponse : NoteWithQuote
{ {
[J("reply")] public NoteBase? Reply { get; set; } [J("reply")] public NoteBase? Reply { get; set; }
[J("renote")] public NoteRenote? Renote { get; set; } [J("replyId")] public string? ReplyId { get; set; }
[J("quote")] public NoteBase? Quote { 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 public class NoteBase

View file

@ -280,12 +280,14 @@ public class Note : IEntity
(Visibility == NoteVisibility.Followers && (Visibility == NoteVisibility.Followers &&
(User.IsFollowedBy(user) || ReplyUserId == user.Id)); (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) if (Reply != null)
PrecomputedIsReplyVisible = reply; PrecomputedIsReplyVisible = reply;
if (Renote != null) if (Renote != null)
PrecomputedIsRenoteVisible = renote; PrecomputedIsRenoteVisible = renote;
if (Renote?.Renote != null)
Renote.PrecomputedIsRenoteVisible = renoteRenote;
return this; return this;
} }

View file

@ -126,9 +126,9 @@ public class Notification : IEntity
[StringLength(32)] [StringLength(32)]
public string Id { get; set; } = null!; 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; return this;
} }
} }

View file

@ -225,7 +225,10 @@ public static class QueryableExtensions
{ {
return query.Select(p => p.WithPrecomputedVisibilities(p.Reply != null && p.Reply.IsVisibleFor(user), return query.Select(p => p.WithPrecomputedVisibilities(p.Reply != null && p.Reply.IsVisibleFor(user),
p.Renote != null && 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<Notification> PrecomputeNoteVisibilities(this IQueryable<Notification> query, User user) public static IQueryable<Notification> PrecomputeNoteVisibilities(this IQueryable<Notification> query, User user)
@ -235,7 +238,11 @@ public static class QueryableExtensions
p.Note.Reply.IsVisibleFor(user), p.Note.Reply.IsVisibleFor(user),
p.Note != null && p.Note != null &&
p.Note.Renote != 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<User> PrecomputeRelationshipData(this IQueryable<User> query, User user) public static IQueryable<User> PrecomputeRelationshipData(this IQueryable<User> query, User user)
@ -251,6 +258,10 @@ public static class QueryableExtensions
return query.Where(note => !note.User.IsBlocking(user) && !note.User.IsBlockedBy(user)) return query.Where(note => !note.User.IsBlocking(user) && !note.User.IsBlockedBy(user))
.Where(note => note.Renote == null || .Where(note => note.Renote == null ||
(!note.Renote.User.IsBlockedBy(user) && !note.Renote.User.IsBlocking(user))) (!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 || .Where(note => note.Reply == null ||
(!note.Reply.User.IsBlockedBy(user) && !note.Reply.User.IsBlocking(user))); (!note.Reply.User.IsBlockedBy(user) && !note.Reply.User.IsBlocking(user)));
} }
@ -275,6 +286,10 @@ public static class QueryableExtensions
(note.Renote == null || (note.Renote == null ||
(!note.Renote.User.IsBlockedBy(user) && (!note.Renote.User.IsBlockedBy(user) &&
!note.Renote.User.IsBlocking(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 == null ||
(!note.Reply.User.IsBlockedBy(user) && (!note.Reply.User.IsBlockedBy(user) &&
!note.Reply.User.IsBlocking(user)))))); !note.Reply.User.IsBlocking(user))))));
@ -286,6 +301,9 @@ public static class QueryableExtensions
return query.Where(note => !note.User.IsMuting(user)) 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.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)); .Where(note => note.Reply == null || !note.Reply.User.IsMuting(user));
} }
@ -315,6 +333,8 @@ public static class QueryableExtensions
note.Reply = null; note.Reply = null;
if (!(note.PrecomputedIsRenoteVisible ?? false)) if (!(note.PrecomputedIsRenoteVisible ?? false))
note.Renote = null; note.Renote = null;
if (note.Renote?.Renote != null && !(note.Renote.PrecomputedIsRenoteVisible ?? false))
note.Renote.Renote = null;
return note; return note;
} }
@ -332,6 +352,8 @@ public static class QueryableExtensions
note.Reply = null; note.Reply = null;
if (!(note.PrecomputedIsRenoteVisible ?? false)) if (!(note.PrecomputedIsRenoteVisible ?? false))
note.Renote = null; note.Renote = null;
if (note.Renote?.Renote != null && !(note.Renote.PrecomputedIsRenoteVisible ?? false))
note.Renote.Renote = null;
return source; return source;
} }
@ -444,6 +466,7 @@ public static class QueryableExtensions
{ {
return query.Include(p => p.User.UserProfile) return query.Include(p => p.User.UserProfile)
.Include(p => p.Renote.User.UserProfile) .Include(p => p.Renote.User.UserProfile)
.Include(p => p.Renote.Renote.User.UserProfile)
.Include(p => p.Reply.User.UserProfile); .Include(p => p.Reply.User.UserProfile);
} }