feat: 增加删除文件功能

This commit is contained in:
oliviamn 2025-05-26 23:19:43 +08:00
parent dea3a6bd6a
commit fbdeba5088
4 changed files with 119 additions and 3 deletions

View File

@ -6,7 +6,7 @@ import os
from ...core.config import settings
from ...core.database import get_db
from ...models.file import File as FileModel, FileStatus
from ...services.file_service import process_file
from ...services.file_service import process_file, delete_file
from ...schemas.file import FileResponse as FileResponseSchema, FileList
import asyncio
from fastapi import WebSocketDisconnect
@ -115,4 +115,24 @@ async def websocket_endpoint(websocket: WebSocket, file_id: str, db: Session = D
await asyncio.sleep(1)
except WebSocketDisconnect:
pass
pass
@router.delete("/files/{file_id}")
async def delete_file_endpoint(
file_id: str,
db: Session = Depends(get_db)
):
"""
Delete a file and its associated records.
This will remove:
1. The database record
2. The original uploaded file
3. The processed markdown file (if it exists)
"""
try:
delete_file(file_id)
return {"message": "File deleted successfully"}
except HTTPException as e:
raise e
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))

View File

@ -7,6 +7,7 @@ import sys
import os
from ..core.services.document_service import DocumentService
from pathlib import Path
from fastapi import HTTPException
celery = Celery(
@ -15,6 +16,39 @@ celery = Celery(
backend=settings.CELERY_RESULT_BACKEND
)
def delete_file(file_id: str):
"""
Delete a file and its associated records.
This will:
1. Delete the database record
2. Delete the original uploaded file
3. Delete the processed markdown file (if it exists)
"""
db = SessionLocal()
try:
# Get the file record
file = db.query(File).filter(File.id == file_id).first()
if not file:
raise HTTPException(status_code=404, detail="File not found")
# Delete the original file if it exists
if file.original_path and os.path.exists(file.original_path):
os.remove(file.original_path)
# Delete the processed file if it exists
if file.processed_path and os.path.exists(file.processed_path):
os.remove(file.processed_path)
# Delete the database record
db.delete(file)
db.commit()
except Exception as e:
db.rollback()
raise HTTPException(status_code=500, detail=f"Error deleting file: {str(e)}")
finally:
db.close()
@celery.task
def process_file(file_id: str):
db = SessionLocal()

View File

@ -11,8 +11,13 @@ import {
Checkbox,
Button,
Chip,
Dialog,
DialogTitle,
DialogContent,
DialogActions,
Typography,
} from '@mui/material';
import { Download as DownloadIcon } from '@mui/icons-material';
import { Download as DownloadIcon, Delete as DeleteIcon } from '@mui/icons-material';
import { File, FileStatus } from '../types/file';
import { api } from '../services/api';
@ -23,6 +28,8 @@ interface FileListProps {
const FileList: React.FC<FileListProps> = ({ files, onFileStatusChange }) => {
const [selectedFiles, setSelectedFiles] = useState<string[]>([]);
const [deleteDialogOpen, setDeleteDialogOpen] = useState(false);
const [fileToDelete, setFileToDelete] = useState<string | null>(null);
const handleSelectFile = (fileId: string) => {
setSelectedFiles((prev) =>
@ -60,6 +67,29 @@ const FileList: React.FC<FileListProps> = ({ files, onFileStatusChange }) => {
}
};
const handleDeleteClick = (fileId: string) => {
setFileToDelete(fileId);
setDeleteDialogOpen(true);
};
const handleDeleteConfirm = async () => {
if (fileToDelete) {
try {
await api.deleteFile(fileToDelete);
onFileStatusChange();
} catch (error) {
console.error('Error deleting file:', error);
}
}
setDeleteDialogOpen(false);
setFileToDelete(null);
};
const handleDeleteCancel = () => {
setDeleteDialogOpen(false);
setFileToDelete(null);
};
const getStatusColor = (status: FileStatus) => {
switch (status) {
case FileStatus.SUCCESS:
@ -81,6 +111,7 @@ const FileList: React.FC<FileListProps> = ({ files, onFileStatusChange }) => {
color="primary"
onClick={handleDownloadSelected}
disabled={selectedFiles.length === 0}
sx={{ mr: 1 }}
>
Download Selected
</Button>
@ -123,10 +154,19 @@ const FileList: React.FC<FileListProps> = ({ files, onFileStatusChange }) => {
{new Date(file.created_at).toLocaleString()}
</TableCell>
<TableCell>
<IconButton
onClick={() => handleDeleteClick(file.id)}
size="small"
color="error"
sx={{ mr: 1 }}
>
<DeleteIcon />
</IconButton>
{file.status === FileStatus.SUCCESS && (
<IconButton
onClick={() => handleDownload(file.id)}
size="small"
color="primary"
>
<DownloadIcon />
</IconButton>
@ -137,6 +177,24 @@ const FileList: React.FC<FileListProps> = ({ files, onFileStatusChange }) => {
</TableBody>
</Table>
</TableContainer>
<Dialog
open={deleteDialogOpen}
onClose={handleDeleteCancel}
>
<DialogTitle>Confirm Delete</DialogTitle>
<DialogContent>
<Typography>
Are you sure you want to delete this file? This action cannot be undone.
</Typography>
</DialogContent>
<DialogActions>
<Button onClick={handleDeleteCancel}>Cancel</Button>
<Button onClick={handleDeleteConfirm} color="error" variant="contained">
Delete
</Button>
</DialogActions>
</Dialog>
</div>
);
};

View File

@ -31,4 +31,8 @@ export const api = {
});
return response.data;
},
deleteFile: async (fileId: string): Promise<void> => {
await axios.delete(`${API_BASE_URL}/files/files/${fileId}`);
},
};