mirror of
https://github.com/kmc7468/arkvault.git
synced 2025-12-16 15:08:46 +00:00
파일 및 디렉터리 목록을 IndexedDB에 캐싱하도록 구현
This commit is contained in:
52
src/lib/indexedDB/filesystem.ts
Normal file
52
src/lib/indexedDB/filesystem.ts
Normal file
@@ -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<DirectoryInfo, "id">;
|
||||||
|
file: EntityTable<FileInfo, "id">;
|
||||||
|
};
|
||||||
|
|
||||||
|
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);
|
||||||
|
};
|
||||||
@@ -1,2 +1,3 @@
|
|||||||
export * from "./cacheIndex";
|
export * from "./cacheIndex";
|
||||||
|
export * from "./filesystem";
|
||||||
export * from "./keyStore";
|
export * from "./keyStore";
|
||||||
|
|||||||
@@ -1,3 +1,2 @@
|
|||||||
export * from "./cache";
|
export * from "./cache";
|
||||||
export * from "./info";
|
|
||||||
export * from "./upload";
|
export * from "./upload";
|
||||||
|
|||||||
@@ -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<DirectoryInfo | null>,
|
|
||||||
) => {
|
|
||||||
const res = await callGetApi(`/api/directory/${directoryId}`);
|
|
||||||
if (!res.ok) throw new Error("Failed to fetch directory information");
|
|
||||||
const { metadata, subDirectories, files }: DirectoryInfoResponse = await res.json();
|
|
||||||
|
|
||||||
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<FileInfo | null>,
|
|
||||||
) => {
|
|
||||||
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;
|
|
||||||
};
|
|
||||||
@@ -191,7 +191,7 @@ export const uploadFile = async (
|
|||||||
form.set(
|
form.set(
|
||||||
"metadata",
|
"metadata",
|
||||||
JSON.stringify({
|
JSON.stringify({
|
||||||
parentId,
|
parent: parentId,
|
||||||
mekVersion: masterKey.version,
|
mekVersion: masterKey.version,
|
||||||
dek: dataKeyWrapped,
|
dek: dataKeyWrapped,
|
||||||
dekVersion: dataKeyVersion.toISOString(),
|
dekVersion: dataKeyVersion.toISOString(),
|
||||||
|
|||||||
192
src/lib/modules/filesystem.ts
Normal file
192
src/lib/modules/filesystem.ts
Normal file
@@ -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<DirectoryId, Writable<DirectoryInfo | null>>();
|
||||||
|
const fileInfoStore = new Map<number, Writable<FileInfo | null>>();
|
||||||
|
|
||||||
|
const fetchDirectoryInfoFromIndexedDB = async (
|
||||||
|
id: DirectoryId,
|
||||||
|
info: Writable<DirectoryInfo | null>,
|
||||||
|
) => {
|
||||||
|
if (get(info)) return;
|
||||||
|
|
||||||
|
const [directory, subDirectories, files] = await Promise.all([
|
||||||
|
id !== "root" ? getDirectoryInfoFromIndexedDB(id) : undefined,
|
||||||
|
getDirectoryInfosFromIndexedDB(id),
|
||||||
|
getFileInfosFromIndexedDB(id),
|
||||||
|
]);
|
||||||
|
const subDirectoryIds = subDirectories.map(({ id }) => id);
|
||||||
|
const fileIds = files.map(({ id }) => id);
|
||||||
|
|
||||||
|
if (id === "root") {
|
||||||
|
info.set({ id, subDirectoryIds, fileIds });
|
||||||
|
} else {
|
||||||
|
if (!directory) return;
|
||||||
|
info.set({ id, name: directory.name, subDirectoryIds, fileIds });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const fetchDirectoryInfoFromServer = async (
|
||||||
|
id: DirectoryId,
|
||||||
|
info: Writable<DirectoryInfo | null>,
|
||||||
|
masterKey: CryptoKey,
|
||||||
|
) => {
|
||||||
|
const res = await callGetApi(`/api/directory/${id}`);
|
||||||
|
if (!res.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<DirectoryInfo | null>,
|
||||||
|
masterKey: CryptoKey,
|
||||||
|
) => {
|
||||||
|
await fetchDirectoryInfoFromIndexedDB(id, info);
|
||||||
|
await fetchDirectoryInfoFromServer(id, info, masterKey);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getDirectoryInfo = (id: DirectoryId, masterKey: CryptoKey) => {
|
||||||
|
// TODO: MEK rotation
|
||||||
|
|
||||||
|
let info = directoryInfoStore.get(id);
|
||||||
|
if (!info) {
|
||||||
|
info = writable(null);
|
||||||
|
directoryInfoStore.set(id, info);
|
||||||
|
}
|
||||||
|
|
||||||
|
fetchDirectoryInfo(id, info, masterKey);
|
||||||
|
return info;
|
||||||
|
};
|
||||||
|
|
||||||
|
const fetchFileInfoFromIndexedDB = async (id: number, info: Writable<FileInfo | null>) => {
|
||||||
|
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<FileInfo | null>,
|
||||||
|
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<FileInfo | null>, 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;
|
||||||
|
};
|
||||||
@@ -3,6 +3,7 @@ import { z } from "zod";
|
|||||||
export const directoryInfoResponse = z.object({
|
export const directoryInfoResponse = z.object({
|
||||||
metadata: z
|
metadata: z
|
||||||
.object({
|
.object({
|
||||||
|
parent: z.union([z.enum(["root"]), z.number().int().positive()]),
|
||||||
mekVersion: z.number().int().positive(),
|
mekVersion: z.number().int().positive(),
|
||||||
dek: z.string().base64().nonempty(),
|
dek: z.string().base64().nonempty(),
|
||||||
dekVersion: z.string().datetime(),
|
dekVersion: z.string().datetime(),
|
||||||
@@ -28,7 +29,7 @@ export const directoryRenameRequest = z.object({
|
|||||||
export type DirectoryRenameRequest = z.infer<typeof directoryRenameRequest>;
|
export type DirectoryRenameRequest = z.infer<typeof directoryRenameRequest>;
|
||||||
|
|
||||||
export const directoryCreateRequest = z.object({
|
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(),
|
mekVersion: z.number().int().positive(),
|
||||||
dek: z.string().base64().nonempty(),
|
dek: z.string().base64().nonempty(),
|
||||||
dekVersion: z.string().datetime(),
|
dekVersion: z.string().datetime(),
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import mime from "mime";
|
|||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
export const fileInfoResponse = z.object({
|
export const fileInfoResponse = z.object({
|
||||||
|
parent: z.union([z.enum(["root"]), z.number().int().positive()]),
|
||||||
mekVersion: z.number().int().positive(),
|
mekVersion: z.number().int().positive(),
|
||||||
dek: z.string().base64().nonempty(),
|
dek: z.string().base64().nonempty(),
|
||||||
dekVersion: z.string().datetime(),
|
dekVersion: z.string().datetime(),
|
||||||
@@ -38,7 +39,7 @@ export const duplicateFileScanResponse = z.object({
|
|||||||
export type DuplicateFileScanResponse = z.infer<typeof duplicateFileScanResponse>;
|
export type DuplicateFileScanResponse = z.infer<typeof duplicateFileScanResponse>;
|
||||||
|
|
||||||
export const fileUploadRequest = z.object({
|
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(),
|
mekVersion: z.number().int().positive(),
|
||||||
dek: z.string().base64().nonempty(),
|
dek: z.string().base64().nonempty(),
|
||||||
dekVersion: z.string().datetime(),
|
dekVersion: z.string().datetime(),
|
||||||
|
|||||||
@@ -19,9 +19,9 @@ export const getDirectoryInformation = async (userId: number, directoryId: "root
|
|||||||
|
|
||||||
const directories = await getAllDirectoriesByParent(userId, directoryId);
|
const directories = await getAllDirectoriesByParent(userId, directoryId);
|
||||||
const files = await getAllFilesByParent(userId, directoryId);
|
const files = await getAllFilesByParent(userId, directoryId);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
metadata: directory && {
|
metadata: directory && {
|
||||||
|
parentId: directory.parentId ?? ("root" as const),
|
||||||
mekVersion: directory.mekVersion,
|
mekVersion: directory.mekVersion,
|
||||||
encDek: directory.encDek,
|
encDek: directory.encDek,
|
||||||
dekVersion: directory.dekVersion,
|
dekVersion: directory.dekVersion,
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ export const getFileInformation = async (userId: number, fileId: number) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
parentId: file.parentId ?? ("root" as const),
|
||||||
mekVersion: file.mekVersion,
|
mekVersion: file.mekVersion,
|
||||||
encDek: file.encDek,
|
encDek: file.encDek,
|
||||||
dekVersion: file.dekVersion,
|
dekVersion: file.dekVersion,
|
||||||
|
|||||||
@@ -1,34 +1,4 @@
|
|||||||
import { writable, type Writable } from "svelte/store";
|
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 {
|
export interface FileUploadStatus {
|
||||||
name: string;
|
name: string;
|
||||||
parentId: "root" | number;
|
parentId: "root" | number;
|
||||||
@@ -45,8 +15,4 @@ export interface FileUploadStatus {
|
|||||||
estimated?: number;
|
estimated?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const directoryInfoStore = new Map<"root" | number, Writable<DirectoryInfo | null>>();
|
|
||||||
|
|
||||||
export const fileInfoStore = new Map<number, Writable<FileInfo | null>>();
|
|
||||||
|
|
||||||
export const fileUploadStatusStore = writable<Writable<FileUploadStatus>[]>([]);
|
export const fileUploadStatusStore = writable<Writable<FileUploadStatus>[]>([]);
|
||||||
|
|||||||
@@ -3,8 +3,8 @@
|
|||||||
import { untrack } from "svelte";
|
import { untrack } from "svelte";
|
||||||
import type { Writable } from "svelte/store";
|
import type { Writable } from "svelte/store";
|
||||||
import { TopBar } from "$lib/components";
|
import { TopBar } from "$lib/components";
|
||||||
import { getFileInfo } from "$lib/modules/file";
|
import { getFileInfo, type FileInfo } from "$lib/modules/filesystem";
|
||||||
import { masterKeyStore, type FileInfo } from "$lib/stores";
|
import { masterKeyStore } from "$lib/stores";
|
||||||
import { requestFileDownload } from "./service";
|
import { requestFileDownload } from "./service";
|
||||||
|
|
||||||
type ContentType = "image" | "video";
|
type ContentType = "image" | "video";
|
||||||
@@ -27,7 +27,7 @@
|
|||||||
});
|
});
|
||||||
|
|
||||||
$effect(() => {
|
$effect(() => {
|
||||||
if ($info && !isDownloaded) {
|
if ($info?.contentIv && $info?.dataKey && !isDownloaded) {
|
||||||
untrack(() => {
|
untrack(() => {
|
||||||
isDownloaded = true;
|
isDownloaded = true;
|
||||||
|
|
||||||
@@ -37,7 +37,7 @@
|
|||||||
contentType = "video";
|
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 });
|
content = new Blob([res], { type: $info.contentType });
|
||||||
if (content.type === "image/heic" || content.type === "image/heif") {
|
if (content.type === "image/heic" || content.type === "image/heif") {
|
||||||
const { default: heic2any } = await import("heic2any");
|
const { default: heic2any } = await import("heic2any");
|
||||||
|
|||||||
@@ -3,8 +3,9 @@
|
|||||||
import type { Writable } from "svelte/store";
|
import type { Writable } from "svelte/store";
|
||||||
import { TopBar } from "$lib/components";
|
import { TopBar } from "$lib/components";
|
||||||
import type { FileCacheIndex } from "$lib/indexedDB";
|
import type { FileCacheIndex } from "$lib/indexedDB";
|
||||||
import { getFileCacheIndex, getFileInfo } from "$lib/modules/file";
|
import { getFileCacheIndex } 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 File from "./File.svelte";
|
import File from "./File.svelte";
|
||||||
import { formatFileSize, deleteFileCache as doDeleteFileCache } from "./service";
|
import { formatFileSize, deleteFileCache as doDeleteFileCache } from "./service";
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import type { Writable } from "svelte/store";
|
import type { Writable } from "svelte/store";
|
||||||
import type { FileCacheIndex } from "$lib/indexedDB";
|
import type { FileCacheIndex } from "$lib/indexedDB";
|
||||||
import type { FileInfo } from "$lib/stores";
|
import type { FileInfo } from "$lib/modules/filesystem";
|
||||||
import { formatDate, formatFileSize } from "./service";
|
import { formatDate, formatFileSize } from "./service";
|
||||||
|
|
||||||
import IconDraft from "~icons/material-symbols/draft";
|
import IconDraft from "~icons/material-symbols/draft";
|
||||||
|
|||||||
@@ -4,8 +4,8 @@
|
|||||||
import { goto } from "$app/navigation";
|
import { goto } from "$app/navigation";
|
||||||
import { TopBar } from "$lib/components";
|
import { TopBar } from "$lib/components";
|
||||||
import { FloatingButton } from "$lib/components/buttons";
|
import { FloatingButton } from "$lib/components/buttons";
|
||||||
import { getDirectoryInfo } from "$lib/modules/file";
|
import { getDirectoryInfo, type DirectoryInfo } from "$lib/modules/filesystem";
|
||||||
import { masterKeyStore, hmacSecretStore, type DirectoryInfo } from "$lib/stores";
|
import { masterKeyStore, hmacSecretStore } from "$lib/stores";
|
||||||
import CreateBottomSheet from "./CreateBottomSheet.svelte";
|
import CreateBottomSheet from "./CreateBottomSheet.svelte";
|
||||||
import CreateDirectoryModal from "./CreateDirectoryModal.svelte";
|
import CreateDirectoryModal from "./CreateDirectoryModal.svelte";
|
||||||
import DeleteDirectoryEntryModal from "./DeleteDirectoryEntryModal.svelte";
|
import DeleteDirectoryEntryModal from "./DeleteDirectoryEntryModal.svelte";
|
||||||
|
|||||||
@@ -1,14 +1,13 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { untrack } from "svelte";
|
import { untrack } from "svelte";
|
||||||
import { get, type Writable } from "svelte/store";
|
import { get, type Writable } from "svelte/store";
|
||||||
import { getDirectoryInfo, getFileInfo } from "$lib/modules/file";
|
|
||||||
import {
|
import {
|
||||||
fileUploadStatusStore,
|
getDirectoryInfo,
|
||||||
masterKeyStore,
|
getFileInfo,
|
||||||
type DirectoryInfo,
|
type DirectoryInfo,
|
||||||
type FileInfo,
|
type FileInfo,
|
||||||
type FileUploadStatus,
|
} from "$lib/modules/filesystem";
|
||||||
} from "$lib/stores";
|
import { fileUploadStatusStore, masterKeyStore, type FileUploadStatus } from "$lib/stores";
|
||||||
import File from "./File.svelte";
|
import File from "./File.svelte";
|
||||||
import SubDirectory from "./SubDirectory.svelte";
|
import SubDirectory from "./SubDirectory.svelte";
|
||||||
import { SortBy, sortEntries } from "./service";
|
import { SortBy, sortEntries } from "./service";
|
||||||
@@ -110,7 +109,7 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if subDirectories.length + files.length > 0}
|
{#if subDirectories.length + files.length > 0}
|
||||||
<div class="pb-[4.5rem]">
|
<div class="space-y-1 pb-[4.5rem]">
|
||||||
{#each subDirectories as { info }}
|
{#each subDirectories as { info }}
|
||||||
<SubDirectory {info} onclick={onEntryClick} onOpenMenuClick={onEntryMenuClick} />
|
<SubDirectory {info} onclick={onEntryClick} onOpenMenuClick={onEntryMenuClick} />
|
||||||
{/each}
|
{/each}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import type { Writable } from "svelte/store";
|
import type { Writable } from "svelte/store";
|
||||||
import type { FileInfo } from "$lib/stores";
|
import type { FileInfo } from "$lib/modules/filesystem";
|
||||||
import { formatDateTime } from "./service";
|
import { formatDateTime } from "./service";
|
||||||
import type { SelectedDirectoryEntry } from "../service";
|
import type { SelectedDirectoryEntry } from "../service";
|
||||||
|
|
||||||
@@ -17,6 +17,8 @@
|
|||||||
|
|
||||||
const openFile = () => {
|
const openFile = () => {
|
||||||
const { id, dataKey, dataKeyVersion, name } = $info!;
|
const { id, dataKey, dataKeyVersion, name } = $info!;
|
||||||
|
if (!dataKey || !dataKeyVersion) return; // TODO: Error handling
|
||||||
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
onclick({ type: "file", id, dataKey, dataKeyVersion, name });
|
onclick({ type: "file", id, dataKey, dataKeyVersion, name });
|
||||||
}, 100);
|
}, 100);
|
||||||
@@ -26,6 +28,8 @@
|
|||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
|
|
||||||
const { id, dataKey, dataKeyVersion, name } = $info!;
|
const { id, dataKey, dataKeyVersion, name } = $info!;
|
||||||
|
if (!dataKey || !dataKeyVersion) return; // TODO: Error handling
|
||||||
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
onOpenMenuClick({ type: "file", id, dataKey, dataKeyVersion, name });
|
onOpenMenuClick({ type: "file", id, dataKey, dataKeyVersion, name });
|
||||||
}, 100);
|
}, 100);
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import type { Writable } from "svelte/store";
|
import type { Writable } from "svelte/store";
|
||||||
import type { DirectoryInfo } from "$lib/stores";
|
import type { DirectoryInfo } from "$lib/modules/filesystem";
|
||||||
import type { SelectedDirectoryEntry } from "../service";
|
import type { SelectedDirectoryEntry } from "../service";
|
||||||
|
|
||||||
import IconFolder from "~icons/material-symbols/folder";
|
import IconFolder from "~icons/material-symbols/folder";
|
||||||
@@ -18,6 +18,8 @@
|
|||||||
|
|
||||||
const openDirectory = () => {
|
const openDirectory = () => {
|
||||||
const { id, dataKey, dataKeyVersion, name } = $info as SubDirectoryInfo;
|
const { id, dataKey, dataKeyVersion, name } = $info as SubDirectoryInfo;
|
||||||
|
if (!dataKey || !dataKeyVersion) return; // TODO: Error handling
|
||||||
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
onclick({ type: "directory", id, dataKey, dataKeyVersion, name });
|
onclick({ type: "directory", id, dataKey, dataKeyVersion, name });
|
||||||
}, 100);
|
}, 100);
|
||||||
@@ -27,6 +29,8 @@
|
|||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
|
|
||||||
const { id, dataKey, dataKeyVersion, name } = $info as SubDirectoryInfo;
|
const { id, dataKey, dataKeyVersion, name } = $info as SubDirectoryInfo;
|
||||||
|
if (!dataKey || !dataKeyVersion) return; // TODO: Error handling
|
||||||
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
onOpenMenuClick({ type: "directory", id, dataKey, dataKeyVersion, name });
|
onOpenMenuClick({ type: "directory", id, dataKey, dataKeyVersion, name });
|
||||||
}, 100);
|
}, 100);
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ export const requestDirectoryCreation = async (
|
|||||||
const { dataKey, dataKeyVersion } = await generateDataKey();
|
const { dataKey, dataKeyVersion } = await generateDataKey();
|
||||||
const nameEncrypted = await encryptString(name, dataKey);
|
const nameEncrypted = await encryptString(name, dataKey);
|
||||||
await callPostApi<DirectoryCreateRequest>("/api/directory/create", {
|
await callPostApi<DirectoryCreateRequest>("/api/directory/create", {
|
||||||
parentId,
|
parent: parentId,
|
||||||
mekVersion: masterKey.version,
|
mekVersion: masterKey.version,
|
||||||
dek: await wrapDataKey(dataKey, masterKey.key),
|
dek: await wrapDataKey(dataKey, masterKey.key),
|
||||||
dekVersion: dataKeyVersion.toISOString(),
|
dekVersion: dataKeyVersion.toISOString(),
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ export const GET: RequestHandler = async ({ locals, params }) => {
|
|||||||
return json(
|
return json(
|
||||||
directoryInfoResponse.parse({
|
directoryInfoResponse.parse({
|
||||||
metadata: metadata && {
|
metadata: metadata && {
|
||||||
|
parent: metadata.parentId,
|
||||||
mekVersion: metadata.mekVersion,
|
mekVersion: metadata.mekVersion,
|
||||||
dek: metadata.encDek,
|
dek: metadata.encDek,
|
||||||
dekVersion: metadata.dekVersion.toISOString(),
|
dekVersion: metadata.dekVersion.toISOString(),
|
||||||
|
|||||||
@@ -9,11 +9,11 @@ export const POST: RequestHandler = async ({ locals, request }) => {
|
|||||||
|
|
||||||
const zodRes = directoryCreateRequest.safeParse(await request.json());
|
const zodRes = directoryCreateRequest.safeParse(await request.json());
|
||||||
if (!zodRes.success) error(400, "Invalid request body");
|
if (!zodRes.success) error(400, "Invalid request body");
|
||||||
const { parentId, mekVersion, dek, dekVersion, name, nameIv } = zodRes.data;
|
const { parent, mekVersion, dek, dekVersion, name, nameIv } = zodRes.data;
|
||||||
|
|
||||||
await createDirectory({
|
await createDirectory({
|
||||||
userId,
|
userId,
|
||||||
parentId,
|
parentId: parent,
|
||||||
mekVersion,
|
mekVersion,
|
||||||
encDek: dek,
|
encDek: dek,
|
||||||
dekVersion: new Date(dekVersion),
|
dekVersion: new Date(dekVersion),
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ export const GET: RequestHandler = async ({ locals, params }) => {
|
|||||||
const { id } = zodRes.data;
|
const { id } = zodRes.data;
|
||||||
|
|
||||||
const {
|
const {
|
||||||
|
parentId,
|
||||||
mekVersion,
|
mekVersion,
|
||||||
encDek,
|
encDek,
|
||||||
dekVersion,
|
dekVersion,
|
||||||
@@ -28,6 +29,7 @@ export const GET: RequestHandler = async ({ locals, params }) => {
|
|||||||
} = await getFileInformation(userId, id);
|
} = await getFileInformation(userId, id);
|
||||||
return json(
|
return json(
|
||||||
fileInfoResponse.parse({
|
fileInfoResponse.parse({
|
||||||
|
parent: parentId,
|
||||||
mekVersion,
|
mekVersion,
|
||||||
dek: encDek,
|
dek: encDek,
|
||||||
dekVersion: dekVersion.toISOString(),
|
dekVersion: dekVersion.toISOString(),
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ const parseFileMetadata = (userId: number, json: string) => {
|
|||||||
const zodRes = fileUploadRequest.safeParse(JSON.parse(json));
|
const zodRes = fileUploadRequest.safeParse(JSON.parse(json));
|
||||||
if (!zodRes.success) error(400, "Invalid request body");
|
if (!zodRes.success) error(400, "Invalid request body");
|
||||||
const {
|
const {
|
||||||
parentId,
|
parent,
|
||||||
mekVersion,
|
mekVersion,
|
||||||
dek,
|
dek,
|
||||||
dekVersion,
|
dekVersion,
|
||||||
@@ -32,7 +32,7 @@ const parseFileMetadata = (userId: number, json: string) => {
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
userId,
|
userId,
|
||||||
parentId,
|
parentId: parent,
|
||||||
mekVersion,
|
mekVersion,
|
||||||
encDek: dek,
|
encDek: dek,
|
||||||
dekVersion: new Date(dekVersion),
|
dekVersion: new Date(dekVersion),
|
||||||
|
|||||||
Reference in New Issue
Block a user