Files
nodewarden/src/handlers/folders.ts
T
shuaiplus f4d2e7932a Refactor VaultPage component: remove exposed password checks, add bulk delete functionality for folders, and improve list rendering performance
- Removed password breach checking logic and related state management from VaultPage.
- Introduced bulk delete functionality for folders with a confirmation dialog.
- Enhanced list rendering with virtualization to improve performance.
- Updated styles for folder actions and list items for better UI consistency.
- Removed unused password breach library and related translations.
2026-03-11 02:22:35 +08:00

163 lines
5.0 KiB
TypeScript

import { Env, Folder, FolderResponse } from '../types';
import { notifyUserVaultSync } from '../durable/notifications-hub';
import { StorageService } from '../services/storage';
import { jsonResponse, errorResponse } from '../utils/response';
import { readActingDeviceIdentifier } from '../utils/device';
import { generateUUID } from '../utils/uuid';
import { parsePagination, encodeContinuationToken } from '../utils/pagination';
async function notifyVaultSyncForRequest(
request: Request,
env: Env,
userId: string,
revisionDate: string
): Promise<void> {
await notifyUserVaultSync(env, userId, revisionDate, readActingDeviceIdentifier(request));
}
// Convert internal folder to API response format
function folderToResponse(folder: Folder): FolderResponse {
return {
id: folder.id,
name: folder.name,
revisionDate: folder.updatedAt,
object: 'folder',
};
}
// GET /api/folders
export async function handleGetFolders(request: Request, env: Env, userId: string): Promise<Response> {
const storage = new StorageService(env.DB);
const url = new URL(request.url);
const pagination = parsePagination(url);
let folders: Folder[];
let continuationToken: string | null = null;
if (pagination) {
const pageRows = await storage.getFoldersPage(userId, pagination.limit + 1, pagination.offset);
const hasNext = pageRows.length > pagination.limit;
folders = hasNext ? pageRows.slice(0, pagination.limit) : pageRows;
continuationToken = hasNext ? encodeContinuationToken(pagination.offset + folders.length) : null;
} else {
folders = await storage.getAllFolders(userId);
}
return jsonResponse({
data: folders.map(folderToResponse),
object: 'list',
continuationToken: continuationToken,
});
}
// GET /api/folders/:id
export async function handleGetFolder(request: Request, env: Env, userId: string, id: string): Promise<Response> {
const storage = new StorageService(env.DB);
const folder = await storage.getFolder(id);
if (!folder || folder.userId !== userId) {
return errorResponse('Folder not found', 404);
}
return jsonResponse(folderToResponse(folder));
}
// POST /api/folders
export async function handleCreateFolder(request: Request, env: Env, userId: string): Promise<Response> {
const storage = new StorageService(env.DB);
let body: { name?: string };
try {
body = await request.json();
} catch {
return errorResponse('Invalid JSON', 400);
}
if (!body.name) {
return errorResponse('Name is required', 400);
}
const now = new Date().toISOString();
const folder: Folder = {
id: generateUUID(),
userId: userId,
name: body.name,
createdAt: now,
updatedAt: now,
};
await storage.saveFolder(folder);
const revisionDate = await storage.updateRevisionDate(userId);
await notifyVaultSyncForRequest(request, env, userId, revisionDate);
return jsonResponse(folderToResponse(folder), 200);
}
// PUT /api/folders/:id
export async function handleUpdateFolder(request: Request, env: Env, userId: string, id: string): Promise<Response> {
const storage = new StorageService(env.DB);
const folder = await storage.getFolder(id);
if (!folder || folder.userId !== userId) {
return errorResponse('Folder not found', 404);
}
let body: { name?: string };
try {
body = await request.json();
} catch {
return errorResponse('Invalid JSON', 400);
}
if (body.name) {
folder.name = body.name;
}
folder.updatedAt = new Date().toISOString();
await storage.saveFolder(folder);
const revisionDate = await storage.updateRevisionDate(userId);
await notifyVaultSyncForRequest(request, env, userId, revisionDate);
return jsonResponse(folderToResponse(folder));
}
// DELETE /api/folders/:id
export async function handleDeleteFolder(request: Request, env: Env, userId: string, id: string): Promise<Response> {
const storage = new StorageService(env.DB);
const folder = await storage.getFolder(id);
if (!folder || folder.userId !== userId) {
return errorResponse('Folder not found', 404);
}
await storage.clearFolderFromCiphers(userId, id);
await storage.deleteFolder(id, userId);
const revisionDate = await storage.updateRevisionDate(userId);
await notifyVaultSyncForRequest(request, env, userId, revisionDate);
return new Response(null, { status: 204 });
}
// POST /api/folders/delete
export async function handleBulkDeleteFolders(request: Request, env: Env, userId: string): Promise<Response> {
const storage = new StorageService(env.DB);
let body: { ids?: string[] };
try {
body = await request.json();
} catch {
return errorResponse('Invalid JSON', 400);
}
const ids = Array.isArray(body.ids) ? body.ids.map((id) => String(id || '').trim()).filter(Boolean) : [];
if (!ids.length) {
return errorResponse('Folder ids are required', 400);
}
const revisionDate = await storage.bulkDeleteFolders(ids, userId);
if (revisionDate) {
await notifyVaultSyncForRequest(request, env, userId, revisionDate);
}
return new Response(null, { status: 204 });
}