mirror of
https://github.com/shuaiplus/nodewarden.git
synced 2026-06-20 21:00:41 +00:00
192 lines
5.7 KiB
TypeScript
192 lines
5.7 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';
|
|
import { auditRequestMetadata, writeAuditEvent } from '../services/audit-events';
|
|
|
|
function notifyVaultSyncForRequest(
|
|
request: Request,
|
|
env: Env,
|
|
userId: string,
|
|
revisionDate: string
|
|
): void {
|
|
notifyUserVaultSync(env, userId, revisionDate, readActingDeviceIdentifier(request));
|
|
}
|
|
|
|
async function writeFolderAudit(
|
|
storage: StorageService,
|
|
request: Request,
|
|
userId: string,
|
|
action: string,
|
|
metadata: Record<string, unknown>
|
|
): Promise<void> {
|
|
await writeAuditEvent(storage, {
|
|
actorUserId: userId,
|
|
action,
|
|
category: 'data',
|
|
level: action.includes('delete') ? 'security' : 'info',
|
|
targetType: 'folder',
|
|
targetId: typeof metadata.id === 'string' ? metadata.id : null,
|
|
metadata: {
|
|
...metadata,
|
|
...auditRequestMetadata(request),
|
|
},
|
|
});
|
|
}
|
|
|
|
// Convert internal folder to API response format
|
|
function folderToResponse(folder: Folder): FolderResponse {
|
|
return {
|
|
id: folder.id,
|
|
name: folder.name,
|
|
revisionDate: folder.updatedAt,
|
|
creationDate: folder.createdAt,
|
|
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);
|
|
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);
|
|
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);
|
|
notifyVaultSyncForRequest(request, env, userId, revisionDate);
|
|
await writeFolderAudit(storage, request, userId, 'folder.delete', {
|
|
id,
|
|
});
|
|
|
|
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) {
|
|
notifyVaultSyncForRequest(request, env, userId, revisionDate);
|
|
await writeFolderAudit(storage, request, userId, 'folder.delete.bulk', {
|
|
count: ids.length,
|
|
});
|
|
}
|
|
|
|
return new Response(null, { status: 204 });
|
|
}
|