from flask import Blueprint, request, jsonify from datetime import datetime from pymongo.errors import DuplicateKeyError from database import subscribers_collection subscription_bp = Blueprint('subscription', __name__) @subscription_bp.route('/api/subscribe', methods=['POST']) def subscribe(): """Subscribe a user to the newsletter with category preferences""" data = request.json email = data.get('email', '').strip().lower() # Get valid categories from RSS feeds from database import rss_feeds_collection valid_categories = rss_feeds_collection.distinct('category') categories = data.get('categories', valid_categories) # Default: all categories if not email or '@' not in email: return jsonify({'error': 'Invalid email address'}), 400 # Validate categories if not isinstance(categories, list) or not categories: categories = valid_categories # Default to all if invalid else: # Filter to only valid categories (categories that exist in RSS feeds) categories = [c for c in categories if c in valid_categories] if not categories: categories = valid_categories # Default to all if none valid try: subscriber_doc = { 'email': email, 'subscribed_at': datetime.utcnow(), 'status': 'active', 'categories': categories } # Try to insert, if duplicate key error, subscriber already exists try: subscribers_collection.insert_one(subscriber_doc) return jsonify({ 'message': 'Successfully subscribed!', 'categories': categories }), 201 except DuplicateKeyError: # Check if subscriber is active existing = subscribers_collection.find_one({'email': email}) if existing and existing.get('status') == 'active': # Update categories even if already subscribed subscribers_collection.update_one( {'email': email}, {'$set': {'categories': categories}} ) return jsonify({ 'message': 'Email already subscribed. Preferences updated!', 'categories': categories }), 200 else: # Reactivate if previously unsubscribed subscribers_collection.update_one( {'email': email}, {'$set': { 'status': 'active', 'subscribed_at': datetime.utcnow(), 'categories': categories }} ) return jsonify({ 'message': 'Successfully re-subscribed!', 'categories': categories }), 200 except Exception as e: return jsonify({'error': str(e)}), 500 @subscription_bp.route('/api/unsubscribe', methods=['POST']) def unsubscribe(): """Unsubscribe a user from the newsletter""" data = request.json email = data.get('email', '').strip().lower() try: result = subscribers_collection.update_one( {'email': email}, {'$set': {'status': 'inactive'}} ) if result.matched_count > 0: return jsonify({'message': 'Successfully unsubscribed'}), 200 else: return jsonify({'error': 'Email not found in subscribers'}), 404 except Exception as e: return jsonify({'error': str(e)}), 500 @subscription_bp.route('/api/subscribers', methods=['GET']) def list_subscribers(): """List all subscribers with optional status filter""" try: # Get status filter from query params (default: all) status = request.args.get('status', None) # Build query query = {} if status: query['status'] = status # Fetch subscribers subscribers = list(subscribers_collection.find( query, {'_id': 0, 'email': 1, 'subscribed_at': 1, 'status': 1} ).sort('subscribed_at', -1)) # Convert datetime to ISO format for sub in subscribers: if 'subscribed_at' in sub: sub['subscribed_at'] = sub['subscribed_at'].isoformat() return jsonify({ 'subscribers': subscribers, 'total': len(subscribers) }), 200 except Exception as e: return jsonify({'error': str(e)}), 500 @subscription_bp.route('/api/subscribers/', methods=['GET']) def get_subscriber(email): """Get a single subscriber's information""" try: email = email.strip().lower() subscriber = subscribers_collection.find_one( {'email': email}, {'_id': 0, 'email': 1, 'categories': 1, 'status': 1, 'subscribed_at': 1} ) if subscriber: # Convert datetime to ISO format if 'subscribed_at' in subscriber: subscriber['subscribed_at'] = subscriber['subscribed_at'].isoformat() # Ensure categories field exists if 'categories' not in subscriber: subscriber['categories'] = ['general', 'local', 'sports'] return jsonify(subscriber), 200 else: return jsonify({'error': 'Email not found in subscribers'}), 404 except Exception as e: return jsonify({'error': str(e)}), 500 @subscription_bp.route('/api/subscribers/', methods=['DELETE']) def remove_subscriber(email): """Permanently remove a subscriber from the database""" try: email = email.strip().lower() result = subscribers_collection.delete_one({'email': email}) if result.deleted_count > 0: return jsonify({'message': f'Subscriber {email} permanently removed'}), 200 else: return jsonify({'error': 'Email not found in subscribers'}), 404 except Exception as e: return jsonify({'error': str(e)}), 500 @subscription_bp.route('/api/admin/subscribers/delete-all', methods=['DELETE']) def delete_all_subscribers(): """Delete all subscribers (admin only)""" try: result = subscribers_collection.delete_many({}) return jsonify({ 'message': f'Successfully deleted {result.deleted_count} subscribers', 'deleted_count': result.deleted_count }), 200 except Exception as e: return jsonify({'error': str(e)}), 500 @subscription_bp.route('/api/categories', methods=['GET']) def get_categories(): """Get available newsletter categories from RSS feeds""" from database import rss_feeds_collection # Get unique categories from RSS feeds categories_from_db = rss_feeds_collection.distinct('category') # Category metadata category_info = { 'general': { 'name': 'Top Trending', 'description': 'Top trending news and updates', 'icon': '🔥' }, 'local': { 'name': 'Local Events', 'description': 'Local events, culture, and community news', 'icon': '🏛️' }, 'sports': { 'name': 'Sports', 'description': 'Sports news and updates', 'icon': '⚽' }, 'science': { 'name': 'Science & Tech', 'description': 'Science and technology news', 'icon': '🔬' } } # Build category list categories = [] for cat_id in sorted(categories_from_db): info = category_info.get(cat_id, { 'name': cat_id.title(), 'description': f'{cat_id.title()} news', 'icon': '📄' }) categories.append({ 'id': cat_id, 'name': info['name'], 'description': info['description'], 'icon': info['icon'] }) return jsonify({'categories': categories}), 200