diff --git a/src/lib/modules/filesystem.ts b/src/lib/modules/filesystem.ts index 54b5c10..cfafbbe 100644 --- a/src/lib/modules/filesystem.ts +++ b/src/lib/modules/filesystem.ts @@ -13,6 +13,7 @@ import { } from "$lib/indexedDB"; import { unwrapDataKey, decryptString } from "$lib/modules/crypto"; import type { + CategoryFileListResponse, CategoryInfoResponse, DirectoryInfoResponse, FileInfoResponse, @@ -56,6 +57,7 @@ export type CategoryInfo = dataKeyVersion?: undefined; name?: undefined; subCategoryIds: number[]; + files?: undefined; } | { id: number; @@ -63,6 +65,7 @@ export type CategoryInfo = dataKeyVersion?: Date; name: string; subCategoryIds: number[]; + files: number[]; }; const directoryInfoStore = new Map>(); @@ -235,7 +238,7 @@ const fetchCategoryInfoFromServer = async ( info: Writable, masterKey: CryptoKey, ) => { - const res = await callGetApi(`/api/category/${id}`); + let res = await callGetApi(`/api/category/${id}`); if (res.status === 404) { info.set(null); return; @@ -251,12 +254,20 @@ const fetchCategoryInfoFromServer = async ( const { dataKey } = await unwrapDataKey(metadata!.dek, masterKey); const name = await decryptString(metadata!.name, metadata!.nameIv, dataKey); + res = await callGetApi(`/api/category/${id}/file/list`); + if (!res.ok) { + throw new Error("Failed to fetch category files"); + } + + const { files }: CategoryFileListResponse = await res.json(); + info.set({ id, dataKey, dataKeyVersion: new Date(metadata!.dekVersion), name, subCategoryIds: subCategories, + files, }); } }; diff --git a/src/lib/server/schemas/category.ts b/src/lib/server/schemas/category.ts index 13032b3..a693bee 100644 --- a/src/lib/server/schemas/category.ts +++ b/src/lib/server/schemas/category.ts @@ -17,6 +17,16 @@ export const categoryInfoResponse = z.object({ }); export type CategoryInfoResponse = z.infer; +export const categoryFileAddRequest = z.object({ + file: z.number().int().positive(), +}); +export type CategoryFileAddRequest = z.infer; + +export const categoryFileListResponse = z.object({ + files: z.number().int().positive().array(), +}); +export type CategoryFileListResponse = z.infer; + export const categoryCreateRequest = z.object({ parent: categoryIdSchema, mekVersion: z.number().int().positive(), diff --git a/src/lib/server/services/category.ts b/src/lib/server/services/category.ts index 5dd6c65..e81e458 100644 --- a/src/lib/server/services/category.ts +++ b/src/lib/server/services/category.ts @@ -7,6 +7,7 @@ import { type NewCategory, } from "$lib/server/db/category"; import { IntegrityError } from "$lib/server/db/error"; +import { getAllFilesByCategory, getFile, addFileToCategory } from "$lib/server/db/file"; export const getCategoryInformation = async (userId: number, categoryId: CategoryId) => { const category = categoryId !== "root" ? await getCategory(userId, categoryId) : undefined; @@ -27,6 +28,35 @@ export const getCategoryInformation = async (userId: number, categoryId: Categor }; }; +export const addCategoryFile = async (userId: number, categoryId: number, fileId: number) => { + const category = await getCategory(userId, categoryId); + const file = await getFile(userId, fileId); + if (!category) { + error(404, "Invalid category id"); + } else if (!file) { + error(404, "Invalid file id"); + } + + try { + await addFileToCategory(fileId, categoryId); + } catch (e) { + if (e instanceof IntegrityError && e.message === "File already added to category") { + error(400, "File already added"); + } + throw e; + } +}; + +export const getCategoryFiles = async (userId: number, categoryId: number) => { + const category = await getCategory(userId, categoryId); + if (!category) { + error(404, "Invalid category id"); + } + + const files = await getAllFilesByCategory(userId, categoryId); + return { files: files.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); diff --git a/src/routes/(main)/category/[[id]]/+page.svelte b/src/routes/(main)/category/[[id]]/+page.svelte index 91e027b..33ac0a7 100644 --- a/src/routes/(main)/category/[[id]]/+page.svelte +++ b/src/routes/(main)/category/[[id]]/+page.svelte @@ -5,6 +5,7 @@ import { getCategoryInfo, type CategoryInfo } from "$lib/modules/filesystem"; import { masterKeyStore } from "$lib/stores"; import CreateCategoryModal from "./CreateCategoryModal.svelte"; + import Files from "./Files.svelte"; import SubCategories from "./SubCategories.svelte"; import { requestCategoryCreation } from "./service"; @@ -34,7 +35,7 @@ {/if} {#if $info} -
+
{#if data.id !== "root"}

하위 카테고리

@@ -52,6 +53,9 @@ {#if data.id !== "root"}

파일

+ {#key $info} + goto(`/file/${id}`)} /> + {/key}
{/if}
diff --git a/src/routes/(main)/category/[[id]]/File.svelte b/src/routes/(main)/category/[[id]]/File.svelte new file mode 100644 index 0000000..c3b9b97 --- /dev/null +++ b/src/routes/(main)/category/[[id]]/File.svelte @@ -0,0 +1,61 @@ + + +{#if $info} + + +
+
+
+ +
+

+ {$info.name} +

+ +
+
+{/if} + + diff --git a/src/routes/(main)/category/[[id]]/Files.svelte b/src/routes/(main)/category/[[id]]/Files.svelte new file mode 100644 index 0000000..6679931 --- /dev/null +++ b/src/routes/(main)/category/[[id]]/Files.svelte @@ -0,0 +1,34 @@ + + +
+ {#each files as file} + + {:else} +

이 카테고리에 추가된 파일이 없어요.

+ {/each} +
diff --git a/src/routes/(main)/category/[[id]]/service.ts b/src/routes/(main)/category/[[id]]/service.ts index a5d354a..6af11e3 100644 --- a/src/routes/(main)/category/[[id]]/service.ts +++ b/src/routes/(main)/category/[[id]]/service.ts @@ -10,6 +10,13 @@ export interface SelectedSubCategory { name: string; } +export interface SelectedFile { + id: number; + dataKey: CryptoKey; + dataKeyVersion: Date; + name: string; +} + export const requestCategoryCreation = async ( name: string, parentId: "root" | number, diff --git a/src/routes/api/category/[id]/file/add/+server.ts b/src/routes/api/category/[id]/file/add/+server.ts new file mode 100644 index 0000000..2eaf2f2 --- /dev/null +++ b/src/routes/api/category/[id]/file/add/+server.ts @@ -0,0 +1,25 @@ +import { error, text } from "@sveltejs/kit"; +import { z } from "zod"; +import { authorize } from "$lib/server/modules/auth"; +import { categoryFileAddRequest } from "$lib/server/schemas"; +import { addCategoryFile } from "$lib/server/services/category"; +import type { RequestHandler } from "./$types"; + +export const POST: RequestHandler = async ({ locals, params, request }) => { + const { userId } = await authorize(locals, "activeClient"); + + const paramsZodRes = z + .object({ + id: z.coerce.number().int().positive(), + }) + .safeParse(params); + if (!paramsZodRes.success) error(400, "Invalid path parameters"); + const { id } = paramsZodRes.data; + + const bodyZodRes = categoryFileAddRequest.safeParse(await request.json()); + if (!bodyZodRes.success) error(400, "Invalid request body"); + const { file } = bodyZodRes.data; + + await addCategoryFile(userId, id, file); + return text("File added", { headers: { "Content-Type": "text/plain" } }); +}; diff --git a/src/routes/api/category/[id]/file/list/+server.ts b/src/routes/api/category/[id]/file/list/+server.ts new file mode 100644 index 0000000..e2aa7af --- /dev/null +++ b/src/routes/api/category/[id]/file/list/+server.ts @@ -0,0 +1,17 @@ +import { error, json } from "@sveltejs/kit"; +import { z } from "zod"; +import { authorize } from "$lib/server/modules/auth"; +import { categoryFileListResponse, type CategoryFileListResponse } from "$lib/server/schemas"; +import { getCategoryFiles } 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.coerce.number().int().positive() }).safeParse(params); + if (!zodRes.success) error(400, "Invalid path parameters"); + const { id } = zodRes.data; + + const { files } = await getCategoryFiles(userId, id); + return json(categoryFileListResponse.parse({ files }) as CategoryFileListResponse); +};