[backend/core] Denormalize like counts (ISH-110)

This commit is contained in:
Laura Hausmann 2024-03-04 02:38:04 +01:00
parent fe06c64242
commit e42812d2b0
No known key found for this signature in database
GPG key ID: D044E84C5BE01605
9 changed files with 6228 additions and 21 deletions

View file

@ -33,8 +33,6 @@ public class NoteRenderer(
text += $"\n\nRE: {quoteUri}"; //TODO: render as inline quote text += $"\n\nRE: {quoteUri}"; //TODO: render as inline quote
} }
var likeCount = data?.LikeCounts?.GetValueOrDefault(note.Id, 0) ??
await db.NoteLikes.CountAsync(p => p.Note == note);
var liked = data?.LikedNotes?.Contains(note.Id) ?? var liked = data?.LikedNotes?.Contains(note.Id) ??
await db.NoteLikes.AnyAsync(p => p.Note == note && p.User == user); await db.NoteLikes.AnyAsync(p => p.Note == note && p.User == user);
var renoted = data?.Renotes?.Contains(note.Id) ?? var renoted = data?.Renotes?.Contains(note.Id) ??
@ -82,7 +80,7 @@ public class NoteRenderer(
EditedAt = note.UpdatedAt?.ToStringIso8601Like(), EditedAt = note.UpdatedAt?.ToStringIso8601Like(),
RepliesCount = note.RepliesCount, RepliesCount = note.RepliesCount,
RenoteCount = note.RenoteCount, RenoteCount = note.RenoteCount,
FavoriteCount = likeCount, FavoriteCount = note.LikeCount,
IsFavorited = liked, IsFavorited = liked,
IsRenoted = renoted, IsRenoted = renoted,
IsBookmarked = false, //FIXME IsBookmarked = false, //FIXME
@ -133,14 +131,6 @@ public class NoteRenderer(
return (await userRenderer.RenderManyAsync(users.DistinctBy(p => p.Id))).ToList(); return (await userRenderer.RenderManyAsync(users.DistinctBy(p => p.Id))).ToList();
} }
private async Task<Dictionary<string, int>> GetLikeCounts(IEnumerable<Note> notes)
{
return await db.NoteLikes.Where(p => notes.Contains(p.Note))
.Select(p => p.NoteId)
.GroupBy(p => p)
.ToDictionaryAsync(p => p.First(), p => p.Count());
}
private async Task<List<string>> GetLikedNotes(IEnumerable<Note> notes, User? user) private async Task<List<string>> GetLikedNotes(IEnumerable<Note> notes, User? user)
{ {
if (user == null) return []; if (user == null) return [];
@ -193,7 +183,6 @@ public class NoteRenderer(
Accounts = accounts ?? await GetAccounts(noteList.Select(p => p.User)), Accounts = accounts ?? await GetAccounts(noteList.Select(p => p.User)),
Mentions = await GetMentions(noteList), Mentions = await GetMentions(noteList),
Attachments = await GetAttachments(noteList), Attachments = await GetAttachments(noteList),
LikeCounts = await GetLikeCounts(noteList),
LikedNotes = await GetLikedNotes(noteList, user), LikedNotes = await GetLikedNotes(noteList, user),
Renotes = await GetRenotes(noteList, user), Renotes = await GetRenotes(noteList, user),
Emoji = await GetEmoji(noteList) Emoji = await GetEmoji(noteList)
@ -204,13 +193,12 @@ public class NoteRenderer(
public class NoteRendererDto public class NoteRendererDto
{ {
public List<AccountEntity>? Accounts; public List<AccountEntity>? Accounts;
public List<MentionEntity>? Mentions; public List<MentionEntity>? Mentions;
public List<AttachmentEntity>? Attachments; public List<AttachmentEntity>? Attachments;
public Dictionary<string, int>? LikeCounts; public List<string>? LikedNotes;
public List<string>? LikedNotes; public List<string>? Renotes;
public List<string>? Renotes; public List<EmojiEntity>? Emoji;
public List<EmojiEntity>? Emoji; public bool Source;
public bool Source;
} }
} }

View file

@ -96,6 +96,7 @@ public class StatusController(
throw GracefulException.RecordNotFound(); throw GracefulException.RecordNotFound();
await noteSvc.LikeNoteAsync(note, user); await noteSvc.LikeNoteAsync(note, user);
note.LikeCount++; // we do not want to call save changes after this point
return await GetNote(id); return await GetNote(id);
} }
@ -113,6 +114,7 @@ public class StatusController(
throw GracefulException.RecordNotFound(); throw GracefulException.RecordNotFound();
await noteSvc.UnlikeNoteAsync(note, user); await noteSvc.UnlikeNoteAsync(note, user);
note.LikeCount--; // we do not want to call save changes after this point
return await GetNote(id); return await GetNote(id);
} }

View file

