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>
55 lines
2.1 KiB
Python
55 lines
2.1 KiB
Python
"""Scheduled tasks for Yottob."""
|
|
|
|
import logging
|
|
from celery.schedules import crontab
|
|
from sqlalchemy import desc
|
|
|
|
from celery_app import celery_app
|
|
from database import SessionLocal
|
|
from models import Channel, VideoEntry, DownloadStatus
|
|
from feed_parser import YouTubeFeedParser
|
|
from download_service import download_video
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
@celery_app.task
|
|
def check_and_download_latest_videos():
|
|
"""Check all channels for new videos and download the latest one if pending."""
|
|
session = SessionLocal()
|
|
try:
|
|
channels = session.query(Channel).all()
|
|
logger.info(f"Checking {len(channels)} channels for new videos")
|
|
|
|
for channel in channels:
|
|
try:
|
|
# Fetch latest feed
|
|
parser = YouTubeFeedParser(channel.channel_id)
|
|
feed_data = parser.fetch_feed()
|
|
|
|
if not feed_data:
|
|
logger.warning(f"Failed to fetch feed for channel {channel.title} ({channel.channel_id})")
|
|
continue
|
|
|
|
# Save to DB (updates channel and adds new videos)
|
|
parser.save_to_db(session, feed_data, channel.user_id)
|
|
|
|
# Get the latest video for this channel
|
|
latest_video = session.query(VideoEntry)\
|
|
.filter_by(channel_id=channel.id)\
|
|
.order_by(desc(VideoEntry.published_at))\
|
|
.first()
|
|
|
|
if latest_video and latest_video.download_status == DownloadStatus.PENDING:
|
|
logger.info(f"Queueing download for latest video: {latest_video.title} ({latest_video.video_id})")
|
|
download_video.delay(latest_video.id)
|
|
elif latest_video:
|
|
logger.info(f"Latest video {latest_video.title} status is {latest_video.download_status.value}")
|
|
|
|
except Exception as e:
|
|
logger.error(f"Error processing channel {channel.title}: {e}")
|
|
continue
|
|
|
|
finally:
|
|
session.close()
|