diff --git a/android/app/build.gradle b/android/app/build.gradle index ef80cbe..86a56ac 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -21,7 +21,7 @@ android { defaultConfig { // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). - applicationId = "com.example.marco" + applicationId = "com.example.marcostage" // You can update the following values to match your application needs. // For more information, see: https://flutter.dev/to/review-gradle-config. minSdk = flutter.minSdkVersion diff --git a/lib/helpers/services/api_service.dart b/lib/helpers/services/api_service.dart index 22c3110..0b51c3a 100644 --- a/lib/helpers/services/api_service.dart +++ b/lib/helpers/services/api_service.dart @@ -1,12 +1,12 @@ import 'dart:convert'; +import 'package:get/get.dart'; import 'package:http/http.dart' as http; import 'package:image_picker/image_picker.dart'; import 'package:intl/intl.dart'; import 'package:logger/logger.dart'; -import 'package:marco/helpers/services/storage/local_storage.dart'; import 'package:marco/helpers/services/auth_service.dart'; import 'package:marco/helpers/services/api_endpoints.dart'; -import 'package:get/get.dart'; +import 'package:marco/helpers/services/storage/local_storage.dart'; final Logger logger = Logger(); @@ -14,13 +14,11 @@ class ApiService { static const Duration timeout = Duration(seconds: 10); static const bool enableLogs = true; - // ===== Helpers ===== + // === Helpers === static Future _getToken() async { final token = await LocalStorage.getJwtToken(); - if (token == null && enableLogs) { - logger.w("No JWT token found. Please log in."); - } + if (token == null && enableLogs) logger.w("No JWT token found."); return token; } @@ -47,13 +45,12 @@ class ApiService { return null; } - static dynamic _parseResponseForAllData(http.Response response, - {String label = ''}) { + static dynamic _parseResponseForAllData(http.Response response, {String label = ''}) { _log("$label Response: ${response.body}"); try { final json = jsonDecode(response.body); if (response.statusCode == 200 && json['success'] == true) { - return json; // 👈 Return full response, not just json['data'] + return json; } _log("API Error [$label]: ${json['message'] ?? 'Unknown error'}"); } catch (e) { @@ -62,28 +59,23 @@ class ApiService { return null; } - static Future _getRequest(String endpoint, - {Map? queryParams, bool hasRetried = false}) async { + static Future _getRequest( + String endpoint, { + Map? queryParams, + bool hasRetried = false, + }) async { String? token = await _getToken(); if (token == null) return null; - Uri uri = Uri.parse("${ApiEndpoints.baseUrl}$endpoint") - .replace(queryParameters: queryParams); + final uri = Uri.parse("${ApiEndpoints.baseUrl}$endpoint").replace(queryParameters: queryParams); _log("GET $uri"); try { - http.Response response = - await http.get(uri, headers: _headers(token)).timeout(timeout); - + final response = await http.get(uri, headers: _headers(token)).timeout(timeout); if (response.statusCode == 401 && !hasRetried) { _log("Unauthorized. Attempting token refresh..."); - bool refreshed = await AuthService.refreshToken(); - if (refreshed) { - token = await _getToken(); - if (token != null) { - return await _getRequest(endpoint, - queryParams: queryParams, hasRetried: true); - } + if (await AuthService.refreshToken()) { + return await _getRequest(endpoint, queryParams: queryParams, hasRetried: true); } _log("Token refresh failed."); } @@ -98,22 +90,25 @@ class ApiService { String endpoint, dynamic body, { Duration customTimeout = timeout, + bool hasRetried = false, }) async { String? token = await _getToken(); if (token == null) return null; final uri = Uri.parse("${ApiEndpoints.baseUrl}$endpoint"); - - _log("POST $uri"); - _log("Headers: ${_headers(token)}"); - _log("Body: $body"); + _log("POST $uri\nHeaders: ${_headers(token)}\nBody: $body"); try { final response = await http .post(uri, headers: _headers(token), body: jsonEncode(body)) .timeout(customTimeout); - _log("Response Status: ${response.statusCode}"); + if (response.statusCode == 401 && !hasRetried) { + _log("Unauthorized POST. Attempting token refresh..."); + if (await AuthService.refreshToken()) { + return await _postRequest(endpoint, body, customTimeout: customTimeout, hasRetried: true); + } + } return response; } catch (e) { _log("HTTP POST Exception: $e"); @@ -121,61 +116,39 @@ class ApiService { } } - // ===== Attendence Screen API Calls ===== + // === Attendance APIs === - static Future?> getProjects() async { - final response = await _getRequest(ApiEndpoints.getProjects); - return response != null - ? _parseResponse(response, label: 'Projects') - : null; - } - static Future?> getGlobalProjects() async { - final response = await _getRequest(ApiEndpoints.getProjects); - return response != null - ? _parseResponse(response, label: 'Global Projects') - : null; - } - static Future?> getEmployeesByProject(String projectId) async { - final response = await _getRequest(ApiEndpoints.getEmployeesByProject, - queryParams: {"projectId": projectId}); - return response != null - ? _parseResponse(response, label: 'Employees') - : null; - } + static Future?> getProjects() async => + _getRequest(ApiEndpoints.getProjects).then((res) => res != null ? _parseResponse(res, label: 'Projects') : null); - static Future?> getAttendanceLogs(String projectId, - {DateTime? dateFrom, DateTime? dateTo}) async { + static Future?> getGlobalProjects() async => + _getRequest(ApiEndpoints.getProjects).then((res) => res != null ? _parseResponse(res, label: 'Global Projects') : null); + + static Future?> getEmployeesByProject(String projectId) async => + _getRequest(ApiEndpoints.getEmployeesByProject, queryParams: {"projectId": projectId}) + .then((res) => res != null ? _parseResponse(res, label: 'Employees') : null); + + static Future?> getAttendanceLogs( + String projectId, { + DateTime? dateFrom, + DateTime? dateTo, + }) async { final query = { "projectId": projectId, - if (dateFrom != null) - "dateFrom": DateFormat('yyyy-MM-dd').format(dateFrom), + if (dateFrom != null) "dateFrom": DateFormat('yyyy-MM-dd').format(dateFrom), if (dateTo != null) "dateTo": DateFormat('yyyy-MM-dd').format(dateTo), }; - - final response = - await _getRequest(ApiEndpoints.getAttendanceLogs, queryParams: query); - return response != null - ? _parseResponse(response, label: 'Attendance Logs') - : null; + return _getRequest(ApiEndpoints.getAttendanceLogs, queryParams: query) + .then((res) => res != null ? _parseResponse(res, label: 'Attendance Logs') : null); } - static Future?> getAttendanceLogView(String id) async { - final response = - await _getRequest("${ApiEndpoints.getAttendanceLogView}/$id"); - return response != null - ? _parseResponse(response, label: 'Log Details') - : null; - } + static Future?> getAttendanceLogView(String id) async => + _getRequest("${ApiEndpoints.getAttendanceLogView}/$id") + .then((res) => res != null ? _parseResponse(res, label: 'Log Details') : null); - static Future?> getRegularizationLogs(String projectId) async { - final response = await _getRequest(ApiEndpoints.getRegularizationLogs, - queryParams: {"projectId": projectId}); - return response != null - ? _parseResponse(response, label: 'Regularization Logs') - : null; - } - - // ===== Upload Attendance Image ===== + static Future?> getRegularizationLogs(String projectId) async => + _getRequest(ApiEndpoints.getRegularizationLogs, queryParams: {"projectId": projectId}) + .then((res) => res != null ? _parseResponse(res, label: 'Regularization Logs') : null); static Future uploadAttendanceImage( String id, @@ -188,7 +161,7 @@ class ApiService { String comment = "", required int action, bool imageCapture = true, - String? markTime, // <-- Optional markTime parameter + String? markTime, }) async { final now = DateTime.now(); final body = { @@ -206,16 +179,14 @@ class ApiService { if (imageCapture && imageFile != null) { try { final bytes = await imageFile.readAsBytes(); - final base64Image = base64Encode(bytes); final fileSize = await imageFile.length(); final contentType = "image/${imageFile.path.split('.').last}"; - body["image"] = { "fileName": imageName, "contentType": contentType, "fileSize": fileSize, "description": "Employee attendance photo", - "base64Data": base64Image, + "base64Data": base64Encode(bytes), }; } catch (e) { _log("Image encoding error: $e"); @@ -223,22 +194,16 @@ class ApiService { } } - final response = - await _postRequest(ApiEndpoints.uploadAttendanceImage, body); + final response = await _postRequest(ApiEndpoints.uploadAttendanceImage, body); if (response == null) return false; final json = jsonDecode(response.body); - if (response.statusCode == 200 && json['success'] == true) { - return true; - } else { - _log("Failed to upload image: ${json['message'] ?? 'Unknown error'}"); - } + if (response.statusCode == 200 && json['success'] == true) return true; + _log("Failed to upload image: ${json['message'] ?? 'Unknown error'}"); return false; } - // ===== Utilities ===== - static String generateImageName(String employeeId, int count) { final now = DateTime.now(); final dateStr = DateFormat('yyyyMMdd_HHmmss').format(now); @@ -246,35 +211,19 @@ class ApiService { return "${employeeId}_${dateStr}_$imageNumber.jpg"; } -// ===== Employee Screen API Calls ===== - static Future?> getAllEmployeesByProject( - String projectId) async { - if (projectId.isEmpty) { - throw ArgumentError('projectId must not be empty'); - } + // === Employee APIs === - final String endpoint = - "${ApiEndpoints.getAllEmployeesByProject}/$projectId"; - final response = await _getRequest(endpoint); - - return response != null - ? _parseResponse(response, label: 'Employees by Project') - : null; + static Future?> getAllEmployeesByProject(String projectId) async { + if (projectId.isEmpty) throw ArgumentError('projectId must not be empty'); + final endpoint = "${ApiEndpoints.getAllEmployeesByProject}/$projectId"; + return _getRequest(endpoint).then((res) => res != null ? _parseResponse(res, label: 'Employees by Project') : null); } - static Future?> getAllEmployees() async { - final response = await _getRequest(ApiEndpoints.getAllEmployees); - return response != null - ? _parseResponse(response, label: 'All Employees') - : null; - } + static Future?> getAllEmployees() async => + _getRequest(ApiEndpoints.getAllEmployees).then((res) => res != null ? _parseResponse(res, label: 'All Employees') : null); - static Future?> getRoles() async { - final response = await _getRequest(ApiEndpoints.getRoles); - return response != null - ? _parseResponse(response, label: 'All Employees') - : null; - } + static Future?> getRoles() async => + _getRequest(ApiEndpoints.getRoles).then((res) => res != null ? _parseResponse(res, label: 'Roles') : null); static Future createEmployee({ required String firstName, @@ -290,61 +239,33 @@ class ApiService { "gender": gender, "jobRoleId": jobRoleId, }; - - // Make the API request final response = await _postRequest(ApiEndpoints.createEmployee, body); - - if (response == null) { - _log("Error: No response from server."); - return false; - } - + if (response == null) return false; final json = jsonDecode(response.body); - - if (response.statusCode == 200) { - if (json['success'] == true) { - return true; - } else { - _log( - "Failed to create employee: ${json['message'] ?? 'Unknown error'}"); - return false; - } - } else { - _log( - "Failed to create employee. Status code: ${response.statusCode}, Response: ${json['message'] ?? 'No message'}"); - return false; - } + return response.statusCode == 200 && json['success'] == true; } - static Future?> getEmployeeDetails( - String employeeId) async { + static Future?> getEmployeeDetails(String employeeId) async { final url = "${ApiEndpoints.getEmployeeInfo}/$employeeId"; - final response = await _getRequest(url); - final data = response != null - ? _parseResponse(response, label: 'Employee Details') - : null; - if (data is Map) { - return data; - } - return null; + final data = response != null ? _parseResponse(response, label: 'Employee Details') : null; + return data is Map ? data : null; } - // ===== Daily Tasks API Calls ===== - static Future?> getDailyTasks(String projectId, - {DateTime? dateFrom, DateTime? dateTo}) async { + // === Daily Task APIs === + + static Future?> getDailyTasks( + String projectId, { + DateTime? dateFrom, + DateTime? dateTo, + }) async { final query = { "projectId": projectId, - if (dateFrom != null) - "dateFrom": DateFormat('yyyy-MM-dd').format(dateFrom), + if (dateFrom != null) "dateFrom": DateFormat('yyyy-MM-dd').format(dateFrom), if (dateTo != null) "dateTo": DateFormat('yyyy-MM-dd').format(dateTo), }; - - final response = - await _getRequest(ApiEndpoints.getDailyTask, queryParams: query); - return response != null - ? _parseResponse(response, label: 'Daily Tasks') - : null; + return _getRequest(ApiEndpoints.getDailyTask, queryParams: query) + .then((res) => res != null ? _parseResponse(res, label: 'Daily Tasks') : null); } static Future reportTask({ @@ -363,34 +284,14 @@ class ApiService { if (images != null && images.isNotEmpty) "images": images, }; - String? token = await _getToken(); - if (token == null) return false; - - final uri = Uri.parse("${ApiEndpoints.baseUrl}${ApiEndpoints.reportTask}"); - - _log("POST $uri"); - _log("Headers: ${_headers(token)}"); - _log("Body: $body"); - - try { - final response = await http - .post(uri, headers: _headers(token), body: jsonEncode(body)) - .timeout(const Duration(seconds: 30)); - - _log("Response Status: ${response.statusCode}"); - - final json = jsonDecode(response.body); - - if (response.statusCode == 200 && json['success'] == true) { - Get.back(); - return true; - } else { - _log("Failed to report task: ${json['message'] ?? 'Unknown error'}"); - } - } catch (e) { - _log("HTTP POST Exception (reportTask): $e"); + final response = await _postRequest(ApiEndpoints.reportTask, body); + if (response == null) return false; + final json = jsonDecode(response.body); + if (response.statusCode == 200 && json['success'] == true) { + Get.back(); + return true; } - + _log("Failed to report task: ${json['message'] ?? 'Unknown error'}"); return false; } @@ -406,45 +307,17 @@ class ApiService { if (images != null && images.isNotEmpty) "images": images, }; - String? token = await _getToken(); - if (token == null) return false; - - final uri = Uri.parse("${ApiEndpoints.baseUrl}${ApiEndpoints.commentTask}"); - - _log("POST $uri"); - _log("Headers: ${_headers(token)}"); - _log("Body: $body"); - - try { - final response = await http - .post(uri, headers: _headers(token), body: jsonEncode(body)) - .timeout(const Duration(seconds: 30)); - - _log("Response Status: ${response.statusCode}"); - - final json = jsonDecode(response.body); - if (response.statusCode == 200 && json['success'] == true) { - return true; - } else { - _log("Failed to comment task: ${json['message'] ?? 'Unknown error'}"); - } - } catch (e) { - _log("HTTP POST Exception (commentTask): $e"); - } - - return false; + final response = await _postRequest(ApiEndpoints.commentTask, body); + if (response == null) return false; + final json = jsonDecode(response.body); + return response.statusCode == 200 && json['success'] == true; } - // Daily Task Planing // - - static Future?> getDailyTasksDetails( - String projectId) async { + static Future?> getDailyTasksDetails(String projectId) async { final url = "${ApiEndpoints.dailyTaskDetails}/$projectId"; - final response = await _getRequest(url); return response != null - ? _parseResponseForAllData(response, label: 'Daily Task Details') - as Map? + ? _parseResponseForAllData(response, label: 'Daily Task Details') as Map? : null; } @@ -460,26 +333,16 @@ class ApiService { "plannedTask": plannedTask, "description": description, "taskTeam": taskTeam, - "assignmentDate": - (assignmentDate ?? DateTime.now()).toUtc().toIso8601String(), + "assignmentDate": (assignmentDate ?? DateTime.now()).toUtc().toIso8601String(), }; - final response = await _postRequest(ApiEndpoints.assignDailyTask, body); - - if (response == null) { - _log("Error: No response from server."); - return false; - } - + if (response == null) return false; final json = jsonDecode(response.body); - if (response.statusCode == 200 && json['success'] == true) { Get.back(); return true; - } else { - _log( - "Failed to assign daily task: ${json['message'] ?? 'Unknown error'}"); - return false; } + _log("Failed to assign daily task: ${json['message'] ?? 'Unknown error'}"); + return false; } } diff --git a/lib/helpers/services/auth_service.dart b/lib/helpers/services/auth_service.dart index af2e593..7e212fc 100644 --- a/lib/helpers/services/auth_service.dart +++ b/lib/helpers/services/auth_service.dart @@ -1,11 +1,11 @@ import 'dart:convert'; -import 'package:http/http.dart' as http; import 'package:get/get.dart'; -import 'package:marco/helpers/services/storage/local_storage.dart'; -import 'package:marco/controller/permission_controller.dart'; +import 'package:http/http.dart' as http; import 'package:logger/logger.dart'; -import 'package:marco/helpers/services/api_endpoints.dart'; +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'; final Logger logger = Logger(); @@ -14,9 +14,11 @@ class AuthService { static const Map _headers = { 'Content-Type': 'application/json', }; + static bool isLoggedIn = false; - static Future?> loginUser( - Map data) async { + + /// Login with email and password + static Future?> loginUser(Map data) async { try { final response = await http.post( Uri.parse("$_baseUrl/auth/login-mobile"), @@ -31,9 +33,7 @@ class AuthService { } else if (response.statusCode == 401) { return {"password": "Invalid email or password"}; } else { - return { - "error": responseData['message'] ?? "Unexpected error occurred" - }; + return {"error": responseData['message'] ?? "Unexpected error occurred"}; } } catch (e) { logger.e("Login error: $e"); @@ -41,16 +41,13 @@ class AuthService { } } - /// Refreshes the JWT token using the refresh token. + /// Refresh JWT token static Future refreshToken() async { final accessToken = await LocalStorage.getJwtToken(); final refreshToken = await LocalStorage.getRefreshToken(); - if (accessToken == null || - refreshToken == null || - accessToken.isEmpty || - refreshToken.isEmpty) { - logger.w("Missing token or refresh token for refresh."); + if (accessToken == null || refreshToken == null || accessToken.isEmpty || refreshToken.isEmpty) { + logger.w("Missing access/refresh token."); return false; } @@ -59,82 +56,50 @@ class AuthService { "refreshToken": refreshToken, }; - logger.i("Sending refresh token request with body: $requestBody"); - try { final response = await http.post( Uri.parse("$_baseUrl/auth/refresh-token"), - headers: { - 'Content-Type': 'application/json', - }, - body: jsonEncode(requestBody), - ); - - logger.i( - "Refresh token API response (${response.statusCode}): ${response.body}"); - - final data = jsonDecode(response.body); - if (response.statusCode == 200 && data['success'] == true) { - final newAccessToken = data['data']['token']; - final newRefreshToken = data['data']['refreshToken']; - - if (newAccessToken == null || newRefreshToken == null) { - logger.w("Invalid tokens received during refresh."); - return false; - } - - await LocalStorage.setJwtToken(newAccessToken); - await LocalStorage.setRefreshToken(newRefreshToken); - await LocalStorage.setLoggedInUser(true); - - logger.i("Token refreshed successfully."); - return true; - } else { - logger.w("Refresh failed: ${data['message']}"); - return false; - } - } catch (e) { - logger.e("Exception during token refresh: $e"); - return false; - } - } - -// Forgot password API - static Future?> forgotPassword(String email) async { - final requestBody = {"email": email}; - - logger.i("Sending forgot password request with email: $email"); - - try { - final response = await http.post( - Uri.parse("$_baseUrl/auth/forgot-password"), headers: _headers, body: jsonEncode(requestBody), ); - logger.i( - "Forgot password API response (${response.statusCode}): ${response.body}"); - - final responseData = jsonDecode(response.body); - - if (response.statusCode == 200 && responseData['success'] == true) { - logger.i("Forgot password request successful."); - return null; + final data = jsonDecode(response.body); + if (response.statusCode == 200 && data['success'] == true) { + await LocalStorage.setJwtToken(data['data']['token']); + await LocalStorage.setRefreshToken(data['data']['refreshToken']); + await LocalStorage.setLoggedInUser(true); + logger.i("Token refreshed."); + return true; } else { - return { - "error": - responseData['message'] ?? "Failed to send password reset link." - }; + logger.w("Refresh token failed: ${data['message']}"); + return false; } } catch (e) { - logger.e("Exception during forgot password request: $e"); + logger.e("Token refresh error: $e"); + return false; + } + } + + /// Forgot password + static Future?> forgotPassword(String email) async { + try { + final response = await http.post( + Uri.parse("$_baseUrl/auth/forgot-password"), + headers: _headers, + body: jsonEncode({"email": email}), + ); + + final data = jsonDecode(response.body); + if (response.statusCode == 200 && data['success'] == true) return null; + return {"error": data['message'] ?? "Failed to send reset link."}; + } catch (e) { + logger.e("Forgot password error: $e"); return {"error": "Network error. Please check your connection."}; } } -// Request demo API - static Future?> requestDemo( - Map demoData) async { + /// Request demo + static Future?> requestDemo(Map demoData) async { try { final response = await http.post( Uri.parse("$_baseUrl/market/inquiry"), @@ -142,22 +107,16 @@ class AuthService { body: jsonEncode(demoData), ); - final responseData = jsonDecode(response.body); - - if (response.statusCode == 200 && responseData['success'] == true) { - logger.i("Request Demo submitted successfully."); - return null; - } else { - return { - "error": responseData['message'] ?? "Failed to submit demo request." - }; - } + final data = jsonDecode(response.body); + if (response.statusCode == 200 && data['success'] == true) return null; + return {"error": data['message'] ?? "Failed to submit demo request."}; } catch (e) { - logger.e("Exception during request demo: $e"); + logger.e("Request demo error: $e"); return {"error": "Network error. Please check your connection."}; } } + /// Get list of industries static Future>?> getIndustries() async { try { final response = await http.get( @@ -165,201 +124,129 @@ class AuthService { headers: _headers, ); - logger.i( - "Get Industries API response (${response.statusCode}): ${response.body}"); - - final responseData = jsonDecode(response.body); - - if (response.statusCode == 200 && responseData['success'] == true) { - // Return the list of industries as List> - final List industriesData = responseData['data']; - return industriesData.cast>(); - } else { - logger.w("Failed to fetch industries: ${responseData['message']}"); - return null; + final data = jsonDecode(response.body); + if (response.statusCode == 200 && data['success'] == true) { + return List>.from(data['data']); } + return null; } catch (e) { - logger.e("Exception during getIndustries: $e"); + logger.e("Get industries error: $e"); return null; } } - /// Generates a new MPIN for the user. + /// Generate MPIN static Future?> generateMpin({ required String employeeId, required String mpin, }) async { - final jwtToken = await LocalStorage.getJwtToken(); - - final requestBody = { - "employeeId": employeeId, - "mpin": mpin, - }; - - logger.i("Sending MPIN generation request: $requestBody"); + final token = await LocalStorage.getJwtToken(); try { final response = await http.post( Uri.parse("$_baseUrl/auth/generate-mpin"), headers: { - 'Content-Type': 'application/json', - if (jwtToken != null && jwtToken.isNotEmpty) - 'Authorization': 'Bearer $jwtToken', + ..._headers, + if (token != null && token.isNotEmpty) 'Authorization': 'Bearer $token', }, - body: jsonEncode(requestBody), + body: jsonEncode({"employeeId": employeeId, "mpin": mpin}), ); - logger.i( - "Generate MPIN API response (${response.statusCode}): ${response.body}"); - - final responseData = jsonDecode(response.body); - - if (response.statusCode == 200 && responseData['success'] == true) { - logger.i("MPIN generated successfully."); - return null; - } else { - return {"error": responseData['message'] ?? "Failed to generate MPIN."}; - } + final data = jsonDecode(response.body); + if (response.statusCode == 200 && data['success'] == true) return null; + return {"error": data['message'] ?? "Failed to generate MPIN."}; } catch (e) { - logger.e("Exception during generate MPIN: $e"); + logger.e("Generate MPIN error: $e"); return {"error": "Network error. Please check your connection."}; } } + /// Verify MPIN static Future?> verifyMpin({ required String mpin, required String mpinToken, }) async { - // Get employee info from local storage final employeeInfo = LocalStorage.getEmployeeInfo(); + if (employeeInfo == null) return {"error": "Employee info not found."}; - if (employeeInfo == null) { - logger.w("Employee info not found in local storage."); - return {"error": "Employee info not found. Please login again."}; - } - - final employeeId = employeeInfo.id; - - final jwtToken = await LocalStorage.getJwtToken(); - - final requestBody = { - "employeeId": employeeId, - "mpin": mpin, - "mpinToken": mpinToken, - }; - - logger.i("Sending MPIN verification request: $requestBody"); + final token = await LocalStorage.getJwtToken(); try { final response = await http.post( Uri.parse("$_baseUrl/auth/login-mpin"), headers: { - 'Content-Type': 'application/json', - if (jwtToken != null && jwtToken.isNotEmpty) - 'Authorization': 'Bearer $jwtToken', + ..._headers, + if (token != null && token.isNotEmpty) 'Authorization': 'Bearer $token', }, - body: jsonEncode(requestBody), + body: jsonEncode({ + "employeeId": employeeInfo.id, + "mpin": mpin, + "mpinToken": mpinToken, + }), ); - logger.i( - "Verify MPIN API response (${response.statusCode}): ${response.body}"); - - final responseData = jsonDecode(response.body); - - if (response.statusCode == 200 && responseData['success'] == true) { - logger.i("MPIN verified successfully."); - return null; - } else { - return {"error": responseData['message'] ?? "Failed to verify MPIN."}; - } + final data = jsonDecode(response.body); + if (response.statusCode == 200 && data['success'] == true) return null; + return {"error": data['message'] ?? "MPIN verification failed."}; } catch (e) { - logger.e("Exception during verify MPIN: $e"); + logger.e("Verify MPIN error: $e"); return {"error": "Network error. Please check your connection."}; } } - // Generate OTP API + /// Generate OTP static Future?> generateOtp(String email) async { - final requestBody = {"email": email}; - - logger.i("Sending generate OTP request: $requestBody"); - try { final response = await http.post( Uri.parse("$_baseUrl/auth/send-otp"), headers: _headers, - body: jsonEncode(requestBody), + body: jsonEncode({"email": email}), ); - logger.i( - "Generate OTP API response (${response.statusCode}): ${response.body}"); - - final responseData = jsonDecode(response.body); - - if (response.statusCode == 200 && responseData['success'] == true) { - logger.i("OTP generated successfully."); - return null; - } else { - return {"error": responseData['message'] ?? "Failed to generate OTP."}; - } + final data = jsonDecode(response.body); + if (response.statusCode == 200 && data['success'] == true) return null; + return {"error": data['message'] ?? "Failed to generate OTP."}; } catch (e) { - logger.e("Exception during generate OTP: $e"); + logger.e("Generate OTP error: $e"); return {"error": "Network error. Please check your connection."}; } } -// Verify OTP API + /// Verify OTP and login static Future?> verifyOtp({ required String email, required String otp, }) async { - final requestBody = { - "email": email, - "otp": otp, - }; - - logger.i("Sending verify OTP request: $requestBody"); - try { final response = await http.post( Uri.parse("$_baseUrl/auth/login-otp"), headers: _headers, - body: jsonEncode(requestBody), + body: jsonEncode({"email": email, "otp": otp}), ); - logger.i( - "Verify OTP API response (${response.statusCode}): ${response.body}"); - - final responseData = jsonDecode(response.body); - - if (response.statusCode == 200 && responseData['data'] != null) { - await _handleLoginSuccess(responseData['data']); - logger.i("OTP verified and login state initialized successfully."); + final data = jsonDecode(response.body); + if (response.statusCode == 200 && data['data'] != null) { + await _handleLoginSuccess(data['data']); return null; - } else { - return {"error": responseData['message'] ?? "Failed to verify OTP."}; } + return {"error": data['message'] ?? "OTP verification failed."}; } catch (e) { - logger.e("Exception during verify OTP: $e"); + logger.e("Verify OTP error: $e"); return {"error": "Network error. Please check your connection."}; } } + /// Handle login success flow static Future _handleLoginSuccess(Map data) async { final jwtToken = data['token']; final refreshToken = data['refreshToken']; final mpinToken = data['mpinToken']; - logger.i("JWT Token: $jwtToken"); - if (refreshToken != null) logger.i("Refresh Token: $refreshToken"); - if (mpinToken != null) logger.i("MPIN Token: $mpinToken"); - await LocalStorage.setJwtToken(jwtToken); await LocalStorage.setLoggedInUser(true); - if (refreshToken != null) { - await LocalStorage.setRefreshToken(refreshToken); - } + if (refreshToken != null) await LocalStorage.setRefreshToken(refreshToken); + if (mpinToken != null && mpinToken.isNotEmpty) { await LocalStorage.setMpinToken(mpinToken); await LocalStorage.setIsMpin(true); @@ -368,11 +255,11 @@ class AuthService { await LocalStorage.removeMpinToken(); } - // ✅ Put and load PermissionController final permissionController = Get.put(PermissionController()); await permissionController.loadData(jwtToken); await Get.find().fetchProjects(); isLoggedIn = true; + logger.i("Login success initialized."); } } diff --git a/lib/helpers/services/permission_service.dart b/lib/helpers/services/permission_service.dart index f4d4dba..3259a5d 100644 --- a/lib/helpers/services/permission_service.dart +++ b/lib/helpers/services/permission_service.dart @@ -1,7 +1,8 @@ import 'dart:convert'; -import 'package:http/http.dart' as http; import 'package:get/get.dart'; +import 'package:http/http.dart' as http; import 'package:logger/logger.dart'; + import 'package:marco/model/user_permission.dart'; import 'package:marco/model/employee_info.dart'; import 'package:marco/model/projects_model.dart'; @@ -13,19 +14,21 @@ final Logger logger = Logger(); class PermissionService { static final Map> _userDataCache = {}; - static Future> fetchAllUserData(String token, - {bool hasRetried = false}) async { - // Return from cache if available + /// Fetches all user-related data (permissions, employee info, projects) + static Future> fetchAllUserData( + String token, { + bool hasRetried = false, + }) async { + // Return cached data if already available if (_userDataCache.containsKey(token)) { return _userDataCache[token]!; } + final uri = Uri.parse('https://stageapi.marcoaiot.com/api/user/profile'); + final headers = {'Authorization': 'Bearer $token'}; + try { - final response = await http.get( - Uri.parse('https://stageapi.marcoaiot.com/api/user/profile'), - // Uri.parse('https://api.marcoaiot.com/api/user/profile'), - headers: {'Authorization': 'Bearer $token'}, - ); + final response = await http.get(uri, headers: headers); if (response.statusCode == 200) { final data = json.decode(response.body)['data']; @@ -40,11 +43,12 @@ class PermissionService { return result; } + // Handle 401 by attempting a single retry with refreshed token if (response.statusCode == 401 && !hasRetried) { final refreshed = await AuthService.refreshToken(); if (refreshed) { final newToken = await LocalStorage.getJwtToken(); - if (newToken != null) { + if (newToken != null && newToken.isNotEmpty) { return fetchAllUserData(newToken, hasRetried: true); } } @@ -53,15 +57,15 @@ class PermissionService { throw Exception('Unauthorized. Token refresh failed.'); } - final errorMessage = - json.decode(response.body)['message'] ?? 'Unknown error'; - throw Exception('Failed to load data: $errorMessage'); + final error = json.decode(response.body)['message'] ?? 'Unknown error'; + throw Exception('Failed to fetch user data: $error'); } catch (e) { logger.e('Error fetching user data: $e'); rethrow; } } + /// Clears auth data and redirects to login static Future _handleUnauthorized() async { await LocalStorage.removeToken('jwt_token'); await LocalStorage.removeToken('refresh_token'); @@ -69,15 +73,20 @@ class PermissionService { Get.offAllNamed('/auth/login-option'); } - static List _parsePermissions( - List featurePermissions) => - featurePermissions - .map((id) => UserPermission.fromJson({'id': id})) - .toList(); + /// Converts raw permission data into list of `UserPermission` + static List _parsePermissions(List permissions) { + return permissions + .map((id) => UserPermission.fromJson({'id': id})) + .toList(); + } - static EmployeeInfo _parseEmployeeInfo(Map employeeData) => - EmployeeInfo.fromJson(employeeData); + /// Converts raw employee JSON into `EmployeeInfo` + static EmployeeInfo _parseEmployeeInfo(Map data) { + return EmployeeInfo.fromJson(data); + } - static List _parseProjectsInfo(List projects) => - projects.map((proj) => ProjectInfo.fromJson(proj)).toList(); + /// Converts raw projects JSON into list of `ProjectInfo` + static List _parseProjectsInfo(List projects) { + return projects.map((proj) => ProjectInfo.fromJson(proj)).toList(); + } }