feat(app): add evaluation history, bot control panel, simulation visualizer, and vendored signalr_core
This commit is contained in:
@@ -1,8 +1,13 @@
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:finlytic_app/core/network/api_client.dart';
|
||||
import 'package:finlytic_app/features/admin/models/admin_user_model.dart';
|
||||
import 'package:finlytic_app/features/admin/models/admin_create_user_request_dto.dart';
|
||||
import 'package:finlytic_app/features/admin/models/admin_update_user_request_dto.dart';
|
||||
import 'package:finlytic_app/features/admin/models/evaluation_history_enums.dart';
|
||||
import 'package:finlytic_app/features/admin/models/evaluation_history_response_model.dart';
|
||||
import 'package:finlytic_app/features/admin/models/recent_setup_model.dart';
|
||||
import 'package:finlytic_app/features/admin/models/service_setting_dto.dart';
|
||||
import 'package:finlytic_app/features/admin/models/watchlist_entry_model.dart';
|
||||
|
||||
class AdminRepository {
|
||||
final ApiClient apiClient;
|
||||
@@ -60,4 +65,86 @@ class AdminRepository {
|
||||
throw Exception('Einstellungen konnten nicht gespeichert werden');
|
||||
}
|
||||
}
|
||||
|
||||
/// Fetches a filtered, paginated page of the evaluation history plus its
|
||||
/// accompanying summary from `GET /api/v1/admin/evaluations`
|
||||
/// (`AdminEvaluationHistoryController`). All filter parameters are optional —
|
||||
/// omitting one means "do not filter on this field", mirroring the server
|
||||
/// contract exactly (`GetEvaluationHistoryRequest`).
|
||||
///
|
||||
/// `[Authorize(Roles = "Admin")]` on the server means a non-admin caller (or an
|
||||
/// expired/invalid token) gets a `401`/`403`, which `ApiClient`'s interceptor
|
||||
/// already turns into an auto-logout (Rules.md §8) before this method's
|
||||
/// `catch` even runs — this method only has to turn the remaining
|
||||
/// error responses (engine unreachable `503`, RPC timeout `502`, unexpected
|
||||
/// `500` — all `ProblemDetails` bodies per the controller) into a readable
|
||||
/// message instead of letting a raw `DioException` reach the UI.
|
||||
Future<EvaluationHistoryResponseModel> fetchEvaluationHistory({
|
||||
DateTime? fromUtc,
|
||||
DateTime? toUtc,
|
||||
OutcomeReason? outcome,
|
||||
TriggerSource? triggerSource,
|
||||
String? search,
|
||||
int page = 1,
|
||||
int pageSize = 50,
|
||||
}) async {
|
||||
final query = <String, dynamic>{
|
||||
'page': page,
|
||||
'pageSize': pageSize,
|
||||
};
|
||||
if (fromUtc != null) query['fromUtc'] = fromUtc.toUtc().toIso8601String();
|
||||
if (toUtc != null) query['toUtc'] = toUtc.toUtc().toIso8601String();
|
||||
if (outcome != null) query['outcome'] = outcome.toApiValue();
|
||||
if (triggerSource != null) query['triggerSource'] = triggerSource.toApiValue();
|
||||
if (search != null && search.trim().isNotEmpty) query['search'] = search.trim();
|
||||
|
||||
try {
|
||||
final res = await apiClient.get('/api/v1/admin/evaluations', queryParameters: query);
|
||||
if (res.data is Map<String, dynamic>) {
|
||||
return EvaluationHistoryResponseModel.fromJson(res.data as Map<String, dynamic>);
|
||||
}
|
||||
return EvaluationHistoryResponseModel.empty();
|
||||
} on DioException catch (e) {
|
||||
final data = e.response?.data;
|
||||
final title = (data is Map && data['title'] is String) ? data['title'] as String : null;
|
||||
throw Exception(title ?? 'Evaluierungs-Historie konnte nicht geladen werden.');
|
||||
}
|
||||
}
|
||||
|
||||
/// Fetches FinlyticTechnicals' current scan universe ("watchlist") from
|
||||
/// `GET /api/v1/admin/evaluations/watchlist` — the assets actually being
|
||||
/// evaluated every cycle in the background, independent of the (filtered)
|
||||
/// evaluation history above.
|
||||
Future<List<WatchlistEntryModel>> fetchWatchlist() async {
|
||||
try {
|
||||
final res = await apiClient.get('/api/v1/admin/evaluations/watchlist');
|
||||
if (res.data is List) {
|
||||
return (res.data as List).whereType<Map<String, dynamic>>().map(WatchlistEntryModel.fromJson).toList();
|
||||
}
|
||||
return [];
|
||||
} on DioException catch (e) {
|
||||
final data = e.response?.data;
|
||||
final title = (data is Map && data['title'] is String) ? data['title'] as String : null;
|
||||
throw Exception(title ?? 'Watchlist konnte nicht geladen werden.');
|
||||
}
|
||||
}
|
||||
|
||||
/// Fetches the last [limit] technical-analysis setups computed for [isin]
|
||||
/// (most recent first) from `GET /api/v1/admin/evaluations/watchlist/{isin}/history`.
|
||||
Future<List<RecentSetupModel>> fetchWatchlistEntryHistory(String isin, {int limit = 8}) async {
|
||||
try {
|
||||
final res = await apiClient.get(
|
||||
'/api/v1/admin/evaluations/watchlist/${Uri.encodeComponent(isin)}/history',
|
||||
queryParameters: {'limit': limit},
|
||||
);
|
||||
if (res.data is List) {
|
||||
return (res.data as List).whereType<Map<String, dynamic>>().map(RecentSetupModel.fromJson).toList();
|
||||
}
|
||||
return [];
|
||||
} on DioException catch (e) {
|
||||
final data = e.response?.data;
|
||||
final title = (data is Map && data['title'] is String) ? data['title'] as String : null;
|
||||
throw Exception(title ?? 'Score-Verlauf konnte nicht geladen werden.');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user