mirror of
https://github.com/kmc7468/arkvault.git
synced 2025-12-14 22:08:45 +00:00
/api/category/[id], /api/category/create Endpoint 구현
This commit is contained in:
@@ -2,7 +2,7 @@ import { IntegrityError } from "./error";
|
|||||||
import db from "./kysely";
|
import db from "./kysely";
|
||||||
import type { Ciphertext } from "./schema";
|
import type { Ciphertext } from "./schema";
|
||||||
|
|
||||||
type CategoryId = "root" | number;
|
export type CategoryId = "root" | number;
|
||||||
|
|
||||||
interface Category {
|
interface Category {
|
||||||
id: number;
|
id: number;
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { IntegrityError } from "./error";
|
|||||||
import db from "./kysely";
|
import db from "./kysely";
|
||||||
import type { Ciphertext } from "./schema";
|
import type { Ciphertext } from "./schema";
|
||||||
|
|
||||||
type DirectoryId = "root" | number;
|
export type DirectoryId = "root" | number;
|
||||||
|
|
||||||
interface Directory {
|
interface Directory {
|
||||||
id: number;
|
id: number;
|
||||||
|
|||||||
28
src/lib/server/schemas/category.ts
Normal file
28
src/lib/server/schemas/category.ts
Normal 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>;
|
||||||
@@ -1,9 +1,11 @@
|
|||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
|
export const directoryIdSchema = z.union([z.enum(["root"]), z.number().int().positive()]);
|
||||||
|
|
||||||
export const directoryInfoResponse = z.object({
|
export const directoryInfoResponse = z.object({
|
||||||
metadata: z
|
metadata: z
|
||||||
.object({
|
.object({
|
||||||
parent: z.union([z.enum(["root"]), z.number().int().positive()]),
|
parent: directoryIdSchema,
|
||||||
mekVersion: z.number().int().positive(),
|
mekVersion: z.number().int().positive(),
|
||||||
dek: z.string().base64().nonempty(),
|
dek: z.string().base64().nonempty(),
|
||||||
dekVersion: z.string().datetime(),
|
dekVersion: z.string().datetime(),
|
||||||
@@ -29,7 +31,7 @@ export const directoryRenameRequest = z.object({
|
|||||||
export type DirectoryRenameRequest = z.infer<typeof directoryRenameRequest>;
|
export type DirectoryRenameRequest = z.infer<typeof directoryRenameRequest>;
|
||||||
|
|
||||||
export const directoryCreateRequest = z.object({
|
export const directoryCreateRequest = z.object({
|
||||||
parent: z.union([z.enum(["root"]), z.number().int().positive()]),
|
parent: directoryIdSchema,
|
||||||
mekVersion: z.number().int().positive(),
|
mekVersion: z.number().int().positive(),
|
||||||
dek: z.string().base64().nonempty(),
|
dek: z.string().base64().nonempty(),
|
||||||
dekVersion: z.string().datetime(),
|
dekVersion: z.string().datetime(),
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
import mime from "mime";
|
import mime from "mime";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
import { directoryIdSchema } from "./directory";
|
||||||
|
|
||||||
export const fileInfoResponse = z.object({
|
export const fileInfoResponse = z.object({
|
||||||
parent: z.union([z.enum(["root"]), z.number().int().positive()]),
|
parent: directoryIdSchema,
|
||||||
mekVersion: z.number().int().positive(),
|
mekVersion: z.number().int().positive(),
|
||||||
dek: z.string().base64().nonempty(),
|
dek: z.string().base64().nonempty(),
|
||||||
dekVersion: z.string().datetime(),
|
dekVersion: z.string().datetime(),
|
||||||
@@ -39,7 +40,7 @@ export const duplicateFileScanResponse = z.object({
|
|||||||
export type DuplicateFileScanResponse = z.infer<typeof duplicateFileScanResponse>;
|
export type DuplicateFileScanResponse = z.infer<typeof duplicateFileScanResponse>;
|
||||||
|
|
||||||
export const fileUploadRequest = z.object({
|
export const fileUploadRequest = z.object({
|
||||||
parent: z.union([z.enum(["root"]), z.number().int().positive()]),
|
parent: directoryIdSchema,
|
||||||
mekVersion: z.number().int().positive(),
|
mekVersion: z.number().int().positive(),
|
||||||
dek: z.string().base64().nonempty(),
|
dek: z.string().base64().nonempty(),
|
||||||
dekVersion: z.string().datetime(),
|
dekVersion: z.string().datetime(),
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
export * from "./auth";
|
export * from "./auth";
|
||||||
|
export * from "./category";
|
||||||
export * from "./client";
|
export * from "./client";
|
||||||
export * from "./directory";
|
export * from "./directory";
|
||||||
export * from "./file";
|
export * from "./file";
|
||||||
|
|||||||
45
src/lib/server/services/category.ts
Normal file
45
src/lib/server/services/category.ts
Normal 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;
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -8,11 +8,12 @@ import {
|
|||||||
setDirectoryEncName,
|
setDirectoryEncName,
|
||||||
unregisterDirectory,
|
unregisterDirectory,
|
||||||
getAllFilesByParent,
|
getAllFilesByParent,
|
||||||
|
type DirectoryId,
|
||||||
type NewDirectory,
|
type NewDirectory,
|
||||||
} from "$lib/server/db/file";
|
} from "$lib/server/db/file";
|
||||||
import type { Ciphertext } from "$lib/server/db/schema";
|
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;
|
const directory = directoryId !== "root" ? await getDirectory(userId, directoryId) : undefined;
|
||||||
if (directory === null) {
|
if (directory === null) {
|
||||||
error(404, "Invalid directory id");
|
error(404, "Invalid directory id");
|
||||||
|
|||||||
33
src/routes/api/category/[id]/+server.ts
Normal file
33
src/routes/api/category/[id]/+server.ts
Normal 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),
|
||||||
|
);
|
||||||
|
};
|
||||||
23
src/routes/api/category/create/+server.ts
Normal file
23
src/routes/api/category/create/+server.ts
Normal 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" } });
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user