321 lines
11 KiB
Dart
321 lines
11 KiB
Dart
import 'dart:convert';
|
|
import 'package:get/get.dart';
|
|
import 'package:http/http.dart' as http;
|
|
|
|
import 'package:marco/controller/permission_controller.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';
|
|
|
|
class AuthService {
|
|
static const String _baseUrl = ApiEndpoints.baseUrl;
|
|
static const Map<String, String> _headers = {
|
|
'Content-Type': 'application/json',
|
|
};
|
|
|
|
static bool isLoggedIn = false;
|
|
/* -------------------------------------------------------------------------- */
|
|
/* Logout API */
|
|
/* -------------------------------------------------------------------------- */
|
|
static Future<bool> logoutApi(String refreshToken, String fcmToken) async {
|
|
try {
|
|
final body = {
|
|
"refreshToken": refreshToken,
|
|
"fcmToken": fcmToken,
|
|
};
|
|
|
|
final response = await _post("/auth/logout", body);
|
|
|
|
if (response != null && response['statusCode'] == 200) {
|
|
logSafe("✅ Logout API successful");
|
|
return true;
|
|
}
|
|
|
|
logSafe("⚠️ Logout API failed: ${response?['message']}",
|
|
level: LogLevel.warning);
|
|
return false;
|
|
} catch (e, st) {
|
|
_handleError("Logout API error", e, st);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/* -------------------------------------------------------------------------- */
|
|
/* Public Methods */
|
|
/* -------------------------------------------------------------------------- */
|
|
|
|
static Future<bool> registerDeviceToken(String fcmToken) async {
|
|
final token = await LocalStorage.getJwtToken();
|
|
if (token == null || token.isEmpty) {
|
|
logSafe("❌ Cannot register device token: missing JWT token",
|
|
level: LogLevel.warning);
|
|
return false;
|
|
}
|
|
|
|
final body = {"fcmToken": fcmToken};
|
|
final headers = {
|
|
..._headers,
|
|
'Authorization': 'Bearer $token',
|
|
};
|
|
final endpoint = "$_baseUrl/auth/set/device-token";
|
|
|
|
// 🔹 Log request details
|
|
logSafe("📡 Device Token API Request");
|
|
logSafe("➡️ Endpoint: $endpoint");
|
|
logSafe("➡️ Headers: ${jsonEncode(headers)}");
|
|
logSafe("➡️ Payload: ${jsonEncode(body)}");
|
|
|
|
final data = await _post("/auth/set/device-token", body, authToken: token);
|
|
|
|
if (data != null && data['success'] == true) {
|
|
logSafe("✅ Device token registered successfully.");
|
|
return true;
|
|
}
|
|
logSafe("⚠️ Failed to register device token: ${data?['message']}",
|
|
level: LogLevel.warning);
|
|
return false;
|
|
}
|
|
|
|
static Future<Map<String, String>?> loginUser(
|
|
Map<String, dynamic> data) async {
|
|
logSafe("Attempting login...");
|
|
logSafe("Login payload (raw): $data");
|
|
logSafe("Login payload (JSON): ${jsonEncode(data)}");
|
|
|
|
final responseData = await _post("/auth/login-mobile", data);
|
|
if (responseData == null)
|
|
return {"error": "Network error. Please check your connection."};
|
|
|
|
if (responseData['data'] != null) {
|
|
await _handleLoginSuccess(responseData['data']);
|
|
return null;
|
|
}
|
|
if (responseData['statusCode'] == 401) {
|
|
return {"password": "Invalid email or password"};
|
|
}
|
|
return {"error": responseData['message'] ?? "Unexpected error occurred"};
|
|
}
|
|
|
|
static Future<bool> refreshToken() async {
|
|
final accessToken = await LocalStorage.getJwtToken();
|
|
final refreshToken = await LocalStorage.getRefreshToken();
|
|
|
|
if ([accessToken, refreshToken].any((t) => t == null || t.isEmpty)) {
|
|
logSafe("Missing access or refresh token.", level: LogLevel.warning);
|
|
return false;
|
|
}
|
|
|
|
final body = {"token": accessToken, "refreshToken": refreshToken};
|
|
final data = await _post("/auth/refresh-token", body);
|
|
if (data != null && data['success'] == true) {
|
|
await LocalStorage.setJwtToken(data['data']['token']);
|
|
await LocalStorage.setRefreshToken(data['data']['refreshToken']);
|
|
await LocalStorage.setLoggedInUser(true);
|
|
logSafe("Token refreshed successfully.");
|
|
|
|
// 🔹 Retry FCM token registration after token refresh
|
|
final newFcmToken = await LocalStorage.getFcmToken();
|
|
if (newFcmToken?.isNotEmpty ?? false) {
|
|
final success = await registerDeviceToken(newFcmToken!);
|
|
logSafe(
|
|
success
|
|
? "✅ FCM token re-registered after JWT refresh."
|
|
: "⚠️ Failed to register FCM token after JWT refresh.",
|
|
level: success ? LogLevel.info : LogLevel.warning);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
logSafe("Refresh token failed: ${data?['message']}",
|
|
level: LogLevel.warning);
|
|
return false;
|
|
}
|
|
|
|
static Future<Map<String, String>?> forgotPassword(String email) =>
|
|
_wrapErrorHandling(() => _post("/auth/forgot-password", {"email": email}),
|
|
successCondition: (data) => data['success'] == true,
|
|
defaultError: "Failed to send reset link.");
|
|
|
|
static Future<Map<String, String>?> requestDemo(
|
|
Map<String, dynamic> demoData) =>
|
|
_wrapErrorHandling(() => _post("/market/inquiry", demoData),
|
|
successCondition: (data) => data['success'] == true,
|
|
defaultError: "Failed to submit demo request.");
|
|
|
|
static Future<List<Map<String, dynamic>>?> getIndustries() async {
|
|
final data = await _get("/market/industries");
|
|
if (data != null && data['success'] == true) {
|
|
return List<Map<String, dynamic>>.from(data['data']);
|
|
}
|
|
return null;
|
|
}
|
|
|
|
static Future<Map<String, String>?> generateMpin({
|
|
required String employeeId,
|
|
required String mpin,
|
|
}) =>
|
|
_wrapErrorHandling(
|
|
() async {
|
|
final token = await LocalStorage.getJwtToken();
|
|
return _post(
|
|
"/auth/generate-mpin",
|
|
{"employeeId": employeeId, "mpin": mpin},
|
|
authToken: token,
|
|
);
|
|
},
|
|
successCondition: (data) => data['success'] == true,
|
|
defaultError: "Failed to generate MPIN.",
|
|
);
|
|
|
|
static Future<Map<String, String>?> verifyMpin({
|
|
required String mpin,
|
|
required String mpinToken,
|
|
required String fcmToken,
|
|
}) =>
|
|
_wrapErrorHandling(
|
|
() async {
|
|
final employeeInfo = LocalStorage.getEmployeeInfo();
|
|
if (employeeInfo == null) return null;
|
|
final token = await LocalStorage.getJwtToken();
|
|
return _post(
|
|
"/auth/login-mpin/v1",
|
|
{
|
|
"employeeId": employeeInfo.id,
|
|
"mpin": mpin,
|
|
"mpinToken": mpinToken,
|
|
"fcmToken": fcmToken,
|
|
},
|
|
authToken: token,
|
|
);
|
|
},
|
|
successCondition: (data) => data['success'] == true,
|
|
defaultError: "MPIN verification failed.",
|
|
);
|
|
|
|
static Future<Map<String, String>?> generateOtp(String email) =>
|
|
_wrapErrorHandling(() => _post("/auth/send-otp", {"email": email}),
|
|
successCondition: (data) => data['success'] == true,
|
|
defaultError: "Failed to generate OTP.");
|
|
|
|
static Future<Map<String, String>?> verifyOtp({
|
|
required String email,
|
|
required String otp,
|
|
}) async {
|
|
final data = await _post("/auth/login-otp/v1", {"email": email, "otp": otp});
|
|
if (data != null && data['data'] != null) {
|
|
await _handleLoginSuccess(data['data']);
|
|
return null;
|
|
}
|
|
return {"error": data?['message'] ?? "OTP verification failed."};
|
|
}
|
|
|
|
/* -------------------------------------------------------------------------- */
|
|
/* Private Utilities */
|
|
/* -------------------------------------------------------------------------- */
|
|
|
|
static Future<Map<String, dynamic>?> _post(
|
|
String path,
|
|
Map<String, dynamic> body, {
|
|
String? authToken,
|
|
}) async {
|
|
try {
|
|
final headers = {
|
|
..._headers,
|
|
if (authToken?.isNotEmpty ?? false)
|
|
'Authorization': 'Bearer $authToken',
|
|
};
|
|
final response = await http.post(Uri.parse("$_baseUrl$path"),
|
|
headers: headers, body: jsonEncode(body));
|
|
return {
|
|
...jsonDecode(response.body),
|
|
"statusCode": response.statusCode,
|
|
};
|
|
} catch (e, st) {
|
|
_handleError("$path POST error", e, st);
|
|
return null;
|
|
}
|
|
}
|
|
|
|
static Future<Map<String, dynamic>?> _get(
|
|
String path, {
|
|
String? authToken,
|
|
}) async {
|
|
try {
|
|
final headers = {
|
|
..._headers,
|
|
if (authToken?.isNotEmpty ?? false)
|
|
'Authorization': 'Bearer $authToken',
|
|
};
|
|
final response =
|
|
await http.get(Uri.parse("$_baseUrl$path"), headers: headers);
|
|
return {
|
|
...jsonDecode(response.body),
|
|
"statusCode": response.statusCode,
|
|
};
|
|
} catch (e, st) {
|
|
_handleError("$path GET error", e, st);
|
|
return null;
|
|
}
|
|
}
|
|
|
|
static Future<Map<String, String>?> _wrapErrorHandling(
|
|
Future<Map<String, dynamic>?> Function() request, {
|
|
required bool Function(Map<String, dynamic> data) successCondition,
|
|
required String defaultError,
|
|
}) async {
|
|
final data = await request();
|
|
if (data != null && successCondition(data)) return null;
|
|
return {"error": data?['message'] ?? defaultError};
|
|
}
|
|
|
|
static void _handleError(String message, Object error, StackTrace st) {
|
|
logSafe(message, level: LogLevel.error, error: error, stackTrace: st);
|
|
}
|
|
|
|
static Future<void> _handleLoginSuccess(Map<String, dynamic> data) async {
|
|
logSafe("Processing login success...");
|
|
|
|
await LocalStorage.setJwtToken(data['token']);
|
|
await LocalStorage.setLoggedInUser(true);
|
|
|
|
if (data['refreshToken'] != null) {
|
|
await LocalStorage.setRefreshToken(data['refreshToken']);
|
|
}
|
|
|
|
if (data['mpinToken']?.isNotEmpty ?? false) {
|
|
await LocalStorage.setMpinToken(data['mpinToken']);
|
|
await LocalStorage.setIsMpin(true);
|
|
} else {
|
|
await LocalStorage.setIsMpin(false);
|
|
await LocalStorage.removeMpinToken();
|
|
}
|
|
|
|
if (!Get.isRegistered<PermissionController>()) {
|
|
Get.put(PermissionController());
|
|
logSafe("✅ PermissionController injected after login.");
|
|
}
|
|
if (!Get.isRegistered<ProjectController>()) {
|
|
Get.put(ProjectController(), permanent: true);
|
|
logSafe("✅ ProjectController injected after login.");
|
|
}
|
|
|
|
await Get.find<PermissionController>().loadData(data['token']);
|
|
await Get.find<ProjectController>().fetchProjects();
|
|
|
|
// 🔹 Always try to register FCM token after login
|
|
final fcmToken = await LocalStorage.getFcmToken();
|
|
if (fcmToken?.isNotEmpty ?? false) {
|
|
final success = await registerDeviceToken(fcmToken!);
|
|
logSafe(
|
|
success
|
|
? "✅ FCM token registered after login."
|
|
: "⚠️ Failed to register FCM token after login.",
|
|
level: success ? LogLevel.info : LogLevel.warning);
|
|
}
|
|
|
|
isLoggedIn = true;
|
|
logSafe("✅ Login flow completed and controllers initialized.");
|
|
}
|
|
}
|