mirror of
https://github.com/kmc7468/arkvault.git
synced 2025-12-12 21:08:46 +00:00
자잘한 리팩토링 2 및 TopBar가 상단에 고정되지 않던 버그 수정
This commit is contained in:
@@ -5,3 +5,6 @@ yarn.lock
|
||||
|
||||
# Output
|
||||
/drizzle
|
||||
|
||||
# Documents
|
||||
*.md
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
- 🔒 사용자의 미디어는 클라이언트에서 암호화한 상태로 저장돼요.
|
||||
- 🔑 메타 데이터도 클라이언트에서 암호화돼요.
|
||||
- ⚠️ 검색의 용이성을 위해, 스키마는 암호화되지 않아요.
|
||||
- ⚠️ 파일의 MIME 타입과 같은 일부 메타 데이터는 암호화되지 않아요.
|
||||
- ⚠️ 파일의 MIME 타입과 같은 일부 메타 데이터는 암호화되지 않아요.
|
||||
- 📱 여러 디바이스에서 동시에 접근할 수 있어요.
|
||||
|
||||
## How to Install
|
||||
@@ -36,7 +36,7 @@ docker compose up --build -d
|
||||
|`JWT_REFRESH_TOKEN_EXPIRES`||`14d`|Refresh Token의 유효 시간이에요.|
|
||||
|`USER_CLIENT_CHALLENGE_EXPIRES`||`5m`|암호 키를 서버에 처음 등록할 때 사용되는 챌린지의 유효 시간이에요.|
|
||||
|`TOKEN_UPGRADE_CHALLENGE_EXPIRES`||`5m`|암호 키와 함께 로그인할 때 사용되는 챌린지의 유효 시간이에요.|
|
||||
|`TRUST_PROXY`|||신뢰할 수 있는 리버스 프록시의 수예요. 설정할 경우, 1 이상의 정수로 설정해 주세요. 리버스 프록시에서 `X-Forwarded-For` HTTP 헤더를 올바르게 설정하도록 구성해 주세요.|
|
||||
|`TRUST_PROXY`|||신뢰할 수 있는 리버스 프록시의 수예요. 설정할 경우 1 이상의 정수로 설정해 주세요. 프록시에서 `X-Forwarded-For` HTTP 헤더를 올바르게 설정하도록 구성해 주세요.|
|
||||
|`NODE_ENV`||`production`|ArkVault의 사용 용도예요. `production`인 경우, 컨테이너가 실행될 때마다 DB 마이그레이션이 자동으로 실행돼요.|
|
||||
|`PORT`||`80`|ArkVault 서버의 포트예요.|
|
||||
|`CONTAINER_UID`||`0`|Docker 컨테이너에 매핑할 UID예요. NFS와 함께 사용할 경우 설정이 필요할 수 있어요.|
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
});
|
||||
</script>
|
||||
|
||||
<div class="sticky top-0 flex items-center justify-between bg-white pt-4">
|
||||
<div class="sticky top-0 z-10 flex flex-shrink-0 items-center justify-between bg-white py-4">
|
||||
<button onclick={back} class="w-[2.3rem] flex-shrink-0 rounded-full p-1 active:bg-gray-100">
|
||||
<IconArrowBack class="text-2xl" />
|
||||
</button>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { writable } from "svelte/store";
|
||||
import { writable, type Writable } from "svelte/store";
|
||||
import { callGetApi } from "$lib/hooks";
|
||||
import { unwrapDataKey, decryptString } from "$lib/modules/crypto";
|
||||
import type { DirectoryInfoResponse, FileInfoResponse } from "$lib/server/schemas";
|
||||
@@ -9,7 +9,11 @@ import {
|
||||
type FileInfo,
|
||||
} from "$lib/stores/file";
|
||||
|
||||
const fetchDirectoryInfo = async (directoryId: "root" | number, masterKey: CryptoKey) => {
|
||||
const fetchDirectoryInfo = async (
|
||||
directoryId: "root" | number,
|
||||
masterKey: CryptoKey,
|
||||
infoStore: Writable<DirectoryInfo | null>,
|
||||
) => {
|
||||
const res = await callGetApi(`/api/directory/${directoryId}`);
|
||||
if (!res.ok) throw new Error("Failed to fetch directory information");
|
||||
const { metadata, subDirectories, files }: DirectoryInfoResponse = await res.json();
|
||||
@@ -33,12 +37,7 @@ const fetchDirectoryInfo = async (directoryId: "root" | number, masterKey: Crypt
|
||||
};
|
||||
}
|
||||
|
||||
const info = directoryInfoStore.get(directoryId);
|
||||
if (info) {
|
||||
info.update(() => newInfo);
|
||||
} else {
|
||||
directoryInfoStore.set(directoryId, writable(newInfo));
|
||||
}
|
||||
infoStore.update(() => newInfo);
|
||||
};
|
||||
|
||||
export const getDirectoryInfo = (directoryId: "root" | number, masterKey: CryptoKey) => {
|
||||
@@ -50,11 +49,15 @@ export const getDirectoryInfo = (directoryId: "root" | number, masterKey: Crypto
|
||||
directoryInfoStore.set(directoryId, info);
|
||||
}
|
||||
|
||||
fetchDirectoryInfo(directoryId, masterKey);
|
||||
fetchDirectoryInfo(directoryId, masterKey, info);
|
||||
return info;
|
||||
};
|
||||
|
||||
const fetchFileInfo = async (fileId: number, masterKey: CryptoKey) => {
|
||||
const fetchFileInfo = async (
|
||||
fileId: number,
|
||||
masterKey: CryptoKey,
|
||||
infoStore: Writable<FileInfo | null>,
|
||||
) => {
|
||||
const res = await callGetApi(`/api/file/${fileId}`);
|
||||
if (!res.ok) throw new Error("Failed to fetch file information");
|
||||
const metadata: FileInfoResponse = await res.json();
|
||||
@@ -69,12 +72,7 @@ const fetchFileInfo = async (fileId: number, masterKey: CryptoKey) => {
|
||||
name: await decryptString(metadata.name, metadata.nameIv, dataKey),
|
||||
};
|
||||
|
||||
const info = fileInfoStore.get(fileId);
|
||||
if (info) {
|
||||
info.update(() => newInfo);
|
||||
} else {
|
||||
fileInfoStore.set(fileId, writable(newInfo));
|
||||
}
|
||||
infoStore.update(() => newInfo);
|
||||
};
|
||||
|
||||
export const getFileInfo = (fileId: number, masterKey: CryptoKey) => {
|
||||
@@ -86,6 +84,6 @@ export const getFileInfo = (fileId: number, masterKey: CryptoKey) => {
|
||||
fileInfoStore.set(fileId, info);
|
||||
}
|
||||
|
||||
fetchFileInfo(fileId, masterKey);
|
||||
fetchFileInfo(fileId, masterKey, info);
|
||||
return info;
|
||||
};
|
||||
|
||||
@@ -67,11 +67,9 @@
|
||||
<title>파일</title>
|
||||
</svelte:head>
|
||||
|
||||
<div class="flex h-full flex-col">
|
||||
<div class="flex-shrink-0">
|
||||
<TopBar title={$info?.name} />
|
||||
</div>
|
||||
<div class="flex w-full flex-grow flex-col items-center py-4">
|
||||
<div class="flex flex-col">
|
||||
<TopBar title={$info?.name} />
|
||||
<div class="mb-4 flex w-full flex-grow flex-col items-center">
|
||||
{#snippet viewerLoading(message: string)}
|
||||
<div class="flex flex-grow items-center justify-center">
|
||||
<p class="text-gray-500">{message}</p>
|
||||
|
||||
@@ -61,22 +61,23 @@
|
||||
<input bind:this={fileInput} onchange={uploadFile} type="file" class="hidden" />
|
||||
|
||||
<div class="flex min-h-full flex-col px-4">
|
||||
<div class="flex-shrink-0">
|
||||
{#if data.id !== "root"}
|
||||
<TopBar title={$info?.name} />
|
||||
{/if}
|
||||
</div>
|
||||
{#if data.id !== "root"}
|
||||
<TopBar title={$info?.name} />
|
||||
{/if}
|
||||
{#if $info}
|
||||
{#key $info}
|
||||
<DirectoryEntries
|
||||
info={$info}
|
||||
onEntryClick={({ type, id }) => goto(`/${type}/${id}`)}
|
||||
onEntryMenuClick={(entry) => {
|
||||
selectedEntry = entry;
|
||||
isDirectoryEntryMenuBottomSheetOpen = true;
|
||||
}}
|
||||
/>
|
||||
{/key}
|
||||
{@const topMargin = data.id === "root" ? "mt-4" : ""}
|
||||
<div class="mb-4 flex flex-grow flex-col {topMargin}">
|
||||
{#key $info}
|
||||
<DirectoryEntries
|
||||
info={$info}
|
||||
onEntryClick={({ type, id }) => goto(`/${type}/${id}`)}
|
||||
onEntryMenuClick={(entry) => {
|
||||
selectedEntry = entry;
|
||||
isDirectoryEntryMenuBottomSheetOpen = true;
|
||||
}}
|
||||
/>
|
||||
{/key}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
|
||||
@@ -42,7 +42,7 @@
|
||||
</script>
|
||||
|
||||
{#if info.subDirectoryIds.length + info.fileIds.length > 0}
|
||||
<div class="my-4 pb-[4.5rem]">
|
||||
<div class="pb-[4.5rem]">
|
||||
{#each subDirectoryInfos as subDirectory}
|
||||
<SubDirectory info={subDirectory} onclick={onEntryClick} onOpenMenuClick={onEntryMenuClick} />
|
||||
{/each}
|
||||
@@ -51,7 +51,7 @@
|
||||
{/each}
|
||||
</div>
|
||||
{:else}
|
||||
<div class="my-4 flex flex-grow items-center justify-center">
|
||||
<div class="flex flex-grow items-center justify-center">
|
||||
<p class="text-gray-500">폴더가 비어 있어요.</p>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
@@ -1,11 +1,5 @@
|
||||
import { callPostApi } from "$lib/hooks";
|
||||
import {
|
||||
encodeToBase64,
|
||||
generateDataKey,
|
||||
wrapDataKey,
|
||||
encryptData,
|
||||
encryptString,
|
||||
} from "$lib/modules/crypto";
|
||||
import { generateDataKey, wrapDataKey, encryptData, encryptString } from "$lib/modules/crypto";
|
||||
import type {
|
||||
DirectoryRenameRequest,
|
||||
DirectoryCreateRequest,
|
||||
@@ -28,42 +22,46 @@ export const requestDirectoryCreation = async (
|
||||
masterKey: MasterKey,
|
||||
) => {
|
||||
const { dataKey, dataKeyVersion } = await generateDataKey();
|
||||
const nameEncrypted = await encryptData(new TextEncoder().encode(name), dataKey);
|
||||
const nameEncrypted = await encryptString(name, dataKey);
|
||||
await callPostApi<DirectoryCreateRequest>("/api/directory/create", {
|
||||
parentId,
|
||||
mekVersion: masterKey.version,
|
||||
dek: await wrapDataKey(dataKey, masterKey.key),
|
||||
dekVersion: dataKeyVersion.toISOString(),
|
||||
name: encodeToBase64(nameEncrypted.ciphertext),
|
||||
name: nameEncrypted.ciphertext,
|
||||
nameIv: nameEncrypted.iv,
|
||||
});
|
||||
};
|
||||
|
||||
export const requestFileUpload = (file: File, parentId: "root" | number, masterKey: MasterKey) => {
|
||||
return new Promise<void>(async (resolve, reject) => {
|
||||
const { dataKey, dataKeyVersion } = await generateDataKey();
|
||||
const fileEncrypted = await encryptData(await file.arrayBuffer(), dataKey);
|
||||
const nameEncrypted = await encryptString(file.name, dataKey);
|
||||
export const requestFileUpload = async (
|
||||
file: File,
|
||||
parentId: "root" | number,
|
||||
masterKey: MasterKey,
|
||||
) => {
|
||||
const { dataKey, dataKeyVersion } = await generateDataKey();
|
||||
const fileEncrypted = await encryptData(await file.arrayBuffer(), dataKey);
|
||||
const nameEncrypted = await encryptString(file.name, dataKey);
|
||||
|
||||
const form = new FormData();
|
||||
form.set(
|
||||
"metadata",
|
||||
JSON.stringify({
|
||||
parentId,
|
||||
mekVersion: masterKey.version,
|
||||
dek: await wrapDataKey(dataKey, masterKey.key),
|
||||
dekVersion: dataKeyVersion.toISOString(),
|
||||
contentType: file.type,
|
||||
contentIv: fileEncrypted.iv,
|
||||
name: nameEncrypted.ciphertext,
|
||||
nameIv: nameEncrypted.iv,
|
||||
} satisfies FileUploadRequest),
|
||||
);
|
||||
form.set("content", new Blob([fileEncrypted.ciphertext]));
|
||||
const form = new FormData();
|
||||
form.set(
|
||||
"metadata",
|
||||
JSON.stringify({
|
||||
parentId,
|
||||
mekVersion: masterKey.version,
|
||||
dek: await wrapDataKey(dataKey, masterKey.key),
|
||||
dekVersion: dataKeyVersion.toISOString(),
|
||||
contentType: file.type,
|
||||
contentIv: fileEncrypted.iv,
|
||||
name: nameEncrypted.ciphertext,
|
||||
nameIv: nameEncrypted.iv,
|
||||
} satisfies FileUploadRequest),
|
||||
);
|
||||
form.set("content", new Blob([fileEncrypted.ciphertext]));
|
||||
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
// TODO: Progress, Scheduling, ...
|
||||
const xhr = new XMLHttpRequest();
|
||||
|
||||
const xhr = new XMLHttpRequest();
|
||||
xhr.addEventListener("load", () => {
|
||||
if (xhr.status === 200) {
|
||||
resolve();
|
||||
|
||||
Reference in New Issue
Block a user