import 'dart:convert'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_dotenv/flutter_dotenv.dart'; import 'package:userfront/core/services/auth_proxy_service.dart'; import 'package:userfront/core/services/auth_token_store.dart'; import 'package:userfront/core/services/http_client.dart'; class LinkedRp { final String id; final String name; final String logo; final String url; final String status; final List scopes; final DateTime? lastAuthenticatedAt; LinkedRp({ required this.id, required this.name, required this.logo, required this.url, required this.status, required this.scopes, required this.lastAuthenticatedAt, }); factory LinkedRp.fromJson(Map json) { final rawLastAuth = json['lastAuthenticatedAt']?.toString() ?? ''; DateTime? parsedLastAuth; if (rawLastAuth.isNotEmpty) { try { parsedLastAuth = DateTime.parse(rawLastAuth).toLocal(); } catch (_) { parsedLastAuth = null; } } return LinkedRp( id: json['id']?.toString() ?? '', name: json['name']?.toString() ?? '', logo: json['logo']?.toString() ?? '', url: json['url']?.toString() ?? '', status: json['status']?.toString() ?? 'unknown', scopes: (json['scopes'] as List?)?.whereType().toList() ?? [], lastAuthenticatedAt: parsedLastAuth, ); } } class LinkedRpsNotifier extends AsyncNotifier> { @override Future> build() async { return _fetchLinkedRps(); } String _envOrDefault(String key, String fallback) { if (!dotenv.isInitialized) { return fallback; } return dotenv.env[key] ?? fallback; } Future> _fetchLinkedRps() async { try { final baseUrl = _envOrDefault('BACKEND_URL', 'https://sso.hmac.kr'); final url = Uri.parse('$baseUrl/api/v1/user/rp/linked'); final useCookie = AuthTokenStore.usesCookie(); final token = AuthTokenStore.getToken(); final client = createHttpClient(withCredentials: useCookie); final headers = {'Content-Type': 'application/json'}; if (!useCookie && token != null) { headers['Authorization'] = 'Bearer $token'; } final response = await client.get(url, headers: headers); client.close(); if (response.statusCode != 200) { throw Exception('Failed to load linked rps: ${response.statusCode}'); } final body = jsonDecode(response.body) as Map; final items = (body['items'] as List?) ?? []; return items .whereType>() .map(LinkedRp.fromJson) .toList(); } catch (e) { rethrow; } } Future refresh() async { state = const AsyncLoading(); state = await AsyncValue.guard(() => _fetchLinkedRps()); } Future revokeRp(String clientId) async { await AuthProxyService.revokeLinkedRp(clientId); await refresh(); } } final linkedRpsProvider = AsyncNotifierProvider>(() { return LinkedRpsNotifier(); });