refactor: 🚚 Move more utilities into packages

This commit is contained in:
Jesse Wierzbinski 2025-06-15 23:43:27 +02:00
parent 5cae547f8d
commit 3798e170d0
No known key found for this signature in database
140 changed files with 913 additions and 735 deletions

View file

@ -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 ||

View file

@ -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;

View file

@ -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"])