inou-mobile/lib/services/inou_api.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!);
}
}
}