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

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

No files matched your search

+194
View File
@@ -0,0 +1,194 @@
import { TRPCError } from "@trpc/server";
import { z } from "zod";
import { CategoryRepo, FileRepo, IntegrityError } from "$lib/server/db";
import { categoryIdSchema } from "$lib/server/schemas";
import { router, roleProcedure } from "../init.server";
const categoryRouter = router({
get: roleProcedure["activeClient"]
.input(
z.object({
id: categoryIdSchema,
}),
)
.query(async ({ ctx, input }) => {
const category =
input.id !== "root"
? await CategoryRepo.getCategory(ctx.session.userId, input.id)
: undefined;
if (category === null) {
throw new TRPCError({ code: "NOT_FOUND", message: "Invalid category id" });
}
const categories = await CategoryRepo.getAllCategoriesByParent(ctx.session.userId, input.id);
return {
metadata: category && {
parent: category.parentId,
mekVersion: category.mekVersion,
dek: category.encDek,
dekVersion: category.dekVersion,
name: category.encName.ciphertext,
nameIv: category.encName.iv,
},
subCategories: categories.map(({ id }) => id),
};
}),
create: roleProcedure["activeClient"]
.input(
z.object({
parent: categoryIdSchema,
mekVersion: z.number().int().positive(),
dek: z.string().base64().nonempty(),
dekVersion: z.date(),
name: z.string().base64().nonempty(),
nameIv: z.string().base64().nonempty(),
}),
)
.mutation(async ({ ctx, input }) => {
const oneMinuteAgo = new Date(Date.now() - 60 * 1000);
const oneMinuteLater = new Date(Date.now() + 60 * 1000);
if (input.dekVersion <= oneMinuteAgo || input.dekVersion >= oneMinuteLater) {
throw new TRPCError({ code: "BAD_REQUEST", message: "Invalid DEK version" });
}
try {
await CategoryRepo.registerCategory({
parentId: input.parent,
userId: ctx.session.userId,
mekVersion: input.mekVersion,
encDek: input.dek,
dekVersion: input.dekVersion,
encName: { ciphertext: input.name, iv: input.nameIv },
});
} catch (e) {
if (e instanceof IntegrityError && e.message === "Inactive MEK version") {
throw new TRPCError({ code: "BAD_REQUEST", message: e.message });
}
throw e;
}
}),
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 CategoryRepo.setCategoryEncName(ctx.session.userId, input.id, input.dekVersion, {
ciphertext: input.name,
iv: input.nameIv,
});
} catch (e) {
if (e instanceof IntegrityError) {
if (e.message === "Category not found") {
throw new TRPCError({ code: "NOT_FOUND", message: "Invalid category 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 {
await CategoryRepo.unregisterCategory(ctx.session.userId, input.id);
} catch (e) {
if (e instanceof IntegrityError && e.message === "Category not found") {
throw new TRPCError({ code: "NOT_FOUND", message: "Invalid category id" });
}
throw e;
}
}),
files: roleProcedure["activeClient"]
.input(
z.object({
id: z.number().int().positive(),
recurse: z.boolean().default(false),
}),
)
.query(async ({ ctx, input }) => {
const category = await CategoryRepo.getCategory(ctx.session.userId, input.id);
if (!category) {
throw new TRPCError({ code: "NOT_FOUND", message: "Invalid category id" });
}
const files = await FileRepo.getAllFilesByCategory(
ctx.session.userId,
input.id,
input.recurse,
);
return files.map(({ id, isRecursive }) => ({ file: id, isRecursive }));
}),
addFile: roleProcedure["activeClient"]
.input(
z.object({
id: z.number().int().positive(),
file: z.number().int().positive(),
}),
)
.mutation(async ({ ctx, input }) => {
const [category, file] = await Promise.all([
CategoryRepo.getCategory(ctx.session.userId, input.id),
FileRepo.getFile(ctx.session.userId, input.file),
]);
if (!category) {
throw new TRPCError({ code: "NOT_FOUND", message: "Invalid category id" });
} else if (!file) {
throw new TRPCError({ code: "NOT_FOUND", message: "Invalid file id" });
}
try {
await FileRepo.addFileToCategory(input.file, input.id);
} catch (e) {
if (e instanceof IntegrityError && e.message === "File already added to category") {
throw new TRPCError({ code: "BAD_REQUEST", message: "File already added" });
}
throw e;
}
}),
removeFile: roleProcedure["activeClient"]
.input(
z.object({
id: z.number().int().positive(),
file: z.number().int().positive(),
}),
)
.mutation(async ({ ctx, input }) => {
const [category, file] = await Promise.all([
CategoryRepo.getCategory(ctx.session.userId, input.id),
FileRepo.getFile(ctx.session.userId, input.file),
]);
if (!category) {
throw new TRPCError({ code: "NOT_FOUND", message: "Invalid category id" });
} else if (!file) {
throw new TRPCError({ code: "NOT_FOUND", message: "Invalid file id" });
}
try {
await FileRepo.removeFileFromCategory(input.file, input.id);
} catch (e) {
if (e instanceof IntegrityError && e.message === "File not found in category") {
throw new TRPCError({ code: "BAD_REQUEST", message: "File not added" });
}
throw e;
}
}),
});
export default categoryRouter;