@ -609,6 +609,7 @@ public class DatabaseContext(DbContextOptions<DatabaseContext> options)
entity.Property(e => e.Emojis).HasDefaultValueSql("'{}'::character varying[]"); entity.Property(e => e.Emojis).HasDefaultValueSql("'{}'::character varying[]");
entity.Property(e => e.FileIds).HasDefaultValueSql("'{}'::character varying[]"); entity.Property(e => e.FileIds).HasDefaultValueSql("'{}'::character varying[]");
entity.Property(e => e.HasPoll).HasDefaultValue(false); entity.Property(e => e.HasPoll).HasDefaultValue(false);
entity.Property(e => e.LikeCount).HasDefaultValue(0);
entity.Property(e => e.LocalOnly).HasDefaultValue(false); entity.Property(e => e.LocalOnly).HasDefaultValue(false);
entity.Property(e => e.MentionedRemoteUsers).HasDefaultValueSql("'[]'::jsonb"); entity.Property(e => e.MentionedRemoteUsers).HasDefaultValueSql("'[]'::jsonb");
entity.Property(e => e.Mentions).HasDefaultValueSql("'{}'::character varying[]"); entity.Property(e => e.Mentions).HasDefaultValueSql("'{}'::character varying[]");

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,29 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Iceshrimp.Backend.Core.Database.Migrations
{
/// <inheritdoc />
public partial class AddNoteLikeCountColumn : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<int>(
name: "likeCount",
table: "note",
type: "integer",
nullable: false,
defaultValue: 0);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "likeCount",
table: "note");
}
}
}

View file

@ -2457,6 +2457,12 @@ namespace Iceshrimp.Backend.Core.Database.Migrations
.HasDefaultValue(false) .HasDefaultValue(false)
.HasColumnName("hasPoll"); .HasColumnName("hasPoll");
b.Property<int>("LikeCount")
.ValueGeneratedOnAdd()
.HasColumnType("integer")
.HasDefaultValue(0)
.HasColumnName("likeCount");
b.Property<bool>("LocalOnly") b.Property<bool>("LocalOnly")
.ValueGeneratedOnAdd() .ValueGeneratedOnAdd()
.HasColumnType("boolean") .HasColumnType("boolean")

View file

@ -86,6 +86,8 @@ public class Note : IEntity
[Column("repliesCount")] public short RepliesCount { get; set; } [Column("repliesCount")] public short RepliesCount { get; set; }
[Column("likeCount")] public int LikeCount { get; set; }
[Column("reactions", TypeName = "jsonb")] [Column("reactions", TypeName = "jsonb")]
public Dictionary<string, long> Reactions { get; set; } = null!; public Dictionary<string, long> Reactions { get; set; } = null!;

View file

@ -10,7 +10,9 @@ public class DatabaseMaintenanceService(DatabaseContext db)
await db.Notes.ExecuteUpdateAsync(p => p.SetProperty(n => n.RenoteCount, await db.Notes.ExecuteUpdateAsync(p => p.SetProperty(n => n.RenoteCount,
n => db.Notes.Count(r => r.IsPureRenote && r.Renote == n)) n => db.Notes.Count(r => r.IsPureRenote && r.Renote == n))
.SetProperty(n => n.RepliesCount, .SetProperty(n => n.RepliesCount,
n => db.Notes.Count(r => r.Reply == n))); n => db.Notes.Count(r => r.Reply == n))
.SetProperty(n => n.LikeCount,
n => db.NoteLikes.Count(r => r.Note == n)));
//TODO: update reaction counts as well? (can likely not be done database-side :/) //TODO: update reaction counts as well? (can likely not be done database-side :/)
} }

View file

@ -727,6 +727,9 @@ public class NoteService(
await db.NoteLikes.AddAsync(like); await db.NoteLikes.AddAsync(like);
await db.SaveChangesAsync(); await db.SaveChangesAsync();
await db.Notes.Where(p => p.Id == note.Id)
.ExecuteUpdateAsync(p => p.SetProperty(n => n.LikeCount, n => n.LikeCount + 1));
if (user.Host == null && note.UserHost != null) if (user.Host == null && note.UserHost != null)
{ {
var activity = activityRenderer.RenderLike(note, user); var activity = activityRenderer.RenderLike(note, user);
@ -742,6 +745,10 @@ public class NoteService(
{ {
var count = await db.NoteLikes.Where(p => p.Note == note && p.User == actor).ExecuteDeleteAsync(); var count = await db.NoteLikes.Where(p => p.Note == note && p.User == actor).ExecuteDeleteAsync();
if (count == 0) return; if (count == 0) return;
await db.Notes.Where(p => p.Id == note.Id)
.ExecuteUpdateAsync(p => p.SetProperty(n => n.LikeCount, n => n.LikeCount - count));
if (actor.Host == null && note.UserHost != null) if (actor.Host == null && note.UserHost != null)
{ {
var activity = activityRenderer.RenderUndo(userRenderer.RenderLite(actor), var activity = activityRenderer.RenderUndo(userRenderer.RenderLite(actor),