mirror of
https://github.com/kmc7468/arkvault.git
synced 2026-02-04 08:06:56 +00:00
/api/category, /api/directory, /api/file 아래의 대부분의 Endpoint들을 tRPC로 마이그레이션
This commit is contained in:
194
src/trpc/routers/category.ts
Normal file
194
src/trpc/routers/category.ts
Normal 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;
|
||||
125
src/trpc/routers/directory.ts
Normal file
125
src/trpc/routers/directory.ts
Normal file
@@ -0,0 +1,125 @@
|
||||
import { TRPCError } from "@trpc/server";
|
||||
import { z } from "zod";
|
||||
import { FileRepo, IntegrityError } from "$lib/server/db";
|
||||
import { safeUnlink } from "$lib/server/modules/filesystem";
|
||||
import { directoryIdSchema } from "$lib/server/schemas";
|
||||
import { router, roleProcedure } from "../init.server";
|
||||
|
||||
const directoryRouter = router({
|
||||
get: roleProcedure["activeClient"]
|
||||
.input(
|
||||
z.object({
|
||||
id: directoryIdSchema,
|
||||
}),
|
||||
)
|
||||
.query(async ({ ctx, input }) => {
|
||||
const directory =
|
||||
input.id !== "root" ? await FileRepo.getDirectory(ctx.session.userId, input.id) : undefined;
|
||||
if (directory === null) {
|
||||
throw new TRPCError({ code: "NOT_FOUND", message: "Invalid directory id" });
|
||||
}
|
||||
|
||||
const [directories, files] = await Promise.all([
|
||||
FileRepo.getAllDirectoriesByParent(ctx.session.userId, input.id),
|
||||
FileRepo.getAllFilesByParent(ctx.session.userId, input.id),
|
||||
]);
|
||||
return {
|
||||
metadata: directory && {
|
||||
parent: directory.parentId,
|
||||
mekVersion: directory.mekVersion,
|
||||
dek: directory.encDek,
|
||||
dekVersion: directory.dekVersion,
|
||||
name: directory.encName.ciphertext,
|
||||
nameIv: directory.encName.iv,
|
||||
},
|
||||
subDirectories: directories.map(({ id }) => id),
|
||||
files: files.map(({ id }) => id),
|
||||
};
|
||||
}),
|
||||
|
||||
create: roleProcedure["activeClient"]
|
||||
.input(
|
||||
z.object({
|
||||
parent: directoryIdSchema,
|
||||
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 FileRepo.registerDirectory({
|
||||
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 FileRepo.setDirectoryEncName(ctx.session.userId, input.id, input.dekVersion, {
|
||||
ciphertext: input.name,
|
||||
iv: input.nameIv,
|
||||
});
|
||||
} catch (e) {
|
||||
if (e instanceof IntegrityError) {
|
||||
if (e.message === "Directory not found") {
|
||||
throw new TRPCError({ code: "NOT_FOUND", message: "Invalid directory 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 files = await FileRepo.unregisterDirectory(ctx.session.userId, input.id);
|
||||
files.forEach(({ path, thumbnailPath }) => {
|
||||
safeUnlink(path); // Intended
|
||||
safeUnlink(thumbnailPath); // Intended
|
||||
});
|
||||
return { deletedFiles: files.map(({ id }) => id) };
|
||||
} catch (e) {
|
||||
if (e instanceof IntegrityError && e.message === "Directory not found") {
|
||||
throw new TRPCError({ code: "NOT_FOUND", message: "Invalid directory id" });
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
}),
|
||||
});
|
||||
|
||||
export default directoryRouter;
|
||||
122
src/trpc/routers/file.ts
Normal file
122
src/trpc/routers/file.ts
Normal 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;
|
||||
@@ -1,4 +1,7 @@
|
||||
export { default as categoryRouter } from "./category";
|
||||
export { default as clientRouter } from "./client";
|
||||
export { default as directoryRouter } from "./directory";
|
||||
export { default as fileRouter } from "./file";
|
||||
export { default as hskRouter } from "./hsk";
|
||||
export { default as mekRouter } from "./mek";
|
||||
export { default as userRouter } from "./user";
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
import { TRPCError } from "@trpc/server";
|
||||
import { z } from "zod";
|
||||
import { UserRepo } from "$lib/server/db";
|
||||
import { router, roleProcedure } from "../init.server";
|
||||
|
||||
const userRouter = router({
|
||||
info: roleProcedure.any.query(async ({ ctx }) => {
|
||||
get: roleProcedure["any"].query(async ({ ctx }) => {
|
||||
const user = await UserRepo.getUser(ctx.session.userId);
|
||||
if (!user) {
|
||||
throw new TRPCError({ code: "INTERNAL_SERVER_ERROR", message: "Invalid session id" });
|
||||
@@ -12,16 +11,6 @@ const userRouter = router({
|
||||
|
||||
return { email: user.email, nickname: user.nickname };
|
||||
}),
|
||||
|
||||
changeNickname: roleProcedure.any
|
||||
.input(
|
||||
z.object({
|
||||
newNickname: z.string().trim().min(2).max(8),
|
||||
}),
|
||||
)
|
||||
.mutation(async ({ ctx, input }) => {
|
||||
await UserRepo.setUserNickname(ctx.session.userId, input.newNickname);
|
||||
}),
|
||||
});
|
||||
|
||||
export default userRouter;
|
||||
|
||||
Reference in New Issue
Block a user