// lib/services/saha/saha_service.dart // // Small client for the panel's `/delphi/my-forms` endpoint. Used from the // home explore state to decide whether to render a Saha section (and to seed // it with one card per panel). The card tap opens the existing `/saha` // WebView page on the panel origin. import 'dart:async'; import 'dart:convert'; import 'dart:io' show SocketException; import 'package:flutter/foundation.dart' show debugPrint; // ignore: depend_on_referenced_packages import 'package:http/http.dart' as http; import 'package:didvan/config/auth_config.dart'; import 'package:didvan/services/auth/token_storage.dart'; class SahaPanel { final String id; final String title; final String? description; final String? type; const SahaPanel({ required this.id, required this.title, this.description, this.type, }); factory SahaPanel.fromJson(Map json) => SahaPanel( id: json['id'] as String, title: (json['title'] ?? '') as String, description: json['description'] as String?, type: json['type'] as String?, ); } class SahaService { SahaService._(); static final SahaService instance = SahaService._(); static const _timeout = Duration(seconds: 15); String get _base => AuthConfig.apiBaseUrl; Future> fetchMyPanels() async { final token = TokenStorage.accessToken; if (token == null || token.isEmpty) { debugPrint('🗳️ SAHA: no access token in TokenStorage'); return []; } try { final res = await http.get( Uri.parse('$_base/delphi/my-forms'), headers: { 'Accept': 'application/json', 'Authorization': 'Bearer $token', }, ).timeout(_timeout); if (res.statusCode != 200) { final body = res.body.length > 300 ? res.body.substring(0, 300) : res.body; debugPrint('🗳️ SAHA: my-forms HTTP ${res.statusCode}: $body'); return []; } final decoded = json.decode(res.body); if (decoded is List) { return decoded .whereType() .map((m) => SahaPanel.fromJson(Map.from(m))) .toList(); } debugPrint('🗳️ SAHA: my-forms returned non-list payload: ' '${res.body.length > 300 ? res.body.substring(0, 300) : res.body}'); return []; } on TimeoutException { debugPrint('🗳️ SAHA: my-forms timed out'); return []; } on SocketException catch (e) { debugPrint('🗳️ SAHA: my-forms network error: $e'); return []; } catch (e) { debugPrint('🗳️ SAHA: fetchMyPanels error: $e'); return []; } } /// Signs a magic-link invite token for the current Keycloak user on a /// specific panel and returns the ready-to-open respondent URL, or null /// on failure. The URL points at the panel origin so `Routes.web` can /// open it in the cookie-injecting WebView. Future buildMyPanelUrl(String panelId) async { final token = TokenStorage.accessToken; if (token == null || token.isEmpty) { debugPrint('🗳️ SAHA: my-link no access token'); return null; } try { final res = await http.post( Uri.parse('$_base/delphi/forms/$panelId/my-link'), headers: { 'Accept': 'application/json', 'Authorization': 'Bearer $token', }, ).timeout(_timeout); if (res.statusCode != 200 && res.statusCode != 201) { final body = res.body.length > 300 ? res.body.substring(0, 300) : res.body; debugPrint('🗳️ SAHA: my-link HTTP ${res.statusCode}: $body'); return null; } final decoded = json.decode(res.body); if (decoded is! Map || decoded['token'] is! String) { debugPrint('🗳️ SAHA: my-link bad payload: ${res.body}'); return null; } final t = Uri.encodeComponent(decoded['token'] as String); return 'https://app.didvan.com/panel/delphi/forms/user/$panelId?token=$t'; } on TimeoutException { debugPrint('🗳️ SAHA: my-link timed out'); return null; } on SocketException catch (e) { debugPrint('🗳️ SAHA: my-link network error: $e'); return null; } catch (e) { debugPrint('🗳️ SAHA: buildMyPanelUrl error: $e'); return null; } } }