diff --git a/packages/api/src/routes/videos.ts b/packages/api/src/routes/videos.ts index 3de08a1..d5da7ab 100644 --- a/packages/api/src/routes/videos.ts +++ b/packages/api/src/routes/videos.ts @@ -215,6 +215,50 @@ router.post('/:id/queue-upload', async (req: AuthRequest, res: Response) => { } }); +// DELETE /videos/:id - Remove a processed video from history +router.delete('/:id', async (req: AuthRequest, res: Response) => { + try { + const userId = req.user!.userId; + const { id } = req.params; + + const video = await prisma.video.findFirst({ + where: { id, userId }, + }); + + if (!video) { + res.status(404).json({ + error: 'Not Found', + message: 'Video no encontrado', + }); + return; + } + + const deletableStatuses = [ + VideoStatus.EDITED, + VideoStatus.FAILED_EDIT, + VideoStatus.FAILED_UPLOAD, + ]; + + if (!deletableStatuses.includes(video.status)) { + res.status(400).json({ + error: 'Bad Request', + message: `Video en estado ${video.status} no se puede eliminar`, + }); + return; + } + + await prisma.video.delete({ where: { id: video.id } }); + + res.status(200).json({ ok: true }); + } catch (error) { + console.error('Delete video error:', error); + res.status(500).json({ + error: 'Internal Server Error', + message: 'Error al eliminar video', + }); + } +}); + // GET /videos/:id - Get video details with jobs router.get('/:id', async (req: AuthRequest, res: Response) => { try { diff --git a/packages/web/src/pages/HistoryPage.tsx b/packages/web/src/pages/HistoryPage.tsx index e10e9e5..1a8b0ab 100644 --- a/packages/web/src/pages/HistoryPage.tsx +++ b/packages/web/src/pages/HistoryPage.tsx @@ -45,12 +45,13 @@ const getStateLabel = (state: string) => { export const HistoryPage: React.FC = () => { const navigate = useNavigate(); const { isAuthenticated, token } = useAuthStore(); - const { jobs, fetchJobs } = useAppStore(); + const { jobs, fetchJobs, deleteVideo } = useAppStore(); const [previewVideo, setPreviewVideo] = useState<{ id: string; url: string; title: string; } | null>(null); + const [deletingId, setDeletingId] = useState(null); const queueForUpload = async (videoId: string) => { if (!token) return; @@ -81,6 +82,35 @@ export const HistoryPage: React.FC = () => { } }; + const handleDeleteJob = async (jobId: string) => { + if (!token) return; + + const confirmDelete = window.confirm( + '¿Seguro que deseas descartar este video del historial?' + ); + + if (!confirmDelete) return; + + setDeletingId(jobId); + + try { + await deleteVideo(token, jobId); + if (previewVideo?.id === jobId) { + setPreviewVideo(null); + } + alert('🗑️ Video eliminado del historial'); + } catch (error) { + console.error('Delete video error:', error); + alert( + `Error al eliminar video: ${ + error instanceof Error ? error.message : 'Error desconocido' + }` + ); + } finally { + setDeletingId(null); + } + }; + useEffect(() => { if (!isAuthenticated) { navigate('/login'); @@ -148,6 +178,19 @@ export const HistoryPage: React.FC = () => { {getStateLabel(job.state)} + {job.status && + ['failed', 'completed', 'edited'].includes( + job.state.toLowerCase() + ) && ( + + )} {job.state.toLowerCase() === 'completed' && ( <>