287 lines
9.6 KiB
Python
287 lines
9.6 KiB
Python
"""
|
|
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/<int:podcast_id>', 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, podcast_metadata = get_podcast_episodes(podcast.feed_url)
|
|
logger.info(f"Found {len(episodes_data)} episodes in feed")
|
|
|
|
# Update podcast metadata if available from feed
|
|
updated = False
|
|
|
|
# Update image URL if available
|
|
if podcast_metadata.get('image_url') and not podcast.image_url:
|
|
logger.info(f"Updating podcast image URL from feed: {podcast_metadata['image_url']}")
|
|
podcast.image_url = podcast_metadata['image_url']
|
|
updated = True
|
|
|
|
# Update author if available
|
|
if podcast_metadata.get('author') and (not podcast.author or podcast.author == "Unknown Author"):
|
|
logger.info(f"Updating podcast author from feed: '{podcast_metadata['author']}'")
|
|
podcast.author = podcast_metadata['author']
|
|
updated = True
|
|
|
|
# Update description if available
|
|
if podcast_metadata.get('description') and not podcast.description:
|
|
logger.info(f"Updating podcast description from feed")
|
|
podcast.description = podcast_metadata['description']
|
|
updated = True
|
|
|
|
# Commit changes if any updates were made
|
|
if updated:
|
|
db.session.commit()
|
|
|
|
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/<int:podcast_id>', 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/<int:podcast_id>/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/<int:podcast_id>/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/<int:episode_id>', 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/<int:episode_id>/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/<int:episode_id>', 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
|
|
|
|
# Update all podcasts API
|
|
@api_bp.route('/podcasts/update-all', methods=['POST'])
|
|
def update_all_podcasts_api():
|
|
"""
|
|
Update all podcasts to fetch new episodes.
|
|
"""
|
|
from app.services.podcast_updater import update_all_podcasts
|
|
from app.services.task_manager import task_manager
|
|
|
|
try:
|
|
# Create a background task for updating all podcasts
|
|
task_id = task_manager.create_task(
|
|
'update_all',
|
|
"Updating all podcasts",
|
|
update_all_podcasts
|
|
)
|
|
|
|
return jsonify({
|
|
'message': 'Update of all podcasts started in the background. Check the status in the tasks panel.',
|
|
'task_id': task_id
|
|
})
|
|
except Exception as e:
|
|
return jsonify({'error': str(e)}), 500
|