10 Commits

Author SHA1 Message Date
static
ac6aaa18ca Merge pull request #20 from kmc7468/dev
v0.9.0
2026-01-18 13:30:15 +09:00
static
7b621d6e98 Merge pull request #19 from kmc7468/dev
v0.8.0
2026-01-13 00:29:14 +09:00
static
3906ec4371 Merge pull request #17 from kmc7468/dev
v0.7.0
2026-01-06 07:50:16 +09:00
static
90ac5ba4c3 Merge pull request #15 from kmc7468/dev
v0.6.0
2025-12-27 14:22:26 +09:00
static
dfffa004ac Merge pull request #13 from kmc7468/dev
v0.5.1
2025-07-12 19:56:12 +09:00
static
0cd55a413d Merge pull request #12 from kmc7468/dev
v0.5.0
2025-07-12 06:01:08 +09:00
static
361d966a59 Merge pull request #10 from kmc7468/dev
v0.4.0
2025-01-30 21:06:50 +09:00
static
aef43b8bfa Merge pull request #6 from kmc7468/dev
v0.3.0
2025-01-18 13:29:09 +09:00
static
7f128cccf6 Merge pull request #5 from kmc7468/dev
v0.2.0
2025-01-13 03:53:14 +09:00
static
a198e5f6dc Merge pull request #2 from kmc7468/dev
v0.1.0
2025-01-09 06:24:31 +09:00
14 changed files with 802 additions and 915 deletions

View File

