mirror of
https://github.com/versia-pub/server.git
synced 2025-12-06 16:38: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 { MediaBackendType } from "media-manager";
|
||||||
import { db } from "~/drizzle/db";
|
import type { Config } from "~/packages/config-manager";
|
||||||
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];
|
|
||||||
};
|
|
||||||
|
|
||||||
export const getUrl = (name: string, config: Config) => {
|
export const getUrl = (name: string, config: Config) => {
|
||||||
if (config.media.backend === MediaBackendType.LOCAL) {
|
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,
|
type Application,
|
||||||
applicationToAPI,
|
applicationToAPI,
|
||||||
} from "~/database/entities/Application";
|
} from "~/database/entities/Application";
|
||||||
import {
|
|
||||||
attachmentFromLysand,
|
|
||||||
attachmentToAPI,
|
|
||||||
attachmentToLysand,
|
|
||||||
} from "~/database/entities/Attachment";
|
|
||||||
import {
|
import {
|
||||||
type EmojiWithInstance,
|
type EmojiWithInstance,
|
||||||
emojiToAPI,
|
emojiToAPI,
|
||||||
|
|
@ -51,6 +46,7 @@ import {
|
||||||
import { config } from "~/packages/config-manager";
|
import { config } from "~/packages/config-manager";
|
||||||
import type { Attachment as APIAttachment } from "~/types/mastodon/attachment";
|
import type { Attachment as APIAttachment } from "~/types/mastodon/attachment";
|
||||||
import type { Status as APIStatus } from "~/types/mastodon/status";
|
import type { Status as APIStatus } from "~/types/mastodon/status";
|
||||||
|
import { Attachment } from "./attachment";
|
||||||
import { BaseInterface } from "./base";
|
import { BaseInterface } from "./base";
|
||||||
import { User } from "./user";
|
import { User } from "./user";
|
||||||
|
|
||||||
|
|
@ -515,7 +511,7 @@ export class Note extends BaseInterface<typeof Notes, StatusWithRelations> {
|
||||||
const attachments = [];
|
const attachments = [];
|
||||||
|
|
||||||
for (const attachment of note.attachments ?? []) {
|
for (const attachment of note.attachments ?? []) {
|
||||||
const resolvedAttachment = await attachmentFromLysand(
|
const resolvedAttachment = await Attachment.fromLysand(
|
||||||
attachment,
|
attachment,
|
||||||
).catch((e) => {
|
).catch((e) => {
|
||||||
dualLogger.logError(
|
dualLogger.logError(
|
||||||
|
|
@ -711,7 +707,7 @@ export class Note extends BaseInterface<typeof Notes, StatusWithRelations> {
|
||||||
favourited: data.liked,
|
favourited: data.liked,
|
||||||
favourites_count: data.likeCount,
|
favourites_count: data.likeCount,
|
||||||
media_attachments: (data.attachments ?? []).map(
|
media_attachments: (data.attachments ?? []).map(
|
||||||
(a) => attachmentToAPI(a) as APIAttachment,
|
(a) => new Attachment(a).toAPI() as APIAttachment,
|
||||||
),
|
),
|
||||||
mentions: data.mentions.map((mention) => ({
|
mentions: data.mentions.map((mention) => ({
|
||||||
id: mention.id,
|
id: mention.id,
|
||||||
|
|
@ -786,7 +782,7 @@ export class Note extends BaseInterface<typeof Notes, StatusWithRelations> {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
attachments: (status.attachments ?? []).map((attachment) =>
|
attachments: (status.attachments ?? []).map((attachment) =>
|
||||||
attachmentToLysand(attachment),
|
new Attachment(attachment).toLysand(),
|
||||||
),
|
),
|
||||||
is_sensitive: status.sensitive,
|
is_sensitive: status.sensitive,
|
||||||
mentions: status.mentions.map((mention) => mention.uri || ""),
|
mentions: status.mentions.map((mention) => mention.uri || ""),
|
||||||
|
|
|
||||||
|
|
@ -26,10 +26,6 @@ export class Role extends BaseInterface<typeof Roles> {
|
||||||
this.data = reloaded.data;
|
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> {
|
public static async fromId(id: string | null): Promise<Role | null> {
|
||||||
if (!id) return null;
|
if (!id) return null;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -597,9 +597,9 @@ export class User extends BaseInterface<typeof Users, UserWithRelations> {
|
||||||
discoverable: undefined,
|
discoverable: undefined,
|
||||||
mute_expires_at: undefined,
|
mute_expires_at: undefined,
|
||||||
roles: user.roles
|
roles: user.roles
|
||||||
.map((role) => Role.fromRole(role))
|
.map((role) => new Role(role))
|
||||||
.concat(
|
.concat(
|
||||||
Role.fromRole({
|
new Role({
|
||||||
id: "default",
|
id: "default",
|
||||||
name: "Default",
|
name: "Default",
|
||||||
permissions: config.permissions.default,
|
permissions: config.permissions.default,
|
||||||
|
|
@ -612,7 +612,7 @@ export class User extends BaseInterface<typeof Users, UserWithRelations> {
|
||||||
.concat(
|
.concat(
|
||||||
user.isAdmin
|
user.isAdmin
|
||||||
? [
|
? [
|
||||||
Role.fromRole({
|
new Role({
|
||||||
id: "admin",
|
id: "admin",
|
||||||
name: "Admin",
|
name: "Admin",
|
||||||
permissions: config.permissions.admin,
|
permissions: config.permissions.admin,
|
||||||
|
|
|
||||||
|
|
@ -2,15 +2,14 @@ import { applyConfig, auth, handleZodError, idValidator } from "@/api";
|
||||||
import { errorResponse, jsonResponse, response } from "@/response";
|
import { errorResponse, jsonResponse, response } from "@/response";
|
||||||
import { zValidator } from "@hono/zod-validator";
|
import { zValidator } from "@hono/zod-validator";
|
||||||
import { config } from "config-manager";
|
import { config } from "config-manager";
|
||||||
import { eq } from "drizzle-orm";
|
|
||||||
import type { Hono } from "hono";
|
import type { Hono } from "hono";
|
||||||
import type { MediaBackend } from "media-manager";
|
import type { MediaBackend } from "media-manager";
|
||||||
import { MediaBackendType } from "media-manager";
|
import { MediaBackendType } from "media-manager";
|
||||||
import { LocalMediaBackend, S3MediaBackend } from "media-manager";
|
import { LocalMediaBackend, S3MediaBackend } from "media-manager";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import { attachmentToAPI, getUrl } from "~/database/entities/Attachment";
|
import { getUrl } from "~/database/entities/Attachment";
|
||||||
import { db } from "~/drizzle/db";
|
import { RolePermissions } from "~/drizzle/schema";
|
||||||
import { Attachments, RolePermissions } from "~/drizzle/schema";
|
import { Attachment } from "~/packages/database-interface/attachment";
|
||||||
|
|
||||||
export const meta = applyConfig({
|
export const meta = applyConfig({
|
||||||
allowedMethods: ["GET", "PUT"],
|
allowedMethods: ["GET", "PUT"],
|
||||||
|
|
@ -56,18 +55,16 @@ export default (app: Hono) =>
|
||||||
return errorResponse("Invalid ID, must be of type UUIDv7", 404);
|
return errorResponse("Invalid ID, must be of type UUIDv7", 404);
|
||||||
}
|
}
|
||||||
|
|
||||||
const foundAttachment = await db.query.Attachments.findFirst({
|
const attachment = await Attachment.fromId(id);
|
||||||
where: (attachment, { eq }) => eq(attachment.id, id),
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!foundAttachment) {
|
if (!attachment) {
|
||||||
return errorResponse("Media not found", 404);
|
return errorResponse("Media not found", 404);
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (context.req.method) {
|
switch (context.req.method) {
|
||||||
case "GET": {
|
case "GET": {
|
||||||
if (foundAttachment.url) {
|
if (attachment.data.url) {
|
||||||
return jsonResponse(attachmentToAPI(foundAttachment));
|
return jsonResponse(attachment.toAPI());
|
||||||
}
|
}
|
||||||
return response(null, 206);
|
return response(null, 206);
|
||||||
}
|
}
|
||||||
|
|
@ -75,7 +72,7 @@ export default (app: Hono) =>
|
||||||
const { description, thumbnail } =
|
const { description, thumbnail } =
|
||||||
context.req.valid("form");
|
context.req.valid("form");
|
||||||
|
|
||||||
let thumbnailUrl = foundAttachment.thumbnailUrl;
|
let thumbnailUrl = attachment.data.thumbnailUrl;
|
||||||
|
|
||||||
let mediaManager: MediaBackend;
|
let mediaManager: MediaBackend;
|
||||||
|
|
||||||
|
|
@ -97,27 +94,21 @@ export default (app: Hono) =>
|
||||||
}
|
}
|
||||||
|
|
||||||
const descriptionText =
|
const descriptionText =
|
||||||
description || foundAttachment.description;
|
description || attachment.data.description;
|
||||||
|
|
||||||
if (
|
if (
|
||||||
descriptionText !== foundAttachment.description ||
|
descriptionText !== attachment.data.description ||
|
||||||
thumbnailUrl !== foundAttachment.thumbnailUrl
|
thumbnailUrl !== attachment.data.thumbnailUrl
|
||||||
) {
|
) {
|
||||||
const newAttachment = (
|
await attachment.update({
|
||||||
await db
|
|
||||||
.update(Attachments)
|
|
||||||
.set({
|
|
||||||
description: descriptionText,
|
description: descriptionText,
|
||||||
thumbnailUrl,
|
thumbnailUrl,
|
||||||
})
|
});
|
||||||
.where(eq(Attachments.id, id))
|
|
||||||
.returning()
|
|
||||||
)[0];
|
|
||||||
|
|
||||||
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 { LocalMediaBackend, S3MediaBackend } from "media-manager";
|
||||||
import sharp from "sharp";
|
import sharp from "sharp";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import { attachmentToAPI, getUrl } from "~/database/entities/Attachment";
|
import { getUrl } from "~/database/entities/Attachment";
|
||||||
import { db } from "~/drizzle/db";
|
import { RolePermissions } from "~/drizzle/schema";
|
||||||
import { Attachments, RolePermissions } from "~/drizzle/schema";
|
import { Attachment } from "~/packages/database-interface/attachment";
|
||||||
|
|
||||||
export const meta = applyConfig({
|
export const meta = applyConfig({
|
||||||
allowedMethods: ["POST"],
|
allowedMethods: ["POST"],
|
||||||
|
|
@ -127,26 +127,20 @@ export default (app: Hono) =>
|
||||||
thumbnailUrl = getUrl(path, config);
|
thumbnailUrl = getUrl(path, config);
|
||||||
}
|
}
|
||||||
|
|
||||||
const newAttachment = (
|
const newAttachment = await Attachment.insert({
|
||||||
await db
|
|
||||||
.insert(Attachments)
|
|
||||||
.values({
|
|
||||||
url,
|
url,
|
||||||
thumbnailUrl,
|
thumbnailUrl,
|
||||||
sha256: sha256
|
sha256: sha256.update(await file.arrayBuffer()).digest("hex"),
|
||||||
.update(await file.arrayBuffer())
|
|
||||||
.digest("hex"),
|
|
||||||
mimeType: file.type,
|
mimeType: file.type,
|
||||||
description: description ?? "",
|
description: description ?? "",
|
||||||
size: file.size,
|
size: file.size,
|
||||||
blurhash: blurhash ?? undefined,
|
blurhash: blurhash ?? undefined,
|
||||||
width: metadata?.width ?? undefined,
|
width: metadata?.width ?? undefined,
|
||||||
height: metadata?.height ?? undefined,
|
height: metadata?.height ?? undefined,
|
||||||
})
|
});
|
||||||
.returning()
|
|
||||||
)[0];
|
|
||||||
// TODO: Add job to process videos and other media
|
// 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 { LocalMediaBackend, S3MediaBackend } from "media-manager";
|
||||||
import sharp from "sharp";
|
import sharp from "sharp";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import { attachmentToAPI, getUrl } from "~/database/entities/Attachment";
|
import { getUrl } from "~/database/entities/Attachment";
|
||||||
import { db } from "~/drizzle/db";
|
import { RolePermissions } from "~/drizzle/schema";
|
||||||
import { Attachments, RolePermissions } from "~/drizzle/schema";
|
import { Attachment } from "~/packages/database-interface/attachment";
|
||||||
|
|
||||||
export const meta = applyConfig({
|
export const meta = applyConfig({
|
||||||
allowedMethods: ["POST"],
|
allowedMethods: ["POST"],
|
||||||
|
|
@ -127,34 +127,27 @@ export default (app: Hono) =>
|
||||||
thumbnailUrl = getUrl(path, config);
|
thumbnailUrl = getUrl(path, config);
|
||||||
}
|
}
|
||||||
|
|
||||||
const newAttachment = (
|
const newAttachment = await Attachment.insert({
|
||||||
await db
|
|
||||||
.insert(Attachments)
|
|
||||||
.values({
|
|
||||||
url,
|
url,
|
||||||
thumbnailUrl,
|
thumbnailUrl,
|
||||||
sha256: sha256
|
sha256: sha256.update(await file.arrayBuffer()).digest("hex"),
|
||||||
.update(await file.arrayBuffer())
|
|
||||||
.digest("hex"),
|
|
||||||
mimeType: file.type,
|
mimeType: file.type,
|
||||||
description: description ?? "",
|
description: description ?? "",
|
||||||
size: file.size,
|
size: file.size,
|
||||||
blurhash: blurhash ?? undefined,
|
blurhash: blurhash ?? undefined,
|
||||||
width: metadata?.width ?? undefined,
|
width: metadata?.width ?? undefined,
|
||||||
height: metadata?.height ?? undefined,
|
height: metadata?.height ?? undefined,
|
||||||
})
|
});
|
||||||
.returning()
|
|
||||||
)[0];
|
|
||||||
|
|
||||||
// TODO: Add job to process videos and other media
|
// TODO: Add job to process videos and other media
|
||||||
|
|
||||||
if (isImage) {
|
if (isImage) {
|
||||||
return jsonResponse(attachmentToAPI(newAttachment));
|
return jsonResponse(newAttachment.toAPI());
|
||||||
}
|
}
|
||||||
|
|
||||||
return jsonResponse(
|
return jsonResponse(
|
||||||
{
|
{
|
||||||
...attachmentToAPI(newAttachment),
|
...newAttachment.toAPI(),
|
||||||
url: null,
|
url: null,
|
||||||
},
|
},
|
||||||
202,
|
202,
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue