mirror of
https://github.com/versia-pub/server.git
synced 2025-12-06 16:38:19 +01:00
refactor(database): ♻️ Simplify User and Note logic further
This commit is contained in:
parent
a8541bdc44
commit
83399ba5f1
|
|
@ -1,4 +1,5 @@
|
||||||
import { apiRoute, applyConfig, auth, jsonOrForm } from "@/api";
|
import { apiRoute, applyConfig, auth, jsonOrForm } from "@/api";
|
||||||
|
import { mergeAndDeduplicate } from "@/lib";
|
||||||
import { sanitizedHtmlStrip } from "@/sanitization";
|
import { sanitizedHtmlStrip } from "@/sanitization";
|
||||||
import { createRoute } from "@hono/zod-openapi";
|
import { createRoute } from "@hono/zod-openapi";
|
||||||
import { Attachment, Emoji, User } from "@versia/kit/db";
|
import { Attachment, Emoji, User } from "@versia/kit/db";
|
||||||
|
|
@ -335,14 +336,10 @@ export default apiRoute((app) =>
|
||||||
await Emoji.parseFromText(sanitizedDisplayName);
|
await Emoji.parseFromText(sanitizedDisplayName);
|
||||||
const noteEmojis = await Emoji.parseFromText(self.note);
|
const noteEmojis = await Emoji.parseFromText(self.note);
|
||||||
|
|
||||||
const emojis = [
|
const emojis = mergeAndDeduplicate(
|
||||||
...displaynameEmojis,
|
displaynameEmojis,
|
||||||
...noteEmojis,
|
noteEmojis,
|
||||||
...fieldEmojis,
|
fieldEmojis,
|
||||||
].filter(
|
|
||||||
// Deduplicate emojis
|
|
||||||
(emoji, index, self) =>
|
|
||||||
self.findIndex((e) => e.id === emoji.id) === index,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
// Connect emojis, if any
|
// Connect emojis, if any
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import { apiRoute, applyConfig, auth, jsonOrForm } from "@/api";
|
import { apiRoute, applyConfig, auth, jsonOrForm } from "@/api";
|
||||||
import { createRoute } from "@hono/zod-openapi";
|
import { createRoute } from "@hono/zod-openapi";
|
||||||
import { Note, Notification } from "@versia/kit/db";
|
import { Note } from "@versia/kit/db";
|
||||||
import { Notes, RolePermissions } from "@versia/kit/tables";
|
import { Notes, RolePermissions } from "@versia/kit/tables";
|
||||||
import { and, eq } from "drizzle-orm";
|
import { and, eq } from "drizzle-orm";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
@ -138,12 +138,7 @@ export default apiRoute((app) =>
|
||||||
}
|
}
|
||||||
|
|
||||||
if (note.author.isLocal() && user.isLocal()) {
|
if (note.author.isLocal() && user.isLocal()) {
|
||||||
await Notification.insert({
|
await note.author.createNotification("reblog", user, newReblog);
|
||||||
accountId: user.id,
|
|
||||||
notifiedId: note.author.id,
|
|
||||||
type: "reblog",
|
|
||||||
noteId: newReblog.data.reblogId,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return context.json(await finalNewReblog.toApi(user), 201);
|
return context.json(await finalNewReblog.toApi(user), 201);
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
import { idValidator } from "@/api";
|
import { idValidator } from "@/api";
|
||||||
import { localObjectUri } from "@/constants";
|
import { localObjectUri } from "@/constants";
|
||||||
|
import { mergeAndDeduplicate } from "@/lib.ts";
|
||||||
import { sanitizedHtmlStrip } from "@/sanitization";
|
import { sanitizedHtmlStrip } from "@/sanitization";
|
||||||
import { sentry } from "@/sentry";
|
import { sentry } from "@/sentry";
|
||||||
import { getLogger } from "@logtape/logtape";
|
import { getLogger } from "@logtape/logtape";
|
||||||
|
|
@ -13,7 +14,7 @@ import type {
|
||||||
Delete as VersiaDelete,
|
Delete as VersiaDelete,
|
||||||
Note as VersiaNote,
|
Note as VersiaNote,
|
||||||
} from "@versia/federation/types";
|
} from "@versia/federation/types";
|
||||||
import { Instance, Notification, db } from "@versia/kit/db";
|
import { Instance, db } from "@versia/kit/db";
|
||||||
import {
|
import {
|
||||||
Attachments,
|
Attachments,
|
||||||
EmojiToNote,
|
EmojiToNote,
|
||||||
|
|
@ -364,14 +365,12 @@ export class Note extends BaseInterface<typeof Notes, NoteTypeWithRelations> {
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
const fusedUsers = [...mentionedUsers, ...usersThatCanSeePost];
|
const fusedUsers = mergeAndDeduplicate(
|
||||||
|
mentionedUsers,
|
||||||
const deduplicatedUsersById = fusedUsers.filter(
|
usersThatCanSeePost,
|
||||||
(user, index, self) =>
|
|
||||||
index === self.findIndex((t) => t.id === user.id),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
return deduplicatedUsersById;
|
return fusedUsers;
|
||||||
}
|
}
|
||||||
|
|
||||||
public get author(): User {
|
public get author(): User {
|
||||||
|
|
@ -452,22 +451,13 @@ export class Note extends BaseInterface<typeof Notes, NoteTypeWithRelations> {
|
||||||
data.content["text/plain"]?.content ??
|
data.content["text/plain"]?.content ??
|
||||||
Object.entries(data.content)[0][1].content;
|
Object.entries(data.content)[0][1].content;
|
||||||
|
|
||||||
const parsedMentions = [
|
const parsedMentions = mergeAndDeduplicate(
|
||||||
...(data.mentions ?? []),
|
data.mentions ?? [],
|
||||||
...(await parseTextMentions(plaintextContent, data.author)),
|
await parseTextMentions(plaintextContent, data.author),
|
||||||
// Deduplicate by .id
|
|
||||||
].filter(
|
|
||||||
(mention, index, self) =>
|
|
||||||
index === self.findIndex((t) => t.id === mention.id),
|
|
||||||
);
|
);
|
||||||
|
const parsedEmojis = mergeAndDeduplicate(
|
||||||
const parsedEmojis = [
|
data.emojis ?? [],
|
||||||
...(data.emojis ?? []),
|
await Emoji.parseFromText(plaintextContent),
|
||||||
...(await Emoji.parseFromText(plaintextContent)),
|
|
||||||
// Deduplicate by .id
|
|
||||||
].filter(
|
|
||||||
(emoji, index, self) =>
|
|
||||||
index === self.findIndex((t) => t.id === emoji.id),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
const htmlContent = await contentToHtml(data.content, parsedMentions);
|
const htmlContent = await contentToHtml(data.content, parsedMentions);
|
||||||
|
|
@ -500,14 +490,13 @@ export class Note extends BaseInterface<typeof Notes, NoteTypeWithRelations> {
|
||||||
await newNote.updateAttachments(data.mediaAttachments ?? []);
|
await newNote.updateAttachments(data.mediaAttachments ?? []);
|
||||||
|
|
||||||
// Send notifications for mentioned local users
|
// Send notifications for mentioned local users
|
||||||
for (const mention of parsedMentions ?? []) {
|
for (const mention of parsedMentions) {
|
||||||
if (mention.isLocal()) {
|
if (mention.isLocal()) {
|
||||||
await Notification.insert({
|
await mention.createNotification(
|
||||||
accountId: data.author.id,
|
"mention",
|
||||||
notifiedId: mention.id,
|
data.author,
|
||||||
type: "mention",
|
newNote,
|
||||||
noteId: newNote.id,
|
);
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -540,26 +529,15 @@ export class Note extends BaseInterface<typeof Notes, NoteTypeWithRelations> {
|
||||||
Object.entries(data.content)[0][1].content)
|
Object.entries(data.content)[0][1].content)
|
||||||
: undefined;
|
: undefined;
|
||||||
|
|
||||||
const parsedMentions = [
|
const parsedMentions = mergeAndDeduplicate(
|
||||||
...(data.mentions ?? []),
|
data.mentions ?? [],
|
||||||
...(plaintextContent
|
plaintextContent
|
||||||
? await parseTextMentions(plaintextContent, data.author)
|
? await parseTextMentions(plaintextContent, data.author)
|
||||||
: []),
|
: [],
|
||||||
// Deduplicate by .id
|
|
||||||
].filter(
|
|
||||||
(mention, index, self) =>
|
|
||||||
index === self.findIndex((t) => t.id === mention.id),
|
|
||||||
);
|
);
|
||||||
|
const parsedEmojis = mergeAndDeduplicate(
|
||||||
const parsedEmojis = [
|
data.emojis ?? [],
|
||||||
...(data.emojis ?? []),
|
plaintextContent ? await Emoji.parseFromText(plaintextContent) : [],
|
||||||
...(plaintextContent
|
|
||||||
? await Emoji.parseFromText(plaintextContent)
|
|
||||||
: []),
|
|
||||||
// Deduplicate by .id
|
|
||||||
].filter(
|
|
||||||
(emoji, index, self) =>
|
|
||||||
index === self.findIndex((t) => t.id === emoji.id),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
const htmlContent = data.content
|
const htmlContent = data.content
|
||||||
|
|
@ -608,18 +586,12 @@ export class Note extends BaseInterface<typeof Notes, NoteTypeWithRelations> {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fuse and deduplicate
|
|
||||||
const fusedEmojis = emojis.filter(
|
|
||||||
(emoji, index, self) =>
|
|
||||||
index === self.findIndex((t) => t.id === emoji.id),
|
|
||||||
);
|
|
||||||
|
|
||||||
// Connect emojis
|
// Connect emojis
|
||||||
await db
|
await db
|
||||||
.delete(EmojiToNote)
|
.delete(EmojiToNote)
|
||||||
.where(eq(EmojiToNote.noteId, this.data.id));
|
.where(eq(EmojiToNote.noteId, this.data.id));
|
||||||
await db.insert(EmojiToNote).values(
|
await db.insert(EmojiToNote).values(
|
||||||
fusedEmojis.map((emoji) => ({
|
emojis.map((emoji) => ({
|
||||||
emojiId: emoji.id,
|
emojiId: emoji.id,
|
||||||
noteId: this.data.id,
|
noteId: this.data.id,
|
||||||
})),
|
})),
|
||||||
|
|
|
||||||
|
|
@ -275,11 +275,10 @@ export class User extends BaseInterface<typeof Users, UserWithRelations> {
|
||||||
senderId: this.id,
|
senderId: this.id,
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
await Notification.insert({
|
await otherUser.createNotification(
|
||||||
accountId: this.id,
|
otherUser.data.isLocked ? "follow_request" : "follow",
|
||||||
type: otherUser.data.isLocked ? "follow_request" : "follow",
|
this,
|
||||||
notifiedId: otherUser.id,
|
);
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return foundRelationship;
|
return foundRelationship;
|
||||||
|
|
@ -295,20 +294,6 @@ export class User extends BaseInterface<typeof Users, UserWithRelations> {
|
||||||
recipientId: followee.id,
|
recipientId: followee.id,
|
||||||
senderId: this.id,
|
senderId: this.id,
|
||||||
});
|
});
|
||||||
} else if (!this.data.isLocked) {
|
|
||||||
if (relationship.data.following) {
|
|
||||||
await Notification.insert({
|
|
||||||
accountId: followee.id,
|
|
||||||
type: "unfollow",
|
|
||||||
notifiedId: this.id,
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
await Notification.insert({
|
|
||||||
accountId: followee.id,
|
|
||||||
type: "cancel-follow",
|
|
||||||
notifiedId: this.id,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
await relationship.update({
|
await relationship.update({
|
||||||
|
|
@ -526,12 +511,7 @@ export class User extends BaseInterface<typeof Users, UserWithRelations> {
|
||||||
|
|
||||||
if (this.isLocal() && note.author.isLocal()) {
|
if (this.isLocal() && note.author.isLocal()) {
|
||||||
// Notify the user that their post has been favourited
|
// Notify the user that their post has been favourited
|
||||||
await Notification.insert({
|
await note.author.createNotification("favourite", this, note);
|
||||||
accountId: this.id,
|
|
||||||
type: "favourite",
|
|
||||||
notifiedId: note.author.id,
|
|
||||||
noteId: note.id,
|
|
||||||
});
|
|
||||||
} else if (this.isLocal() && note.author.isRemote()) {
|
} else if (this.isLocal() && note.author.isRemote()) {
|
||||||
// Federate the like
|
// Federate the like
|
||||||
this.federateToFollowers(newLike.toVersia());
|
this.federateToFollowers(newLike.toVersia());
|
||||||
|
|
@ -567,6 +547,19 @@ export class User extends BaseInterface<typeof Users, UserWithRelations> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async createNotification(
|
||||||
|
type: "mention" | "follow_request" | "follow" | "favourite" | "reblog",
|
||||||
|
relatedUser: User,
|
||||||
|
note?: Note,
|
||||||
|
): Promise<void> {
|
||||||
|
await Notification.insert({
|
||||||
|
accountId: relatedUser.id,
|
||||||
|
type,
|
||||||
|
notifiedId: this.id,
|
||||||
|
noteId: note?.id ?? null,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
public async clearAllNotifications(): Promise<void> {
|
public async clearAllNotifications(): Promise<void> {
|
||||||
await db
|
await db
|
||||||
.update(Notifications)
|
.update(Notifications)
|
||||||
|
|
|
||||||
|
|
@ -15,14 +15,7 @@ import type {
|
||||||
Note as VersiaNote,
|
Note as VersiaNote,
|
||||||
User as VersiaUser,
|
User as VersiaUser,
|
||||||
} from "@versia/federation/types";
|
} from "@versia/federation/types";
|
||||||
import {
|
import { Instance, Like, Note, Relationship, User } from "@versia/kit/db";
|
||||||
Instance,
|
|
||||||
Like,
|
|
||||||
Note,
|
|
||||||
Notification,
|
|
||||||
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 chalk from "chalk";
|
import chalk from "chalk";
|
||||||
|
|
@ -340,11 +333,10 @@ export class InboxProcessor {
|
||||||
languages: [],
|
languages: [],
|
||||||
});
|
});
|
||||||
|
|
||||||
await Notification.insert({
|
await followee.createNotification(
|
||||||
accountId: author.id,
|
followee.data.isLocked ? "follow_request" : "follow",
|
||||||
type: followee.data.isLocked ? "follow_request" : "follow",
|
author,
|
||||||
notifiedId: followee.id,
|
);
|
||||||
});
|
|
||||||
|
|
||||||
if (!followee.data.isLocked) {
|
if (!followee.data.isLocked) {
|
||||||
await followee.sendFollowAccept(author);
|
await followee.sendFollowAccept(author);
|
||||||
|
|
|
||||||
11
utils/lib.ts
Normal file
11
utils/lib.ts
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
type ElementWithId = { id: string };
|
||||||
|
|
||||||
|
export const mergeAndDeduplicate = <T extends ElementWithId>(
|
||||||
|
...elements: T[][]
|
||||||
|
): T[] =>
|
||||||
|
elements
|
||||||
|
.reduce((acc, val) => acc.concat(val), [])
|
||||||
|
.filter(
|
||||||
|
(element, index, self) =>
|
||||||
|
index === self.findIndex((t) => t.id === element.id),
|
||||||
|
);
|
||||||
Loading…
Reference in a new issue