@@ -1,7 +1,7 @@
{ {
"name": "arkvault", "name": "arkvault",
"private": true, "private": true,
"version": "0.9.1", "version": "0.9.0",
"type": "module", "type": "module",
"scripts": { "scripts": {
"dev": "vite dev", "dev": "vite dev",
@@ -16,56 +16,56 @@
"db:migrate": "kysely migrate" "db:migrate": "kysely migrate"
}, },
"devDependencies": { "devDependencies": {
"@eslint/compat": "^2.0.3", "@eslint/compat": "^2.0.1",
"@eslint/js": "^9.39.4", "@eslint/js": "^9.39.2",
"@iconify-json/material-symbols": "^1.2.60", "@iconify-json/material-symbols": "^1.2.51",
"@noble/hashes": "^2.0.1", "@noble/hashes": "^2.0.1",
"@sveltejs/adapter-node": "^5.5.4", "@sveltejs/adapter-node": "^5.5.1",
"@sveltejs/kit": "^2.53.4", "@sveltejs/kit": "^2.49.5",
"@sveltejs/vite-plugin-svelte": "^6.2.4", "@sveltejs/vite-plugin-svelte": "^6.2.4",
"@tanstack/svelte-virtual": "^3.13.21", "@tanstack/svelte-virtual": "^3.13.18",
"@trpc/client": "^11.12.0", "@trpc/client": "^11.8.1",
"@types/file-saver": "^2.0.7", "@types/file-saver": "^2.0.7",
"@types/ms": "^0.7.34", "@types/ms": "^0.7.34",
"@types/node-schedule": "^2.1.8", "@types/node-schedule": "^2.1.8",
"@types/pg": "^8.18.0", "@types/pg": "^8.16.0",
"autoprefixer": "^10.4.27", "autoprefixer": "^10.4.23",
"axios": "^1.13.6", "axios": "^1.13.2",
"dexie": "^4.3.0", "dexie": "^4.2.1",
"es-hangul": "^2.3.8", "es-hangul": "^2.3.8",
"eslint": "^9.39.4", "eslint": "^9.39.2",
"eslint-config-prettier": "^10.1.8", "eslint-config-prettier": "^10.1.8",
"eslint-plugin-svelte": "^3.15.0", "eslint-plugin-svelte": "^3.14.0",
"eslint-plugin-tailwindcss": "^3.18.2", "eslint-plugin-tailwindcss": "^3.18.2",
"exifreader": "^4.36.2", "exifreader": "^4.36.0",
"file-saver": "^2.0.5", "file-saver": "^2.0.5",
"globals": "^17.4.0", "globals": "^17.0.0",
"heic2any": "^0.0.4", "heic2any": "^0.0.4",
"kysely-ctl": "^0.20.0", "kysely-ctl": "^0.20.0",
"lru-cache": "^11.2.6", "lru-cache": "^11.2.4",
"mime": "^4.1.0", "mime": "^4.1.0",
"p-limit": "^7.3.0", "p-limit": "^7.2.0",
"prettier": "^3.8.1", "prettier": "^3.8.0",
"prettier-plugin-svelte": "^3.5.1", "prettier-plugin-svelte": "^3.4.1",
"prettier-plugin-tailwindcss": "^0.7.2", "prettier-plugin-tailwindcss": "^0.7.2",
"svelte": "^5.53.7", "svelte": "^5.46.4",
"svelte-check": "^4.4.5", "svelte-check": "^4.3.5",
"tailwindcss": "^3.4.19", "tailwindcss": "^3.4.19",
"typescript": "^5.9.3", "typescript": "^5.9.3",
"typescript-eslint": "^8.56.1", "typescript-eslint": "^8.53.0",
"unplugin-icons": "^23.0.1", "unplugin-icons": "^23.0.1",
"vite": "^7.3.1" "vite": "^7.3.1"
}, },
"dependencies": { "dependencies": {
"@trpc/server": "^11.12.0", "@trpc/server": "^11.8.1",
"argon2": "^0.44.0", "argon2": "^0.44.0",
"kysely": "^0.28.11", "kysely": "^0.28.9",
"ms": "^2.1.3", "ms": "^2.1.3",
"node-schedule": "^2.1.1", "node-schedule": "^2.1.1",
"pg": "^8.20.0", "pg": "^8.17.1",
"superjson": "^2.2.6", "superjson": "^2.2.6",
"uuid": "^13.0.0", "uuid": "^13.0.0",
"zod": "^4.3.6" "zod": "^4.3.5"
}, },
"engines": { "engines": {
"node": "^22.0.0", "node": "^22.0.0",

1386
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@@ -34,7 +34,7 @@ export class FilesystemCache<K, V extends object> {
} }
(state.value (state.value
? Promise.resolve($state.snapshot(state.value) as V) ? Promise.resolve(state.value)
: this.options.fetchFromIndexedDB(key).then((loadedInfo) => { : this.options.fetchFromIndexedDB(key).then((loadedInfo) => {
if (loadedInfo) { if (loadedInfo) {
state.value = loadedInfo; state.value = loadedInfo;

View File

@@ -74,6 +74,28 @@ export const getAllDirectoriesByParent = async (userId: number, parentId: Direct
return directories.map(toDirectory); return directories.map(toDirectory);
}; };
export const getAllRecursiveDirectoriesByParent = async (userId: number, parentId: DirectoryId) => {
const directories = await db
.withRecursive("directory_tree", (db) =>
db
.selectFrom("directory")
.selectAll()
.$if(parentId === "root", (qb) => qb.where("parent_id", "is", null))
.$if(parentId !== "root", (qb) => qb.where("parent_id", "=", parentId as number))
.where("user_id", "=", userId)
.unionAll((db) =>
db
.selectFrom("directory")
.innerJoin("directory_tree", "directory.parent_id", "directory_tree.id")
.selectAll("directory"),
),
)
.selectFrom("directory_tree")
.selectAll()
.execute();
return directories.map(toDirectory);
};
export const getAllFavoriteDirectories = async (userId: number) => { export const getAllFavoriteDirectories = async (userId: number) => {
const directories = await db const directories = await db
.selectFrom("directory") .selectFrom("directory")
@@ -95,61 +117,6 @@ export const getDirectory = async (userId: number, directoryId: number) => {
return directory ? toDirectory(directory) : null; return directory ? toDirectory(directory) : null;
}; };
export const searchDirectories = async (
userId: number,
filters: {
parentId: DirectoryId;
inFavorites: boolean;
},
) => {
const directories = await db
.withRecursive("directory_tree", (db) =>
db
.selectFrom("directory")
.select("id")
.where("user_id", "=", userId)
.$if(filters.parentId === "root", (qb) => qb.where((eb) => eb.lit(false))) // directory_tree will be empty if parentId is "root"
.$if(filters.parentId !== "root", (qb) => qb.where("id", "=", filters.parentId as number))
.unionAll(
db
.selectFrom("directory as d")
.innerJoin("directory_tree as dt", "d.parent_id", "dt.id")
.select("d.id"),
),
)
.withRecursive("favorite_directory_tree", (db) =>
db
.selectFrom("directory")
.select("id")
.where("user_id", "=", userId)
.$if(!filters.inFavorites, (qb) => qb.where((eb) => eb.lit(false))) // favorite_directory_tree will be empty if inFavorites is false
.$if(filters.inFavorites, (qb) => qb.where("is_favorite", "=", true))
.unionAll((db) =>
db
.selectFrom("directory as d")
.innerJoin("favorite_directory_tree as dt", "d.parent_id", "dt.id")
.select("d.id"),
),
)
.selectFrom("directory")
.selectAll()
.where("user_id", "=", userId)
.$if(filters.parentId !== "root", (qb) =>
qb.where((eb) =>
eb.exists(eb.selectFrom("directory_tree as dt").whereRef("dt.id", "=", "parent_id")),
),
)
.$if(filters.inFavorites, (qb) =>
qb.where((eb) =>
eb.exists(
eb.selectFrom("favorite_directory_tree as dt").whereRef("dt.id", "=", "directory.id"),
),
),
)
.execute();
return directories.map(toDirectory);
};
export const setDirectoryEncName = async ( export const setDirectoryEncName = async (
userId: number, userId: number,
directoryId: number, directoryId: number,

View File

@@ -248,7 +248,6 @@ export const searchFiles = async (
userId: number, userId: number,
filters: { filters: {
parentId: DirectoryId; parentId: DirectoryId;
inFavorites: boolean;
includeCategoryIds: number[]; includeCategoryIds: number[];
excludeCategoryIds: number[]; excludeCategoryIds: number[];
}, },
@@ -259,7 +258,7 @@ export const searchFiles = async (
.selectFrom("directory") .selectFrom("directory")
.select("id") .select("id")
.where("user_id", "=", userId) .where("user_id", "=", userId)
.$if(filters.parentId === "root", (qb) => qb.where((eb) => eb.lit(false))) // directory_tree will be empty if parentId is "root" .where((eb) => eb.val(filters.parentId !== "root")) // directory_tree will be empty if parentId is "root"
.$if(filters.parentId !== "root", (qb) => qb.where("id", "=", filters.parentId as number)) .$if(filters.parentId !== "root", (qb) => qb.where("id", "=", filters.parentId as number))
.unionAll( .unionAll(
db db
@@ -268,20 +267,6 @@ export const searchFiles = async (
.select("d.id"), .select("d.id"),
), ),
) )
.withRecursive("favorite_directory_tree", (db) =>
db
.selectFrom("directory")
.select("id")
.where("user_id", "=", userId)
.$if(!filters.inFavorites, (qb) => qb.where((eb) => eb.lit(false))) // favorite_directory_tree will be empty if inFavorites is false
.$if(filters.inFavorites, (qb) => qb.where("is_favorite", "=", true))
.unionAll((db) =>
db
.selectFrom("directory as d")
.innerJoin("favorite_directory_tree as dt", "d.parent_id", "dt.id")
.select("d.id"),
),
)
.withRecursive("include_category_tree", (db) => .withRecursive("include_category_tree", (db) =>
db db
.selectFrom("category") .selectFrom("category")
@@ -310,30 +295,18 @@ export const searchFiles = async (
) )
.selectFrom("file") .selectFrom("file")
.selectAll("file") .selectAll("file")
.where("user_id", "=", userId) .$if(filters.parentId === "root", (qb) => qb.where("user_id", "=", userId)) // directory_tree isn't used if parentId is "root"
.$if(filters.parentId !== "root", (qb) => .$if(filters.parentId !== "root", (qb) =>
qb.where((eb) => qb.where("parent_id", "in", (eb) => eb.selectFrom("directory_tree").select("id")),
eb.exists(eb.selectFrom("directory_tree as dt").whereRef("dt.id", "=", "file.parent_id")),
),
) )
.$if(filters.inFavorites, (qb) => .where((eb) =>
qb.where((eb) =>
eb.or([
eb("is_favorite", "=", true),
eb.exists(
eb.selectFrom("favorite_directory_tree as dt").whereRef("dt.id", "=", "file.parent_id"),
),
]),
),
)
.$if(filters.excludeCategoryIds.length > 0, (qb) =>
qb.where((eb) =>
eb.not( eb.not(
eb.exists( eb.exists(
eb eb
.selectFrom("file_category") .selectFrom("file_category")
.innerJoin("exclude_category_tree", "category_id", "exclude_category_tree.id") .whereRef("file_id", "=", "file.id")
.whereRef("file_id", "=", "file.id"), .where("category_id", "in", (eb) =>
eb.selectFrom("exclude_category_tree").select("id"),
), ),
), ),
), ),

View File

@@ -46,7 +46,7 @@
{#if isOpen && (directoryId || downloadUrl || fileBlob)} {#if isOpen && (directoryId || downloadUrl || fileBlob)}
<div <div
class="absolute right-2 top-full z-20 min-w-44 space-y-1 rounded-lg bg-white px-1 py-2 shadow-2xl" class="absolute right-2 top-full z-20 min-w-40 space-y-1 rounded-lg bg-white px-1 py-2 shadow-2xl"
transition:fly={{ y: -8, duration: 200 }} transition:fly={{ y: -8, duration: 200 }}
> >
<p class="px-3 pt-2 text-sm font-semibold text-gray-600">더보기</p> <p class="px-3 pt-2 text-sm font-semibold text-gray-600">더보기</p>
@@ -57,9 +57,7 @@
onclick: () => void, onclick: () => void,
)} )}
<button {onclick} class="rounded-xl active:bg-gray-100"> <button {onclick} class="rounded-xl active:bg-gray-100">
<div <div class="flex items-center gap-x-3 px-3 py-2 text-gray-700 transition active:scale-95">
class="flex items-center gap-x-3 px-3 py-2 text-lg text-gray-700 transition active:scale-95"
>
<Icon /> <Icon />
<p class="font-medium">{text}</p> <p class="font-medium">{text}</p>
</div> </div>

View File

@@ -37,8 +37,8 @@
includeImages: false, includeImages: false,
includeVideos: false, includeVideos: false,
includeDirectories: false, includeDirectories: false,
searchInDirectory: false,
searchInFavorites: false, searchInFavorites: false,
searchInDirectory: false,
categories: [], categories: [],
}); });
let hasCategoryFilter = $derived(filters.categories.length > 0); let hasCategoryFilter = $derived(filters.categories.length > 0);
@@ -47,6 +47,7 @@
filters.includeImages || filters.includeImages ||
filters.includeVideos || filters.includeVideos ||
filters.includeDirectories || filters.includeDirectories ||
filters.searchInFavorites ||
filters.name.trim().length > 0, filters.name.trim().length > 0,
); );
@@ -83,7 +84,9 @@
return sortEntries( return sortEntries(
[...directories, ...files].filter( [...directories, ...files].filter(
(entry) => !nameFilter || searchString(entry.name, nameFilter), (entry) =>
(!nameFilter || searchString(entry.name, nameFilter)) &&
(!filters.searchInFavorites || entry.isFavorite),
), ),
); );
}); });
@@ -142,7 +145,6 @@
// Svelte sucks // Svelte sucks
hasAnyFilter; hasAnyFilter;
filters.searchInDirectory; filters.searchInDirectory;
filters.searchInFavorites;
filters.categories.length; filters.categories.length;
if (untrack(() => isRestoredFromSnapshot)) { if (untrack(() => isRestoredFromSnapshot)) {
@@ -154,7 +156,6 @@
requestSearch( requestSearch(
{ {
ancestorId: filters.searchInDirectory ? data.directoryId! : "root", ancestorId: filters.searchInDirectory ? data.directoryId! : "root",
inFavorites: filters.searchInFavorites,
categories: filters.categories, categories: filters.categories,
}, },
$masterKeyStore?.get(1)?.key!, $masterKeyStore?.get(1)?.key!,
@@ -215,12 +216,7 @@
</div> </div>
{#if hasAnyFilter} {#if hasAnyFilter}
<div class="flex flex-grow flex-col space-y-2 bg-white p-4"> <div class="flex flex-grow flex-col space-y-2 bg-white p-4">
<p class="text-lg font-bold text-gray-800"> <p class="text-lg font-bold text-gray-800">검색 결과</p>
검색 결과
{#if result.length > 0}
{" "}({result.length}개)
{/if}
</p>
{#if result.length > 0} {#if result.length > 0}
<RowVirtualizer <RowVirtualizer
count={result.length} count={result.length}

View File

@@ -1,14 +1,17 @@
<script lang="ts"> <script lang="ts">
import type { ClassValue } from "svelte/elements";
import IconArrowBack from "~icons/material-symbols/arrow-back"; import IconArrowBack from "~icons/material-symbols/arrow-back";
interface Props { interface Props {
class?: ClassValue;
value: string; value: string;
} }
let { value = $bindable() }: Props = $props(); let { class: className, value = $bindable() }: Props = $props();
</script> </script>
<div class={"sticky top-0 z-10 flex items-center gap-x-2 px-2 py-3 backdrop-blur-2xl"}> <div class={["sticky top-0 z-10 flex items-center gap-x-2 px-2 py-3 backdrop-blur-2xl", className]}>
<button <button
onclick={() => history.back()} onclick={() => history.back()}
class="w-[2.3rem] flex-shrink-0 rounded-full p-1 active:bg-black active:bg-opacity-[0.04]" class="w-[2.3rem] flex-shrink-0 rounded-full p-1 active:bg-black active:bg-opacity-[0.04]"
@@ -19,6 +22,6 @@
bind:value bind:value
type="text" type="text"
placeholder="검색" placeholder="검색"
class="mr-1 h-[2.3rem] min-w-0 flex-grow rounded-lg bg-gray-100 px-3 py-1.5 placeholder-gray-600 focus:outline-none" class="mr-1 h-[2.3rem] flex-grow rounded-lg bg-gray-100 px-3 py-1.5 placeholder-gray-600 focus:outline-none"
/> />
</div> </div>

View File

@@ -10,7 +10,6 @@ import { trpc } from "$trpc/client";
export interface SearchFilter { export interface SearchFilter {
ancestorId: DirectoryId; ancestorId: DirectoryId;
inFavorites: boolean;
categories: { info: LocalCategoryInfo; type: "include" | "exclude" }[]; categories: { info: LocalCategoryInfo; type: "include" | "exclude" }[];
} }
@@ -22,7 +21,6 @@ export interface SearchResult {
export const requestSearch = async (filter: SearchFilter, masterKey: CryptoKey) => { export const requestSearch = async (filter: SearchFilter, masterKey: CryptoKey) => {
const { directories: directoriesRaw, files: filesRaw } = await trpc().search.search.query({ const { directories: directoriesRaw, files: filesRaw } = await trpc().search.search.query({
ancestor: filter.ancestorId, ancestor: filter.ancestorId,
inFavorites: filter.inFavorites,
includeCategories: filter.categories includeCategories: filter.categories
.filter(({ type }) => type === "include") .filter(({ type }) => type === "include")
.map(({ info }) => info.id), .map(({ info }) => info.id),

View File

@@ -3,7 +3,6 @@
import { DirectoryEntryLabel } from "$lib/components/molecules"; import { DirectoryEntryLabel } from "$lib/components/molecules";
import { getFileThumbnail } from "$lib/modules/file"; import { getFileThumbnail } from "$lib/modules/file";
import type { CategoryFileInfo } from "$lib/modules/filesystem"; import type { CategoryFileInfo } from "$lib/modules/filesystem";
import { formatDateTime } from "$lib/utils";
import type { SelectedFile } from "./service.svelte"; import type { SelectedFile } from "./service.svelte";
import IconClose from "~icons/material-symbols/close"; import IconClose from "~icons/material-symbols/close";
@@ -20,7 +19,7 @@
</script> </script>
<ActionEntryButton <ActionEntryButton
class="h-14" class="h-12"
onclick={() => onclick(info)} onclick={() => onclick(info)}
actionButtonIcon={onRemoveClick && IconClose} actionButtonIcon={onRemoveClick && IconClose}
onActionButtonClick={() => onRemoveClick?.(info)} onActionButtonClick={() => onRemoveClick?.(info)}
@@ -29,7 +28,6 @@
type="file" type="file"
thumbnail={$thumbnail} thumbnail={$thumbnail}
name={info.name} name={info.name}
subtext={formatDateTime(info.createdAt ?? info.lastModifiedAt)}
isFavorite={info.isFavorite} isFavorite={info.isFavorite}
/> />
</ActionEntryButton> </ActionEntryButton>

View File

@@ -103,7 +103,7 @@
</script> </script>
<svelte:head> <svelte:head>
<title>파일</title> <title>파일</title>
</svelte:head> </svelte:head>
<input bind:this={fileInput} onchange={uploadFile} type="file" multiple class="hidden" /> <input bind:this={fileInput} onchange={uploadFile} type="file" multiple class="hidden" />

View File

@@ -44,17 +44,30 @@
<title>즐겨찾기</title> <title>즐겨찾기</title>
</svelte:head> </svelte:head>
<div class="flex h-full flex-col"> <TopBar title="즐겨찾기" showBackButton={false}>
<TopBar title="즐겨찾기" showBackButton={false}>
<button <button
onclick={() => goto("/search?from=favorites")} onclick={() => goto("/search?from=favorites")}
class="w-full rounded-full p-1 text-2xl active:bg-black active:bg-opacity-[0.04]" class="w-full rounded-full p-1 text-2xl active:bg-black active:bg-opacity-[0.04]"
> >
<IconSearch /> <IconSearch />
</button> </button>
</TopBar> </TopBar>
<div class="flex flex-grow flex-col px-4 pb-4"> <div class="flex h-full flex-col p-4 !pt-0">
{#if entries.length > 0} {#if isLoading}
<div class="flex flex-grow items-center justify-center">
<p class="text-gray-500">
{#if data.favorites.files.length === 0 && data.favorites.directories.length === 0}
즐겨찾기한 항목이 없어요.
{:else}
즐겨찾기 목록을 불러오고 있어요.
{/if}
</p>
</div>
{:else if entries.length === 0}
<div class="flex flex-grow items-center justify-center">
<p class="text-gray-500">즐겨찾기한 항목이 없어요.</p>
</div>
{:else}
<RowVirtualizer <RowVirtualizer
count={entries.length} count={entries.length}
getItemKey={(index) => `${entries[index]!.type}-${entries[index]!.details.id}`} getItemKey={(index) => `${entries[index]!.type}-${entries[index]!.details.id}`}
@@ -78,16 +91,5 @@
{/if} {/if}
{/snippet} {/snippet}
</RowVirtualizer> </RowVirtualizer>
{:else}
<div class="flex flex-grow items-center justify-center">
<p class="text-gray-500">
{#if isLoading}
즐겨찾기 목록을 불러오고 있어요.
{:else}
즐겨찾기한 항목이 없어요.
{/if} {/if}
</p>
</div>
{/if}
</div>
</div> </div>

View File

@@ -3,7 +3,6 @@
import { DirectoryEntryLabel } from "$lib/components/molecules"; import { DirectoryEntryLabel } from "$lib/components/molecules";
import { getFileThumbnail } from "$lib/modules/file"; import { getFileThumbnail } from "$lib/modules/file";
import type { SummarizedFileInfo } from "$lib/modules/filesystem"; import type { SummarizedFileInfo } from "$lib/modules/filesystem";
import { formatDateTime } from "$lib/utils";
import IconClose from "~icons/material-symbols/close"; import IconClose from "~icons/material-symbols/close";
@@ -24,11 +23,5 @@
actionButtonIcon={IconClose} actionButtonIcon={IconClose}
onActionButtonClick={onRemoveClick} onActionButtonClick={onRemoveClick}
> >
<DirectoryEntryLabel <DirectoryEntryLabel type="file" thumbnail={$thumbnail} name={info.name} isFavorite />
type="file"
thumbnail={$thumbnail}
name={info.name}
subtext={formatDateTime(info.createdAt ?? info.lastModifiedAt)}
isFavorite
/>
</ActionEntryButton> </ActionEntryButton>

View File

@@ -8,7 +8,6 @@ const searchRouter = router({
.input( .input(
z.object({ z.object({
ancestor: DirectoryIdSchema.default("root"), ancestor: DirectoryIdSchema.default("root"),
inFavorites: z.boolean().default(false),
includeCategories: z.number().positive().array().default([]), includeCategories: z.number().positive().array().default([]),
excludeCategories: z.number().positive().array().default([]), excludeCategories: z.number().positive().array().default([]),
}), }),
@@ -16,14 +15,10 @@ const searchRouter = router({
.query(async ({ ctx, input }) => { .query(async ({ ctx, input }) => {
const [directories, files] = await Promise.all([ const [directories, files] = await Promise.all([
input.includeCategories.length === 0 && input.excludeCategories.length === 0 input.includeCategories.length === 0 && input.excludeCategories.length === 0
? DirectoryRepo.searchDirectories(ctx.session.userId, { ? DirectoryRepo.getAllRecursiveDirectoriesByParent(ctx.session.userId, input.ancestor)
parentId: input.ancestor,
inFavorites: input.inFavorites,
})
: [], : [],
FileRepo.searchFiles(ctx.session.userId, { FileRepo.searchFiles(ctx.session.userId, {
parentId: input.ancestor, parentId: input.ancestor,
inFavorites: input.inFavorites,
includeCategoryIds: input.includeCategories, includeCategoryIds: input.includeCategories,
excludeCategoryIds: input.excludeCategories, excludeCategoryIds: input.excludeCategories,
}), }),