mirror of
https://github.com/versia-pub/server.git
synced 2026-03-13 05:49:16 +01:00
refactor: 🚚 Move more utilities into packages
This commit is contained in:
parent
5cae547f8d
commit
3798e170d0
140 changed files with 913 additions and 735 deletions
|
|
@ -1,5 +1,7 @@
|
|||
import type { NoteReactionWithAccounts, Status } from "@versia/client/schemas";
|
||||
import { db, Instance, type Reaction } from "@versia/kit/db";
|
||||
import { versiaTextToHtml } from "@versia/kit/parsers";
|
||||
import { uuid } from "@versia/kit/regex";
|
||||
import {
|
||||
EmojiToNote,
|
||||
Likes,
|
||||
|
|
@ -26,10 +28,8 @@ import {
|
|||
import { htmlToText } from "html-to-text";
|
||||
import { createRegExp, exactly, global } from "magic-regexp";
|
||||
import type { z } from "zod";
|
||||
import { idValidator } from "@/api";
|
||||
import { mergeAndDeduplicate } from "@/lib.ts";
|
||||
import { sanitizedHtmlStrip } from "@/sanitization";
|
||||
import { contentToHtml, findManyNotes } from "~/classes/functions/status";
|
||||
import {
|
||||
DeliveryJobType,
|
||||
deliveryQueue,
|
||||
|
|
@ -38,7 +38,188 @@ import { Application } from "./application.ts";
|
|||
import { BaseInterface } from "./base.ts";
|
||||
import { Emoji } from "./emoji.ts";
|
||||
import { Media } from "./media.ts";
|
||||
import { User } from "./user.ts";
|
||||
import {
|
||||
transformOutputToUserWithRelations,
|
||||
User,
|
||||
userRelations,
|
||||
} from "./user.ts";
|
||||
|
||||
/**
|
||||
* Wrapper against the Status object to make it easier to work with
|
||||
* @param query
|
||||
* @returns
|
||||
*/
|
||||
const findManyNotes = async (
|
||||
query: Parameters<typeof db.query.Notes.findMany>[0],
|
||||
userId?: string,
|
||||
): Promise<(typeof Note.$type)[]> => {
|
||||
const output = await db.query.Notes.findMany({
|
||||
...query,
|
||||
with: {
|
||||
...query?.with,
|
||||
attachments: {
|
||||
with: {
|
||||
media: true,
|
||||
},
|
||||
},
|
||||
reactions: {
|
||||
with: {
|
||||
emoji: {
|
||||
with: {
|
||||
instance: true,
|
||||
media: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
emojis: {
|
||||
with: {
|
||||
emoji: {
|
||||
with: {
|
||||
instance: true,
|
||||
media: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
author: {
|
||||
with: {
|
||||
...userRelations,
|
||||
},
|
||||
},
|
||||
mentions: {
|
||||
with: {
|
||||
user: {
|
||||
with: {
|
||||
instance: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
reblog: {
|
||||
with: {
|
||||
attachments: {
|
||||
with: {
|
||||
media: true,
|
||||
},
|
||||
},
|
||||
reactions: {
|
||||
with: {
|
||||
emoji: {
|
||||
with: {
|
||||
instance: true,
|
||||
media: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
emojis: {
|
||||
with: {
|
||||
emoji: {
|
||||
with: {
|
||||
instance: true,
|
||||
media: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
likes: true,
|
||||
application: true,
|
||||
mentions: {
|
||||
with: {
|
||||
user: {
|
||||
with: userRelations,
|
||||
},
|
||||
},
|
||||
},
|
||||
author: {
|
||||
with: {
|
||||
...userRelations,
|
||||
},
|
||||
},
|
||||
},
|
||||
extras: {
|
||||
pinned: userId
|
||||
? sql`EXISTS (SELECT 1 FROM "UserToPinnedNotes" WHERE "UserToPinnedNotes"."noteId" = "Notes_reblog".id AND "UserToPinnedNotes"."userId" = ${userId})`.as(
|
||||
"pinned",
|
||||
)
|
||||
: sql`false`.as("pinned"),
|
||||
reblogged: userId
|
||||
? sql`EXISTS (SELECT 1 FROM "Notes" WHERE "Notes"."authorId" = ${userId} AND "Notes"."reblogId" = "Notes_reblog".id)`.as(
|
||||
"reblogged",
|
||||
)
|
||||
: sql`false`.as("reblogged"),
|
||||
muted: userId
|
||||
? sql`EXISTS (SELECT 1 FROM "Relationships" WHERE "Relationships"."ownerId" = ${userId} AND "Relationships"."subjectId" = "Notes_reblog"."authorId" AND "Relationships"."muting" = true)`.as(
|
||||
"muted",
|
||||
)
|
||||
: sql`false`.as("muted"),
|
||||
liked: userId
|
||||
? sql`EXISTS (SELECT 1 FROM "Likes" WHERE "Likes"."likedId" = "Notes_reblog".id AND "Likes"."likerId" = ${userId})`.as(
|
||||
"liked",
|
||||
)
|
||||
: sql`false`.as("liked"),
|
||||
},
|
||||
},
|
||||
reply: true,
|
||||
quote: true,
|
||||
},
|
||||
extras: {
|
||||
pinned: userId
|
||||
? sql`EXISTS (SELECT 1 FROM "UserToPinnedNotes" WHERE "UserToPinnedNotes"."noteId" = "Notes".id AND "UserToPinnedNotes"."userId" = ${userId})`.as(
|
||||
"pinned",
|
||||
)
|
||||
: sql`false`.as("pinned"),
|
||||
reblogged: userId
|
||||
? sql`EXISTS (SELECT 1 FROM "Notes" WHERE "Notes"."authorId" = ${userId} AND "Notes"."reblogId" = "Notes".id)`.as(
|
||||
"reblogged",
|
||||
)
|
||||
: sql`false`.as("reblogged"),
|
||||
muted: userId
|
||||
? sql`EXISTS (SELECT 1 FROM "Relationships" WHERE "Relationships"."ownerId" = ${userId} AND "Relationships"."subjectId" = "Notes"."authorId" AND "Relationships"."muting" = true)`.as(
|
||||
"muted",
|
||||
)
|
||||
: sql`false`.as("muted"),
|
||||
liked: userId
|
||||
? sql`EXISTS (SELECT 1 FROM "Likes" WHERE "Likes"."likedId" = "Notes".id AND "Likes"."likerId" = ${userId})`.as(
|
||||
"liked",
|
||||
)
|
||||
: sql`false`.as("liked"),
|
||||
...query?.extras,
|
||||
},
|
||||
});
|
||||
|
||||
return output.map((post) => ({
|
||||
...post,
|
||||
author: transformOutputToUserWithRelations(post.author),
|
||||
mentions: post.mentions.map((mention) => ({
|
||||
...mention.user,
|
||||
endpoints: mention.user.endpoints,
|
||||
})),
|
||||
attachments: post.attachments.map((attachment) => attachment.media),
|
||||
emojis: (post.emojis ?? []).map((emoji) => emoji.emoji),
|
||||
reblog: post.reblog && {
|
||||
...post.reblog,
|
||||
author: transformOutputToUserWithRelations(post.reblog.author),
|
||||
mentions: post.reblog.mentions.map((mention) => ({
|
||||
...mention.user,
|
||||
endpoints: mention.user.endpoints,
|
||||
})),
|
||||
attachments: post.reblog.attachments.map(
|
||||
(attachment) => attachment.media,
|
||||
),
|
||||
emojis: (post.reblog.emojis ?? []).map((emoji) => emoji.emoji),
|
||||
pinned: Boolean(post.reblog.pinned),
|
||||
reblogged: Boolean(post.reblog.reblogged),
|
||||
muted: Boolean(post.reblog.muted),
|
||||
liked: Boolean(post.reblog.liked),
|
||||
},
|
||||
pinned: Boolean(post.pinned),
|
||||
reblogged: Boolean(post.reblogged),
|
||||
muted: Boolean(post.muted),
|
||||
liked: Boolean(post.liked),
|
||||
}));
|
||||
};
|
||||
|
||||
type NoteType = InferSelectModel<typeof Notes>;
|
||||
|
||||
|
|
@ -423,15 +604,15 @@ export class Note extends BaseInterface<typeof Notes, NoteTypeWithRelations> {
|
|||
|
||||
// Check if URI is of a local note
|
||||
if (uri.origin === config.http.base_url.origin) {
|
||||
const uuid = uri.pathname.match(idValidator);
|
||||
const noteUuid = uri.pathname.match(uuid);
|
||||
|
||||
if (!uuid?.[0]) {
|
||||
if (!noteUuid?.[0]) {
|
||||
throw new Error(
|
||||
`URI ${uri} is of a local note, but it could not be parsed`,
|
||||
);
|
||||
}
|
||||
|
||||
return await Note.fromId(uuid[0]);
|
||||
return await Note.fromId(noteUuid[0]);
|
||||
}
|
||||
|
||||
return Note.fromVersia(uri);
|
||||
|
|
@ -535,7 +716,7 @@ export class Note extends BaseInterface<typeof Notes, NoteTypeWithRelations> {
|
|||
|
||||
await note.update({
|
||||
content: versiaNote.content
|
||||
? await contentToHtml(versiaNote.content, mentions)
|
||||
? await versiaTextToHtml(versiaNote.content, mentions)
|
||||
: undefined,
|
||||
contentSource: versiaNote.content
|
||||
? versiaNote.content.data["text/plain"]?.content ||
|
||||
|
|
|
|||
|
|
@ -10,11 +10,8 @@ import {
|
|||
type SQL,
|
||||
} from "drizzle-orm";
|
||||
import type { z } from "zod";
|
||||
import {
|
||||
transformOutputToUserWithRelations,
|
||||
userRelations,
|
||||
} from "../../../classes/functions/user.ts";
|
||||
import { BaseInterface } from "./base.ts";
|
||||
import { transformOutputToUserWithRelations, userRelations } from "./user.ts";
|
||||
|
||||
export type NotificationType = InferSelectModel<typeof Notifications> & {
|
||||
status: typeof Note.$type | null;
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ import {
|
|||
PushSubscription,
|
||||
Reaction,
|
||||
} from "@versia/kit/db";
|
||||
import { uuid } from "@versia/kit/regex";
|
||||
import {
|
||||
EmojiToUser,
|
||||
Likes,
|
||||
|
|
@ -46,11 +47,9 @@ import {
|
|||
} from "drizzle-orm";
|
||||
import { htmlToText } from "html-to-text";
|
||||
import type { z } from "zod";
|
||||
import { idValidator } from "@/api";
|
||||
import { getBestContentType } from "@/content_types";
|
||||
import { randomString } from "@/math";
|
||||
import { sentry } from "@/sentry";
|
||||
import { findManyUsers } from "~/classes/functions/user";
|
||||
import { searchManager } from "~/classes/search/search-manager";
|
||||
import type { HttpVerb, KnownEntity } from "~/types/api.ts";
|
||||
import {
|
||||
|
|
@ -66,6 +65,89 @@ import { Note } from "./note.ts";
|
|||
import { Relationship } from "./relationship.ts";
|
||||
import { Role } from "./role.ts";
|
||||
|
||||
export const userRelations = {
|
||||
instance: true,
|
||||
emojis: {
|
||||
with: {
|
||||
emoji: {
|
||||
with: {
|
||||
instance: true,
|
||||
media: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
avatar: true,
|
||||
header: true,
|
||||
roles: {
|
||||
with: {
|
||||
role: true,
|
||||
},
|
||||
},
|
||||
} as const;
|
||||
|
||||
export const transformOutputToUserWithRelations = (
|
||||
user: Omit<InferSelectModel<typeof Users>, "endpoints"> & {
|
||||
followerCount: unknown;
|
||||
followingCount: unknown;
|
||||
statusCount: unknown;
|
||||
avatar: typeof Media.$type | null;
|
||||
header: typeof Media.$type | null;
|
||||
emojis: {
|
||||
userId: string;
|
||||
emojiId: string;
|
||||
emoji?: typeof Emoji.$type;
|
||||
}[];
|
||||
instance: typeof Instance.$type | null;
|
||||
roles: {
|
||||
userId: string;
|
||||
roleId: string;
|
||||
role?: typeof Role.$type;
|
||||
}[];
|
||||
endpoints: unknown;
|
||||
},
|
||||
): typeof User.$type => {
|
||||
return {
|
||||
...user,
|
||||
followerCount: Number(user.followerCount),
|
||||
followingCount: Number(user.followingCount),
|
||||
statusCount: Number(user.statusCount),
|
||||
endpoints:
|
||||
user.endpoints ??
|
||||
({} as Partial<{
|
||||
dislikes: string;
|
||||
featured: string;
|
||||
likes: string;
|
||||
followers: string;
|
||||
following: string;
|
||||
inbox: string;
|
||||
outbox: string;
|
||||
}>),
|
||||
emojis: user.emojis.map(
|
||||
(emoji) =>
|
||||
(emoji as unknown as Record<string, object>)
|
||||
.emoji as typeof Emoji.$type,
|
||||
),
|
||||
roles: user.roles
|
||||
.map((role) => role.role)
|
||||
.filter(Boolean) as (typeof Role.$type)[],
|
||||
};
|
||||
};
|
||||
|
||||
const findManyUsers = async (
|
||||
query: Parameters<typeof db.query.Users.findMany>[0],
|
||||
): Promise<(typeof User.$type)[]> => {
|
||||
const output = await db.query.Users.findMany({
|
||||
...query,
|
||||
with: {
|
||||
...userRelations,
|
||||
...query?.with,
|
||||
},
|
||||
});
|
||||
|
||||
return output.map((user) => transformOutputToUserWithRelations(user));
|
||||
};
|
||||
|
||||
type UserWithInstance = InferSelectModel<typeof Users> & {
|
||||
instance: typeof Instance.$type | null;
|
||||
};
|
||||
|
|
@ -1094,15 +1176,15 @@ export class User extends BaseInterface<typeof Users, UserWithRelations> {
|
|||
|
||||
// Check if URI is of a local user
|
||||
if (uri.origin === config.http.base_url.origin) {
|
||||
const uuid = uri.href.match(idValidator);
|
||||
const userUuid = uri.href.match(uuid);
|
||||
|
||||
if (!uuid?.[0]) {
|
||||
if (!userUuid?.[0]) {
|
||||
throw new Error(
|
||||
`URI ${uri} is of a local user, but it could not be parsed`,
|
||||
);
|
||||
}
|
||||
|
||||
return await User.fromId(uuid[0]);
|
||||
return await User.fromId(userUuid[0]);
|
||||
}
|
||||
|
||||
getLogger(["federation", "resolvers"])
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue