refactor(federation): 🔥 Refactor Note federation and creation code

This commit is contained in:
Jesse Wierzbinski 2025-04-08 18:13:30 +02:00
parent 54b2dfb78d
commit f79b0bc999
No known key found for this signature in database
19 changed files with 243 additions and 354 deletions

View file

@ -45,7 +45,7 @@ export default apiRoute((app) =>
async (context) => {
const otherUser = context.get("user");
if (otherUser.isLocal()) {
if (otherUser.local) {
throw new ApiError(400, "Cannot refetch a local user");
}

View file

@ -71,7 +71,7 @@ export default apiRoute((app) =>
);
// Check if accepting remote follow
if (account.isRemote()) {
if (account.remote) {
// Federate follow accept
await user.acceptFollowRequest(account);
}

View file

@ -72,7 +72,7 @@ export default apiRoute((app) =>
);
// Check if rejecting remote follow
if (account.isRemote()) {
if (account.remote) {
// Federate follow reject
await user.rejectFollowRequest(account);
}

View file

@ -5,6 +5,7 @@ import {
jsonOrForm,
withNoteParam,
} from "@/api";
import { sanitizedHtmlStrip } from "@/sanitization";
import {
Attachment as AttachmentSchema,
PollOption,
@ -13,12 +14,13 @@ import {
zBoolean,
} from "@versia/client/schemas";
import { RolePermission } from "@versia/client/schemas";
import { Media } from "@versia/kit/db";
import { Emoji, Media } from "@versia/kit/db";
import * as VersiaEntities from "@versia/sdk/entities";
import { describeRoute } from "hono-openapi";
import { resolver, validator } from "hono-openapi/zod";
import { z } from "zod";
import { ApiError } from "~/classes/errors/api-error";
import { contentToHtml, parseTextMentions } from "~/classes/functions/status";
import { config } from "~/config.ts";
const schema = z
@ -226,22 +228,50 @@ export default apiRoute((app) => {
);
}
const newNote = await note.updateFromData({
author: user,
content: statusText
? new VersiaEntities.TextContentFormat({
[content_type]: {
content: statusText,
remote: false,
},
})
const sanitizedSpoilerText = spoiler_text
? await sanitizedHtmlStrip(spoiler_text)
: undefined;
const content = statusText
? new VersiaEntities.TextContentFormat({
[content_type]: {
content: statusText,
remote: false,
},
})
: undefined;
const parsedMentions = statusText
? await parseTextMentions(statusText, user)
: [];
const parsedEmojis = statusText
? await Emoji.parseFromText(statusText)
: [];
await note.update({
spoilerText: sanitizedSpoilerText,
sensitive,
content: content
? await contentToHtml(content, parsedMentions)
: undefined,
isSensitive: sensitive,
spoilerText: spoiler_text,
mediaAttachments: foundAttachments,
});
return context.json(await newNote.toApi(user), 200);
// Emojis, mentions, and attachments are stored in a different table, so update them there too
await note.updateEmojis(parsedEmojis);
await note.updateMentions(parsedMentions);
await note.updateAttachments(foundAttachments);
await note.reload();
// Send notifications for mentioned local users
for (const mentioned of parsedMentions) {
if (mentioned.local) {
await mentioned.notify("mention", user, note);
}
}
return context.json(await note.toApi(user), 200);
},
);
});

View file

@ -83,7 +83,7 @@ export default apiRoute((app) =>
throw new Error("Failed to reblog");
}
if (note.author.isLocal() && user.isLocal()) {
if (note.author.local && user.local) {
await note.author.notify("reblog", user, newReblog);
}

View file

@ -1,4 +1,5 @@
import { apiRoute, auth, handleZodError, jsonOrForm } from "@/api";
import { sanitizedHtmlStrip } from "@/sanitization";
import {
Attachment as AttachmentSchema,
PollOption,
@ -7,12 +8,14 @@ import {
zBoolean,
} from "@versia/client/schemas";
import { RolePermission } from "@versia/client/schemas";
import { Media, Note } from "@versia/kit/db";
import { Emoji, Media, Note } from "@versia/kit/db";
import * as VersiaEntities from "@versia/sdk/entities";
import { randomUUIDv7 } from "bun";
import { describeRoute } from "hono-openapi";
import { resolver, validator } from "hono-openapi/zod";
import { z } from "zod";
import { ApiError } from "~/classes/errors/api-error";
import { contentToHtml, parseTextMentions } from "~/classes/functions/status";
import { config } from "~/config.ts";
const schema = z
@ -175,27 +178,59 @@ export default apiRoute((app) =>
);
}
const newNote = await Note.fromData({
author: user,
content: new VersiaEntities.TextContentFormat({
[content_type]: {
content: status ?? "",
remote: false,
},
}),
const sanitizedSpoilerText = spoiler_text
? await sanitizedHtmlStrip(spoiler_text)
: undefined;
const content = status
? new VersiaEntities.TextContentFormat({
[content_type]: {
content: status,
remote: false,
},
})
: undefined;
const parsedMentions = status
? await parseTextMentions(status, user)
: [];
const parsedEmojis = status
? await Emoji.parseFromText(status)
: [];
const newNote = await Note.insert({
id: randomUUIDv7(),
authorId: user.id,
visibility,
isSensitive: sensitive ?? false,
spoilerText: spoiler_text ?? "",
mediaAttachments: foundAttachments,
content: content
? await contentToHtml(content, parsedMentions)
: undefined,
sensitive,
spoilerText: sanitizedSpoilerText,
replyId: in_reply_to_id ?? undefined,
quoteId: quote_id ?? undefined,
application: application ?? undefined,
quotingId: quote_id ?? undefined,
applicationId: application?.id,
});
// Emojis, mentions, and attachments are stored in a different table, so update them there too
await newNote.updateEmojis(parsedEmojis);
await newNote.updateMentions(parsedMentions);
await newNote.updateAttachments(foundAttachments);
await newNote.reload();
if (!local_only) {
await newNote.federateToUsers();
}
// Send notifications for mentioned local users
for (const mentioned of parsedMentions) {
if (mentioned.local) {
await mentioned.notify("mention", user, newNote);
}
}
return context.json(await newNote.toApi(user), 200);
},
),

View file

@ -59,7 +59,7 @@ export default apiRoute((app) =>
const liker = await User.fromId(like.data.likerId);
if (!liker || liker.isRemote()) {
if (!liker || liker.remote) {
throw ApiError.accountNotFound();
}

View file

@ -53,10 +53,7 @@ export default apiRoute((app) =>
),
);
if (
!(note && (await note.isViewableByUser(null))) ||
note.isRemote()
) {
if (!(note && (await note.isViewableByUser(null))) || note.remote) {
throw ApiError.noteNotFound();
}

View file

@ -63,10 +63,7 @@ export default apiRoute((app) =>
),
);
if (
!(note && (await note.isViewableByUser(null))) ||
note.isRemote()
) {
if (!(note && (await note.isViewableByUser(null))) || note.remote) {
throw ApiError.noteNotFound();
}

View file

@ -61,10 +61,7 @@ export default apiRoute((app) =>
),
);
if (
!(note && (await note.isViewableByUser(null))) ||
note.isRemote()
) {
if (!(note && (await note.isViewableByUser(null))) || note.remote) {
throw ApiError.noteNotFound();
}

View file

@ -53,7 +53,7 @@ export default apiRoute((app) =>
throw ApiError.accountNotFound();
}
if (user.isRemote()) {
if (user.remote) {
throw new ApiError(403, "User is not on this instance");
}

View file

@ -70,7 +70,7 @@ export default apiRoute((app) =>
throw new ApiError(404, "User not found");
}
if (author.isRemote()) {
if (author.remote) {
throw new ApiError(403, "User is not on this instance");
}