mirror of
https://github.com/kmc7468/arkvault.git
synced 2025-12-14 22:08:45 +00:00
파일/폴더 삭제 및 이름 변경 구현 완료
This commit is contained in:
@@ -20,7 +20,7 @@ export const callGetApi = async (input: RequestInfo, fetchInternal?: typeof fetc
|
|||||||
|
|
||||||
export const callPostApi = async <T>(
|
export const callPostApi = async <T>(
|
||||||
input: RequestInfo,
|
input: RequestInfo,
|
||||||
payload: T,
|
payload?: T,
|
||||||
fetchInternal?: typeof fetch,
|
fetchInternal?: typeof fetch,
|
||||||
) => {
|
) => {
|
||||||
return await callApi(
|
return await callApi(
|
||||||
@@ -28,7 +28,7 @@ export const callPostApi = async <T>(
|
|||||||
{
|
{
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: { "Content-Type": "application/json" },
|
headers: { "Content-Type": "application/json" },
|
||||||
body: JSON.stringify(payload),
|
body: payload ? JSON.stringify(payload) : undefined,
|
||||||
},
|
},
|
||||||
fetchInternal,
|
fetchInternal,
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,11 +1,3 @@
|
|||||||
<script module lang="ts">
|
|
||||||
export interface SelectedDirectoryEntry {
|
|
||||||
type: "directory" | "file";
|
|
||||||
id: number;
|
|
||||||
name: string;
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { goto } from "$app/navigation";
|
import { goto } from "$app/navigation";
|
||||||
import { TopBar } from "$lib/components";
|
import { TopBar } from "$lib/components";
|
||||||
@@ -22,6 +14,9 @@
|
|||||||
decryptFileMetadata,
|
decryptFileMetadata,
|
||||||
requestDirectoryCreation,
|
requestDirectoryCreation,
|
||||||
requestFileUpload,
|
requestFileUpload,
|
||||||
|
requestDirectoryEntryRename,
|
||||||
|
requestDirectoryEntryDeletion,
|
||||||
|
type SelectedDirectoryEntry,
|
||||||
} from "./service";
|
} from "./service";
|
||||||
|
|
||||||
import IconAdd from "~icons/material-symbols/add";
|
import IconAdd from "~icons/material-symbols/add";
|
||||||
@@ -114,12 +109,12 @@
|
|||||||
<div class="my-4 pb-[4.5rem]">
|
<div class="my-4 pb-[4.5rem]">
|
||||||
{#if subDirectories}
|
{#if subDirectories}
|
||||||
{#await subDirectories then subDirectories}
|
{#await subDirectories then subDirectories}
|
||||||
{#each subDirectories as { id, name }}
|
{#each subDirectories as { id, dataKey, name }}
|
||||||
<DirectoryEntry
|
<DirectoryEntry
|
||||||
{name}
|
{name}
|
||||||
onclick={() => goto(`/directory/${id}`)}
|
onclick={() => goto(`/directory/${id}`)}
|
||||||
onOpenMenuClick={() => {
|
onOpenMenuClick={() => {
|
||||||
selectedEntry = { type: "directory", id, name };
|
selectedEntry = { type: "directory", id, dataKey, name };
|
||||||
isDirectoryEntryMenuBottomSheetOpen = true;
|
isDirectoryEntryMenuBottomSheetOpen = true;
|
||||||
}}
|
}}
|
||||||
type="directory"
|
type="directory"
|
||||||
@@ -129,12 +124,12 @@
|
|||||||
{/if}
|
{/if}
|
||||||
{#if files}
|
{#if files}
|
||||||
{#await files then files}
|
{#await files then files}
|
||||||
{#each files as { id, name }}
|
{#each files as { id, dataKey, name }}
|
||||||
<DirectoryEntry
|
<DirectoryEntry
|
||||||
{name}
|
{name}
|
||||||
onclick={() => goto(`/file/${id}`)}
|
onclick={() => goto(`/file/${id}`)}
|
||||||
onOpenMenuClick={() => {
|
onOpenMenuClick={() => {
|
||||||
selectedEntry = { type: "file", id, name };
|
selectedEntry = { type: "file", id, dataKey, name };
|
||||||
isDirectoryEntryMenuBottomSheetOpen = true;
|
isDirectoryEntryMenuBottomSheetOpen = true;
|
||||||
}}
|
}}
|
||||||
type="file"
|
type="file"
|
||||||
@@ -177,5 +172,19 @@
|
|||||||
isDeleteDirectoryEntryModalOpen = true;
|
isDeleteDirectoryEntryModalOpen = true;
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<RenameDirectoryEntryModal bind:isOpen={isRenameDirectoryEntryModalOpen} bind:selectedEntry />
|
<RenameDirectoryEntryModal
|
||||||
<DeleteDirectoryEntryModal bind:isOpen={isDeleteDirectoryEntryModalOpen} bind:selectedEntry />
|
bind:isOpen={isRenameDirectoryEntryModalOpen}
|
||||||
|
bind:selectedEntry
|
||||||
|
onRenameClick={async (newName) => {
|
||||||
|
await requestDirectoryEntryRename(selectedEntry!, newName);
|
||||||
|
return true;
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<DeleteDirectoryEntryModal
|
||||||
|
bind:isOpen={isDeleteDirectoryEntryModalOpen}
|
||||||
|
bind:selectedEntry
|
||||||
|
onDeleteClick={async () => {
|
||||||
|
await requestDirectoryEntryDeletion(selectedEntry!);
|
||||||
|
return true;
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|||||||
@@ -1,24 +1,27 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { Modal } from "$lib/components";
|
import { Modal } from "$lib/components";
|
||||||
import { Button } from "$lib/components/buttons";
|
import { Button } from "$lib/components/buttons";
|
||||||
import type { SelectedDirectoryEntry } from "./+page.svelte";
|
import type { SelectedDirectoryEntry } from "./service";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
|
onDeleteClick: () => Promise<boolean>;
|
||||||
isOpen: boolean;
|
isOpen: boolean;
|
||||||
selectedEntry: SelectedDirectoryEntry | undefined;
|
selectedEntry: SelectedDirectoryEntry | undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
let { isOpen = $bindable(), selectedEntry = $bindable() }: Props = $props();
|
let { onDeleteClick, isOpen = $bindable(), selectedEntry = $bindable() }: Props = $props();
|
||||||
|
|
||||||
const closeModal = () => {
|
const closeModal = () => {
|
||||||
isOpen = false;
|
isOpen = false;
|
||||||
selectedEntry = undefined;
|
selectedEntry = undefined;
|
||||||
};
|
};
|
||||||
|
|
||||||
const deleteEntry = () => {
|
const deleteEntry = async () => {
|
||||||
// TODO
|
// TODO: Validation
|
||||||
|
|
||||||
closeModal();
|
if (await onDeleteClick()) {
|
||||||
|
closeModal();
|
||||||
|
}
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { BottomSheet } from "$lib/components";
|
import { BottomSheet } from "$lib/components";
|
||||||
import { EntryButton } from "$lib/components/buttons";
|
import { EntryButton } from "$lib/components/buttons";
|
||||||
import type { SelectedDirectoryEntry } from "./+page.svelte";
|
import type { SelectedDirectoryEntry } from "./service";
|
||||||
|
|
||||||
import IconFolder from "~icons/material-symbols/folder";
|
import IconFolder from "~icons/material-symbols/folder";
|
||||||
import IconDraft from "~icons/material-symbols/draft";
|
import IconDraft from "~icons/material-symbols/draft";
|
||||||
|
|||||||
@@ -2,14 +2,15 @@
|
|||||||
import { Modal } from "$lib/components";
|
import { Modal } from "$lib/components";
|
||||||
import { Button } from "$lib/components/buttons";
|
import { Button } from "$lib/components/buttons";
|
||||||
import { TextInput } from "$lib/components/inputs";
|
import { TextInput } from "$lib/components/inputs";
|
||||||
import type { SelectedDirectoryEntry } from "./+page.svelte";
|
import type { SelectedDirectoryEntry } from "./service";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
|
onRenameClick: (newName: string) => Promise<boolean>;
|
||||||
isOpen: boolean;
|
isOpen: boolean;
|
||||||
selectedEntry: SelectedDirectoryEntry | undefined;
|
selectedEntry: SelectedDirectoryEntry | undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
let { isOpen = $bindable(), selectedEntry = $bindable() }: Props = $props();
|
let { onRenameClick, isOpen = $bindable(), selectedEntry = $bindable() }: Props = $props();
|
||||||
|
|
||||||
let name = $state("");
|
let name = $state("");
|
||||||
|
|
||||||
@@ -19,10 +20,12 @@
|
|||||||
selectedEntry = undefined;
|
selectedEntry = undefined;
|
||||||
};
|
};
|
||||||
|
|
||||||
const renameEntry = () => {
|
const renameEntry = async () => {
|
||||||
// TODO
|
// TODO: Validation
|
||||||
|
|
||||||
closeModal();
|
if (await onRenameClick(name)) {
|
||||||
|
closeModal();
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
$effect(() => {
|
$effect(() => {
|
||||||
|
|||||||
@@ -9,20 +9,30 @@ import {
|
|||||||
decryptString,
|
decryptString,
|
||||||
} from "$lib/modules/crypto";
|
} from "$lib/modules/crypto";
|
||||||
import type {
|
import type {
|
||||||
|
DirectoryRenameRequest,
|
||||||
DirectoryInfoResponse,
|
DirectoryInfoResponse,
|
||||||
DirectoryCreateRequest,
|
DirectoryCreateRequest,
|
||||||
|
FileRenameRequest,
|
||||||
FileUploadRequest,
|
FileUploadRequest,
|
||||||
} from "$lib/server/schemas";
|
} from "$lib/server/schemas";
|
||||||
import type { MasterKey } from "$lib/stores";
|
import type { MasterKey } from "$lib/stores";
|
||||||
|
|
||||||
export { decryptFileMetadata } from "$lib/services/file";
|
export { decryptFileMetadata } from "$lib/services/file";
|
||||||
|
|
||||||
|
export interface SelectedDirectoryEntry {
|
||||||
|
type: "directory" | "file";
|
||||||
|
id: number;
|
||||||
|
dataKey: CryptoKey;
|
||||||
|
name: string;
|
||||||
|
}
|
||||||
|
|
||||||
export const decryptDirectoryMetadata = async (
|
export const decryptDirectoryMetadata = async (
|
||||||
metadata: NonNullable<DirectoryInfoResponse["metadata"]>,
|
metadata: NonNullable<DirectoryInfoResponse["metadata"]>,
|
||||||
masterKey: CryptoKey,
|
masterKey: CryptoKey,
|
||||||
) => {
|
) => {
|
||||||
const { dataKey } = await unwrapDataKey(metadata.dek, masterKey);
|
const { dataKey } = await unwrapDataKey(metadata.dek, masterKey);
|
||||||
return {
|
return {
|
||||||
|
dataKey,
|
||||||
name: await decryptString(metadata.name, metadata.nameIv, dataKey),
|
name: await decryptString(metadata.name, metadata.nameIv, dataKey),
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
@@ -71,3 +81,26 @@ export const requestFileUpload = async (
|
|||||||
xhr.open("POST", "/api/file/upload");
|
xhr.open("POST", "/api/file/upload");
|
||||||
xhr.send(form);
|
xhr.send(form);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const requestDirectoryEntryRename = async (
|
||||||
|
entry: SelectedDirectoryEntry,
|
||||||
|
newName: string,
|
||||||
|
) => {
|
||||||
|
const newNameEncrypted = await encryptString(newName, entry.dataKey);
|
||||||
|
|
||||||
|
if (entry.type === "directory") {
|
||||||
|
await callPostApi<DirectoryRenameRequest>(`/api/directory/${entry.id}/rename`, {
|
||||||
|
name: newNameEncrypted.ciphertext,
|
||||||
|
nameIv: newNameEncrypted.iv,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
await callPostApi<FileRenameRequest>(`/api/file/${entry.id}/rename`, {
|
||||||
|
name: newNameEncrypted.ciphertext,
|
||||||
|
nameIv: newNameEncrypted.iv,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const requestDirectoryEntryDeletion = async (entry: SelectedDirectoryEntry) => {
|
||||||
|
await callPostApi(`/api/${entry.type}/${entry.id}/delete`);
|
||||||
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user