mirror of
https://github.com/versia-pub/server.git
synced 2025-12-06 08:28:19 +01:00
feat(federation): ✨ Add inbound Reaction processing
This commit is contained in:
parent
1fba91f772
commit
e0adaca2a2
|
|
@ -9,9 +9,10 @@ import {
|
||||||
import { and, eq } from "drizzle-orm";
|
import { and, eq } from "drizzle-orm";
|
||||||
import { Instance } from "~/classes/database/instance";
|
import { Instance } from "~/classes/database/instance";
|
||||||
import { Note } from "~/classes/database/note";
|
import { Note } from "~/classes/database/note";
|
||||||
|
import { Reaction } from "~/classes/database/reaction";
|
||||||
import { User } from "~/classes/database/user";
|
import { User } from "~/classes/database/user";
|
||||||
import { config } from "~/config";
|
import { config } from "~/config";
|
||||||
import { Notes } from "~/drizzle/schema";
|
import { Notes, Reactions, Users } from "~/drizzle/schema";
|
||||||
import { sign } from "~/packages/sdk/crypto";
|
import { sign } from "~/packages/sdk/crypto";
|
||||||
import * as VersiaEntities from "~/packages/sdk/entities";
|
import * as VersiaEntities from "~/packages/sdk/entities";
|
||||||
import { fakeRequest } from "~/tests/utils";
|
import { fakeRequest } from "~/tests/utils";
|
||||||
|
|
@ -20,6 +21,7 @@ const instanceUrl = new URL("https://versia.example.com");
|
||||||
const noteId = randomUUIDv7();
|
const noteId = randomUUIDv7();
|
||||||
const userId = randomUUIDv7();
|
const userId = randomUUIDv7();
|
||||||
const shareId = randomUUIDv7();
|
const shareId = randomUUIDv7();
|
||||||
|
const reactionId = randomUUIDv7();
|
||||||
const userKeys = await User.generateKeys();
|
const userKeys = await User.generateKeys();
|
||||||
const privateKey = await crypto.subtle.importKey(
|
const privateKey = await crypto.subtle.importKey(
|
||||||
"pkcs8",
|
"pkcs8",
|
||||||
|
|
@ -222,7 +224,71 @@ describe("Inbox Tests", () => {
|
||||||
expect(share).not.toBeNull();
|
expect(share).not.toBeNull();
|
||||||
});
|
});
|
||||||
|
|
||||||
test("should correctly process Delete for Note", async () => {
|
test("should correctly process Reaction", async () => {
|
||||||
|
const exampleRequest = new VersiaEntities.Reaction({
|
||||||
|
id: reactionId,
|
||||||
|
created_at: "2025-04-18T10:32:01.427Z",
|
||||||
|
uri: new URL(`/reactions/${reactionId}`, instanceUrl).href,
|
||||||
|
type: "pub.versia:reactions/Reaction",
|
||||||
|
author: new URL(`/users/${userId}`, instanceUrl).href,
|
||||||
|
object: new URL(`/notes/${noteId}`, instanceUrl).href,
|
||||||
|
content: "👍",
|
||||||
|
});
|
||||||
|
|
||||||
|
const signedRequest = await sign(
|
||||||
|
privateKey,
|
||||||
|
new URL(exampleRequest.data.author),
|
||||||
|
new Request(inboxUrl, {
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
Accept: "application/json",
|
||||||
|
"User-Agent": "Versia/1.0.0",
|
||||||
|
},
|
||||||
|
body: JSON.stringify(exampleRequest.toJSON()),
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
const response = await fakeRequest(inboxUrl, {
|
||||||
|
method: "POST",
|
||||||
|
headers: signedRequest.headers,
|
||||||
|
body: signedRequest.body,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(response.status).toBe(200);
|
||||||
|
|
||||||
|
await sleep(500);
|
||||||
|
|
||||||
|
const dbNote = await Note.fromSql(
|
||||||
|
eq(Notes.uri, new URL(`/notes/${noteId}`, instanceUrl).href),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!dbNote) {
|
||||||
|
throw new Error("DBNote not found");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find the remote user who reacted by URI
|
||||||
|
const remoteUser = await User.fromSql(
|
||||||
|
eq(Users.uri, new URL(`/users/${userId}`, instanceUrl).href),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!remoteUser) {
|
||||||
|
throw new Error("Remote user not found");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if reaction was created in the database
|
||||||
|
const reaction = await Reaction.fromSql(
|
||||||
|
and(
|
||||||
|
eq(Reactions.noteId, dbNote.id),
|
||||||
|
eq(Reactions.authorId, remoteUser.id),
|
||||||
|
eq(Reactions.emojiText, "👍"),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(reaction).not.toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("should correctly process Delete", async () => {
|
||||||
const deleteId = randomUUIDv7();
|
const deleteId = randomUUIDv7();
|
||||||
|
|
||||||
// First check that the note exists in the database
|
// First check that the note exists in the database
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,12 @@
|
||||||
import { getLogger, type Logger } from "@logtape/logtape";
|
import { getLogger, type Logger } from "@logtape/logtape";
|
||||||
import { type Instance, Like, Note, Relationship, User } from "@versia/kit/db";
|
import {
|
||||||
|
type Instance,
|
||||||
|
Like,
|
||||||
|
Note,
|
||||||
|
Reaction,
|
||||||
|
Relationship,
|
||||||
|
User,
|
||||||
|
} from "@versia/kit/db";
|
||||||
import { Likes, Notes } from "@versia/kit/tables";
|
import { Likes, Notes } from "@versia/kit/tables";
|
||||||
import type { SocketAddress } from "bun";
|
import type { SocketAddress } from "bun";
|
||||||
import { Glob } from "bun";
|
import { Glob } from "bun";
|
||||||
|
|
@ -198,8 +205,9 @@ export class InboxProcessor {
|
||||||
InboxProcessor.processDelete(d),
|
InboxProcessor.processDelete(d),
|
||||||
)
|
)
|
||||||
.on(VersiaEntities.User, (u) => InboxProcessor.processUser(u))
|
.on(VersiaEntities.User, (u) => InboxProcessor.processUser(u))
|
||||||
.on(VersiaEntities.Share, async (s) =>
|
.on(VersiaEntities.Share, (s) => InboxProcessor.processShare(s))
|
||||||
InboxProcessor.processShare(s),
|
.on(VersiaEntities.Reaction, (r) =>
|
||||||
|
InboxProcessor.processReaction(r),
|
||||||
)
|
)
|
||||||
.sort(() => {
|
.sort(() => {
|
||||||
throw new ApiError(400, "Unknown entity type");
|
throw new ApiError(400, "Unknown entity type");
|
||||||
|
|
@ -209,6 +217,29 @@ export class InboxProcessor {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles Reaction entity processing
|
||||||
|
*
|
||||||
|
* @param {VersiaEntities.Reaction} reaction - The Reaction entity to process.
|
||||||
|
* @returns {Promise<void>}
|
||||||
|
*/
|
||||||
|
private static async processReaction(
|
||||||
|
reaction: VersiaEntities.Reaction,
|
||||||
|
): Promise<void> {
|
||||||
|
const author = await User.resolve(new URL(reaction.data.author));
|
||||||
|
const note = await Note.resolve(new URL(reaction.data.object));
|
||||||
|
|
||||||
|
if (!author) {
|
||||||
|
throw new ApiError(404, "Author not found");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!note) {
|
||||||
|
throw new ApiError(404, "Note not found");
|
||||||
|
}
|
||||||
|
|
||||||
|
await Reaction.fromVersia(reaction, author, note);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handles Note entity processing
|
* Handles Note entity processing
|
||||||
*
|
*
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue