[backend/core] Emit notification on note edit for users that have interacted with the note
This commit is contained in:
parent
be00d5237f
commit
6044cdb52c
13 changed files with 12275 additions and 7 deletions
|
@ -40,7 +40,8 @@ public class NotificationController(DatabaseContext db, NotificationRenderer not
|
|||
p.Type == NotificationType.Quote ||
|
||||
p.Type == NotificationType.Like ||
|
||||
p.Type == NotificationType.PollEnded ||
|
||||
p.Type == NotificationType.FollowRequestReceived)
|
||||
p.Type == NotificationType.FollowRequestReceived ||
|
||||
p.Type == NotificationType.Edit)
|
||||
.EnsureNoteVisibilityFor(p => p.Note, user)
|
||||
.FilterBlocked(p => p.Notifier, user)
|
||||
.FilterBlocked(p => p.Note, user)
|
||||
|
|
|
@ -27,6 +27,7 @@ public class NotificationEntity : IEntity
|
|||
NotificationType.Like => "favourite",
|
||||
NotificationType.PollEnded => "poll",
|
||||
NotificationType.FollowRequestReceived => "follow_request",
|
||||
NotificationType.Edit => "update",
|
||||
|
||||
_ => throw new GracefulException($"Unsupported notification type: {type}")
|
||||
};
|
||||
|
|
6017
Iceshrimp.Backend/Core/Database/Migrations/20240217040655_AddEditNotificationType.Designer.cs
generated
Normal file
6017
Iceshrimp.Backend/Core/Database/Migrations/20240217040655_AddEditNotificationType.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load diff
|
@ -0,0 +1,50 @@
|
|||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Iceshrimp.Backend.Core.Database.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class AddEditNotificationType : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AlterDatabase()
|
||||
.Annotation("Npgsql:Enum:antenna_src_enum", "home,all,users,list,group,instances")
|
||||
.Annotation("Npgsql:Enum:note_visibility_enum", "public,home,followers,specified")
|
||||
.Annotation("Npgsql:Enum:notification_type_enum", "follow,mention,reply,renote,quote,like,reaction,pollVote,pollEnded,receiveFollowRequest,followRequestAccepted,groupInvited,app,edit")
|
||||
.Annotation("Npgsql:Enum:page_visibility_enum", "public,followers,specified")
|
||||
.Annotation("Npgsql:Enum:relay_status_enum", "requesting,accepted,rejected")
|
||||
.Annotation("Npgsql:Enum:user_profile_ffvisibility_enum", "public,followers,private")
|
||||
.Annotation("Npgsql:PostgresExtension:pg_trgm", ",,")
|
||||
.OldAnnotation("Npgsql:Enum:antenna_src_enum", "home,all,users,list,group,instances")
|
||||
.OldAnnotation("Npgsql:Enum:note_visibility_enum", "public,home,followers,specified")
|
||||
.OldAnnotation("Npgsql:Enum:notification_type_enum", "follow,mention,reply,renote,quote,like,reaction,pollVote,pollEnded,receiveFollowRequest,followRequestAccepted,groupInvited,app")
|
||||
.OldAnnotation("Npgsql:Enum:page_visibility_enum", "public,followers,specified")
|
||||
.OldAnnotation("Npgsql:Enum:relay_status_enum", "requesting,accepted,rejected")
|
||||
.OldAnnotation("Npgsql:Enum:user_profile_ffvisibility_enum", "public,followers,private")
|
||||
.OldAnnotation("Npgsql:PostgresExtension:pg_trgm", ",,");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AlterDatabase()
|
||||
.Annotation("Npgsql:Enum:antenna_src_enum", "home,all,users,list,group,instances")
|
||||
.Annotation("Npgsql:Enum:note_visibility_enum", "public,home,followers,specified")
|
||||
.Annotation("Npgsql:Enum:notification_type_enum", "follow,mention,reply,renote,quote,like,reaction,pollVote,pollEnded,receiveFollowRequest,followRequestAccepted,groupInvited,app")
|
||||
.Annotation("Npgsql:Enum:page_visibility_enum", "public,followers,specified")
|
||||
.Annotation("Npgsql:Enum:relay_status_enum", "requesting,accepted,rejected")
|
||||
.Annotation("Npgsql:Enum:user_profile_ffvisibility_enum", "public,followers,private")
|
||||
.Annotation("Npgsql:PostgresExtension:pg_trgm", ",,")
|
||||
.OldAnnotation("Npgsql:Enum:antenna_src_enum", "home,all,users,list,group,instances")
|
||||
.OldAnnotation("Npgsql:Enum:note_visibility_enum", "public,home,followers,specified")
|
||||
.OldAnnotation("Npgsql:Enum:notification_type_enum", "follow,mention,reply,renote,quote,like,reaction,pollVote,pollEnded,receiveFollowRequest,followRequestAccepted,groupInvited,app,edit")
|
||||
.OldAnnotation("Npgsql:Enum:page_visibility_enum", "public,followers,specified")
|
||||
.OldAnnotation("Npgsql:Enum:relay_status_enum", "requesting,accepted,rejected")
|
||||
.OldAnnotation("Npgsql:Enum:user_profile_ffvisibility_enum", "public,followers,private")
|
||||
.OldAnnotation("Npgsql:PostgresExtension:pg_trgm", ",,");
|
||||
}
|
||||
}
|
||||
}
|
6017
Iceshrimp.Backend/Core/Database/Migrations/20240217044102_RenameNoteBookmarksTable2.Designer.cs
generated
Normal file
6017
Iceshrimp.Backend/Core/Database/Migrations/20240217044102_RenameNoteBookmarksTable2.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load diff
|
@ -0,0 +1,122 @@
|
|||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Iceshrimp.Backend.Core.Database.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class RenameNoteBookmarksTable2 : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropForeignKey(
|
||||
name: "FK_note_favorite_note_noteId",
|
||||
table: "note_favorite");
|
||||
|
||||
migrationBuilder.DropForeignKey(
|
||||
name: "FK_note_favorite_user_userId",
|
||||
table: "note_favorite");
|
||||
|
||||
migrationBuilder.DropPrimaryKey(
|
||||
name: "PK_note_favorite",
|
||||
table: "note_favorite");
|
||||
|
||||
migrationBuilder.RenameTable(
|
||||
name: "note_favorite",
|
||||
newName: "note_bookmark");
|
||||
|
||||
migrationBuilder.RenameIndex(
|
||||
name: "IX_note_favorite_userId_noteId",
|
||||
table: "note_bookmark",
|
||||
newName: "IX_note_bookmark_userId_noteId");
|
||||
|
||||
migrationBuilder.RenameIndex(
|
||||
name: "IX_note_favorite_userId",
|
||||
table: "note_bookmark",
|
||||
newName: "IX_note_bookmark_userId");
|
||||
|
||||
migrationBuilder.RenameIndex(
|
||||
name: "IX_note_favorite_noteId",
|
||||
table: "note_bookmark",
|
||||
newName: "IX_note_bookmark_noteId");
|
||||
|
||||
migrationBuilder.AddPrimaryKey(
|
||||
name: "PK_note_bookmark",
|
||||
table: "note_bookmark",
|
||||
column: "id");
|
||||
|
||||
migrationBuilder.AddForeignKey(
|
||||
name: "FK_note_bookmark_note_noteId",
|
||||
table: "note_bookmark",
|
||||
column: "noteId",
|
||||
principalTable: "note",
|
||||
principalColumn: "id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
|
||||
migrationBuilder.AddForeignKey(
|
||||
name: "FK_note_bookmark_user_userId",
|
||||
table: "note_bookmark",
|
||||
column: "userId",
|
||||
principalTable: "user",
|
||||
principalColumn: "id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropForeignKey(
|
||||
name: "FK_note_bookmark_note_noteId",
|
||||
table: "note_bookmark");
|
||||
|
||||
migrationBuilder.DropForeignKey(
|
||||
name: "FK_note_bookmark_user_userId",
|
||||
table: "note_bookmark");
|
||||
|
||||
migrationBuilder.DropPrimaryKey(
|
||||
name: "PK_note_bookmark",
|
||||
table: "note_bookmark");
|
||||
|
||||
migrationBuilder.RenameTable(
|
||||
name: "note_bookmark",
|
||||
newName: "note_favorite");
|
||||
|
||||
migrationBuilder.RenameIndex(
|
||||
name: "IX_note_bookmark_userId_noteId",
|
||||
table: "note_favorite",
|
||||
newName: "IX_note_favorite_userId_noteId");
|
||||
|
||||
migrationBuilder.RenameIndex(
|
||||
name: "IX_note_bookmark_userId",
|
||||
table: "note_favorite",
|
||||
newName: "IX_note_favorite_userId");
|
||||
|
||||
migrationBuilder.RenameIndex(
|
||||
name: "IX_note_bookmark_noteId",
|
||||
table: "note_favorite",
|
||||
newName: "IX_note_favorite_noteId");
|
||||
|
||||
migrationBuilder.AddPrimaryKey(
|
||||
name: "PK_note_favorite",
|
||||
table: "note_favorite",
|
||||
column: "id");
|
||||
|
||||
migrationBuilder.AddForeignKey(
|
||||
name: "FK_note_favorite_note_noteId",
|
||||
table: "note_favorite",
|
||||
column: "noteId",
|
||||
principalTable: "note",
|
||||
principalColumn: "id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
|
||||
migrationBuilder.AddForeignKey(
|
||||
name: "FK_note_favorite_user_userId",
|
||||
table: "note_favorite",
|
||||
column: "userId",
|
||||
principalTable: "user",
|
||||
principalColumn: "id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -25,7 +25,7 @@ namespace Iceshrimp.Backend.Core.Database.Migrations
|
|||
|
||||
NpgsqlModelBuilderExtensions.HasPostgresEnum(modelBuilder, "antenna_src_enum", new[] { "home", "all", "users", "list", "group", "instances" });
|
||||
NpgsqlModelBuilderExtensions.HasPostgresEnum(modelBuilder, "note_visibility_enum", new[] { "public", "home", "followers", "specified" });
|
||||
NpgsqlModelBuilderExtensions.HasPostgresEnum(modelBuilder, "notification_type_enum", new[] { "follow", "mention", "reply", "renote", "quote", "like", "reaction", "pollVote", "pollEnded", "receiveFollowRequest", "followRequestAccepted", "groupInvited", "app" });
|
||||
NpgsqlModelBuilderExtensions.HasPostgresEnum(modelBuilder, "notification_type_enum", new[] { "follow", "mention", "reply", "renote", "quote", "like", "reaction", "pollVote", "pollEnded", "receiveFollowRequest", "followRequestAccepted", "groupInvited", "app", "edit" });
|
||||
NpgsqlModelBuilderExtensions.HasPostgresEnum(modelBuilder, "page_visibility_enum", new[] { "public", "followers", "specified" });
|
||||
NpgsqlModelBuilderExtensions.HasPostgresEnum(modelBuilder, "relay_status_enum", new[] { "requesting", "accepted", "rejected" });
|
||||
NpgsqlModelBuilderExtensions.HasPostgresEnum(modelBuilder, "user_profile_ffvisibility_enum", new[] { "public", "followers", "private" });
|
||||
|
@ -2640,7 +2640,7 @@ namespace Iceshrimp.Backend.Core.Database.Migrations
|
|||
b.HasIndex("UserId", "NoteId")
|
||||
.IsUnique();
|
||||
|
||||
b.ToTable("note_favorite");
|
||||
b.ToTable("note_bookmark");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Iceshrimp.Backend.Core.Database.Tables.NoteEdit", b =>
|
||||
|
@ -3490,7 +3490,7 @@ namespace Iceshrimp.Backend.Core.Database.Migrations
|
|||
b.ToTable("promo_read");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Iceshrimp.Backend.Core.Database.Tables.RegistrationTicket", b =>
|
||||
modelBuilder.Entity("Iceshrimp.Backend.Core.Database.Tables.RegistrationInvite", b =>
|
||||
{
|
||||
b.Property<string>("Id")
|
||||
.HasMaxLength(32)
|
||||
|
|
|
@ -4,7 +4,7 @@ using Microsoft.EntityFrameworkCore;
|
|||
|
||||
namespace Iceshrimp.Backend.Core.Database.Tables;
|
||||
|
||||
[Table("note_favorite")]
|
||||
[Table("note_bookmark")]
|
||||
[Index("UserId", "NoteId", IsUnique = true)]
|
||||
[Index("UserId")]
|
||||
public class NoteBookmark
|
||||
|
|
|
@ -29,7 +29,8 @@ public class Notification : IEntity
|
|||
[PgName("receiveFollowRequest")] FollowRequestReceived,
|
||||
[PgName("followRequestAccepted")] FollowRequestAccepted,
|
||||
[PgName("groupInvited")] GroupInvited,
|
||||
[PgName("app")] App
|
||||
[PgName("app")] App,
|
||||
[PgName("edit")] Edit
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
|
@ -384,12 +384,20 @@ public class User : IEntity
|
|||
[InverseProperty(nameof(NoteBookmark.User))]
|
||||
public virtual ICollection<NoteBookmark> NoteBookmarks { get; set; } = new List<NoteBookmark>();
|
||||
|
||||
[NotMapped] [Projectable] public virtual IEnumerable<Note> BookmarkedNotes => NoteBookmarks.Select(p => p.Note);
|
||||
|
||||
[InverseProperty(nameof(NoteReaction.User))]
|
||||
public virtual ICollection<NoteLike> NoteLikes { get; set; } = new List<NoteLike>();
|
||||
|
||||
[NotMapped] [Projectable] public virtual IEnumerable<Note> LikedNotes => NoteLikes.Select(p => p.Note);
|
||||
|
||||
[InverseProperty(nameof(NoteReaction.User))]
|
||||
public virtual ICollection<NoteReaction> NoteReactions { get; set; } = new List<NoteReaction>();
|
||||
|
||||
[NotMapped]
|
||||
[Projectable]
|
||||
public virtual IEnumerable<Note> ReactedNotes => NoteReactions.Select(p => p.Note).Distinct();
|
||||
|
||||
[InverseProperty(nameof(NoteThreadMuting.User))]
|
||||
public virtual ICollection<NoteThreadMuting> NoteThreadMutings { get; set; } = new List<NoteThreadMuting>();
|
||||
|
||||
|
@ -530,6 +538,29 @@ public class User : IEntity
|
|||
[Projectable]
|
||||
public bool HasPinned(Note note) => PinnedNotes.Contains(note);
|
||||
|
||||
[Projectable]
|
||||
public bool HasBookmarked(Note note) => BookmarkedNotes.Contains(note);
|
||||
|
||||
[Projectable]
|
||||
public bool HasLiked(Note note) => LikedNotes.Contains(note);
|
||||
|
||||
[Projectable]
|
||||
public bool HasReacted(Note note) => ReactedNotes.Contains(note);
|
||||
|
||||
[Projectable]
|
||||
public bool HasRenoted(Note note) => Notes.Any(p => p.Renote == note);
|
||||
|
||||
[Projectable]
|
||||
public bool HasReplied(Note note) => Notes.Any(p => p.Reply == note);
|
||||
|
||||
[Projectable]
|
||||
public bool HasInteractedWith(Note note) =>
|
||||
HasLiked(note) ||
|
||||
HasReacted(note) ||
|
||||
HasBookmarked(note) ||
|
||||
HasReplied(note) ||
|
||||
HasRenoted(note);
|
||||
|
||||
public User WithPrecomputedBlockStatus(bool blocking, bool blockedBy)
|
||||
{
|
||||
PrecomputedIsBlocking = blocking;
|
||||
|
|
|
@ -99,7 +99,7 @@ public class ASUpdate : ASActivity
|
|||
[J($"{Constants.ActivityStreamsNs}#cc")]
|
||||
public List<ASObjectBase>? Cc { get; set; }
|
||||
|
||||
[J($"{Constants.ActivityStreamsNs}#object")]
|
||||
[JI]
|
||||
public new ASObject? Object
|
||||
{
|
||||
get => base.Object;
|
||||
|
|
|
@ -179,6 +179,7 @@ public class NoteService(
|
|||
await db.SaveChangesAsync();
|
||||
await notificationSvc.GenerateMentionNotifications(note, mentionedLocalUserIds);
|
||||
await notificationSvc.GenerateReplyNotifications(note, mentionedLocalUserIds);
|
||||
await notificationSvc.GenerateEditNotifications(note);
|
||||
eventSvc.RaiseNoteUpdated(this, note);
|
||||
|
||||
var actor = userRenderer.RenderLite(note.User);
|
||||
|
@ -405,6 +406,7 @@ public class NoteService(
|
|||
await db.SaveChangesAsync();
|
||||
await notificationSvc.GenerateMentionNotifications(dbNote, mentionedLocalUserIds);
|
||||
await notificationSvc.GenerateReplyNotifications(dbNote, mentionedLocalUserIds);
|
||||
await notificationSvc.GenerateEditNotifications(dbNote);
|
||||
eventSvc.RaiseNoteUpdated(this, dbNote);
|
||||
return dbNote;
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@ using System.Diagnostics.CodeAnalysis;
|
|||
using Iceshrimp.Backend.Core.Database;
|
||||
using Iceshrimp.Backend.Core.Database.Tables;
|
||||
using Iceshrimp.Backend.Core.Helpers;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace Iceshrimp.Backend.Core.Services;
|
||||
|
||||
|
@ -59,6 +60,31 @@ public class NotificationService(
|
|||
eventSvc.RaiseNotifications(this, notifications);
|
||||
}
|
||||
|
||||
[SuppressMessage("ReSharper", "EntityFramework.UnsupportedServerSideFunctionCall",
|
||||
Justification = "Projectable functions are very much translatable")]
|
||||
public async Task GenerateEditNotifications(Note note)
|
||||
{
|
||||
var notifications = await db.Users
|
||||
.Where(p => p.Host == null && p != note.User && p.HasInteractedWith(note))
|
||||
.Select(p => new Notification
|
||||
{
|
||||
Id = IdHelpers.GenerateSlowflakeId(DateTime.UtcNow),
|
||||
CreatedAt = DateTime.UtcNow,
|
||||
Note = note,
|
||||
NotifierId = note.UserId,
|
||||
Notifiee = p,
|
||||
Type = Notification.NotificationType.Edit
|
||||
})
|
||||
.ToListAsync();
|
||||
|
||||
if (notifications.Count == 0)
|
||||
return;
|
||||
|
||||
await db.AddRangeAsync(notifications);
|
||||
await db.SaveChangesAsync();
|
||||
eventSvc.RaiseNotifications(this, notifications);
|
||||
}
|
||||
|
||||
public async Task GenerateLikeNotification(Note note, User user)
|
||||
{
|
||||
if (note.UserHost != null) return;
|
||||
|
|
Loading…
Add table
Reference in a new issue