update
This commit is contained in:
@@ -1,8 +1,8 @@
|
||||
"""
|
||||
RSS Feed management routes
|
||||
"""
|
||||
from flask import Blueprint, request, jsonify
|
||||
from datetime import datetime
|
||||
from pymongo.errors import DuplicateKeyError
|
||||
from bson.objectid import ObjectId
|
||||
import feedparser
|
||||
from database import rss_feeds_collection
|
||||
|
||||
rss_bp = Blueprint('rss', __name__)
|
||||
@@ -10,145 +10,208 @@ rss_bp = Blueprint('rss', __name__)
|
||||
|
||||
@rss_bp.route('/api/rss-feeds', methods=['GET'])
|
||||
def get_rss_feeds():
|
||||
"""Get all RSS feeds, optionally filtered by category"""
|
||||
"""Get all RSS feeds"""
|
||||
try:
|
||||
# Get optional category filter
|
||||
category = request.args.get('category')
|
||||
feeds = list(rss_feeds_collection.find(
|
||||
{},
|
||||
{'_id': 0}
|
||||
).sort('name', 1))
|
||||
|
||||
# Build query
|
||||
query = {}
|
||||
if category:
|
||||
query['category'] = category
|
||||
# Convert datetime to ISO format
|
||||
for feed in feeds:
|
||||
if 'created_at' in feed:
|
||||
feed['created_at'] = feed['created_at'].isoformat()
|
||||
|
||||
return jsonify({
|
||||
'feeds': feeds,
|
||||
'total': len(feeds)
|
||||
}), 200
|
||||
|
||||
cursor = rss_feeds_collection.find(query).sort('created_at', -1)
|
||||
feeds = []
|
||||
for feed in cursor:
|
||||
feeds.append({
|
||||
'id': str(feed['_id']),
|
||||
'name': feed.get('name', ''),
|
||||
'url': feed.get('url', ''),
|
||||
'category': feed.get('category', 'general'),
|
||||
'active': feed.get('active', True),
|
||||
'created_at': feed.get('created_at', '').isoformat() if feed.get('created_at') else ''
|
||||
})
|
||||
return jsonify({'feeds': feeds}), 200
|
||||
except Exception as e:
|
||||
return jsonify({'error': str(e)}), 500
|
||||
|
||||
|
||||
@rss_bp.route('/api/rss-feeds', methods=['POST'])
|
||||
def add_rss_feed():
|
||||
"""Add a new RSS feed with optional category"""
|
||||
data = request.json
|
||||
name = data.get('name', '').strip()
|
||||
url = data.get('url', '').strip()
|
||||
category = data.get('category', 'general').strip().lower()
|
||||
|
||||
if not name or not url:
|
||||
return jsonify({'error': 'Name and URL are required'}), 400
|
||||
|
||||
if not url.startswith('http://') and not url.startswith('https://'):
|
||||
return jsonify({'error': 'URL must start with http:// or https://'}), 400
|
||||
|
||||
# Validate category (optional, but if provided should be reasonable)
|
||||
valid_categories = ['general', 'local', 'politics', 'sports', 'culture', 'business', 'technology', 'entertainment']
|
||||
if category and category not in valid_categories:
|
||||
# Allow custom categories but warn
|
||||
pass
|
||||
|
||||
"""Add a new RSS feed"""
|
||||
try:
|
||||
# Test if the RSS feed is valid
|
||||
try:
|
||||
feed = feedparser.parse(url)
|
||||
if not feed.entries:
|
||||
return jsonify({'error': 'Invalid RSS feed or no entries found'}), 400
|
||||
except Exception as e:
|
||||
return jsonify({'error': f'Failed to parse RSS feed: {str(e)}'}), 400
|
||||
data = request.get_json()
|
||||
|
||||
# Validate required fields
|
||||
if not data.get('name'):
|
||||
return jsonify({'error': 'Feed name is required'}), 400
|
||||
if not data.get('url'):
|
||||
return jsonify({'error': 'Feed URL is required'}), 400
|
||||
if not data.get('category'):
|
||||
return jsonify({'error': 'Category is required'}), 400
|
||||
|
||||
# Category validation - accept any string, but recommend common ones
|
||||
category = data['category'].strip().lower()
|
||||
if not category:
|
||||
return jsonify({'error': 'Category cannot be empty'}), 400
|
||||
|
||||
data['category'] = category # Normalize to lowercase
|
||||
|
||||
# Check if feed already exists
|
||||
existing = rss_feeds_collection.find_one({'url': data['url']})
|
||||
if existing:
|
||||
return jsonify({'error': 'Feed URL already exists'}), 400
|
||||
|
||||
# Create feed document
|
||||
feed_doc = {
|
||||
'name': name,
|
||||
'url': url,
|
||||
'category': category,
|
||||
'active': True,
|
||||
'name': data['name'].strip(),
|
||||
'url': data['url'].strip(),
|
||||
'category': data['category'],
|
||||
'active': data.get('active', True),
|
||||
'created_at': datetime.utcnow()
|
||||
}
|
||||
|
||||
try:
|
||||
result = rss_feeds_collection.insert_one(feed_doc)
|
||||
return jsonify({
|
||||
'message': 'RSS feed added successfully',
|
||||
'id': str(result.inserted_id),
|
||||
'category': category
|
||||
}), 201
|
||||
except DuplicateKeyError:
|
||||
return jsonify({'error': 'RSS feed URL already exists'}), 409
|
||||
|
||||
rss_feeds_collection.insert_one(feed_doc)
|
||||
|
||||
return jsonify({
|
||||
'message': 'RSS feed added successfully',
|
||||
'feed': {
|
||||
'name': feed_doc['name'],
|
||||
'url': feed_doc['url'],
|
||||
'category': feed_doc['category'],
|
||||
'active': feed_doc['active']
|
||||
}
|
||||
}), 201
|
||||
|
||||
except Exception as e:
|
||||
return jsonify({'error': str(e)}), 500
|
||||
|
||||
|
||||
@rss_bp.route('/api/rss-feeds/<feed_id>', methods=['DELETE'])
|
||||
def remove_rss_feed(feed_id):
|
||||
"""Remove an RSS feed"""
|
||||
@rss_bp.route('/api/rss-feeds/<feed_name>', methods=['PUT'])
|
||||
def update_rss_feed(feed_name):
|
||||
"""Update an RSS feed"""
|
||||
try:
|
||||
# Validate ObjectId
|
||||
try:
|
||||
obj_id = ObjectId(feed_id)
|
||||
except Exception:
|
||||
return jsonify({'error': 'Invalid feed ID'}), 400
|
||||
data = request.get_json()
|
||||
|
||||
result = rss_feeds_collection.delete_one({'_id': obj_id})
|
||||
# Build update document
|
||||
update_doc = {}
|
||||
if 'url' in data:
|
||||
update_doc['url'] = data['url'].strip()
|
||||
if 'category' in data:
|
||||
category = data['category'].strip().lower()
|
||||
if not category:
|
||||
return jsonify({'error': 'Category cannot be empty'}), 400
|
||||
update_doc['category'] = category
|
||||
if 'active' in data:
|
||||
update_doc['active'] = bool(data['active'])
|
||||
|
||||
if result.deleted_count > 0:
|
||||
return jsonify({'message': 'RSS feed removed successfully'}), 200
|
||||
else:
|
||||
return jsonify({'error': 'RSS feed not found'}), 404
|
||||
|
||||
except Exception as e:
|
||||
return jsonify({'error': str(e)}), 500
|
||||
|
||||
|
||||
@rss_bp.route('/api/rss-feeds/<feed_id>/toggle', methods=['PATCH'])
|
||||
def toggle_rss_feed(feed_id):
|
||||
"""Toggle RSS feed active status"""
|
||||
try:
|
||||
# Validate ObjectId
|
||||
try:
|
||||
obj_id = ObjectId(feed_id)
|
||||
except Exception:
|
||||
return jsonify({'error': 'Invalid feed ID'}), 400
|
||||
if not update_doc:
|
||||
return jsonify({'error': 'No fields to update'}), 400
|
||||
|
||||
# Get current status
|
||||
feed = rss_feeds_collection.find_one({'_id': obj_id})
|
||||
if not feed:
|
||||
return jsonify({'error': 'RSS feed not found'}), 404
|
||||
|
||||
# Toggle status
|
||||
new_status = not feed.get('active', True)
|
||||
result = rss_feeds_collection.update_one(
|
||||
{'_id': obj_id},
|
||||
{'$set': {'active': new_status}}
|
||||
{'name': feed_name},
|
||||
{'$set': update_doc}
|
||||
)
|
||||
|
||||
if result.modified_count > 0:
|
||||
return jsonify({
|
||||
'message': f'RSS feed {"activated" if new_status else "deactivated"} successfully',
|
||||
'active': new_status
|
||||
}), 200
|
||||
if result.matched_count > 0:
|
||||
return jsonify({'message': 'RSS feed updated successfully'}), 200
|
||||
else:
|
||||
return jsonify({'error': 'Failed to update RSS feed'}), 500
|
||||
return jsonify({'error': 'Feed not found'}), 404
|
||||
|
||||
except Exception as e:
|
||||
return jsonify({'error': str(e)}), 500
|
||||
|
||||
|
||||
@rss_bp.route('/api/rss-feeds/categories', methods=['GET'])
|
||||
def get_categories():
|
||||
"""Get all unique categories from RSS feeds"""
|
||||
@rss_bp.route('/api/rss-feeds/<feed_name>', methods=['DELETE'])
|
||||
def delete_rss_feed(feed_name):
|
||||
"""Delete an RSS feed"""
|
||||
try:
|
||||
categories = rss_feeds_collection.distinct('category')
|
||||
# Filter out None/empty and sort
|
||||
categories = sorted([c for c in categories if c])
|
||||
return jsonify({'categories': categories}), 200
|
||||
result = rss_feeds_collection.delete_one({'name': feed_name})
|
||||
|
||||
if result.deleted_count > 0:
|
||||
return jsonify({'message': f'RSS feed "{feed_name}" deleted successfully'}), 200
|
||||
else:
|
||||
return jsonify({'error': 'Feed not found'}), 404
|
||||
|
||||
except Exception as e:
|
||||
return jsonify({'error': str(e)}), 500
|
||||
|
||||
|
||||
|
||||
@rss_bp.route('/api/rss-feeds/export', methods=['GET'])
|
||||
def export_rss_feeds():
|
||||
"""Export all RSS feeds as JSON"""
|
||||
try:
|
||||
feeds = list(rss_feeds_collection.find(
|
||||
{},
|
||||
{'_id': 0}
|
||||
).sort('name', 1))
|
||||
|
||||
# Convert datetime to ISO format
|
||||
for feed in feeds:
|
||||
if 'created_at' in feed:
|
||||
feed['created_at'] = feed['created_at'].isoformat()
|
||||
|
||||
return jsonify({
|
||||
'feeds': feeds,
|
||||
'total': len(feeds),
|
||||
'exported_at': datetime.utcnow().isoformat()
|
||||
}), 200
|
||||
|
||||
except Exception as e:
|
||||
return jsonify({'error': str(e)}), 500
|
||||
|
||||
|
||||
@rss_bp.route('/api/rss-feeds/import', methods=['POST'])
|
||||
def import_rss_feeds():
|
||||
"""Import RSS feeds from JSON"""
|
||||
try:
|
||||
data = request.get_json()
|
||||
|
||||
if not data or 'feeds' not in data:
|
||||
return jsonify({'error': 'Invalid import data. Expected {feeds: [...]}'}), 400
|
||||
|
||||
feeds = data['feeds']
|
||||
|
||||
if not isinstance(feeds, list):
|
||||
return jsonify({'error': 'feeds must be an array'}), 400
|
||||
|
||||
imported = 0
|
||||
skipped = 0
|
||||
errors = []
|
||||
|
||||
for feed in feeds:
|
||||
try:
|
||||
# Validate required fields
|
||||
if not feed.get('name') or not feed.get('url') or not feed.get('category'):
|
||||
errors.append(f"Skipped invalid feed: {feed.get('name', 'unknown')}")
|
||||
skipped += 1
|
||||
continue
|
||||
|
||||
# Check if feed already exists
|
||||
existing = rss_feeds_collection.find_one({'url': feed['url']})
|
||||
if existing:
|
||||
errors.append(f"Skipped duplicate: {feed['name']}")
|
||||
skipped += 1
|
||||
continue
|
||||
|
||||
# Create feed document
|
||||
feed_doc = {
|
||||
'name': feed['name'].strip(),
|
||||
'url': feed['url'].strip(),
|
||||
'category': feed['category'].strip().lower(),
|
||||
'active': feed.get('active', True),
|
||||
'created_at': datetime.utcnow()
|
||||
}
|
||||
|
||||
rss_feeds_collection.insert_one(feed_doc)
|
||||
imported += 1
|
||||
|
||||
except Exception as e:
|
||||
errors.append(f"Error importing {feed.get('name', 'unknown')}: {str(e)}")
|
||||
skipped += 1
|
||||
|
||||
return jsonify({
|
||||
'message': f'Import complete: {imported} imported, {skipped} skipped',
|
||||
'imported': imported,
|
||||
'skipped': skipped,
|
||||
'errors': errors if errors else None
|
||||
}), 200
|
||||
|
||||
except Exception as e:
|
||||
return jsonify({'error': str(e)}), 500
|
||||
|
||||
Reference in New Issue
Block a user