/api/category, /api/directory, /api/file 아래의 대부분의 Endpoint들을 tRPC로 마이그레이션

This commit is contained in:
static
2025-12-25 22:45:55 +09:00
parent a08ddf2c09
commit 6d95059450
45 changed files with 691 additions and 1097 deletions

122
src/trpc/routers/file.ts Normal file
View File

@@ -0,0 +1,122 @@
import { TRPCError } from "@trpc/server";
import { z } from "zod";
import { FileRepo, MediaRepo, IntegrityError } from "$lib/server/db";
import { safeUnlink } from "$lib/server/modules/filesystem";
import { router, roleProcedure } from "../init.server";
const fileRouter = router({
get: roleProcedure["activeClient"]
.input(
z.object({
id: z.number().int().positive(),
}),
)
.query(async ({ ctx, input }) => {
const file = await FileRepo.getFile(ctx.session.userId, input.id);
if (!file) {
throw new TRPCError({ code: "NOT_FOUND", message: "Invalid file id" });
}
const categories = await FileRepo.getAllFileCategories(input.id);
return {
parent: file.parentId,
mekVersion: file.mekVersion,
dek: file.encDek,
dekVersion: file.dekVersion,
contentType: file.contentType,
contentIv: file.encContentIv,
name: file.encName.ciphertext,
nameIv: file.encName.iv,
createdAt: file.encCreatedAt?.ciphertext,
createdAtIv: file.encCreatedAt?.iv,
lastModifiedAt: file.encLastModifiedAt.ciphertext,
lastModifiedAtIv: file.encLastModifiedAt.iv,
categories: categories.map(({ id }) => id),
};
}),
list: roleProcedure["activeClient"].query(async ({ ctx }) => {
return await FileRepo.getAllFileIds(ctx.session.userId);
}),
listByHash: roleProcedure["activeClient"]
.input(
z.object({
hskVersion: z.number().int().positive(),
contentHmac: z.string().base64().nonempty(),
}),
)
.query(async ({ ctx, input }) => {
return await FileRepo.getAllFileIdsByContentHmac(
ctx.session.userId,
input.hskVersion,
input.contentHmac,
);
}),
listWithoutThumbnail: roleProcedure["activeClient"].query(async ({ ctx }) => {
return await MediaRepo.getMissingFileThumbnails(ctx.session.userId);
}),
rename: roleProcedure["activeClient"]
.input(
z.object({
id: z.number().int().positive(),
dekVersion: z.date(),
name: z.string().base64().nonempty(),
nameIv: z.string().base64().nonempty(),
}),
)
.mutation(async ({ ctx, input }) => {
try {
await FileRepo.setFileEncName(ctx.session.userId, input.id, input.dekVersion, {
ciphertext: input.name,
iv: input.nameIv,
});
} catch (e) {
if (e instanceof IntegrityError) {
if (e.message === "File not found") {
throw new TRPCError({ code: "NOT_FOUND", message: "Invalid file id" });
} else if (e.message === "Invalid DEK version") {
throw new TRPCError({ code: "BAD_REQUEST", message: e.message });
}
}
throw e;
}
}),
delete: roleProcedure["activeClient"]
.input(
z.object({
id: z.number().int().positive(),
}),
)
.mutation(async ({ ctx, input }) => {
try {
const { path, thumbnailPath } = await FileRepo.unregisterFile(ctx.session.userId, input.id);
safeUnlink(path); // Intended
safeUnlink(thumbnailPath); // Intended
} catch (e) {
if (e instanceof IntegrityError && e.message === "File not found") {
throw new TRPCError({ code: "NOT_FOUND", message: "Invalid file id" });
}
throw e;
}
}),
thumbnail: roleProcedure["activeClient"]
.input(
z.object({
id: z.number().int().positive(),
}),
)
.query(async ({ ctx, input }) => {
const thumbnail = await MediaRepo.getFileThumbnail(ctx.session.userId, input.id);
if (!thumbnail) {
throw new TRPCError({ code: "NOT_FOUND", message: "File or its thumbnail not found" });
}
return { updatedAt: thumbnail.updatedAt, contentIv: thumbnail.encContentIv };
}),
});
export default fileRouter;