""" API routes for the Podcastrr application. """ from flask import Blueprint, jsonify, request, current_app from app.models.podcast import Podcast, Episode from app.models.database import db from app.services.podcast_search import search_podcasts from app.services.podcast_downloader import download_episode import os api_bp = Blueprint('api', __name__) # Podcasts API @api_bp.route('/podcasts', methods=['GET']) def get_podcasts(): """ Get all tracked podcasts. """ podcasts = Podcast.query.all() return jsonify({ 'podcasts': [podcast.to_dict() for podcast in podcasts] }) @api_bp.route('/podcasts/', methods=['GET']) def get_podcast(podcast_id): """ Get a specific podcast. """ podcast = Podcast.query.get_or_404(podcast_id) return jsonify(podcast.to_dict()) @api_bp.route('/podcasts/search', methods=['GET']) def search_api(): """ Search for podcasts. """ query = request.args.get('q', '') if not query: return jsonify({'error': 'Query parameter is required'}), 400 results = search_podcasts(query) return jsonify({'results': results}) @api_bp.route('/podcasts', methods=['POST']) def add_podcast(): """ Add a podcast to track. """ data = request.json if not data or 'podcast_id' not in data: return jsonify({'error': 'podcast_id is required'}), 400 # Check if podcast already exists existing = Podcast.query.filter_by(external_id=data['podcast_id']).first() if existing: return jsonify({'error': 'Podcast is already being tracked'}), 409 # Get podcast details from service podcast_data = search_podcasts(podcast_id=data['podcast_id']) if not podcast_data: return jsonify({'error': 'Failed to find podcast'}), 404 # Check if feed URL is valid import logging logger = logging.getLogger(__name__) if not podcast_data.get('feed_url'): logger.error(f"No feed URL found for podcast ID: {data['podcast_id']}") return jsonify({'error': 'Podcast has no valid RSS feed URL'}), 400 logger.info(f"Adding podcast: {podcast_data['title']} with feed URL: {podcast_data['feed_url']}") podcast = Podcast( title=podcast_data['title'], author=podcast_data['author'], description=podcast_data['description'], image_url=podcast_data['image_url'], feed_url=podcast_data['feed_url'], external_id=data['podcast_id'] ) db.session.add(podcast) db.session.commit() # Fetch and add episodes for the podcast from app.services.podcast_search import get_podcast_episodes import logging logger = logging.getLogger(__name__) logger.info(f"Fetching episodes for podcast: {podcast.title} (ID: {podcast.id})") logger.info(f"Feed URL: {podcast.feed_url}") episodes_data = get_podcast_episodes(podcast.feed_url) logger.info(f"Found {len(episodes_data)} episodes in feed") episodes_added = 0 for episode_data in episodes_data: # Check if episode has required fields if not episode_data.get('guid'): logger.warning(f"Skipping episode without GUID: {episode_data.get('title', 'Unknown')}") continue if not episode_data.get('audio_url'): logger.warning(f"Skipping episode without audio URL: {episode_data.get('title', 'Unknown')}") continue # Check if episode already exists by GUID existing = Episode.query.filter_by(guid=episode_data.get('guid')).first() if existing: logger.debug(f"Episode already exists: {episode_data.get('title', 'Unknown')}") continue # Create new episode try: episode = Episode( podcast_id=podcast.id, title=episode_data.get('title', ''), description=episode_data.get('description', ''), audio_url=episode_data.get('audio_url', ''), image_url=episode_data.get('image_url', podcast.image_url), # Use podcast image if episode has none published_date=episode_data.get('published_date'), duration=episode_data.get('duration'), file_size=episode_data.get('file_size'), episode_number=episode_data.get('episode_number', ''), guid=episode_data.get('guid', '') ) db.session.add(episode) episodes_added += 1 logger.debug(f"Added episode: {episode.title}") except Exception as e: logger.error(f"Error adding episode: {str(e)}") db.session.commit() logger.info(f"Added {episodes_added} new episodes for podcast: {podcast.title}") # If no episodes were added, try updating the podcast if episodes_added == 0 and len(episodes_data) == 0: logger.warning(f"No episodes found for podcast: {podcast.title}. Trying to update...") try: from app.services.podcast_updater import update_podcast stats = update_podcast(podcast.id) logger.info(f"Podcast update completed: {stats}") except Exception as e: logger.error(f"Error updating podcast: {str(e)}") return jsonify(podcast.to_dict()), 201 @api_bp.route('/podcasts/', methods=['DELETE']) def delete_podcast(podcast_id): """ Delete a podcast from tracking. """ podcast = Podcast.query.get_or_404(podcast_id) # Delete associated episodes Episode.query.filter_by(podcast_id=podcast_id).delete() db.session.delete(podcast) db.session.commit() return jsonify({'message': f'Podcast "{podcast.title}" has been deleted'}) # Podcast update API @api_bp.route('/podcasts//update', methods=['POST']) def update_podcast_api(podcast_id): """ Update a podcast to fetch new episodes. """ from app.services.podcast_updater import update_podcast try: stats = update_podcast(podcast_id) return jsonify({ 'message': f'Podcast updated successfully. {stats["new_episodes"]} new episodes found.', 'stats': stats }) except Exception as e: return jsonify({'error': str(e)}), 500 # Episodes API @api_bp.route('/podcasts//episodes', methods=['GET']) def get_episodes(podcast_id): """ Get all episodes for a podcast. """ Podcast.query.get_or_404(podcast_id) # Ensure podcast exists episodes = Episode.query.filter_by(podcast_id=podcast_id).order_by(Episode.published_date.desc()).all() return jsonify({ 'episodes': [episode.to_dict() for episode in episodes] }) @api_bp.route('/episodes/', methods=['GET']) def get_episode(episode_id): """ Get a specific episode. """ episode = Episode.query.get_or_404(episode_id) return jsonify(episode.to_dict()) @api_bp.route('/episodes//download', methods=['POST']) def download_episode_api(episode_id): """ Download an episode. """ # Verify episode exists episode = Episode.query.get_or_404(episode_id) try: download_path = download_episode(episode_id) return jsonify({ 'message': 'Episode downloaded successfully', 'path': download_path }) except Exception as e: return jsonify({'error': str(e)}), 500 @api_bp.route('/episodes/', methods=['DELETE']) def delete_episode(episode_id): """ Delete a downloaded episode. """ episode = Episode.query.get_or_404(episode_id) if episode.file_path and os.path.exists(episode.file_path): try: os.remove(episode.file_path) episode.file_path = None episode.downloaded = False db.session.commit() return jsonify({'message': 'Episode deleted successfully'}) except Exception as e: return jsonify({'error': str(e)}), 500 else: return jsonify({'error': 'Episode file not found'}), 404