mirror of
https://github.com/versia-pub/server.git
synced 2025-12-06 08:28:19 +01:00
refactor(database): ♻️ Move Attachment into its own class
This commit is contained in:
parent
5565bf00de
commit
2e98859153
|
|
@ -1,114 +1,5 @@
|
|||
import { proxyUrl } from "@/response";
|
||||
import type { EntityValidator } from "@lysand-org/federation";
|
||||
import type { Config } from "config-manager";
|
||||
import type { InferSelectModel } from "drizzle-orm";
|
||||
import { MediaBackendType } from "media-manager";
|
||||
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";
|
||||
|
||||
export type Attachment = InferSelectModel<typeof Attachments>;
|
||||
|
||||
export const attachmentToAPI = (
|
||||
attachment: Attachment,
|
||||
): APIAsyncAttachment | APIAttachment => {
|
||||
let type = "unknown";
|
||||
|
||||
if (attachment.mimeType.startsWith("image/")) {
|
||||
type = "image";
|
||||
} else if (attachment.mimeType.startsWith("video/")) {
|
||||
type = "video";
|
||||
} else if (attachment.mimeType.startsWith("audio/")) {
|
||||
type = "audio";
|
||||
}
|
||||
|
||||
return {
|
||||
id: attachment.id,
|
||||
type: type as "image" | "video" | "audio" | "unknown",
|
||||
url: proxyUrl(attachment.url) ?? "",
|
||||
remote_url: proxyUrl(attachment.remoteUrl),
|
||||
preview_url: proxyUrl(attachment.thumbnailUrl || attachment.url),
|
||||
text_url: null,
|
||||
meta: {
|
||||
width: attachment.width || undefined,
|
||||
height: attachment.height || undefined,
|
||||
fps: attachment.fps || undefined,
|
||||
size:
|
||||
attachment.width && attachment.height
|
||||
? `${attachment.width}x${attachment.height}`
|
||||
: undefined,
|
||||
duration: attachment.duration || undefined,
|
||||
length: undefined,
|
||||
aspect:
|
||||
attachment.width && attachment.height
|
||||
? attachment.width / attachment.height
|
||||
: undefined,
|
||||
original: {
|
||||
width: attachment.width || undefined,
|
||||
height: attachment.height || undefined,
|
||||
size:
|
||||
attachment.width && attachment.height
|
||||
? `${attachment.width}x${attachment.height}`
|
||||
: undefined,
|
||||
aspect:
|
||||
attachment.width && attachment.height
|
||||
? attachment.width / attachment.height
|
||||
: undefined,
|
||||
},
|
||||
// Idk whether size or length is the right value
|
||||
},
|
||||
description: attachment.description,
|
||||
blurhash: attachment.blurhash,
|
||||
};
|
||||
};
|
||||
|
||||
export const attachmentToLysand = (
|
||||
attachment: Attachment,
|
||||
): typeof EntityValidator.$ContentFormat => {
|
||||
return {
|
||||
[attachment.mimeType]: {
|
||||
content: attachment.url,
|
||||
blurhash: attachment.blurhash ?? undefined,
|
||||
description: attachment.description ?? undefined,
|
||||
duration: attachment.duration ?? undefined,
|
||||
fps: attachment.fps ?? undefined,
|
||||
height: attachment.height ?? undefined,
|
||||
size: attachment.size ?? undefined,
|
||||
hash: attachment.sha256
|
||||
? {
|
||||
sha256: attachment.sha256,
|
||||
}
|
||||
: undefined,
|
||||
width: attachment.width ?? undefined,
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
export const attachmentFromLysand = async (
|
||||
attachmentToConvert: typeof EntityValidator.$ContentFormat,
|
||||
): Promise<InferSelectModel<typeof Attachments>> => {
|
||||
const key = Object.keys(attachmentToConvert)[0];
|
||||
const value = attachmentToConvert[key];
|
||||
|
||||
const result = await db
|
||||
.insert(Attachments)
|
||||
.values({
|
||||
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,
|
||||
})
|
||||
.returning();
|
||||
|
||||
return result[0];
|
||||
};
|
||||
import type { Config } from "~/packages/config-manager";
|
||||
|
||||
export const getUrl = (name: string, config: Config) => {
|
||||
if (config.media.backend === MediaBackendType.LOCAL) {
|
||||
|
|
|
|||
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,
|
||||
|
|
|
|||
|
|
@ -2,15 +2,14 @@ import { applyConfig, auth, handleZodError, idValidator } from "@/api";
|
|||
import { errorResponse, jsonResponse, response } from "@/response";
|
||||
import { zValidator } from "@hono/zod-validator";
|
||||
import { config } from "config-manager";
|
||||
import { eq } from "drizzle-orm";
|
||||
import type { Hono } from "hono";
|
||||
import type { MediaBackend } from "media-manager";
|
||||
import { MediaBackendType } from "media-manager";
|
||||
import { LocalMediaBackend, S3MediaBackend } from "media-manager";
|
||||
import { z } from "zod";
|
||||
import { attachmentToAPI, getUrl } from "~/database/entities/Attachment";
|
||||
import { db } from "~/drizzle/db";
|
||||
import { Attachments, RolePermissions } from "~/drizzle/schema";
|
||||
import { getUrl } from "~/database/entities/Attachment";
|
||||
import { RolePermissions } from "~/drizzle/schema";
|
||||
import { Attachment } from "~/packages/database-interface/attachment";
|
||||
|
||||
export const meta = applyConfig({
|
||||
allowedMethods: ["GET", "PUT"],
|
||||
|
|
@ -56,18 +55,16 @@ export default (app: Hono) =>
|
|||
return errorResponse("Invalid ID, must be of type UUIDv7", 404);
|
||||
}
|
||||
|
||||
const foundAttachment = await db.query.Attachments.findFirst({
|
||||
where: (attachment, { eq }) => eq(attachment.id, id),
|
||||
});
|
||||
const attachment = await Attachment.fromId(id);
|
||||
|
||||
if (!foundAttachment) {
|
||||
if (!attachment) {
|
||||
return errorResponse("Media not found", 404);
|
||||
}
|
||||
|
||||
switch (context.req.method) {
|
||||
case "GET": {
|
||||
if (foundAttachment.url) {
|
||||
return jsonResponse(attachmentToAPI(foundAttachment));
|
||||
if (attachment.data.url) {
|
||||
return jsonResponse(attachment.toAPI());
|
||||
}
|
||||
return response(null, 206);
|
||||
}
|
||||
|
|
@ -75,7 +72,7 @@ export default (app: Hono) =>
|
|||
const { description, thumbnail } =
|
||||
context.req.valid("form");
|
||||
|
||||
let thumbnailUrl = foundAttachment.thumbnailUrl;
|
||||
let thumbnailUrl = attachment.data.thumbnailUrl;
|
||||
|
||||
let mediaManager: MediaBackend;
|
||||
|
||||
|
|
@ -97,27 +94,21 @@ export default (app: Hono) =>
|
|||
}
|
||||
|
||||
const descriptionText =
|
||||
description || foundAttachment.description;
|
||||
description || attachment.data.description;
|
||||
|
||||
if (
|
||||
descriptionText !== foundAttachment.description ||
|
||||
thumbnailUrl !== foundAttachment.thumbnailUrl
|
||||
descriptionText !== attachment.data.description ||
|
||||
thumbnailUrl !== attachment.data.thumbnailUrl
|
||||
) {
|
||||
const newAttachment = (
|
||||
await db
|
||||
.update(Attachments)
|
||||
.set({
|
||||
description: descriptionText,
|
||||
thumbnailUrl,
|
||||
})
|
||||
.where(eq(Attachments.id, id))
|
||||
.returning()
|
||||
)[0];
|
||||
await attachment.update({
|
||||
description: descriptionText,
|
||||
thumbnailUrl,
|
||||
});
|
||||
|
||||
return jsonResponse(attachmentToAPI(newAttachment));
|
||||
return jsonResponse(attachment.toAPI());
|
||||
}
|
||||
|
||||
return jsonResponse(attachmentToAPI(foundAttachment));
|
||||
return jsonResponse(attachment.toAPI());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -9,9 +9,9 @@ import type { MediaBackend } from "media-manager";
|
|||
import { LocalMediaBackend, S3MediaBackend } from "media-manager";
|
||||
import sharp from "sharp";
|
||||
import { z } from "zod";
|
||||
import { attachmentToAPI, getUrl } from "~/database/entities/Attachment";
|
||||
import { db } from "~/drizzle/db";
|
||||
import { Attachments, RolePermissions } from "~/drizzle/schema";
|
||||
import { getUrl } from "~/database/entities/Attachment";
|
||||
import { RolePermissions } from "~/drizzle/schema";
|
||||
import { Attachment } from "~/packages/database-interface/attachment";
|
||||
|
||||
export const meta = applyConfig({
|
||||
allowedMethods: ["POST"],
|
||||
|
|
@ -127,26 +127,20 @@ export default (app: Hono) =>
|
|||
thumbnailUrl = getUrl(path, config);
|
||||
}
|
||||
|
||||
const newAttachment = (
|
||||
await db
|
||||
.insert(Attachments)
|
||||
.values({
|
||||
url,
|
||||
thumbnailUrl,
|
||||
sha256: sha256
|
||||
.update(await file.arrayBuffer())
|
||||
.digest("hex"),
|
||||
mimeType: file.type,
|
||||
description: description ?? "",
|
||||
size: file.size,
|
||||
blurhash: blurhash ?? undefined,
|
||||
width: metadata?.width ?? undefined,
|
||||
height: metadata?.height ?? undefined,
|
||||
})
|
||||
.returning()
|
||||
)[0];
|
||||
const newAttachment = await Attachment.insert({
|
||||
url,
|
||||
thumbnailUrl,
|
||||
sha256: sha256.update(await file.arrayBuffer()).digest("hex"),
|
||||
mimeType: file.type,
|
||||
description: description ?? "",
|
||||
size: file.size,
|
||||
blurhash: blurhash ?? undefined,
|
||||
width: metadata?.width ?? undefined,
|
||||
height: metadata?.height ?? undefined,
|
||||
});
|
||||
|
||||
// TODO: Add job to process videos and other media
|
||||
|
||||
return jsonResponse(attachmentToAPI(newAttachment));
|
||||
return jsonResponse(newAttachment.toAPI());
|
||||
},
|
||||
);
|
||||
|
|
|
|||
|
|
@ -9,9 +9,9 @@ import { MediaBackendType } from "media-manager";
|
|||
import { LocalMediaBackend, S3MediaBackend } from "media-manager";
|
||||
import sharp from "sharp";
|
||||
import { z } from "zod";
|
||||
import { attachmentToAPI, getUrl } from "~/database/entities/Attachment";
|
||||
import { db } from "~/drizzle/db";
|
||||
import { Attachments, RolePermissions } from "~/drizzle/schema";
|
||||
import { getUrl } from "~/database/entities/Attachment";
|
||||
import { RolePermissions } from "~/drizzle/schema";
|
||||
import { Attachment } from "~/packages/database-interface/attachment";
|
||||
|
||||
export const meta = applyConfig({
|
||||
allowedMethods: ["POST"],
|
||||
|
|
@ -127,34 +127,27 @@ export default (app: Hono) =>
|
|||
thumbnailUrl = getUrl(path, config);
|
||||
}
|
||||
|
||||
const newAttachment = (
|
||||
await db
|
||||
.insert(Attachments)
|
||||
.values({
|
||||
url,
|
||||
thumbnailUrl,
|
||||
sha256: sha256
|
||||
.update(await file.arrayBuffer())
|
||||
.digest("hex"),
|
||||
mimeType: file.type,
|
||||
description: description ?? "",
|
||||
size: file.size,
|
||||
blurhash: blurhash ?? undefined,
|
||||
width: metadata?.width ?? undefined,
|
||||
height: metadata?.height ?? undefined,
|
||||
})
|
||||
.returning()
|
||||
)[0];
|
||||
const newAttachment = await Attachment.insert({
|
||||
url,
|
||||
thumbnailUrl,
|
||||
sha256: sha256.update(await file.arrayBuffer()).digest("hex"),
|
||||
mimeType: file.type,
|
||||
description: description ?? "",
|
||||
size: file.size,
|
||||
blurhash: blurhash ?? undefined,
|
||||
width: metadata?.width ?? undefined,
|
||||
height: metadata?.height ?? undefined,
|
||||
});
|
||||
|
||||
// TODO: Add job to process videos and other media
|
||||
|
||||
if (isImage) {
|
||||
return jsonResponse(attachmentToAPI(newAttachment));
|
||||
return jsonResponse(newAttachment.toAPI());
|
||||
}
|
||||
|
||||
return jsonResponse(
|
||||
{
|
||||
...attachmentToAPI(newAttachment),
|
||||
...newAttachment.toAPI(),
|
||||
url: null,
|
||||
},
|
||||
202,
|
||||
|
|
|
|||
Loading…
Reference in a new issue