From ea0f0e4a71e7d6bc569043dbed73b4217b124cd8 Mon Sep 17 00:00:00 2001 From: static Date: Tue, 14 Jan 2025 01:03:26 +0900 Subject: [PATCH 01/30] =?UTF-8?q?=ED=8C=8C=EC=9D=BC=20=EC=BA=90=EC=8B=9C?= =?UTF-8?q?=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/hooks.client.ts | 10 +++- src/lib/indexedDB/cacheIndex.ts | 24 +++++++++ src/lib/indexedDB/index.ts | 2 + .../{indexedDB.ts => indexedDB/keyStore.ts} | 0 src/lib/modules/cache.ts | 33 ++++++++++++ src/lib/modules/opfs.ts | 53 +++++++++++++++++++ src/routes/(fullscreen)/file/[id]/service.ts | 7 ++- 7 files changed, 127 insertions(+), 2 deletions(-) create mode 100644 src/lib/indexedDB/cacheIndex.ts create mode 100644 src/lib/indexedDB/index.ts rename src/lib/{indexedDB.ts => indexedDB/keyStore.ts} (100%) create mode 100644 src/lib/modules/cache.ts create mode 100644 src/lib/modules/opfs.ts diff --git a/src/hooks.client.ts b/src/hooks.client.ts index 3f0ccfb..10d9e4e 100644 --- a/src/hooks.client.ts +++ b/src/hooks.client.ts @@ -1,5 +1,7 @@ import type { ClientInit } from "@sveltejs/kit"; import { getClientKey, getMasterKeys, getHmacSecrets } from "$lib/indexedDB"; +import { prepareFileCache } from "$lib/modules/cache"; +import { prepareOpfs } from "$lib/modules/opfs"; import { clientKeyStore, masterKeyStore, hmacSecretStore } from "$lib/stores"; const prepareClientKeyStore = async () => { @@ -29,5 +31,11 @@ const prepareHmacSecretStore = async () => { }; export const init: ClientInit = async () => { - await Promise.all([prepareClientKeyStore(), prepareMasterKeyStore(), prepareHmacSecretStore()]); + await Promise.all([ + prepareFileCache(), + prepareClientKeyStore(), + prepareMasterKeyStore(), + prepareHmacSecretStore(), + prepareOpfs(), + ]); }; diff --git a/src/lib/indexedDB/cacheIndex.ts b/src/lib/indexedDB/cacheIndex.ts new file mode 100644 index 0000000..fb03377 --- /dev/null +++ b/src/lib/indexedDB/cacheIndex.ts @@ -0,0 +1,24 @@ +import { Dexie, type EntityTable } from "dexie"; + +export interface FileCacheIndex { + fileId: number; + cachedAt: Date; + lastRetrievedAt: Date; + size: number; +} + +const cacheIndex = new Dexie("cacheIndex") as Dexie & { + fileCache: EntityTable; +}; + +cacheIndex.version(1).stores({ + fileCache: "fileId", +}); + +export const getFileCacheIndex = async () => { + return await cacheIndex.fileCache.toArray(); +}; + +export const storeFileCacheIndex = async (fileCacheIndex: FileCacheIndex) => { + await cacheIndex.fileCache.put(fileCacheIndex); +}; diff --git a/src/lib/indexedDB/index.ts b/src/lib/indexedDB/index.ts new file mode 100644 index 0000000..c9bb3d0 --- /dev/null +++ b/src/lib/indexedDB/index.ts @@ -0,0 +1,2 @@ +export * from "./cacheIndex"; +export * from "./keyStore"; diff --git a/src/lib/indexedDB.ts b/src/lib/indexedDB/keyStore.ts similarity index 100% rename from src/lib/indexedDB.ts rename to src/lib/indexedDB/keyStore.ts diff --git a/src/lib/modules/cache.ts b/src/lib/modules/cache.ts new file mode 100644 index 0000000..0bd4342 --- /dev/null +++ b/src/lib/modules/cache.ts @@ -0,0 +1,33 @@ +import { getFileCacheIndex, storeFileCacheIndex, type FileCacheIndex } from "$lib/indexedDB"; +import { readFileFromOpfs, writeFileToOpfs } from "$lib/modules/opfs"; + +const fileCacheIndex = new Map(); + +export const prepareFileCache = async () => { + for (const cache of await getFileCacheIndex()) { + fileCacheIndex.set(cache.fileId, cache); + } +}; + +export const getFileCache = async (fileId: number) => { + const cacheIndex = fileCacheIndex.get(fileId); + if (!cacheIndex) return null; + + cacheIndex.lastRetrievedAt = new Date(); + storeFileCacheIndex(cacheIndex); // Intended + return await readFileFromOpfs(`/cache/${fileId}`); +}; + +export const storeFileCache = async (fileId: number, fileBuffer: ArrayBuffer) => { + const now = new Date(); + await writeFileToOpfs(`/cache/${fileId}`, fileBuffer); + + const cacheIndex: FileCacheIndex = { + fileId, + cachedAt: now, + lastRetrievedAt: now, + size: fileBuffer.byteLength, + }; + fileCacheIndex.set(fileId, cacheIndex); + await storeFileCacheIndex(cacheIndex); +}; diff --git a/src/lib/modules/opfs.ts b/src/lib/modules/opfs.ts new file mode 100644 index 0000000..f96e6ba --- /dev/null +++ b/src/lib/modules/opfs.ts @@ -0,0 +1,53 @@ +let rootHandle: FileSystemDirectoryHandle | null = null; + +export const prepareOpfs = async () => { + rootHandle = await navigator.storage.getDirectory(); +}; + +const getFileHandle = async (path: string, create = true) => { + if (!rootHandle) { + throw new Error("OPFS not prepared"); + } else if (path[0] !== "/") { + throw new Error("Path must be absolute"); + } + + const parts = path.split("/"); + if (parts.length <= 1) { + throw new Error("Invalid path"); + } + + try { + let directoryHandle: FileSystemDirectoryHandle = rootHandle; + + for (const part of parts.slice(0, -1)) { + if (!part) continue; + directoryHandle = await directoryHandle.getDirectoryHandle(part, { create }); + } + + return directoryHandle.getFileHandle(parts[parts.length - 1]!, { create }); + } catch (e) { + if (e instanceof DOMException && e.name === "NotFoundError") { + return null; + } + throw e; + } +}; + +export const readFileFromOpfs = async (path: string) => { + const fileHandle = await getFileHandle(path, false); + if (!fileHandle) return null; + + const file = await fileHandle.getFile(); + return await file.arrayBuffer(); +}; + +export const writeFileToOpfs = async (path: string, data: ArrayBuffer) => { + const fileHandle = await getFileHandle(path); + const writable = await fileHandle!.createWritable(); + + try { + await writable.write(data); + } finally { + await writable.close(); + } +}; diff --git a/src/routes/(fullscreen)/file/[id]/service.ts b/src/routes/(fullscreen)/file/[id]/service.ts index fc97c3e..dfdb92b 100644 --- a/src/routes/(fullscreen)/file/[id]/service.ts +++ b/src/routes/(fullscreen)/file/[id]/service.ts @@ -1,10 +1,14 @@ +import { getFileCache, storeFileCache } from "$lib/modules/cache"; import { decryptData } from "$lib/modules/crypto"; -export const requestFileDownload = ( +export const requestFileDownload = async ( fileId: number, fileEncryptedIv: string, dataKey: CryptoKey, ) => { + const cache = await getFileCache(fileId); + if (cache) return cache; + return new Promise((resolve, reject) => { const xhr = new XMLHttpRequest(); xhr.responseType = "arraybuffer"; @@ -21,6 +25,7 @@ export const requestFileDownload = ( dataKey, ); resolve(fileDecrypted); + await storeFileCache(fileId, fileDecrypted); }); // TODO: Progress, ... From f37df53991b4df2d007bbd7c93039280bff1521b Mon Sep 17 00:00:00 2001 From: static Date: Tue, 14 Jan 2025 03:07:54 +0900 Subject: [PATCH 02/30] =?UTF-8?q?=EC=BA=90=EC=8B=9C=20=EB=AA=A9=EB=A1=9D?= =?UTF-8?q?=20=ED=8E=98=EC=9D=B4=EC=A7=80=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/lib/modules/cache.ts | 16 ++++-- src/lib/modules/file.ts | 8 ++- src/lib/modules/util.ts | 22 +++++++ .../(fullscreen)/setting/cache/+page.svelte | 57 +++++++++++++++++++ .../(fullscreen)/setting/cache/File.svelte | 42 ++++++++++++++ .../(fullscreen)/setting/cache/service.ts | 1 + .../[[id]]/DirectoryEntries/File.svelte | 6 +- .../[[id]]/DirectoryEntries/service.ts | 14 +---- src/routes/(main)/menu/+page.svelte | 39 +++++++------ src/routes/(main)/menu/MenuEntryButton.svelte | 25 ++++++++ 10 files changed, 194 insertions(+), 36 deletions(-) create mode 100644 src/lib/modules/util.ts create mode 100644 src/routes/(fullscreen)/setting/cache/+page.svelte create mode 100644 src/routes/(fullscreen)/setting/cache/File.svelte create mode 100644 src/routes/(fullscreen)/setting/cache/service.ts create mode 100644 src/routes/(main)/menu/MenuEntryButton.svelte diff --git a/src/lib/modules/cache.ts b/src/lib/modules/cache.ts index 0bd4342..383fb75 100644 --- a/src/lib/modules/cache.ts +++ b/src/lib/modules/cache.ts @@ -1,20 +1,28 @@ -import { getFileCacheIndex, storeFileCacheIndex, type FileCacheIndex } from "$lib/indexedDB"; +import { + getFileCacheIndex as getFileCacheIndexFromIndexedDB, + storeFileCacheIndex as storeFileCacheIndexToIndexedDB, + type FileCacheIndex, +} from "$lib/indexedDB"; import { readFileFromOpfs, writeFileToOpfs } from "$lib/modules/opfs"; const fileCacheIndex = new Map(); export const prepareFileCache = async () => { - for (const cache of await getFileCacheIndex()) { + for (const cache of await getFileCacheIndexFromIndexedDB()) { fileCacheIndex.set(cache.fileId, cache); } }; +export const getFileCacheIndex = () => { + return Array.from(fileCacheIndex.values()); +}; + export const getFileCache = async (fileId: number) => { const cacheIndex = fileCacheIndex.get(fileId); if (!cacheIndex) return null; cacheIndex.lastRetrievedAt = new Date(); - storeFileCacheIndex(cacheIndex); // Intended + storeFileCacheIndexToIndexedDB(cacheIndex); // Intended return await readFileFromOpfs(`/cache/${fileId}`); }; @@ -29,5 +37,5 @@ export const storeFileCache = async (fileId: number, fileBuffer: ArrayBuffer) => size: fileBuffer.byteLength, }; fileCacheIndex.set(fileId, cacheIndex); - await storeFileCacheIndex(cacheIndex); + await storeFileCacheIndexToIndexedDB(cacheIndex); }; diff --git a/src/lib/modules/file.ts b/src/lib/modules/file.ts index 82d136e..342febc 100644 --- a/src/lib/modules/file.ts +++ b/src/lib/modules/file.ts @@ -63,7 +63,13 @@ const fetchFileInfo = async ( infoStore: Writable, ) => { const res = await callGetApi(`/api/file/${fileId}`); - if (!res.ok) throw new Error("Failed to fetch file information"); + if (!res.ok) { + if (res.status === 404) { + infoStore.update(() => null); + return; + } + throw new Error("Failed to fetch file information"); + } const metadata: FileInfoResponse = await res.json(); const { dataKey } = await unwrapDataKey(metadata.dek, masterKey); diff --git a/src/lib/modules/util.ts b/src/lib/modules/util.ts new file mode 100644 index 0000000..31e971b --- /dev/null +++ b/src/lib/modules/util.ts @@ -0,0 +1,22 @@ +const pad2 = (num: number) => num.toString().padStart(2, "0"); + +export const formatDate = (date: Date) => { + const year = date.getFullYear(); + const month = date.getMonth() + 1; + const day = date.getDate(); + return `${year}. ${month}. ${day}.`; +}; + +export const formatDateTime = (date: Date) => { + const dateFormatted = formatDate(date); + const hours = date.getHours(); + const minutes = date.getMinutes(); + return `${dateFormatted} ${pad2(hours)}:${pad2(minutes)}`; +}; + +export const formatFileSize = (size: number) => { + if (size < 1024) return `${size} B`; + if (size < 1024 * 1024) return `${(size / 1024).toFixed(1)} KiB`; + if (size < 1024 * 1024 * 1024) return `${(size / 1024 / 1024).toFixed(1)} MiB`; + return `${(size / 1024 / 1024 / 1024).toFixed(1)} GiB`; +}; diff --git a/src/routes/(fullscreen)/setting/cache/+page.svelte b/src/routes/(fullscreen)/setting/cache/+page.svelte new file mode 100644 index 0000000..4c799fc --- /dev/null +++ b/src/routes/(fullscreen)/setting/cache/+page.svelte @@ -0,0 +1,57 @@ + + + + 캐시 설정 + + +
+ + {#if fileCache} +
+
+

+ {fileCache.length}개 파일이 캐시되어 {formatFileSize(fileCacheTotalSize)}를 사용하고 + 있어요. +

+

캐시를 삭제하더라도 원본 파일은 삭제되지 않아요.

+
+
+ {#each fileCache as { index, fileInfo }} + + {/each} +
+
+ {:else} +
+

캐시 목록을 불러오고 있어요.

+
+ {/if} +
diff --git a/src/routes/(fullscreen)/setting/cache/File.svelte b/src/routes/(fullscreen)/setting/cache/File.svelte new file mode 100644 index 0000000..f5dfcbb --- /dev/null +++ b/src/routes/(fullscreen)/setting/cache/File.svelte @@ -0,0 +1,42 @@ + + +
+ {#if $info} +
+ +
+ {:else} +
+ +
+ {/if} +
+ {#if $info} +

{$info.name}

+ {:else} +

삭제된 파일

+ {/if} +

+ 읽음 {formatDate(index.lastRetrievedAt)} · {formatFileSize(index.size)} +

+
+ +
diff --git a/src/routes/(fullscreen)/setting/cache/service.ts b/src/routes/(fullscreen)/setting/cache/service.ts new file mode 100644 index 0000000..ec16564 --- /dev/null +++ b/src/routes/(fullscreen)/setting/cache/service.ts @@ -0,0 +1 @@ +export { formatDate, formatFileSize } from "$lib/modules/util"; diff --git a/src/routes/(main)/directory/[[id]]/DirectoryEntries/File.svelte b/src/routes/(main)/directory/[[id]]/DirectoryEntries/File.svelte index c95ba6d..80479ea 100644 --- a/src/routes/(main)/directory/[[id]]/DirectoryEntries/File.svelte +++ b/src/routes/(main)/directory/[[id]]/DirectoryEntries/File.svelte @@ -1,7 +1,7 @@ + + +
+
+ +
+

+ {@render children?.()} +

+
+
From 27d2b834643754da70b18c42281611453daffc17 Mon Sep 17 00:00:00 2001 From: static Date: Tue, 14 Jan 2025 03:26:32 +0900 Subject: [PATCH 03/30] =?UTF-8?q?=EC=BA=90=EC=8B=9C=20=EC=82=AD=EC=A0=9C?= =?UTF-8?q?=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/lib/indexedDB/cacheIndex.ts | 4 ++++ src/lib/modules/cache.ts | 19 +++++++++++----- src/lib/modules/opfs.ts | 22 +++++++++++++------ .../(fullscreen)/setting/cache/+page.svelte | 9 ++++++-- .../(fullscreen)/setting/cache/File.svelte | 12 ++++++---- .../(fullscreen)/setting/cache/service.ts | 6 +++++ src/routes/(main)/directory/[[id]]/service.ts | 2 ++ 7 files changed, 55 insertions(+), 19 deletions(-) diff --git a/src/lib/indexedDB/cacheIndex.ts b/src/lib/indexedDB/cacheIndex.ts index fb03377..c820007 100644 --- a/src/lib/indexedDB/cacheIndex.ts +++ b/src/lib/indexedDB/cacheIndex.ts @@ -22,3 +22,7 @@ export const getFileCacheIndex = async () => { export const storeFileCacheIndex = async (fileCacheIndex: FileCacheIndex) => { await cacheIndex.fileCache.put(fileCacheIndex); }; + +export const deleteFileCacheIndex = async (fileId: number) => { + await cacheIndex.fileCache.delete(fileId); +}; diff --git a/src/lib/modules/cache.ts b/src/lib/modules/cache.ts index 383fb75..62c92c0 100644 --- a/src/lib/modules/cache.ts +++ b/src/lib/modules/cache.ts @@ -1,9 +1,10 @@ import { getFileCacheIndex as getFileCacheIndexFromIndexedDB, - storeFileCacheIndex as storeFileCacheIndexToIndexedDB, + storeFileCacheIndex, + deleteFileCacheIndex, type FileCacheIndex, } from "$lib/indexedDB"; -import { readFileFromOpfs, writeFileToOpfs } from "$lib/modules/opfs"; +import { readFile, writeFile, deleteFile } from "$lib/modules/opfs"; const fileCacheIndex = new Map(); @@ -22,13 +23,13 @@ export const getFileCache = async (fileId: number) => { if (!cacheIndex) return null; cacheIndex.lastRetrievedAt = new Date(); - storeFileCacheIndexToIndexedDB(cacheIndex); // Intended - return await readFileFromOpfs(`/cache/${fileId}`); + storeFileCacheIndex(cacheIndex); // Intended + return await readFile(`/cache/${fileId}`); }; export const storeFileCache = async (fileId: number, fileBuffer: ArrayBuffer) => { const now = new Date(); - await writeFileToOpfs(`/cache/${fileId}`, fileBuffer); + await writeFile(`/cache/${fileId}`, fileBuffer); const cacheIndex: FileCacheIndex = { fileId, @@ -37,5 +38,11 @@ export const storeFileCache = async (fileId: number, fileBuffer: ArrayBuffer) => size: fileBuffer.byteLength, }; fileCacheIndex.set(fileId, cacheIndex); - await storeFileCacheIndexToIndexedDB(cacheIndex); + await storeFileCacheIndex(cacheIndex); +}; + +export const deleteFileCache = async (fileId: number) => { + await deleteFile(`/cache/${fileId}`); + fileCacheIndex.delete(fileId); + await deleteFileCacheIndex(fileId); }; diff --git a/src/lib/modules/opfs.ts b/src/lib/modules/opfs.ts index f96e6ba..8aedc43 100644 --- a/src/lib/modules/opfs.ts +++ b/src/lib/modules/opfs.ts @@ -18,31 +18,32 @@ const getFileHandle = async (path: string, create = true) => { try { let directoryHandle: FileSystemDirectoryHandle = rootHandle; - for (const part of parts.slice(0, -1)) { if (!part) continue; directoryHandle = await directoryHandle.getDirectoryHandle(part, { create }); } - return directoryHandle.getFileHandle(parts[parts.length - 1]!, { create }); + const filename = parts[parts.length - 1]!; + const fileHandle = await directoryHandle.getFileHandle(filename, { create }); + return { parentHandle: directoryHandle, filename, fileHandle }; } catch (e) { if (e instanceof DOMException && e.name === "NotFoundError") { - return null; + return {}; } throw e; } }; -export const readFileFromOpfs = async (path: string) => { - const fileHandle = await getFileHandle(path, false); +export const readFile = async (path: string) => { + const { fileHandle } = await getFileHandle(path, false); if (!fileHandle) return null; const file = await fileHandle.getFile(); return await file.arrayBuffer(); }; -export const writeFileToOpfs = async (path: string, data: ArrayBuffer) => { - const fileHandle = await getFileHandle(path); +export const writeFile = async (path: string, data: ArrayBuffer) => { + const { fileHandle } = await getFileHandle(path); const writable = await fileHandle!.createWritable(); try { @@ -51,3 +52,10 @@ export const writeFileToOpfs = async (path: string, data: ArrayBuffer) => { await writable.close(); } }; + +export const deleteFile = async (path: string) => { + const { parentHandle, filename } = await getFileHandle(path, false); + if (!parentHandle) return; + + await parentHandle.removeEntry(filename); +}; diff --git a/src/routes/(fullscreen)/setting/cache/+page.svelte b/src/routes/(fullscreen)/setting/cache/+page.svelte index 4c799fc..a32b5c6 100644 --- a/src/routes/(fullscreen)/setting/cache/+page.svelte +++ b/src/routes/(fullscreen)/setting/cache/+page.svelte @@ -7,7 +7,7 @@ import { getFileInfo } from "$lib/modules/file"; import { masterKeyStore, type FileInfo } from "$lib/stores"; import File from "./File.svelte"; - import { formatFileSize } from "./service"; + import { formatFileSize, deleteFileCache as doDeleteFileCache } from "./service"; interface FileCache { index: FileCacheIndex; @@ -17,6 +17,11 @@ let fileCache: FileCache[] | undefined = $state(); let fileCacheTotalSize = $state(0); + const deleteFileCache = async (fileId: number) => { + await doDeleteFileCache(fileId); + fileCache = fileCache?.filter(({ index }) => index.fileId !== fileId); + }; + onMount(() => { fileCache = getFileCacheIndex() .map((index) => ({ @@ -45,7 +50,7 @@
{#each fileCache as { index, fileInfo }} - + {/each}
diff --git a/src/routes/(fullscreen)/setting/cache/File.svelte b/src/routes/(fullscreen)/setting/cache/File.svelte index f5dfcbb..e92cc5c 100644 --- a/src/routes/(fullscreen)/setting/cache/File.svelte +++ b/src/routes/(fullscreen)/setting/cache/File.svelte @@ -6,14 +6,15 @@ import IconDraft from "~icons/material-symbols/draft"; import IconScanDelete from "~icons/material-symbols/scan-delete"; - // import IconDelete from "~icons/material-symbols/delete"; + import IconDelete from "~icons/material-symbols/delete"; interface Props { index: FileCacheIndex; info: Writable; + onDeleteClick: (fileId: number) => void; } - let { index, info }: Props = $props(); + let { index, info, onDeleteClick }: Props = $props();
@@ -36,7 +37,10 @@ 읽음 {formatDate(index.lastRetrievedAt)} · {formatFileSize(index.size)}

- + diff --git a/src/routes/(fullscreen)/setting/cache/service.ts b/src/routes/(fullscreen)/setting/cache/service.ts index ec16564..a3fb37b 100644 --- a/src/routes/(fullscreen)/setting/cache/service.ts +++ b/src/routes/(fullscreen)/setting/cache/service.ts @@ -1 +1,7 @@ +import { deleteFileCache as doDeleteFileCache } from "$lib/modules/cache"; + export { formatDate, formatFileSize } from "$lib/modules/util"; + +export const deleteFileCache = async (fileId: number) => { + await doDeleteFileCache(fileId); +}; diff --git a/src/routes/(main)/directory/[[id]]/service.ts b/src/routes/(main)/directory/[[id]]/service.ts index fa3fcfc..bf1f867 100644 --- a/src/routes/(main)/directory/[[id]]/service.ts +++ b/src/routes/(main)/directory/[[id]]/service.ts @@ -1,6 +1,7 @@ import ExifReader from "exifreader"; import { callGetApi, callPostApi } from "$lib/hooks"; import { storeHmacSecrets } from "$lib/indexedDB"; +import { deleteFileCache } from "$lib/modules/cache"; import { encodeToBase64, generateDataKey, @@ -191,4 +192,5 @@ export const requestDirectoryEntryRename = async ( export const requestDirectoryEntryDeletion = async (entry: SelectedDirectoryEntry) => { await callPostApi(`/api/${entry.type}/${entry.id}/delete`); + await deleteFileCache(entry.id); }; From 4bd666a5d51cd60324ac428814465233a59ac962 Mon Sep 17 00:00:00 2001 From: static Date: Tue, 14 Jan 2025 03:37:31 +0900 Subject: [PATCH 04/30] =?UTF-8?q?=EB=94=94=EB=A0=89=ED=84=B0=EB=A6=AC?= =?UTF-8?q?=EB=A5=BC=20=EC=82=AD=EC=A0=9C=ED=95=98=EB=8A=94=20=EA=B2=BD?= =?UTF-8?q?=EC=9A=B0,=20=EB=94=94=EB=A0=89=ED=84=B0=EB=A6=AC=20=ED=95=98?= =?UTF-8?q?=EC=9C=84=EC=97=90=20=EC=9E=88=EB=8D=98=20=ED=8C=8C=EC=9D=BC?= =?UTF-8?q?=EC=9D=98=20=EC=BA=90=EC=8B=9C=EB=A5=BC=20=EC=9E=90=EB=8F=99?= =?UTF-8?q?=EC=9C=BC=EB=A1=9C=20=EC=82=AD=EC=A0=9C=ED=95=98=EB=8F=84?= =?UTF-8?q?=EB=A1=9D=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/lib/server/db/file.ts | 13 +++++++------ src/lib/server/schemas/directory.ts | 5 +++++ src/lib/server/services/directory.ts | 9 +++++++-- .../(fullscreen)/setting/cache/+page.svelte | 17 ++++++++++++++--- src/routes/(main)/directory/[[id]]/service.ts | 14 ++++++++++++-- src/routes/api/directory/[id]/delete/+server.ts | 9 ++++++--- 6 files changed, 51 insertions(+), 16 deletions(-) diff --git a/src/lib/server/db/file.ts b/src/lib/server/db/file.ts index b99bd38..a42235b 100644 --- a/src/lib/server/db/file.ts +++ b/src/lib/server/db/file.ts @@ -129,14 +129,15 @@ export const unregisterDirectory = async (userId: number, directoryId: number) = return await db.transaction( async (tx) => { const unregisterFiles = async (parentId: number) => { - const files = await tx + return await tx .delete(file) .where(and(eq(file.userId, userId), eq(file.parentId, parentId))) - .returning({ path: file.path }); - return files.map(({ path }) => path); + .returning({ id: file.id, path: file.path }); }; - const unregisterDirectoryRecursively = async (directoryId: number): Promise => { - const filePaths = await unregisterFiles(directoryId); + const unregisterDirectoryRecursively = async ( + directoryId: number, + ): Promise<{ id: number; path: string }[]> => { + const files = await unregisterFiles(directoryId); const subDirectories = await tx .select({ id: directory.id }) .from(directory) @@ -149,7 +150,7 @@ export const unregisterDirectory = async (userId: number, directoryId: number) = if (deleteRes.changes === 0) { throw new IntegrityError("Directory not found"); } - return filePaths.concat(...subDirectoryFilePaths); + return files.concat(...subDirectoryFilePaths); }; return await unregisterDirectoryRecursively(directoryId); }, diff --git a/src/lib/server/schemas/directory.ts b/src/lib/server/schemas/directory.ts index 5f526aa..17d5720 100644 --- a/src/lib/server/schemas/directory.ts +++ b/src/lib/server/schemas/directory.ts @@ -15,6 +15,11 @@ export const directoryInfoResponse = z.object({ }); export type DirectoryInfoResponse = z.infer; +export const directoryDeleteResponse = z.object({ + deletedFiles: z.number().int().positive().array(), +}); +export type DirectoryDeleteResponse = z.infer; + export const directoryRenameRequest = z.object({ dekVersion: z.string().datetime(), name: z.string().base64().nonempty(), diff --git a/src/lib/server/services/directory.ts b/src/lib/server/services/directory.ts index aba0343..3f6b55d 100644 --- a/src/lib/server/services/directory.ts +++ b/src/lib/server/services/directory.ts @@ -34,8 +34,13 @@ export const getDirectoryInformation = async (userId: number, directoryId: "root export const deleteDirectory = async (userId: number, directoryId: number) => { try { - const filePaths = await unregisterDirectory(userId, directoryId); - filePaths.map((path) => unlink(path)); // Intended + const files = await unregisterDirectory(userId, directoryId); + return { + files: files.map(({ id, path }) => { + unlink(path); // Intended + return id; + }), + }; } catch (e) { if (e instanceof IntegrityError && e.message === "Directory not found") { error(404, "Invalid directory id"); diff --git a/src/routes/(fullscreen)/setting/cache/+page.svelte b/src/routes/(fullscreen)/setting/cache/+page.svelte index a32b5c6..6f3770e 100644 --- a/src/routes/(fullscreen)/setting/cache/+page.svelte +++ b/src/routes/(fullscreen)/setting/cache/+page.svelte @@ -29,7 +29,12 @@ fileInfo: getFileInfo(index.fileId, $masterKeyStore?.get(1)?.key!), })) .sort((a, b) => a.index.lastRetrievedAt.getTime() - b.index.lastRetrievedAt.getTime()); - fileCacheTotalSize = fileCache.reduce((acc, { index }) => acc + index.size, 0); + }); + + $effect(() => { + if (fileCache) { + fileCacheTotalSize = fileCache.reduce((acc, { index }) => acc + index.size, 0); + } }); @@ -39,7 +44,7 @@
- {#if fileCache} + {#if fileCache && fileCache.length > 0}

@@ -56,7 +61,13 @@

{:else}
-

캐시 목록을 불러오고 있어요.

+

+ {#if fileCache} + 캐시된 파일이 없어요. + {:else} + 캐시 목록을 불러오고 있어요. + {/if} +

{/if}
diff --git a/src/routes/(main)/directory/[[id]]/service.ts b/src/routes/(main)/directory/[[id]]/service.ts index bf1f867..00d28ab 100644 --- a/src/routes/(main)/directory/[[id]]/service.ts +++ b/src/routes/(main)/directory/[[id]]/service.ts @@ -19,6 +19,7 @@ import type { HmacSecretListResponse, DuplicateFileScanRequest, DuplicateFileScanResponse, + DirectoryDeleteResponse, } from "$lib/server/schemas"; import { hmacSecretStore, type MasterKey, type HmacSecret } from "$lib/stores"; @@ -191,6 +192,15 @@ export const requestDirectoryEntryRename = async ( }; export const requestDirectoryEntryDeletion = async (entry: SelectedDirectoryEntry) => { - await callPostApi(`/api/${entry.type}/${entry.id}/delete`); - await deleteFileCache(entry.id); + const res = await callPostApi(`/api/${entry.type}/${entry.id}/delete`); + if (!res.ok) return false; + + if (entry.type === "directory") { + const { deletedFiles }: DirectoryDeleteResponse = await res.json(); + await Promise.all(deletedFiles.map(deleteFileCache)); + return true; + } else { + await deleteFileCache(entry.id); + return true; + } }; diff --git a/src/routes/api/directory/[id]/delete/+server.ts b/src/routes/api/directory/[id]/delete/+server.ts index 4873912..4d29fd8 100644 --- a/src/routes/api/directory/[id]/delete/+server.ts +++ b/src/routes/api/directory/[id]/delete/+server.ts @@ -1,6 +1,7 @@ -import { error, text } from "@sveltejs/kit"; +import { error, json } from "@sveltejs/kit"; import { z } from "zod"; import { authorize } from "$lib/server/modules/auth"; +import { directoryDeleteResponse, type DirectoryDeleteResponse } from "$lib/server/schemas"; import { deleteDirectory } from "$lib/server/services/directory"; import type { RequestHandler } from "./$types"; @@ -15,6 +16,8 @@ export const POST: RequestHandler = async ({ locals, params }) => { if (!zodRes.success) error(400, "Invalid path parameters"); const { id } = zodRes.data; - await deleteDirectory(userId, id); - return text("Directory deleted", { headers: { "Content-Type": "text/plain" } }); + const { files } = await deleteDirectory(userId, id); + return json( + directoryDeleteResponse.parse({ deletedFiles: files } satisfies DirectoryDeleteResponse), + ); }; From 6015a9bca47e64fc1421f2f37d5ece9ed69667fb Mon Sep 17 00:00:00 2001 From: static Date: Tue, 14 Jan 2025 03:39:44 +0900 Subject: [PATCH 05/30] =?UTF-8?q?=EC=BA=90=EC=8B=9C=EB=90=98=EC=A7=80=20?= =?UTF-8?q?=EC=95=8A=EC=9D=80=20=ED=8C=8C=EC=9D=BC=EC=9D=98=20=EC=BA=90?= =?UTF-8?q?=EC=8B=9C=EB=A5=BC=20=EC=82=AD=EC=A0=9C=ED=95=98=EB=A0=A4?= =?UTF-8?q?=EA=B3=A0=20=EC=8B=9C=EB=8F=84=ED=95=98=EB=8D=98=20=EB=B2=84?= =?UTF-8?q?=EA=B7=B8=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/lib/modules/cache.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/lib/modules/cache.ts b/src/lib/modules/cache.ts index 62c92c0..fe3c66c 100644 --- a/src/lib/modules/cache.ts +++ b/src/lib/modules/cache.ts @@ -42,7 +42,9 @@ export const storeFileCache = async (fileId: number, fileBuffer: ArrayBuffer) => }; export const deleteFileCache = async (fileId: number) => { - await deleteFile(`/cache/${fileId}`); + if (!fileCacheIndex.has(fileId)) return; + fileCacheIndex.delete(fileId); + await deleteFile(`/cache/${fileId}`); await deleteFileCacheIndex(fileId); }; From f4b9f870870227c2a5d1e603f02d2e5df50b211d Mon Sep 17 00:00:00 2001 From: static Date: Tue, 14 Jan 2025 18:06:41 +0900 Subject: [PATCH 06/30] =?UTF-8?q?=ED=8C=8C=EC=9D=BC=EC=9D=84=20=EC=97=85?= =?UTF-8?q?=EB=A1=9C=EB=93=9C=ED=95=A0=20=EB=95=8C=20=EC=8A=A4=ED=8A=B8?= =?UTF-8?q?=EB=A6=AC=EB=B0=8D=EC=9D=B4=20=EB=90=98=EC=A7=80=20=EC=95=8A?= =?UTF-8?q?=EA=B3=A0=20=EB=B2=84=ED=8D=BC=EB=A7=81=ED=95=98=EB=8D=98=20?= =?UTF-8?q?=EB=B2=84=EA=B7=B8=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package.json | 1 + pnpm-lock.yaml | 8 ++++ src/lib/server/services/file.ts | 9 ++-- .../(main)/directory/[[id]]/+page.svelte | 13 +++-- src/routes/api/file/upload/+server.ts | 48 +++++++++++++++++-- 5 files changed, 66 insertions(+), 13 deletions(-) diff --git a/package.json b/package.json index dfa988e..91c3c3e 100644 --- a/package.json +++ b/package.json @@ -50,6 +50,7 @@ "vite": "^5.4.11" }, "dependencies": { + "@fastify/busboy": "^3.1.1", "argon2": "^0.41.1", "better-sqlite3": "^11.7.2", "drizzle-orm": "^0.33.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index dccbc50..43441bd 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -8,6 +8,9 @@ importers: .: dependencies: + '@fastify/busboy': + specifier: ^3.1.1 + version: 3.1.1 argon2: specifier: ^0.41.1 version: 0.41.1 @@ -602,6 +605,9 @@ packages: resolution: {integrity: sha512-zSkKow6H5Kdm0ZUQUB2kV5JIXqoG0+uH5YADhaEHswm664N9Db8dXSi0nMJpacpMf+MyyglF1vnZohpEg5yUtg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@fastify/busboy@3.1.1': + resolution: {integrity: sha512-5DGmA8FTdB2XbDeEwc/5ZXBl6UbBAyBOOLlPuBnZ/N1SwdH9Ii+cOX3tBROlDgcTXxjOYnLMVoKk9+FXAw0CJw==} + '@humanfs/core@0.19.1': resolution: {integrity: sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==} engines: {node: '>=18.18.0'} @@ -2543,6 +2549,8 @@ snapshots: dependencies: levn: 0.4.1 + '@fastify/busboy@3.1.1': {} + '@humanfs/core@0.19.1': {} '@humanfs/node@0.16.6': diff --git a/src/lib/server/services/file.ts b/src/lib/server/services/file.ts index cb5559b..8a05eb6 100644 --- a/src/lib/server/services/file.ts +++ b/src/lib/server/services/file.ts @@ -2,7 +2,8 @@ import { error } from "@sveltejs/kit"; import { createReadStream, createWriteStream } from "fs"; import { mkdir, stat, unlink } from "fs/promises"; import { dirname } from "path"; -import { Readable, Writable } from "stream"; +import { Readable } from "stream"; +import { pipeline } from "stream/promises"; import { v4 as uuidv4 } from "uuid"; import { IntegrityError } from "$lib/server/db/error"; import { @@ -94,7 +95,7 @@ const safeUnlink = async (path: string) => { export const uploadFile = async ( params: Omit, - encContentStream: ReadableStream, + encContentStream: Readable, ) => { const oneMinuteAgo = new Date(Date.now() - 60 * 1000); const oneMinuteLater = new Date(Date.now() + 60 * 1000); @@ -106,9 +107,7 @@ export const uploadFile = async ( await mkdir(dirname(path), { recursive: true }); try { - await encContentStream.pipeTo( - Writable.toWeb(createWriteStream(path, { flags: "wx", mode: 0o600 })), - ); + await pipeline(encContentStream, createWriteStream(path, { flags: "wx", mode: 0o600 })); await registerFile({ ...params, path, diff --git a/src/routes/(main)/directory/[[id]]/+page.svelte b/src/routes/(main)/directory/[[id]]/+page.svelte index 5361fd7..866adbe 100644 --- a/src/routes/(main)/directory/[[id]]/+page.svelte +++ b/src/routes/(main)/directory/[[id]]/+page.svelte @@ -60,9 +60,16 @@ data.id, $masterKeyStore?.get(1)!, $hmacSecretStore?.get(1)!, - ).then(() => { - info = getDirectoryInfo(data.id, $masterKeyStore?.get(1)?.key!); // TODO: FIXME - }); + ) + .then(() => { + // TODO: FIXME + info = getDirectoryInfo(data.id, $masterKeyStore?.get(1)?.key!); + window.alert("파일이 업로드되었어요."); + }) + .catch(() => { + // TODO: FIXME + window.alert("파일 업로드에 실패했어요."); + }); }; const loadAndUploadFile = async () => { diff --git a/src/routes/api/file/upload/+server.ts b/src/routes/api/file/upload/+server.ts index 0b72402..66624f6 100644 --- a/src/routes/api/file/upload/+server.ts +++ b/src/routes/api/file/upload/+server.ts @@ -1,19 +1,57 @@ +import Busboy from "@fastify/busboy"; import { error, text } from "@sveltejs/kit"; +import { Readable, Writable } from "stream"; import { authorize } from "$lib/server/modules/auth"; import { fileUploadRequest } from "$lib/server/schemas"; import { uploadFile } from "$lib/server/services/file"; import type { RequestHandler } from "./$types"; +const parseFormData = async (contentType: string, body: ReadableStream) => { + return new Promise<{ metadata: string; content: Readable }>((resolve, reject) => { + let metadata: string | null = null; + let content: Readable | null = null; + + const bb = Busboy({ headers: { "content-type": contentType } }); + bb.on("field", (fieldname, val) => { + if (fieldname !== "metadata") return reject(new Error("Invalid request body")); + if (metadata || content) return reject(new Error("Invalid request body")); // metadata must be first + metadata = val; + }); + bb.on("file", (fieldname, file) => { + if (fieldname !== "content") return reject(new Error("Invalid request body")); + if (!metadata || content) return reject(new Error("Invalid request body")); // metadata must be first + content = file; + resolve({ metadata, content }); + }); + bb.on("finish", () => reject(new Error("Invalid request body"))); + bb.on("error", (e) => reject(e)); + + body.pipeTo(Writable.toWeb(bb)); + }); +}; + export const POST: RequestHandler = async ({ locals, request }) => { const { userId } = await authorize(locals, "activeClient"); - const form = await request.formData(); - const metadata = form.get("metadata"); - const content = form.get("content"); - if (typeof metadata !== "string" || !(content instanceof File)) { + const contentTypeHeader = request.headers.get("Content-Type"); + if (!contentTypeHeader?.startsWith("multipart/form-data") || !request.body) { error(400, "Invalid request body"); } + let metadata; + let content; + + try { + const formData = await parseFormData(contentTypeHeader, request.body); + metadata = formData.metadata; + content = formData.content; + } catch (e) { + if (e instanceof Error && e.message === "Invalid request body") { + error(400, "Invalid request body"); + } + throw e; + } + const zodRes = fileUploadRequest.safeParse(JSON.parse(metadata)); if (!zodRes.success) error(400, "Invalid request body"); const { @@ -53,7 +91,7 @@ export const POST: RequestHandler = async ({ locals, request }) => { encLastModifiedAt: lastModifiedAt, encLastModifiedAtIv: lastModifiedAtIv, }, - content.stream(), + content, ); return text("File uploaded", { headers: { "Content-Type": "text/plain" } }); }; From ed4da7b1df058fa6ce6d1a5e3bd4bea2b84fdb40 Mon Sep 17 00:00:00 2001 From: static Date: Wed, 15 Jan 2025 02:53:14 +0900 Subject: [PATCH 07/30] =?UTF-8?q?=ED=8C=8C=EC=9D=BC=20=EC=97=85=EB=A1=9C?= =?UTF-8?q?=EB=93=9C=EC=97=90=205=EB=B6=84=20=EC=9D=B4=EC=83=81=20?= =?UTF-8?q?=EA=B1=B8=EB=A6=AC=EB=8A=94=20=EA=B2=BD=EC=9A=B0=20408=20?= =?UTF-8?q?=EC=98=A4=EB=A5=98=EA=B0=80=20=EB=B0=9C=EC=83=9D=ED=95=98?= =?UTF-8?q?=EB=8D=98=20=EB=B2=84=EA=B7=B8=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Dockerfile | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Dockerfile b/Dockerfile index 87e0df0..eec42f6 100644 --- a/Dockerfile +++ b/Dockerfile @@ -13,6 +13,8 @@ COPY . . RUN pnpm install --offline RUN pnpm build +RUN sed -i "s/http\.createServer()/http.createServer({ requestTimeout: 0 })/g" ./build/index.js + # Deploy Stage FROM base RUN pnpm fetch --prod From 9f9c52ff94b65d03dc0e00d5f06e15d39a6c86dc Mon Sep 17 00:00:00 2001 From: static Date: Wed, 15 Jan 2025 06:09:27 +0900 Subject: [PATCH 08/30] =?UTF-8?q?=ED=8C=8C=EC=9D=BC=EC=9D=84=20=EC=97=85?= =?UTF-8?q?=EB=A1=9C=EB=93=9C=ED=95=98=EB=8A=94=20=EB=8F=84=EC=A4=91?= =?UTF-8?q?=EC=97=90=20HTTP=20=EC=97=B0=EA=B2=B0=EC=9D=B4=20=EB=81=8A?= =?UTF-8?q?=EA=B8=B0=EB=A9=B4=20=EC=84=9C=EB=B2=84=EA=B0=80=20=ED=81=AC?= =?UTF-8?q?=EB=9E=98=EC=8B=9C=EB=90=98=EB=8D=98=20=EB=B2=84=EA=B7=B8=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/lib/server/services/file.ts | 6 +- .../(main)/directory/[[id]]/+page.svelte | 4 +- src/routes/api/file/upload/+server.ts | 131 +++++++++--------- 3 files changed, 68 insertions(+), 73 deletions(-) diff --git a/src/lib/server/services/file.ts b/src/lib/server/services/file.ts index 8a05eb6..b3086f9 100644 --- a/src/lib/server/services/file.ts +++ b/src/lib/server/services/file.ts @@ -115,10 +115,8 @@ export const uploadFile = async ( } catch (e) { await safeUnlink(path); - if (e instanceof IntegrityError) { - if (e.message === "Inactive MEK version") { - error(400, "Invalid MEK version"); - } + if (e instanceof IntegrityError && e.message === "Inactive MEK version") { + error(400, "Invalid MEK version"); } throw e; } diff --git a/src/routes/(main)/directory/[[id]]/+page.svelte b/src/routes/(main)/directory/[[id]]/+page.svelte index 866adbe..c5e9f8b 100644 --- a/src/routes/(main)/directory/[[id]]/+page.svelte +++ b/src/routes/(main)/directory/[[id]]/+page.svelte @@ -66,9 +66,9 @@ info = getDirectoryInfo(data.id, $masterKeyStore?.get(1)?.key!); window.alert("파일이 업로드되었어요."); }) - .catch(() => { + .catch((e: Error) => { // TODO: FIXME - window.alert("파일 업로드에 실패했어요."); + window.alert(`파일 업로드에 실패했어요.\n${e.message}`); }); }; diff --git a/src/routes/api/file/upload/+server.ts b/src/routes/api/file/upload/+server.ts index 66624f6..b7b38ea 100644 --- a/src/routes/api/file/upload/+server.ts +++ b/src/routes/api/file/upload/+server.ts @@ -6,53 +6,10 @@ import { fileUploadRequest } from "$lib/server/schemas"; import { uploadFile } from "$lib/server/services/file"; import type { RequestHandler } from "./$types"; -const parseFormData = async (contentType: string, body: ReadableStream) => { - return new Promise<{ metadata: string; content: Readable }>((resolve, reject) => { - let metadata: string | null = null; - let content: Readable | null = null; +type FileMetadata = Parameters[0]; - const bb = Busboy({ headers: { "content-type": contentType } }); - bb.on("field", (fieldname, val) => { - if (fieldname !== "metadata") return reject(new Error("Invalid request body")); - if (metadata || content) return reject(new Error("Invalid request body")); // metadata must be first - metadata = val; - }); - bb.on("file", (fieldname, file) => { - if (fieldname !== "content") return reject(new Error("Invalid request body")); - if (!metadata || content) return reject(new Error("Invalid request body")); // metadata must be first - content = file; - resolve({ metadata, content }); - }); - bb.on("finish", () => reject(new Error("Invalid request body"))); - bb.on("error", (e) => reject(e)); - - body.pipeTo(Writable.toWeb(bb)); - }); -}; - -export const POST: RequestHandler = async ({ locals, request }) => { - const { userId } = await authorize(locals, "activeClient"); - - const contentTypeHeader = request.headers.get("Content-Type"); - if (!contentTypeHeader?.startsWith("multipart/form-data") || !request.body) { - error(400, "Invalid request body"); - } - - let metadata; - let content; - - try { - const formData = await parseFormData(contentTypeHeader, request.body); - metadata = formData.metadata; - content = formData.content; - } catch (e) { - if (e instanceof Error && e.message === "Invalid request body") { - error(400, "Invalid request body"); - } - throw e; - } - - const zodRes = fileUploadRequest.safeParse(JSON.parse(metadata)); +const parseFileMetadata = (userId: number, json: string) => { + const zodRes = fileUploadRequest.safeParse(JSON.parse(json)); if (!zodRes.success) error(400, "Invalid request body"); const { parentId, @@ -73,25 +30,65 @@ export const POST: RequestHandler = async ({ locals, request }) => { if ((createdAt && !createdAtIv) || (!createdAt && createdAtIv)) error(400, "Invalid request body"); - await uploadFile( - { - userId, - parentId, - mekVersion, - encDek: dek, - dekVersion: new Date(dekVersion), - hskVersion, - contentHmac, - contentType, - encContentIv: contentIv, - encName: name, - encNameIv: nameIv, - encCreatedAt: createdAt ?? null, - encCreatedAtIv: createdAtIv ?? null, - encLastModifiedAt: lastModifiedAt, - encLastModifiedAtIv: lastModifiedAtIv, - }, - content, - ); - return text("File uploaded", { headers: { "Content-Type": "text/plain" } }); + return { + userId, + parentId, + mekVersion, + encDek: dek, + dekVersion: new Date(dekVersion), + hskVersion, + contentHmac, + contentType, + encContentIv: contentIv, + encName: name, + encNameIv: nameIv, + encCreatedAt: createdAt ?? null, + encCreatedAtIv: createdAtIv ?? null, + encLastModifiedAt: lastModifiedAt, + encLastModifiedAtIv: lastModifiedAtIv, + } satisfies FileMetadata; +}; + +export const POST: RequestHandler = async ({ locals, request }) => { + const { userId } = await authorize(locals, "activeClient"); + + const contentType = request.headers.get("Content-Type"); + if (!contentType?.startsWith("multipart/form-data") || !request.body) { + error(400, "Invalid request body"); + } + + return new Promise((resolve, reject) => { + const bb = Busboy({ headers: { "content-type": contentType } }); + const handler = + (f: (...args: T) => Promise) => + (...args: T) => { + f(...args).catch(reject); + }; + + let metadata: FileMetadata | null = null; + let content: Readable | null = null; + + bb.on( + "field", + handler(async (fieldname, val) => { + if (fieldname !== "metadata") error(400, "Invalid request body"); + if (metadata || content) error(400, "Invalid request body"); + metadata = parseFileMetadata(userId, val); + }), + ); + bb.on( + "file", + handler(async (fieldname, file) => { + if (fieldname !== "content") error(400, "Invalid request body"); + if (!metadata || content) error(400, "Invalid request body"); + content = file; + + await uploadFile(metadata, content); + resolve(text("File uploaded", { headers: { "Content-Type": "text/plain" } })); + }), + ); + bb.on("error", (e) => content?.emit("error", e) ?? reject(e)); + + request.body!.pipeTo(Writable.toWeb(bb)).catch(() => {}); // busboy will handle the error + }); }; From 366f6571136a901c8bfc2902ada677bc38522303 Mon Sep 17 00:00:00 2001 From: static Date: Wed, 15 Jan 2025 08:34:50 +0900 Subject: [PATCH 09/30] =?UTF-8?q?heic2any=EB=A5=BC=20=EB=8F=99=EC=A0=81?= =?UTF-8?q?=EC=9C=BC=EB=A1=9C=20import=ED=95=98=EB=8F=84=EB=A1=9D=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/routes/(fullscreen)/file/[id]/+page.svelte | 2 +- src/routes/(fullscreen)/file/[id]/+page.ts | 2 -- src/routes/(fullscreen)/key/export/+page.svelte | 4 ++-- 3 files changed, 3 insertions(+), 5 deletions(-) diff --git a/src/routes/(fullscreen)/file/[id]/+page.svelte b/src/routes/(fullscreen)/file/[id]/+page.svelte index 642017a..b9fc0ff 100644 --- a/src/routes/(fullscreen)/file/[id]/+page.svelte +++ b/src/routes/(fullscreen)/file/[id]/+page.svelte @@ -1,6 +1,5 @@ -{#if info.subDirectoryIds.length + info.fileIds.length > 0} +{#if subDirectories.length + files.length > 0}
- {#each subDirectoryInfos as subDirectory} - + {#each subDirectories as { info }} + {/each} - {#each fileInfos as file} - + {#each files as file} + {#if file.type === "file"} + + {:else} + + {/if} {/each}
{:else} diff --git a/src/routes/(main)/directory/[[id]]/DirectoryEntries/UploadingFile.svelte b/src/routes/(main)/directory/[[id]]/DirectoryEntries/UploadingFile.svelte new file mode 100644 index 0000000..e50d4bf --- /dev/null +++ b/src/routes/(main)/directory/[[id]]/DirectoryEntries/UploadingFile.svelte @@ -0,0 +1,37 @@ + + +{#if $info.status !== "uploaded" && $info.status !== "canceled" && $info.status !== "error"} +
+
+ +
+
+

+ {$info.name} +

+

+ {#if $info.status === "encryption-pending"} + 준비 중 + {:else if $info.status === "encrypting"} + 암호화하는 중 + {:else if $info.status === "upload-pending"} + 업로드를 기다리는 중 + {:else if $info.status === "uploading"} + 전송됨 {formatUploadProgress($info.progress)} · {formatUploadRate($info.rate)} + {/if} +

+
+
+{/if} diff --git a/src/routes/(main)/directory/[[id]]/DirectoryEntries/service.ts b/src/routes/(main)/directory/[[id]]/DirectoryEntries/service.ts index a879d68..10f8e32 100644 --- a/src/routes/(main)/directory/[[id]]/DirectoryEntries/service.ts +++ b/src/routes/(main)/directory/[[id]]/DirectoryEntries/service.ts @@ -1,5 +1,4 @@ -import { get, type Writable } from "svelte/store"; -import type { DirectoryInfo, FileInfo } from "$lib/stores"; +import { formatFileSize } from "$lib/modules/util"; export { formatDateTime } from "$lib/modules/util"; @@ -8,17 +7,19 @@ export enum SortBy { NAME_DESC, } -type SortFunc = (a: DirectoryInfo | FileInfo | null, b: DirectoryInfo | FileInfo | null) => number; +type SortFunc = (a?: string, b?: string) => number; const sortByNameAsc: SortFunc = (a, b) => { - if (a && b) return a.name!.localeCompare(b.name!); + if (a && b) return a.localeCompare(b); + if (a) return -1; + if (b) return 1; return 0; }; const sortByNameDesc: SortFunc = (a, b) => -sortByNameAsc(a, b); -export const sortEntries = ( - entries: Writable[], +export const sortEntries = ( + entries: T[], sortBy: SortBy = SortBy.NAME_ASC, ) => { let sortFunc: SortFunc; @@ -28,5 +29,13 @@ export const sortEntries = ( sortFunc = sortByNameDesc; } - entries.sort((a, b) => sortFunc(get(a), get(b))); + entries.sort((a, b) => sortFunc(a.name, b.name)); +}; + +export const formatUploadProgress = (progress?: number) => { + return `${Math.floor((progress ?? 0) * 100)}%`; +}; + +export const formatUploadRate = (rate?: number) => { + return `${formatFileSize((rate ?? 0) / 8)}/s`; }; From 40745d5da492ac94bbc0a475ccead298f408c5ca Mon Sep 17 00:00:00 2001 From: static Date: Thu, 16 Jan 2025 04:25:20 +0900 Subject: [PATCH 12/30] =?UTF-8?q?=ED=8C=8C=EC=9D=BC/=EB=94=94=EB=A0=89?= =?UTF-8?q?=ED=84=B0=EB=A6=AC=20=EB=AA=A9=EB=A1=9D=EC=97=90=EC=84=9C=20?= =?UTF-8?q?=EB=AC=B4=ED=95=9C=ED=95=9C=20Request=EA=B0=80=20=EB=B0=9C?= =?UTF-8?q?=EC=83=9D=ED=95=98=EB=8D=98=20=EB=B2=84=EA=B7=B8=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../directory/[[id]]/DirectoryEntries/DirectoryEntries.svelte | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/routes/(main)/directory/[[id]]/DirectoryEntries/DirectoryEntries.svelte b/src/routes/(main)/directory/[[id]]/DirectoryEntries/DirectoryEntries.svelte index 0933650..647e174 100644 --- a/src/routes/(main)/directory/[[id]]/DirectoryEntries/DirectoryEntries.svelte +++ b/src/routes/(main)/directory/[[id]]/DirectoryEntries/DirectoryEntries.svelte @@ -84,9 +84,9 @@ sortEntries(subDirectories, sortBy); sortEntries(files, sortBy); }; - sort(); - return untrack(() => { + sort(); + const unsubscribes = subDirectories .map((subDirectory) => subDirectory.info.subscribe((value) => { From 0ed3d17fefde6a88ba14a5c06ccd4cd843745ea2 Mon Sep 17 00:00:00 2001 From: static Date: Fri, 17 Jan 2025 07:39:01 +0900 Subject: [PATCH 13/30] =?UTF-8?q?=EC=97=AC=EB=9F=AC=20=ED=8C=8C=EC=9D=BC?= =?UTF-8?q?=EC=9D=84=20=ED=95=9C=20=EB=B2=88=EC=97=90=20=EC=97=85=EB=A1=9C?= =?UTF-8?q?=EB=93=9C=ED=95=A0=20=EC=88=98=20=EC=9E=88=EB=8F=84=EB=A1=9D=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../(main)/directory/[[id]]/+page.svelte | 51 +++++++++++-------- .../[[id]]/DuplicateFileModal.svelte | 25 +++++---- 2 files changed, 44 insertions(+), 32 deletions(-) diff --git a/src/routes/(main)/directory/[[id]]/+page.svelte b/src/routes/(main)/directory/[[id]]/+page.svelte index ee7e86e..c02d482 100644 --- a/src/routes/(main)/directory/[[id]]/+page.svelte +++ b/src/routes/(main)/directory/[[id]]/+page.svelte @@ -29,6 +29,7 @@ let info: Writable | undefined = $state(); let fileInput: HTMLInputElement | undefined = $state(); let resolveForDuplicateFileModal: ((res: boolean) => void) | undefined = $state(); + let duplicatedFile: File | undefined = $state(); let selectedEntry: SelectedDirectoryEntry | undefined = $state(); let isCreateBottomSheetOpen = $state(false); @@ -46,29 +47,32 @@ }; const uploadFile = () => { - const file = fileInput?.files?.[0]; - if (!file) return; + const files = fileInput?.files; + if (!files || files.length === 0) return; + + for (const file of files) { + requestFileUpload(file, data.id, $hmacSecretStore?.get(1)!, $masterKeyStore?.get(1)!, () => { + return new Promise((resolve) => { + resolveForDuplicateFileModal = resolve; + duplicatedFile = file; + isDuplicateFileModalOpen = true; + }); + }) + .then((res) => { + if (!res) return; + + // TODO: FIXME + info = getDirectoryInfo(data.id, $masterKeyStore?.get(1)?.key!); + window.alert(`'${file.name}' 파일이 업로드되었어요.`); + }) + .catch((e: Error) => { + // TODO: FIXME + console.error(e); + window.alert(`'${file.name}' 파일 업로드에 실패했어요.\n${e.message}`); + }); + } fileInput!.value = ""; - - requestFileUpload(file, data.id, $hmacSecretStore?.get(1)!, $masterKeyStore?.get(1)!, () => { - return new Promise((resolve) => { - resolveForDuplicateFileModal = resolve; - isDuplicateFileModalOpen = true; - }); - }) - .then((res) => { - if (!res) return; - - // TODO: FIXME - info = getDirectoryInfo(data.id, $masterKeyStore?.get(1)?.key!); - window.alert("파일이 업로드되었어요."); - }) - .catch((e: Error) => { - // TODO: FIXME - console.error(e); - window.alert(`파일 업로드에 실패했어요.\n${e.message}`); - }); }; onMount(async () => { @@ -86,7 +90,7 @@ 파일 - +
{#if data.id !== "root"} @@ -129,14 +133,17 @@ { resolveForDuplicateFileModal?.(false); resolveForDuplicateFileModal = undefined; + duplicatedFile = undefined; isDuplicateFileModalOpen = false; }} onDuplicateClick={() => { resolveForDuplicateFileModal?.(true); resolveForDuplicateFileModal = undefined; + duplicatedFile = undefined; isDuplicateFileModalOpen = false; }} /> diff --git a/src/routes/(main)/directory/[[id]]/DuplicateFileModal.svelte b/src/routes/(main)/directory/[[id]]/DuplicateFileModal.svelte index 277d8ce..6c9d9af 100644 --- a/src/routes/(main)/directory/[[id]]/DuplicateFileModal.svelte +++ b/src/routes/(main)/directory/[[id]]/DuplicateFileModal.svelte @@ -3,23 +3,28 @@ import { Button } from "$lib/components/buttons"; interface Props { + file: File | undefined; onclose: () => void; onDuplicateClick: () => void; isOpen: boolean; } - let { onclose, onDuplicateClick, isOpen = $bindable() }: Props = $props(); + let { file, onclose, onDuplicateClick, isOpen = $bindable() }: Props = $props(); -
-
-

이미 업로드된 파일이에요.

-

그래도 업로드할까요?

+ {#if file} + {@const { name } = file} + {@const nameShort = name.length > 20 ? `${name.slice(0, 20)}...` : name} +
+
+

'{nameShort}' 파일이 있어요.

+

예전에 이미 업로드된 파일이에요. 그래도 업로드할까요?

+
+
+ + +
-
- - -
-
+ {/if} From 7e711c1b8ff26fa9f46b667f2ac919b3b4832950 Mon Sep 17 00:00:00 2001 From: static Date: Fri, 17 Jan 2025 07:54:09 +0900 Subject: [PATCH 14/30] =?UTF-8?q?/api/file/upload=20Endpoint=EC=97=90?= =?UTF-8?q?=EC=84=9C=EC=9D=98=20dekVersion=20=EC=A0=9C=ED=95=9C=20?= =?UTF-8?q?=EC=99=84=ED=99=94=20=EB=B0=8F=20=ED=8C=8C=EC=9D=BC=20=EC=97=85?= =?UTF-8?q?=EB=A1=9C=EB=93=9C=20=EC=A4=91=20=ED=8E=98=EC=9D=B4=EC=A7=80?= =?UTF-8?q?=EB=A5=BC=20=EB=96=A0=EB=82=98=EB=A0=A4=EB=8A=94=20=EA=B2=BD?= =?UTF-8?q?=EC=9A=B0=20=EA=B2=BD=EA=B3=A0=20=ED=91=9C=EC=8B=9C=20=EA=B8=B0?= =?UTF-8?q?=EB=8A=A5=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit dekVersion의 경우, Request를 받은 시점으로부터 하루 전 ~ 1분 후 사이에 있어야 하도록 완화했습니다. 기존에는 1분 전 ~ 1분 후 사이에 있어야 했습니다. 파일을 한 번에 업로드하는 경우 오류가 발생하는 것을 방지하기 위한 조치입니다. --- src/lib/server/services/file.ts | 4 ++-- src/routes/+layout.svelte | 21 ++++++++++++++++++++- 2 files changed, 22 insertions(+), 3 deletions(-) diff --git a/src/lib/server/services/file.ts b/src/lib/server/services/file.ts index b3086f9..f8153ca 100644 --- a/src/lib/server/services/file.ts +++ b/src/lib/server/services/file.ts @@ -97,9 +97,9 @@ export const uploadFile = async ( params: Omit, encContentStream: Readable, ) => { - const oneMinuteAgo = new Date(Date.now() - 60 * 1000); + const oneDayAgo = new Date(Date.now() - 24 * 60 * 60 * 1000); const oneMinuteLater = new Date(Date.now() + 60 * 1000); - if (params.dekVersion <= oneMinuteAgo || params.dekVersion >= oneMinuteLater) { + if (params.dekVersion <= oneDayAgo || params.dekVersion >= oneMinuteLater) { error(400, "Invalid DEK version"); } diff --git a/src/routes/+layout.svelte b/src/routes/+layout.svelte index 091c034..3d058ec 100644 --- a/src/routes/+layout.svelte +++ b/src/routes/+layout.svelte @@ -1,11 +1,28 @@ + + {@render children()} From 7aa6ba0eab9c267b2016c67792232192341adb94 Mon Sep 17 00:00:00 2001 From: static Date: Fri, 17 Jan 2025 12:22:51 +0900 Subject: [PATCH 15/30] =?UTF-8?q?=ED=8C=8C=EC=9D=BC=20=EB=B0=8F=20?= =?UTF-8?q?=EB=94=94=EB=A0=89=ED=84=B0=EB=A6=AC=20=EB=AA=A9=EB=A1=9D?= =?UTF-8?q?=EC=9D=84=20IndexedDB=EC=97=90=20=EC=BA=90=EC=8B=B1=ED=95=98?= =?UTF-8?q?=EB=8F=84=EB=A1=9D=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/lib/indexedDB/filesystem.ts | 52 +++++ src/lib/indexedDB/index.ts | 1 + src/lib/modules/file/index.ts | 1 - src/lib/modules/file/info.ts | 104 ---------- src/lib/modules/file/upload.ts | 2 +- src/lib/modules/filesystem.ts | 192 ++++++++++++++++++ src/lib/server/schemas/directory.ts | 3 +- src/lib/server/schemas/file.ts | 3 +- src/lib/server/services/directory.ts | 2 +- src/lib/server/services/file.ts | 1 + src/lib/stores/file.ts | 34 ---- .../(fullscreen)/file/[id]/+page.svelte | 8 +- .../(fullscreen)/setting/cache/+page.svelte | 5 +- .../(fullscreen)/setting/cache/File.svelte | 2 +- .../(main)/directory/[[id]]/+page.svelte | 4 +- .../DirectoryEntries/DirectoryEntries.svelte | 11 +- .../[[id]]/DirectoryEntries/File.svelte | 6 +- .../DirectoryEntries/SubDirectory.svelte | 6 +- src/routes/(main)/directory/[[id]]/service.ts | 2 +- src/routes/api/directory/[id]/+server.ts | 1 + src/routes/api/directory/create/+server.ts | 4 +- src/routes/api/file/[id]/+server.ts | 2 + src/routes/api/file/upload/+server.ts | 4 +- 23 files changed, 285 insertions(+), 165 deletions(-) create mode 100644 src/lib/indexedDB/filesystem.ts delete mode 100644 src/lib/modules/file/info.ts create mode 100644 src/lib/modules/filesystem.ts diff --git a/src/lib/indexedDB/filesystem.ts b/src/lib/indexedDB/filesystem.ts new file mode 100644 index 0000000..a6567ff --- /dev/null +++ b/src/lib/indexedDB/filesystem.ts @@ -0,0 +1,52 @@ +import { Dexie, type EntityTable } from "dexie"; + +export type DirectoryId = "root" | number; + +interface DirectoryInfo { + id: number; + parentId: DirectoryId; + name: string; +} + +interface FileInfo { + id: number; + parentId: DirectoryId; + name: string; + contentType: string; + createdAt?: Date; + lastModifiedAt: Date; +} + +const filesystem = new Dexie("filesystem") as Dexie & { + directory: EntityTable; + file: EntityTable; +}; + +filesystem.version(1).stores({ + directory: "id, parentId", + file: "id, parentId", +}); + +export const getDirectoryInfos = async (parentId: DirectoryId) => { + return await filesystem.directory.where({ parentId }).toArray(); +}; + +export const getDirectoryInfo = async (id: number) => { + return await filesystem.directory.get(id); +}; + +export const storeDirectoryInfo = async (directoryInfo: DirectoryInfo) => { + await filesystem.directory.put(directoryInfo); +}; + +export const getFileInfos = async (parentId: DirectoryId) => { + return await filesystem.file.where({ parentId }).toArray(); +}; + +export const getFileInfo = async (id: number) => { + return await filesystem.file.get(id); +}; + +export const storeFileInfo = async (fileInfo: FileInfo) => { + await filesystem.file.put(fileInfo); +}; diff --git a/src/lib/indexedDB/index.ts b/src/lib/indexedDB/index.ts index c9bb3d0..4ca1202 100644 --- a/src/lib/indexedDB/index.ts +++ b/src/lib/indexedDB/index.ts @@ -1,2 +1,3 @@ export * from "./cacheIndex"; +export * from "./filesystem"; export * from "./keyStore"; diff --git a/src/lib/modules/file/index.ts b/src/lib/modules/file/index.ts index c4ea9aa..bb3d0e6 100644 --- a/src/lib/modules/file/index.ts +++ b/src/lib/modules/file/index.ts @@ -1,3 +1,2 @@ export * from "./cache"; -export * from "./info"; export * from "./upload"; diff --git a/src/lib/modules/file/info.ts b/src/lib/modules/file/info.ts deleted file mode 100644 index 342febc..0000000 --- a/src/lib/modules/file/info.ts +++ /dev/null @@ -1,104 +0,0 @@ -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"; -import { - directoryInfoStore, - fileInfoStore, - type DirectoryInfo, - type FileInfo, -} from "$lib/stores/file"; - -const fetchDirectoryInfo = async ( - directoryId: "root" | number, - masterKey: CryptoKey, - infoStore: Writable, -) => { - 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(); - - let newInfo: DirectoryInfo; - if (directoryId === "root") { - newInfo = { - id: "root", - subDirectoryIds: subDirectories, - fileIds: files, - }; - } else { - const { dataKey } = await unwrapDataKey(metadata!.dek, masterKey); - newInfo = { - id: directoryId, - dataKey, - dataKeyVersion: new Date(metadata!.dekVersion), - name: await decryptString(metadata!.name, metadata!.nameIv, dataKey), - subDirectoryIds: subDirectories, - fileIds: files, - }; - } - - infoStore.update(() => newInfo); -}; - -export const getDirectoryInfo = (directoryId: "root" | number, masterKey: CryptoKey) => { - // TODO: MEK rotation - - let info = directoryInfoStore.get(directoryId); - if (!info) { - info = writable(null); - directoryInfoStore.set(directoryId, info); - } - - fetchDirectoryInfo(directoryId, masterKey, info); - return info; -}; - -const decryptDate = async (ciphertext: string, iv: string, dataKey: CryptoKey) => { - return new Date(parseInt(await decryptString(ciphertext, iv, dataKey), 10)); -}; - -const fetchFileInfo = async ( - fileId: number, - masterKey: CryptoKey, - infoStore: Writable, -) => { - const res = await callGetApi(`/api/file/${fileId}`); - if (!res.ok) { - if (res.status === 404) { - infoStore.update(() => null); - return; - } - throw new Error("Failed to fetch file information"); - } - const metadata: FileInfoResponse = await res.json(); - - const { dataKey } = await unwrapDataKey(metadata.dek, masterKey); - const newInfo: FileInfo = { - id: fileId, - dataKey, - dataKeyVersion: new Date(metadata.dekVersion), - contentType: metadata.contentType, - contentIv: metadata.contentIv, - name: await decryptString(metadata.name, metadata.nameIv, dataKey), - createdAt: - metadata.createdAt && metadata.createdAtIv - ? await decryptDate(metadata.createdAt, metadata.createdAtIv, dataKey) - : undefined, - lastModifiedAt: await decryptDate(metadata.lastModifiedAt, metadata.lastModifiedAtIv, dataKey), - }; - - infoStore.update(() => newInfo); -}; - -export const getFileInfo = (fileId: number, masterKey: CryptoKey) => { - // TODO: MEK rotation - - let info = fileInfoStore.get(fileId); - if (!info) { - info = writable(null); - fileInfoStore.set(fileId, info); - } - - fetchFileInfo(fileId, masterKey, info); - return info; -}; diff --git a/src/lib/modules/file/upload.ts b/src/lib/modules/file/upload.ts index 09dffe1..b24f444 100644 --- a/src/lib/modules/file/upload.ts +++ b/src/lib/modules/file/upload.ts @@ -191,7 +191,7 @@ export const uploadFile = async ( form.set( "metadata", JSON.stringify({ - parentId, + parent: parentId, mekVersion: masterKey.version, dek: dataKeyWrapped, dekVersion: dataKeyVersion.toISOString(), diff --git a/src/lib/modules/filesystem.ts b/src/lib/modules/filesystem.ts new file mode 100644 index 0000000..e1c929e --- /dev/null +++ b/src/lib/modules/filesystem.ts @@ -0,0 +1,192 @@ +import { get, writable, type Writable } from "svelte/store"; +import { callGetApi } from "$lib/hooks"; +import { + getDirectoryInfos as getDirectoryInfosFromIndexedDB, + getDirectoryInfo as getDirectoryInfoFromIndexedDB, + storeDirectoryInfo, + getFileInfos as getFileInfosFromIndexedDB, + getFileInfo as getFileInfoFromIndexedDB, + storeFileInfo, + type DirectoryId, +} from "$lib/indexedDB"; +import { unwrapDataKey, decryptString } from "$lib/modules/crypto"; +import type { DirectoryInfoResponse, FileInfoResponse } 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 { + id: number; + dataKey?: CryptoKey; + dataKeyVersion?: Date; + contentType: string; + contentIv?: string; + name: string; + createdAt?: Date; + lastModifiedAt: Date; +} + +const directoryInfoStore = new Map>(); +const fileInfoStore = new Map>(); + +const fetchDirectoryInfoFromIndexedDB = async ( + id: DirectoryId, + info: Writable, +) => { + 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, + masterKey: CryptoKey, +) => { + const res = await callGetApi(`/api/directory/${id}`); + if (!res.ok) throw new Error("Failed to fetch directory information"); // TODO: Handle 404 + 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, + 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); + return info; +}; + +const fetchFileInfoFromIndexedDB = async (id: number, info: Writable) => { + if (get(info)) return; + + const file = await getFileInfoFromIndexedDB(id); + if (!file) return; + + info.set(file); +}; + +const decryptDate = async (ciphertext: string, iv: string, dataKey: CryptoKey) => { + return new Date(parseInt(await decryptString(ciphertext, iv, dataKey), 10)); +}; + +const fetchFileInfoFromServer = async ( + id: number, + info: Writable, + masterKey: CryptoKey, +) => { + const res = await callGetApi(`/api/file/${id}`); + if (!res.ok) throw new Error("Failed to fetch file information"); // TODO: Handle 404 + const metadata: FileInfoResponse = await res.json(); + + const { dataKey } = await unwrapDataKey(metadata.dek, masterKey); + const name = await decryptString(metadata.name, metadata.nameIv, dataKey); + const createdAt = + metadata.createdAt && metadata.createdAtIv + ? await decryptDate(metadata.createdAt, metadata.createdAtIv, dataKey) + : undefined; + const lastModifiedAt = await decryptDate( + metadata.lastModifiedAt, + metadata.lastModifiedAtIv, + dataKey, + ); + + info.set({ + id, + dataKey, + dataKeyVersion: new Date(metadata.dekVersion), + contentType: metadata.contentType, + contentIv: metadata.contentIv, + name, + createdAt, + lastModifiedAt, + }); + await storeFileInfo({ + id, + parentId: metadata.parent, + name, + contentType: metadata.contentType, + createdAt, + lastModifiedAt, + }); +}; + +const fetchFileInfo = async (id: number, info: Writable, masterKey: CryptoKey) => { + await fetchFileInfoFromIndexedDB(id, info); + await fetchFileInfoFromServer(id, info, masterKey); +}; + +export const getFileInfo = (fileId: number, masterKey: CryptoKey) => { + // TODO: MEK rotation + + let info = fileInfoStore.get(fileId); + if (!info) { + info = writable(null); + fileInfoStore.set(fileId, info); + } + + fetchFileInfo(fileId, info, masterKey); + return info; +}; diff --git a/src/lib/server/schemas/directory.ts b/src/lib/server/schemas/directory.ts index 17d5720..15a5886 100644 --- a/src/lib/server/schemas/directory.ts +++ b/src/lib/server/schemas/directory.ts @@ -3,6 +3,7 @@ import { z } from "zod"; export const directoryInfoResponse = z.object({ metadata: z .object({ + parent: z.union([z.enum(["root"]), z.number().int().positive()]), mekVersion: z.number().int().positive(), dek: z.string().base64().nonempty(), dekVersion: z.string().datetime(), @@ -28,7 +29,7 @@ export const directoryRenameRequest = z.object({ export type DirectoryRenameRequest = z.infer; export const directoryCreateRequest = z.object({ - parentId: z.union([z.enum(["root"]), z.number().int().positive()]), + parent: z.union([z.enum(["root"]), z.number().int().positive()]), mekVersion: z.number().int().positive(), dek: z.string().base64().nonempty(), dekVersion: z.string().datetime(), diff --git a/src/lib/server/schemas/file.ts b/src/lib/server/schemas/file.ts index f6ac315..781baf2 100644 --- a/src/lib/server/schemas/file.ts +++ b/src/lib/server/schemas/file.ts @@ -2,6 +2,7 @@ import mime from "mime"; import { z } from "zod"; export const fileInfoResponse = z.object({ + parent: z.union([z.enum(["root"]), z.number().int().positive()]), mekVersion: z.number().int().positive(), dek: z.string().base64().nonempty(), dekVersion: z.string().datetime(), @@ -38,7 +39,7 @@ export const duplicateFileScanResponse = z.object({ export type DuplicateFileScanResponse = z.infer; export const fileUploadRequest = z.object({ - parentId: z.union([z.enum(["root"]), z.number().int().positive()]), + parent: z.union([z.enum(["root"]), z.number().int().positive()]), mekVersion: z.number().int().positive(), dek: z.string().base64().nonempty(), dekVersion: z.string().datetime(), diff --git a/src/lib/server/services/directory.ts b/src/lib/server/services/directory.ts index 3f6b55d..4dc14ce 100644 --- a/src/lib/server/services/directory.ts +++ b/src/lib/server/services/directory.ts @@ -19,9 +19,9 @@ export const getDirectoryInformation = async (userId: number, directoryId: "root const directories = await getAllDirectoriesByParent(userId, directoryId); const files = await getAllFilesByParent(userId, directoryId); - return { metadata: directory && { + parentId: directory.parentId ?? ("root" as const), mekVersion: directory.mekVersion, encDek: directory.encDek, dekVersion: directory.dekVersion, diff --git a/src/lib/server/services/file.ts b/src/lib/server/services/file.ts index f8153ca..3589bed 100644 --- a/src/lib/server/services/file.ts +++ b/src/lib/server/services/file.ts @@ -23,6 +23,7 @@ export const getFileInformation = async (userId: number, fileId: number) => { } return { + parentId: file.parentId ?? ("root" as const), mekVersion: file.mekVersion, encDek: file.encDek, dekVersion: file.dekVersion, diff --git a/src/lib/stores/file.ts b/src/lib/stores/file.ts index f7bf8b4..d3234d8 100644 --- a/src/lib/stores/file.ts +++ b/src/lib/stores/file.ts @@ -1,34 +1,4 @@ import { writable, type Writable } from "svelte/store"; - -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 { - id: number; - dataKey: CryptoKey; - dataKeyVersion: Date; - contentType: string; - contentIv: string; - name: string; - createdAt?: Date; - lastModifiedAt: Date; -} - export interface FileUploadStatus { name: string; parentId: "root" | number; @@ -45,8 +15,4 @@ export interface FileUploadStatus { estimated?: number; } -export const directoryInfoStore = new Map<"root" | number, Writable>(); - -export const fileInfoStore = new Map>(); - export const fileUploadStatusStore = writable[]>([]); diff --git a/src/routes/(fullscreen)/file/[id]/+page.svelte b/src/routes/(fullscreen)/file/[id]/+page.svelte index b9fc0ff..4d735ba 100644 --- a/src/routes/(fullscreen)/file/[id]/+page.svelte +++ b/src/routes/(fullscreen)/file/[id]/+page.svelte @@ -3,8 +3,8 @@ import { untrack } from "svelte"; import type { Writable } from "svelte/store"; import { TopBar } from "$lib/components"; - import { getFileInfo } from "$lib/modules/file"; - import { masterKeyStore, type FileInfo } from "$lib/stores"; + import { getFileInfo, type FileInfo } from "$lib/modules/filesystem"; + import { masterKeyStore } from "$lib/stores"; import { requestFileDownload } from "./service"; type ContentType = "image" | "video"; @@ -27,7 +27,7 @@ }); $effect(() => { - if ($info && !isDownloaded) { + if ($info?.contentIv && $info?.dataKey && !isDownloaded) { untrack(() => { isDownloaded = true; @@ -37,7 +37,7 @@ contentType = "video"; } - requestFileDownload(data.id, $info.contentIv, $info.dataKey).then(async (res) => { + requestFileDownload(data.id, $info.contentIv!, $info.dataKey!).then(async (res) => { content = new Blob([res], { type: $info.contentType }); if (content.type === "image/heic" || content.type === "image/heif") { const { default: heic2any } = await import("heic2any"); diff --git a/src/routes/(fullscreen)/setting/cache/+page.svelte b/src/routes/(fullscreen)/setting/cache/+page.svelte index fac9f31..f7581ea 100644 --- a/src/routes/(fullscreen)/setting/cache/+page.svelte +++ b/src/routes/(fullscreen)/setting/cache/+page.svelte @@ -3,8 +3,9 @@ import type { Writable } from "svelte/store"; import { TopBar } from "$lib/components"; import type { FileCacheIndex } from "$lib/indexedDB"; - import { getFileCacheIndex, getFileInfo } from "$lib/modules/file"; - import { masterKeyStore, type FileInfo } from "$lib/stores"; + import { getFileCacheIndex } from "$lib/modules/file"; + import { getFileInfo, type FileInfo } from "$lib/modules/filesystem"; + import { masterKeyStore } from "$lib/stores"; import File from "./File.svelte"; import { formatFileSize, deleteFileCache as doDeleteFileCache } from "./service"; diff --git a/src/routes/(fullscreen)/setting/cache/File.svelte b/src/routes/(fullscreen)/setting/cache/File.svelte index e92cc5c..2ee02c7 100644 --- a/src/routes/(fullscreen)/setting/cache/File.svelte +++ b/src/routes/(fullscreen)/setting/cache/File.svelte @@ -1,7 +1,7 @@ {#if subDirectories.length + files.length > 0} -
+
{#each subDirectories as { info }} {/each} diff --git a/src/routes/(main)/directory/[[id]]/DirectoryEntries/File.svelte b/src/routes/(main)/directory/[[id]]/DirectoryEntries/File.svelte index 80479ea..9946d36 100644 --- a/src/routes/(main)/directory/[[id]]/DirectoryEntries/File.svelte +++ b/src/routes/(main)/directory/[[id]]/DirectoryEntries/File.svelte @@ -1,6 +1,6 @@ @@ -69,23 +87,24 @@
-
+ +
{#snippet viewerLoading(message: string)}

{message}

{/snippet} - {#if $info && contentType === "image"} - {#if contentUrl} - {$info.name} + {#if $info && viewerType === "image"} + {#if fileBlobUrl} + {$info.name} {:else} {@render viewerLoading("이미지를 불러오고 있어요.")} {/if} - {:else if contentType === "video"} - {#if contentUrl} + {:else if viewerType === "video"} + {#if fileBlobUrl} - + {:else} {@render viewerLoading("비디오를 불러오고 있어요.")} {/if} diff --git a/src/routes/(fullscreen)/file/[id]/DownloadStatus.svelte b/src/routes/(fullscreen)/file/[id]/DownloadStatus.svelte new file mode 100644 index 0000000..f1b4d89 --- /dev/null +++ b/src/routes/(fullscreen)/file/[id]/DownloadStatus.svelte @@ -0,0 +1,32 @@ + + +{#if $info && $info.status !== "decrypted" && $info.status !== "canceled" && $info.status !== "error"} +
+

+ {#if $info.status === "download-pending"} + 다운로드를 기다리는 중 + {:else if $info.status === "downloading"} + 다운로드하는 중 + {:else if $info.status === "decryption-pending"} + 복호화를 기다리는 중 + {:else if $info.status === "decrypting"} + 복호화하는 중 + {/if} +

+

+ {#if $info.status === "downloading"} + 전송됨 {formatDownloadProgress($info.progress)} · {formatDownloadRate($info.rate)} + {/if} +

+
+{/if} diff --git a/src/routes/(fullscreen)/file/[id]/service.ts b/src/routes/(fullscreen)/file/[id]/service.ts index 55a5806..32ee666 100644 --- a/src/routes/(fullscreen)/file/[id]/service.ts +++ b/src/routes/(fullscreen)/file/[id]/service.ts @@ -1,5 +1,5 @@ -import { getFileCache, storeFileCache } from "$lib/modules/file"; -import { decryptData } from "$lib/modules/crypto"; +import { getFileCache, storeFileCache, downloadFile } from "$lib/modules/file"; +import { formatFileSize } from "$lib/modules/util"; export const requestFileDownload = async ( fileId: number, @@ -9,28 +9,15 @@ export const requestFileDownload = async ( const cache = await getFileCache(fileId); if (cache) return cache; - return new Promise((resolve, reject) => { - const xhr = new XMLHttpRequest(); - xhr.responseType = "arraybuffer"; - - xhr.addEventListener("load", async () => { - if (xhr.status !== 200) { - reject(new Error("Failed to download file")); - return; - } - - const fileDecrypted = await decryptData( - xhr.response as ArrayBuffer, - fileEncryptedIv, - dataKey, - ); - resolve(fileDecrypted); - await storeFileCache(fileId, fileDecrypted); - }); - - // TODO: Progress, ... - - xhr.open("GET", `/api/file/${fileId}/download`); - xhr.send(); - }); + const fileBuffer = await downloadFile(fileId, fileEncryptedIv, dataKey); + storeFileCache(fileId, fileBuffer); // Intended + return fileBuffer; +}; + +export const formatDownloadProgress = (progress?: number) => { + return `${Math.floor((progress ?? 0) * 100)}%`; +}; + +export const formatDownloadRate = (rate?: number) => { + return `${formatFileSize((rate ?? 0) / 8)}/s`; }; From 811713cd03ba76bc7092fe5c6847256cc354ba47 Mon Sep 17 00:00:00 2001 From: static Date: Sat, 18 Jan 2025 10:26:35 +0900 Subject: [PATCH 18/30] =?UTF-8?q?=ED=8C=8C=EC=9D=BC=20=EC=97=85=EB=A1=9C?= =?UTF-8?q?=EB=93=9C/=EB=8B=A4=EC=9A=B4=EB=A1=9C=EB=93=9C=20=ED=98=84?= =?UTF-8?q?=ED=99=A9=EC=9D=84=20=EB=AA=A8=EB=91=90=20=EB=B3=BC=20=EC=88=98?= =?UTF-8?q?=20=EC=9E=88=EB=8A=94=20=ED=8E=98=EC=9D=B4=EC=A7=80=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/lib/modules/file/upload.ts | 4 ++ src/lib/modules/util.ts | 7 ++ src/lib/stores/file.ts | 13 ++++ .../(fullscreen)/file/[id]/+page.svelte | 2 +- .../file/[id]/DownloadStatus.svelte | 21 +++--- src/routes/(fullscreen)/file/[id]/service.ts | 9 --- .../(fullscreen)/file/downloads/+page.svelte | 29 ++++++++ .../(fullscreen)/file/downloads/File.svelte | 66 +++++++++++++++++++ .../(fullscreen)/file/uploads/+page.svelte | 29 ++++++++ .../(fullscreen)/file/uploads/File.svelte | 57 ++++++++++++++++ .../{setting => settings}/cache/+page.svelte | 3 +- .../{setting => settings}/cache/File.svelte | 2 +- .../{setting => settings}/cache/service.ts | 2 - .../(main)/directory/[[id]]/+page.svelte | 6 ++ .../[[id]]/DirectoryEntries/File.svelte | 2 +- .../DirectoryEntries/UploadingFile.svelte | 4 +- .../[[id]]/DirectoryEntries/service.ts | 12 ---- .../[[id]]/DownloadStatusCard.svelte | 41 ++++++++++++ .../directory/[[id]]/UploadStatusCard.svelte | 39 +++++++++++ src/routes/(main)/menu/+page.svelte | 2 +- src/routes/+layout.svelte | 24 +++---- 21 files changed, 322 insertions(+), 52 deletions(-) create mode 100644 src/routes/(fullscreen)/file/downloads/+page.svelte create mode 100644 src/routes/(fullscreen)/file/downloads/File.svelte create mode 100644 src/routes/(fullscreen)/file/uploads/+page.svelte create mode 100644 src/routes/(fullscreen)/file/uploads/File.svelte rename src/routes/(fullscreen)/{setting => settings}/cache/+page.svelte (94%) rename src/routes/(fullscreen)/{setting => settings}/cache/File.svelte (95%) rename src/routes/(fullscreen)/{setting => settings}/cache/service.ts (72%) create mode 100644 src/routes/(main)/directory/[[id]]/DownloadStatusCard.svelte create mode 100644 src/routes/(main)/directory/[[id]]/UploadStatusCard.svelte diff --git a/src/lib/modules/file/upload.ts b/src/lib/modules/file/upload.ts index 4a6b8a6..2518c7f 100644 --- a/src/lib/modules/file/upload.ts +++ b/src/lib/modules/file/upload.ts @@ -174,6 +174,10 @@ export const uploadFile = async ( value.status = "canceled"; return value; }); + fileUploadStatusStore.update((value) => { + value = value.filter((v) => v !== status); + return value; + }); return false; } diff --git a/src/lib/modules/util.ts b/src/lib/modules/util.ts index 31e971b..67e1b3b 100644 --- a/src/lib/modules/util.ts +++ b/src/lib/modules/util.ts @@ -20,3 +20,10 @@ export const formatFileSize = (size: number) => { if (size < 1024 * 1024 * 1024) return `${(size / 1024 / 1024).toFixed(1)} MiB`; return `${(size / 1024 / 1024 / 1024).toFixed(1)} GiB`; }; + +export const formatNetworkSpeed = (speed: number) => { + if (speed < 1000) return `${speed} bps`; + if (speed < 1000 * 1000) return `${(speed / 1000).toFixed(1)} kbps`; + if (speed < 1000 * 1000 * 1000) return `${(speed / 1000 / 1000).toFixed(1)} Mbps`; + return `${(speed / 1000 / 1000 / 1000).toFixed(1)} Gbps`; +}; diff --git a/src/lib/stores/file.ts b/src/lib/stores/file.ts index 4dd2df4..61db95d 100644 --- a/src/lib/stores/file.ts +++ b/src/lib/stores/file.ts @@ -1,4 +1,5 @@ import { writable, type Writable } from "svelte/store"; + export interface FileUploadStatus { name: string; parentId: "root" | number; @@ -34,3 +35,15 @@ export interface FileDownloadStatus { export const fileUploadStatusStore = writable[]>([]); export const fileDownloadStatusStore = writable[]>([]); + +export const isFileUploading = ( + status: FileUploadStatus["status"], +): status is "encryption-pending" | "encrypting" | "upload-pending" | "uploading" => { + return ["encryption-pending", "encrypting", "upload-pending", "uploading"].includes(status); +}; + +export const isFileDownloading = ( + status: FileDownloadStatus["status"], +): status is "download-pending" | "downloading" | "decryption-pending" | "decrypting" => { + return ["download-pending", "downloading", "decryption-pending", "decrypting"].includes(status); +}; diff --git a/src/routes/(fullscreen)/file/[id]/+page.svelte b/src/routes/(fullscreen)/file/[id]/+page.svelte index 8d8e1eb..15c4571 100644 --- a/src/routes/(fullscreen)/file/[id]/+page.svelte +++ b/src/routes/(fullscreen)/file/[id]/+page.svelte @@ -87,7 +87,7 @@
- +
{#snippet viewerLoading(message: string)}
diff --git a/src/routes/(fullscreen)/file/[id]/DownloadStatus.svelte b/src/routes/(fullscreen)/file/[id]/DownloadStatus.svelte index f1b4d89..48a0459 100644 --- a/src/routes/(fullscreen)/file/[id]/DownloadStatus.svelte +++ b/src/routes/(fullscreen)/file/[id]/DownloadStatus.svelte @@ -1,31 +1,32 @@ -{#if $info && $info.status !== "decrypted" && $info.status !== "canceled" && $info.status !== "error"} +{#if $status && $status.status !== "decrypted" && $status.status !== "canceled" && $status.status !== "error"}

- {#if $info.status === "download-pending"} + {#if $status.status === "download-pending"} 다운로드를 기다리는 중 - {:else if $info.status === "downloading"} + {:else if $status.status === "downloading"} 다운로드하는 중 - {:else if $info.status === "decryption-pending"} + {:else if $status.status === "decryption-pending"} 복호화를 기다리는 중 - {:else if $info.status === "decrypting"} + {:else if $status.status === "decrypting"} 복호화하는 중 {/if}

- {#if $info.status === "downloading"} - 전송됨 {formatDownloadProgress($info.progress)} · {formatDownloadRate($info.rate)} + {#if $status.status === "downloading"} + 전송됨 + {Math.floor(($status.progress ?? 0) * 100)}% · {formatNetworkSpeed($status.rate ?? 0)} {/if}

diff --git a/src/routes/(fullscreen)/file/[id]/service.ts b/src/routes/(fullscreen)/file/[id]/service.ts index 32ee666..fcc5ce7 100644 --- a/src/routes/(fullscreen)/file/[id]/service.ts +++ b/src/routes/(fullscreen)/file/[id]/service.ts @@ -1,5 +1,4 @@ import { getFileCache, storeFileCache, downloadFile } from "$lib/modules/file"; -import { formatFileSize } from "$lib/modules/util"; export const requestFileDownload = async ( fileId: number, @@ -13,11 +12,3 @@ export const requestFileDownload = async ( storeFileCache(fileId, fileBuffer); // Intended return fileBuffer; }; - -export const formatDownloadProgress = (progress?: number) => { - return `${Math.floor((progress ?? 0) * 100)}%`; -}; - -export const formatDownloadRate = (rate?: number) => { - return `${formatFileSize((rate ?? 0) / 8)}/s`; -}; diff --git a/src/routes/(fullscreen)/file/downloads/+page.svelte b/src/routes/(fullscreen)/file/downloads/+page.svelte new file mode 100644 index 0000000..a29b147 --- /dev/null +++ b/src/routes/(fullscreen)/file/downloads/+page.svelte @@ -0,0 +1,29 @@ + + + + 진행 중인 다운로드 + + +
+ +
+ {#each downloadingFiles as status} + + {/each} +
+
diff --git a/src/routes/(fullscreen)/file/downloads/File.svelte b/src/routes/(fullscreen)/file/downloads/File.svelte new file mode 100644 index 0000000..670324c --- /dev/null +++ b/src/routes/(fullscreen)/file/downloads/File.svelte @@ -0,0 +1,66 @@ + + +{#if $fileInfo} +
+
+ {#if $status.status === "download-pending"} + + {:else if $status.status === "downloading"} + + {:else if $status.status === "decryption-pending"} + + {:else if $status.status === "decrypting"} + + {:else if $status.status === "decrypted"} + + {:else if $status.status === "error"} + + {/if} +
+
+

+ {$fileInfo.name} +

+

+ {#if $status.status === "download-pending"} + 다운로드를 기다리는 중 + {:else if $status.status === "downloading"} + 전송됨 + {Math.floor(($status.progress ?? 0) * 100)}% · {formatNetworkSpeed($status.rate ?? 0)} + {:else if $status.status === "decryption-pending"} + 복호화를 기다리는 중 + {:else if $status.status === "decrypting"} + 복호화하는 중 + {:else if $status.status === "decrypted"} + 다운로드 완료 + {:else if $status.status === "error"} + 다운로드 실패 + {/if} +

+
+
+{/if} diff --git a/src/routes/(fullscreen)/file/uploads/+page.svelte b/src/routes/(fullscreen)/file/uploads/+page.svelte new file mode 100644 index 0000000..9f59e3f --- /dev/null +++ b/src/routes/(fullscreen)/file/uploads/+page.svelte @@ -0,0 +1,29 @@ + + + + 진행 중인 업로드 + + +
+ +
+ {#each uploadingFiles as status} + + {/each} +
+
diff --git a/src/routes/(fullscreen)/file/uploads/File.svelte b/src/routes/(fullscreen)/file/uploads/File.svelte new file mode 100644 index 0000000..e0d235d --- /dev/null +++ b/src/routes/(fullscreen)/file/uploads/File.svelte @@ -0,0 +1,57 @@ + + +
+
+ {#if $status.status === "encryption-pending"} + + {:else if $status.status === "encrypting"} + + {:else if $status.status === "upload-pending"} + + {:else if $status.status === "uploading"} + + {:else if $status.status === "uploaded"} + + {:else if $status.status === "error"} + + {/if} +
+
+

+ {$status.name} +

+

+ {#if $status.status === "encryption-pending"} + 준비 중 + {:else if $status.status === "encrypting"} + 암호화하는 중 + {:else if $status.status === "upload-pending"} + 업로드를 기다리는 중 + {:else if $status.status === "uploading"} + 전송됨 + {Math.floor(($status.progress ?? 0) * 100)}% · {formatNetworkSpeed($status.rate ?? 0)} + {:else if $status.status === "uploaded"} + 업로드 완료 + {:else if $status.status === "error"} + 업로드 실패 + {/if} +

+
+
diff --git a/src/routes/(fullscreen)/setting/cache/+page.svelte b/src/routes/(fullscreen)/settings/cache/+page.svelte similarity index 94% rename from src/routes/(fullscreen)/setting/cache/+page.svelte rename to src/routes/(fullscreen)/settings/cache/+page.svelte index f7581ea..9f61afd 100644 --- a/src/routes/(fullscreen)/setting/cache/+page.svelte +++ b/src/routes/(fullscreen)/settings/cache/+page.svelte @@ -5,9 +5,10 @@ import type { FileCacheIndex } from "$lib/indexedDB"; import { getFileCacheIndex } from "$lib/modules/file"; import { getFileInfo, type FileInfo } from "$lib/modules/filesystem"; + import { formatFileSize } from "$lib/modules/util"; import { masterKeyStore } from "$lib/stores"; import File from "./File.svelte"; - import { formatFileSize, deleteFileCache as doDeleteFileCache } from "./service"; + import { deleteFileCache as doDeleteFileCache } from "./service"; interface FileCache { index: FileCacheIndex; diff --git a/src/routes/(fullscreen)/setting/cache/File.svelte b/src/routes/(fullscreen)/settings/cache/File.svelte similarity index 95% rename from src/routes/(fullscreen)/setting/cache/File.svelte rename to src/routes/(fullscreen)/settings/cache/File.svelte index 2ee02c7..9e9a88a 100644 --- a/src/routes/(fullscreen)/setting/cache/File.svelte +++ b/src/routes/(fullscreen)/settings/cache/File.svelte @@ -2,7 +2,7 @@ import type { Writable } from "svelte/store"; import type { FileCacheIndex } from "$lib/indexedDB"; import type { FileInfo } from "$lib/modules/filesystem"; - import { formatDate, formatFileSize } from "./service"; + import { formatDate, formatFileSize } from "$lib/modules/util"; import IconDraft from "~icons/material-symbols/draft"; import IconScanDelete from "~icons/material-symbols/scan-delete"; diff --git a/src/routes/(fullscreen)/setting/cache/service.ts b/src/routes/(fullscreen)/settings/cache/service.ts similarity index 72% rename from src/routes/(fullscreen)/setting/cache/service.ts rename to src/routes/(fullscreen)/settings/cache/service.ts index a8fc1c6..35b0251 100644 --- a/src/routes/(fullscreen)/setting/cache/service.ts +++ b/src/routes/(fullscreen)/settings/cache/service.ts @@ -1,7 +1,5 @@ import { deleteFileCache as doDeleteFileCache } from "$lib/modules/file"; -export { formatDate, formatFileSize } from "$lib/modules/util"; - export const deleteFileCache = async (fileId: number) => { await doDeleteFileCache(fileId); }; diff --git a/src/routes/(main)/directory/[[id]]/+page.svelte b/src/routes/(main)/directory/[[id]]/+page.svelte index 9c496cb..f8bd0c9 100644 --- a/src/routes/(main)/directory/[[id]]/+page.svelte +++ b/src/routes/(main)/directory/[[id]]/+page.svelte @@ -11,8 +11,10 @@ import DeleteDirectoryEntryModal from "./DeleteDirectoryEntryModal.svelte"; import DirectoryEntries from "./DirectoryEntries"; import DirectoryEntryMenuBottomSheet from "./DirectoryEntryMenuBottomSheet.svelte"; + import DownloadStatusCard from "./DownloadStatusCard.svelte"; import DuplicateFileModal from "./DuplicateFileModal.svelte"; import RenameDirectoryEntryModal from "./RenameDirectoryEntryModal.svelte"; + import UploadStatusCard from "./UploadStatusCard.svelte"; import { requestHmacSecretDownload, requestDirectoryCreation, @@ -99,6 +101,10 @@ {#if $info} {@const topMargin = data.id === "root" ? "mt-4" : ""}
+
+ goto("/file/uploads")} /> + goto("/file/downloads")} /> +
{#key $info} import type { Writable } from "svelte/store"; import type { FileInfo } from "$lib/modules/filesystem"; - import { formatDateTime } from "./service"; + import { formatDateTime } from "$lib/modules/util"; import type { SelectedDirectoryEntry } from "../service"; import IconDraft from "~icons/material-symbols/draft"; diff --git a/src/routes/(main)/directory/[[id]]/DirectoryEntries/UploadingFile.svelte b/src/routes/(main)/directory/[[id]]/DirectoryEntries/UploadingFile.svelte index e50d4bf..bc410e4 100644 --- a/src/routes/(main)/directory/[[id]]/DirectoryEntries/UploadingFile.svelte +++ b/src/routes/(main)/directory/[[id]]/DirectoryEntries/UploadingFile.svelte @@ -1,7 +1,7 @@ + +{#if downloadingFiles.length > 0} + +{/if} diff --git a/src/routes/(main)/directory/[[id]]/UploadStatusCard.svelte b/src/routes/(main)/directory/[[id]]/UploadStatusCard.svelte new file mode 100644 index 0000000..885673d --- /dev/null +++ b/src/routes/(main)/directory/[[id]]/UploadStatusCard.svelte @@ -0,0 +1,39 @@ + + +{#if uploadingFiles.length > 0} + +{/if} diff --git a/src/routes/(main)/menu/+page.svelte b/src/routes/(main)/menu/+page.svelte index 80187d4..5ca21b5 100644 --- a/src/routes/(main)/menu/+page.svelte +++ b/src/routes/(main)/menu/+page.svelte @@ -27,7 +27,7 @@

설정

goto("/setting/cache")} + onclick={() => goto("/settings/cache")} icon={IconStorage} iconColor="text-green-500" > diff --git a/src/routes/+layout.svelte b/src/routes/+layout.svelte index 3d058ec..e4bca97 100644 --- a/src/routes/+layout.svelte +++ b/src/routes/+layout.svelte @@ -2,22 +2,22 @@ import { onMount } from "svelte"; import { get } from "svelte/store"; import { goto as svelteGoto } from "$app/navigation"; - import { fileUploadStatusStore, clientKeyStore, masterKeyStore } from "$lib/stores"; + import { + fileUploadStatusStore, + fileDownloadStatusStore, + isFileUploading, + isFileDownloading, + clientKeyStore, + masterKeyStore, + } from "$lib/stores"; import "../app.css"; let { children } = $props(); - const checkFileUploadStatus = (e: BeforeUnloadEvent) => { + const protectFileUploadAndDownload = (e: BeforeUnloadEvent) => { if ( - $fileUploadStatusStore.some((statusStore) => { - const status = get(statusStore); - return ( - status.status === "encryption-pending" || - status.status === "encrypting" || - status.status === "upload-pending" || - status.status === "uploading" - ); - }) + $fileUploadStatusStore.some((status) => isFileUploading(get(status).status)) || + $fileDownloadStatusStore.some((status) => isFileDownloading(get(status).status)) ) { e.preventDefault(); } @@ -41,6 +41,6 @@ }); - + {@render children()} From d0d4afd2c3746749c7d0d80bc23472ebf8d08cf4 Mon Sep 17 00:00:00 2001 From: static Date: Sat, 18 Jan 2025 11:23:22 +0900 Subject: [PATCH 19/30] =?UTF-8?q?=EB=8B=A4=EC=9A=B4=EB=A1=9C=EB=93=9C=20?= =?UTF-8?q?=EB=B0=8F=20=EC=97=85=EB=A1=9C=EB=93=9C=20=EC=86=8D=EB=8F=84?= =?UTF-8?q?=EA=B0=80=20=EC=9E=98=EB=AA=BB=20=ED=91=9C=EA=B8=B0=EB=90=98?= =?UTF-8?q?=EB=8D=98=20=EB=B2=84=EA=B7=B8=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../file/[id]/DownloadStatus.svelte | 6 ++--- .../(fullscreen)/file/downloads/File.svelte | 3 ++- .../(fullscreen)/file/uploads/File.svelte | 2 +- .../DirectoryEntries/UploadingFile.svelte | 23 ++++++++++--------- 4 files changed, 18 insertions(+), 16 deletions(-) diff --git a/src/routes/(fullscreen)/file/[id]/DownloadStatus.svelte b/src/routes/(fullscreen)/file/[id]/DownloadStatus.svelte index 48a0459..c1e5729 100644 --- a/src/routes/(fullscreen)/file/[id]/DownloadStatus.svelte +++ b/src/routes/(fullscreen)/file/[id]/DownloadStatus.svelte @@ -1,7 +1,7 @@ -{#if $status && $status.status !== "decrypted" && $status.status !== "canceled" && $status.status !== "error"} +{#if $status && isFileDownloading($status.status)}

{#if $status.status === "download-pending"} @@ -26,7 +26,7 @@

{#if $status.status === "downloading"} 전송됨 - {Math.floor(($status.progress ?? 0) * 100)}% · {formatNetworkSpeed($status.rate ?? 0)} + {Math.floor(($status.progress ?? 0) * 100)}% · {formatNetworkSpeed(($status.rate ?? 0) * 8)} {/if}

diff --git a/src/routes/(fullscreen)/file/downloads/File.svelte b/src/routes/(fullscreen)/file/downloads/File.svelte index 670324c..4a2d501 100644 --- a/src/routes/(fullscreen)/file/downloads/File.svelte +++ b/src/routes/(fullscreen)/file/downloads/File.svelte @@ -50,7 +50,8 @@ 다운로드를 기다리는 중 {:else if $status.status === "downloading"} 전송됨 - {Math.floor(($status.progress ?? 0) * 100)}% · {formatNetworkSpeed($status.rate ?? 0)} + {Math.floor(($status.progress ?? 0) * 100)}% · + {formatNetworkSpeed(($status.rate ?? 0) * 8)} {:else if $status.status === "decryption-pending"} 복호화를 기다리는 중 {:else if $status.status === "decrypting"} diff --git a/src/routes/(fullscreen)/file/uploads/File.svelte b/src/routes/(fullscreen)/file/uploads/File.svelte index e0d235d..bcc17d2 100644 --- a/src/routes/(fullscreen)/file/uploads/File.svelte +++ b/src/routes/(fullscreen)/file/uploads/File.svelte @@ -46,7 +46,7 @@ 업로드를 기다리는 중 {:else if $status.status === "uploading"} 전송됨 - {Math.floor(($status.progress ?? 0) * 100)}% · {formatNetworkSpeed($status.rate ?? 0)} + {Math.floor(($status.progress ?? 0) * 100)}% · {formatNetworkSpeed(($status.rate ?? 0) * 8)} {:else if $status.status === "uploaded"} 업로드 완료 {:else if $status.status === "error"} diff --git a/src/routes/(main)/directory/[[id]]/DirectoryEntries/UploadingFile.svelte b/src/routes/(main)/directory/[[id]]/DirectoryEntries/UploadingFile.svelte index bc410e4..6216141 100644 --- a/src/routes/(main)/directory/[[id]]/DirectoryEntries/UploadingFile.svelte +++ b/src/routes/(main)/directory/[[id]]/DirectoryEntries/UploadingFile.svelte @@ -1,35 +1,36 @@ -{#if $info.status !== "uploaded" && $info.status !== "canceled" && $info.status !== "error"} +{#if isFileUploading($status.status)}
-

- {$info.name} +

+ {$status.name}

- {#if $info.status === "encryption-pending"} + {#if $status.status === "encryption-pending"} 준비 중 - {:else if $info.status === "encrypting"} + {:else if $status.status === "encrypting"} 암호화하는 중 - {:else if $info.status === "upload-pending"} + {:else if $status.status === "upload-pending"} 업로드를 기다리는 중 - {:else if $info.status === "uploading"} - 전송됨 {Math.floor(($info.progress ?? 0) * 100)}% · {formatNetworkSpeed($info.rate ?? 0)} + {:else if $status.status === "uploading"} + 전송됨 {Math.floor(($status.progress ?? 0) * 100)}% · + {formatNetworkSpeed(($status.rate ?? 0) * 8)} {/if}

From da47a07da7d508afdf7523d4cad844626125dbb2 Mon Sep 17 00:00:00 2001 From: static Date: Sat, 18 Jan 2025 12:48:01 +0900 Subject: [PATCH 20/30] =?UTF-8?q?=EC=82=AC=EC=86=8C=ED=95=9C=20=EB=A6=AC?= =?UTF-8?q?=ED=8C=A9=ED=86=A0=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/lib/modules/opfs.ts | 2 +- src/routes/(fullscreen)/file/[id]/+page.svelte | 12 +++--------- src/routes/(main)/directory/[[id]]/service.ts | 2 +- src/routes/(main)/menu/+page.svelte | 2 +- src/routes/(main)/menu/MenuEntryButton.svelte | 2 +- 5 files changed, 7 insertions(+), 13 deletions(-) diff --git a/src/lib/modules/opfs.ts b/src/lib/modules/opfs.ts index 8aedc43..5ac70da 100644 --- a/src/lib/modules/opfs.ts +++ b/src/lib/modules/opfs.ts @@ -17,7 +17,7 @@ const getFileHandle = async (path: string, create = true) => { } try { - let directoryHandle: FileSystemDirectoryHandle = rootHandle; + let directoryHandle = rootHandle; for (const part of parts.slice(0, -1)) { if (!part) continue; directoryHandle = await directoryHandle.getDirectoryHandle(part, { create }); diff --git a/src/routes/(fullscreen)/file/[id]/+page.svelte b/src/routes/(fullscreen)/file/[id]/+page.svelte index 15c4571..6188520 100644 --- a/src/routes/(fullscreen)/file/[id]/+page.svelte +++ b/src/routes/(fullscreen)/file/[id]/+page.svelte @@ -4,7 +4,7 @@ import { get, type Writable } from "svelte/store"; import { TopBar } from "$lib/components"; import { getFileInfo, type FileInfo } from "$lib/modules/filesystem"; - import { fileDownloadStatusStore, masterKeyStore } from "$lib/stores"; + import { fileDownloadStatusStore, isFileDownloading, masterKeyStore } from "$lib/stores"; import DownloadStatus from "./DownloadStatus.svelte"; import { requestFileDownload } from "./service"; @@ -14,14 +14,8 @@ const downloadStatus = $derived( $fileDownloadStatusStore.find((statusStore) => { - const status = get(statusStore); - return ( - status.id === data.id && - (status.status === "download-pending" || - status.status === "downloading" || - status.status === "decryption-pending" || - status.status === "decrypting") - ); + const { id, status } = get(statusStore); + return id === data.id && isFileDownloading(status); }), ); diff --git a/src/routes/(main)/directory/[[id]]/service.ts b/src/routes/(main)/directory/[[id]]/service.ts index d635c17..c9a62b2 100644 --- a/src/routes/(main)/directory/[[id]]/service.ts +++ b/src/routes/(main)/directory/[[id]]/service.ts @@ -1,7 +1,7 @@ import { callGetApi, callPostApi } from "$lib/hooks"; import { storeHmacSecrets } from "$lib/indexedDB"; -import { deleteFileCache, uploadFile } from "$lib/modules/file"; import { generateDataKey, wrapDataKey, unwrapHmacSecret, encryptString } from "$lib/modules/crypto"; +import { deleteFileCache, uploadFile } from "$lib/modules/file"; import type { DirectoryRenameRequest, DirectoryCreateRequest, diff --git a/src/routes/(main)/menu/+page.svelte b/src/routes/(main)/menu/+page.svelte index 5ca21b5..13ccb92 100644 --- a/src/routes/(main)/menu/+page.svelte +++ b/src/routes/(main)/menu/+page.svelte @@ -1,7 +1,7 @@ {#if $status && isFileDownloading($status.status)} -
+

{#if $status.status === "download-pending"} 다운로드를 기다리는 중 diff --git a/src/routes/(fullscreen)/file/downloads/File.svelte b/src/routes/(fullscreen)/file/downloads/File.svelte index 4a2d501..56ee319 100644 --- a/src/routes/(fullscreen)/file/downloads/File.svelte +++ b/src/routes/(fullscreen)/file/downloads/File.svelte @@ -41,8 +41,8 @@ {/if}

-
-

+

+

{$fileInfo.name}

diff --git a/src/routes/(fullscreen)/file/uploads/File.svelte b/src/routes/(fullscreen)/file/uploads/File.svelte index bcc17d2..1a228dc 100644 --- a/src/routes/(fullscreen)/file/uploads/File.svelte +++ b/src/routes/(fullscreen)/file/uploads/File.svelte @@ -33,8 +33,8 @@ {/if}

-
-

+

+

{$status.name}

diff --git a/src/routes/(fullscreen)/settings/cache/File.svelte b/src/routes/(fullscreen)/settings/cache/File.svelte index 9e9a88a..f21445b 100644 --- a/src/routes/(fullscreen)/settings/cache/File.svelte +++ b/src/routes/(fullscreen)/settings/cache/File.svelte @@ -27,7 +27,7 @@

{/if} -
+
{#if $info}

{$info.name}

{:else} diff --git a/src/routes/(main)/directory/[[id]]/DirectoryEntries/DirectoryEntries.svelte b/src/routes/(main)/directory/[[id]]/DirectoryEntries/DirectoryEntries.svelte index 0281a97..845f523 100644 --- a/src/routes/(main)/directory/[[id]]/DirectoryEntries/DirectoryEntries.svelte +++ b/src/routes/(main)/directory/[[id]]/DirectoryEntries/DirectoryEntries.svelte @@ -117,7 +117,7 @@ {#if file.type === "file"} {:else} - + {/if} {/each}
diff --git a/src/routes/(main)/directory/[[id]]/DirectoryEntries/File.svelte b/src/routes/(main)/directory/[[id]]/DirectoryEntries/File.svelte index 0c467ee..0dad51b 100644 --- a/src/routes/(main)/directory/[[id]]/DirectoryEntries/File.svelte +++ b/src/routes/(main)/directory/[[id]]/DirectoryEntries/File.svelte @@ -44,7 +44,7 @@
-
+

{$info.name}

From c24e84a79c6380abbd1ad792fd78b11d831d59f3 Mon Sep 17 00:00:00 2001 From: static Date: Sat, 18 Jan 2025 13:09:51 +0900 Subject: [PATCH 22/30] =?UTF-8?q?=EB=B6=88=ED=95=84=EC=9A=94=ED=95=9C=20CS?= =?UTF-8?q?S=20=EC=86=8D=EC=84=B1=20=EC=A0=9C=EA=B1=B0=202?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../directory/[[id]]/DirectoryEntries/UploadingFile.svelte | 6 +++--- .../(main)/directory/[[id]]/DownloadStatusCard.svelte | 2 +- src/routes/(main)/directory/[[id]]/UploadStatusCard.svelte | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/routes/(main)/directory/[[id]]/DirectoryEntries/UploadingFile.svelte b/src/routes/(main)/directory/[[id]]/DirectoryEntries/UploadingFile.svelte index 6216141..7977c53 100644 --- a/src/routes/(main)/directory/[[id]]/DirectoryEntries/UploadingFile.svelte +++ b/src/routes/(main)/directory/[[id]]/DirectoryEntries/UploadingFile.svelte @@ -17,11 +17,11 @@
-
-

+

+

{$status.name}

-

+

{#if $status.status === "encryption-pending"} 준비 중 {:else if $status.status === "encrypting"} diff --git a/src/routes/(main)/directory/[[id]]/DownloadStatusCard.svelte b/src/routes/(main)/directory/[[id]]/DownloadStatusCard.svelte index 4724695..18bb159 100644 --- a/src/routes/(main)/directory/[[id]]/DownloadStatusCard.svelte +++ b/src/routes/(main)/directory/[[id]]/DownloadStatusCard.svelte @@ -33,7 +33,7 @@ onclick={() => setTimeout(onclick, 100)} class="mb-4 max-w-[50%] flex-1 rounded-xl bg-green-100 p-3 active:bg-green-200" > -

+

진행 중인 다운로드

{downloadingFiles.length}개

diff --git a/src/routes/(main)/directory/[[id]]/UploadStatusCard.svelte b/src/routes/(main)/directory/[[id]]/UploadStatusCard.svelte index 885673d..1ac40b3 100644 --- a/src/routes/(main)/directory/[[id]]/UploadStatusCard.svelte +++ b/src/routes/(main)/directory/[[id]]/UploadStatusCard.svelte @@ -31,7 +31,7 @@ onclick={() => setTimeout(onclick, 100)} class="mb-4 max-w-[50%] flex-1 rounded-xl bg-blue-100 p-3 active:bg-blue-200" > -
+

진행 중인 업로드

{uploadingFiles.length}개

From b8e1584575485c3e41646f6f00bd813e07194470 Mon Sep 17 00:00:00 2001 From: static Date: Sat, 18 Jan 2025 13:18:07 +0900 Subject: [PATCH 23/30] =?UTF-8?q?=EC=82=AC=EC=86=8C=ED=95=9C=20=EB=A6=AC?= =?UTF-8?q?=ED=8C=A9=ED=86=A0=EB=A7=81=202?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../DirectoryEntries/DirectoryEntries.svelte | 28 +++++++++---------- 1 file changed, 13 insertions(+), 15 deletions(-) diff --git a/src/routes/(main)/directory/[[id]]/DirectoryEntries/DirectoryEntries.svelte b/src/routes/(main)/directory/[[id]]/DirectoryEntries/DirectoryEntries.svelte index 845f523..e2a187c 100644 --- a/src/routes/(main)/directory/[[id]]/DirectoryEntries/DirectoryEntries.svelte +++ b/src/routes/(main)/directory/[[id]]/DirectoryEntries/DirectoryEntries.svelte @@ -7,7 +7,12 @@ type DirectoryInfo, type FileInfo, } from "$lib/modules/filesystem"; - import { fileUploadStatusStore, masterKeyStore, type FileUploadStatus } from "$lib/stores"; + import { + fileUploadStatusStore, + isFileUploading, + masterKeyStore, + type FileUploadStatus, + } from "$lib/stores"; import File from "./File.svelte"; import SubDirectory from "./SubDirectory.svelte"; import { SortBy, sortEntries } from "./service"; @@ -62,21 +67,14 @@ .concat( $fileUploadStatusStore .filter((statusStore) => { - const status = get(statusStore); - return ( - status.parentId === info.id && - status.status !== "uploaded" && - status.status !== "canceled" && - status.status !== "error" - ); + const { parentId, status } = get(statusStore); + return parentId === info.id && !isFileUploading(status); }) - .map( - (status): FileEntry => ({ - type: "uploading-file", - name: get(status).name, - info: status, - }), - ), + .map((status) => ({ + type: "uploading-file", + name: get(status).name, + info: status, + })), ); const sort = () => { From 2b303f91978a220877f7f4cf005ca82218a37f57 Mon Sep 17 00:00:00 2001 From: static Date: Sat, 18 Jan 2025 16:29:39 +0900 Subject: [PATCH 24/30] =?UTF-8?q?=ED=8C=8C=EC=9D=BC=20=EB=AA=A9=EB=A1=9D?= =?UTF-8?q?=EC=97=90=EC=84=9C=20=EC=97=85=EB=A1=9C=EB=93=9C=20=EC=A4=91?= =?UTF-8?q?=EC=9D=B8=20=ED=8C=8C=EC=9D=BC=EC=9D=B4=20=ED=91=9C=EC=8B=9C?= =?UTF-8?q?=EB=90=98=EC=A7=80=20=EC=95=8A=EB=8D=98=20=EB=B2=84=EA=B7=B8=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../directory/[[id]]/DirectoryEntries/DirectoryEntries.svelte | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/routes/(main)/directory/[[id]]/DirectoryEntries/DirectoryEntries.svelte b/src/routes/(main)/directory/[[id]]/DirectoryEntries/DirectoryEntries.svelte index e2a187c..e482e38 100644 --- a/src/routes/(main)/directory/[[id]]/DirectoryEntries/DirectoryEntries.svelte +++ b/src/routes/(main)/directory/[[id]]/DirectoryEntries/DirectoryEntries.svelte @@ -68,7 +68,7 @@ $fileUploadStatusStore .filter((statusStore) => { const { parentId, status } = get(statusStore); - return parentId === info.id && !isFileUploading(status); + return parentId === info.id && isFileUploading(status); }) .map((status) => ({ type: "uploading-file", From 2af3caf3b9bdf0ba2a22446de13b77a892fb4946 Mon Sep 17 00:00:00 2001 From: static Date: Sat, 18 Jan 2025 16:45:07 +0900 Subject: [PATCH 25/30] =?UTF-8?q?=ED=8C=8C=EC=9D=BC=20=EB=B0=8F=20?= =?UTF-8?q?=EB=94=94=EB=A0=89=ED=84=B0=EB=A6=AC=20=EB=AA=A9=EB=A1=9D?= =?UTF-8?q?=EC=9D=84=20=EC=A0=95=EB=A0=AC=ED=95=A0=20=EB=95=8C=20=EC=9E=90?= =?UTF-8?q?=EC=97=B0=20=EC=A0=95=EB=A0=AC=EC=9D=84=20=EC=82=AC=EC=9A=A9?= =?UTF-8?q?=ED=95=98=EB=8F=84=EB=A1=9D=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app.html | 2 +- .../(main)/directory/[[id]]/DirectoryEntries/service.ts | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/app.html b/src/app.html index 4471298..2e7fd3e 100644 --- a/src/app.html +++ b/src/app.html @@ -1,5 +1,5 @@ - + diff --git a/src/routes/(main)/directory/[[id]]/DirectoryEntries/service.ts b/src/routes/(main)/directory/[[id]]/DirectoryEntries/service.ts index e1fc716..b797727 100644 --- a/src/routes/(main)/directory/[[id]]/DirectoryEntries/service.ts +++ b/src/routes/(main)/directory/[[id]]/DirectoryEntries/service.ts @@ -5,8 +5,10 @@ export enum SortBy { type SortFunc = (a?: string, b?: string) => number; +const collator = new Intl.Collator(undefined, { numeric: true, sensitivity: "base" }); + const sortByNameAsc: SortFunc = (a, b) => { - if (a && b) return a.localeCompare(b); + if (a && b) return collator.compare(a, b); if (a) return -1; if (b) return 1; return 0; From 10eba7844471e4703965d1a81d32bb44ede4dff0 Mon Sep 17 00:00:00 2001 From: static Date: Sat, 18 Jan 2025 18:12:40 +0900 Subject: [PATCH 26/30] =?UTF-8?q?=ED=8C=8C=EC=9D=BC=20=EC=97=85=EB=A1=9C?= =?UTF-8?q?=EB=93=9C=EC=8B=9C=EC=9D=98=20=EC=B2=B4=ED=81=AC=EC=84=AC=20?= =?UTF-8?q?=EA=B2=80=EC=82=AC=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- drizzle/0002_good_talisman.sql | 1 + drizzle/meta/0002_snapshot.json | 1308 +++++++++++++++++++++++++ drizzle/meta/_journal.json | 7 + src/lib/modules/file/upload.ts | 10 +- src/lib/server/db/file.ts | 6 +- src/lib/server/db/schema/file.ts | 1 + src/lib/server/services/file.ts | 20 +- src/routes/api/file/upload/+server.ts | 52 +- 8 files changed, 1379 insertions(+), 26 deletions(-) create mode 100644 drizzle/0002_good_talisman.sql create mode 100644 drizzle/meta/0002_snapshot.json diff --git a/drizzle/0002_good_talisman.sql b/drizzle/0002_good_talisman.sql new file mode 100644 index 0000000..55387df --- /dev/null +++ b/drizzle/0002_good_talisman.sql @@ -0,0 +1 @@ +ALTER TABLE `file` ADD `encrypted_content_hash` text NOT NULL; \ No newline at end of file diff --git a/drizzle/meta/0002_snapshot.json b/drizzle/meta/0002_snapshot.json new file mode 100644 index 0000000..d9da594 --- /dev/null +++ b/drizzle/meta/0002_snapshot.json @@ -0,0 +1,1308 @@ +{ + "version": "6", + "dialect": "sqlite", + "id": "93a0c9c7-dde3-4025-bd59-0fe3a6b70fa0", + "prevId": "5e999e6f-1ec4-40b0-bb10-741ffc6da4af", + "tables": { + "client": { + "name": "client", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "encryption_public_key": { + "name": "encryption_public_key", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "signature_public_key": { + "name": "signature_public_key", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "client_encryption_public_key_unique": { + "name": "client_encryption_public_key_unique", + "columns": [ + "encryption_public_key" + ], + "isUnique": true + }, + "client_signature_public_key_unique": { + "name": "client_signature_public_key_unique", + "columns": [ + "signature_public_key" + ], + "isUnique": true + }, + "client_encryption_public_key_signature_public_key_unique": { + "name": "client_encryption_public_key_signature_public_key_unique", + "columns": [ + "encryption_public_key", + "signature_public_key" + ], + "isUnique": true + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "user_client": { + "name": "user_client", + "columns": { + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "client_id": { + "name": "client_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "state": { + "name": "state", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'challenging'" + } + }, + "indexes": {}, + "foreignKeys": { + "user_client_user_id_user_id_fk": { + "name": "user_client_user_id_user_id_fk", + "tableFrom": "user_client", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "user_client_client_id_client_id_fk": { + "name": "user_client_client_id_client_id_fk", + "tableFrom": "user_client", + "tableTo": "client", + "columnsFrom": [ + "client_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "user_client_user_id_client_id_pk": { + "columns": [ + "client_id", + "user_id" + ], + "name": "user_client_user_id_client_id_pk" + } + }, + "uniqueConstraints": {} + }, + "user_client_challenge": { + "name": "user_client_challenge", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "client_id": { + "name": "client_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "answer": { + "name": "answer", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "allowed_ip": { + "name": "allowed_ip", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "expires_at": { + "name": "expires_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "user_client_challenge_answer_unique": { + "name": "user_client_challenge_answer_unique", + "columns": [ + "answer" + ], + "isUnique": true + } + }, + "foreignKeys": { + "user_client_challenge_user_id_user_id_fk": { + "name": "user_client_challenge_user_id_user_id_fk", + "tableFrom": "user_client_challenge", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "user_client_challenge_client_id_client_id_fk": { + "name": "user_client_challenge_client_id_client_id_fk", + "tableFrom": "user_client_challenge", + "tableTo": "client", + "columnsFrom": [ + "client_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "user_client_challenge_user_id_client_id_user_client_user_id_client_id_fk": { + "name": "user_client_challenge_user_id_client_id_user_client_user_id_client_id_fk", + "tableFrom": "user_client_challenge", + "tableTo": "user_client", + "columnsFrom": [ + "user_id", + "client_id" + ], + "columnsTo": [ + "user_id", + "client_id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "directory": { + "name": "directory", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "parent_id": { + "name": "parent_id", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "master_encryption_key_version": { + "name": "master_encryption_key_version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "encrypted_data_encryption_key": { + "name": "encrypted_data_encryption_key", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "data_encryption_key_version": { + "name": "data_encryption_key_version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "encrypted_name": { + "name": "encrypted_name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "directory_encrypted_data_encryption_key_unique": { + "name": "directory_encrypted_data_encryption_key_unique", + "columns": [ + "encrypted_data_encryption_key" + ], + "isUnique": true + } + }, + "foreignKeys": { + "directory_user_id_user_id_fk": { + "name": "directory_user_id_user_id_fk", + "tableFrom": "directory", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "directory_parent_id_directory_id_fk": { + "name": "directory_parent_id_directory_id_fk", + "tableFrom": "directory", + "tableTo": "directory", + "columnsFrom": [ + "parent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "directory_user_id_master_encryption_key_version_master_encryption_key_user_id_version_fk": { + "name": "directory_user_id_master_encryption_key_version_master_encryption_key_user_id_version_fk", + "tableFrom": "directory", + "tableTo": "master_encryption_key", + "columnsFrom": [ + "user_id", + "master_encryption_key_version" + ], + "columnsTo": [ + "user_id", + "version" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "directory_log": { + "name": "directory_log", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "directory_id": { + "name": "directory_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "timestamp": { + "name": "timestamp", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "action": { + "name": "action", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "new_name": { + "name": "new_name", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "directory_log_directory_id_directory_id_fk": { + "name": "directory_log_directory_id_directory_id_fk", + "tableFrom": "directory_log", + "tableTo": "directory", + "columnsFrom": [ + "directory_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "file": { + "name": "file", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "parent_id": { + "name": "parent_id", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "path": { + "name": "path", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "master_encryption_key_version": { + "name": "master_encryption_key_version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "encrypted_data_encryption_key": { + "name": "encrypted_data_encryption_key", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "data_encryption_key_version": { + "name": "data_encryption_key_version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "hmac_secret_key_version": { + "name": "hmac_secret_key_version", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "content_hmac": { + "name": "content_hmac", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "content_type": { + "name": "content_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "encrypted_content_iv": { + "name": "encrypted_content_iv", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "encrypted_content_hash": { + "name": "encrypted_content_hash", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "encrypted_name": { + "name": "encrypted_name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "encrypted_created_at": { + "name": "encrypted_created_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "encrypted_last_modified_at": { + "name": "encrypted_last_modified_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "file_path_unique": { + "name": "file_path_unique", + "columns": [ + "path" + ], + "isUnique": true + }, + "file_encrypted_data_encryption_key_unique": { + "name": "file_encrypted_data_encryption_key_unique", + "columns": [ + "encrypted_data_encryption_key" + ], + "isUnique": true + } + }, + "foreignKeys": { + "file_parent_id_directory_id_fk": { + "name": "file_parent_id_directory_id_fk", + "tableFrom": "file", + "tableTo": "directory", + "columnsFrom": [ + "parent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "file_user_id_user_id_fk": { + "name": "file_user_id_user_id_fk", + "tableFrom": "file", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "file_user_id_master_encryption_key_version_master_encryption_key_user_id_version_fk": { + "name": "file_user_id_master_encryption_key_version_master_encryption_key_user_id_version_fk", + "tableFrom": "file", + "tableTo": "master_encryption_key", + "columnsFrom": [ + "user_id", + "master_encryption_key_version" + ], + "columnsTo": [ + "user_id", + "version" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "file_user_id_hmac_secret_key_version_hmac_secret_key_user_id_version_fk": { + "name": "file_user_id_hmac_secret_key_version_hmac_secret_key_user_id_version_fk", + "tableFrom": "file", + "tableTo": "hmac_secret_key", + "columnsFrom": [ + "user_id", + "hmac_secret_key_version" + ], + "columnsTo": [ + "user_id", + "version" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "file_log": { + "name": "file_log", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "file_id": { + "name": "file_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "timestamp": { + "name": "timestamp", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "action": { + "name": "action", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "new_name": { + "name": "new_name", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "file_log_file_id_file_id_fk": { + "name": "file_log_file_id_file_id_fk", + "tableFrom": "file_log", + "tableTo": "file", + "columnsFrom": [ + "file_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "hmac_secret_key": { + "name": "hmac_secret_key", + "columns": { + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "version": { + "name": "version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "state": { + "name": "state", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "master_encryption_key_version": { + "name": "master_encryption_key_version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "encrypted_key": { + "name": "encrypted_key", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "hmac_secret_key_encrypted_key_unique": { + "name": "hmac_secret_key_encrypted_key_unique", + "columns": [ + "encrypted_key" + ], + "isUnique": true + } + }, + "foreignKeys": { + "hmac_secret_key_user_id_user_id_fk": { + "name": "hmac_secret_key_user_id_user_id_fk", + "tableFrom": "hmac_secret_key", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "hmac_secret_key_user_id_master_encryption_key_version_master_encryption_key_user_id_version_fk": { + "name": "hmac_secret_key_user_id_master_encryption_key_version_master_encryption_key_user_id_version_fk", + "tableFrom": "hmac_secret_key", + "tableTo": "master_encryption_key", + "columnsFrom": [ + "user_id", + "master_encryption_key_version" + ], + "columnsTo": [ + "user_id", + "version" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "hmac_secret_key_user_id_version_pk": { + "columns": [ + "user_id", + "version" + ], + "name": "hmac_secret_key_user_id_version_pk" + } + }, + "uniqueConstraints": {} + }, + "hmac_secret_key_log": { + "name": "hmac_secret_key_log", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "hmac_secret_key_version": { + "name": "hmac_secret_key_version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "timestamp": { + "name": "timestamp", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "action": { + "name": "action", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "action_by": { + "name": "action_by", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "hmac_secret_key_log_user_id_user_id_fk": { + "name": "hmac_secret_key_log_user_id_user_id_fk", + "tableFrom": "hmac_secret_key_log", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "hmac_secret_key_log_action_by_user_id_fk": { + "name": "hmac_secret_key_log_action_by_user_id_fk", + "tableFrom": "hmac_secret_key_log", + "tableTo": "user", + "columnsFrom": [ + "action_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "hmac_secret_key_log_user_id_hmac_secret_key_version_hmac_secret_key_user_id_version_fk": { + "name": "hmac_secret_key_log_user_id_hmac_secret_key_version_hmac_secret_key_user_id_version_fk", + "tableFrom": "hmac_secret_key_log", + "tableTo": "hmac_secret_key", + "columnsFrom": [ + "user_id", + "hmac_secret_key_version" + ], + "columnsTo": [ + "user_id", + "version" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "client_master_encryption_key": { + "name": "client_master_encryption_key", + "columns": { + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "client_id": { + "name": "client_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "version": { + "name": "version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "encrypted_key": { + "name": "encrypted_key", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "encrypted_key_signature": { + "name": "encrypted_key_signature", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "client_master_encryption_key_user_id_user_id_fk": { + "name": "client_master_encryption_key_user_id_user_id_fk", + "tableFrom": "client_master_encryption_key", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "client_master_encryption_key_client_id_client_id_fk": { + "name": "client_master_encryption_key_client_id_client_id_fk", + "tableFrom": "client_master_encryption_key", + "tableTo": "client", + "columnsFrom": [ + "client_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "client_master_encryption_key_user_id_version_master_encryption_key_user_id_version_fk": { + "name": "client_master_encryption_key_user_id_version_master_encryption_key_user_id_version_fk", + "tableFrom": "client_master_encryption_key", + "tableTo": "master_encryption_key", + "columnsFrom": [ + "user_id", + "version" + ], + "columnsTo": [ + "user_id", + "version" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "client_master_encryption_key_user_id_client_id_version_pk": { + "columns": [ + "client_id", + "user_id", + "version" + ], + "name": "client_master_encryption_key_user_id_client_id_version_pk" + } + }, + "uniqueConstraints": {} + }, + "master_encryption_key": { + "name": "master_encryption_key", + "columns": { + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "version": { + "name": "version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "state": { + "name": "state", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "retired_at": { + "name": "retired_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "master_encryption_key_user_id_user_id_fk": { + "name": "master_encryption_key_user_id_user_id_fk", + "tableFrom": "master_encryption_key", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "master_encryption_key_user_id_version_pk": { + "columns": [ + "user_id", + "version" + ], + "name": "master_encryption_key_user_id_version_pk" + } + }, + "uniqueConstraints": {} + }, + "master_encryption_key_log": { + "name": "master_encryption_key_log", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "master_encryption_key_version": { + "name": "master_encryption_key_version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "timestamp": { + "name": "timestamp", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "action": { + "name": "action", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "action_by": { + "name": "action_by", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "master_encryption_key_log_user_id_user_id_fk": { + "name": "master_encryption_key_log_user_id_user_id_fk", + "tableFrom": "master_encryption_key_log", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "master_encryption_key_log_action_by_client_id_fk": { + "name": "master_encryption_key_log_action_by_client_id_fk", + "tableFrom": "master_encryption_key_log", + "tableTo": "client", + "columnsFrom": [ + "action_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "master_encryption_key_log_user_id_master_encryption_key_version_master_encryption_key_user_id_version_fk": { + "name": "master_encryption_key_log_user_id_master_encryption_key_version_master_encryption_key_user_id_version_fk", + "tableFrom": "master_encryption_key_log", + "tableTo": "master_encryption_key", + "columnsFrom": [ + "user_id", + "master_encryption_key_version" + ], + "columnsTo": [ + "user_id", + "version" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "session": { + "name": "session", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "client_id": { + "name": "client_id", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "last_used_at": { + "name": "last_used_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "last_used_by_ip": { + "name": "last_used_by_ip", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "last_used_by_user_agent": { + "name": "last_used_by_user_agent", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "session_user_id_client_id_unique": { + "name": "session_user_id_client_id_unique", + "columns": [ + "user_id", + "client_id" + ], + "isUnique": true + } + }, + "foreignKeys": { + "session_user_id_user_id_fk": { + "name": "session_user_id_user_id_fk", + "tableFrom": "session", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "session_client_id_client_id_fk": { + "name": "session_client_id_client_id_fk", + "tableFrom": "session", + "tableTo": "client", + "columnsFrom": [ + "client_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "session_upgrade_challenge": { + "name": "session_upgrade_challenge", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "session_id": { + "name": "session_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "client_id": { + "name": "client_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "answer": { + "name": "answer", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "allowed_ip": { + "name": "allowed_ip", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "expires_at": { + "name": "expires_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "session_upgrade_challenge_session_id_unique": { + "name": "session_upgrade_challenge_session_id_unique", + "columns": [ + "session_id" + ], + "isUnique": true + }, + "session_upgrade_challenge_answer_unique": { + "name": "session_upgrade_challenge_answer_unique", + "columns": [ + "answer" + ], + "isUnique": true + } + }, + "foreignKeys": { + "session_upgrade_challenge_session_id_session_id_fk": { + "name": "session_upgrade_challenge_session_id_session_id_fk", + "tableFrom": "session_upgrade_challenge", + "tableTo": "session", + "columnsFrom": [ + "session_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "session_upgrade_challenge_client_id_client_id_fk": { + "name": "session_upgrade_challenge_client_id_client_id_fk", + "tableFrom": "session_upgrade_challenge", + "tableTo": "client", + "columnsFrom": [ + "client_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "user": { + "name": "user", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "password": { + "name": "password", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "nickname": { + "name": "nickname", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "user_email_unique": { + "name": "user_email_unique", + "columns": [ + "email" + ], + "isUnique": true + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + } + }, + "enums": {}, + "_meta": { + "schemas": {}, + "tables": {}, + "columns": {} + }, + "internal": { + "indexes": {} + } +} \ No newline at end of file diff --git a/drizzle/meta/_journal.json b/drizzle/meta/_journal.json index 65be42a..f5d931a 100644 --- a/drizzle/meta/_journal.json +++ b/drizzle/meta/_journal.json @@ -15,6 +15,13 @@ "when": 1736720831242, "tag": "0001_blushing_alice", "breakpoints": true + }, + { + "idx": 2, + "version": "6", + "when": 1737191517463, + "tag": "0002_good_talisman", + "breakpoints": true } ] } \ No newline at end of file diff --git a/src/lib/modules/file/upload.ts b/src/lib/modules/file/upload.ts index 2518c7f..a2a9707 100644 --- a/src/lib/modules/file/upload.ts +++ b/src/lib/modules/file/upload.ts @@ -8,6 +8,7 @@ import { wrapDataKey, encryptData, encryptString, + digestMessage, signMessageHmac, } from "$lib/modules/crypto"; import type { @@ -97,6 +98,8 @@ const encryptFile = limitFunction( const dataKeyWrapped = await wrapDataKey(dataKey, masterKey.key); const fileEncrypted = await encryptData(fileBuffer, dataKey); + const fileEncryptedHash = encodeToBase64(await digestMessage(fileEncrypted.ciphertext)); + const nameEncrypted = await encryptString(file.name, dataKey); const createdAtEncrypted = createdAt && (await encryptString(createdAt.getTime().toString(), dataKey)); @@ -110,8 +113,9 @@ const encryptFile = limitFunction( return { dataKeyWrapped, dataKeyVersion, - fileEncrypted, fileType, + fileEncrypted, + fileEncryptedHash, nameEncrypted, createdAtEncrypted, lastModifiedAtEncrypted, @@ -184,8 +188,9 @@ export const uploadFile = async ( const { dataKeyWrapped, dataKeyVersion, - fileEncrypted, fileType, + fileEncrypted, + fileEncryptedHash, nameEncrypted, createdAtEncrypted, lastModifiedAtEncrypted, @@ -212,6 +217,7 @@ export const uploadFile = async ( } as FileUploadRequest), ); form.set("content", new Blob([fileEncrypted.ciphertext])); + form.set("checksum", fileEncryptedHash); await requestFileUpload(status, form); return true; diff --git a/src/lib/server/db/file.ts b/src/lib/server/db/file.ts index a42235b..6bd0452 100644 --- a/src/lib/server/db/file.ts +++ b/src/lib/server/db/file.ts @@ -26,6 +26,7 @@ export interface NewFileParams { contentHmac: string | null; contentType: string; encContentIv: string; + encContentHash: string; encName: string; encNameIv: string; encCreatedAt: string | null; @@ -198,11 +199,12 @@ export const registerFile = async (params: NewFileParams) => { userId: params.userId, mekVersion: params.mekVersion, hskVersion: params.hskVersion, - contentHmac: params.contentHmac, - contentType: params.contentType, encDek: params.encDek, dekVersion: params.dekVersion, + contentHmac: params.contentHmac, + contentType: params.contentType, encContentIv: params.encContentIv, + encContentHash: params.encContentHash, encName: { ciphertext: params.encName, iv: params.encNameIv }, encCreatedAt: params.encCreatedAt && params.encCreatedAtIv diff --git a/src/lib/server/db/schema/file.ts b/src/lib/server/db/schema/file.ts index 65c5471..ffe303b 100644 --- a/src/lib/server/db/schema/file.ts +++ b/src/lib/server/db/schema/file.ts @@ -60,6 +60,7 @@ export const file = sqliteTable( contentHmac: text("content_hmac"), // Base64 contentType: text("content_type").notNull(), encContentIv: text("encrypted_content_iv").notNull(), // Base64 + encContentHash: text("encrypted_content_hash").notNull(), // Base64 encName: ciphertext("encrypted_name").notNull(), encCreatedAt: ciphertext("encrypted_created_at"), encLastModifiedAt: ciphertext("encrypted_last_modified_at").notNull(), diff --git a/src/lib/server/services/file.ts b/src/lib/server/services/file.ts index 3589bed..cabcc1d 100644 --- a/src/lib/server/services/file.ts +++ b/src/lib/server/services/file.ts @@ -1,4 +1,5 @@ import { error } from "@sveltejs/kit"; +import { createHash } from "crypto"; import { createReadStream, createWriteStream } from "fs"; import { mkdir, stat, unlink } from "fs/promises"; import { dirname } from "path"; @@ -95,8 +96,9 @@ const safeUnlink = async (path: string) => { }; export const uploadFile = async ( - params: Omit, + params: Omit, encContentStream: Readable, + encContentHash: Promise, ) => { const oneDayAgo = new Date(Date.now() - 24 * 60 * 60 * 1000); const oneMinuteLater = new Date(Date.now() + 60 * 1000); @@ -108,16 +110,30 @@ export const uploadFile = async ( await mkdir(dirname(path), { recursive: true }); try { - await pipeline(encContentStream, createWriteStream(path, { flags: "wx", mode: 0o600 })); + const hashStream = createHash("sha256"); + const [_, hash] = await Promise.all([ + pipeline(encContentStream, hashStream, createWriteStream(path, { flags: "wx", mode: 0o600 })), + encContentHash, + ]); + if (hashStream.digest("base64") != hash) { + throw new Error("Invalid checksum"); + } + await registerFile({ ...params, path, + encContentHash: hash, }); } catch (e) { await safeUnlink(path); if (e instanceof IntegrityError && e.message === "Inactive MEK version") { error(400, "Invalid MEK version"); + } else if ( + e instanceof Error && + (e.message === "Invalid request body" || e.message === "Invalid checksum") + ) { + error(400, "Invalid request body"); } throw e; } diff --git a/src/routes/api/file/upload/+server.ts b/src/routes/api/file/upload/+server.ts index 0e8c082..a69df0c 100644 --- a/src/routes/api/file/upload/+server.ts +++ b/src/routes/api/file/upload/+server.ts @@ -67,27 +67,39 @@ export const POST: RequestHandler = async ({ locals, request }) => { let metadata: FileMetadata | null = null; let content: Readable | null = null; + const checksum = new Promise((resolveChecksum, rejectChecksum) => { + bb.on( + "field", + handler(async (fieldname, val) => { + if (fieldname === "metadata") { + if (!metadata) { + // Ignore subsequent metadata fields + metadata = parseFileMetadata(userId, val); + } + } else if (fieldname === "checksum") { + resolveChecksum(val); // Ignore subsequent checksum fields + } else { + error(400, "Invalid request body"); + } + }), + ); + bb.on( + "file", + handler(async (fieldname, file) => { + if (fieldname !== "content") error(400, "Invalid request body"); + if (!metadata || content) error(400, "Invalid request body"); + content = file; - bb.on( - "field", - handler(async (fieldname, val) => { - if (fieldname !== "metadata") error(400, "Invalid request body"); - if (metadata || content) error(400, "Invalid request body"); - metadata = parseFileMetadata(userId, val); - }), - ); - bb.on( - "file", - handler(async (fieldname, file) => { - if (fieldname !== "content") error(400, "Invalid request body"); - if (!metadata || content) error(400, "Invalid request body"); - content = file; - - await uploadFile(metadata, content); - resolve(text("File uploaded", { headers: { "Content-Type": "text/plain" } })); - }), - ); - bb.on("error", (e) => content?.emit("error", e) ?? reject(e)); + await uploadFile(metadata, content, checksum); + resolve(text("File uploaded", { headers: { "Content-Type": "text/plain" } })); + }), + ); + bb.on("finish", () => rejectChecksum(new Error("Invalid request body"))); + bb.on("error", (e) => { + content?.emit("error", e) ?? reject(e); + rejectChecksum(e); + }); + }); request.body!.pipeTo(Writable.toWeb(bb)).catch(() => {}); // busboy will handle the error }); From 53bc4264876227bc49945bdb17c72c3c50e5b29e Mon Sep 17 00:00:00 2001 From: static Date: Sat, 18 Jan 2025 18:54:49 +0900 Subject: [PATCH 27/30] =?UTF-8?q?=ED=8C=8C=EC=9D=BC=EC=9D=84=20=EC=97=85?= =?UTF-8?q?=EB=A1=9C=EB=93=9C=ED=95=A0=20=EB=95=8C,=20=ED=8C=8C=EC=9D=BC?= =?UTF-8?q?=EC=9D=98=20=EB=82=B4=EC=9A=A9=EC=9D=B4=20=EC=95=84=EB=8B=8C=20?= =?UTF-8?q?=ED=95=B4=EC=8B=9C=EA=B0=80=20=EC=84=9C=EB=B2=84=EC=9D=98=20?= =?UTF-8?q?=ED=8C=8C=EC=9D=BC=20=EC=8B=9C=EC=8A=A4=ED=85=9C=EC=97=90=20?= =?UTF-8?q?=EA=B8=B0=EB=A1=9D=EB=90=98=EB=8D=98=20=EB=B2=84=EA=B7=B8=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/lib/server/services/file.ts | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/lib/server/services/file.ts b/src/lib/server/services/file.ts index cabcc1d..ea01f16 100644 --- a/src/lib/server/services/file.ts +++ b/src/lib/server/services/file.ts @@ -112,7 +112,16 @@ export const uploadFile = async ( try { const hashStream = createHash("sha256"); const [_, hash] = await Promise.all([ - pipeline(encContentStream, hashStream, createWriteStream(path, { flags: "wx", mode: 0o600 })), + pipeline( + encContentStream, + async function* (source) { + for await (const chunk of source) { + hashStream.update(chunk); + yield chunk; + } + }, + createWriteStream(path, { flags: "wx", mode: 0o600 }), + ), encContentHash, ]); if (hashStream.digest("base64") != hash) { From 5517d9f81117d8b11d4da82a3061e49c39fbd0d7 Mon Sep 17 00:00:00 2001 From: static Date: Sun, 19 Jan 2025 00:19:10 +0900 Subject: [PATCH 28/30] =?UTF-8?q?=EC=82=AD=EC=A0=9C=EB=90=9C=20=ED=8C=8C?= =?UTF-8?q?=EC=9D=BC=EC=9D=B4=EB=82=98=20=EB=94=94=EB=A0=89=ED=84=B0?= =?UTF-8?q?=EB=A6=AC=EC=9D=98=20=EA=B2=BD=EC=9A=B0=EC=97=90=EB=8F=84=20?= =?UTF-8?q?=EB=A9=94=ED=83=80=EB=8D=B0=EC=9D=B4=ED=84=B0=EB=A5=BC=20?= =?UTF-8?q?=EB=B3=B5=ED=98=B8=ED=99=94=ED=95=98=EB=A0=A4=EA=B3=A0=20?= =?UTF-8?q?=EC=8B=9C=EB=8F=84=ED=95=98=EB=8D=98=20=EB=B2=84=EA=B7=B8=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/lib/modules/filesystem.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/lib/modules/filesystem.ts b/src/lib/modules/filesystem.ts index 7313cb5..1a1ff0f 100644 --- a/src/lib/modules/filesystem.ts +++ b/src/lib/modules/filesystem.ts @@ -77,6 +77,7 @@ const fetchDirectoryInfoFromServer = async ( 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"); } @@ -149,6 +150,7 @@ const fetchFileInfoFromServer = async ( if (res.status === 404) { info.set(null); await deleteFileInfo(id); + return; } else if (!res.ok) { throw new Error("Failed to fetch file information"); } From 6018b03523ddd9276b4fe1deac7215f3547ee59c Mon Sep 17 00:00:00 2001 From: static Date: Sun, 19 Jan 2025 01:21:52 +0900 Subject: [PATCH 29/30] =?UTF-8?q?=ED=8C=8C=EC=9D=BC=20=EC=97=85=EB=A1=9C?= =?UTF-8?q?=EB=93=9C/=EB=8B=A4=EC=9A=B4=EB=A1=9C=EB=93=9C=20=ED=98=84?= =?UTF-8?q?=ED=99=A9=20=ED=8E=98=EC=9D=B4=EC=A7=80=EC=97=90=EC=84=9C=20Top?= =?UTF-8?q?Bar=EA=B0=80=20=EA=B3=A0=EC=A0=95=EB=90=98=EC=96=B4=20=EC=9E=88?= =?UTF-8?q?=EC=A7=80=20=EC=95=8A=EB=8D=98=20=EB=B2=84=EA=B7=B8=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/routes/(fullscreen)/file/downloads/+page.svelte | 4 ++-- src/routes/(fullscreen)/file/uploads/+page.svelte | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/routes/(fullscreen)/file/downloads/+page.svelte b/src/routes/(fullscreen)/file/downloads/+page.svelte index a29b147..a3ce387 100644 --- a/src/routes/(fullscreen)/file/downloads/+page.svelte +++ b/src/routes/(fullscreen)/file/downloads/+page.svelte @@ -19,9 +19,9 @@ 진행 중인 다운로드 -
+
-
+
{#each downloadingFiles as status} {/each} diff --git a/src/routes/(fullscreen)/file/uploads/+page.svelte b/src/routes/(fullscreen)/file/uploads/+page.svelte index 9f59e3f..4e61868 100644 --- a/src/routes/(fullscreen)/file/uploads/+page.svelte +++ b/src/routes/(fullscreen)/file/uploads/+page.svelte @@ -19,9 +19,9 @@ 진행 중인 업로드 -
+
-
+
{#each uploadingFiles as status} {/each} From 0002b4e5f2979caf3eee314aaf4033898be1037a Mon Sep 17 00:00:00 2001 From: static Date: Sun, 19 Jan 2025 02:03:44 +0900 Subject: [PATCH 30/30] =?UTF-8?q?hskLog=20=ED=85=8C=EC=9D=B4=EB=B8=94?= =?UTF-8?q?=EC=9D=98=20actionBy=20=ED=95=84=EB=93=9C=EC=9D=98=20Foreign=20?= =?UTF-8?q?key=20constraint=EC=9D=B4=20=EC=9E=98=EB=AA=BB=20=EC=A7=80?= =?UTF-8?q?=EC=A0=95=EB=90=98=EC=96=B4=20=EC=9E=88=EB=8D=98=20=EB=B2=84?= =?UTF-8?q?=EA=B7=B8=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...ries.sql => 0000_regular_the_watchers.sql} | 5 +- drizzle/0001_blushing_alice.sql | 2 - drizzle/0002_good_talisman.sql | 1 - drizzle/meta/0000_snapshot.json | 29 +- drizzle/meta/0001_snapshot.json | 1301 ---------------- drizzle/meta/0002_snapshot.json | 1308 ----------------- drizzle/meta/_journal.json | 18 +- src/lib/server/db/schema/hsk.ts | 3 +- 8 files changed, 33 insertions(+), 2634 deletions(-) rename drizzle/{0000_unknown_stark_industries.sql => 0000_regular_the_watchers.sql} (97%) delete mode 100644 drizzle/0001_blushing_alice.sql delete mode 100644 drizzle/0002_good_talisman.sql delete mode 100644 drizzle/meta/0001_snapshot.json delete mode 100644 drizzle/meta/0002_snapshot.json diff --git a/drizzle/0000_unknown_stark_industries.sql b/drizzle/0000_regular_the_watchers.sql similarity index 97% rename from drizzle/0000_unknown_stark_industries.sql rename to drizzle/0000_regular_the_watchers.sql index 28a9787..6cbdec8 100644 --- a/drizzle/0000_unknown_stark_industries.sql +++ b/drizzle/0000_regular_the_watchers.sql @@ -59,7 +59,10 @@ CREATE TABLE `file` ( `content_hmac` text, `content_type` text NOT NULL, `encrypted_content_iv` text NOT NULL, + `encrypted_content_hash` text NOT NULL, `encrypted_name` text NOT NULL, + `encrypted_created_at` text, + `encrypted_last_modified_at` text NOT NULL, FOREIGN KEY (`parent_id`) REFERENCES `directory`(`id`) ON UPDATE no action ON DELETE no action, FOREIGN KEY (`user_id`) REFERENCES `user`(`id`) ON UPDATE no action ON DELETE no action, FOREIGN KEY (`user_id`,`master_encryption_key_version`) REFERENCES `master_encryption_key`(`user_id`,`version`) ON UPDATE no action ON DELETE no action, @@ -94,7 +97,7 @@ CREATE TABLE `hmac_secret_key_log` ( `action` text NOT NULL, `action_by` integer, FOREIGN KEY (`user_id`) REFERENCES `user`(`id`) ON UPDATE no action ON DELETE no action, - FOREIGN KEY (`action_by`) REFERENCES `user`(`id`) ON UPDATE no action ON DELETE no action, + FOREIGN KEY (`action_by`) REFERENCES `client`(`id`) ON UPDATE no action ON DELETE no action, FOREIGN KEY (`user_id`,`hmac_secret_key_version`) REFERENCES `hmac_secret_key`(`user_id`,`version`) ON UPDATE no action ON DELETE no action ); --> statement-breakpoint diff --git a/drizzle/0001_blushing_alice.sql b/drizzle/0001_blushing_alice.sql deleted file mode 100644 index f68ba02..0000000 --- a/drizzle/0001_blushing_alice.sql +++ /dev/null @@ -1,2 +0,0 @@ -ALTER TABLE `file` ADD `encrypted_created_at` text;--> statement-breakpoint -ALTER TABLE `file` ADD `encrypted_last_modified_at` text NOT NULL; \ No newline at end of file diff --git a/drizzle/0002_good_talisman.sql b/drizzle/0002_good_talisman.sql deleted file mode 100644 index 55387df..0000000 --- a/drizzle/0002_good_talisman.sql +++ /dev/null @@ -1 +0,0 @@ -ALTER TABLE `file` ADD `encrypted_content_hash` text NOT NULL; \ No newline at end of file diff --git a/drizzle/meta/0000_snapshot.json b/drizzle/meta/0000_snapshot.json index 42641f1..27a42bc 100644 --- a/drizzle/meta/0000_snapshot.json +++ b/drizzle/meta/0000_snapshot.json @@ -1,7 +1,7 @@ { "version": "6", "dialect": "sqlite", - "id": "928e5669-81cf-486c-9122-8ee64fc9f457", + "id": "396a26d6-6f55-4162-a23e-c1117f3a3757", "prevId": "00000000-0000-0000-0000-000000000000", "tables": { "client": { @@ -470,12 +470,33 @@ "notNull": true, "autoincrement": false }, + "encrypted_content_hash": { + "name": "encrypted_content_hash", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, "encrypted_name": { "name": "encrypted_name", "type": "text", "primaryKey": false, "notNull": true, "autoincrement": false + }, + "encrypted_created_at": { + "name": "encrypted_created_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "encrypted_last_modified_at": { + "name": "encrypted_last_modified_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false } }, "indexes": { @@ -763,10 +784,10 @@ "onDelete": "no action", "onUpdate": "no action" }, - "hmac_secret_key_log_action_by_user_id_fk": { - "name": "hmac_secret_key_log_action_by_user_id_fk", + "hmac_secret_key_log_action_by_client_id_fk": { + "name": "hmac_secret_key_log_action_by_client_id_fk", "tableFrom": "hmac_secret_key_log", - "tableTo": "user", + "tableTo": "client", "columnsFrom": [ "action_by" ], diff --git a/drizzle/meta/0001_snapshot.json b/drizzle/meta/0001_snapshot.json deleted file mode 100644 index 3425d7b..0000000 --- a/drizzle/meta/0001_snapshot.json +++ /dev/null @@ -1,1301 +0,0 @@ -{ - "version": "6", - "dialect": "sqlite", - "id": "5e999e6f-1ec4-40b0-bb10-741ffc6da4af", - "prevId": "928e5669-81cf-486c-9122-8ee64fc9f457", - "tables": { - "client": { - "name": "client", - "columns": { - "id": { - "name": "id", - "type": "integer", - "primaryKey": true, - "notNull": true, - "autoincrement": true - }, - "encryption_public_key": { - "name": "encryption_public_key", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "signature_public_key": { - "name": "signature_public_key", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - } - }, - "indexes": { - "client_encryption_public_key_unique": { - "name": "client_encryption_public_key_unique", - "columns": [ - "encryption_public_key" - ], - "isUnique": true - }, - "client_signature_public_key_unique": { - "name": "client_signature_public_key_unique", - "columns": [ - "signature_public_key" - ], - "isUnique": true - }, - "client_encryption_public_key_signature_public_key_unique": { - "name": "client_encryption_public_key_signature_public_key_unique", - "columns": [ - "encryption_public_key", - "signature_public_key" - ], - "isUnique": true - } - }, - "foreignKeys": {}, - "compositePrimaryKeys": {}, - "uniqueConstraints": {} - }, - "user_client": { - "name": "user_client", - "columns": { - "user_id": { - "name": "user_id", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "client_id": { - "name": "client_id", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "state": { - "name": "state", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "default": "'challenging'" - } - }, - "indexes": {}, - "foreignKeys": { - "user_client_user_id_user_id_fk": { - "name": "user_client_user_id_user_id_fk", - "tableFrom": "user_client", - "tableTo": "user", - "columnsFrom": [ - "user_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "no action", - "onUpdate": "no action" - }, - "user_client_client_id_client_id_fk": { - "name": "user_client_client_id_client_id_fk", - "tableFrom": "user_client", - "tableTo": "client", - "columnsFrom": [ - "client_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "no action", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": { - "user_client_user_id_client_id_pk": { - "columns": [ - "client_id", - "user_id" - ], - "name": "user_client_user_id_client_id_pk" - } - }, - "uniqueConstraints": {} - }, - "user_client_challenge": { - "name": "user_client_challenge", - "columns": { - "id": { - "name": "id", - "type": "integer", - "primaryKey": true, - "notNull": true, - "autoincrement": false - }, - "user_id": { - "name": "user_id", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "client_id": { - "name": "client_id", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "answer": { - "name": "answer", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "allowed_ip": { - "name": "allowed_ip", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "expires_at": { - "name": "expires_at", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false - } - }, - "indexes": { - "user_client_challenge_answer_unique": { - "name": "user_client_challenge_answer_unique", - "columns": [ - "answer" - ], - "isUnique": true - } - }, - "foreignKeys": { - "user_client_challenge_user_id_user_id_fk": { - "name": "user_client_challenge_user_id_user_id_fk", - "tableFrom": "user_client_challenge", - "tableTo": "user", - "columnsFrom": [ - "user_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "no action", - "onUpdate": "no action" - }, - "user_client_challenge_client_id_client_id_fk": { - "name": "user_client_challenge_client_id_client_id_fk", - "tableFrom": "user_client_challenge", - "tableTo": "client", - "columnsFrom": [ - "client_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "no action", - "onUpdate": "no action" - }, - "user_client_challenge_user_id_client_id_user_client_user_id_client_id_fk": { - "name": "user_client_challenge_user_id_client_id_user_client_user_id_client_id_fk", - "tableFrom": "user_client_challenge", - "tableTo": "user_client", - "columnsFrom": [ - "user_id", - "client_id" - ], - "columnsTo": [ - "user_id", - "client_id" - ], - "onDelete": "no action", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {} - }, - "directory": { - "name": "directory", - "columns": { - "id": { - "name": "id", - "type": "integer", - "primaryKey": true, - "notNull": true, - "autoincrement": true - }, - "parent_id": { - "name": "parent_id", - "type": "integer", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "user_id": { - "name": "user_id", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "master_encryption_key_version": { - "name": "master_encryption_key_version", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "encrypted_data_encryption_key": { - "name": "encrypted_data_encryption_key", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "data_encryption_key_version": { - "name": "data_encryption_key_version", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "encrypted_name": { - "name": "encrypted_name", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - } - }, - "indexes": { - "directory_encrypted_data_encryption_key_unique": { - "name": "directory_encrypted_data_encryption_key_unique", - "columns": [ - "encrypted_data_encryption_key" - ], - "isUnique": true - } - }, - "foreignKeys": { - "directory_user_id_user_id_fk": { - "name": "directory_user_id_user_id_fk", - "tableFrom": "directory", - "tableTo": "user", - "columnsFrom": [ - "user_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "no action", - "onUpdate": "no action" - }, - "directory_parent_id_directory_id_fk": { - "name": "directory_parent_id_directory_id_fk", - "tableFrom": "directory", - "tableTo": "directory", - "columnsFrom": [ - "parent_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "no action", - "onUpdate": "no action" - }, - "directory_user_id_master_encryption_key_version_master_encryption_key_user_id_version_fk": { - "name": "directory_user_id_master_encryption_key_version_master_encryption_key_user_id_version_fk", - "tableFrom": "directory", - "tableTo": "master_encryption_key", - "columnsFrom": [ - "user_id", - "master_encryption_key_version" - ], - "columnsTo": [ - "user_id", - "version" - ], - "onDelete": "no action", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {} - }, - "directory_log": { - "name": "directory_log", - "columns": { - "id": { - "name": "id", - "type": "integer", - "primaryKey": true, - "notNull": true, - "autoincrement": true - }, - "directory_id": { - "name": "directory_id", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "timestamp": { - "name": "timestamp", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "action": { - "name": "action", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "new_name": { - "name": "new_name", - "type": "text", - "primaryKey": false, - "notNull": false, - "autoincrement": false - } - }, - "indexes": {}, - "foreignKeys": { - "directory_log_directory_id_directory_id_fk": { - "name": "directory_log_directory_id_directory_id_fk", - "tableFrom": "directory_log", - "tableTo": "directory", - "columnsFrom": [ - "directory_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {} - }, - "file": { - "name": "file", - "columns": { - "id": { - "name": "id", - "type": "integer", - "primaryKey": true, - "notNull": true, - "autoincrement": true - }, - "parent_id": { - "name": "parent_id", - "type": "integer", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "user_id": { - "name": "user_id", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "path": { - "name": "path", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "master_encryption_key_version": { - "name": "master_encryption_key_version", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "encrypted_data_encryption_key": { - "name": "encrypted_data_encryption_key", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "data_encryption_key_version": { - "name": "data_encryption_key_version", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "hmac_secret_key_version": { - "name": "hmac_secret_key_version", - "type": "integer", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "content_hmac": { - "name": "content_hmac", - "type": "text", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "content_type": { - "name": "content_type", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "encrypted_content_iv": { - "name": "encrypted_content_iv", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "encrypted_name": { - "name": "encrypted_name", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "encrypted_created_at": { - "name": "encrypted_created_at", - "type": "text", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "encrypted_last_modified_at": { - "name": "encrypted_last_modified_at", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - } - }, - "indexes": { - "file_path_unique": { - "name": "file_path_unique", - "columns": [ - "path" - ], - "isUnique": true - }, - "file_encrypted_data_encryption_key_unique": { - "name": "file_encrypted_data_encryption_key_unique", - "columns": [ - "encrypted_data_encryption_key" - ], - "isUnique": true - } - }, - "foreignKeys": { - "file_parent_id_directory_id_fk": { - "name": "file_parent_id_directory_id_fk", - "tableFrom": "file", - "tableTo": "directory", - "columnsFrom": [ - "parent_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "no action", - "onUpdate": "no action" - }, - "file_user_id_user_id_fk": { - "name": "file_user_id_user_id_fk", - "tableFrom": "file", - "tableTo": "user", - "columnsFrom": [ - "user_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "no action", - "onUpdate": "no action" - }, - "file_user_id_master_encryption_key_version_master_encryption_key_user_id_version_fk": { - "name": "file_user_id_master_encryption_key_version_master_encryption_key_user_id_version_fk", - "tableFrom": "file", - "tableTo": "master_encryption_key", - "columnsFrom": [ - "user_id", - "master_encryption_key_version" - ], - "columnsTo": [ - "user_id", - "version" - ], - "onDelete": "no action", - "onUpdate": "no action" - }, - "file_user_id_hmac_secret_key_version_hmac_secret_key_user_id_version_fk": { - "name": "file_user_id_hmac_secret_key_version_hmac_secret_key_user_id_version_fk", - "tableFrom": "file", - "tableTo": "hmac_secret_key", - "columnsFrom": [ - "user_id", - "hmac_secret_key_version" - ], - "columnsTo": [ - "user_id", - "version" - ], - "onDelete": "no action", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {} - }, - "file_log": { - "name": "file_log", - "columns": { - "id": { - "name": "id", - "type": "integer", - "primaryKey": true, - "notNull": true, - "autoincrement": true - }, - "file_id": { - "name": "file_id", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "timestamp": { - "name": "timestamp", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "action": { - "name": "action", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "new_name": { - "name": "new_name", - "type": "text", - "primaryKey": false, - "notNull": false, - "autoincrement": false - } - }, - "indexes": {}, - "foreignKeys": { - "file_log_file_id_file_id_fk": { - "name": "file_log_file_id_file_id_fk", - "tableFrom": "file_log", - "tableTo": "file", - "columnsFrom": [ - "file_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {} - }, - "hmac_secret_key": { - "name": "hmac_secret_key", - "columns": { - "user_id": { - "name": "user_id", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "version": { - "name": "version", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "state": { - "name": "state", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "master_encryption_key_version": { - "name": "master_encryption_key_version", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "encrypted_key": { - "name": "encrypted_key", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - } - }, - "indexes": { - "hmac_secret_key_encrypted_key_unique": { - "name": "hmac_secret_key_encrypted_key_unique", - "columns": [ - "encrypted_key" - ], - "isUnique": true - } - }, - "foreignKeys": { - "hmac_secret_key_user_id_user_id_fk": { - "name": "hmac_secret_key_user_id_user_id_fk", - "tableFrom": "hmac_secret_key", - "tableTo": "user", - "columnsFrom": [ - "user_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "no action", - "onUpdate": "no action" - }, - "hmac_secret_key_user_id_master_encryption_key_version_master_encryption_key_user_id_version_fk": { - "name": "hmac_secret_key_user_id_master_encryption_key_version_master_encryption_key_user_id_version_fk", - "tableFrom": "hmac_secret_key", - "tableTo": "master_encryption_key", - "columnsFrom": [ - "user_id", - "master_encryption_key_version" - ], - "columnsTo": [ - "user_id", - "version" - ], - "onDelete": "no action", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": { - "hmac_secret_key_user_id_version_pk": { - "columns": [ - "user_id", - "version" - ], - "name": "hmac_secret_key_user_id_version_pk" - } - }, - "uniqueConstraints": {} - }, - "hmac_secret_key_log": { - "name": "hmac_secret_key_log", - "columns": { - "id": { - "name": "id", - "type": "integer", - "primaryKey": true, - "notNull": true, - "autoincrement": true - }, - "user_id": { - "name": "user_id", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "hmac_secret_key_version": { - "name": "hmac_secret_key_version", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "timestamp": { - "name": "timestamp", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "action": { - "name": "action", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "action_by": { - "name": "action_by", - "type": "integer", - "primaryKey": false, - "notNull": false, - "autoincrement": false - } - }, - "indexes": {}, - "foreignKeys": { - "hmac_secret_key_log_user_id_user_id_fk": { - "name": "hmac_secret_key_log_user_id_user_id_fk", - "tableFrom": "hmac_secret_key_log", - "tableTo": "user", - "columnsFrom": [ - "user_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "no action", - "onUpdate": "no action" - }, - "hmac_secret_key_log_action_by_user_id_fk": { - "name": "hmac_secret_key_log_action_by_user_id_fk", - "tableFrom": "hmac_secret_key_log", - "tableTo": "user", - "columnsFrom": [ - "action_by" - ], - "columnsTo": [ - "id" - ], - "onDelete": "no action", - "onUpdate": "no action" - }, - "hmac_secret_key_log_user_id_hmac_secret_key_version_hmac_secret_key_user_id_version_fk": { - "name": "hmac_secret_key_log_user_id_hmac_secret_key_version_hmac_secret_key_user_id_version_fk", - "tableFrom": "hmac_secret_key_log", - "tableTo": "hmac_secret_key", - "columnsFrom": [ - "user_id", - "hmac_secret_key_version" - ], - "columnsTo": [ - "user_id", - "version" - ], - "onDelete": "no action", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {} - }, - "client_master_encryption_key": { - "name": "client_master_encryption_key", - "columns": { - "user_id": { - "name": "user_id", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "client_id": { - "name": "client_id", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "version": { - "name": "version", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "encrypted_key": { - "name": "encrypted_key", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "encrypted_key_signature": { - "name": "encrypted_key_signature", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - } - }, - "indexes": {}, - "foreignKeys": { - "client_master_encryption_key_user_id_user_id_fk": { - "name": "client_master_encryption_key_user_id_user_id_fk", - "tableFrom": "client_master_encryption_key", - "tableTo": "user", - "columnsFrom": [ - "user_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "no action", - "onUpdate": "no action" - }, - "client_master_encryption_key_client_id_client_id_fk": { - "name": "client_master_encryption_key_client_id_client_id_fk", - "tableFrom": "client_master_encryption_key", - "tableTo": "client", - "columnsFrom": [ - "client_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "no action", - "onUpdate": "no action" - }, - "client_master_encryption_key_user_id_version_master_encryption_key_user_id_version_fk": { - "name": "client_master_encryption_key_user_id_version_master_encryption_key_user_id_version_fk", - "tableFrom": "client_master_encryption_key", - "tableTo": "master_encryption_key", - "columnsFrom": [ - "user_id", - "version" - ], - "columnsTo": [ - "user_id", - "version" - ], - "onDelete": "no action", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": { - "client_master_encryption_key_user_id_client_id_version_pk": { - "columns": [ - "client_id", - "user_id", - "version" - ], - "name": "client_master_encryption_key_user_id_client_id_version_pk" - } - }, - "uniqueConstraints": {} - }, - "master_encryption_key": { - "name": "master_encryption_key", - "columns": { - "user_id": { - "name": "user_id", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "version": { - "name": "version", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "state": { - "name": "state", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "retired_at": { - "name": "retired_at", - "type": "integer", - "primaryKey": false, - "notNull": false, - "autoincrement": false - } - }, - "indexes": {}, - "foreignKeys": { - "master_encryption_key_user_id_user_id_fk": { - "name": "master_encryption_key_user_id_user_id_fk", - "tableFrom": "master_encryption_key", - "tableTo": "user", - "columnsFrom": [ - "user_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "no action", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": { - "master_encryption_key_user_id_version_pk": { - "columns": [ - "user_id", - "version" - ], - "name": "master_encryption_key_user_id_version_pk" - } - }, - "uniqueConstraints": {} - }, - "master_encryption_key_log": { - "name": "master_encryption_key_log", - "columns": { - "id": { - "name": "id", - "type": "integer", - "primaryKey": true, - "notNull": true, - "autoincrement": true - }, - "user_id": { - "name": "user_id", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "master_encryption_key_version": { - "name": "master_encryption_key_version", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "timestamp": { - "name": "timestamp", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "action": { - "name": "action", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "action_by": { - "name": "action_by", - "type": "integer", - "primaryKey": false, - "notNull": false, - "autoincrement": false - } - }, - "indexes": {}, - "foreignKeys": { - "master_encryption_key_log_user_id_user_id_fk": { - "name": "master_encryption_key_log_user_id_user_id_fk", - "tableFrom": "master_encryption_key_log", - "tableTo": "user", - "columnsFrom": [ - "user_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "no action", - "onUpdate": "no action" - }, - "master_encryption_key_log_action_by_client_id_fk": { - "name": "master_encryption_key_log_action_by_client_id_fk", - "tableFrom": "master_encryption_key_log", - "tableTo": "client", - "columnsFrom": [ - "action_by" - ], - "columnsTo": [ - "id" - ], - "onDelete": "no action", - "onUpdate": "no action" - }, - "master_encryption_key_log_user_id_master_encryption_key_version_master_encryption_key_user_id_version_fk": { - "name": "master_encryption_key_log_user_id_master_encryption_key_version_master_encryption_key_user_id_version_fk", - "tableFrom": "master_encryption_key_log", - "tableTo": "master_encryption_key", - "columnsFrom": [ - "user_id", - "master_encryption_key_version" - ], - "columnsTo": [ - "user_id", - "version" - ], - "onDelete": "no action", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {} - }, - "session": { - "name": "session", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true, - "autoincrement": false - }, - "user_id": { - "name": "user_id", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "client_id": { - "name": "client_id", - "type": "integer", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "created_at": { - "name": "created_at", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "last_used_at": { - "name": "last_used_at", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "last_used_by_ip": { - "name": "last_used_by_ip", - "type": "text", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "last_used_by_user_agent": { - "name": "last_used_by_user_agent", - "type": "text", - "primaryKey": false, - "notNull": false, - "autoincrement": false - } - }, - "indexes": { - "session_user_id_client_id_unique": { - "name": "session_user_id_client_id_unique", - "columns": [ - "user_id", - "client_id" - ], - "isUnique": true - } - }, - "foreignKeys": { - "session_user_id_user_id_fk": { - "name": "session_user_id_user_id_fk", - "tableFrom": "session", - "tableTo": "user", - "columnsFrom": [ - "user_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "no action", - "onUpdate": "no action" - }, - "session_client_id_client_id_fk": { - "name": "session_client_id_client_id_fk", - "tableFrom": "session", - "tableTo": "client", - "columnsFrom": [ - "client_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "no action", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {} - }, - "session_upgrade_challenge": { - "name": "session_upgrade_challenge", - "columns": { - "id": { - "name": "id", - "type": "integer", - "primaryKey": true, - "notNull": true, - "autoincrement": false - }, - "session_id": { - "name": "session_id", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "client_id": { - "name": "client_id", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "answer": { - "name": "answer", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "allowed_ip": { - "name": "allowed_ip", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "expires_at": { - "name": "expires_at", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false - } - }, - "indexes": { - "session_upgrade_challenge_session_id_unique": { - "name": "session_upgrade_challenge_session_id_unique", - "columns": [ - "session_id" - ], - "isUnique": true - }, - "session_upgrade_challenge_answer_unique": { - "name": "session_upgrade_challenge_answer_unique", - "columns": [ - "answer" - ], - "isUnique": true - } - }, - "foreignKeys": { - "session_upgrade_challenge_session_id_session_id_fk": { - "name": "session_upgrade_challenge_session_id_session_id_fk", - "tableFrom": "session_upgrade_challenge", - "tableTo": "session", - "columnsFrom": [ - "session_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "no action", - "onUpdate": "no action" - }, - "session_upgrade_challenge_client_id_client_id_fk": { - "name": "session_upgrade_challenge_client_id_client_id_fk", - "tableFrom": "session_upgrade_challenge", - "tableTo": "client", - "columnsFrom": [ - "client_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "no action", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {} - }, - "user": { - "name": "user", - "columns": { - "id": { - "name": "id", - "type": "integer", - "primaryKey": true, - "notNull": true, - "autoincrement": true - }, - "email": { - "name": "email", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "password": { - "name": "password", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "nickname": { - "name": "nickname", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - } - }, - "indexes": { - "user_email_unique": { - "name": "user_email_unique", - "columns": [ - "email" - ], - "isUnique": true - } - }, - "foreignKeys": {}, - "compositePrimaryKeys": {}, - "uniqueConstraints": {} - } - }, - "enums": {}, - "_meta": { - "schemas": {}, - "tables": {}, - "columns": {} - }, - "internal": { - "indexes": {} - } -} \ No newline at end of file diff --git a/drizzle/meta/0002_snapshot.json b/drizzle/meta/0002_snapshot.json deleted file mode 100644 index d9da594..0000000 --- a/drizzle/meta/0002_snapshot.json +++ /dev/null @@ -1,1308 +0,0 @@ -{ - "version": "6", - "dialect": "sqlite", - "id": "93a0c9c7-dde3-4025-bd59-0fe3a6b70fa0", - "prevId": "5e999e6f-1ec4-40b0-bb10-741ffc6da4af", - "tables": { - "client": { - "name": "client", - "columns": { - "id": { - "name": "id", - "type": "integer", - "primaryKey": true, - "notNull": true, - "autoincrement": true - }, - "encryption_public_key": { - "name": "encryption_public_key", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "signature_public_key": { - "name": "signature_public_key", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - } - }, - "indexes": { - "client_encryption_public_key_unique": { - "name": "client_encryption_public_key_unique", - "columns": [ - "encryption_public_key" - ], - "isUnique": true - }, - "client_signature_public_key_unique": { - "name": "client_signature_public_key_unique", - "columns": [ - "signature_public_key" - ], - "isUnique": true - }, - "client_encryption_public_key_signature_public_key_unique": { - "name": "client_encryption_public_key_signature_public_key_unique", - "columns": [ - "encryption_public_key", - "signature_public_key" - ], - "isUnique": true - } - }, - "foreignKeys": {}, - "compositePrimaryKeys": {}, - "uniqueConstraints": {} - }, - "user_client": { - "name": "user_client", - "columns": { - "user_id": { - "name": "user_id", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "client_id": { - "name": "client_id", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "state": { - "name": "state", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false, - "default": "'challenging'" - } - }, - "indexes": {}, - "foreignKeys": { - "user_client_user_id_user_id_fk": { - "name": "user_client_user_id_user_id_fk", - "tableFrom": "user_client", - "tableTo": "user", - "columnsFrom": [ - "user_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "no action", - "onUpdate": "no action" - }, - "user_client_client_id_client_id_fk": { - "name": "user_client_client_id_client_id_fk", - "tableFrom": "user_client", - "tableTo": "client", - "columnsFrom": [ - "client_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "no action", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": { - "user_client_user_id_client_id_pk": { - "columns": [ - "client_id", - "user_id" - ], - "name": "user_client_user_id_client_id_pk" - } - }, - "uniqueConstraints": {} - }, - "user_client_challenge": { - "name": "user_client_challenge", - "columns": { - "id": { - "name": "id", - "type": "integer", - "primaryKey": true, - "notNull": true, - "autoincrement": false - }, - "user_id": { - "name": "user_id", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "client_id": { - "name": "client_id", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "answer": { - "name": "answer", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "allowed_ip": { - "name": "allowed_ip", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "expires_at": { - "name": "expires_at", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false - } - }, - "indexes": { - "user_client_challenge_answer_unique": { - "name": "user_client_challenge_answer_unique", - "columns": [ - "answer" - ], - "isUnique": true - } - }, - "foreignKeys": { - "user_client_challenge_user_id_user_id_fk": { - "name": "user_client_challenge_user_id_user_id_fk", - "tableFrom": "user_client_challenge", - "tableTo": "user", - "columnsFrom": [ - "user_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "no action", - "onUpdate": "no action" - }, - "user_client_challenge_client_id_client_id_fk": { - "name": "user_client_challenge_client_id_client_id_fk", - "tableFrom": "user_client_challenge", - "tableTo": "client", - "columnsFrom": [ - "client_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "no action", - "onUpdate": "no action" - }, - "user_client_challenge_user_id_client_id_user_client_user_id_client_id_fk": { - "name": "user_client_challenge_user_id_client_id_user_client_user_id_client_id_fk", - "tableFrom": "user_client_challenge", - "tableTo": "user_client", - "columnsFrom": [ - "user_id", - "client_id" - ], - "columnsTo": [ - "user_id", - "client_id" - ], - "onDelete": "no action", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {} - }, - "directory": { - "name": "directory", - "columns": { - "id": { - "name": "id", - "type": "integer", - "primaryKey": true, - "notNull": true, - "autoincrement": true - }, - "parent_id": { - "name": "parent_id", - "type": "integer", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "user_id": { - "name": "user_id", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "master_encryption_key_version": { - "name": "master_encryption_key_version", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "encrypted_data_encryption_key": { - "name": "encrypted_data_encryption_key", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "data_encryption_key_version": { - "name": "data_encryption_key_version", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "encrypted_name": { - "name": "encrypted_name", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - } - }, - "indexes": { - "directory_encrypted_data_encryption_key_unique": { - "name": "directory_encrypted_data_encryption_key_unique", - "columns": [ - "encrypted_data_encryption_key" - ], - "isUnique": true - } - }, - "foreignKeys": { - "directory_user_id_user_id_fk": { - "name": "directory_user_id_user_id_fk", - "tableFrom": "directory", - "tableTo": "user", - "columnsFrom": [ - "user_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "no action", - "onUpdate": "no action" - }, - "directory_parent_id_directory_id_fk": { - "name": "directory_parent_id_directory_id_fk", - "tableFrom": "directory", - "tableTo": "directory", - "columnsFrom": [ - "parent_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "no action", - "onUpdate": "no action" - }, - "directory_user_id_master_encryption_key_version_master_encryption_key_user_id_version_fk": { - "name": "directory_user_id_master_encryption_key_version_master_encryption_key_user_id_version_fk", - "tableFrom": "directory", - "tableTo": "master_encryption_key", - "columnsFrom": [ - "user_id", - "master_encryption_key_version" - ], - "columnsTo": [ - "user_id", - "version" - ], - "onDelete": "no action", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {} - }, - "directory_log": { - "name": "directory_log", - "columns": { - "id": { - "name": "id", - "type": "integer", - "primaryKey": true, - "notNull": true, - "autoincrement": true - }, - "directory_id": { - "name": "directory_id", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "timestamp": { - "name": "timestamp", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "action": { - "name": "action", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "new_name": { - "name": "new_name", - "type": "text", - "primaryKey": false, - "notNull": false, - "autoincrement": false - } - }, - "indexes": {}, - "foreignKeys": { - "directory_log_directory_id_directory_id_fk": { - "name": "directory_log_directory_id_directory_id_fk", - "tableFrom": "directory_log", - "tableTo": "directory", - "columnsFrom": [ - "directory_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {} - }, - "file": { - "name": "file", - "columns": { - "id": { - "name": "id", - "type": "integer", - "primaryKey": true, - "notNull": true, - "autoincrement": true - }, - "parent_id": { - "name": "parent_id", - "type": "integer", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "user_id": { - "name": "user_id", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "path": { - "name": "path", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "master_encryption_key_version": { - "name": "master_encryption_key_version", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "encrypted_data_encryption_key": { - "name": "encrypted_data_encryption_key", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "data_encryption_key_version": { - "name": "data_encryption_key_version", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "hmac_secret_key_version": { - "name": "hmac_secret_key_version", - "type": "integer", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "content_hmac": { - "name": "content_hmac", - "type": "text", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "content_type": { - "name": "content_type", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "encrypted_content_iv": { - "name": "encrypted_content_iv", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "encrypted_content_hash": { - "name": "encrypted_content_hash", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "encrypted_name": { - "name": "encrypted_name", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "encrypted_created_at": { - "name": "encrypted_created_at", - "type": "text", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "encrypted_last_modified_at": { - "name": "encrypted_last_modified_at", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - } - }, - "indexes": { - "file_path_unique": { - "name": "file_path_unique", - "columns": [ - "path" - ], - "isUnique": true - }, - "file_encrypted_data_encryption_key_unique": { - "name": "file_encrypted_data_encryption_key_unique", - "columns": [ - "encrypted_data_encryption_key" - ], - "isUnique": true - } - }, - "foreignKeys": { - "file_parent_id_directory_id_fk": { - "name": "file_parent_id_directory_id_fk", - "tableFrom": "file", - "tableTo": "directory", - "columnsFrom": [ - "parent_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "no action", - "onUpdate": "no action" - }, - "file_user_id_user_id_fk": { - "name": "file_user_id_user_id_fk", - "tableFrom": "file", - "tableTo": "user", - "columnsFrom": [ - "user_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "no action", - "onUpdate": "no action" - }, - "file_user_id_master_encryption_key_version_master_encryption_key_user_id_version_fk": { - "name": "file_user_id_master_encryption_key_version_master_encryption_key_user_id_version_fk", - "tableFrom": "file", - "tableTo": "master_encryption_key", - "columnsFrom": [ - "user_id", - "master_encryption_key_version" - ], - "columnsTo": [ - "user_id", - "version" - ], - "onDelete": "no action", - "onUpdate": "no action" - }, - "file_user_id_hmac_secret_key_version_hmac_secret_key_user_id_version_fk": { - "name": "file_user_id_hmac_secret_key_version_hmac_secret_key_user_id_version_fk", - "tableFrom": "file", - "tableTo": "hmac_secret_key", - "columnsFrom": [ - "user_id", - "hmac_secret_key_version" - ], - "columnsTo": [ - "user_id", - "version" - ], - "onDelete": "no action", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {} - }, - "file_log": { - "name": "file_log", - "columns": { - "id": { - "name": "id", - "type": "integer", - "primaryKey": true, - "notNull": true, - "autoincrement": true - }, - "file_id": { - "name": "file_id", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "timestamp": { - "name": "timestamp", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "action": { - "name": "action", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "new_name": { - "name": "new_name", - "type": "text", - "primaryKey": false, - "notNull": false, - "autoincrement": false - } - }, - "indexes": {}, - "foreignKeys": { - "file_log_file_id_file_id_fk": { - "name": "file_log_file_id_file_id_fk", - "tableFrom": "file_log", - "tableTo": "file", - "columnsFrom": [ - "file_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {} - }, - "hmac_secret_key": { - "name": "hmac_secret_key", - "columns": { - "user_id": { - "name": "user_id", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "version": { - "name": "version", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "state": { - "name": "state", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "master_encryption_key_version": { - "name": "master_encryption_key_version", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "encrypted_key": { - "name": "encrypted_key", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - } - }, - "indexes": { - "hmac_secret_key_encrypted_key_unique": { - "name": "hmac_secret_key_encrypted_key_unique", - "columns": [ - "encrypted_key" - ], - "isUnique": true - } - }, - "foreignKeys": { - "hmac_secret_key_user_id_user_id_fk": { - "name": "hmac_secret_key_user_id_user_id_fk", - "tableFrom": "hmac_secret_key", - "tableTo": "user", - "columnsFrom": [ - "user_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "no action", - "onUpdate": "no action" - }, - "hmac_secret_key_user_id_master_encryption_key_version_master_encryption_key_user_id_version_fk": { - "name": "hmac_secret_key_user_id_master_encryption_key_version_master_encryption_key_user_id_version_fk", - "tableFrom": "hmac_secret_key", - "tableTo": "master_encryption_key", - "columnsFrom": [ - "user_id", - "master_encryption_key_version" - ], - "columnsTo": [ - "user_id", - "version" - ], - "onDelete": "no action", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": { - "hmac_secret_key_user_id_version_pk": { - "columns": [ - "user_id", - "version" - ], - "name": "hmac_secret_key_user_id_version_pk" - } - }, - "uniqueConstraints": {} - }, - "hmac_secret_key_log": { - "name": "hmac_secret_key_log", - "columns": { - "id": { - "name": "id", - "type": "integer", - "primaryKey": true, - "notNull": true, - "autoincrement": true - }, - "user_id": { - "name": "user_id", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "hmac_secret_key_version": { - "name": "hmac_secret_key_version", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "timestamp": { - "name": "timestamp", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "action": { - "name": "action", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "action_by": { - "name": "action_by", - "type": "integer", - "primaryKey": false, - "notNull": false, - "autoincrement": false - } - }, - "indexes": {}, - "foreignKeys": { - "hmac_secret_key_log_user_id_user_id_fk": { - "name": "hmac_secret_key_log_user_id_user_id_fk", - "tableFrom": "hmac_secret_key_log", - "tableTo": "user", - "columnsFrom": [ - "user_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "no action", - "onUpdate": "no action" - }, - "hmac_secret_key_log_action_by_user_id_fk": { - "name": "hmac_secret_key_log_action_by_user_id_fk", - "tableFrom": "hmac_secret_key_log", - "tableTo": "user", - "columnsFrom": [ - "action_by" - ], - "columnsTo": [ - "id" - ], - "onDelete": "no action", - "onUpdate": "no action" - }, - "hmac_secret_key_log_user_id_hmac_secret_key_version_hmac_secret_key_user_id_version_fk": { - "name": "hmac_secret_key_log_user_id_hmac_secret_key_version_hmac_secret_key_user_id_version_fk", - "tableFrom": "hmac_secret_key_log", - "tableTo": "hmac_secret_key", - "columnsFrom": [ - "user_id", - "hmac_secret_key_version" - ], - "columnsTo": [ - "user_id", - "version" - ], - "onDelete": "no action", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {} - }, - "client_master_encryption_key": { - "name": "client_master_encryption_key", - "columns": { - "user_id": { - "name": "user_id", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "client_id": { - "name": "client_id", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "version": { - "name": "version", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "encrypted_key": { - "name": "encrypted_key", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "encrypted_key_signature": { - "name": "encrypted_key_signature", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - } - }, - "indexes": {}, - "foreignKeys": { - "client_master_encryption_key_user_id_user_id_fk": { - "name": "client_master_encryption_key_user_id_user_id_fk", - "tableFrom": "client_master_encryption_key", - "tableTo": "user", - "columnsFrom": [ - "user_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "no action", - "onUpdate": "no action" - }, - "client_master_encryption_key_client_id_client_id_fk": { - "name": "client_master_encryption_key_client_id_client_id_fk", - "tableFrom": "client_master_encryption_key", - "tableTo": "client", - "columnsFrom": [ - "client_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "no action", - "onUpdate": "no action" - }, - "client_master_encryption_key_user_id_version_master_encryption_key_user_id_version_fk": { - "name": "client_master_encryption_key_user_id_version_master_encryption_key_user_id_version_fk", - "tableFrom": "client_master_encryption_key", - "tableTo": "master_encryption_key", - "columnsFrom": [ - "user_id", - "version" - ], - "columnsTo": [ - "user_id", - "version" - ], - "onDelete": "no action", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": { - "client_master_encryption_key_user_id_client_id_version_pk": { - "columns": [ - "client_id", - "user_id", - "version" - ], - "name": "client_master_encryption_key_user_id_client_id_version_pk" - } - }, - "uniqueConstraints": {} - }, - "master_encryption_key": { - "name": "master_encryption_key", - "columns": { - "user_id": { - "name": "user_id", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "version": { - "name": "version", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "state": { - "name": "state", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "retired_at": { - "name": "retired_at", - "type": "integer", - "primaryKey": false, - "notNull": false, - "autoincrement": false - } - }, - "indexes": {}, - "foreignKeys": { - "master_encryption_key_user_id_user_id_fk": { - "name": "master_encryption_key_user_id_user_id_fk", - "tableFrom": "master_encryption_key", - "tableTo": "user", - "columnsFrom": [ - "user_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "no action", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": { - "master_encryption_key_user_id_version_pk": { - "columns": [ - "user_id", - "version" - ], - "name": "master_encryption_key_user_id_version_pk" - } - }, - "uniqueConstraints": {} - }, - "master_encryption_key_log": { - "name": "master_encryption_key_log", - "columns": { - "id": { - "name": "id", - "type": "integer", - "primaryKey": true, - "notNull": true, - "autoincrement": true - }, - "user_id": { - "name": "user_id", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "master_encryption_key_version": { - "name": "master_encryption_key_version", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "timestamp": { - "name": "timestamp", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "action": { - "name": "action", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "action_by": { - "name": "action_by", - "type": "integer", - "primaryKey": false, - "notNull": false, - "autoincrement": false - } - }, - "indexes": {}, - "foreignKeys": { - "master_encryption_key_log_user_id_user_id_fk": { - "name": "master_encryption_key_log_user_id_user_id_fk", - "tableFrom": "master_encryption_key_log", - "tableTo": "user", - "columnsFrom": [ - "user_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "no action", - "onUpdate": "no action" - }, - "master_encryption_key_log_action_by_client_id_fk": { - "name": "master_encryption_key_log_action_by_client_id_fk", - "tableFrom": "master_encryption_key_log", - "tableTo": "client", - "columnsFrom": [ - "action_by" - ], - "columnsTo": [ - "id" - ], - "onDelete": "no action", - "onUpdate": "no action" - }, - "master_encryption_key_log_user_id_master_encryption_key_version_master_encryption_key_user_id_version_fk": { - "name": "master_encryption_key_log_user_id_master_encryption_key_version_master_encryption_key_user_id_version_fk", - "tableFrom": "master_encryption_key_log", - "tableTo": "master_encryption_key", - "columnsFrom": [ - "user_id", - "master_encryption_key_version" - ], - "columnsTo": [ - "user_id", - "version" - ], - "onDelete": "no action", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {} - }, - "session": { - "name": "session", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true, - "autoincrement": false - }, - "user_id": { - "name": "user_id", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "client_id": { - "name": "client_id", - "type": "integer", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "created_at": { - "name": "created_at", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "last_used_at": { - "name": "last_used_at", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "last_used_by_ip": { - "name": "last_used_by_ip", - "type": "text", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "last_used_by_user_agent": { - "name": "last_used_by_user_agent", - "type": "text", - "primaryKey": false, - "notNull": false, - "autoincrement": false - } - }, - "indexes": { - "session_user_id_client_id_unique": { - "name": "session_user_id_client_id_unique", - "columns": [ - "user_id", - "client_id" - ], - "isUnique": true - } - }, - "foreignKeys": { - "session_user_id_user_id_fk": { - "name": "session_user_id_user_id_fk", - "tableFrom": "session", - "tableTo": "user", - "columnsFrom": [ - "user_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "no action", - "onUpdate": "no action" - }, - "session_client_id_client_id_fk": { - "name": "session_client_id_client_id_fk", - "tableFrom": "session", - "tableTo": "client", - "columnsFrom": [ - "client_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "no action", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {} - }, - "session_upgrade_challenge": { - "name": "session_upgrade_challenge", - "columns": { - "id": { - "name": "id", - "type": "integer", - "primaryKey": true, - "notNull": true, - "autoincrement": false - }, - "session_id": { - "name": "session_id", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "client_id": { - "name": "client_id", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "answer": { - "name": "answer", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "allowed_ip": { - "name": "allowed_ip", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "expires_at": { - "name": "expires_at", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false - } - }, - "indexes": { - "session_upgrade_challenge_session_id_unique": { - "name": "session_upgrade_challenge_session_id_unique", - "columns": [ - "session_id" - ], - "isUnique": true - }, - "session_upgrade_challenge_answer_unique": { - "name": "session_upgrade_challenge_answer_unique", - "columns": [ - "answer" - ], - "isUnique": true - } - }, - "foreignKeys": { - "session_upgrade_challenge_session_id_session_id_fk": { - "name": "session_upgrade_challenge_session_id_session_id_fk", - "tableFrom": "session_upgrade_challenge", - "tableTo": "session", - "columnsFrom": [ - "session_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "no action", - "onUpdate": "no action" - }, - "session_upgrade_challenge_client_id_client_id_fk": { - "name": "session_upgrade_challenge_client_id_client_id_fk", - "tableFrom": "session_upgrade_challenge", - "tableTo": "client", - "columnsFrom": [ - "client_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "no action", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {} - }, - "user": { - "name": "user", - "columns": { - "id": { - "name": "id", - "type": "integer", - "primaryKey": true, - "notNull": true, - "autoincrement": true - }, - "email": { - "name": "email", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "password": { - "name": "password", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "nickname": { - "name": "nickname", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - } - }, - "indexes": { - "user_email_unique": { - "name": "user_email_unique", - "columns": [ - "email" - ], - "isUnique": true - } - }, - "foreignKeys": {}, - "compositePrimaryKeys": {}, - "uniqueConstraints": {} - } - }, - "enums": {}, - "_meta": { - "schemas": {}, - "tables": {}, - "columns": {} - }, - "internal": { - "indexes": {} - } -} \ No newline at end of file diff --git a/drizzle/meta/_journal.json b/drizzle/meta/_journal.json index f5d931a..bdbdf66 100644 --- a/drizzle/meta/_journal.json +++ b/drizzle/meta/_journal.json @@ -5,22 +5,8 @@ { "idx": 0, "version": "6", - "when": 1736704436996, - "tag": "0000_unknown_stark_industries", - "breakpoints": true - }, - { - "idx": 1, - "version": "6", - "when": 1736720831242, - "tag": "0001_blushing_alice", - "breakpoints": true - }, - { - "idx": 2, - "version": "6", - "when": 1737191517463, - "tag": "0002_good_talisman", + "when": 1737219722656, + "tag": "0000_regular_the_watchers", "breakpoints": true } ] diff --git a/src/lib/server/db/schema/hsk.ts b/src/lib/server/db/schema/hsk.ts index b78c512..51f25cc 100644 --- a/src/lib/server/db/schema/hsk.ts +++ b/src/lib/server/db/schema/hsk.ts @@ -1,4 +1,5 @@ import { sqliteTable, text, integer, primaryKey, foreignKey } from "drizzle-orm/sqlite-core"; +import { client } from "./client"; import { mek } from "./mek"; import { user } from "./user"; @@ -32,7 +33,7 @@ export const hskLog = sqliteTable( hskVersion: integer("hmac_secret_key_version").notNull(), timestamp: integer("timestamp", { mode: "timestamp_ms" }).notNull(), action: text("action", { enum: ["create"] }).notNull(), - actionBy: integer("action_by").references(() => user.id), + actionBy: integer("action_by").references(() => client.id), }, (t) => ({ ref: foreignKey({