mirror of
https://github.com/versia-pub/server.git
synced 2025-12-06 08:28:19 +01:00
refactor(database): 🚚 Rename "Attachment" to "Media"
This commit is contained in:
parent
bbd56b600d
commit
2f61cd8f0a
|
|
@ -97,7 +97,7 @@ export default apiRoute((app) =>
|
||||||
min_id ? gt(Notes.id, min_id) : undefined,
|
min_id ? gt(Notes.id, min_id) : undefined,
|
||||||
eq(Notes.authorId, otherUser.id),
|
eq(Notes.authorId, otherUser.id),
|
||||||
only_media
|
only_media
|
||||||
? sql`EXISTS (SELECT 1 FROM "Attachments" WHERE "Attachments"."noteId" = ${Notes.id})`
|
? sql`EXISTS (SELECT 1 FROM "Medias" WHERE "Medias"."noteId" = ${Notes.id})`
|
||||||
: undefined,
|
: undefined,
|
||||||
pinned
|
pinned
|
||||||
? sql`EXISTS (SELECT 1 FROM "UserToPinnedNotes" WHERE "UserToPinnedNotes"."noteId" = ${Notes.id} AND "UserToPinnedNotes"."userId" = ${otherUser.id})`
|
? sql`EXISTS (SELECT 1 FROM "UserToPinnedNotes" WHERE "UserToPinnedNotes"."noteId" = ${Notes.id} AND "UserToPinnedNotes"."userId" = ${otherUser.id})`
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ import { mimeLookup } from "@/content_types";
|
||||||
import { mergeAndDeduplicate } from "@/lib";
|
import { mergeAndDeduplicate } from "@/lib";
|
||||||
import { sanitizedHtmlStrip } from "@/sanitization";
|
import { sanitizedHtmlStrip } from "@/sanitization";
|
||||||
import { createRoute } from "@hono/zod-openapi";
|
import { createRoute } from "@hono/zod-openapi";
|
||||||
import { Attachment, Emoji, User } from "@versia/kit/db";
|
import { Emoji, Media, User } from "@versia/kit/db";
|
||||||
import { RolePermissions, Users } from "@versia/kit/tables";
|
import { RolePermissions, Users } from "@versia/kit/tables";
|
||||||
import { and, eq, isNull } from "drizzle-orm";
|
import { and, eq, isNull } from "drizzle-orm";
|
||||||
import ISO6391 from "iso-639-1";
|
import ISO6391 from "iso-639-1";
|
||||||
|
|
@ -251,7 +251,7 @@ export default apiRoute((app) =>
|
||||||
await mediaManager.addFile(avatar);
|
await mediaManager.addFile(avatar);
|
||||||
const contentType = uploadedFile.type;
|
const contentType = uploadedFile.type;
|
||||||
|
|
||||||
self.avatar = Attachment.getUrl(path);
|
self.avatar = Media.getUrl(path);
|
||||||
self.source.avatar = {
|
self.source.avatar = {
|
||||||
content_type: contentType,
|
content_type: contentType,
|
||||||
};
|
};
|
||||||
|
|
@ -269,7 +269,7 @@ export default apiRoute((app) =>
|
||||||
await mediaManager.addFile(header);
|
await mediaManager.addFile(header);
|
||||||
const contentType = uploadedFile.type;
|
const contentType = uploadedFile.type;
|
||||||
|
|
||||||
self.header = Attachment.getUrl(path);
|
self.header = Media.getUrl(path);
|
||||||
self.source.header = {
|
self.source.header = {
|
||||||
content_type: contentType,
|
content_type: contentType,
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import { apiRoute, auth, emojiValidator, jsonOrForm } from "@/api";
|
import { apiRoute, auth, emojiValidator, jsonOrForm } from "@/api";
|
||||||
import { mimeLookup } from "@/content_types";
|
import { mimeLookup } from "@/content_types";
|
||||||
import { createRoute } from "@hono/zod-openapi";
|
import { createRoute } from "@hono/zod-openapi";
|
||||||
import { Attachment, Emoji, db } from "@versia/kit/db";
|
import { Emoji, Media, db } from "@versia/kit/db";
|
||||||
import { Emojis, RolePermissions } from "@versia/kit/tables";
|
import { Emojis, RolePermissions } from "@versia/kit/tables";
|
||||||
import { eq } from "drizzle-orm";
|
import { eq } from "drizzle-orm";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
@ -270,7 +270,7 @@ export default apiRoute((app) => {
|
||||||
if (element instanceof File) {
|
if (element instanceof File) {
|
||||||
const uploaded = await mediaManager.addFile(element);
|
const uploaded = await mediaManager.addFile(element);
|
||||||
|
|
||||||
url = Attachment.getUrl(uploaded.path);
|
url = Media.getUrl(uploaded.path);
|
||||||
contentType = uploaded.uploadedFile.type;
|
contentType = uploaded.uploadedFile.type;
|
||||||
} else {
|
} else {
|
||||||
url = element;
|
url = element;
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import { apiRoute, auth, emojiValidator, jsonOrForm } from "@/api";
|
import { apiRoute, auth, emojiValidator, jsonOrForm } from "@/api";
|
||||||
import { mimeLookup } from "@/content_types";
|
import { mimeLookup } from "@/content_types";
|
||||||
import { createRoute } from "@hono/zod-openapi";
|
import { createRoute } from "@hono/zod-openapi";
|
||||||
import { Attachment, Emoji } from "@versia/kit/db";
|
import { Emoji, Media } from "@versia/kit/db";
|
||||||
import { Emojis, RolePermissions } from "@versia/kit/tables";
|
import { Emojis, RolePermissions } from "@versia/kit/tables";
|
||||||
import { and, eq, isNull, or } from "drizzle-orm";
|
import { and, eq, isNull, or } from "drizzle-orm";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
@ -149,7 +149,7 @@ export default apiRoute((app) =>
|
||||||
|
|
||||||
const uploaded = await mediaManager.addFile(element);
|
const uploaded = await mediaManager.addFile(element);
|
||||||
|
|
||||||
url = Attachment.getUrl(uploaded.path);
|
url = Media.getUrl(uploaded.path);
|
||||||
contentType = uploaded.uploadedFile.type;
|
contentType = uploaded.uploadedFile.type;
|
||||||
} else {
|
} else {
|
||||||
url = element;
|
url = element;
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import { apiRoute, auth } from "@/api";
|
import { apiRoute, auth } from "@/api";
|
||||||
import { createRoute } from "@hono/zod-openapi";
|
import { createRoute } from "@hono/zod-openapi";
|
||||||
import { Attachment } from "@versia/kit/db";
|
import { Media } from "@versia/kit/db";
|
||||||
import { RolePermissions } from "@versia/kit/tables";
|
import { RolePermissions } from "@versia/kit/tables";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import { ApiError } from "~/classes/errors/api-error";
|
import { ApiError } from "~/classes/errors/api-error";
|
||||||
|
|
@ -48,7 +48,7 @@ const routePut = createRoute({
|
||||||
description: "Media updated",
|
description: "Media updated",
|
||||||
content: {
|
content: {
|
||||||
"application/json": {
|
"application/json": {
|
||||||
schema: Attachment.schema,
|
schema: Media.schema,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
@ -82,7 +82,7 @@ const routeGet = createRoute({
|
||||||
description: "Media",
|
description: "Media",
|
||||||
content: {
|
content: {
|
||||||
"application/json": {
|
"application/json": {
|
||||||
schema: Attachment.schema,
|
schema: Media.schema,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
@ -101,7 +101,7 @@ export default apiRoute((app) => {
|
||||||
app.openapi(routePut, async (context) => {
|
app.openapi(routePut, async (context) => {
|
||||||
const { id } = context.req.valid("param");
|
const { id } = context.req.valid("param");
|
||||||
|
|
||||||
const attachment = await Attachment.fromId(id);
|
const attachment = await Media.fromId(id);
|
||||||
|
|
||||||
if (!attachment) {
|
if (!attachment) {
|
||||||
throw new ApiError(404, "Media not found");
|
throw new ApiError(404, "Media not found");
|
||||||
|
|
@ -115,7 +115,7 @@ export default apiRoute((app) => {
|
||||||
|
|
||||||
if (thumbnail) {
|
if (thumbnail) {
|
||||||
const { path } = await mediaManager.addFile(thumbnail);
|
const { path } = await mediaManager.addFile(thumbnail);
|
||||||
thumbnailUrl = Attachment.getUrl(path);
|
thumbnailUrl = Media.getUrl(path);
|
||||||
}
|
}
|
||||||
|
|
||||||
const descriptionText = description || attachment.data.description;
|
const descriptionText = description || attachment.data.description;
|
||||||
|
|
@ -138,7 +138,7 @@ export default apiRoute((app) => {
|
||||||
app.openapi(routeGet, async (context) => {
|
app.openapi(routeGet, async (context) => {
|
||||||
const { id } = context.req.valid("param");
|
const { id } = context.req.valid("param");
|
||||||
|
|
||||||
const attachment = await Attachment.fromId(id);
|
const attachment = await Media.fromId(id);
|
||||||
|
|
||||||
if (!attachment) {
|
if (!attachment) {
|
||||||
throw new ApiError(404, "Media not found");
|
throw new ApiError(404, "Media not found");
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import { apiRoute, auth } from "@/api";
|
import { apiRoute, auth } from "@/api";
|
||||||
import { createRoute } from "@hono/zod-openapi";
|
import { createRoute } from "@hono/zod-openapi";
|
||||||
import { Attachment } from "@versia/kit/db";
|
import { Media } from "@versia/kit/db";
|
||||||
import { RolePermissions } from "@versia/kit/tables";
|
import { RolePermissions } from "@versia/kit/tables";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import { config } from "~/packages/config-manager/index.ts";
|
import { config } from "~/packages/config-manager/index.ts";
|
||||||
|
|
@ -43,7 +43,7 @@ const route = createRoute({
|
||||||
description: "Attachment",
|
description: "Attachment",
|
||||||
content: {
|
content: {
|
||||||
"application/json": {
|
"application/json": {
|
||||||
schema: Attachment.schema,
|
schema: Media.schema,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
@ -71,7 +71,7 @@ export default apiRoute((app) =>
|
||||||
app.openapi(route, async (context) => {
|
app.openapi(route, async (context) => {
|
||||||
const { file, thumbnail, description } = context.req.valid("form");
|
const { file, thumbnail, description } = context.req.valid("form");
|
||||||
|
|
||||||
const attachment = await Attachment.fromFile(file, {
|
const attachment = await Media.fromFile(file, {
|
||||||
thumbnail,
|
thumbnail,
|
||||||
description,
|
description,
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import { apiRoute, auth, jsonOrForm, withNoteParam } from "@/api";
|
import { apiRoute, auth, jsonOrForm, withNoteParam } from "@/api";
|
||||||
import { createRoute } from "@hono/zod-openapi";
|
import { createRoute } from "@hono/zod-openapi";
|
||||||
import { Attachment, Note } from "@versia/kit/db";
|
import { Media, Note } from "@versia/kit/db";
|
||||||
import { RolePermissions } from "@versia/kit/tables";
|
import { RolePermissions } from "@versia/kit/tables";
|
||||||
import ISO6391 from "iso-639-1";
|
import ISO6391 from "iso-639-1";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
@ -246,7 +246,7 @@ export default apiRoute((app) => {
|
||||||
} = context.req.valid("json");
|
} = context.req.valid("json");
|
||||||
|
|
||||||
const foundAttachments =
|
const foundAttachments =
|
||||||
media_ids.length > 0 ? await Attachment.fromIds(media_ids) : [];
|
media_ids.length > 0 ? await Media.fromIds(media_ids) : [];
|
||||||
|
|
||||||
if (foundAttachments.length !== media_ids.length) {
|
if (foundAttachments.length !== media_ids.length) {
|
||||||
throw new ApiError(
|
throw new ApiError(
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import { apiRoute, auth, jsonOrForm } from "@/api";
|
import { apiRoute, auth, jsonOrForm } from "@/api";
|
||||||
import { createRoute } from "@hono/zod-openapi";
|
import { createRoute } from "@hono/zod-openapi";
|
||||||
import { Attachment, Note } from "@versia/kit/db";
|
import { Media, Note } from "@versia/kit/db";
|
||||||
import { RolePermissions } from "@versia/kit/tables";
|
import { RolePermissions } from "@versia/kit/tables";
|
||||||
import ISO6391 from "iso-639-1";
|
import ISO6391 from "iso-639-1";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
@ -151,7 +151,7 @@ export default apiRoute((app) =>
|
||||||
|
|
||||||
// Check if media attachments are all valid
|
// Check if media attachments are all valid
|
||||||
const foundAttachments =
|
const foundAttachments =
|
||||||
media_ids.length > 0 ? await Attachment.fromIds(media_ids) : [];
|
media_ids.length > 0 ? await Media.fromIds(media_ids) : [];
|
||||||
|
|
||||||
if (foundAttachments.length !== media_ids.length) {
|
if (foundAttachments.length !== media_ids.length) {
|
||||||
throw new ApiError(
|
throw new ApiError(
|
||||||
|
|
|
||||||
|
|
@ -74,7 +74,7 @@ export default apiRoute((app) =>
|
||||||
? sql`EXISTS (SELECT 1 FROM "Users" WHERE "Users"."id" = ${Notes.authorId} AND "Users"."instanceId" IS NULL)`
|
? sql`EXISTS (SELECT 1 FROM "Users" WHERE "Users"."id" = ${Notes.authorId} AND "Users"."instanceId" IS NULL)`
|
||||||
: undefined,
|
: undefined,
|
||||||
only_media
|
only_media
|
||||||
? sql`EXISTS (SELECT 1 FROM "Attachments" WHERE "Attachments"."noteId" = ${Notes.id})`
|
? sql`EXISTS (SELECT 1 FROM "Medias" WHERE "Medias"."noteId" = ${Notes.id})`
|
||||||
: undefined,
|
: undefined,
|
||||||
user
|
user
|
||||||
? sql`NOT EXISTS (SELECT 1 FROM "Filters" WHERE "Filters"."userId" = ${user.id} AND "Filters"."filter_action" = 'hide' AND EXISTS (SELECT 1 FROM "FilterKeywords" WHERE "FilterKeywords"."filterId" = "Filters"."id" AND "Notes"."content" LIKE '%' || "FilterKeywords"."keyword" || '%') AND "Filters"."context" @> ARRAY['public'])`
|
? sql`NOT EXISTS (SELECT 1 FROM "Filters" WHERE "Filters"."userId" = ${user.id} AND "Filters"."filter_action" = 'hide' AND EXISTS (SELECT 1 FROM "FilterKeywords" WHERE "FilterKeywords"."filterId" = "Filters"."id" AND "Notes"."content" LIKE '%' || "FilterKeywords"."keyword" || '%') AND "Filters"."context" @> ARRAY['public'])`
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import { apiRoute, auth } from "@/api";
|
import { apiRoute, auth } from "@/api";
|
||||||
import { createRoute } from "@hono/zod-openapi";
|
import { createRoute } from "@hono/zod-openapi";
|
||||||
import { Attachment } from "@versia/kit/db";
|
import { Media } from "@versia/kit/db";
|
||||||
import { RolePermissions } from "@versia/kit/tables";
|
import { RolePermissions } from "@versia/kit/tables";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import { config } from "~/packages/config-manager/index.ts";
|
import { config } from "~/packages/config-manager/index.ts";
|
||||||
|
|
@ -43,7 +43,7 @@ const route = createRoute({
|
||||||
description: "Uploaded media",
|
description: "Uploaded media",
|
||||||
content: {
|
content: {
|
||||||
"application/json": {
|
"application/json": {
|
||||||
schema: Attachment.schema,
|
schema: Media.schema,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
@ -70,7 +70,7 @@ export default apiRoute((app) =>
|
||||||
app.openapi(route, async (context) => {
|
app.openapi(route, async (context) => {
|
||||||
const { file, thumbnail, description } = context.req.valid("form");
|
const { file, thumbnail, description } = context.req.valid("form");
|
||||||
|
|
||||||
const attachment = await Attachment.fromFile(file, {
|
const attachment = await Media.fromFile(file, {
|
||||||
thumbnail,
|
thumbnail,
|
||||||
description,
|
description,
|
||||||
});
|
});
|
||||||
|
|
|
||||||
2
bun.lock
2
bun.lock
|
|
@ -21,7 +21,7 @@
|
||||||
"@tufjs/canonical-json": "^2.0.0",
|
"@tufjs/canonical-json": "^2.0.0",
|
||||||
"@versia/client": "^0.1.5",
|
"@versia/client": "^0.1.5",
|
||||||
"@versia/federation": "^0.1.4",
|
"@versia/federation": "^0.1.4",
|
||||||
"@versia/kit": "packages/plugin-kit",
|
"@versia/kit": "workspace:*",
|
||||||
"altcha-lib": "^1.2.0",
|
"altcha-lib": "^1.2.0",
|
||||||
"blurhash": "^2.0.5",
|
"blurhash": "^2.0.5",
|
||||||
"bullmq": "^5.35.1",
|
"bullmq": "^5.35.1",
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ import { proxyUrl } from "@/response";
|
||||||
import type { Attachment as ApiAttachment } from "@versia/client/types";
|
import type { Attachment as ApiAttachment } from "@versia/client/types";
|
||||||
import type { ContentFormat } from "@versia/federation/types";
|
import type { ContentFormat } from "@versia/federation/types";
|
||||||
import { db } from "@versia/kit/db";
|
import { db } from "@versia/kit/db";
|
||||||
import { Attachments } from "@versia/kit/tables";
|
import { Medias } from "@versia/kit/tables";
|
||||||
import {
|
import {
|
||||||
type InferInsertModel,
|
type InferInsertModel,
|
||||||
type InferSelectModel,
|
type InferSelectModel,
|
||||||
|
|
@ -20,9 +20,9 @@ import { MediaManager } from "../media/media-manager.ts";
|
||||||
import { MediaJobType, mediaQueue } from "../queues/media.ts";
|
import { MediaJobType, mediaQueue } from "../queues/media.ts";
|
||||||
import { BaseInterface } from "./base.ts";
|
import { BaseInterface } from "./base.ts";
|
||||||
|
|
||||||
type AttachmentType = InferSelectModel<typeof Attachments>;
|
type MediaType = InferSelectModel<typeof Medias>;
|
||||||
|
|
||||||
export class Attachment extends BaseInterface<typeof Attachments> {
|
export class Media extends BaseInterface<typeof Medias> {
|
||||||
public static schema: z.ZodType<ApiAttachment> = z.object({
|
public static schema: z.ZodType<ApiAttachment> = z.object({
|
||||||
id: z.string().uuid(),
|
id: z.string().uuid(),
|
||||||
type: z.enum(["unknown", "image", "gifv", "video", "audio"]),
|
type: z.enum(["unknown", "image", "gifv", "video", "audio"]),
|
||||||
|
|
@ -51,10 +51,10 @@ export class Attachment extends BaseInterface<typeof Attachments> {
|
||||||
blurhash: z.string().nullable(),
|
blurhash: z.string().nullable(),
|
||||||
});
|
});
|
||||||
|
|
||||||
public static $type: AttachmentType;
|
public static $type: MediaType;
|
||||||
|
|
||||||
public async reload(): Promise<void> {
|
public async reload(): Promise<void> {
|
||||||
const reloaded = await Attachment.fromId(this.data.id);
|
const reloaded = await Media.fromId(this.data.id);
|
||||||
|
|
||||||
if (!reloaded) {
|
if (!reloaded) {
|
||||||
throw new Error("Failed to reload attachment");
|
throw new Error("Failed to reload attachment");
|
||||||
|
|
@ -63,23 +63,23 @@ export class Attachment extends BaseInterface<typeof Attachments> {
|
||||||
this.data = reloaded.data;
|
this.data = reloaded.data;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static async fromId(id: string | null): Promise<Attachment | null> {
|
public static async fromId(id: string | null): Promise<Media | null> {
|
||||||
if (!id) {
|
if (!id) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return await Attachment.fromSql(eq(Attachments.id, id));
|
return await Media.fromSql(eq(Medias.id, id));
|
||||||
}
|
}
|
||||||
|
|
||||||
public static async fromIds(ids: string[]): Promise<Attachment[]> {
|
public static async fromIds(ids: string[]): Promise<Media[]> {
|
||||||
return await Attachment.manyFromSql(inArray(Attachments.id, ids));
|
return await Media.manyFromSql(inArray(Medias.id, ids));
|
||||||
}
|
}
|
||||||
|
|
||||||
public static async fromSql(
|
public static async fromSql(
|
||||||
sql: SQL<unknown> | undefined,
|
sql: SQL<unknown> | undefined,
|
||||||
orderBy: SQL<unknown> | undefined = desc(Attachments.id),
|
orderBy: SQL<unknown> | undefined = desc(Medias.id),
|
||||||
): Promise<Attachment | null> {
|
): Promise<Media | null> {
|
||||||
const found = await db.query.Attachments.findFirst({
|
const found = await db.query.Medias.findFirst({
|
||||||
where: sql,
|
where: sql,
|
||||||
orderBy,
|
orderBy,
|
||||||
});
|
});
|
||||||
|
|
@ -87,17 +87,17 @@ export class Attachment extends BaseInterface<typeof Attachments> {
|
||||||
if (!found) {
|
if (!found) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return new Attachment(found);
|
return new Media(found);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static async manyFromSql(
|
public static async manyFromSql(
|
||||||
sql: SQL<unknown> | undefined,
|
sql: SQL<unknown> | undefined,
|
||||||
orderBy: SQL<unknown> | undefined = desc(Attachments.id),
|
orderBy: SQL<unknown> | undefined = desc(Medias.id),
|
||||||
limit?: number,
|
limit?: number,
|
||||||
offset?: number,
|
offset?: number,
|
||||||
extra?: Parameters<typeof db.query.Attachments.findMany>[0],
|
extra?: Parameters<typeof db.query.Medias.findMany>[0],
|
||||||
): Promise<Attachment[]> {
|
): Promise<Media[]> {
|
||||||
const found = await db.query.Attachments.findMany({
|
const found = await db.query.Medias.findMany({
|
||||||
where: sql,
|
where: sql,
|
||||||
orderBy,
|
orderBy,
|
||||||
limit,
|
limit,
|
||||||
|
|
@ -105,18 +105,16 @@ export class Attachment extends BaseInterface<typeof Attachments> {
|
||||||
with: extra?.with,
|
with: extra?.with,
|
||||||
});
|
});
|
||||||
|
|
||||||
return found.map((s) => new Attachment(s));
|
return found.map((s) => new Media(s));
|
||||||
}
|
}
|
||||||
|
|
||||||
public async update(
|
public async update(newAttachment: Partial<MediaType>): Promise<MediaType> {
|
||||||
newAttachment: Partial<AttachmentType>,
|
|
||||||
): Promise<AttachmentType> {
|
|
||||||
await db
|
await db
|
||||||
.update(Attachments)
|
.update(Medias)
|
||||||
.set(newAttachment)
|
.set(newAttachment)
|
||||||
.where(eq(Attachments.id, this.id));
|
.where(eq(Medias.id, this.id));
|
||||||
|
|
||||||
const updated = await Attachment.fromId(this.data.id);
|
const updated = await Media.fromId(this.data.id);
|
||||||
|
|
||||||
if (!updated) {
|
if (!updated) {
|
||||||
throw new Error("Failed to update attachment");
|
throw new Error("Failed to update attachment");
|
||||||
|
|
@ -126,26 +124,24 @@ export class Attachment extends BaseInterface<typeof Attachments> {
|
||||||
return updated.data;
|
return updated.data;
|
||||||
}
|
}
|
||||||
|
|
||||||
public save(): Promise<AttachmentType> {
|
public save(): Promise<MediaType> {
|
||||||
return this.update(this.data);
|
return this.update(this.data);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async delete(ids?: string[]): Promise<void> {
|
public async delete(ids?: string[]): Promise<void> {
|
||||||
if (Array.isArray(ids)) {
|
if (Array.isArray(ids)) {
|
||||||
await db.delete(Attachments).where(inArray(Attachments.id, ids));
|
await db.delete(Medias).where(inArray(Medias.id, ids));
|
||||||
} else {
|
} else {
|
||||||
await db.delete(Attachments).where(eq(Attachments.id, this.id));
|
await db.delete(Medias).where(eq(Medias.id, this.id));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static async insert(
|
public static async insert(
|
||||||
data: InferInsertModel<typeof Attachments>,
|
data: InferInsertModel<typeof Medias>,
|
||||||
): Promise<Attachment> {
|
): Promise<Media> {
|
||||||
const inserted = (
|
const inserted = (await db.insert(Medias).values(data).returning())[0];
|
||||||
await db.insert(Attachments).values(data).returning()
|
|
||||||
)[0];
|
|
||||||
|
|
||||||
const attachment = await Attachment.fromId(inserted.id);
|
const attachment = await Media.fromId(inserted.id);
|
||||||
|
|
||||||
if (!attachment) {
|
if (!attachment) {
|
||||||
throw new Error("Failed to insert attachment");
|
throw new Error("Failed to insert attachment");
|
||||||
|
|
@ -160,7 +156,7 @@ export class Attachment extends BaseInterface<typeof Attachments> {
|
||||||
description?: string;
|
description?: string;
|
||||||
thumbnail?: File;
|
thumbnail?: File;
|
||||||
},
|
},
|
||||||
): Promise<Attachment> {
|
): Promise<Media> {
|
||||||
if (file.size > config.validation.max_media_size) {
|
if (file.size > config.validation.max_media_size) {
|
||||||
throw new ApiError(
|
throw new ApiError(
|
||||||
413,
|
413,
|
||||||
|
|
@ -191,17 +187,17 @@ export class Attachment extends BaseInterface<typeof Attachments> {
|
||||||
|
|
||||||
const { path } = await mediaManager.addFile(file);
|
const { path } = await mediaManager.addFile(file);
|
||||||
|
|
||||||
const url = Attachment.getUrl(path);
|
const url = Media.getUrl(path);
|
||||||
|
|
||||||
let thumbnailUrl = "";
|
let thumbnailUrl = "";
|
||||||
|
|
||||||
if (options?.thumbnail) {
|
if (options?.thumbnail) {
|
||||||
const { path } = await mediaManager.addFile(options.thumbnail);
|
const { path } = await mediaManager.addFile(options.thumbnail);
|
||||||
|
|
||||||
thumbnailUrl = Attachment.getUrl(path);
|
thumbnailUrl = Media.getUrl(path);
|
||||||
}
|
}
|
||||||
|
|
||||||
const newAttachment = await Attachment.insert({
|
const newAttachment = await Media.insert({
|
||||||
url,
|
url,
|
||||||
thumbnailUrl: thumbnailUrl || undefined,
|
thumbnailUrl: thumbnailUrl || undefined,
|
||||||
sha256: sha256.update(await file.arrayBuffer()).digest("hex"),
|
sha256: sha256.update(await file.arrayBuffer()).digest("hex"),
|
||||||
|
|
@ -319,11 +315,11 @@ export class Attachment extends BaseInterface<typeof Attachments> {
|
||||||
|
|
||||||
public static fromVersia(
|
public static fromVersia(
|
||||||
attachmentToConvert: ContentFormat,
|
attachmentToConvert: ContentFormat,
|
||||||
): Promise<Attachment> {
|
): Promise<Media> {
|
||||||
const key = Object.keys(attachmentToConvert)[0];
|
const key = Object.keys(attachmentToConvert)[0];
|
||||||
const value = attachmentToConvert[key];
|
const value = attachmentToConvert[key];
|
||||||
|
|
||||||
return Attachment.insert({
|
return Media.insert({
|
||||||
mimeType: key,
|
mimeType: key,
|
||||||
url: value.content,
|
url: value.content,
|
||||||
description: value.description || undefined,
|
description: value.description || undefined,
|
||||||
|
|
|
||||||
|
|
@ -16,8 +16,8 @@ import type {
|
||||||
} from "@versia/federation/types";
|
} from "@versia/federation/types";
|
||||||
import { Instance, db } from "@versia/kit/db";
|
import { Instance, db } from "@versia/kit/db";
|
||||||
import {
|
import {
|
||||||
Attachments,
|
|
||||||
EmojiToNote,
|
EmojiToNote,
|
||||||
|
Medias,
|
||||||
NoteToMentions,
|
NoteToMentions,
|
||||||
Notes,
|
Notes,
|
||||||
Users,
|
Users,
|
||||||
|
|
@ -44,7 +44,7 @@ import {
|
||||||
import { config } from "~/packages/config-manager";
|
import { config } from "~/packages/config-manager";
|
||||||
import { DeliveryJobType, deliveryQueue } from "../queues/delivery.ts";
|
import { DeliveryJobType, deliveryQueue } from "../queues/delivery.ts";
|
||||||
import { Application } from "./application.ts";
|
import { Application } from "./application.ts";
|
||||||
import { Attachment } from "./attachment.ts";
|
import { Media } from "./attachment.ts";
|
||||||
import { BaseInterface } from "./base.ts";
|
import { BaseInterface } from "./base.ts";
|
||||||
import { Emoji } from "./emoji.ts";
|
import { Emoji } from "./emoji.ts";
|
||||||
import { User } from "./user.ts";
|
import { User } from "./user.ts";
|
||||||
|
|
@ -56,7 +56,7 @@ type NoteTypeWithRelations = NoteType & {
|
||||||
mentions: (InferSelectModel<typeof Users> & {
|
mentions: (InferSelectModel<typeof Users> & {
|
||||||
instance: typeof Instance.$type | null;
|
instance: typeof Instance.$type | null;
|
||||||
})[];
|
})[];
|
||||||
attachments: (typeof Attachment.$type)[];
|
attachments: (typeof Media.$type)[];
|
||||||
reblog: NoteTypeWithoutRecursiveRelations | null;
|
reblog: NoteTypeWithoutRecursiveRelations | null;
|
||||||
emojis: (typeof Emoji.$type)[];
|
emojis: (typeof Emoji.$type)[];
|
||||||
reply: NoteType | null;
|
reply: NoteType | null;
|
||||||
|
|
@ -102,7 +102,7 @@ export class Note extends BaseInterface<typeof Notes, NoteTypeWithRelations> {
|
||||||
sensitive: z.boolean(),
|
sensitive: z.boolean(),
|
||||||
spoiler_text: z.string(),
|
spoiler_text: z.string(),
|
||||||
visibility: z.enum(["public", "unlisted", "private", "direct"]),
|
visibility: z.enum(["public", "unlisted", "private", "direct"]),
|
||||||
media_attachments: z.array(Attachment.schema),
|
media_attachments: z.array(Media.schema),
|
||||||
mentions: z.array(
|
mentions: z.array(
|
||||||
z.object({
|
z.object({
|
||||||
id: z.string().uuid(),
|
id: z.string().uuid(),
|
||||||
|
|
@ -442,7 +442,7 @@ export class Note extends BaseInterface<typeof Notes, NoteTypeWithRelations> {
|
||||||
uri?: string;
|
uri?: string;
|
||||||
mentions?: User[];
|
mentions?: User[];
|
||||||
/** List of IDs of database Attachment objects */
|
/** List of IDs of database Attachment objects */
|
||||||
mediaAttachments?: Attachment[];
|
mediaAttachments?: Media[];
|
||||||
replyId?: string;
|
replyId?: string;
|
||||||
quoteId?: string;
|
quoteId?: string;
|
||||||
application?: Application;
|
application?: Application;
|
||||||
|
|
@ -515,7 +515,7 @@ export class Note extends BaseInterface<typeof Notes, NoteTypeWithRelations> {
|
||||||
emojis?: Emoji[];
|
emojis?: Emoji[];
|
||||||
uri?: string;
|
uri?: string;
|
||||||
mentions?: User[];
|
mentions?: User[];
|
||||||
mediaAttachments?: Attachment[];
|
mediaAttachments?: Media[];
|
||||||
replyId?: string;
|
replyId?: string;
|
||||||
quoteId?: string;
|
quoteId?: string;
|
||||||
application?: Application;
|
application?: Application;
|
||||||
|
|
@ -623,28 +623,26 @@ export class Note extends BaseInterface<typeof Notes, NoteTypeWithRelations> {
|
||||||
* Deletes all existing attachments associated with this note, then replaces them with the provided attachments.
|
* Deletes all existing attachments associated with this note, then replaces them with the provided attachments.
|
||||||
* @param mediaAttachments - The IDs of the attachments to associate with this note
|
* @param mediaAttachments - The IDs of the attachments to associate with this note
|
||||||
*/
|
*/
|
||||||
public async updateAttachments(
|
public async updateAttachments(mediaAttachments: Media[]): Promise<void> {
|
||||||
mediaAttachments: Attachment[],
|
|
||||||
): Promise<void> {
|
|
||||||
if (mediaAttachments.length === 0) {
|
if (mediaAttachments.length === 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove old attachments
|
// Remove old attachments
|
||||||
await db
|
await db
|
||||||
.update(Attachments)
|
.update(Medias)
|
||||||
.set({
|
.set({
|
||||||
noteId: null,
|
noteId: null,
|
||||||
})
|
})
|
||||||
.where(eq(Attachments.noteId, this.data.id));
|
.where(eq(Medias.noteId, this.data.id));
|
||||||
await db
|
await db
|
||||||
.update(Attachments)
|
.update(Medias)
|
||||||
.set({
|
.set({
|
||||||
noteId: this.data.id,
|
noteId: this.data.id,
|
||||||
})
|
})
|
||||||
.where(
|
.where(
|
||||||
inArray(
|
inArray(
|
||||||
Attachments.id,
|
Medias.id,
|
||||||
mediaAttachments.map((i) => i.id),
|
mediaAttachments.map((i) => i.id),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
@ -740,16 +738,16 @@ export class Note extends BaseInterface<typeof Notes, NoteTypeWithRelations> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const attachments: Attachment[] = [];
|
const attachments: Media[] = [];
|
||||||
|
|
||||||
for (const attachment of note.attachments ?? []) {
|
for (const attachment of note.attachments ?? []) {
|
||||||
const resolvedAttachment = await Attachment.fromVersia(
|
const resolvedAttachment = await Media.fromVersia(attachment).catch(
|
||||||
attachment,
|
(e) => {
|
||||||
).catch((e) => {
|
logger.error`${e}`;
|
||||||
logger.error`${e}`;
|
sentry?.captureException(e);
|
||||||
sentry?.captureException(e);
|
return null;
|
||||||
return null;
|
},
|
||||||
});
|
);
|
||||||
|
|
||||||
if (resolvedAttachment) {
|
if (resolvedAttachment) {
|
||||||
attachments.push(resolvedAttachment);
|
attachments.push(resolvedAttachment);
|
||||||
|
|
@ -908,7 +906,7 @@ export class Note extends BaseInterface<typeof Notes, NoteTypeWithRelations> {
|
||||||
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) => new Attachment(a).toApi() as ApiAttachment,
|
(a) => new Media(a).toApi() as ApiAttachment,
|
||||||
),
|
),
|
||||||
mentions: data.mentions.map((mention) => ({
|
mentions: data.mentions.map((mention) => ({
|
||||||
id: mention.id,
|
id: mention.id,
|
||||||
|
|
@ -1014,7 +1012,7 @@ export class Note extends BaseInterface<typeof Notes, NoteTypeWithRelations> {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
attachments: (status.attachments ?? []).map((attachment) =>
|
attachments: (status.attachments ?? []).map((attachment) =>
|
||||||
new Attachment(attachment).toVersia(),
|
new Media(attachment).toVersia(),
|
||||||
),
|
),
|
||||||
is_sensitive: status.sensitive,
|
is_sensitive: status.sensitive,
|
||||||
mentions: status.mentions.map((mention) =>
|
mentions: status.mentions.map((mention) =>
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import { Attachment } from "@versia/kit/db";
|
import { Media } from "@versia/kit/db";
|
||||||
import { Worker } from "bullmq";
|
import { Worker } from "bullmq";
|
||||||
import { config } from "~/packages/config-manager";
|
import { config } from "~/packages/config-manager";
|
||||||
import { connection } from "~/utils/redis.ts";
|
import { connection } from "~/utils/redis.ts";
|
||||||
|
|
@ -21,7 +21,7 @@ export const getMediaWorker = (): Worker<MediaJobData, void, MediaJobType> =>
|
||||||
|
|
||||||
await job.log(`Fetching attachment ID [${attachmentId}]`);
|
await job.log(`Fetching attachment ID [${attachmentId}]`);
|
||||||
|
|
||||||
const attachment = await Attachment.fromId(attachmentId);
|
const attachment = await Media.fromId(attachmentId);
|
||||||
|
|
||||||
if (!attachment) {
|
if (!attachment) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
|
|
@ -68,7 +68,7 @@ export const getMediaWorker = (): Worker<MediaJobData, void, MediaJobType> =>
|
||||||
const { path, uploadedFile } =
|
const { path, uploadedFile } =
|
||||||
await mediaManager.addFile(processedFile);
|
await mediaManager.addFile(processedFile);
|
||||||
|
|
||||||
const url = Attachment.getUrl(path);
|
const url = Media.getUrl(path);
|
||||||
|
|
||||||
const sha256 = new Bun.SHA256();
|
const sha256 = new Bun.SHA256();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import { Args } from "@oclif/core";
|
import { Args } from "@oclif/core";
|
||||||
import { Attachment, Emoji } from "@versia/kit/db";
|
import { Emoji, Media } from "@versia/kit/db";
|
||||||
import { Emojis } from "@versia/kit/tables";
|
import { Emojis } from "@versia/kit/tables";
|
||||||
import chalk from "chalk";
|
import chalk from "chalk";
|
||||||
import { and, eq, isNull } from "drizzle-orm";
|
import { and, eq, isNull } from "drizzle-orm";
|
||||||
|
|
@ -115,7 +115,7 @@ export default class EmojiAdd extends BaseCommand<typeof EmojiAdd> {
|
||||||
|
|
||||||
await Emoji.insert({
|
await Emoji.insert({
|
||||||
shortcode: args.shortcode,
|
shortcode: args.shortcode,
|
||||||
url: Attachment.getUrl(uploaded.path),
|
url: Media.getUrl(uploaded.path),
|
||||||
visibleInPicker: true,
|
visibleInPicker: true,
|
||||||
contentType: uploaded.uploadedFile.type,
|
contentType: uploaded.uploadedFile.type,
|
||||||
});
|
});
|
||||||
|
|
@ -124,7 +124,7 @@ export default class EmojiAdd extends BaseCommand<typeof EmojiAdd> {
|
||||||
`${chalk.green("✓")} Created emoji ${chalk.green(
|
`${chalk.green("✓")} Created emoji ${chalk.green(
|
||||||
args.shortcode,
|
args.shortcode,
|
||||||
)} with url ${chalk.blue(
|
)} with url ${chalk.blue(
|
||||||
chalk.underline(Attachment.getUrl(uploaded.path)),
|
chalk.underline(Media.getUrl(uploaded.path)),
|
||||||
)}`,
|
)}`,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import { Args, Flags } from "@oclif/core";
|
import { Args, Flags } from "@oclif/core";
|
||||||
import { Attachment, Emoji } from "@versia/kit/db";
|
import { Emoji, Media } from "@versia/kit/db";
|
||||||
import { Emojis } from "@versia/kit/tables";
|
import { Emojis } from "@versia/kit/tables";
|
||||||
import chalk from "chalk";
|
import chalk from "chalk";
|
||||||
import { and, inArray, isNull } from "drizzle-orm";
|
import { and, inArray, isNull } from "drizzle-orm";
|
||||||
|
|
@ -214,7 +214,7 @@ export default class EmojiImport extends BaseCommand<typeof EmojiImport> {
|
||||||
|
|
||||||
await Emoji.insert({
|
await Emoji.insert({
|
||||||
shortcode: emoji.emoji.name,
|
shortcode: emoji.emoji.name,
|
||||||
url: Attachment.getUrl(uploaded.path),
|
url: Media.getUrl(uploaded.path),
|
||||||
visibleInPicker: true,
|
visibleInPicker: true,
|
||||||
contentType: uploaded.uploadedFile.type,
|
contentType: uploaded.uploadedFile.type,
|
||||||
});
|
});
|
||||||
|
|
|
||||||
4
drizzle/migrations/0041_bright_doctor_spectrum.sql
Normal file
4
drizzle/migrations/0041_bright_doctor_spectrum.sql
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
ALTER TABLE "Attachments" RENAME TO "Medias";--> statement-breakpoint
|
||||||
|
ALTER TABLE "Medias" DROP CONSTRAINT "Attachments_noteId_Notes_id_fk";
|
||||||
|
--> statement-breakpoint
|
||||||
|
ALTER TABLE "Medias" ADD CONSTRAINT "Medias_noteId_Notes_id_fk" FOREIGN KEY ("noteId") REFERENCES "public"."Notes"("id") ON DELETE cascade ON UPDATE cascade;
|
||||||
2323
drizzle/migrations/meta/0041_snapshot.json
Normal file
2323
drizzle/migrations/meta/0041_snapshot.json
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -288,6 +288,13 @@
|
||||||
"when": 1735776034097,
|
"when": 1735776034097,
|
||||||
"tag": "0040_good_nocturne",
|
"tag": "0040_good_nocturne",
|
||||||
"breakpoints": true
|
"breakpoints": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idx": 41,
|
||||||
|
"version": "7",
|
||||||
|
"when": 1737644734501,
|
||||||
|
"tag": "0041_bright_doctor_spectrum",
|
||||||
|
"breakpoints": true
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -303,7 +303,7 @@ export const Tokens = pgTable("Tokens", {
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
export const Attachments = pgTable("Attachments", {
|
export const Medias = pgTable("Medias", {
|
||||||
id: id(),
|
id: id(),
|
||||||
url: text("url").notNull(),
|
url: text("url").notNull(),
|
||||||
remoteUrl: text("remote_url"),
|
remoteUrl: text("remote_url"),
|
||||||
|
|
@ -799,9 +799,9 @@ export const UserToPinnedNotes = pgTable(
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|
||||||
export const AttachmentsRelations = relations(Attachments, ({ one }) => ({
|
export const AttachmentsRelations = relations(Medias, ({ one }) => ({
|
||||||
notes: one(Notes, {
|
notes: one(Notes, {
|
||||||
fields: [Attachments.noteId],
|
fields: [Medias.noteId],
|
||||||
references: [Notes.id],
|
references: [Notes.id],
|
||||||
}),
|
}),
|
||||||
}));
|
}));
|
||||||
|
|
@ -894,7 +894,7 @@ export const NotesRelations = relations(Notes, ({ many, one }) => ({
|
||||||
references: [Users.id],
|
references: [Users.id],
|
||||||
relationName: "NoteToAuthor",
|
relationName: "NoteToAuthor",
|
||||||
}),
|
}),
|
||||||
attachments: many(Attachments),
|
attachments: many(Medias),
|
||||||
mentions: many(NoteToMentions),
|
mentions: many(NoteToMentions),
|
||||||
reblog: one(Notes, {
|
reblog: one(Notes, {
|
||||||
fields: [Notes.reblogId],
|
fields: [Notes.reblogId],
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
// biome-ignore lint/performance/noBarrelFile: <explanation>
|
// biome-ignore lint/performance/noBarrelFile: <explanation>
|
||||||
export { User } from "~/classes/database/user.ts";
|
export { User } from "~/classes/database/user.ts";
|
||||||
export { Role } from "~/classes/database/role.ts";
|
export { Role } from "~/classes/database/role.ts";
|
||||||
export { Attachment } from "~/classes/database/attachment.ts";
|
export { Media } from "~/classes/database/attachment.ts";
|
||||||
export { Emoji } from "~/classes/database/emoji.ts";
|
export { Emoji } from "~/classes/database/emoji.ts";
|
||||||
export { Instance } from "~/classes/database/instance.ts";
|
export { Instance } from "~/classes/database/instance.ts";
|
||||||
export { Note } from "~/classes/database/note.ts";
|
export { Note } from "~/classes/database/note.ts";
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue