mirror of
https://github.com/versia-pub/server.git
synced 2026-03-13 05:49:16 +01:00
refactor(database): ♻️ Move Attachment into its own class
This commit is contained in:
parent
5565bf00de
commit
2e98859153
8 changed files with 270 additions and 196 deletions
213
packages/database-interface/attachment.ts
Normal file
213
packages/database-interface/attachment.ts
Normal file
|
|
@ -0,0 +1,213 @@
|
|||
import { proxyUrl } from "@/response";
|
||||
import type { EntityValidator } from "@lysand-org/federation";
|
||||
import {
|
||||
type InferInsertModel,
|
||||
type InferSelectModel,
|
||||
type SQL,
|
||||
desc,
|
||||
eq,
|
||||
inArray,
|
||||
} from "drizzle-orm";
|
||||
import { db } from "~/drizzle/db";
|
||||
import { Attachments } from "~/drizzle/schema";
|
||||
import type { AsyncAttachment as APIAsyncAttachment } from "~/types/mastodon/async_attachment";
|
||||
import type { Attachment as APIAttachment } from "~/types/mastodon/attachment";
|
||||
import { BaseInterface } from "./base";
|
||||
|
||||
export type AttachmentType = InferSelectModel<typeof Attachments>;
|
||||
|
||||
export class Attachment extends BaseInterface<typeof Attachments> {
|
||||
async reload(): Promise<void> {
|
||||
const reloaded = await Attachment.fromId(this.data.id);
|
||||
|
||||
if (!reloaded) {
|
||||
throw new Error("Failed to reload role");
|
||||
}
|
||||
|
||||
this.data = reloaded.data;
|
||||
}
|
||||
|
||||
public static async fromId(id: string | null): Promise<Attachment | null> {
|
||||
if (!id) return null;
|
||||
|
||||
return await Attachment.fromSql(eq(Attachments.id, id));
|
||||
}
|
||||
|
||||
public static async fromIds(ids: string[]): Promise<Attachment[]> {
|
||||
return await Attachment.manyFromSql(inArray(Attachments.id, ids));
|
||||
}
|
||||
|
||||
public static async fromSql(
|
||||
sql: SQL<unknown> | undefined,
|
||||
orderBy: SQL<unknown> | undefined = desc(Attachments.id),
|
||||
): Promise<Attachment | null> {
|
||||
const found = await db.query.Attachments.findFirst({
|
||||
where: sql,
|
||||
orderBy,
|
||||
});
|
||||
|
||||
if (!found) return null;
|
||||
return new Attachment(found);
|
||||
}
|
||||
|
||||
public static async manyFromSql(
|
||||
sql: SQL<unknown> | undefined,
|
||||
orderBy: SQL<unknown> | undefined = desc(Attachments.id),
|
||||
limit?: number,
|
||||
offset?: number,
|
||||
extra?: Parameters<typeof db.query.Attachments.findMany>[0],
|
||||
): Promise<Attachment[]> {
|
||||
const found = await db.query.Attachments.findMany({
|
||||
where: sql,
|
||||
orderBy,
|
||||
limit,
|
||||
offset,
|
||||
with: extra?.with,
|
||||
});
|
||||
|
||||
return found.map((s) => new Attachment(s));
|
||||
}
|
||||
|
||||
async update(
|
||||
newAttachment: Partial<AttachmentType>,
|
||||
): Promise<AttachmentType> {
|
||||
await db
|
||||
.update(Attachments)
|
||||
.set(newAttachment)
|
||||
.where(eq(Attachments.id, this.id));
|
||||
|
||||
const updated = await Attachment.fromId(this.data.id);
|
||||
|
||||
if (!updated) {
|
||||
throw new Error("Failed to update role");
|
||||
}
|
||||
|
||||
this.data = updated.data;
|
||||
return updated.data;
|
||||
}
|
||||
|
||||
async save(): Promise<AttachmentType> {
|
||||
return this.update(this.data);
|
||||
}
|
||||
|
||||
async delete(ids: string[]): Promise<void>;
|
||||
async delete(): Promise<void>;
|
||||
async delete(ids?: unknown): Promise<void> {
|
||||
if (Array.isArray(ids)) {
|
||||
await db.delete(Attachments).where(inArray(Attachments.id, ids));
|
||||
} else {
|
||||
await db.delete(Attachments).where(eq(Attachments.id, this.id));
|
||||
}
|
||||
}
|
||||
|
||||
public static async insert(
|
||||
data: InferInsertModel<typeof Attachments>,
|
||||
): Promise<Attachment> {
|
||||
const inserted = (
|
||||
await db.insert(Attachments).values(data).returning()
|
||||
)[0];
|
||||
|
||||
const role = await Attachment.fromId(inserted.id);
|
||||
|
||||
if (!role) {
|
||||
throw new Error("Failed to insert role");
|
||||
}
|
||||
|
||||
return role;
|
||||
}
|
||||
|
||||
get id() {
|
||||
return this.data.id;
|
||||
}
|
||||
|
||||
public toAPI(): APIAttachment | APIAsyncAttachment {
|
||||
let type = "unknown";
|
||||
|
||||
if (this.data.mimeType.startsWith("image/")) {
|
||||
type = "image";
|
||||
} else if (this.data.mimeType.startsWith("video/")) {
|
||||
type = "video";
|
||||
} else if (this.data.mimeType.startsWith("audio/")) {
|
||||
type = "audio";
|
||||
}
|
||||
|
||||
return {
|
||||
id: this.data.id,
|
||||
type: type as "image" | "video" | "audio" | "unknown",
|
||||
url: proxyUrl(this.data.url) ?? "",
|
||||
remote_url: proxyUrl(this.data.remoteUrl),
|
||||
preview_url: proxyUrl(this.data.thumbnailUrl || this.data.url),
|
||||
text_url: null,
|
||||
meta: {
|
||||
width: this.data.width || undefined,
|
||||
height: this.data.height || undefined,
|
||||
fps: this.data.fps || undefined,
|
||||
size:
|
||||
this.data.width && this.data.height
|
||||
? `${this.data.width}x${this.data.height}`
|
||||
: undefined,
|
||||
duration: this.data.duration || undefined,
|
||||
length: undefined,
|
||||
aspect:
|
||||
this.data.width && this.data.height
|
||||
? this.data.width / this.data.height
|
||||
: undefined,
|
||||
original: {
|
||||
width: this.data.width || undefined,
|
||||
height: this.data.height || undefined,
|
||||
size:
|
||||
this.data.width && this.data.height
|
||||
? `${this.data.width}x${this.data.height}`
|
||||
: undefined,
|
||||
aspect:
|
||||
this.data.width && this.data.height
|
||||
? this.data.width / this.data.height
|
||||
: undefined,
|
||||
},
|
||||
// Idk whether size or length is the right value
|
||||
},
|
||||
description: this.data.description,
|
||||
blurhash: this.data.blurhash,
|
||||
};
|
||||
}
|
||||
|
||||
public toLysand(): typeof EntityValidator.$ContentFormat {
|
||||
return {
|
||||
[this.data.mimeType]: {
|
||||
content: this.data.url,
|
||||
blurhash: this.data.blurhash ?? undefined,
|
||||
description: this.data.description ?? undefined,
|
||||
duration: this.data.duration ?? undefined,
|
||||
fps: this.data.fps ?? undefined,
|
||||
height: this.data.height ?? undefined,
|
||||
size: this.data.size ?? undefined,
|
||||
hash: this.data.sha256
|
||||
? {
|
||||
sha256: this.data.sha256,
|
||||
}
|
||||
: undefined,
|
||||
width: this.data.width ?? undefined,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
public static fromLysand(
|
||||
attachmentToConvert: typeof EntityValidator.$ContentFormat,
|
||||
): Promise<Attachment> {
|
||||
const key = Object.keys(attachmentToConvert)[0];
|
||||
const value = attachmentToConvert[key];
|
||||
|
||||
return Attachment.insert({
|
||||
mimeType: key,
|
||||
url: value.content,
|
||||
description: value.description || undefined,
|
||||
duration: value.duration || undefined,
|
||||
fps: value.fps || undefined,
|
||||
height: value.height || undefined,
|
||||
size: value.size || undefined,
|
||||
width: value.width || undefined,
|
||||
sha256: value.hash?.sha256 || undefined,
|
||||
blurhash: value.blurhash || undefined,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -21,11 +21,6 @@ import {
|
|||
type Application,
|
||||
applicationToAPI,
|
||||
} from "~/database/entities/Application";
|
||||
import {
|
||||
attachmentFromLysand,
|
||||
attachmentToAPI,
|
||||
attachmentToLysand,
|
||||
} from "~/database/entities/Attachment";
|
||||
import {
|
||||
type EmojiWithInstance,
|
||||
emojiToAPI,
|
||||
|
|
@ -51,6 +46,7 @@ import {
|
|||
import { config } from "~/packages/config-manager";
|
||||
import type { Attachment as APIAttachment } from "~/types/mastodon/attachment";
|
||||
import type { Status as APIStatus } from "~/types/mastodon/status";
|
||||
import { Attachment } from "./attachment";
|
||||
import { BaseInterface } from "./base";
|
||||
import { User } from "./user";
|
||||
|
||||
|
|
@ -515,7 +511,7 @@ export class Note extends BaseInterface<typeof Notes, StatusWithRelations> {
|
|||
const attachments = [];
|
||||
|
||||
for (const attachment of note.attachments ?? []) {
|
||||
const resolvedAttachment = await attachmentFromLysand(
|
||||
const resolvedAttachment = await Attachment.fromLysand(
|
||||
attachment,
|
||||
).catch((e) => {
|
||||
dualLogger.logError(
|
||||
|
|
@ -711,7 +707,7 @@ export class Note extends BaseInterface<typeof Notes, StatusWithRelations> {
|
|||
favourited: data.liked,
|
||||
favourites_count: data.likeCount,
|
||||
media_attachments: (data.attachments ?? []).map(
|
||||
(a) => attachmentToAPI(a) as APIAttachment,
|
||||
(a) => new Attachment(a).toAPI() as APIAttachment,
|
||||
),
|
||||
mentions: data.mentions.map((mention) => ({
|
||||
id: mention.id,
|
||||
|
|
@ -786,7 +782,7 @@ export class Note extends BaseInterface<typeof Notes, StatusWithRelations> {
|
|||
},
|
||||
},
|
||||
attachments: (status.attachments ?? []).map((attachment) =>
|
||||
attachmentToLysand(attachment),
|
||||
new Attachment(attachment).toLysand(),
|
||||
),
|
||||
is_sensitive: status.sensitive,
|
||||
mentions: status.mentions.map((mention) => mention.uri || ""),
|
||||
|
|
|
|||
|
|
@ -26,10 +26,6 @@ export class Role extends BaseInterface<typeof Roles> {
|
|||
this.data = reloaded.data;
|
||||
}
|
||||
|
||||
public static fromRole(role: InferSelectModel<typeof Roles>) {
|
||||
return new Role(role);
|
||||
}
|
||||
|
||||
public static async fromId(id: string | null): Promise<Role | null> {
|
||||
if (!id) return null;
|
||||
|
||||
|
|
|
|||
|
|
@ -597,9 +597,9 @@ export class User extends BaseInterface<typeof Users, UserWithRelations> {
|
|||
discoverable: undefined,
|
||||
mute_expires_at: undefined,
|
||||
roles: user.roles
|
||||
.map((role) => Role.fromRole(role))
|
||||
.map((role) => new Role(role))
|
||||
.concat(
|
||||
Role.fromRole({
|
||||
new Role({
|
||||
id: "default",
|
||||
name: "Default",
|
||||
permissions: config.permissions.default,
|
||||
|
|
@ -612,7 +612,7 @@ export class User extends BaseInterface<typeof Users, UserWithRelations> {
|
|||
.concat(
|
||||
user.isAdmin
|
||||
? [
|
||||
Role.fromRole({
|
||||
new Role({
|
||||
id: "admin",
|
||||
name: "Admin",
|
||||
permissions: config.permissions.admin,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue