feat: enhance backup process with lease management and attachment deletion

- Implemented a backup runner lease mechanism to prevent concurrent backup executions.
- Added `deleteAllAttachmentsForCiphers` function to delete attachments for multiple ciphers efficiently.
- Introduced `bulkDeleteAttachmentsByIds` method in storage to handle batch deletion of attachments.
- Updated backup execution logic to utilize the new lease management and ensure timely updates during the backup process.
- Refactored cipher deletion to handle attachments more effectively.
- Improved website icon loading with a dedicated caching mechanism for better performance.
- Added new index on `ciphers` table for `folder_id` to optimize queries related to folder management.
- Enhanced response handling for CORS policy to allow credentials for specific origins.
This commit is contained in:
shuaiplus
2026-04-28 23:40:43 +08:00
parent 69b98f9e67
commit 68ded534a4
16 changed files with 505 additions and 284 deletions
+21 -37
View File
@@ -1,4 +1,4 @@
import type { Cipher, Folder } from '../types';
import type { Folder } from '../types';
function mapFolderRow(row: any): Folder {
return {
@@ -36,26 +36,18 @@ export async function deleteFolder(db: D1Database, id: string, userId: string):
export async function clearFolderFromCiphers(
db: D1Database,
userId: string,
folderId: string,
saveCipher: (cipher: Cipher) => Promise<void>
folderId: string
): Promise<void> {
const now = new Date().toISOString();
const res = await db
.prepare('SELECT data FROM ciphers WHERE user_id = ? AND folder_id = ?')
.bind(userId, folderId)
.all<{ data: string }>();
for (const row of (res.results || [])) {
let cipher: Cipher;
try {
cipher = JSON.parse(row.data) as Cipher;
} catch {
continue;
}
cipher.folderId = null;
cipher.updatedAt = now;
await saveCipher(cipher);
}
const patch = JSON.stringify({ folderId: null, updatedAt: now });
await db
.prepare(
`UPDATE ciphers
SET folder_id = NULL, updated_at = ?, data = json_patch(data, ?)
WHERE user_id = ? AND folder_id = ?`
)
.bind(now, patch, userId, folderId)
.run();
}
export async function bulkDeleteFolders(
@@ -63,34 +55,26 @@ export async function bulkDeleteFolders(
userId: string,
ids: string[],
sqlChunkSize: (fixedBindCount: number) => number,
saveCipher: (cipher: Cipher) => Promise<void>,
updateRevisionDate: (userId: string) => Promise<string>
): Promise<string | null> {
const uniqueIds = Array.from(new Set(ids.map((id) => String(id || '').trim()).filter(Boolean)));
if (!uniqueIds.length) return null;
const chunkSize = sqlChunkSize(1);
const now = new Date().toISOString();
const patch = JSON.stringify({ folderId: null, updatedAt: now });
const chunkSize = sqlChunkSize(3);
for (let i = 0; i < uniqueIds.length; i += chunkSize) {
const chunk = uniqueIds.slice(i, i + chunkSize);
const placeholders = chunk.map(() => '?').join(',');
const res = await db
.prepare(`SELECT data FROM ciphers WHERE user_id = ? AND folder_id IN (${placeholders})`)
.bind(userId, ...chunk)
.all<{ data: string }>();
for (const row of res.results || []) {
let cipher: Cipher;
try {
cipher = JSON.parse(row.data) as Cipher;
} catch {
continue;
}
cipher.folderId = null;
cipher.updatedAt = now;
await saveCipher(cipher);
}
await db
.prepare(
`UPDATE ciphers
SET folder_id = NULL, updated_at = ?, data = json_patch(data, ?)
WHERE user_id = ? AND folder_id IN (${placeholders})`
)
.bind(now, patch, userId, ...chunk)
.run();
await db
.prepare(`DELETE FROM folders WHERE user_id = ? AND id IN (${placeholders})`)