Coverage for src / local_deep_research / web / routes / news_routes.py: 20%
154 statements
« prev ^ index » next coverage.py v7.12.0, created at 2026-01-11 00:51 +0000
« prev ^ index » next coverage.py v7.12.0, created at 2026-01-11 00:51 +0000
1"""
2Flask routes for news API endpoints.
3"""
5from flask import Blueprint, jsonify, request, session
6from loguru import logger
8from ...news import api as news_api
9from ...news.exceptions import NewsAPIException
11# Create blueprint
12bp = Blueprint("news_api", __name__, url_prefix="/api/news")
15@bp.errorhandler(NewsAPIException)
16def handle_news_api_exception(error: NewsAPIException):
17 """Handle NewsAPIException and convert to JSON response."""
18 logger.error(f"News API error: {error.message} (code: {error.error_code})")
19 return jsonify(error.to_dict()), error.status_code
22@bp.route("/feed", methods=["GET"])
23def get_news_feed():
24 """Get personalized news feed."""
25 try:
26 user_id = session.get("username", "anonymous")
27 limit = request.args.get("limit", 20, type=int)
28 use_cache = request.args.get("use_cache", "true").lower() == "true"
29 focus = request.args.get("focus")
30 search_strategy = request.args.get("search_strategy")
31 subscription_id = request.args.get("subscription_id")
33 result = news_api.get_news_feed(
34 user_id=user_id,
35 limit=limit,
36 use_cache=use_cache,
37 focus=focus,
38 search_strategy=search_strategy,
39 subscription_id=subscription_id,
40 )
42 return jsonify(result)
43 except NewsAPIException:
44 # Let the error handler deal with it
45 raise
46 except Exception:
47 logger.exception("Unexpected error in get_news_feed")
48 return jsonify({"error": "Internal server error"}), 500
51@bp.route("/debug/research", methods=["GET"])
52def debug_research_items():
53 """Debug endpoint to check research items in database."""
54 try:
55 user_id = session.get("username", "anonymous")
56 result = news_api.debug_research_items(user_id)
57 return jsonify(result)
58 except NewsAPIException:
59 raise
60 except Exception:
61 logger.exception("Unexpected error in debug_research_items")
62 return jsonify({"error": "Internal server error"}), 500
65@bp.route("/subscriptions", methods=["GET"])
66def get_subscriptions():
67 """Get all subscriptions for the current user."""
68 try:
69 user_id = session.get("username", "anonymous")
70 result = news_api.get_subscriptions(user_id)
71 return jsonify(result)
72 except NewsAPIException:
73 raise
74 except Exception:
75 logger.exception("Unexpected error in get_subscriptions")
76 return jsonify({"error": "Internal server error"}), 500
79@bp.route("/subscriptions", methods=["POST"])
80def create_subscription():
81 """Create a new subscription."""
82 try:
83 user_id = session.get("username", "anonymous")
84 data = request.get_json()
86 result = news_api.create_subscription(
87 user_id=user_id,
88 query=data.get("query"),
89 subscription_type=data.get("type", "search"),
90 refresh_minutes=data.get("refresh_minutes"),
91 source_research_id=data.get("source_research_id"),
92 model_provider=data.get("model_provider"),
93 model=data.get("model"),
94 search_strategy=data.get("search_strategy"),
95 custom_endpoint=data.get("custom_endpoint"),
96 name=data.get("name"),
97 folder_id=data.get("folder_id"),
98 is_active=data.get("is_active", True),
99 search_engine=data.get("search_engine"),
100 search_iterations=data.get("search_iterations"),
101 questions_per_iteration=data.get("questions_per_iteration"),
102 )
104 return jsonify(result), 201
105 except NewsAPIException:
106 raise
107 except Exception:
108 logger.exception("Unexpected error in create_subscription")
109 return jsonify({"error": "Internal server error"}), 500
112@bp.route("/subscriptions/<subscription_id>", methods=["GET"])
113def get_subscription(subscription_id):
114 """Get a single subscription by ID."""
115 try:
116 result = news_api.get_subscription(subscription_id)
117 return jsonify(result)
118 except NewsAPIException:
119 raise
120 except Exception:
121 logger.exception(
122 f"Unexpected error getting subscription {subscription_id}"
123 )
124 return jsonify({"error": "Internal server error"}), 500
127@bp.route("/subscriptions/<subscription_id>", methods=["PUT", "PATCH"])
128def update_subscription(subscription_id):
129 """Update an existing subscription."""
130 try:
131 data = request.get_json()
132 result = news_api.update_subscription(subscription_id, data)
133 return jsonify(result)
134 except NewsAPIException:
135 raise
136 except Exception:
137 logger.exception(
138 f"Unexpected error updating subscription {subscription_id}"
139 )
140 return jsonify({"error": "Internal server error"}), 500
143@bp.route("/subscriptions/<subscription_id>", methods=["DELETE"])
144def delete_subscription(subscription_id):
145 """Delete a subscription."""
146 try:
147 result = news_api.delete_subscription(subscription_id)
148 return jsonify(result)
149 except NewsAPIException:
150 raise
151 except Exception:
152 logger.exception(
153 f"Unexpected error deleting subscription {subscription_id}"
154 )
155 return jsonify({"error": "Internal server error"}), 500
158@bp.route("/subscriptions/<subscription_id>/history", methods=["GET"])
159def get_subscription_history(subscription_id):
160 """Get research history for a specific subscription."""
161 try:
162 limit = request.args.get("limit", 20, type=int)
163 result = news_api.get_subscription_history(subscription_id, limit)
164 return jsonify(result)
165 except NewsAPIException:
166 raise
167 except Exception:
168 logger.exception(
169 f"Unexpected error getting history for {subscription_id}"
170 )
171 return jsonify({"error": "Internal server error"}), 500
174@bp.route("/feedback", methods=["POST"])
175def submit_feedback():
176 """Submit feedback (vote) for a news card."""
177 try:
178 user_id = session.get("username", "anonymous")
179 data = request.get_json()
181 card_id = data.get("card_id")
182 vote = data.get("vote")
184 if not card_id or vote not in ["up", "down"]:
185 return jsonify({"error": "Invalid request"}), 400
187 result = news_api.submit_feedback(card_id, user_id, vote)
188 return jsonify(result)
189 except NewsAPIException:
190 raise
191 except Exception:
192 logger.exception("Unexpected error in submit_feedback")
193 return jsonify({"error": "Internal server error"}), 500
196@bp.route("/research", methods=["POST"])
197def research_news_item():
198 """Perform deeper research on a news item."""
199 try:
200 data = request.get_json()
201 card_id = data.get("card_id")
202 depth = data.get("depth", "quick")
204 if not card_id:
205 return jsonify({"error": "card_id is required"}), 400
207 result = news_api.research_news_item(card_id, depth)
208 return jsonify(result)
209 except NewsAPIException:
210 raise
211 except Exception:
212 logger.exception("Unexpected error in research_news_item")
213 return jsonify({"error": "Internal server error"}), 500
216@bp.route("/preferences", methods=["POST"])
217def save_preferences():
218 """Save user preferences for news."""
219 try:
220 user_id = session.get("username", "anonymous")
221 preferences = request.get_json()
223 result = news_api.save_news_preferences(user_id, preferences)
224 return jsonify(result)
225 except NewsAPIException:
226 raise
227 except Exception:
228 logger.exception("Unexpected error in save_preferences")
229 return jsonify({"error": "Internal server error"}), 500
232@bp.route("/categories", methods=["GET"])
233def get_categories():
234 """Get available news categories with counts."""
235 try:
236 result = news_api.get_news_categories()
237 return jsonify(result)
238 except NewsAPIException:
239 raise
240 except Exception:
241 logger.exception("Unexpected error in get_categories")
242 return jsonify({"error": "Internal server error"}), 500