diff --git a/src/lib/indexedDB/filesystem.ts b/src/lib/indexedDB/filesystem.ts index 293c16d..1c2c060 100644 --- a/src/lib/indexedDB/filesystem.ts +++ b/src/lib/indexedDB/filesystem.ts @@ -55,6 +55,10 @@ export const deleteDirectoryInfo = async (id: number) => { await filesystem.directory.delete(id); }; +export const getAllFileInfos = async () => { + return await filesystem.file.toArray(); +}; + export const getFileInfos = async (parentId: DirectoryId) => { return await filesystem.file.where({ parentId }).toArray(); }; diff --git a/src/lib/server/db/file.ts b/src/lib/server/db/file.ts index db450c7..c3169fc 100644 --- a/src/lib/server/db/file.ts +++ b/src/lib/server/db/file.ts @@ -341,6 +341,11 @@ export const getAllFilesByCategory = async ( return files.map(({ file_id, depth }) => ({ id: file_id, isRecursive: depth > 0 })); }; +export const getAllFileIds = async (userId: number) => { + const files = await db.selectFrom("file").select("id").where("user_id", "=", userId).execute(); + return files.map(({ id }) => id); +}; + export const getAllFileIdsByContentHmac = async ( userId: number, hskVersion: number, diff --git a/src/lib/server/schemas/file.ts b/src/lib/server/schemas/file.ts index 07fd943..8b9cfe9 100644 --- a/src/lib/server/schemas/file.ts +++ b/src/lib/server/schemas/file.ts @@ -42,6 +42,11 @@ export const fileThumbnailUploadRequest = z.object({ }); export type FileThumbnailUploadRequest = z.input; +export const fileListResponse = z.object({ + files: z.number().int().positive().array(), +}); +export type FileListResponse = z.output; + export const duplicateFileScanRequest = z.object({ hskVersion: z.number().int().positive(), contentHmac: z.string().base64().nonempty(), diff --git a/src/lib/server/services/file.ts b/src/lib/server/services/file.ts index 0e20676..ab98dbf 100644 --- a/src/lib/server/services/file.ts +++ b/src/lib/server/services/file.ts @@ -9,6 +9,7 @@ import { v4 as uuidv4 } from "uuid"; import { IntegrityError } from "$lib/server/db/error"; import { registerFile, + getAllFileIds, getAllFileIdsByContentHmac, getFile, setFileEncName, @@ -148,6 +149,11 @@ export const uploadFileThumbnail = async ( } }; +export const getFileList = async (userId: number) => { + const fileIds = await getAllFileIds(userId); + return { files: fileIds }; +}; + export const scanDuplicateFiles = async ( userId: number, hskVersion: number, diff --git a/src/lib/services/file.ts b/src/lib/services/file.ts index 9ee6e1d..64d9fd4 100644 --- a/src/lib/services/file.ts +++ b/src/lib/services/file.ts @@ -1,14 +1,17 @@ import { callGetApi } from "$lib/hooks"; +import { getAllFileInfos } from "$lib/indexedDB/filesystem"; import { decryptData } from "$lib/modules/crypto"; import { getFileCache, storeFileCache, + deleteFileCache, getFileThumbnailCache, storeFileThumbnailCache, + deleteFileThumbnailCache, downloadFile, } from "$lib/modules/file"; import { getThumbnailUrl } from "$lib/modules/thumbnail"; -import type { FileThumbnailInfoResponse } from "$lib/server/schemas"; +import type { FileThumbnailInfoResponse, FileListResponse } from "$lib/server/schemas"; export const requestFileDownload = async ( fileId: number, @@ -41,3 +44,18 @@ export const requestFileThumbnailDownload = async (fileId: number, dataKey: Cryp storeFileThumbnailCache(fileId, thumbnailBuffer); // Intended return getThumbnailUrl(thumbnailBuffer); }; + +export const requestDeletedFilesCleanup = async () => { + const res = await callGetApi("/api/file/list"); + if (!res.ok) return; + + const { files: liveFiles }: FileListResponse = await res.json(); + const liveFilesSet = new Set(liveFiles); + const maybeCachedFiles = await getAllFileInfos(); + + await Promise.all( + maybeCachedFiles + .filter(({ id }) => !liveFilesSet.has(id)) + .flatMap(({ id }) => [deleteFileCache(id), deleteFileThumbnailCache(id)]), + ); +}; diff --git a/src/routes/(fullscreen)/auth/login/+page.svelte b/src/routes/(fullscreen)/auth/login/+page.svelte index 0adfe98..813b130 100644 --- a/src/routes/(fullscreen)/auth/login/+page.svelte +++ b/src/routes/(fullscreen)/auth/login/+page.svelte @@ -9,6 +9,7 @@ requestLogin, requestClientRegistrationAndSessionUpgrade, requestMasterKeyDownload, + requestDeletedFilesCleanup, } from "./service"; let { data } = $props(); @@ -41,6 +42,7 @@ $masterKeyStore || (await requestMasterKeyDownload($clientKeyStore!.decryptKey, $clientKeyStore!.verifyKey)) ) { + await requestDeletedFilesCleanup(); await goto(data.redirectPath); } else { await redirect("/client/pending"); diff --git a/src/routes/(fullscreen)/auth/login/service.ts b/src/routes/(fullscreen)/auth/login/service.ts index ada0f5f..88613f1 100644 --- a/src/routes/(fullscreen)/auth/login/service.ts +++ b/src/routes/(fullscreen)/auth/login/service.ts @@ -2,6 +2,7 @@ import { callPostApi } from "$lib/hooks"; import type { LoginRequest } from "$lib/server/schemas"; export { requestLogout } from "$lib/services/auth"; +export { requestDeletedFilesCleanup } from "$lib/services/file"; export { requestClientRegistrationAndSessionUpgrade, requestMasterKeyDownload, diff --git a/src/routes/(fullscreen)/key/generate/+page.svelte b/src/routes/(fullscreen)/key/generate/+page.svelte index 141c085..090651f 100644 --- a/src/routes/(fullscreen)/key/generate/+page.svelte +++ b/src/routes/(fullscreen)/key/generate/+page.svelte @@ -15,6 +15,7 @@ importClientKeys, requestClientRegistrationAndSessionUpgrade, requestInitialMasterKeyAndHmacSecretRegistration, + requestDeletedFilesCleanup, } from "./service"; import IconKey from "~icons/material-symbols/key"; @@ -104,6 +105,7 @@ return; } + await requestDeletedFilesCleanup(); await goto("/client/pending?redirect=" + encodeURIComponent(data.redirectPath)); }; diff --git a/src/routes/(fullscreen)/key/generate/service.ts b/src/routes/(fullscreen)/key/generate/service.ts index 2faef15..1ba4a4c 100644 --- a/src/routes/(fullscreen)/key/generate/service.ts +++ b/src/routes/(fullscreen)/key/generate/service.ts @@ -15,6 +15,7 @@ import { deserializeClientKeys } from "$lib/modules/key"; import { clientKeyStore } from "$lib/stores"; export { requestLogout } from "$lib/services/auth"; +export { requestDeletedFilesCleanup } from "$lib/services/file"; export { requestClientRegistrationAndSessionUpgrade, requestInitialMasterKeyAndHmacSecretRegistration, diff --git a/src/routes/api/file/list/+server.ts b/src/routes/api/file/list/+server.ts new file mode 100644 index 0000000..c1b6888 --- /dev/null +++ b/src/routes/api/file/list/+server.ts @@ -0,0 +1,11 @@ +import { json } from "@sveltejs/kit"; +import { authorize } from "$lib/server/modules/auth"; +import { fileListResponse, type FileListResponse } from "$lib/server/schemas"; +import { getFileList } from "$lib/server/services/file"; +import type { RequestHandler } from "./$types"; + +export const GET: RequestHandler = async ({ locals }) => { + const { userId } = await authorize(locals, "activeClient"); + const { files } = await getFileList(userId); + return json(fileListResponse.parse({ files } satisfies FileListResponse)); +};