164 lines
5.7 KiB
Dart
164 lines
5.7 KiB
Dart
import 'dart:convert';
|
|
import 'package:http/http.dart' as http;
|
|
import 'package:get/get.dart';
|
|
import 'package:marco/controller/project_controller.dart';
|
|
|
|
import 'package:marco/helpers/services/api_endpoints.dart';
|
|
import 'package:marco/helpers/services/storage/local_storage.dart';
|
|
import 'package:marco/helpers/services/app_logger.dart';
|
|
import 'package:marco/helpers/services/auth_service.dart';
|
|
import 'package:marco/model/tenant/tenant_list_model.dart';
|
|
|
|
/// Abstract interface for tenant service functionality
|
|
abstract class ITenantService {
|
|
Future<List<Map<String, dynamic>>?> getTenants({bool hasRetried = false});
|
|
Future<bool> selectTenant(String tenantId, {bool hasRetried = false});
|
|
}
|
|
|
|
/// Tenant API service
|
|
class TenantService implements ITenantService {
|
|
static const String _baseUrl = ApiEndpoints.baseUrl;
|
|
static const Map<String, String> _headers = {
|
|
'Content-Type': 'application/json',
|
|
};
|
|
|
|
/// Currently selected tenant
|
|
static Tenant? currentTenant;
|
|
|
|
/// Set the selected tenant
|
|
static void setSelectedTenant(Tenant tenant) {
|
|
currentTenant = tenant;
|
|
}
|
|
|
|
/// Check if tenant is selected
|
|
static bool get isTenantSelected => currentTenant != null;
|
|
|
|
/// Build authorized headers
|
|
static Future<Map<String, String>> _authorizedHeaders() async {
|
|
final token = await LocalStorage.getJwtToken();
|
|
if (token == null || token.isEmpty) {
|
|
throw Exception('Missing JWT token');
|
|
}
|
|
return {..._headers, 'Authorization': 'Bearer $token'};
|
|
}
|
|
|
|
/// Handle API errors
|
|
static void _handleApiError(
|
|
http.Response response, dynamic data, String context) {
|
|
final message = data['message'] ?? 'Unknown error';
|
|
final level =
|
|
response.statusCode >= 500 ? LogLevel.error : LogLevel.warning;
|
|
logSafe("❌ $context failed: $message [Status: ${response.statusCode}]",
|
|
level: level);
|
|
}
|
|
|
|
/// Log exceptions
|
|
static void _logException(dynamic e, dynamic st, String context) {
|
|
logSafe("❌ $context exception",
|
|
level: LogLevel.error, error: e, stackTrace: st);
|
|
}
|
|
|
|
@override
|
|
Future<List<Map<String, dynamic>>?> getTenants(
|
|
{bool hasRetried = false}) async {
|
|
try {
|
|
final headers = await _authorizedHeaders();
|
|
logSafe("➡️ GET $_baseUrl/auth/get/user/tenants\nHeaders: $headers",
|
|
level: LogLevel.info);
|
|
|
|
final response = await http
|
|
.get(Uri.parse("$_baseUrl/auth/get/user/tenants"), headers: headers);
|
|
final data = jsonDecode(response.body);
|
|
|
|
logSafe(
|
|
"⬅️ Response: ${jsonEncode(data)} [Status: ${response.statusCode}]",
|
|
level: LogLevel.info);
|
|
|
|
if (response.statusCode == 200 && data['success'] == true) {
|
|
logSafe("✅ Tenants fetched successfully.");
|
|
return List<Map<String, dynamic>>.from(data['data']);
|
|
}
|
|
|
|
if (response.statusCode == 401 && !hasRetried) {
|
|
logSafe("⚠️ Unauthorized while fetching tenants. Refreshing token...",
|
|
level: LogLevel.warning);
|
|
final refreshed = await AuthService.refreshToken();
|
|
if (refreshed) return getTenants(hasRetried: true);
|
|
logSafe("❌ Token refresh failed while fetching tenants.",
|
|
level: LogLevel.error);
|
|
return null;
|
|
}
|
|
|
|
_handleApiError(response, data, "Fetching tenants");
|
|
return null;
|
|
} catch (e, st) {
|
|
_logException(e, st, "Get Tenants API");
|
|
return null;
|
|
}
|
|
}
|
|
|
|
@override
|
|
Future<bool> selectTenant(String tenantId, {bool hasRetried = false}) async {
|
|
try {
|
|
final headers = await _authorizedHeaders();
|
|
logSafe(
|
|
"➡️ POST $_baseUrl/auth/select-tenant/$tenantId\nHeaders: $headers",
|
|
level: LogLevel.info);
|
|
|
|
final response = await http.post(
|
|
Uri.parse("$_baseUrl/auth/select-tenant/$tenantId"),
|
|
headers: headers,
|
|
);
|
|
final data = jsonDecode(response.body);
|
|
|
|
logSafe(
|
|
"⬅️ Response: ${jsonEncode(data)} [Status: ${response.statusCode}]",
|
|
level: LogLevel.info);
|
|
|
|
if (response.statusCode == 200 && data['success'] == true) {
|
|
await LocalStorage.setJwtToken(data['data']['token']);
|
|
await LocalStorage.setRefreshToken(data['data']['refreshToken']);
|
|
logSafe("✅ Tenant selected successfully. Tokens updated.");
|
|
|
|
// 🔥 Refresh projects when tenant changes
|
|
try {
|
|
final projectController = Get.find<ProjectController>();
|
|
projectController.clearProjects();
|
|
projectController.fetchProjects();
|
|
} catch (_) {
|
|
logSafe("⚠️ ProjectController not found while refreshing projects");
|
|
}
|
|
|
|
// 🔹 Register FCM token after tenant selection
|
|
final fcmToken = LocalStorage.getFcmToken();
|
|
if (fcmToken?.isNotEmpty ?? false) {
|
|
final success = await AuthService.registerDeviceToken(fcmToken!);
|
|
logSafe(
|
|
success
|
|
? "✅ FCM token registered after tenant selection."
|
|
: "⚠️ Failed to register FCM token after tenant selection.",
|
|
level: success ? LogLevel.info : LogLevel.warning);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
if (response.statusCode == 401 && !hasRetried) {
|
|
logSafe("⚠️ Unauthorized while selecting tenant. Refreshing token...",
|
|
level: LogLevel.warning);
|
|
final refreshed = await AuthService.refreshToken();
|
|
if (refreshed) return selectTenant(tenantId, hasRetried: true);
|
|
logSafe("❌ Token refresh failed while selecting tenant.",
|
|
level: LogLevel.error);
|
|
return false;
|
|
}
|
|
|
|
_handleApiError(response, data, "Selecting tenant");
|
|
return false;
|
|
} catch (e, st) {
|
|
_logException(e, st, "Select Tenant API");
|
|
return false;
|
|
}
|
|
}
|
|
}
|