0.2.1 Release (#32)

* feat(security): implement CSRF protection

* chore: clean up CSRF implementation

  - Remove unused generateCsrfToken export from security.ts
  - Remove redundant /csrf-token path check (GET already exempt)
  - Restore defineConfig wrapper in vitest.config.ts for type safety

* add K8S note in README, fix broken e2e

* feat/upload-bar (#30)

* feat/upload-bar: add a upload bar when user upload file, indicate the upload process

* feat/save-loading-status: add save status when click back button from editor

* fix: address PR review issues in upload and save features

- Replace deprecated substr() with substring() in UploadContext
- Fix broken error handling that checked stale task status
- Fix missing useEffect dependency in UploadStatus
- Fix CSS class conflict in progress bar styling
- Add error recovery for save state in Editor (reset on failure)
- Use .finally() instead of .then() to ensure refresh on upload failure
- Fix inconsistent indentation in UploadContext

* fix e2e tests

---------

Co-authored-by: Zimeng Xiong <zxzimeng@gmail.com>

* chore: pre-release v0.2.1-dev

* Update backend/src/security.ts

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* fix filename/math random UUID generation

---------

Co-authored-by: AdrianAcala <adrianacala017@gmail.com>
Co-authored-by: adamant368 <60790941+Yiheng-Liu@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
This commit is contained in:
Zimeng Xiong
2026-01-14 11:25:27 -08:00
committed by GitHub
parent e75b727a5a
commit 0476315322
37 changed files with 2074 additions and 685 deletions
+86
View File
@@ -0,0 +1,86 @@
import React, { createContext, useContext, useState, useCallback, type ReactNode } from 'react';
import { importDrawings } from '../utils/importUtils';
export type UploadStatus = 'pending' | 'uploading' | 'processing' | 'success' | 'error';
export interface UploadTask {
id: string;
fileName: string;
status: UploadStatus;
progress: number;
error?: string;
}
interface UploadContextType {
tasks: UploadTask[];
uploadFiles: (files: File[], targetCollectionId: string | null) => Promise<void>;
clearCompleted: () => void;
removeTask: (id: string) => void;
isUploading: boolean;
}
const UploadContext = createContext<UploadContextType | undefined>(undefined);
export const useUpload = () => {
const context = useContext(UploadContext);
if (!context) {
throw new Error('useUpload must be used within an UploadProvider');
}
return context;
};
export const UploadProvider: React.FC<{ children: ReactNode }> = ({ children }) => {
const [tasks, setTasks] = useState<UploadTask[]>([]);
const isUploading = tasks.some(t => t.status === 'uploading' || t.status === 'processing');
const updateTask = useCallback((id: string, updates: Partial<UploadTask>) => {
setTasks(prev => prev.map(t => t.id === id ? { ...t, ...updates } : t));
}, []);
const removeTask = useCallback((id: string) => {
setTasks(prev => prev.filter(t => t.id !== id));
}, []);
const clearCompleted = useCallback(() => {
setTasks(prev => prev.filter(t => t.status !== 'success' && t.status !== 'error'));
}, []);
const uploadFiles = useCallback(async (files: File[], targetCollectionId: string | null) => {
const newTasks: UploadTask[] = files.map(f => ({
id: crypto.randomUUID(),
fileName: f.name,
status: 'pending',
progress: 0
}));
setTasks(prev => [...newTasks, ...prev]);
// Map file index to task ID for progress callbacks (handles duplicate filenames)
const indexToTaskId = new Map<number, string>();
newTasks.forEach((t, index) => indexToTaskId.set(index, t.id));
const handleProgress = (fileIndex: number, status: UploadStatus, progress: number, error?: string) => {
const taskId = indexToTaskId.get(fileIndex);
if (taskId) {
updateTask(taskId, { status, progress, error });
}
};
try {
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' });
});
}
}, [updateTask]);
return (
<UploadContext.Provider value={{ tasks, uploadFiles, clearCompleted, removeTask, isUploading }}>
{children}
</UploadContext.Provider>
);
};