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

1""" 

2Flask routes for news API endpoints. 

3""" 

4 

5from flask import Blueprint, jsonify, request, session 

6from loguru import logger 

7 

8from ...news import api as news_api 

9from ...news.exceptions import NewsAPIException 

10 

11# Create blueprint 

12bp = Blueprint("news_api", __name__, url_prefix="/api/news") 

13 

14 

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 

20 

21 

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") 

32 

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 ) 

41 

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 

49 

50 

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 

63 

64 

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 

77 

78 

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() 

85 

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 ) 

103 

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 

110 

111 

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 

125 

126 

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 

141 

142 

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 

156 

157 

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 

172 

173 

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() 

180 

181 card_id = data.get("card_id") 

182 vote = data.get("vote") 

183 

184 if not card_id or vote not in ["up", "down"]: 

185 return jsonify({"error": "Invalid request"}), 400 

186 

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 

194 

195 

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") 

203 

204 if not card_id: 

205 return jsonify({"error": "card_id is required"}), 400 

206 

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 

214 

215 

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() 

222 

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 

230 

231 

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