diff --git a/packages/api/package.json b/packages/api/package.json index 4ea3a5646..2448633a3 100644 --- a/packages/api/package.json +++ b/packages/api/package.json @@ -27,6 +27,7 @@ "@homarr/cron-jobs": "workspace:^0.1.0", "@homarr/db": "workspace:^0.1.0", "@homarr/definitions": "workspace:^0.1.0", + "@homarr/icons": "workspace:^0.1.0", "@homarr/integrations": "workspace:^0.1.0", "@homarr/log": "workspace:^", "@homarr/old-import": "workspace:^0.1.0", diff --git a/packages/api/src/router/medias/media-router.ts b/packages/api/src/router/medias/media-router.ts index 600df24b4..8a1c3a71e 100644 --- a/packages/api/src/router/medias/media-router.ts +++ b/packages/api/src/router/medias/media-router.ts @@ -1,7 +1,9 @@ import { TRPCError } from "@trpc/server"; +import type { InferInsertModel } from "@homarr/db"; import { and, createId, desc, eq, like } from "@homarr/db"; -import { medias } from "@homarr/db/schema"; +import { iconRepositories, icons, medias } from "@homarr/db/schema"; +import { createLocalImageUrl, LOCAL_ICON_REPOSITORY_SLUG, mapMediaToIcon } from "@homarr/icons/local"; import { validation, z } from "@homarr/validation"; import { createTRPCRouter, permissionRequiredProcedure, protectedProcedure } from "../../trpc"; @@ -52,13 +54,29 @@ export const mediaRouter = createTRPCRouter({ .mutation(async ({ ctx, input }) => { const content = Buffer.from(await input.file.arrayBuffer()); const id = createId(); - await ctx.db.insert(medias).values({ + const media = { id, creatorId: ctx.session.user.id, content, size: input.file.size, contentType: input.file.type, name: input.file.name, + } satisfies InferInsertModel; + await ctx.db.insert(medias).values(media); + + const localIconRepository = await ctx.db.query.iconRepositories.findFirst({ + where: eq(iconRepositories.slug, LOCAL_ICON_REPOSITORY_SLUG), + }); + + if (!localIconRepository) return id; + + const icon = mapMediaToIcon(media); + await ctx.db.insert(icons).values({ + id: createId(), + checksum: icon.checksum, + name: icon.fileNameWithExtension, + url: icon.imageUrl, + iconRepositoryId: localIconRepository.id, }); return id; @@ -67,6 +85,7 @@ export const mediaRouter = createTRPCRouter({ const dbMedia = await ctx.db.query.medias.findFirst({ where: eq(medias.id, input.id), columns: { + id: true, creatorId: true, }, }); @@ -87,5 +106,6 @@ export const mediaRouter = createTRPCRouter({ } await ctx.db.delete(medias).where(eq(medias.id, input.id)); + await ctx.db.delete(icons).where(eq(icons.url, createLocalImageUrl(input.id))); }), }); diff --git a/packages/icons/package.json b/packages/icons/package.json index 573904b3e..d0bc89830 100644 --- a/packages/icons/package.json +++ b/packages/icons/package.json @@ -5,7 +5,8 @@ "license": "MIT", "type": "module", "exports": { - ".": "./index.ts" + ".": "./index.ts", + "./local": "./src/local.ts" }, "typesVersions": { "*": { diff --git a/packages/icons/src/local.ts b/packages/icons/src/local.ts new file mode 100644 index 000000000..a7ca63c45 --- /dev/null +++ b/packages/icons/src/local.ts @@ -0,0 +1 @@ +export { createLocalImageUrl, mapMediaToIcon, LOCAL_ICON_REPOSITORY_SLUG } from "./repositories/local.icon-repository"; diff --git a/packages/icons/src/repositories/local.icon-repository.ts b/packages/icons/src/repositories/local.icon-repository.ts index 7fc7c74d9..c0d9a5b27 100644 --- a/packages/icons/src/repositories/local.icon-repository.ts +++ b/packages/icons/src/repositories/local.icon-repository.ts @@ -1,26 +1,36 @@ import { createHash } from "crypto"; +import type { InferSelectModel } from "@homarr/db"; import { db } from "@homarr/db"; +import type { medias } from "@homarr/db/schema"; -import type { RepositoryIconGroup } from "../types"; +import type { RepositoryIcon, RepositoryIconGroup } from "../types"; import { IconRepository } from "./icon-repository"; +export const LOCAL_ICON_REPOSITORY_SLUG = "local"; + export class LocalIconRepository extends IconRepository { constructor() { - super("Local", "local", undefined, undefined, undefined, undefined); + super("Local", LOCAL_ICON_REPOSITORY_SLUG, undefined, undefined, undefined, undefined); } protected async getAllIconsInternalAsync(): Promise { const medias = await db.query.medias.findMany(); return { success: true, - icons: medias.map((media) => ({ - local: true, - fileNameWithExtension: media.name, - imageUrl: `/api/user-medias/${media.id}`, - checksum: createHash("md5").update(media.content).digest("hex"), - sizeInBytes: media.size, - })), - slug: "local", + icons: medias.map(mapMediaToIcon), + slug: LOCAL_ICON_REPOSITORY_SLUG, }; } } + +export const createLocalImageUrl = (id: string) => `/api/user-medias/${id}`; + +export const mapMediaToIcon = ( + media: Pick, "name" | "id" | "content" | "size">, +): RepositoryIcon => ({ + local: true, + fileNameWithExtension: media.name, + imageUrl: createLocalImageUrl(media.id), + checksum: createHash("md5").update(media.content).digest("hex"), + sizeInBytes: media.size, +}); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 0c84fd854..a05aa9958 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -509,6 +509,9 @@ importers: '@homarr/definitions': specifier: workspace:^0.1.0 version: link:../definitions + '@homarr/icons': + specifier: workspace:^0.1.0 + version: link:../icons '@homarr/integrations': specifier: workspace:^0.1.0 version: link:../integrations