84 lines
3.1 KiB
Dart
84 lines
3.1 KiB
Dart
import 'package:finlytic_app/core/network/api_client.dart';
|
|
import 'package:finlytic_app/core/network/signalr_service.dart';
|
|
import 'package:finlytic_app/features/news/models/news_article_model.dart';
|
|
|
|
class NewsRepository {
|
|
final ApiClient apiClient;
|
|
final String backendUrl;
|
|
|
|
/// Central Real-Time WebSocket service (shared, authenticated `/hubs/news` connection).
|
|
/// May be null for call-sites that only need REST access (e.g. one-off dialogs) and
|
|
/// don't require the live feed; in that case [liveNewsStream] yields no events.
|
|
final SignalRService? signalRService;
|
|
|
|
NewsRepository({
|
|
required this.apiClient,
|
|
required this.backendUrl,
|
|
this.signalRService,
|
|
});
|
|
|
|
/// Live news articles pushed by the central SignalRService's `/hubs/news` connection.
|
|
/// The connection itself is established centrally (see `SignalRService.initSignalR()`),
|
|
/// so this repository only maps the already-authenticated stream to typed models.
|
|
Stream<NewsArticleModel> get liveNewsStream {
|
|
final signalR = signalRService;
|
|
if (signalR == null) return const Stream.empty();
|
|
return signalR.newsArticleStream.map(NewsArticleModel.fromJson);
|
|
}
|
|
|
|
Future<List<NewsArticleModel>> fetchNews({
|
|
int page = 1,
|
|
int pageSize = 20,
|
|
String? symbol,
|
|
String? isin,
|
|
String? date,
|
|
String? query,
|
|
bool? hasSentiment,
|
|
}) async {
|
|
try {
|
|
final Map<String, dynamic> queryParams = {
|
|
'page': page,
|
|
'pageSize': pageSize,
|
|
};
|
|
|
|
if (symbol != null && symbol.isNotEmpty) queryParams['symbol'] = symbol;
|
|
if (isin != null && isin.isNotEmpty) queryParams['isin'] = isin;
|
|
if (date != null && date.isNotEmpty) queryParams['date'] = date;
|
|
if (query != null && query.isNotEmpty) queryParams['query'] = query;
|
|
if (hasSentiment == true) queryParams['hasSentiment'] = true;
|
|
|
|
final response = await apiClient.get('/api/v1/news', queryParameters: queryParams);
|
|
|
|
if (response.statusCode == 200 && response.data is List) {
|
|
final List<dynamic> data = response.data;
|
|
return data
|
|
.whereType<Map<String, dynamic>>()
|
|
.map((json) => NewsArticleModel.fromJson(json))
|
|
.toList();
|
|
}
|
|
return [];
|
|
} catch (e) {
|
|
throw Exception('Failed to load news: $e');
|
|
}
|
|
}
|
|
|
|
Future<NewsArticleModel> reanalyzeArticle(String articleId) async {
|
|
try {
|
|
final response = await apiClient.post('/api/v1/news/sentiment/article/$articleId/analyze');
|
|
if (response.statusCode == 200 && response.data != null) {
|
|
final Map<String, dynamic> data = response.data is Map ? Map<String, dynamic>.from(response.data) : {};
|
|
return NewsArticleModel.fromJson(data);
|
|
}
|
|
throw Exception('Unerwartete Server-Antwort');
|
|
} catch (e) {
|
|
throw Exception('Sentiment-Analyse fehlgeschlagen: $e');
|
|
}
|
|
}
|
|
|
|
/// No-op: the underlying `/hubs/news` WebSocket connection is owned and
|
|
/// lifecycle-managed centrally by [SignalRService] (started once in
|
|
/// `main.dart` after authentication), so this repository has nothing of
|
|
/// its own to dispose.
|
|
void dispose() {}
|
|
}
|