feat(storage): add method to retrieve attachments by user ID for improved data handling

This commit is contained in:
shuaiplus
2026-02-19 02:27:56 +08:00
parent ba9710cdf0
commit 9edaa647c4
4 changed files with 92 additions and 4 deletions
+58 -2
View File
@@ -2,6 +2,7 @@ import { Env, Cipher, Folder, CipherType } from '../types';
import { StorageService } from '../services/storage';
import { errorResponse } from '../utils/response';
import { generateUUID } from '../utils/uuid';
import { LIMITS } from '../config/limits';
// Bitwarden client import request format
interface CiphersImportRequest {
@@ -66,6 +67,17 @@ interface CiphersImportRequest {
}>;
}
function bindNull(v: any): any {
return v === undefined ? null : v;
}
async function runBatchInChunks(db: D1Database, statements: D1PreparedStatement[], chunkSize: number): Promise<void> {
for (let i = 0; i < statements.length; i += chunkSize) {
const chunk = statements.slice(i, i + chunkSize);
await db.batch(chunk);
}
}
// POST /api/ciphers/import - Bitwarden client import endpoint
export async function handleCiphersImport(request: Request, env: Env, userId: string): Promise<Response> {
const storage = new StorageService(env.DB);
@@ -82,9 +94,11 @@ export async function handleCiphersImport(request: Request, env: Env, userId: st
const folderRelationships = importData.folderRelationships || [];
const now = new Date().toISOString();
const batchChunkSize = LIMITS.performance.bulkMoveChunkSize;
// Create folders and build index -> id mapping
const folderIdMap = new Map<number, string>();
const folderRows: Folder[] = [];
for (let i = 0; i < folders.length; i++) {
const folderId = generateUUID();
@@ -98,7 +112,19 @@ export async function handleCiphersImport(request: Request, env: Env, userId: st
updatedAt: now,
};
await storage.saveFolder(folder);
folderRows.push(folder);
}
if (folderRows.length > 0) {
const folderStatements = folderRows.map(folder =>
env.DB
.prepare(
'INSERT INTO folders(id, user_id, name, created_at, updated_at) VALUES(?, ?, ?, ?, ?) ' +
'ON CONFLICT(id) DO UPDATE SET user_id=excluded.user_id, name=excluded.name, updated_at=excluded.updated_at'
)
.bind(folder.id, folder.userId, folder.name, folder.createdAt, folder.updatedAt)
);
await runBatchInChunks(env.DB, folderStatements, batchChunkSize);
}
// Build cipher index -> folder id mapping from relationships
@@ -111,6 +137,7 @@ export async function handleCiphersImport(request: Request, env: Env, userId: st
}
// Create ciphers
const cipherRows: Cipher[] = [];
for (let i = 0; i < ciphers.length; i++) {
const c = ciphers[i];
const folderId = cipherFolderMap.get(i) || null;
@@ -181,7 +208,36 @@ export async function handleCiphersImport(request: Request, env: Env, userId: st
deletedAt: null,
};
await storage.saveCipher(cipher);
cipherRows.push(cipher);
}
if (cipherRows.length > 0) {
const cipherStatements = cipherRows.map(cipher => {
const data = JSON.stringify(cipher);
return env.DB
.prepare(
'INSERT INTO ciphers(id, user_id, type, folder_id, name, notes, favorite, data, reprompt, key, created_at, updated_at, deleted_at) ' +
'VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) ' +
'ON CONFLICT(id) DO UPDATE SET ' +
'user_id=excluded.user_id, type=excluded.type, folder_id=excluded.folder_id, name=excluded.name, notes=excluded.notes, favorite=excluded.favorite, data=excluded.data, reprompt=excluded.reprompt, key=excluded.key, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at'
)
.bind(
cipher.id,
cipher.userId,
Number(cipher.type) || 1,
bindNull(cipher.folderId),
bindNull(cipher.name),
bindNull(cipher.notes),
cipher.favorite ? 1 : 0,
data,
bindNull(cipher.reprompt ?? 0),
bindNull(cipher.key),
cipher.createdAt,
cipher.updatedAt,
bindNull(cipher.deletedAt)
);
});
await runBatchInChunks(env.DB, cipherStatements, batchChunkSize);
}
// Update revision date