[backend/api] Correctly render replies/renotes/quotes (ISH-205)
This commit is contained in:
parent
f5aa7f6ff6
commit
d5b7fa5ae7
5 changed files with 82 additions and 14 deletions
|
@ -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();
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Add table
Reference in a new issue