diff --git a/backend/api_gateway/api_gateway.py b/backend/api_gateway/api_gateway.py index 4e5c85d..5b69660 100644 --- a/backend/api_gateway/api_gateway.py +++ b/backend/api_gateway/api_gateway.py @@ -26,14 +26,14 @@ from backend.core.config import Config from backend.core.utils import setup_logger, log_exception from backend.microservices.auth_service import load_users -from backend.microservices.news_storage import store_article_in_supabase, log_user_search +from backend.microservices.news_storage import store_article_in_supabase, log_user_search, add_bookmark, get_user_bookmarks, delete_bookmark # Initialize logger logger = setup_logger(__name__) # Initialize Flask app with CORS support app = Flask(__name__) app.config['SECRET_KEY'] = os.getenv('JWT_SECRET_KEY', 'your-secret-key') # Change this in production -CORS(app, origins=Config.CORS_ORIGINS, supports_credentials=True, allow_headers=['Content-Type', 'Authorization']) +CORS(app, origins=Config.CORS_ORIGINS, supports_credentials=True, allow_headers=['Content-Type', 'Authorization', 'Access-Control-Allow-Origin'], methods=['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'], expose_headers=['Access-Control-Allow-Origin']) # Initialize Flask-RestX api = Api(app, version='1.0', title='News Aggregator API', @@ -45,6 +45,7 @@ summarize_ns = api.namespace('summarize', description='Text summarization operations') user_ns = api.namespace('api/user', description='User operations') auth_ns = api.namespace('api/auth', description='Authentication operations') +bookmark_ns = api.namespace('api/bookmarks', description='Bookmark operations') def token_required(f): @wraps(f) @@ -54,7 +55,8 @@ def decorated(*args, **kwargs): return {'error': 'Authorization header missing'}, 401 try: token = auth_header.split()[1] - payload = jwt.decode(token, app.config['SECRET_KEY'], algorithms=['HS256']) + payload = jwt.decode(token, app.config['SECRET_KEY'], algorithms=['HS256'],audience='authenticated') + return f(*args, **kwargs) except Exception as e: return {'error': 'Invalid token', 'message': str(e)}, 401 @@ -139,8 +141,6 @@ def get(self): 'message': str(e) }), 500) - - # News processing endpoint @news_ns.route('/process') @@ -169,7 +169,6 @@ def post(self): class Signup(Resource): @auth_ns.expect(signup_model) def post(self): - print('signup') """Register a new user""" data = request.get_json() username = data.get('username') @@ -196,8 +195,6 @@ def post(self): 'firstName': firstName, 'lastName': lastName } - - print(new_user) users.append(new_user) @@ -223,7 +220,6 @@ def post(self): class Login(Resource): def post(self): """Login and get authentication token""" - print('login in') data = request.get_json() username = data.get('username') password = data.get('password') @@ -263,6 +259,96 @@ def get(self): return {k: user[k] for k in user if k != 'password'}, 200 +@bookmark_ns.route('/') +class Bookmark(Resource): + @token_required + def get(self): + """Get all bookmarked articles for the authenticated user""" + try: + # Get the user ID from the token + auth_header = request.headers.get('Authorization') + token = auth_header.split()[1] + payload = jwt.decode(token, app.config['SECRET_KEY'], algorithms=['HS256'],audience='authenticated') + user_id = payload.get('sub') + + # Get bookmarks using the news_storage service + bookmarks = get_user_bookmarks(user_id) + + return { + 'status': 'success', + 'data': bookmarks + }, 200 + + except Exception as e: + logger.error(f"Error fetching bookmarks: {str(e)}") + return { + 'status': 'error', + 'message': str(e) + }, 500 + + @token_required + def post(self): + """Add a bookmark for a news article""" + try: + # Get the user ID from the token + auth_header = request.headers.get('Authorization') + token = auth_header.split()[1] + payload = jwt.decode(token, app.config['SECRET_KEY'], algorithms=['HS256'],audience='authenticated') + user_id = payload.get('sub') + + # Get the news article ID from the request body + data = request.get_json() + news_id = data.get('news_id') + + if not news_id: + return {'error': 'News article ID is required'}, 400 + + # Add the bookmark using the news_storage service + # bookmark = add_bookmark(user_id, '054c021a-f6f3-44b2-a43f-1ca0d211eb15') + bookmark = add_bookmark(user_id, news_id) + + return { + 'status': 'success', + 'message': 'Bookmark added successfully', + 'data': { + 'bookmark_id': bookmark['id'] if isinstance(bookmark, dict) else bookmark + } + }, 201 + + except Exception as e: + logger.error(f"Error adding bookmark: {str(e)}") + return { + 'status': 'error', + 'message': str(e) + }, 500 + +@bookmark_ns.route('/') +class BookmarkDelete(Resource): + @token_required + def delete(self, bookmark_id): + """Remove a bookmark for a news article""" + try: + # Get the user ID from the token + auth_header = request.headers.get('Authorization') + token = auth_header.split()[1] + payload = jwt.decode(token, app.config['SECRET_KEY'], algorithms=['HS256'],audience='authenticated') + user_id = payload.get('sub') + + # Delete the bookmark using the news_storage service + result = delete_bookmark(user_id, bookmark_id) + + return { + 'status': 'success', + 'message': 'Bookmark removed successfully' + }, 200 + + except Exception as e: + logger.error(f"Error removing bookmark: {str(e)}") + return { + 'status': 'error', + 'message': str(e) + }, 500 + if __name__ == '__main__': port = int(sys.argv[1]) if len(sys.argv) > 1 else Config.API_PORT app.run(host=Config.API_HOST, port=port, debug=True) diff --git a/backend/microservices/auth_service.py b/backend/microservices/auth_service.py index bf233e6..9f065e3 100644 --- a/backend/microservices/auth_service.py +++ b/backend/microservices/auth_service.py @@ -42,54 +42,3 @@ def load_users(): except Exception as e: print(f"Error loading users: {e}") return [] - -# @app.route('/api/auth/login', methods=['POST']) -# def login(): -# data = request.get_json() -# username = data.get('username') -# password = data.get('password') - -# if not username or not password: -# return jsonify({'error': 'Username and password are required'}), 400 - -# users = load_users() -# user = next((u for u in users if u.get('username') == username and u.get('password') == password), None) - -# if not user: -# return jsonify({'error': 'Invalid credentials'}), 401 - -# token = jwt.encode({ -# 'id': user['id'], -# 'username': user['username'], -# 'exp': datetime.datetime.utcnow() + datetime.timedelta(hours=1) -# }, app.config['SECRET_KEY'], algorithm='HS256') - -# user_data = {k: user[k] for k in user if k != 'password'} -# return jsonify({'token': token, 'user': user_data}) - -# @app.route('/api/user/profile', methods=['GET']) -# def profile(): -# auth_header = request.headers.get('Authorization') -# if not auth_header: -# return jsonify({'error': 'Authorization header missing'}), 401 - -# try: -# token = auth_header.split()[1] -# payload = jwt.decode(token, app.config['SECRET_KEY'], algorithms=['HS256']) -# except Exception as e: -# return jsonify({'error': 'Invalid token', 'message': str(e)}), 401 - -# users = load_users() -# user = next((u for u in users if u.get('id') == payload.get('id')), None) -# if not user: -# return jsonify({'error': 'User not found'}), 404 - -# user_data = {k: user[k] for k in user if k != 'password'} -# return jsonify(user_data) - -# @app.route('/health', methods=['GET']) -# def health(): -# return jsonify({'status': 'Authentication service is healthy'}), 200 - -# if __name__ == '__main__': -# app.run(host='0.0.0.0', port=5003, debug=True) \ No newline at end of file diff --git a/backend/microservices/news_fetcher.py b/backend/microservices/news_fetcher.py index 6fa336c..818c52a 100644 --- a/backend/microservices/news_fetcher.py +++ b/backend/microservices/news_fetcher.py @@ -67,4 +67,9 @@ def write_to_file(articles, session_id=None): print(f"Error writing to file: {e}") if __name__ == '__main__': - fetch_news() \ No newline at end of file + fetch_news() + + + + + diff --git a/backend/microservices/news_storage.py b/backend/microservices/news_storage.py index 35861a5..a8ae7e9 100644 --- a/backend/microservices/news_storage.py +++ b/backend/microservices/news_storage.py @@ -46,4 +46,58 @@ def log_user_search(user_id, news_id, session_id): "searched_at": datetime.datetime.utcnow().isoformat(), "session_id": session_id, }).execute() - return result \ No newline at end of file + return result + +def add_bookmark(user_id, news_id): + """ + Adds a bookmark by inserting a record into the user_bookmarks table. + Returns the created bookmark record if successful. + """ + try: + result = supabase.table("user_bookmarks").insert({ + "user_id": user_id, + "news_id": news_id, + }).execute() + return result.data[0] if result.data else None + except Exception as e: + print(f"Error adding bookmark: {str(e)}") + raise e + +def get_user_bookmarks(user_id): + """ + Retrieves all bookmarked articles for a user with full article details. + Returns a list of bookmarked articles with their details. + """ + try: + # Query user_bookmarks and join with news_articles to get full article details + result = supabase.table("user_bookmarks") \ + .select( + "id," + "news_articles(id,title,summary,content,source,published_at,url,image)" + ) \ + .eq("user_id", user_id) \ + .execute() + + # Transform the result to a more friendly format + bookmarks = [] + for item in result.data: + article = item["news_articles"] + article["bookmark_id"] = item["id"] + bookmarks.append(article) + + return bookmarks + except Exception as e: + print(f"Error fetching bookmarks: {str(e)}") + raise e + +def delete_bookmark(user_id, bookmark_id): + """ + Deletes a bookmark from the user_bookmarks table. + Returns True if successful, False otherwise. + """ + try: + result = supabase.table("user_bookmarks").delete().eq("id", bookmark_id).eq("user_id", user_id).execute() + return len(result.data) > 0 + except Exception as e: + print(f"Error deleting bookmark: {str(e)}") + raise e \ No newline at end of file diff --git a/backend/microservices/summarization_service.py b/backend/microservices/summarization_service.py index f2a5547..52c123b 100755 --- a/backend/microservices/summarization_service.py +++ b/backend/microservices/summarization_service.py @@ -128,6 +128,7 @@ def process_articles(session_id): summary = run_summarization(article.get('content', '')) summarized_articles.append({ + 'id': article['id'], 'title': article['title'], 'author': article.get('author', 'Unknown Author'), 'source': article.get('source'),