diff --git a/src/lib/components/atoms/RowVirtualizer.svelte b/src/lib/components/atoms/RowVirtualizer.svelte
index cd7f35f..aa68bd2 100644
--- a/src/lib/components/atoms/RowVirtualizer.svelte
+++ b/src/lib/components/atoms/RowVirtualizer.svelte
@@ -16,7 +16,7 @@
let element: HTMLElement | undefined = $state();
let scrollMargin = $state(0);
- const virtualizer = $derived(
+ let virtualizer = $derived(
createWindowVirtualizer({
count,
estimateSize: itemHeight,
diff --git a/src/lib/components/molecules/Categories/Categories.svelte b/src/lib/components/molecules/Categories/Categories.svelte
index 54368c6..a4a123b 100644
--- a/src/lib/components/molecules/Categories/Categories.svelte
+++ b/src/lib/components/molecules/Categories/Categories.svelte
@@ -1,59 +1,29 @@
{#if categoriesWithName.length > 0}
- {#each categoriesWithName as { info }}
+ {#each categoriesWithName as category}
import type { Component } from "svelte";
import type { SvelteHTMLElements } from "svelte/elements";
- import type { Writable } from "svelte/store";
import { ActionEntryButton } from "$lib/components/atoms";
import { CategoryLabel } from "$lib/components/molecules";
- import type { CategoryInfo } from "$lib/modules/filesystem";
+ import type { SubCategoryInfo } from "$lib/modules/filesystem2.svelte";
import type { SelectedCategory } from "./service";
interface Props {
- info: Writable;
+ info: SubCategoryInfo;
menuIcon?: Component;
onclick: (category: SelectedCategory) => void;
onMenuClick?: (category: SelectedCategory) => void;
}
let { info, menuIcon, onclick, onMenuClick }: Props = $props();
-
- const openCategory = () => {
- const { id, dataKey, dataKeyVersion, name } = $info as CategoryInfo;
- if (!dataKey || !dataKeyVersion) return; // TODO: Error handling
-
- onclick({ id, dataKey, dataKeyVersion, name });
- };
-
- const openMenu = () => {
- const { id, dataKey, dataKeyVersion, name } = $info as CategoryInfo;
- if (!dataKey || !dataKeyVersion) return; // TODO: Error handling
-
- onMenuClick!({ id, dataKey, dataKeyVersion, name });
- };
-{#if $info}
-
-
-
-{/if}
+ onclick(info)}
+ actionButtonIcon={menuIcon}
+ onActionButtonClick={() => onMenuClick?.(info)}
+>
+
+
diff --git a/src/lib/components/molecules/Categories/service.ts b/src/lib/components/molecules/Categories/service.ts
index 08c41db..683d516 100644
--- a/src/lib/components/molecules/Categories/service.ts
+++ b/src/lib/components/molecules/Categories/service.ts
@@ -1,6 +1,5 @@
export interface SelectedCategory {
id: number;
- dataKey: CryptoKey;
- dataKeyVersion: Date;
+ dataKey?: { key: CryptoKey; version: Date };
name: string;
}
diff --git a/src/lib/components/molecules/SubCategories.svelte b/src/lib/components/molecules/SubCategories.svelte
index 9c84a89..a271309 100644
--- a/src/lib/components/molecules/SubCategories.svelte
+++ b/src/lib/components/molecules/SubCategories.svelte
@@ -1,10 +1,8 @@
@@ -53,14 +43,12 @@
{#if subCategoryCreatePosition === "top"}
{@render subCategoryCreate()}
{/if}
- {#key info}
-
- {/key}
+
{#if subCategoryCreatePosition === "bottom"}
{@render subCategoryCreate()}
{/if}
diff --git a/src/lib/components/organisms/Category/Category.svelte b/src/lib/components/organisms/Category/Category.svelte
index b42aeef..4824f82 100644
--- a/src/lib/components/organisms/Category/Category.svelte
+++ b/src/lib/components/organisms/Category/Category.svelte
@@ -1,11 +1,8 @@
@@ -89,26 +58,24 @@
하위 카테고리의 파일
- {#key info}
-
48 + (index + 1 < files.length ? 4 : 0)}
- >
- {#snippet item(index)}
- {@const { info, isRecursive } = files[index]!}
-
-
-
- {/snippet}
- {#snippet placeholder()}
- 이 카테고리에 추가된 파일이 없어요.
- {/snippet}
-
- {/key}
+
48 + (index + 1 < files.length ? 4 : 0)}
+ >
+ {#snippet item(index)}
+ {@const { details } = files[index]!}
+
+
+
+ {/snippet}
+ {#snippet placeholder()}
+ 이 카테고리에 추가된 파일이 없어요.
+ {/snippet}
+
{/if}
diff --git a/src/lib/components/organisms/Category/File.svelte b/src/lib/components/organisms/Category/File.svelte
index 8e3fc12..88e821a 100644
--- a/src/lib/components/organisms/Category/File.svelte
+++ b/src/lib/components/organisms/Category/File.svelte
@@ -1,59 +1,38 @@
-{#if $info}
-
-
-
-{/if}
+ onclick(info)}
+ actionButtonIcon={onRemoveClick && IconClose}
+ onActionButtonClick={() => onRemoveClick?.(info)}
+>
+ {#await thumbnailPromise}
+
+ {:then thumbnail}
+
+ {/await}
+
diff --git a/src/lib/components/organisms/Category/service.ts b/src/lib/components/organisms/Category/service.ts
index fb6e640..3c78d2f 100644
--- a/src/lib/components/organisms/Category/service.ts
+++ b/src/lib/components/organisms/Category/service.ts
@@ -1,8 +1,4 @@
-export { requestFileThumbnailDownload } from "$lib/services/file";
-
export interface SelectedFile {
id: number;
- dataKey: CryptoKey;
- dataKeyVersion: Date;
name: string;
}
diff --git a/src/lib/modules/filesystem.ts b/src/lib/modules/filesystem.ts
index e01145b..5020793 100644
--- a/src/lib/modules/filesystem.ts
+++ b/src/lib/modules/filesystem.ts
@@ -3,11 +3,6 @@ import {
getFileInfo as getFileInfoFromIndexedDB,
storeFileInfo,
deleteFileInfo,
- getCategoryInfos as getCategoryInfosFromIndexedDB,
- getCategoryInfo as getCategoryInfoFromIndexedDB,
- storeCategoryInfo,
- updateCategoryInfo as updateCategoryInfoInIndexedDB,
- deleteCategoryInfo,
} from "$lib/indexedDB";
import { unwrapDataKey, decryptString } from "$lib/modules/crypto";
import { trpc, isTRPCClientError } from "$trpc/client";
@@ -25,28 +20,7 @@ export interface FileInfo {
categoryIds: number[];
}
-export type CategoryInfo =
- | {
- id: "root";
- dataKey?: undefined;
- dataKeyVersion?: undefined;
- name?: undefined;
- subCategoryIds: number[];
- files?: undefined;
- isFileRecursive?: undefined;
- }
- | {
- id: number;
- dataKey?: CryptoKey;
- dataKeyVersion?: Date;
- name: string;
- subCategoryIds: number[];
- files: { id: number; isRecursive: boolean }[];
- isFileRecursive: boolean;
- };
-
const fileInfoStore = new Map>();
-const categoryInfoStore = new Map>();
const fetchFileInfoFromIndexedDB = async (id: number, info: Writable) => {
if (get(info)) return;
@@ -130,124 +104,3 @@ export const getFileInfo = (fileId: number, masterKey: CryptoKey) => {
fetchFileInfo(fileId, info, masterKey); // Intended
return info;
};
-
-const fetchCategoryInfoFromIndexedDB = async (
- id: CategoryId,
- info: Writable,
-) => {
- if (get(info)) return;
-
- const [category, subCategories] = await Promise.all([
- id !== "root" ? getCategoryInfoFromIndexedDB(id) : undefined,
- getCategoryInfosFromIndexedDB(id),
- ]);
- const subCategoryIds = subCategories.map(({ id }) => id);
-
- if (id === "root") {
- info.set({ id, subCategoryIds });
- } else {
- if (!category) return;
- info.set({
- id,
- name: category.name,
- subCategoryIds,
- files: category.files,
- isFileRecursive: category.isFileRecursive,
- });
- }
-};
-
-const fetchCategoryInfoFromServer = async (
- id: CategoryId,
- info: Writable,
- masterKey: CryptoKey,
-) => {
- let data;
- try {
- data = await trpc().category.get.query({ id });
- } catch (e) {
- if (isTRPCClientError(e) && e.data?.code === "NOT_FOUND") {
- info.set(null);
- await deleteCategoryInfo(id as number);
- return;
- }
- throw new Error("Failed to fetch category information");
- }
-
- const { metadata, subCategories } = data;
-
- if (id === "root") {
- info.set({ id, subCategoryIds: subCategories });
- } else {
- const { dataKey } = await unwrapDataKey(metadata!.dek, masterKey);
- const name = await decryptString(metadata!.name, metadata!.nameIv, dataKey);
-
- let files;
- try {
- files = await trpc().category.files.query({ id, recurse: true });
- } catch {
- throw new Error("Failed to fetch category files");
- }
-
- const filesMapped = files.map(({ file, isRecursive }) => ({ id: file, isRecursive }));
- let isFileRecursive: boolean | undefined = undefined;
-
- info.update((value) => {
- const newValue = {
- isFileRecursive: false,
- ...value,
- id,
- dataKey,
- dataKeyVersion: new Date(metadata!.dekVersion),
- name,
- subCategoryIds: subCategories,
- files: filesMapped,
- };
- isFileRecursive = newValue.isFileRecursive;
- return newValue;
- });
- await storeCategoryInfo({
- id,
- parentId: metadata!.parent,
- name,
- files: filesMapped,
- isFileRecursive: isFileRecursive!,
- });
- }
-};
-
-const fetchCategoryInfo = async (
- id: CategoryId,
- info: Writable,
- masterKey: CryptoKey,
-) => {
- await fetchCategoryInfoFromIndexedDB(id, info);
- await fetchCategoryInfoFromServer(id, info, masterKey);
-};
-
-export const getCategoryInfo = (categoryId: CategoryId, masterKey: CryptoKey) => {
- // TODO: MEK rotation
-
- let info = categoryInfoStore.get(categoryId);
- if (!info) {
- info = writable(null);
- categoryInfoStore.set(categoryId, info);
- }
-
- fetchCategoryInfo(categoryId, info, masterKey); // Intended
- return info;
-};
-
-export const updateCategoryInfo = async (
- categoryId: number,
- changes: { isFileRecursive?: boolean },
-) => {
- await updateCategoryInfoInIndexedDB(categoryId, changes);
- categoryInfoStore.get(categoryId)?.update((value) => {
- if (!value) return value;
- if (changes.isFileRecursive !== undefined) {
- value.isFileRecursive = changes.isFileRecursive;
- }
- return value;
- });
-};
diff --git a/src/lib/modules/filesystem2.svelte.ts b/src/lib/modules/filesystem2.svelte.ts
index ade5342..01514cb 100644
--- a/src/lib/modules/filesystem2.svelte.ts
+++ b/src/lib/modules/filesystem2.svelte.ts
@@ -54,13 +54,14 @@ interface FileInfo {
}
export type SummarizedFileInfo = Omit;
+export type CategoryFileInfo = SummarizedFileInfo & { isRecursive: boolean };
interface LocalCategoryInfo {
id: number;
- dataKey: DataKey | undefined;
+ dataKey?: DataKey | undefined;
name: string;
- subCategories: Omit[];
- files: { id: number; name: string; isRecursive: boolean }[];
+ subCategories: SubCategoryInfo[];
+ files: CategoryFileInfo[];
isFileRecursive: boolean;
}
@@ -68,13 +69,19 @@ interface RootCategoryInfo {
id: "root";
dataKey?: undefined;
name?: undefined;
- subCategories: Omit[];
+ subCategories: SubCategoryInfo[];
files?: undefined;
+ isFileRecursive?: undefined;
}
export type CategoryInfo = LocalCategoryInfo | RootCategoryInfo;
+export type SubCategoryInfo = Omit<
+ LocalCategoryInfo,
+ "subCategories" | "files" | "isFileRecursive"
+>;
const directoryInfoCache = new Map>();
+const categoryInfoCache = new Map>();
export const getDirectoryInfo = async (id: DirectoryId, masterKey: CryptoKey) => {
const info = directoryInfoCache.get(id);
@@ -189,3 +196,146 @@ const fetchDirectoryInfoFromServer = async (
const decryptDate = async (ciphertext: string, iv: string, dataKey: CryptoKey) => {
return new Date(parseInt(await decryptString(ciphertext, iv, dataKey), 10));
};
+
+export const getCategoryInfo = async (id: CategoryId, masterKey: CryptoKey) => {
+ const info = categoryInfoCache.get(id);
+ if (info instanceof Promise) {
+ return info;
+ }
+
+ const { promise, resolve } = Promise.withResolvers();
+ if (!info) {
+ categoryInfoCache.set(id, promise);
+ const categoryInfo = await fetchCategoryInfoFromIndexedDB(id);
+ if (categoryInfo) {
+ const state = $state(categoryInfo);
+ categoryInfoCache.set(id, state);
+ resolve(state);
+ }
+ }
+
+ fetchCategoryInfoFromServer(id, masterKey).then((categoryInfo) => {
+ if (!categoryInfo) return;
+
+ let info = categoryInfoCache.get(id);
+ if (info instanceof Promise) {
+ const state = $state(categoryInfo);
+ categoryInfoCache.set(id, state);
+ resolve(state);
+ } else {
+ Object.assign(info!, categoryInfo);
+ resolve(info!);
+ }
+ });
+
+ return info ?? promise;
+};
+
+const fetchCategoryInfoFromIndexedDB = async (
+ id: CategoryId,
+): Promise => {
+ const [category, subCategories] = await Promise.all([
+ id !== "root" ? getCategoryInfoFromIndexedDB(id) : undefined,
+ getCategoryInfosFromIndexedDB(id),
+ ]);
+ const files = category
+ ? await Promise.all(
+ category.files.map(async (file) => {
+ const fileInfo = await getFileInfoFromIndexedDB(file.id);
+ return fileInfo
+ ? {
+ id: file.id,
+ contentType: fileInfo.contentType,
+ name: fileInfo.name,
+ createdAt: fileInfo.createdAt,
+ lastModifiedAt: fileInfo.lastModifiedAt,
+ isRecursive: file.isRecursive,
+ }
+ : undefined;
+ }),
+ )
+ : undefined;
+
+ if (id === "root") {
+ return { id, subCategories };
+ } else if (category) {
+ return {
+ id,
+ name: category.name,
+ subCategories,
+ files: files!.filter((file) => !!file),
+ isFileRecursive: category.isFileRecursive,
+ };
+ }
+};
+
+const fetchCategoryInfoFromServer = async (
+ id: CategoryId,
+ masterKey: CryptoKey,
+): Promise => {
+ try {
+ const {
+ metadata,
+ subCategories: subCategoriesRaw,
+ files: filesRaw,
+ } = await trpc().category.get.query({ id, recurse: true });
+ const [subCategories, files] = await Promise.all([
+ Promise.all(
+ subCategoriesRaw.map(async (category) => {
+ const { dataKey } = await unwrapDataKey(category.dek, masterKey);
+ const name = await decryptString(category.name, category.nameIv, dataKey);
+ return {
+ id: category.id,
+ dataKey: { key: dataKey, version: category.dekVersion },
+ name,
+ };
+ }),
+ ),
+ id !== "root"
+ ? Promise.all(
+ filesRaw!.map(async (file) => {
+ const { dataKey } = await unwrapDataKey(file.dek, masterKey);
+ const [name, createdAt, lastModifiedAt] = await Promise.all([
+ decryptString(file.name, file.nameIv, dataKey),
+ file.createdAt
+ ? decryptDate(file.createdAt, file.createdAtIv!, dataKey)
+ : undefined,
+ decryptDate(file.lastModifiedAt, file.lastModifiedAtIv, dataKey),
+ ]);
+ return {
+ id: file.id,
+ dataKey: { key: dataKey, version: file.dekVersion },
+ contentType: file.contentType,
+ name,
+ createdAt,
+ lastModifiedAt,
+ isRecursive: file.isRecursive,
+ };
+ }),
+ )
+ : undefined,
+ ]);
+
+ if (id === "root") {
+ return { id, subCategories };
+ } else {
+ const { dataKey } = await unwrapDataKey(metadata!.dek, masterKey);
+ const name = await decryptString(metadata!.name, metadata!.nameIv, dataKey);
+ return {
+ id,
+ dataKey: { key: dataKey, version: metadata!.dekVersion },
+ name,
+ subCategories,
+ files: files!,
+ isFileRecursive: false,
+ };
+ }
+ } catch (e) {
+ if (isTRPCClientError(e) && e.data?.code === "NOT_FOUND") {
+ categoryInfoCache.delete(id);
+ await deleteCategoryInfo(id as number);
+ return;
+ }
+ throw new Error("Failed to fetch category information");
+ }
+};
diff --git a/src/lib/server/db/file.ts b/src/lib/server/db/file.ts
index a524ff4..7bea6db 100644
--- a/src/lib/server/db/file.ts
+++ b/src/lib/server/db/file.ts
@@ -304,39 +304,51 @@ export const getAllFilesByCategory = async (
recurse: boolean,
) => {
const files = await db
- .withRecursive("cte", (db) =>
+ .withRecursive("category_tree", (db) =>
db
.selectFrom("category")
- .leftJoin("file_category", "category.id", "file_category.category_id")
- .select(["id", "parent_id", "user_id", "file_category.file_id"])
- .select(sql`0`.as("depth"))
+ .select(["id", sql`0`.as("depth")])
.where("id", "=", categoryId)
+ .where("user_id", "=", userId)
.$if(recurse, (qb) =>
qb.unionAll((db) =>
db
.selectFrom("category")
- .leftJoin("file_category", "category.id", "file_category.category_id")
- .innerJoin("cte", "category.parent_id", "cte.id")
- .select([
- "category.id",
- "category.parent_id",
- "category.user_id",
- "file_category.file_id",
- ])
- .select(sql`cte.depth + 1`.as("depth")),
+ .innerJoin("category_tree", "category.parent_id", "category_tree.id")
+ .select(["category.id", sql`depth + 1`.as("depth")]),
),
),
)
- .selectFrom("cte")
+ .selectFrom("category_tree")
+ .innerJoin("file_category", "category_tree.id", "file_category.category_id")
+ .innerJoin("file", "file_category.file_id", "file.id")
.select(["file_id", "depth"])
+ .selectAll("file")
.distinctOn("file_id")
- .where("user_id", "=", userId)
- .where("file_id", "is not", null)
- .$narrowType<{ file_id: NotNull }>()
.orderBy("file_id")
.orderBy("depth")
.execute();
- return files.map(({ file_id, depth }) => ({ id: file_id, isRecursive: depth > 0 }));
+ return files.map(
+ (file) =>
+ ({
+ id: file.file_id,
+ parentId: file.parent_id ?? "root",
+ userId: file.user_id,
+ path: file.path,
+ mekVersion: file.master_encryption_key_version,
+ encDek: file.encrypted_data_encryption_key,
+ dekVersion: file.data_encryption_key_version,
+ hskVersion: file.hmac_secret_key_version,
+ contentHmac: file.content_hmac,
+ contentType: file.content_type,
+ encContentIv: file.encrypted_content_iv,
+ encContentHash: file.encrypted_content_hash,
+ encName: file.encrypted_name,
+ encCreatedAt: file.encrypted_created_at,
+ encLastModifiedAt: file.encrypted_last_modified_at,
+ isRecursive: file.depth > 0,
+ }) satisfies File & { isRecursive: boolean },
+ );
};
export const getAllFileIds = async (userId: number) => {
diff --git a/src/routes/(fullscreen)/file/[id]/+page.svelte b/src/routes/(fullscreen)/file/[id]/+page.svelte
index 9e0ddc0..ab85dc7 100644
--- a/src/routes/(fullscreen)/file/[id]/+page.svelte
+++ b/src/routes/(fullscreen)/file/[id]/+page.svelte
@@ -6,12 +6,7 @@
import { page } from "$app/state";
import { FullscreenDiv } from "$lib/components/atoms";
import { Categories, IconEntryButton, TopBar } from "$lib/components/molecules";
- import {
- getFileInfo,
- getCategoryInfo,
- type FileInfo,
- type CategoryInfo,
- } from "$lib/modules/filesystem";
+ import { getFileInfo, type FileInfo } from "$lib/modules/filesystem";
import { captureVideoThumbnail } from "$lib/modules/thumbnail";
import { fileDownloadStatusStore, isFileDownloading, masterKeyStore } from "$lib/stores";
import AddToCategoryBottomSheet from "./AddToCategoryBottomSheet.svelte";
@@ -32,7 +27,7 @@
let { data } = $props();
let info: Writable | undefined = $state();
- let categories: Writable[] = $state([]);
+ // let categories: Writable[] = $state([]);
let isMenuOpen = $state(false);
let isAddToCategoryBottomSheetOpen = $state(false);
@@ -90,10 +85,10 @@
viewerType = undefined;
});
- $effect(() => {
- categories =
- $info?.categoryIds.map((id) => getCategoryInfo(id, $masterKeyStore?.get(1)?.key!)) ?? [];
- });
+ // $effect(() => {
+ // categories =
+ // $info?.categoryIds.map((id) => getCategoryInfo(id, $masterKeyStore?.get(1)?.key!)) ?? [];
+ // });
$effect(() => {
if ($info && $info.dataKey && $info.contentIv) {
@@ -190,12 +185,12 @@
카테고리
-
goto(`/category/${id}`)}
onCategoryMenuClick={({ id }) => removeFromCategory(id)}
- />
+ /> -->
(isAddToCategoryBottomSheetOpen = true)}
diff --git a/src/routes/(fullscreen)/file/[id]/AddToCategoryBottomSheet.svelte b/src/routes/(fullscreen)/file/[id]/AddToCategoryBottomSheet.svelte
index f1d0200..91dc7e5 100644
--- a/src/routes/(fullscreen)/file/[id]/AddToCategoryBottomSheet.svelte
+++ b/src/routes/(fullscreen)/file/[id]/AddToCategoryBottomSheet.svelte
@@ -1,9 +1,8 @@
-{#if $category}
-
-
-
- (category = getCategoryInfo(id, $masterKeyStore?.get(1)?.key!))}
- onSubCategoryCreateClick={() => (isCategoryCreateModalOpen = true)}
- subCategoryCreatePosition="top"
- />
- {#if $category.id !== "root"}
-
-
-
- {/if}
-
-
-{/if}
+{#await categoryInfoPromise then categoryInfo}
+ {#if categoryInfo}
+
+
+
+ (categoryInfoPromise = getCategoryInfo(id, $masterKeyStore?.get(1)?.key!))}
+ onSubCategoryCreateClick={() => (isCategoryCreateModalOpen = true)}
+ subCategoryCreatePosition="top"
+ />
+ {#if categoryInfo.id !== "root"}
+
+
+
+ {/if}
+
+
- {
- if (await requestCategoryCreation(name, $category!.id, $masterKeyStore?.get(1)!)) {
- category = getCategoryInfo($category!.id, $masterKeyStore?.get(1)?.key!); // TODO: FIXME
- return true;
- }
- return false;
- }}
-/>
+ {
+ if (await requestCategoryCreation(name, categoryInfo.id, $masterKeyStore?.get(1)!)) {
+ categoryInfoPromise = getCategoryInfo(categoryInfo.id, $masterKeyStore?.get(1)?.key!); // TODO: FIXME
+ return true;
+ }
+ return false;
+ }}
+ />
+ {/if}
+{/await}
diff --git a/src/routes/(main)/category/[[id]]/+page.svelte b/src/routes/(main)/category/[[id]]/+page.svelte
index 9b3e195..4a038d9 100644
--- a/src/routes/(main)/category/[[id]]/+page.svelte
+++ b/src/routes/(main)/category/[[id]]/+page.svelte
@@ -1,9 +1,8 @@
@@ -50,68 +34,70 @@
카테고리
-{#if data.id !== "root"}
-
-{/if}
-
- {#if $info && isFileRecursive !== undefined}
-
goto(`/file/${id}?from=category`)}
- onFileRemoveClick={async ({ id }) => {
- await requestFileRemovalFromCategory(id, data.id as number);
- info = getCategoryInfo(data.id, $masterKeyStore?.get(1)?.key!); // TODO: FIXME
+{#await infoPromise then info}
+ {#if info}
+ {#if info.id !== "root"}
+
+ {/if}
+
+ goto(`/file/${id}?from=category`)}
+ onFileRemoveClick={async ({ id }) => {
+ await requestFileRemovalFromCategory(id, data.id as number);
+ infoPromise = getCategoryInfo(data.id, $masterKeyStore?.get(1)?.key!); // TODO: FIXME
+ }}
+ onSubCategoryClick={({ id }) => goto(`/category/${id}`)}
+ onSubCategoryCreateClick={() => (isCategoryCreateModalOpen = true)}
+ onSubCategoryMenuClick={(subCategory) => {
+ context.selectedCategory = subCategory;
+ isCategoryMenuBottomSheetOpen = true;
+ }}
+ />
+
+
+ {
+ if (await requestCategoryCreation(name, data.id, $masterKeyStore?.get(1)!)) {
+ infoPromise = getCategoryInfo(data.id, $masterKeyStore?.get(1)?.key!); // TODO: FIXME
+ return true;
+ }
+ return false;
}}
- onSubCategoryClick={({ id }) => goto(`/category/${id}`)}
- onSubCategoryCreateClick={() => (isCategoryCreateModalOpen = true)}
- onSubCategoryMenuClick={(subCategory) => {
- context.selectedCategory = subCategory;
- isCategoryMenuBottomSheetOpen = true;
+ />
+
+ {
+ isCategoryMenuBottomSheetOpen = false;
+ isCategoryRenameModalOpen = true;
+ }}
+ onDeleteClick={() => {
+ isCategoryMenuBottomSheetOpen = false;
+ isCategoryDeleteModalOpen = true;
+ }}
+ />
+ {
+ if (await requestCategoryRename(context.selectedCategory!, newName)) {
+ infoPromise = getCategoryInfo(data.id, $masterKeyStore?.get(1)?.key!); // TODO: FIXME
+ return true;
+ }
+ return false;
+ }}
+ />
+ {
+ if (await requestCategoryDeletion(context.selectedCategory!)) {
+ infoPromise = getCategoryInfo(data.id, $masterKeyStore?.get(1)?.key!); // TODO: FIXME
+ return true;
+ }
+ return false;
}}
/>
{/if}
-
-
- {
- if (await requestCategoryCreation(name, data.id, $masterKeyStore?.get(1)!)) {
- info = getCategoryInfo(data.id, $masterKeyStore?.get(1)?.key!); // TODO: FIXME
- return true;
- }
- return false;
- }}
-/>
-
- {
- isCategoryMenuBottomSheetOpen = false;
- isCategoryRenameModalOpen = true;
- }}
- onDeleteClick={() => {
- isCategoryMenuBottomSheetOpen = false;
- isCategoryDeleteModalOpen = true;
- }}
-/>
- {
- if (await requestCategoryRename(context.selectedCategory!, newName)) {
- info = getCategoryInfo(data.id, $masterKeyStore?.get(1)?.key!); // TODO: FIXME
- return true;
- }
- return false;
- }}
-/>
- {
- if (await requestCategoryDeletion(context.selectedCategory!)) {
- info = getCategoryInfo(data.id, $masterKeyStore?.get(1)?.key!); // TODO: FIXME
- return true;
- }
- return false;
- }}
-/>
+{/await}
diff --git a/src/routes/(main)/category/[[id]]/service.svelte.ts b/src/routes/(main)/category/[[id]]/service.svelte.ts
index 18f68fd..c415cf5 100644
--- a/src/routes/(main)/category/[[id]]/service.svelte.ts
+++ b/src/routes/(main)/category/[[id]]/service.svelte.ts
@@ -17,12 +17,17 @@ export const useContext = () => {
};
export const requestCategoryRename = async (category: SelectedCategory, newName: string) => {
- const newNameEncrypted = await encryptString(newName, category.dataKey);
+ if (!category.dataKey) {
+ // TODO: Error Handling
+ return false;
+ }
+
+ const newNameEncrypted = await encryptString(newName, category.dataKey.key);
try {
await trpc().category.rename.mutate({
id: category.id,
- dekVersion: category.dataKeyVersion,
+ dekVersion: category.dataKey.version,
name: newNameEncrypted.ciphertext,
nameIv: newNameEncrypted.iv,
});
diff --git a/src/routes/(main)/directory/[[id]]/DirectoryEntries/DirectoryEntries.svelte b/src/routes/(main)/directory/[[id]]/DirectoryEntries/DirectoryEntries.svelte
index 530cb97..5375574 100644
--- a/src/routes/(main)/directory/[[id]]/DirectoryEntries/DirectoryEntries.svelte
+++ b/src/routes/(main)/directory/[[id]]/DirectoryEntries/DirectoryEntries.svelte
@@ -39,7 +39,7 @@
details,
});
- const entries = $derived([
+ let entries = $derived([
...(showParentEntry ? ([{ type: "parent" }] as const) : []),
...sortEntries(info.subDirectories.map(toEntry("directory"))),
...sortEntries([
diff --git a/src/routes/(main)/directory/[[id]]/DirectoryEntries/File.svelte b/src/routes/(main)/directory/[[id]]/DirectoryEntries/File.svelte
index fdc225c..9a972aa 100644
--- a/src/routes/(main)/directory/[[id]]/DirectoryEntries/File.svelte
+++ b/src/routes/(main)/directory/[[id]]/DirectoryEntries/File.svelte
@@ -35,7 +35,13 @@
actionButtonIcon={IconMoreVert}
onActionButtonClick={() => action(onOpenMenuClick)}
>
- {#await thumbnailPromise then thumbnail}
+ {#await thumbnailPromise}
+
+ {:then thumbnail}
{
if (!entry.dataKey) {
// TODO: Error Handling
- console.log("hi");
return false;
}
diff --git a/src/trpc/routers/category.ts b/src/trpc/routers/category.ts
index 2be80c8..9b2567a 100644
--- a/src/trpc/routers/category.ts
+++ b/src/trpc/routers/category.ts
@@ -9,6 +9,7 @@ const categoryRouter = router({
.input(
z.object({
id: categoryIdSchema,
+ recurse: z.boolean().default(false),
}),
)
.query(async ({ ctx, input }) => {
@@ -20,7 +21,12 @@ const categoryRouter = router({
throw new TRPCError({ code: "NOT_FOUND", message: "Invalid category id" });
}
- const categories = await CategoryRepo.getAllCategoriesByParent(ctx.session.userId, input.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,
+ ]);
return {
metadata: category && {
parent: category.parentId,
@@ -30,7 +36,28 @@ const categoryRouter = router({
name: category.encName.ciphertext,
nameIv: category.encName.iv,
},
- subCategories: categories.map(({ id }) => id),
+ 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,
+ 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,
+ isRecursive: file.isRecursive,
+ })),
};
}),
@@ -113,27 +140,6 @@ const categoryRouter = router({
}
}),
- files: roleProcedure["activeClient"]
- .input(
- z.object({
- id: z.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({