feat(federation): Federate Reactions

This commit is contained in:
Jesse Wierzbinski 2025-05-28 17:17:03 +02:00
parent fa1dd69e2d
commit 343a507ecc
No known key found for this signature in database
4 changed files with 84 additions and 6 deletions

View file

@ -236,6 +236,20 @@ export class Reaction extends BaseInterface<typeof Reactions, ReactionType> {
}); });
} }
public toVersiaUnreact(): VersiaEntities.Delete {
return new VersiaEntities.Delete({
type: "Delete",
id: crypto.randomUUID(),
created_at: new Date().toISOString(),
author: User.getUri(
this.data.authorId,
this.data.author.uri ? new URL(this.data.author.uri) : null,
).href,
deleted_type: "pub.versia:reactions/Reaction",
deleted: this.getUri(config.http.base_url).href,
});
}
public static async fromVersia( public static async fromVersia(
reactionToConvert: VersiaEntities.Reaction, reactionToConvert: VersiaEntities.Reaction,
author: User, author: User,

View file

@ -752,7 +752,7 @@ export class User extends BaseInterface<typeof Users, UserWithRelations> {
} }
// Create the reaction // Create the reaction
await Reaction.insert({ const reaction = await Reaction.insert({
id: randomUUIDv7(), id: randomUUIDv7(),
authorId: this.id, authorId: this.id,
noteId: note.id, noteId: note.id,
@ -760,7 +760,29 @@ export class User extends BaseInterface<typeof Users, UserWithRelations> {
emojiId: emoji instanceof Emoji ? emoji.id : null, emojiId: emoji instanceof Emoji ? emoji.id : null,
}); });
// TODO: Handle federation and notifications const finalNote = await Note.fromId(note.id, this.id);
if (!finalNote) {
throw new Error("Failed to fetch note after reaction");
}
if (note.author.local) {
// Notify the user that their post has been reacted to
await note.author.notify("reaction", this, finalNote);
}
if (this.local) {
const federatedUsers = await this.federateToFollowers(
reaction.toVersia(),
);
if (
note.remote &&
!federatedUsers.find((u) => u.id === note.author.id)
) {
await this.federateToUser(reaction.toVersia(), note.author);
}
}
} }
/** /**
@ -777,11 +799,45 @@ export class User extends BaseInterface<typeof Users, UserWithRelations> {
await reactionToDelete.delete(); await reactionToDelete.delete();
// TODO: Handle federation and notifications if (note.author.local) {
// Remove any eventual notifications for this reaction
await db
.delete(Notifications)
.where(
and(
eq(Notifications.accountId, this.id),
eq(Notifications.type, "reaction"),
eq(Notifications.notifiedId, note.data.authorId),
eq(Notifications.noteId, note.id),
),
);
}
if (this.local) {
const federatedUsers = await this.federateToFollowers(
reactionToDelete.toVersiaUnreact(),
);
if (
note.remote &&
!federatedUsers.find((u) => u.id === note.author.id)
) {
await this.federateToUser(
reactionToDelete.toVersiaUnreact(),
note.author,
);
}
}
} }
public async notify( public async notify(
type: "mention" | "follow_request" | "follow" | "favourite" | "reblog", type:
| "mention"
| "follow_request"
| "follow"
| "favourite"
| "reblog"
| "reaction",
relatedUser: User, relatedUser: User,
note?: Note, note?: Note,
): Promise<void> { ): Promise<void> {
@ -801,7 +857,13 @@ export class User extends BaseInterface<typeof Users, UserWithRelations> {
private async notifyPush( private async notifyPush(
notificationId: string, notificationId: string,
type: "mention" | "follow_request" | "follow" | "favourite" | "reblog", type:
| "mention"
| "follow_request"
| "follow"
| "favourite"
| "reblog"
| "reaction",
relatedUser: User, relatedUser: User,
note?: Note, note?: Note,
): Promise<void> { ): Promise<void> {

View file

@ -21,6 +21,7 @@ export const Notification = z
"favourite", "favourite",
"poll", "poll",
"update", "update",
"reaction",
"admin.sign_up", "admin.sign_up",
"admin.report", "admin.report",
"severed_relationships", "severed_relationships",

View file

@ -32,4 +32,5 @@ export type KnownEntity =
| VersiaEntities.Unfollow | VersiaEntities.Unfollow
| VersiaEntities.Delete | VersiaEntities.Delete
| VersiaEntities.Like | VersiaEntities.Like
| VersiaEntities.Share; | VersiaEntities.Share
| VersiaEntities.Reaction;