mirror of
https://github.com/kmc7468/arkvault.git
synced 2026-02-04 08:06:56 +00:00
디렉터리 관련 TanStack Query 코드 리팩토링
This commit is contained in:
@@ -1,11 +1,6 @@
|
|||||||
import { get, writable, type Writable } from "svelte/store";
|
import { get, writable, type Writable } from "svelte/store";
|
||||||
import { callGetApi } from "$lib/hooks";
|
import { callGetApi } from "$lib/hooks";
|
||||||
import {
|
import {
|
||||||
getDirectoryInfos as getDirectoryInfosFromIndexedDB,
|
|
||||||
getDirectoryInfo as getDirectoryInfoFromIndexedDB,
|
|
||||||
storeDirectoryInfo,
|
|
||||||
deleteDirectoryInfo,
|
|
||||||
getFileInfos as getFileInfosFromIndexedDB,
|
|
||||||
getFileInfo as getFileInfoFromIndexedDB,
|
getFileInfo as getFileInfoFromIndexedDB,
|
||||||
storeFileInfo,
|
storeFileInfo,
|
||||||
deleteFileInfo,
|
deleteFileInfo,
|
||||||
@@ -14,35 +9,15 @@ import {
|
|||||||
storeCategoryInfo,
|
storeCategoryInfo,
|
||||||
updateCategoryInfo as updateCategoryInfoInIndexedDB,
|
updateCategoryInfo as updateCategoryInfoInIndexedDB,
|
||||||
deleteCategoryInfo,
|
deleteCategoryInfo,
|
||||||
type DirectoryId,
|
|
||||||
type CategoryId,
|
type CategoryId,
|
||||||
} from "$lib/indexedDB";
|
} from "$lib/indexedDB";
|
||||||
import { unwrapDataKey, decryptString } from "$lib/modules/crypto";
|
import { unwrapDataKey, decryptString } from "$lib/modules/crypto";
|
||||||
import type {
|
import type {
|
||||||
CategoryInfoResponse,
|
CategoryInfoResponse,
|
||||||
CategoryFileListResponse,
|
CategoryFileListResponse,
|
||||||
DirectoryInfoResponse,
|
|
||||||
FileInfoResponse,
|
FileInfoResponse,
|
||||||
} from "$lib/server/schemas";
|
} from "$lib/server/schemas";
|
||||||
|
|
||||||
export type DirectoryInfo =
|
|
||||||
| {
|
|
||||||
id: "root";
|
|
||||||
dataKey?: undefined;
|
|
||||||
dataKeyVersion?: undefined;
|
|
||||||
name?: undefined;
|
|
||||||
subDirectoryIds: number[];
|
|
||||||
fileIds: number[];
|
|
||||||
}
|
|
||||||
| {
|
|
||||||
id: number;
|
|
||||||
dataKey?: CryptoKey;
|
|
||||||
dataKeyVersion?: Date;
|
|
||||||
name: string;
|
|
||||||
subDirectoryIds: number[];
|
|
||||||
fileIds: number[];
|
|
||||||
};
|
|
||||||
|
|
||||||
export interface FileInfo {
|
export interface FileInfo {
|
||||||
id: number;
|
id: number;
|
||||||
dataKey?: CryptoKey;
|
dataKey?: CryptoKey;
|
||||||
@@ -75,92 +50,9 @@ export type CategoryInfo =
|
|||||||
isFileRecursive: boolean;
|
isFileRecursive: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
const directoryInfoStore = new Map<DirectoryId, Writable<DirectoryInfo | null>>();
|
|
||||||
const fileInfoStore = new Map<number, Writable<FileInfo | null>>();
|
const fileInfoStore = new Map<number, Writable<FileInfo | null>>();
|
||||||
const categoryInfoStore = new Map<CategoryId, Writable<CategoryInfo | null>>();
|
const categoryInfoStore = new Map<CategoryId, Writable<CategoryInfo | null>>();
|
||||||
|
|
||||||
const fetchDirectoryInfoFromIndexedDB = async (
|
|
||||||
id: DirectoryId,
|
|
||||||
info: Writable<DirectoryInfo | null>,
|
|
||||||
) => {
|
|
||||||
if (get(info)) return;
|
|
||||||
|
|
||||||
const [directory, subDirectories, files] = await Promise.all([
|
|
||||||
id !== "root" ? getDirectoryInfoFromIndexedDB(id) : undefined,
|
|
||||||
getDirectoryInfosFromIndexedDB(id),
|
|
||||||
getFileInfosFromIndexedDB(id),
|
|
||||||
]);
|
|
||||||
const subDirectoryIds = subDirectories.map(({ id }) => id);
|
|
||||||
const fileIds = files.map(({ id }) => id);
|
|
||||||
|
|
||||||
if (id === "root") {
|
|
||||||
info.set({ id, subDirectoryIds, fileIds });
|
|
||||||
} else {
|
|
||||||
if (!directory) return;
|
|
||||||
info.set({ id, name: directory.name, subDirectoryIds, fileIds });
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const fetchDirectoryInfoFromServer = async (
|
|
||||||
id: DirectoryId,
|
|
||||||
info: Writable<DirectoryInfo | null>,
|
|
||||||
masterKey: CryptoKey,
|
|
||||||
) => {
|
|
||||||
const res = await callGetApi(`/api/directory/${id}`);
|
|
||||||
if (res.status === 404) {
|
|
||||||
info.set(null);
|
|
||||||
await deleteDirectoryInfo(id as number);
|
|
||||||
return;
|
|
||||||
} else if (!res.ok) {
|
|
||||||
throw new Error("Failed to fetch directory information");
|
|
||||||
}
|
|
||||||
|
|
||||||
const {
|
|
||||||
metadata,
|
|
||||||
subDirectories: subDirectoryIds,
|
|
||||||
files: fileIds,
|
|
||||||
}: DirectoryInfoResponse = await res.json();
|
|
||||||
|
|
||||||
if (id === "root") {
|
|
||||||
info.set({ id, subDirectoryIds, fileIds });
|
|
||||||
} else {
|
|
||||||
const { dataKey } = await unwrapDataKey(metadata!.dek, masterKey);
|
|
||||||
const name = await decryptString(metadata!.name, metadata!.nameIv, dataKey);
|
|
||||||
|
|
||||||
info.set({
|
|
||||||
id,
|
|
||||||
dataKey,
|
|
||||||
dataKeyVersion: new Date(metadata!.dekVersion),
|
|
||||||
name,
|
|
||||||
subDirectoryIds,
|
|
||||||
fileIds,
|
|
||||||
});
|
|
||||||
await storeDirectoryInfo({ id, parentId: metadata!.parent, name });
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const fetchDirectoryInfo = async (
|
|
||||||
id: DirectoryId,
|
|
||||||
info: Writable<DirectoryInfo | null>,
|
|
||||||
masterKey: CryptoKey,
|
|
||||||
) => {
|
|
||||||
await fetchDirectoryInfoFromIndexedDB(id, info);
|
|
||||||
await fetchDirectoryInfoFromServer(id, info, masterKey);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const getDirectoryInfo = (id: DirectoryId, masterKey: CryptoKey) => {
|
|
||||||
// TODO: MEK rotation
|
|
||||||
|
|
||||||
let info = directoryInfoStore.get(id);
|
|
||||||
if (!info) {
|
|
||||||
info = writable(null);
|
|
||||||
directoryInfoStore.set(id, info);
|
|
||||||
}
|
|
||||||
|
|
||||||
fetchDirectoryInfo(id, info, masterKey); // Intended
|
|
||||||
return info;
|
|
||||||
};
|
|
||||||
|
|
||||||
const fetchFileInfoFromIndexedDB = async (id: number, info: Writable<FileInfo | null>) => {
|
const fetchFileInfoFromIndexedDB = async (id: number, info: Writable<FileInfo | null>) => {
|
||||||
if (get(info)) return;
|
if (get(info)) return;
|
||||||
|
|
||||||
|
|||||||
@@ -26,15 +26,33 @@ import {
|
|||||||
encryptString,
|
encryptString,
|
||||||
decryptString,
|
decryptString,
|
||||||
} from "$lib/modules/crypto";
|
} from "$lib/modules/crypto";
|
||||||
import type { DirectoryInfo } from "$lib/modules/filesystem";
|
|
||||||
import type {
|
import type {
|
||||||
|
DirectoryInfoResponse,
|
||||||
|
DirectoryDeleteResponse,
|
||||||
|
DirectoryRenameRequest,
|
||||||
DirectoryCreateRequest,
|
DirectoryCreateRequest,
|
||||||
DirectoryCreateResponse,
|
DirectoryCreateResponse,
|
||||||
DirectoryInfoResponse,
|
|
||||||
DirectoryRenameRequest,
|
|
||||||
} from "$lib/server/schemas";
|
} from "$lib/server/schemas";
|
||||||
import type { MasterKey } from "$lib/stores";
|
import type { MasterKey } from "$lib/stores";
|
||||||
|
|
||||||
|
export type DirectoryInfo =
|
||||||
|
| {
|
||||||
|
id: "root";
|
||||||
|
dataKey?: undefined;
|
||||||
|
dataKeyVersion?: undefined;
|
||||||
|
name?: undefined;
|
||||||
|
subDirectoryIds: number[];
|
||||||
|
fileIds: number[];
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
id: number;
|
||||||
|
dataKey?: CryptoKey;
|
||||||
|
dataKeyVersion?: Date;
|
||||||
|
name: string;
|
||||||
|
subDirectoryIds: number[];
|
||||||
|
fileIds: number[];
|
||||||
|
};
|
||||||
|
|
||||||
const initializedDirectoryIds = new Set<DirectoryId>();
|
const initializedDirectoryIds = new Set<DirectoryId>();
|
||||||
let temporaryIdCounter = -1;
|
let temporaryIdCounter = -1;
|
||||||
|
|
||||||
@@ -99,15 +117,10 @@ export const getDirectoryInfo = (id: DirectoryId, masterKey: CryptoKey) => {
|
|||||||
|
|
||||||
export type DirectoryInfoStore = ReturnType<typeof getDirectoryInfo>;
|
export type DirectoryInfoStore = ReturnType<typeof getDirectoryInfo>;
|
||||||
|
|
||||||
export const useDirectoryCreate = (parentId: DirectoryId) => {
|
export const useDirectoryCreation = (parentId: DirectoryId, masterKey: MasterKey) => {
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
return createMutation<
|
return createMutation<void, Error, { name: string }, { tempId: number }>({
|
||||||
{ id: number; dataKey: CryptoKey; dataKeyVersion: Date },
|
mutationFn: async ({ name }) => {
|
||||||
Error,
|
|
||||||
{ name: string; masterKey: MasterKey },
|
|
||||||
{ prevParentInfo: DirectoryInfo | undefined; tempId: number }
|
|
||||||
>({
|
|
||||||
mutationFn: async ({ name, masterKey }) => {
|
|
||||||
const { dataKey, dataKeyVersion } = await generateDataKey();
|
const { dataKey, dataKeyVersion } = await generateDataKey();
|
||||||
const nameEncrypted = await encryptString(name, dataKey);
|
const nameEncrypted = await encryptString(name, dataKey);
|
||||||
|
|
||||||
@@ -119,30 +132,9 @@ export const useDirectoryCreate = (parentId: DirectoryId) => {
|
|||||||
name: nameEncrypted.ciphertext,
|
name: nameEncrypted.ciphertext,
|
||||||
nameIv: nameEncrypted.iv,
|
nameIv: nameEncrypted.iv,
|
||||||
});
|
});
|
||||||
|
if (!res.ok) throw new Error("Failed to create directory");
|
||||||
|
|
||||||
const { directory: id }: DirectoryCreateResponse = await res.json();
|
const { directory: id }: DirectoryCreateResponse = await res.json();
|
||||||
return { id, dataKey, dataKeyVersion };
|
|
||||||
},
|
|
||||||
onMutate: async ({ name }) => {
|
|
||||||
await queryClient.cancelQueries({ queryKey: ["directory", parentId] });
|
|
||||||
|
|
||||||
const prevParentInfo = queryClient.getQueryData<DirectoryInfo>(["directory", parentId]);
|
|
||||||
const tempId = temporaryIdCounter--;
|
|
||||||
if (prevParentInfo) {
|
|
||||||
queryClient.setQueryData<DirectoryInfo>(["directory", parentId], {
|
|
||||||
...prevParentInfo,
|
|
||||||
subDirectoryIds: [...prevParentInfo.subDirectoryIds, tempId],
|
|
||||||
});
|
|
||||||
queryClient.setQueryData<DirectoryInfo>(["directory", tempId], {
|
|
||||||
id: tempId,
|
|
||||||
name,
|
|
||||||
subDirectoryIds: [],
|
|
||||||
fileIds: [],
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return { prevParentInfo, tempId };
|
|
||||||
},
|
|
||||||
onSuccess: async ({ id, dataKey, dataKeyVersion }, { name }) => {
|
|
||||||
queryClient.setQueryData<DirectoryInfo>(["directory", id], {
|
queryClient.setQueryData<DirectoryInfo>(["directory", id], {
|
||||||
id,
|
id,
|
||||||
name,
|
name,
|
||||||
@@ -153,13 +145,38 @@ export const useDirectoryCreate = (parentId: DirectoryId) => {
|
|||||||
});
|
});
|
||||||
await storeDirectoryInfo({ id, parentId, name });
|
await storeDirectoryInfo({ id, parentId, name });
|
||||||
},
|
},
|
||||||
onError: (error, { name }, context) => {
|
onMutate: async ({ name }) => {
|
||||||
if (context?.prevParentInfo) {
|
const tempId = temporaryIdCounter--;
|
||||||
queryClient.setQueryData<DirectoryInfo>(["directory", parentId], context.prevParentInfo);
|
queryClient.setQueryData<DirectoryInfo>(["directory", tempId], {
|
||||||
}
|
id: tempId,
|
||||||
console.error(`Failed to create directory "${name}" in parent ${parentId}:`, error);
|
name,
|
||||||
|
subDirectoryIds: [],
|
||||||
|
fileIds: [],
|
||||||
|
});
|
||||||
|
|
||||||
|
await queryClient.cancelQueries({ queryKey: ["directory", parentId] });
|
||||||
|
queryClient.setQueryData<DirectoryInfo>(["directory", parentId], (prevParentInfo) => {
|
||||||
|
if (!prevParentInfo) return undefined;
|
||||||
|
return {
|
||||||
|
...prevParentInfo,
|
||||||
|
subDirectoryIds: [...prevParentInfo.subDirectoryIds, tempId],
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
return { tempId };
|
||||||
},
|
},
|
||||||
onSettled: (id) => {
|
onError: (_error, _variables, context) => {
|
||||||
|
if (context) {
|
||||||
|
queryClient.setQueryData<DirectoryInfo>(["directory", parentId], (prevParentInfo) => {
|
||||||
|
if (!prevParentInfo) return undefined;
|
||||||
|
return {
|
||||||
|
...prevParentInfo,
|
||||||
|
subDirectoryIds: prevParentInfo.subDirectoryIds.filter((id) => id !== context.tempId),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onSettled: () => {
|
||||||
queryClient.invalidateQueries({ queryKey: ["directory", parentId] });
|
queryClient.invalidateQueries({ queryKey: ["directory", parentId] });
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@@ -176,15 +193,18 @@ export const useDirectoryRename = () => {
|
|||||||
dataKeyVersion: Date;
|
dataKeyVersion: Date;
|
||||||
newName: string;
|
newName: string;
|
||||||
},
|
},
|
||||||
{ prevInfo: (DirectoryInfo & { id: number }) | undefined }
|
{ oldName: string | undefined }
|
||||||
>({
|
>({
|
||||||
mutationFn: async ({ id, dataKey, dataKeyVersion, newName }) => {
|
mutationFn: async ({ id, dataKey, dataKeyVersion, newName }) => {
|
||||||
const newNameEncrypted = await encryptString(newName, dataKey);
|
const newNameEncrypted = await encryptString(newName, dataKey);
|
||||||
await callPostApi<DirectoryRenameRequest>(`/api/directory/${id}/rename`, {
|
const res = await callPostApi<DirectoryRenameRequest>(`/api/directory/${id}/rename`, {
|
||||||
dekVersion: dataKeyVersion.toISOString(),
|
dekVersion: dataKeyVersion.toISOString(),
|
||||||
name: newNameEncrypted.ciphertext,
|
name: newNameEncrypted.ciphertext,
|
||||||
nameIv: newNameEncrypted.iv,
|
nameIv: newNameEncrypted.iv,
|
||||||
});
|
});
|
||||||
|
if (!res.ok) throw new Error("Failed to rename directory");
|
||||||
|
|
||||||
|
await updateDirectoryInfo(id, { name: newName });
|
||||||
},
|
},
|
||||||
onMutate: async ({ id, newName }) => {
|
onMutate: async ({ id, newName }) => {
|
||||||
await queryClient.cancelQueries({ queryKey: ["directory", id] });
|
await queryClient.cancelQueries({ queryKey: ["directory", id] });
|
||||||
@@ -195,61 +215,62 @@ export const useDirectoryRename = () => {
|
|||||||
...prevInfo,
|
...prevInfo,
|
||||||
name: newName,
|
name: newName,
|
||||||
});
|
});
|
||||||
await updateDirectoryInfo(id, { name: newName });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return { prevInfo };
|
return { oldName: prevInfo?.name };
|
||||||
},
|
},
|
||||||
onSuccess: async (data, { id, newName }) => {
|
onError: (_error, { id }, context) => {
|
||||||
await updateDirectoryInfo(id, { name: newName });
|
if (context?.oldName) {
|
||||||
},
|
queryClient.setQueryData<DirectoryInfo & { id: number }>(["directory", id], (prevInfo) => {
|
||||||
onError: (error, { id }, context) => {
|
if (!prevInfo) return undefined;
|
||||||
if (context?.prevInfo) {
|
return { ...prevInfo, name: context.oldName! };
|
||||||
queryClient.setQueryData<DirectoryInfo>(["directory", id], context.prevInfo);
|
});
|
||||||
}
|
}
|
||||||
console.error("Failed to rename directory:", error);
|
|
||||||
},
|
},
|
||||||
onSettled: (data, error, { id }) => {
|
onSettled: (_data, _error, { id }) => {
|
||||||
queryClient.invalidateQueries({ queryKey: ["directory", id] });
|
queryClient.invalidateQueries({ queryKey: ["directory", id] });
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
export const useDirectoryDelete = (parentId: DirectoryId) => {
|
export const useDirectoryDeletion = (parentId: DirectoryId) => {
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
return createMutation<
|
return createMutation<{ deletedFiles: number[] }, Error, { id: number }, {}>({
|
||||||
void,
|
|
||||||
Error,
|
|
||||||
{ id: number },
|
|
||||||
{ prevInfo: (DirectoryInfo & { id: number }) | undefined }
|
|
||||||
>({
|
|
||||||
mutationFn: async ({ id }) => {
|
mutationFn: async ({ id }) => {
|
||||||
await callPostApi(`/api/directory/${id}/delete`);
|
const res = await callPostApi(`/api/directory/${id}/delete`);
|
||||||
|
if (!res.ok) throw new Error("Failed to delete directory");
|
||||||
|
|
||||||
|
const { deletedDirectories, deletedFiles }: DirectoryDeleteResponse = await res.json();
|
||||||
|
await Promise.all([
|
||||||
|
...deletedDirectories.map(deleteDirectoryInfo),
|
||||||
|
...deletedFiles.map(deleteFileInfo),
|
||||||
|
]);
|
||||||
|
|
||||||
|
return { deletedFiles };
|
||||||
},
|
},
|
||||||
onMutate: async ({ id }) => {
|
onMutate: async ({ id }) => {
|
||||||
await queryClient.cancelQueries({ queryKey: ["directory", parentId] });
|
await queryClient.cancelQueries({ queryKey: ["directory", parentId] });
|
||||||
|
queryClient.setQueryData<DirectoryInfo>(["directory", parentId], (prevParentInfo) => {
|
||||||
const prevParentInfo = queryClient.getQueryData<DirectoryInfo>(["directory", parentId]);
|
if (!prevParentInfo) return undefined;
|
||||||
if (prevParentInfo) {
|
return {
|
||||||
queryClient.setQueryData<DirectoryInfo>(["directory", parentId], {
|
|
||||||
...prevParentInfo,
|
...prevParentInfo,
|
||||||
subDirectoryIds: prevParentInfo.subDirectoryIds.filter((subId) => subId !== id),
|
subDirectoryIds: prevParentInfo.subDirectoryIds.filter((subId) => subId !== id),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
return {};
|
||||||
|
},
|
||||||
|
onError: (_error, { id }, context) => {
|
||||||
|
if (context) {
|
||||||
|
queryClient.setQueryData<DirectoryInfo>(["directory", parentId], (prevParentInfo) => {
|
||||||
|
if (!prevParentInfo) return undefined;
|
||||||
|
return {
|
||||||
|
...prevParentInfo,
|
||||||
|
subDirectoryIds: [...prevParentInfo.subDirectoryIds, id],
|
||||||
|
};
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const prevInfo = queryClient.getQueryData<DirectoryInfo & { id: number }>(["directory", id]);
|
|
||||||
return { prevInfo };
|
|
||||||
},
|
},
|
||||||
onSuccess: async (data, { id }) => {
|
onSettled: () => {
|
||||||
await deleteDirectoryInfo(id);
|
|
||||||
},
|
|
||||||
onError: (error, { id }, context) => {
|
|
||||||
if (context?.prevInfo) {
|
|
||||||
queryClient.setQueryData<DirectoryInfo>(["directory", parentId], context?.prevInfo);
|
|
||||||
}
|
|
||||||
console.error("Failed to delete directory:", error);
|
|
||||||
},
|
|
||||||
onSettled: (data, error, { id }) => {
|
|
||||||
queryClient.invalidateQueries({ queryKey: ["directory", parentId] });
|
queryClient.invalidateQueries({ queryKey: ["directory", parentId] });
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -181,7 +181,10 @@ export const unregisterDirectory = async (userId: number, directoryId: number) =
|
|||||||
};
|
};
|
||||||
const unregisterDirectoryRecursively = async (
|
const unregisterDirectoryRecursively = async (
|
||||||
directoryId: number,
|
directoryId: number,
|
||||||
): Promise<{ id: number; path: string; thumbnailPath: string | null }[]> => {
|
): Promise<{
|
||||||
|
subDirectories: { id: number }[];
|
||||||
|
files: { id: number; path: string; thumbnailPath: string | null }[];
|
||||||
|
}> => {
|
||||||
const files = await unregisterFiles(directoryId);
|
const files = await unregisterFiles(directoryId);
|
||||||
const subDirectories = await trx
|
const subDirectories = await trx
|
||||||
.selectFrom("directory")
|
.selectFrom("directory")
|
||||||
@@ -189,7 +192,7 @@ export const unregisterDirectory = async (userId: number, directoryId: number) =
|
|||||||
.where("parent_id", "=", directoryId)
|
.where("parent_id", "=", directoryId)
|
||||||
.where("user_id", "=", userId)
|
.where("user_id", "=", userId)
|
||||||
.execute();
|
.execute();
|
||||||
const subDirectoryFilePaths = await Promise.all(
|
const subDirectoryEntries = await Promise.all(
|
||||||
subDirectories.map(async ({ id }) => await unregisterDirectoryRecursively(id)),
|
subDirectories.map(async ({ id }) => await unregisterDirectoryRecursively(id)),
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -201,7 +204,12 @@ export const unregisterDirectory = async (userId: number, directoryId: number) =
|
|||||||
if (deleteRes.numDeletedRows === 0n) {
|
if (deleteRes.numDeletedRows === 0n) {
|
||||||
throw new IntegrityError("Directory not found");
|
throw new IntegrityError("Directory not found");
|
||||||
}
|
}
|
||||||
return files.concat(...subDirectoryFilePaths);
|
return {
|
||||||
|
subDirectories: subDirectoryEntries
|
||||||
|
.flatMap(({ subDirectories }) => subDirectories)
|
||||||
|
.concat(subDirectories),
|
||||||
|
files: subDirectoryEntries.flatMap(({ files }) => files).concat(files),
|
||||||
|
};
|
||||||
};
|
};
|
||||||
return await unregisterDirectoryRecursively(directoryId);
|
return await unregisterDirectoryRecursively(directoryId);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ export const directoryInfoResponse = z.object({
|
|||||||
export type DirectoryInfoResponse = z.output<typeof directoryInfoResponse>;
|
export type DirectoryInfoResponse = z.output<typeof directoryInfoResponse>;
|
||||||
|
|
||||||
export const directoryDeleteResponse = z.object({
|
export const directoryDeleteResponse = z.object({
|
||||||
|
deletedDirectories: z.number().int().positive().array(),
|
||||||
deletedFiles: z.number().int().positive().array(),
|
deletedFiles: z.number().int().positive().array(),
|
||||||
});
|
});
|
||||||
export type DirectoryDeleteResponse = z.output<typeof directoryDeleteResponse>;
|
export type DirectoryDeleteResponse = z.output<typeof directoryDeleteResponse>;
|
||||||
|
|||||||
@@ -42,8 +42,9 @@ const safeUnlink = async (path: string | null) => {
|
|||||||
|
|
||||||
export const deleteDirectory = async (userId: number, directoryId: number) => {
|
export const deleteDirectory = async (userId: number, directoryId: number) => {
|
||||||
try {
|
try {
|
||||||
const files = await unregisterDirectory(userId, directoryId);
|
const { subDirectories, files } = await unregisterDirectory(userId, directoryId);
|
||||||
return {
|
return {
|
||||||
|
directories: [...subDirectories.map(({ id }) => id), directoryId],
|
||||||
files: files.map(({ id, path, thumbnailPath }) => {
|
files: files.map(({ id, path, thumbnailPath }) => {
|
||||||
safeUnlink(path); // Intended
|
safeUnlink(path); // Intended
|
||||||
safeUnlink(thumbnailPath); // Intended
|
safeUnlink(thumbnailPath); // Intended
|
||||||
|
|||||||
@@ -1,15 +1,14 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { onMount } from "svelte";
|
import { onMount } from "svelte";
|
||||||
import type { Writable } from "svelte/store";
|
|
||||||
import { goto } from "$app/navigation";
|
import { goto } from "$app/navigation";
|
||||||
import { FloatingButton } from "$lib/components/atoms";
|
import { FloatingButton } from "$lib/components/atoms";
|
||||||
import { TopBar } from "$lib/components/molecules";
|
import { TopBar } from "$lib/components/molecules";
|
||||||
import { type DirectoryInfo } from "$lib/modules/filesystem";
|
import { deleteFileCache, deleteFileThumbnailCache } from "$lib/modules/file";
|
||||||
import {
|
import {
|
||||||
getDirectoryInfo,
|
getDirectoryInfo,
|
||||||
useDirectoryCreate,
|
useDirectoryCreation,
|
||||||
useDirectoryRename,
|
useDirectoryRename,
|
||||||
useDirectoryDelete,
|
useDirectoryDeletion,
|
||||||
} from "$lib/modules/filesystem2";
|
} from "$lib/modules/filesystem2";
|
||||||
import { masterKeyStore, hmacSecretStore } from "$lib/stores";
|
import { masterKeyStore, hmacSecretStore } from "$lib/stores";
|
||||||
import DirectoryCreateModal from "./DirectoryCreateModal.svelte";
|
import DirectoryCreateModal from "./DirectoryCreateModal.svelte";
|
||||||
@@ -35,9 +34,9 @@
|
|||||||
let context = createContext();
|
let context = createContext();
|
||||||
|
|
||||||
let info = $derived(getDirectoryInfo(data.id, $masterKeyStore?.get(1)?.key!));
|
let info = $derived(getDirectoryInfo(data.id, $masterKeyStore?.get(1)?.key!));
|
||||||
let requestDirectoryCreation = $derived(useDirectoryCreate(data.id));
|
let requestDirectoryCreation = $derived(useDirectoryCreation(data.id, $masterKeyStore?.get(1)!));
|
||||||
let requestDirectoryRename = useDirectoryRename();
|
let requestDirectoryRename = useDirectoryRename();
|
||||||
let requestDirectoryDeletion = $derived(useDirectoryDelete(data.id));
|
let requestDirectoryDeletion = $derived(useDirectoryDeletion(data.id));
|
||||||
|
|
||||||
let fileInput: HTMLInputElement | undefined = $state();
|
let fileInput: HTMLInputElement | undefined = $state();
|
||||||
let duplicatedFile: File | undefined = $state();
|
let duplicatedFile: File | undefined = $state();
|
||||||
@@ -135,10 +134,7 @@
|
|||||||
<DirectoryCreateModal
|
<DirectoryCreateModal
|
||||||
bind:isOpen={isDirectoryCreateModalOpen}
|
bind:isOpen={isDirectoryCreateModalOpen}
|
||||||
onCreateClick={async (name) => {
|
onCreateClick={async (name) => {
|
||||||
$requestDirectoryCreation.mutate({
|
$requestDirectoryCreation.mutate({ name });
|
||||||
name,
|
|
||||||
masterKey: $masterKeyStore?.get(1)!,
|
|
||||||
});
|
|
||||||
return true; // TODO
|
return true; // TODO
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
@@ -190,9 +186,16 @@
|
|||||||
bind:isOpen={isEntryDeleteModalOpen}
|
bind:isOpen={isEntryDeleteModalOpen}
|
||||||
onDeleteClick={async () => {
|
onDeleteClick={async () => {
|
||||||
if (context.selectedEntry!.type === "directory") {
|
if (context.selectedEntry!.type === "directory") {
|
||||||
$requestDirectoryDeletion.mutate({
|
const res = await $requestDirectoryDeletion.mutateAsync({
|
||||||
id: context.selectedEntry!.id,
|
id: context.selectedEntry!.id,
|
||||||
});
|
});
|
||||||
|
if (!res) return false;
|
||||||
|
await Promise.all(
|
||||||
|
res.deletedFiles.flatMap((fileId) => [
|
||||||
|
deleteFileCache(fileId),
|
||||||
|
deleteFileThumbnailCache(fileId),
|
||||||
|
]),
|
||||||
|
);
|
||||||
return true; // TODO
|
return true; // TODO
|
||||||
} else {
|
} else {
|
||||||
if (await requestEntryDeletion(context.selectedEntry!)) {
|
if (await requestEntryDeletion(context.selectedEntry!)) {
|
||||||
|
|||||||
@@ -1,8 +1,12 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { untrack } from "svelte";
|
import { untrack } from "svelte";
|
||||||
import { get, type Readable, type Writable } from "svelte/store";
|
import { get, type Writable } from "svelte/store";
|
||||||
import { getFileInfo, type DirectoryInfo, type FileInfo } from "$lib/modules/filesystem";
|
import { getFileInfo, type FileInfo } from "$lib/modules/filesystem";
|
||||||
import { getDirectoryInfo, type DirectoryInfoStore } from "$lib/modules/filesystem2";
|
import {
|
||||||
|
getDirectoryInfo,
|
||||||
|
type DirectoryInfo,
|
||||||
|
type DirectoryInfoStore,
|
||||||
|
} from "$lib/modules/filesystem2";
|
||||||
import { SortBy, sortEntries } from "$lib/modules/util";
|
import { SortBy, sortEntries } from "$lib/modules/util";
|
||||||
import {
|
import {
|
||||||
fileUploadStatusStore,
|
fileUploadStatusStore,
|
||||||
|
|||||||
@@ -1,9 +1,7 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import type { Writable } from "svelte/store";
|
|
||||||
import { ActionEntryButton } from "$lib/components/atoms";
|
import { ActionEntryButton } from "$lib/components/atoms";
|
||||||
import { DirectoryEntryLabel } from "$lib/components/molecules";
|
import { DirectoryEntryLabel } from "$lib/components/molecules";
|
||||||
import type { DirectoryInfo } from "$lib/modules/filesystem";
|
import type { DirectoryInfo, DirectoryInfoStore } from "$lib/modules/filesystem2";
|
||||||
import type { DirectoryInfoStore } from "$lib/modules/filesystem2";
|
|
||||||
import type { SelectedEntry } from "../service.svelte";
|
import type { SelectedEntry } from "../service.svelte";
|
||||||
|
|
||||||
import IconMoreVert from "~icons/material-symbols/more-vert";
|
import IconMoreVert from "~icons/material-symbols/more-vert";
|
||||||
|
|||||||
@@ -16,8 +16,11 @@ export const POST: RequestHandler = async ({ locals, params }) => {
|
|||||||
if (!zodRes.success) error(400, "Invalid path parameters");
|
if (!zodRes.success) error(400, "Invalid path parameters");
|
||||||
const { id } = zodRes.data;
|
const { id } = zodRes.data;
|
||||||
|
|
||||||
const { files } = await deleteDirectory(userId, id);
|
const { directories, files } = await deleteDirectory(userId, id);
|
||||||
return json(
|
return json(
|
||||||
directoryDeleteResponse.parse({ deletedFiles: files } satisfies DirectoryDeleteResponse),
|
directoryDeleteResponse.parse({
|
||||||
|
deletedDirectories: directories,
|
||||||
|
deletedFiles: files,
|
||||||
|
} satisfies DirectoryDeleteResponse),
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user