mirror of
https://github.com/versia-pub/server.git
synced 2025-12-06 00:18:19 +01:00
Compare commits
5 commits
1fba91f772
...
0692aa6efa
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0692aa6efa | ||
|
|
15e291b487 | ||
|
|
343a507ecc | ||
|
|
fa1dd69e2d | ||
|
|
e0adaca2a2 |
|
|
@ -95,6 +95,7 @@ The following extensions are currently supported or being worked on:
|
||||||
- `pub.versia:instance_messaging`: Instance Messaging
|
- `pub.versia:instance_messaging`: Instance Messaging
|
||||||
- `pub.versia:likes`: Likes
|
- `pub.versia:likes`: Likes
|
||||||
- `pub.versia:share`: Share
|
- `pub.versia:share`: Share
|
||||||
|
- `pub.versia:reactions`: Reactions
|
||||||
|
|
||||||
## API
|
## API
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -106,6 +106,7 @@ describe("/api/v1/statuses/:id/reactions/:name", () => {
|
||||||
name: "❤️",
|
name: "❤️",
|
||||||
count: 1,
|
count: 1,
|
||||||
me: false,
|
me: false,
|
||||||
|
remote: false,
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
expect(data.reactions).toContainEqual(
|
expect(data.reactions).toContainEqual(
|
||||||
|
|
@ -113,6 +114,7 @@ describe("/api/v1/statuses/:id/reactions/:name", () => {
|
||||||
name: "😂",
|
name: "😂",
|
||||||
count: 1,
|
count: 1,
|
||||||
me: true,
|
me: true,
|
||||||
|
remote: false,
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
@ -133,6 +135,7 @@ describe("/api/v1/statuses/:id/reactions/:name", () => {
|
||||||
name: "👍",
|
name: "👍",
|
||||||
count: 1,
|
count: 1,
|
||||||
me: true,
|
me: true,
|
||||||
|
remote: false,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -39,6 +39,7 @@ describe("/api/v1/statuses/:id/reactions", () => {
|
||||||
count: 1,
|
count: 1,
|
||||||
me: true,
|
me: true,
|
||||||
account_ids: [users[1].id],
|
account_ids: [users[1].id],
|
||||||
|
remote: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
// Check for ❤️ reaction
|
// Check for ❤️ reaction
|
||||||
|
|
@ -48,6 +49,7 @@ describe("/api/v1/statuses/:id/reactions", () => {
|
||||||
count: 1,
|
count: 1,
|
||||||
me: false,
|
me: false,
|
||||||
account_ids: [users[2].id],
|
account_ids: [users[2].id],
|
||||||
|
remote: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
// Check for 😂 reaction
|
// Check for 😂 reaction
|
||||||
|
|
@ -57,6 +59,7 @@ describe("/api/v1/statuses/:id/reactions", () => {
|
||||||
count: 1,
|
count: 1,
|
||||||
me: true,
|
me: true,
|
||||||
account_ids: [users[1].id],
|
account_ids: [users[1].id],
|
||||||
|
remote: false,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -71,6 +74,7 @@ describe("/api/v1/statuses/:id/reactions", () => {
|
||||||
// All reactions should have me: false when not authenticated
|
// All reactions should have me: false when not authenticated
|
||||||
for (const reaction of data) {
|
for (const reaction of data) {
|
||||||
expect(reaction.me).toBe(false);
|
expect(reaction.me).toBe(false);
|
||||||
|
expect(reaction.remote).toBe(false);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -6,20 +6,23 @@ import {
|
||||||
enableRealRequests,
|
enableRealRequests,
|
||||||
mock,
|
mock,
|
||||||
} from "bun-bagel";
|
} from "bun-bagel";
|
||||||
import { and, eq } from "drizzle-orm";
|
import { and, eq, isNull } 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, generateClient, getTestUsers } from "~/tests/utils";
|
||||||
|
|
||||||
const instanceUrl = new URL("https://versia.example.com");
|
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 reaction2Id = 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",
|
||||||
|
|
@ -30,6 +33,7 @@ const privateKey = await crypto.subtle.importKey(
|
||||||
);
|
);
|
||||||
const instanceKeys = await User.generateKeys();
|
const instanceKeys = await User.generateKeys();
|
||||||
const inboxUrl = new URL("/inbox", config.http.base_url);
|
const inboxUrl = new URL("/inbox", config.http.base_url);
|
||||||
|
const { users, deleteUsers } = await getTestUsers(1);
|
||||||
|
|
||||||
disableRealRequests();
|
disableRealRequests();
|
||||||
|
|
||||||
|
|
@ -99,6 +103,7 @@ afterAll(async () => {
|
||||||
}
|
}
|
||||||
|
|
||||||
await instance.delete();
|
await instance.delete();
|
||||||
|
await deleteUsers();
|
||||||
clearMocks();
|
clearMocks();
|
||||||
enableRealRequests();
|
enableRealRequests();
|
||||||
});
|
});
|
||||||
|
|
@ -222,7 +227,187 @@ 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();
|
||||||
|
|
||||||
|
// Check if API returns the reaction correctly
|
||||||
|
await using client = await generateClient(users[1]);
|
||||||
|
|
||||||
|
const { data, ok } = await client.getStatusReactions(dbNote.id);
|
||||||
|
|
||||||
|
expect(ok).toBe(true);
|
||||||
|
expect(data).toContainEqual(
|
||||||
|
expect.objectContaining({
|
||||||
|
name: "👍",
|
||||||
|
count: 1,
|
||||||
|
me: false,
|
||||||
|
remote: false,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("should correctly process Reaction with custom emoji", async () => {
|
||||||
|
const exampleRequest = new VersiaEntities.Reaction({
|
||||||
|
id: reaction2Id,
|
||||||
|
created_at: "2025-04-18T10:32:01.427Z",
|
||||||
|
uri: new URL(`/reactions/${reaction2Id}`, instanceUrl).href,
|
||||||
|
type: "pub.versia:reactions/Reaction",
|
||||||
|
author: new URL(`/users/${userId}`, instanceUrl).href,
|
||||||
|
object: new URL(`/notes/${noteId}`, instanceUrl).href,
|
||||||
|
content: ":neocat:",
|
||||||
|
extensions: {
|
||||||
|
"pub.versia:custom_emojis": {
|
||||||
|
emojis: [
|
||||||
|
{
|
||||||
|
name: ":neocat:",
|
||||||
|
url: {
|
||||||
|
"image/webp": {
|
||||||
|
hash: {
|
||||||
|
sha256: "e06240155d2cb90e8dc05327d023585ab9d47216ff547ad72aaf75c485fe9649",
|
||||||
|
},
|
||||||
|
size: 4664,
|
||||||
|
width: 256,
|
||||||
|
height: 256,
|
||||||
|
remote: true,
|
||||||
|
content:
|
||||||
|
"https://cdn.cpluspatch.com/versia-cpp/e06240155d2cb90e8dc05327d023585ab9d47216ff547ad72aaf75c485fe9649/neocat.webp",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
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),
|
||||||
|
isNull(Reactions.emojiText), // Custom emoji reactions have emojiText as NULL
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(reaction).not.toBeNull();
|
||||||
|
|
||||||
|
// Check if API returns the reaction correctly
|
||||||
|
await using client = await generateClient(users[1]);
|
||||||
|
|
||||||
|
const { data, ok } = await client.getStatusReactions(dbNote.id);
|
||||||
|
|
||||||
|
expect(ok).toBe(true);
|
||||||
|
expect(data).toContainEqual(
|
||||||
|
expect.objectContaining({
|
||||||
|
name: ":neocat@versia.example.com:",
|
||||||
|
count: 1,
|
||||||
|
me: false,
|
||||||
|
remote: true,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
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
|
||||||
|
|
|
||||||
|
|
@ -49,6 +49,7 @@ export default apiRoute((app) =>
|
||||||
"pub.versia:instance_messaging",
|
"pub.versia:instance_messaging",
|
||||||
"pub.versia:likes",
|
"pub.versia:likes",
|
||||||
"pub.versia:shares",
|
"pub.versia:shares",
|
||||||
|
"pub.versia:reactions",
|
||||||
],
|
],
|
||||||
versions: ["0.5.0"],
|
versions: ["0.5.0"],
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -948,6 +948,7 @@ export class Note extends BaseInterface<typeof Notes, NoteTypeWithRelations> {
|
||||||
{
|
{
|
||||||
count: number;
|
count: number;
|
||||||
me: boolean;
|
me: boolean;
|
||||||
|
instance: typeof Instance.$type | null;
|
||||||
account_ids: string[];
|
account_ids: string[];
|
||||||
}
|
}
|
||||||
>();
|
>();
|
||||||
|
|
@ -958,8 +959,10 @@ export class Note extends BaseInterface<typeof Notes, NoteTypeWithRelations> {
|
||||||
// Determine emoji name based on type
|
// Determine emoji name based on type
|
||||||
if (reaction.emojiText) {
|
if (reaction.emojiText) {
|
||||||
emojiName = reaction.emojiText;
|
emojiName = reaction.emojiText;
|
||||||
} else if (reaction.emoji) {
|
} else if (reaction.emoji?.instance === null) {
|
||||||
emojiName = `:${reaction.emoji.shortcode}:`;
|
emojiName = `:${reaction.emoji.shortcode}:`;
|
||||||
|
} else if (reaction.emoji?.instance) {
|
||||||
|
emojiName = `:${reaction.emoji.shortcode}@${reaction.emoji.instance.baseUrl}:`;
|
||||||
} else {
|
} else {
|
||||||
continue; // Skip invalid reactions
|
continue; // Skip invalid reactions
|
||||||
}
|
}
|
||||||
|
|
@ -970,6 +973,7 @@ export class Note extends BaseInterface<typeof Notes, NoteTypeWithRelations> {
|
||||||
count: 0,
|
count: 0,
|
||||||
me: false,
|
me: false,
|
||||||
account_ids: [],
|
account_ids: [],
|
||||||
|
instance: reaction.emoji?.instance ?? null,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -994,6 +998,7 @@ export class Note extends BaseInterface<typeof Notes, NoteTypeWithRelations> {
|
||||||
count: data.count,
|
count: data.count,
|
||||||
me: data.me,
|
me: data.me,
|
||||||
account_ids: data.account_ids,
|
account_ids: data.account_ids,
|
||||||
|
remote: data.instance !== null,
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
|
|
||||||
|
|
@ -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> {
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
*
|
*
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"$schema": "https://jsr.io/schema/config-file.v1.json",
|
"$schema": "https://jsr.io/schema/config-file.v1.json",
|
||||||
"name": "@versia/client",
|
"name": "@versia/client",
|
||||||
"version": "0.2.0-alpha.3",
|
"version": "0.2.0-alpha.4",
|
||||||
"exports": {
|
"exports": {
|
||||||
".": "./index.ts",
|
".": "./index.ts",
|
||||||
"./schemas": "./schemas.ts"
|
"./schemas": "./schemas.ts"
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"name": "@versia/client",
|
"name": "@versia/client",
|
||||||
"displayName": "Versia Client",
|
"displayName": "Versia Client",
|
||||||
"version": "0.2.0-alpha.3",
|
"version": "0.2.0-alpha.4",
|
||||||
"author": {
|
"author": {
|
||||||
"email": "jesse.wierzbinski@lysand.org",
|
"email": "jesse.wierzbinski@lysand.org",
|
||||||
"name": "Jesse Wierzbinski (CPlusPatch)",
|
"name": "Jesse Wierzbinski (CPlusPatch)",
|
||||||
|
|
|
||||||
|
|
@ -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",
|
||||||
|
|
|
||||||
|
|
@ -62,6 +62,11 @@ export const NoteReaction = z
|
||||||
description: "Number of users who reacted with this emoji.",
|
description: "Number of users who reacted with this emoji.",
|
||||||
example: 5,
|
example: 5,
|
||||||
}),
|
}),
|
||||||
|
remote: z.boolean().openapi({
|
||||||
|
description:
|
||||||
|
"Whether this reaction is from a remote instance (federated).",
|
||||||
|
example: false,
|
||||||
|
}),
|
||||||
me: z.boolean().optional().openapi({
|
me: z.boolean().optional().openapi({
|
||||||
description:
|
description:
|
||||||
"Whether the current authenticated user reacted with this emoji.",
|
"Whether the current authenticated user reacted with this emoji.",
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue