서버 코드를 Router, Service, Repository 계층으로 분리하고 오류 처리 및 파일 삭제 로직 개선

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
staticandClaude Opus 5.5 committed 2026-10-12 00:00:21 +09:00
1 parent ed732be999
commit 6a6aa1765b
72 files changed
+1928 -1509

No files matched your search

+45 -146
View File
@@ -1,8 +1,14 @@
import { TRPCError } from "@trpc/server";
import { z } from "zod";
import { CategoryIdSchema } from "#lib/schemas/index.js";
import { CategoryRepo, FileRepo, IntegrityError } from "#lib/server/db/index.js";
import { router, roleProcedure } from "../init.server";
import { CategoryService } from "#lib/server/services/index.js";
import { toCategoryDto, toFileDto } from "#trpc/dto.js";
import { router, roleProcedure } from "#trpc/init.server.js";
import {
Base64Schema,
CategoryIdSchema,
EncryptedNameSchema,
IdSchema,
toCiphertext,
} from "#trpc/schemas.js";
const categoryRouter = router({
get: roleProcedure["activeClient"]
@@ -13,53 +19,15 @@ const categoryRouter = router({
}),
)
.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, files] = await Promise.all([
CategoryRepo.getAllCategoriesByParent(ctx.session.userId, input.id),
input.id !== "root"
? FileRepo.getAllFilesByCategory(ctx.session.userId, input.id, input.recurse)
: undefined,
]);
const { category, subCategories, files } = await CategoryService.getCategory(
ctx.session.userId,
input.id,
input.recurse,
);
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((category) => ({
id: category.id,
mekVersion: category.mekVersion,
dek: category.encDek,
dekVersion: category.dekVersion,
name: category.encName.ciphertext,
nameIv: category.encName.iv,
})),
files: files?.map((file) => ({
id: file.id,
parent: file.parentId,
mekVersion: file.mekVersion,
dek: file.encDek,
dekVersion: file.dekVersion,
contentType: file.contentType,
name: file.encName.ciphertext,
nameIv: file.encName.iv,
createdAt: file.encCreatedAt?.ciphertext,
createdAtIv: file.encCreatedAt?.iv,
lastModifiedAt: file.encLastModifiedAt.ciphertext,
lastModifiedAtIv: file.encLastModifiedAt.iv,
isFavorite: file.isFavorite,
isRecursive: file.isRecursive,
})),
metadata: category && toCategoryDto(category),
subCategories: subCategories.map(toCategoryDto),
files: files?.map((file) => ({ ...toFileDto(file), isRecursive: file.isRecursive })),
};
}),
@@ -67,139 +35,70 @@ const categoryRouter = router({
.input(
z.object({
parent: CategoryIdSchema,
mekVersion: z.int().positive(),
dek: z.base64().nonempty(),
mekVersion: IdSchema,
dek: Base64Schema,
dekVersion: z.date(),
name: z.base64().nonempty(),
nameIv: z.base64().nonempty(),
...EncryptedNameSchema,
}),
)
.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) {
if (e.message === "Inactive MEK version") {
throw new TRPCError({ code: "BAD_REQUEST", message: e.message });
} else if (e.message === "Category not found") {
throw new TRPCError({ code: "NOT_FOUND", message: "Invalid parent category id" });
}
}
throw e;
}
await CategoryService.createCategory({
userId: ctx.session.userId,
parentId: input.parent,
mekVersion: input.mekVersion,
encDek: input.dek,
dekVersion: input.dekVersion,
encName: toCiphertext(input.name, input.nameIv),
});
}),
rename: roleProcedure["activeClient"]
.input(
z.object({
id: z.int().positive(),
id: IdSchema,
dekVersion: z.date(),
name: z.base64().nonempty(),
nameIv: z.base64().nonempty(),
...EncryptedNameSchema,
}),
)
.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;
}
await CategoryService.renameCategory(
ctx.session.userId,
input.id,
input.dekVersion,
toCiphertext(input.name, input.nameIv),
);
}),
delete: roleProcedure["activeClient"]
.input(
z.object({
id: z.int().positive(),
id: IdSchema,
}),
)
.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;
}
await CategoryService.deleteCategory(ctx.session.userId, input.id);
}),
addFile: roleProcedure["activeClient"]
.input(
z.object({
id: z.int().positive(),
file: z.int().positive(),
id: IdSchema,
file: IdSchema,
}),
)
.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;
}
await CategoryService.addFileToCategory(ctx.session.userId, input.id, input.file);
}),
removeFile: roleProcedure["activeClient"]
.input(
z.object({
id: z.int().positive(),
file: z.int().positive(),
id: IdSchema,
file: IdSchema,
}),
)
.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;
}
await CategoryService.removeFileFromCategory(ctx.session.userId, input.id, input.file);
}),
});