165 lines
4.8 KiB
Dart
165 lines
4.8 KiB
Dart
import 'dart:convert';
|
|
import 'package:flutter/foundation.dart';
|
|
import 'package:http/http.dart' as http;
|
|
import 'package:shared_preferences/shared_preferences.dart';
|
|
|
|
/// inou API client for authentication and data access
|
|
class InouApi {
|
|
// Dev mode switch - set to false for production
|
|
static const bool devMode = true;
|
|
|
|
static const String _prodUrl = 'https://inou.com';
|
|
static const String _devUrl = 'https://dev.inou.com';
|
|
|
|
static String get baseUrl => devMode ? _devUrl : _prodUrl;
|
|
|
|
static const String _prefKeySessionToken = 'inou_session_token';
|
|
static const String _prefKeyDossierId = 'inou_dossier_id';
|
|
|
|
static final InouApi _instance = InouApi._internal();
|
|
factory InouApi() => _instance;
|
|
InouApi._internal();
|
|
|
|
String? _sessionToken;
|
|
String? _dossierId;
|
|
|
|
/// Current session token
|
|
String? get sessionToken => _sessionToken;
|
|
|
|
/// Current dossier ID
|
|
String? get dossierId => _dossierId;
|
|
|
|
/// Whether user is authenticated
|
|
bool get isAuthenticated => _sessionToken != null;
|
|
|
|
/// Initialize - load stored session
|
|
Future<void> initialize() async {
|
|
final prefs = await SharedPreferences.getInstance();
|
|
_sessionToken = prefs.getString(_prefKeySessionToken);
|
|
_dossierId = prefs.getString(_prefKeyDossierId);
|
|
}
|
|
|
|
/// Request login code to be sent to email
|
|
/// Returns error message or null on success
|
|
Future<String?> sendLoginCode(String email) async {
|
|
try {
|
|
final response = await http.post(
|
|
Uri.parse('$baseUrl/api/v1/auth/send'),
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
'Accept': 'application/json',
|
|
},
|
|
body: jsonEncode({
|
|
'email': email,
|
|
}),
|
|
);
|
|
|
|
debugPrint('send-code response: ${response.statusCode} ${response.body}');
|
|
|
|
// Check content type - must be JSON
|
|
final contentType = response.headers['content-type'] ?? '';
|
|
if (!contentType.contains('application/json')) {
|
|
return 'API not available (got HTML instead of JSON)';
|
|
}
|
|
|
|
final json = jsonDecode(response.body);
|
|
|
|
if (response.statusCode == 200 && json['success'] == true) {
|
|
return null; // Success
|
|
}
|
|
|
|
return json['error'] ?? 'Failed to send code';
|
|
} catch (e) {
|
|
debugPrint('send-code error: $e');
|
|
return 'Network error: ${e.toString()}';
|
|
}
|
|
}
|
|
|
|
/// Verify login code and get session
|
|
/// Returns error message or null on success
|
|
Future<String?> verifyCode(String email, String code) async {
|
|
try {
|
|
final response = await http.post(
|
|
Uri.parse('$baseUrl/api/v1/auth/verify'),
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
'Accept': 'application/json',
|
|
},
|
|
body: jsonEncode({
|
|
'email': email,
|
|
'code': code,
|
|
}),
|
|
);
|
|
|
|
debugPrint('verify response: ${response.statusCode} ${response.body}');
|
|
|
|
// Check content type - must be JSON
|
|
final contentType = response.headers['content-type'] ?? '';
|
|
if (!contentType.contains('application/json')) {
|
|
return 'API not available (got HTML instead of JSON)';
|
|
}
|
|
|
|
final json = jsonDecode(response.body);
|
|
|
|
if (response.statusCode == 200 && json['token'] != null) {
|
|
// Success - got session token
|
|
_sessionToken = json['token'];
|
|
_dossierId = json['dossier_id'];
|
|
await _saveSession();
|
|
return null; // Success
|
|
}
|
|
|
|
return json['error'] ?? 'Invalid or expired code';
|
|
} catch (e) {
|
|
debugPrint('verify error: $e');
|
|
return 'Network error: ${e.toString()}';
|
|
}
|
|
}
|
|
|
|
/// Logout - clear session
|
|
Future<void> logout() async {
|
|
_sessionToken = null;
|
|
_dossierId = null;
|
|
final prefs = await SharedPreferences.getInstance();
|
|
await prefs.remove(_prefKeySessionToken);
|
|
await prefs.remove(_prefKeyDossierId);
|
|
}
|
|
|
|
/// Make authenticated API request
|
|
Future<http.Response> get(String endpoint) async {
|
|
return http.get(
|
|
Uri.parse('$baseUrl/api/v1$endpoint'),
|
|
headers: _authHeaders(),
|
|
);
|
|
}
|
|
|
|
/// Make authenticated POST request
|
|
Future<http.Response> post(String endpoint, Map<String, dynamic> body) async {
|
|
return http.post(
|
|
Uri.parse('$baseUrl/api/v1$endpoint'),
|
|
headers: {
|
|
..._authHeaders(),
|
|
'Content-Type': 'application/json',
|
|
},
|
|
body: jsonEncode(body),
|
|
);
|
|
}
|
|
|
|
Map<String, String> _authHeaders() {
|
|
return {
|
|
if (_sessionToken != null) 'Authorization': 'Bearer $_sessionToken',
|
|
'Accept': 'application/json',
|
|
};
|
|
}
|
|
|
|
Future<void> _saveSession() async {
|
|
final prefs = await SharedPreferences.getInstance();
|
|
if (_sessionToken != null) {
|
|
await prefs.setString(_prefKeySessionToken, _sessionToken!);
|
|
}
|
|
if (_dossierId != null) {
|
|
await prefs.setString(_prefKeyDossierId, _dossierId!);
|
|
}
|
|
}
|
|
}
|