mirror of
https://github.com/shuaiplus/nodewarden.git
synced 2026-06-20 21:00:41 +00:00
feat(storage): add method to retrieve attachments by user ID for improved data handling
This commit is contained in:
@@ -77,7 +77,7 @@ export async function handleGetCiphers(request: Request, env: Env, userId: strin
|
|||||||
: ciphers.filter(c => !c.deletedAt);
|
: ciphers.filter(c => !c.deletedAt);
|
||||||
}
|
}
|
||||||
|
|
||||||
const attachmentsByCipher = await storage.getAttachmentsByCipherIds(filteredCiphers.map(c => c.id));
|
const attachmentsByCipher = await storage.getAttachmentsByUserId(userId);
|
||||||
|
|
||||||
// Get attachments for all ciphers
|
// Get attachments for all ciphers
|
||||||
const cipherResponses = [];
|
const cipherResponses = [];
|
||||||
|
|||||||
+58
-2
@@ -2,6 +2,7 @@ import { Env, Cipher, Folder, CipherType } from '../types';
|
|||||||
import { StorageService } from '../services/storage';
|
import { StorageService } from '../services/storage';
|
||||||
import { errorResponse } from '../utils/response';
|
import { errorResponse } from '../utils/response';
|
||||||
import { generateUUID } from '../utils/uuid';
|
import { generateUUID } from '../utils/uuid';
|
||||||
|
import { LIMITS } from '../config/limits';
|
||||||
|
|
||||||
// Bitwarden client import request format
|
// Bitwarden client import request format
|
||||||
interface CiphersImportRequest {
|
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
|
// POST /api/ciphers/import - Bitwarden client import endpoint
|
||||||
export async function handleCiphersImport(request: Request, env: Env, userId: string): Promise<Response> {
|
export async function handleCiphersImport(request: Request, env: Env, userId: string): Promise<Response> {
|
||||||
const storage = new StorageService(env.DB);
|
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 folderRelationships = importData.folderRelationships || [];
|
||||||
|
|
||||||
const now = new Date().toISOString();
|
const now = new Date().toISOString();
|
||||||
|
const batchChunkSize = LIMITS.performance.bulkMoveChunkSize;
|
||||||
|
|
||||||
// Create folders and build index -> id mapping
|
// Create folders and build index -> id mapping
|
||||||
const folderIdMap = new Map<number, string>();
|
const folderIdMap = new Map<number, string>();
|
||||||
|
const folderRows: Folder[] = [];
|
||||||
|
|
||||||
for (let i = 0; i < folders.length; i++) {
|
for (let i = 0; i < folders.length; i++) {
|
||||||
const folderId = generateUUID();
|
const folderId = generateUUID();
|
||||||
@@ -98,7 +112,19 @@ export async function handleCiphersImport(request: Request, env: Env, userId: st
|
|||||||
updatedAt: now,
|
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
|
// Build cipher index -> folder id mapping from relationships
|
||||||
@@ -111,6 +137,7 @@ export async function handleCiphersImport(request: Request, env: Env, userId: st
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Create ciphers
|
// Create ciphers
|
||||||
|
const cipherRows: Cipher[] = [];
|
||||||
for (let i = 0; i < ciphers.length; i++) {
|
for (let i = 0; i < ciphers.length; i++) {
|
||||||
const c = ciphers[i];
|
const c = ciphers[i];
|
||||||
const folderId = cipherFolderMap.get(i) || null;
|
const folderId = cipherFolderMap.get(i) || null;
|
||||||
@@ -181,7 +208,36 @@ export async function handleCiphersImport(request: Request, env: Env, userId: st
|
|||||||
deletedAt: null,
|
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
|
// Update revision date
|
||||||
|
|||||||
@@ -60,7 +60,7 @@ export async function handleSync(request: Request, env: Env, userId: string): Pr
|
|||||||
|
|
||||||
const ciphers = await storage.getAllCiphers(userId);
|
const ciphers = await storage.getAllCiphers(userId);
|
||||||
const folders = await storage.getAllFolders(userId);
|
const folders = await storage.getAllFolders(userId);
|
||||||
const attachmentsByCipher = await storage.getAttachmentsByCipherIds(ciphers.map(c => c.id));
|
const attachmentsByCipher = await storage.getAttachmentsByUserId(userId);
|
||||||
|
|
||||||
// Build profile response
|
// Build profile response
|
||||||
const profile: ProfileResponse = {
|
const profile: ProfileResponse = {
|
||||||
|
|||||||
@@ -503,6 +503,38 @@ export class StorageService {
|
|||||||
return grouped;
|
return grouped;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async getAttachmentsByUserId(userId: string): Promise<Map<string, Attachment[]>> {
|
||||||
|
const grouped = new Map<string, Attachment[]>();
|
||||||
|
const res = await this.db
|
||||||
|
.prepare(
|
||||||
|
`SELECT a.id, a.cipher_id, a.file_name, a.size, a.size_name, a.key
|
||||||
|
FROM attachments a
|
||||||
|
INNER JOIN ciphers c ON c.id = a.cipher_id
|
||||||
|
WHERE c.user_id = ?`
|
||||||
|
)
|
||||||
|
.bind(userId)
|
||||||
|
.all<any>();
|
||||||
|
|
||||||
|
for (const row of (res.results || [])) {
|
||||||
|
const item: Attachment = {
|
||||||
|
id: row.id,
|
||||||
|
cipherId: row.cipher_id,
|
||||||
|
fileName: row.file_name,
|
||||||
|
size: row.size,
|
||||||
|
sizeName: row.size_name,
|
||||||
|
key: row.key,
|
||||||
|
};
|
||||||
|
const list = grouped.get(item.cipherId);
|
||||||
|
if (list) {
|
||||||
|
list.push(item);
|
||||||
|
} else {
|
||||||
|
grouped.set(item.cipherId, [item]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return grouped;
|
||||||
|
}
|
||||||
|
|
||||||
async addAttachmentToCipher(cipherId: string, attachmentId: string): Promise<void> {
|
async addAttachmentToCipher(cipherId: string, attachmentId: string): Promise<void> {
|
||||||
// Kept for API compatibility; no-op because attachments table already links cipher_id.
|
// Kept for API compatibility; no-op because attachments table already links cipher_id.
|
||||||
// We still validate that the attachment exists and belongs to cipher.
|
// We still validate that the attachment exists and belongs to cipher.
|
||||||
|
|||||||
Reference in New Issue
Block a user