feat: add media management (#1337)

* feat: add media management

* feat: add missing page search item

* fix: medias should be hidden for anonymous users

* chore: rename show-all to include-from-all-users

* fix: inconsistent table column for creator-id of media

* fix: schema check not working because of custom type for blob in mysql

* chore: temporarily remove migrations

* chore: readd removed migrations
This commit is contained in:
Meier Lukas
2024-10-26 22:45:32 +02:00
committed by GitHub
parent f8c21f6000
commit db198c6dab
22 changed files with 3762 additions and 5 deletions

View File

@@ -10,6 +10,7 @@ import { integrationRouter } from "./router/integration/integration-router";
import { inviteRouter } from "./router/invite";
import { locationRouter } from "./router/location";
import { logRouter } from "./router/log";
import { mediaRouter } from "./router/medias/media-router";
import { searchEngineRouter } from "./router/search-engine/search-engine-router";
import { serverSettingsRouter } from "./router/serverSettings";
import { userRouter } from "./router/user";
@@ -33,6 +34,7 @@ export const appRouter = createTRPCRouter({
serverSettings: serverSettingsRouter,
cronJobs: cronJobsRouter,
apiKeys: apiKeysRouter,
media: mediaRouter,
});
// export type definition of API

View File

@@ -0,0 +1,88 @@
import { TRPCError } from "@trpc/server";
import { and, createId, desc, eq, like } from "@homarr/db";
import { medias } from "@homarr/db/schema/sqlite";
import { validation, z } from "@homarr/validation";
import { createTRPCRouter, protectedProcedure } from "../../trpc";
export const mediaRouter = createTRPCRouter({
getPaginated: protectedProcedure
.input(
validation.common.paginated.and(
z.object({ includeFromAllUsers: z.boolean().default(false), search: z.string().trim().default("") }),
),
)
.query(async ({ ctx, input }) => {
const includeFromAllUsers = ctx.session.user.permissions.includes("admin") && input.includeFromAllUsers;
const where = and(
input.search.length >= 1 ? like(medias.name, `%${input.search}%`) : undefined,
includeFromAllUsers ? undefined : eq(medias.creatorId, ctx.session.user.id),
);
const dbMedias = await ctx.db.query.medias.findMany({
where,
orderBy: desc(medias.createdAt),
limit: input.pageSize,
offset: (input.page - 1) * input.pageSize,
columns: {
content: false,
},
with: {
creator: {
columns: {
id: true,
name: true,
image: true,
},
},
},
});
const totalCount = await ctx.db.$count(medias, where);
return {
items: dbMedias,
totalCount,
};
}),
uploadMedia: protectedProcedure.input(validation.media.uploadMedia).mutation(async ({ ctx, input }) => {
const content = Buffer.from(await input.file.arrayBuffer());
const id = createId();
await ctx.db.insert(medias).values({
id,
creatorId: ctx.session.user.id,
content,
size: input.file.size,
contentType: input.file.type,
name: input.file.name,
});
return id;
}),
deleteMedia: protectedProcedure.input(validation.common.byId).mutation(async ({ ctx, input }) => {
const dbMedia = await ctx.db.query.medias.findFirst({
where: eq(medias.id, input.id),
columns: {
creatorId: true,
},
});
if (!dbMedia) {
throw new TRPCError({
code: "NOT_FOUND",
message: "Media not found",
});
}
// Only allow admins and the creator of the media to delete it
if (!ctx.session.user.permissions.includes("admin") && ctx.session.user.id !== dbMedia.creatorId) {
throw new TRPCError({
code: "FORBIDDEN",
message: "You don't have permission to delete this media",
});
}
await ctx.db.delete(medias).where(eq(medias.id, input.id));
}),
});