Files

151 lines
6.5 KiB
Dart

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;
const AdminRepository({required this.apiClient});
Future<List<AdminUserModel>> fetchUsers() async {
try {
final res = await apiClient.get('/api/v1/admin/users');
if (res.statusCode == 200 && res.data != null) {
final List<dynamic> data = res.data;
return data.map((json) => AdminUserModel.fromJson(json)).toList();
}
return [];
} catch (e) {
throw Exception('Nutzer konnten nicht geladen werden: $e');
}
}
Future<void> createUser(AdminCreateUserRequestDto dto) async {
final res = await apiClient.post('/api/v1/admin/users', data: dto.toJson());
if (res.statusCode != 200 && res.statusCode != 201) {
throw Exception('Erstellen fehlgeschlagen');
}
}
Future<void> updateUser(String id, AdminUpdateUserRequestDto dto) async {
final res = await apiClient.put('/api/v1/admin/users/$id', data: dto.toJson());
if (res.statusCode != 200) {
throw Exception('Aktualisieren fehlgeschlagen');
}
}
Future<Map<String, List<ServiceSettingDto>>> fetchSettings() async {
final res = await apiClient.get('/api/v1/admin/settings');
if (res.statusCode == 200 && res.data != null && res.data is Map<String, dynamic>) {
final map = res.data as Map<String, dynamic>;
final result = <String, List<ServiceSettingDto>>{};
map.forEach((k, v) {
if (v is List) {
result[k] = v
.whereType<Map<String, dynamic>>()
.map((item) => ServiceSettingDto.fromJson(item))
.toList();
}
});
return result;
}
return {};
}
Future<void> updateServiceSettings(String serviceName, Map<String, dynamic> settings) async {
final res = await apiClient.put('/api/v1/admin/settings/$serviceName', data: settings);
if (res.statusCode != 200 && res.statusCode != 204) {
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.');
}
}
}