test: stabilize e2e auth and rate limits
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
import React, { createContext, useContext, useState, useCallback, type ReactNode } from 'react';
|
||||
import { importDrawings } from '../utils/importUtils';
|
||||
import { importDrawings, type ImportResult } from '../utils/importUtils';
|
||||
|
||||
export type UploadStatus = 'pending' | 'uploading' | 'processing' | 'success' | 'error';
|
||||
|
||||
@@ -11,9 +11,35 @@ export interface UploadTask {
|
||||
error?: string;
|
||||
}
|
||||
|
||||
const generateUploadId = (): string => {
|
||||
const cryptoObj: Crypto | undefined =
|
||||
typeof globalThis !== "undefined"
|
||||
? globalThis.crypto || (globalThis as any).msCrypto
|
||||
: undefined;
|
||||
|
||||
if (cryptoObj?.randomUUID) {
|
||||
return cryptoObj.randomUUID();
|
||||
}
|
||||
|
||||
if (cryptoObj?.getRandomValues) {
|
||||
const bytes = new Uint8Array(16);
|
||||
cryptoObj.getRandomValues(bytes);
|
||||
bytes[6] = (bytes[6] & 0x0f) | 0x40; // RFC 4122 version 4
|
||||
bytes[8] = (bytes[8] & 0x3f) | 0x80; // RFC 4122 variant
|
||||
const hex = Array.from(bytes, (b) => b.toString(16).padStart(2, "0"));
|
||||
return `${hex.slice(0, 4).join("")}-${hex.slice(4, 6).join("")}-${hex
|
||||
.slice(6, 8)
|
||||
.join("")}-${hex.slice(8, 10).join("")}-${hex.slice(10).join("")}`;
|
||||
}
|
||||
|
||||
return `upload-${Date.now().toString(16)}-${Math.random()
|
||||
.toString(16)
|
||||
.slice(2)}`;
|
||||
};
|
||||
|
||||
interface UploadContextType {
|
||||
tasks: UploadTask[];
|
||||
uploadFiles: (files: File[], targetCollectionId: string | null) => Promise<void>;
|
||||
uploadFiles: (files: File[], targetCollectionId: string | null) => Promise<ImportResult>;
|
||||
clearCompleted: () => void;
|
||||
removeTask: (id: string) => void;
|
||||
isUploading: boolean;
|
||||
@@ -48,7 +74,7 @@ export const UploadProvider: React.FC<{ children: ReactNode }> = ({ children })
|
||||
|
||||
const uploadFiles = useCallback(async (files: File[], targetCollectionId: string | null) => {
|
||||
const newTasks: UploadTask[] = files.map(f => ({
|
||||
id: crypto.randomUUID(),
|
||||
id: generateUploadId(),
|
||||
fileName: f.name,
|
||||
status: 'pending',
|
||||
progress: 0
|
||||
@@ -68,13 +94,18 @@ export const UploadProvider: React.FC<{ children: ReactNode }> = ({ children })
|
||||
};
|
||||
|
||||
try {
|
||||
await importDrawings(files, targetCollectionId, undefined, handleProgress);
|
||||
return await importDrawings(files, targetCollectionId, undefined, handleProgress);
|
||||
} catch (e) {
|
||||
console.error("Global upload error", e);
|
||||
// Mark all new tasks as error if something crashed completely
|
||||
newTasks.forEach(t => {
|
||||
updateTask(t.id, { status: 'error', error: 'Upload failed unexpectedly' });
|
||||
});
|
||||
return {
|
||||
success: 0,
|
||||
failed: newTasks.length,
|
||||
errors: ['Upload failed unexpectedly'],
|
||||
};
|
||||
}
|
||||
}, [updateTask]);
|
||||
|
||||
|
||||
@@ -306,10 +306,20 @@ export const Dashboard: React.FC = () => {
|
||||
const targetCollectionId = selectedCollectionId === undefined ? null : selectedCollectionId;
|
||||
|
||||
// Use the global upload context
|
||||
uploadFiles(fileArray, targetCollectionId).finally(() => {
|
||||
try {
|
||||
const result = await uploadFiles(fileArray, targetCollectionId);
|
||||
if (result.failed > 0) {
|
||||
setShowImportError({
|
||||
isOpen: true,
|
||||
message: result.errors.length > 0
|
||||
? result.errors.join("\n")
|
||||
: "Some files failed to import.",
|
||||
});
|
||||
}
|
||||
} finally {
|
||||
// Refresh after all uploads complete (success or failure)
|
||||
refreshData();
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const handleRenameDrawing = async (id: string, name: string) => {
|
||||
@@ -532,9 +542,19 @@ export const Dashboard: React.FC = () => {
|
||||
|
||||
const drawingFiles = files.filter(f => !f.name.endsWith('.excalidrawlib'));
|
||||
if (drawingFiles.length > 0) {
|
||||
uploadFiles(drawingFiles, targetCollectionId).finally(() => {
|
||||
try {
|
||||
const result = await uploadFiles(drawingFiles, targetCollectionId);
|
||||
if (result.failed > 0) {
|
||||
setShowImportError({
|
||||
isOpen: true,
|
||||
message: result.errors.length > 0
|
||||
? result.errors.join("\n")
|
||||
: "Some files failed to import.",
|
||||
});
|
||||
}
|
||||
} finally {
|
||||
refreshData();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
|
||||
@@ -2,6 +2,12 @@ import { exportToSvg } from "@excalidraw/excalidraw";
|
||||
import { api } from "../api";
|
||||
import { type UploadStatus } from "../context/UploadContext";
|
||||
|
||||
export type ImportResult = {
|
||||
success: number;
|
||||
failed: number;
|
||||
errors: string[];
|
||||
};
|
||||
|
||||
export const importDrawings = async (
|
||||
files: File[],
|
||||
targetCollectionId: string | null,
|
||||
|
||||
Reference in New Issue
Block a user