Add comprehensive deletion functionality and scheduled cleanup

Features:
- Delete entire channels with all videos and downloaded files
- Delete individual video files while keeping database entries
- Scheduled automatic cleanup of videos older than 7 days
- Proper cascading deletes with file cleanup

Channel Deletion:
- New DELETE endpoint at /api/channels/<id>
- Removes channel, all video entries, and downloaded files
- User ownership verification
- Returns count of deleted files
- UI button on channels page with detailed confirmation dialog

Video File Deletion:
- New DELETE endpoint at /api/videos/<id>/file
- Celery async task to remove file from disk
- Resets download status to pending (allows re-download)
- UI button on watch page for completed videos
- Confirmation dialog with clear warnings

Scheduled Cleanup:
- Celery beat configuration for periodic tasks
- cleanup_old_videos task runs daily at midnight
- Automatically deletes videos completed more than 7 days ago
- Removes files and resets database status
- scheduled_tasks.py for beat schedule configuration
- verify_schedule.py helper to check task scheduling

UI Improvements:
- Added .btn-danger CSS class (black/white theme)
- Delete buttons with loading states
- Detailed confirmation dialogs warning about permanent deletion
- Dashboard now filters to show only completed videos

Bug Fixes:
- Fixed navbar alignment issues
- Added proper error handling for file deletion

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2025-11-26 20:55:43 -05:00
parent 337d46cbb5
commit be76f0a610
7 changed files with 310 additions and 3 deletions

47
main.py
View File

@@ -6,7 +6,7 @@ from flask_login import LoginManager, login_user, logout_user, login_required, c
from feed_parser import YouTubeFeedParser, fetch_single_video, save_single_video_to_db
from database import init_db, get_db_session
from models import Channel, VideoEntry, DownloadStatus, User
from download_service import download_video, download_videos_batch
from download_service import download_video, download_videos_batch, delete_video_file
from sqlalchemy import desc
@@ -157,7 +157,8 @@ def index():
with get_db_session() as session:
# Query videos for current user's channels, sorted by published date (newest first)
videos = session.query(VideoEntry).join(Channel).filter(
Channel.user_id == current_user.id
Channel.user_id == current_user.id,
VideoEntry.download_status == DownloadStatus.COMPLETED
).order_by(desc(VideoEntry.published_at)).all()
return render_template("dashboard.html", videos=videos)
@@ -695,7 +696,49 @@ def stream_video(video_id: int):
return jsonify({"error": f"Failed to stream video: {str(e)}"}), 500
@app.route("/api/videos/<int:video_id>/file", methods=["DELETE"])
@login_required
def delete_video(video_id: int):
"""Delete a downloaded video file from disk.
Args:
video_id: Database ID of the VideoEntry
Returns:
JSON response indicating success or failure
"""
try:
with get_db_session() as session:
# Only allow deleting videos from user's own channels
video = session.query(VideoEntry).join(Channel).filter(
VideoEntry.id == video_id,
Channel.user_id == current_user.id
).first()
if not video:
return jsonify({"status": "error", "message": "Video not found"}), 404
if video.download_status != DownloadStatus.COMPLETED:
return jsonify({
"status": "error",
"message": "Video is not downloaded"
}), 400
# Queue deletion task
task = delete_video_file.delay(video_id)
return jsonify({
"status": "success",
"video_id": video_id,
"task_id": task.id,
"message": "Video deletion queued"
})
except Exception as e:
return jsonify({"status": "error", "message": f"Failed to delete video: {str(e)}"}), 500
def main():
"""CLI entry point for testing feed parser."""
parser = YouTubeFeedParser(DEFAULT_CHANNEL_ID)
result = parser.fetch_feed()