/api/category/[id], /api/category/create Endpoint 구현

This commit is contained in:
static
2025-01-21 14:35:34 +09:00
parent f66421a5dc
commit 2993593770
10 changed files with 141 additions and 7 deletions

View File

@@ -2,7 +2,7 @@ import { IntegrityError } from "./error";
import db from "./kysely";
import type { Ciphertext } from "./schema";
type CategoryId = "root" | number;
export type CategoryId = "root" | number;
interface Category {
id: number;

View File

@@ -3,7 +3,7 @@ import { IntegrityError } from "./error";
import db from "./kysely";
import type { Ciphertext } from "./schema";
type DirectoryId = "root" | number;
export type DirectoryId = "root" | number;
interface Directory {
id: number;

View File

@@ -0,0 +1,28 @@
import { z } from "zod";
export const categoryIdSchema = z.union([z.enum(["root"]), z.number().int().positive()]);
export const categoryInfoResponse = z.object({
metadata: z
.object({
parent: categoryIdSchema,
mekVersion: z.number().int().positive(),
dek: z.string().base64().nonempty(),
dekVersion: z.string().datetime(),
name: z.string().base64().nonempty(),
nameIv: z.string().base64().nonempty(),
})
.optional(),
subCategories: z.number().int().positive().array(),
});
export type CategoryInfoResponse = z.infer<typeof categoryInfoResponse>;
export const categoryCreateRequest = z.object({
parent: categoryIdSchema,
mekVersion: z.number().int().positive(),
dek: z.string().base64().nonempty(),
dekVersion: z.string().datetime(),
name: z.string().base64().nonempty(),
nameIv: z.string().base64().nonempty(),
});
export type CategoryCreateRequest = z.infer<typeof categoryCreateRequest>;

View File

@@ -1,9 +1,11 @@
import { z } from "zod";
export const directoryIdSchema = z.union([z.enum(["root"]), z.number().int().positive()]);
export const directoryInfoResponse = z.object({
metadata: z
.object({
parent: z.union([z.enum(["root"]), z.number().int().positive()]),
parent: directoryIdSchema,
mekVersion: z.number().int().positive(),
dek: z.string().base64().nonempty(),
dekVersion: z.string().datetime(),
@@ -29,7 +31,7 @@ export const directoryRenameRequest = z.object({
export type DirectoryRenameRequest = z.infer<typeof directoryRenameRequest>;
export const directoryCreateRequest = z.object({
parent: z.union([z.enum(["root"]), z.number().int().positive()]),
parent: directoryIdSchema,
mekVersion: z.number().int().positive(),
dek: z.string().base64().nonempty(),
dekVersion: z.string().datetime(),

View File

@@ -1,8 +1,9 @@
import mime from "mime";
import { z } from "zod";
import { directoryIdSchema } from "./directory";
export const fileInfoResponse = z.object({
parent: z.union([z.enum(["root"]), z.number().int().positive()]),
parent: directoryIdSchema,
mekVersion: z.number().int().positive(),
dek: z.string().base64().nonempty(),
dekVersion: z.string().datetime(),
@@ -39,7 +40,7 @@ export const duplicateFileScanResponse = z.object({
export type DuplicateFileScanResponse = z.infer<typeof duplicateFileScanResponse>;
export const fileUploadRequest = z.object({
parent: z.union([z.enum(["root"]), z.number().int().positive()]),
parent: directoryIdSchema,
mekVersion: z.number().int().positive(),
dek: z.string().base64().nonempty(),
dekVersion: z.string().datetime(),

View File

@@ -1,4 +1,5 @@
export * from "./auth";
export * from "./category";
export * from "./client";
export * from "./directory";
export * from "./file";

View File

@@ -0,0 +1,45 @@
import { error } from "@sveltejs/kit";
import {
registerCategory,
getAllCategoriesByParent,
getCategory,
type CategoryId,
type NewCategory,
} from "$lib/server/db/category";
import { IntegrityError } from "$lib/server/db/error";
export const getCategoryInformation = async (userId: number, categoryId: CategoryId) => {
const category = categoryId !== "root" ? await getCategory(userId, categoryId) : undefined;
if (category === null) {
error(404, "Invalid category id");
}
const categories = await getAllCategoriesByParent(userId, categoryId);
return {
metadata: category && {
parentId: category.parentId ?? ("root" as const),
mekVersion: category.mekVersion,
encDek: category.encDek,
dekVersion: category.dekVersion,
encName: category.encName,
},
categories: categories.map(({ id }) => id),
};
};
export const createCategory = async (params: NewCategory) => {
const oneMinuteAgo = new Date(Date.now() - 60 * 1000);
const oneMinuteLater = new Date(Date.now() + 60 * 1000);
if (params.dekVersion <= oneMinuteAgo || params.dekVersion >= oneMinuteLater) {
error(400, "Invalid DEK version");
}
try {
await registerCategory(params);
} catch (e) {
if (e instanceof IntegrityError && e.message === "Inactive MEK version") {
error(400, "Inactive MEK version");
}
throw e;
}
};

View File

@@ -8,11 +8,12 @@ import {
setDirectoryEncName,
unregisterDirectory,
getAllFilesByParent,
type DirectoryId,
type NewDirectory,
} from "$lib/server/db/file";
import type { Ciphertext } from "$lib/server/db/schema";
export const getDirectoryInformation = async (userId: number, directoryId: "root" | number) => {
export const getDirectoryInformation = async (userId: number, directoryId: DirectoryId) => {
const directory = directoryId !== "root" ? await getDirectory(userId, directoryId) : undefined;
if (directory === null) {
error(404, "Invalid directory id");

View File

@@ -0,0 +1,33 @@
import { error, json } from "@sveltejs/kit";
import { z } from "zod";
import { authorize } from "$lib/server/modules/auth";
import { categoryInfoResponse, type CategoryInfoResponse } from "$lib/server/schemas";
import { getCategoryInformation } from "$lib/server/services/category";
import type { RequestHandler } from "./$types";
export const GET: RequestHandler = async ({ locals, params }) => {
const { userId } = await authorize(locals, "activeClient");
const zodRes = z
.object({
id: z.union([z.enum(["root"]), z.coerce.number().int().positive()]),
})
.safeParse(params);
if (!zodRes.success) error(400, "Invalid path parameters");
const { id } = zodRes.data;
const { metadata, categories } = await getCategoryInformation(userId, id);
return json(
categoryInfoResponse.parse({
metadata: metadata && {
parent: metadata.parentId,
mekVersion: metadata.mekVersion,
dek: metadata.encDek,
dekVersion: metadata.dekVersion.toISOString(),
name: metadata.encName.ciphertext,
nameIv: metadata.encName.iv,
},
subCategories: categories,
} satisfies CategoryInfoResponse),
);
};

View File

@@ -0,0 +1,23 @@
import { error, text } from "@sveltejs/kit";
import { authorize } from "$lib/server/modules/auth";
import { categoryCreateRequest } from "$lib/server/schemas";
import { createCategory } from "$lib/server/services/category";
import type { RequestHandler } from "./$types";
export const POST: RequestHandler = async ({ locals, request }) => {
const { userId } = await authorize(locals, "activeClient");
const zodRes = categoryCreateRequest.safeParse(await request.json());
if (!zodRes.success) error(400, "Invalid request body");
const { parent, mekVersion, dek, dekVersion, name, nameIv } = zodRes.data;
await createCategory({
userId,
parentId: parent,
mekVersion,
encDek: dek,
dekVersion: new Date(dekVersion),
encName: { ciphertext: name, iv: nameIv },
});
return text("Category created", { headers: { "Content-Type": "text/plain" } });
};