로그인할 때마다 다른 디바이스에서 삭제된 파일을 스캔하여 현재 디바이스에서도 삭제하도록 구현

This commit is contained in:
static
2025-07-12 03:27:49 +09:00
parent fa7ba451c3
commit eda5ff7570
10 changed files with 56 additions and 1 deletions

View File

@@ -55,6 +55,10 @@ export const deleteDirectoryInfo = async (id: number) => {
await filesystem.directory.delete(id); await filesystem.directory.delete(id);
}; };
export const getAllFileInfos = async () => {
return await filesystem.file.toArray();
};
export const getFileInfos = async (parentId: DirectoryId) => { export const getFileInfos = async (parentId: DirectoryId) => {
return await filesystem.file.where({ parentId }).toArray(); return await filesystem.file.where({ parentId }).toArray();
}; };

View File

@@ -341,6 +341,11 @@ export const getAllFilesByCategory = async (
return files.map(({ file_id, depth }) => ({ id: file_id, isRecursive: depth > 0 })); 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 ( export const getAllFileIdsByContentHmac = async (
userId: number, userId: number,
hskVersion: number, hskVersion: number,

View File

@@ -42,6 +42,11 @@ export const fileThumbnailUploadRequest = z.object({
}); });
export type FileThumbnailUploadRequest = z.input<typeof fileThumbnailUploadRequest>; export type FileThumbnailUploadRequest = z.input<typeof fileThumbnailUploadRequest>;
export const fileListResponse = z.object({
files: z.number().int().positive().array(),
});
export type FileListResponse = z.output<typeof fileListResponse>;
export const duplicateFileScanRequest = z.object({ export const duplicateFileScanRequest = z.object({
hskVersion: z.number().int().positive(), hskVersion: z.number().int().positive(),
contentHmac: z.string().base64().nonempty(), contentHmac: z.string().base64().nonempty(),

View File

@@ -9,6 +9,7 @@ import { v4 as uuidv4 } from "uuid";
import { IntegrityError } from "$lib/server/db/error"; import { IntegrityError } from "$lib/server/db/error";
import { import {
registerFile, registerFile,
getAllFileIds,
getAllFileIdsByContentHmac, getAllFileIdsByContentHmac,
getFile, getFile,
setFileEncName, 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 ( export const scanDuplicateFiles = async (
userId: number, userId: number,
hskVersion: number, hskVersion: number,

View File

@@ -1,14 +1,17 @@
import { callGetApi } from "$lib/hooks"; import { callGetApi } from "$lib/hooks";
import { getAllFileInfos } from "$lib/indexedDB/filesystem";
import { decryptData } from "$lib/modules/crypto"; import { decryptData } from "$lib/modules/crypto";
import { import {
getFileCache, getFileCache,
storeFileCache, storeFileCache,
deleteFileCache,
getFileThumbnailCache, getFileThumbnailCache,
storeFileThumbnailCache, storeFileThumbnailCache,
deleteFileThumbnailCache,
downloadFile, downloadFile,
} from "$lib/modules/file"; } from "$lib/modules/file";
import { getThumbnailUrl } from "$lib/modules/thumbnail"; 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 ( export const requestFileDownload = async (
fileId: number, fileId: number,
@@ -41,3 +44,18 @@ export const requestFileThumbnailDownload = async (fileId: number, dataKey: Cryp
storeFileThumbnailCache(fileId, thumbnailBuffer); // Intended storeFileThumbnailCache(fileId, thumbnailBuffer); // Intended
return getThumbnailUrl(thumbnailBuffer); 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)]),
);
};

View File

@@ -9,6 +9,7 @@
requestLogin, requestLogin,
requestClientRegistrationAndSessionUpgrade, requestClientRegistrationAndSessionUpgrade,
requestMasterKeyDownload, requestMasterKeyDownload,
requestDeletedFilesCleanup,
} from "./service"; } from "./service";
let { data } = $props(); let { data } = $props();
@@ -41,6 +42,7 @@
$masterKeyStore || $masterKeyStore ||
(await requestMasterKeyDownload($clientKeyStore!.decryptKey, $clientKeyStore!.verifyKey)) (await requestMasterKeyDownload($clientKeyStore!.decryptKey, $clientKeyStore!.verifyKey))
) { ) {
await requestDeletedFilesCleanup();
await goto(data.redirectPath); await goto(data.redirectPath);
} else { } else {
await redirect("/client/pending"); await redirect("/client/pending");

View File

@@ -2,6 +2,7 @@ import { callPostApi } from "$lib/hooks";
import type { LoginRequest } from "$lib/server/schemas"; import type { LoginRequest } from "$lib/server/schemas";
export { requestLogout } from "$lib/services/auth"; export { requestLogout } from "$lib/services/auth";
export { requestDeletedFilesCleanup } from "$lib/services/file";
export { export {
requestClientRegistrationAndSessionUpgrade, requestClientRegistrationAndSessionUpgrade,
requestMasterKeyDownload, requestMasterKeyDownload,

View File

@@ -15,6 +15,7 @@
importClientKeys, importClientKeys,
requestClientRegistrationAndSessionUpgrade, requestClientRegistrationAndSessionUpgrade,
requestInitialMasterKeyAndHmacSecretRegistration, requestInitialMasterKeyAndHmacSecretRegistration,
requestDeletedFilesCleanup,
} from "./service"; } from "./service";
import IconKey from "~icons/material-symbols/key"; import IconKey from "~icons/material-symbols/key";
@@ -104,6 +105,7 @@
return; return;
} }
await requestDeletedFilesCleanup();
await goto("/client/pending?redirect=" + encodeURIComponent(data.redirectPath)); await goto("/client/pending?redirect=" + encodeURIComponent(data.redirectPath));
}; };

View File

@@ -15,6 +15,7 @@ import { deserializeClientKeys } from "$lib/modules/key";
import { clientKeyStore } from "$lib/stores"; import { clientKeyStore } from "$lib/stores";
export { requestLogout } from "$lib/services/auth"; export { requestLogout } from "$lib/services/auth";
export { requestDeletedFilesCleanup } from "$lib/services/file";
export { export {
requestClientRegistrationAndSessionUpgrade, requestClientRegistrationAndSessionUpgrade,
requestInitialMasterKeyAndHmacSecretRegistration, requestInitialMasterKeyAndHmacSecretRegistration,

View File

@@ -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));
};