From 2a91ccc3232a12a3832fae7625c9733988c8a566 Mon Sep 17 00:00:00 2001 From: Vaibhav Surve Date: Mon, 5 May 2025 16:56:58 +0530 Subject: [PATCH] added loggers --- .../attendance_screen_controller.dart | 108 +++++---- lib/controller/permission_controller.dart | 87 ++++--- lib/helpers/services/api_service.dart | 216 +++++++++--------- lib/helpers/services/auth_service.dart | 96 ++++++-- lib/helpers/services/permission_service.dart | 81 ++++--- lib/main.dart | 14 +- pubspec.yaml | 2 +- 7 files changed, 358 insertions(+), 246 deletions(-) diff --git a/lib/controller/dashboard/attendance_screen_controller.dart b/lib/controller/dashboard/attendance_screen_controller.dart index e211243..33c1c48 100644 --- a/lib/controller/dashboard/attendance_screen_controller.dart +++ b/lib/controller/dashboard/attendance_screen_controller.dart @@ -2,7 +2,8 @@ import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:image_picker/image_picker.dart'; import 'package:geolocator/geolocator.dart'; -import 'package:intl/intl.dart'; +import 'package:intl/intl.dart'; + import 'package:marco/helpers/services/api_service.dart'; import 'package:marco/model/attendance_model.dart'; import 'package:marco/model/project_model.dart'; @@ -11,6 +12,10 @@ import 'package:marco/model/attendance_log_model.dart'; import 'package:marco/model/regularization_log_model.dart'; import 'package:marco/model/attendance_log_view_model.dart'; +import 'package:logger/logger.dart'; + +final Logger log = Logger(); + class AttendanceController extends GetxController { List attendances = []; List projects = []; @@ -24,7 +29,7 @@ class AttendanceController extends GetxController { List regularizationLogs = []; List attendenceLogsView = []; - RxBool isLoading = false.obs; // Added loading flag + RxBool isLoading = false.obs; @override void onInit() { @@ -41,59 +46,63 @@ class AttendanceController extends GetxController { final today = DateTime.now(); startDateAttendance = today.subtract(const Duration(days: 7)); endDateAttendance = today; + log.i("Default date range set: $startDateAttendance to $endDateAttendance"); } Future fetchProjects() async { - isLoading.value = true; // Set loading to true before API call + isLoading.value = true; final response = await ApiService.getProjects(); - isLoading.value = false; // Set loading to false after API call completes + isLoading.value = false; if (response != null && response.isNotEmpty) { projects = response.map((json) => ProjectModel.fromJson(json)).toList(); selectedProjectId = projects.first.id.toString(); + log.i("Projects fetched: ${projects.length} projects loaded."); await fetchProjectData(selectedProjectId); update(['attendance_dashboard_controller']); } else { - print("No projects data found or failed to fetch data."); + log.w("No project data found or API call failed."); } } Future fetchProjectData(String? projectId) async { if (projectId == null) return; - isLoading.value = true; // Set loading to true before API call + isLoading.value = true; await Future.wait([ fetchEmployeesByProject(projectId), fetchAttendanceLogs(projectId, dateFrom: startDateAttendance, dateTo: endDateAttendance), fetchRegularizationLogs(projectId), ]); - isLoading.value = false; // Set loading to false after data is fetched + isLoading.value = false; + log.i("Project data fetched for project ID: $projectId"); } Future fetchEmployeesByProject(String? projectId) async { if (projectId == null) return; - isLoading.value = true; // Set loading to true before API call - final response = - await ApiService.getEmployeesByProject(projectId); - isLoading.value = false; // Set loading to false after API call completes + isLoading.value = true; + final response = await ApiService.getEmployeesByProject(projectId); + isLoading.value = false; if (response != null) { employees = response.map((json) => EmployeeModel.fromJson(json)).toList(); + log.i( + "Employees fetched: ${employees.length} employees for project $projectId"); update(); } else { - print("Failed to fetch employees for project $projectId."); + log.e("Failed to fetch employees for project $projectId"); } } Future captureAndUploadAttendance( - String id, // Change from int to String - String employeeId, // Change from int to String - String projectId, { + String id, + String employeeId, + String projectId, { String comment = "Marked via mobile app", required int action, - bool imageCapture = true, // <- add this flag + bool imageCapture = true, }) async { try { XFile? image; @@ -102,7 +111,10 @@ class AttendanceController extends GetxController { source: ImageSource.camera, imageQuality: 80, ); - if (image == null) return false; + if (image == null) { + log.w("Image capture cancelled."); + return false; + } } final position = await Geolocator.getCurrentPosition( @@ -111,9 +123,9 @@ class AttendanceController extends GetxController { final imageName = imageCapture ? ApiService.generateImageName(employeeId, employees.length + 1) - : ""; // Empty or null if not capturing image + : ""; - return await ApiService.uploadAttendanceImage( + final result = await ApiService.uploadAttendanceImage( id, employeeId, image, @@ -123,10 +135,13 @@ class AttendanceController extends GetxController { projectId: projectId, comment: comment, action: action, - imageCapture: imageCapture, // <- pass flag down + imageCapture: imageCapture, ); - } catch (e) { - print("Error capturing or uploading attendance: $e"); + + log.i("Attendance uploaded for $employeeId, action: $action"); + return result; + } catch (e, stacktrace) { + log.e("Error uploading attendance", error: e, stackTrace: stacktrace); return false; } } @@ -150,6 +165,8 @@ class AttendanceController extends GetxController { startDateAttendance = picked.start; endDateAttendance = picked.end; + log.i("Date range selected: $startDateAttendance to $endDateAttendance"); + await controller.fetchAttendanceLogs( controller.selectedProjectId, dateFrom: picked.start, @@ -165,39 +182,39 @@ class AttendanceController extends GetxController { }) async { if (projectId == null) return; - isLoading.value = true; // Set loading to true before API call + isLoading.value = true; final response = await ApiService.getAttendanceLogs( - projectId, + projectId, dateFrom: dateFrom, dateTo: dateTo, ); - isLoading.value = false; // Set loading to false after API call completes + isLoading.value = false; if (response != null) { attendanceLogs = response.map((json) => AttendanceLogModel.fromJson(json)).toList(); - print("Attendance logs fetched: ${response}"); + log.i("Attendance logs fetched: ${attendanceLogs.length}"); update(); } else { - print("Failed to fetch attendance logs for project $projectId."); + log.e("Failed to fetch attendance logs for project $projectId"); } } -Map> groupLogsByCheckInDate() { - final groupedLogs = >{}; - for (var log in attendanceLogs) { - final checkInDate = log.checkIn != null - ? DateFormat('dd MMM yyyy').format(log.checkIn!) - : 'Unknown'; + Map> groupLogsByCheckInDate() { + final groupedLogs = >{}; - if (!groupedLogs.containsKey(checkInDate)) { - groupedLogs[checkInDate] = []; + for (var logItem in attendanceLogs) { + final checkInDate = logItem.checkIn != null + ? DateFormat('dd MMM yyyy').format(logItem.checkIn!) + : 'Unknown'; + + groupedLogs.putIfAbsent(checkInDate, () => []); + groupedLogs[checkInDate]!.add(logItem); } - groupedLogs[checkInDate]!.add(log); - } - return groupedLogs; -} + log.i("Logs grouped by check-in date."); + return groupedLogs; + } Future fetchRegularizationLogs( String? projectId, { @@ -207,34 +224,35 @@ Map> groupLogsByCheckInDate() { if (projectId == null) return; isLoading.value = true; - final response = - await ApiService.getRegularizationLogs(projectId); + final response = await ApiService.getRegularizationLogs(projectId); isLoading.value = false; if (response != null) { regularizationLogs = response .map((json) => RegularizationLogModel.fromJson(json)) .toList(); + log.i("Regularization logs fetched: ${regularizationLogs.length}"); update(); } else { - print("Failed to fetch regularization logs for project $projectId."); + log.e("Failed to fetch regularization logs for project $projectId"); } } Future fetchLogsView(String? id) async { if (id == null) return; - isLoading.value = true; // Set loading to true before API call + isLoading.value = true; final response = await ApiService.getAttendanceLogView(id); - isLoading.value = false; // Set loading to false after API call completes + isLoading.value = false; if (response != null) { attendenceLogsView = response .map((json) => AttendanceLogViewModel.fromJson(json)) .toList(); + log.i("Attendance log view fetched for ID: $id"); update(); } else { - print("Failed to fetch regularization logs for project $id."); + log.e("Failed to fetch attendance log view for ID $id"); } } } diff --git a/lib/controller/permission_controller.dart b/lib/controller/permission_controller.dart index 385384d..8382e75 100644 --- a/lib/controller/permission_controller.dart +++ b/lib/controller/permission_controller.dart @@ -2,11 +2,15 @@ import 'dart:async'; import 'dart:convert'; import 'package:get/get.dart'; import 'package:shared_preferences/shared_preferences.dart'; +import 'package:logger/logger.dart'; + import 'package:marco/helpers/services/permission_service.dart'; import 'package:marco/model/user_permission.dart'; import 'package:marco/model/employee_info.dart'; import 'package:marco/model/projects_model.dart'; +final log = Logger(); + class PermissionController extends GetxController { var permissions = [].obs; var employeeInfo = Rxn(); @@ -20,84 +24,99 @@ class PermissionController extends GetxController { _startAutoRefresh(); } - // Store all data at once to reduce redundant operations Future _storeData() async { - final prefs = await SharedPreferences.getInstance(); + try { + final prefs = await SharedPreferences.getInstance(); - // Store permissions - await prefs.setString( - 'user_permissions', jsonEncode(permissions.map((e) => e.toJson()).toList())); - - // Store employee info if available - if (employeeInfo.value != null) { await prefs.setString( - 'employee_info', jsonEncode(employeeInfo.value!.toJson())); - } + 'user_permissions', + jsonEncode(permissions.map((e) => e.toJson()).toList()), + ); - // Store projects info if available - if (projectsInfo.isNotEmpty) { - await prefs.setString('projects_info', - jsonEncode(projectsInfo.map((e) => e.toJson()).toList())); + if (employeeInfo.value != null) { + await prefs.setString( + 'employee_info', + jsonEncode(employeeInfo.value!.toJson()), + ); + } + + if (projectsInfo.isNotEmpty) { + await prefs.setString( + 'projects_info', + jsonEncode(projectsInfo.map((e) => e.toJson()).toList()), + ); + } + + log.i("User data successfully stored in SharedPreferences."); + } catch (e, stacktrace) { + log.e("Error storing data", error: e, stackTrace: stacktrace); } } - // Fetch and load all required data (permissions, employee info, and projects) Future _loadDataFromAPI() async { final token = await _getAuthToken(); if (token?.isNotEmpty ?? false) { await loadData(token!); } else { - print("No token available for fetching data."); + log.w("No token found for loading API data."); } } - // Fetch data and update the state (permissions, employee info, and projects) Future loadData(String token) async { try { final userData = await PermissionService.fetchAllUserData(token); - - // Update state variables _updateState(userData); - - // Store all data after fetching await _storeData(); - } catch (e) { - print('Error loading data from API: $e'); + log.i("Data loaded and state updated successfully."); + } catch (e, stacktrace) { + log.e("Error loading data from API", error: e, stackTrace: stacktrace); } } - // Update state variables (permissions, employeeInfo, and projects) void _updateState(Map userData) { - permissions.assignAll(userData['permissions']); - employeeInfo.value = userData['employeeInfo']; - projectsInfo.assignAll(userData['projects']); + try { + permissions.assignAll(userData['permissions']); + employeeInfo.value = userData['employeeInfo']; + projectsInfo.assignAll(userData['projects']); + log.i("State updated with new user data."); + } catch (e, stacktrace) { + log.e("Error updating state", error: e, stackTrace: stacktrace); + } } - // Fetch the auth token from SharedPreferences Future _getAuthToken() async { - final prefs = await SharedPreferences.getInstance(); - return prefs.getString('jwt_token'); + try { + final prefs = await SharedPreferences.getInstance(); + return prefs.getString('jwt_token'); + } catch (e, stacktrace) { + log.e("Error retrieving auth token", error: e, stackTrace: stacktrace); + return null; + } } - // Set up automatic data refresh every 30 minutes void _startAutoRefresh() { _refreshTimer = Timer.periodic(Duration(minutes: 30), (timer) async { + log.i("Auto-refresh triggered."); await _loadDataFromAPI(); }); } - // Check if the user has the given permission bool hasPermission(String permissionId) { - return permissions.any((p) => p.id == permissionId); + final hasPerm = permissions.any((p) => p.id == permissionId); + log.d("Checking permission $permissionId: $hasPerm"); + return hasPerm; } bool isUserAssignedToProject(String projectId) { - return projectsInfo.any((project) => project.id == projectId); + final assigned = projectsInfo.any((project) => project.id == projectId); + log.d("Checking project assignment for $projectId: $assigned"); + return assigned; } @override void onClose() { _refreshTimer?.cancel(); + log.i("PermissionController disposed and timer cancelled."); super.onClose(); } } diff --git a/lib/helpers/services/api_service.dart b/lib/helpers/services/api_service.dart index 4f8e025..18b67fb 100644 --- a/lib/helpers/services/api_service.dart +++ b/lib/helpers/services/api_service.dart @@ -1,19 +1,24 @@ import 'dart:convert'; import 'package:http/http.dart' as http; import 'package:image_picker/image_picker.dart'; -import 'package:marco/helpers/services/storage/local_storage.dart'; import 'package:intl/intl.dart'; +import 'package:marco/helpers/services/storage/local_storage.dart'; +import 'package:marco/helpers/services/auth_service.dart'; +import 'package:logger/logger.dart'; +final Logger logger = Logger(); class ApiService { static const String baseUrl = "https://stageapi.marcoaiot.com/api"; + static const Duration timeout = Duration(seconds: 10); + static const bool enableLogs = true; - // ===== Common Helpers ===== + // ===== Helpers ===== static Future _getToken() async { - final token = LocalStorage.getJwtToken(); - if (token == null) { - print("No JWT token found. Please log in."); + final token = await LocalStorage.getJwtToken(); + if (token == null && enableLogs) { + logger.w("No JWT token found. Please log in."); // <-- Use logger } return token; } @@ -23,122 +28,121 @@ class ApiService { 'Authorization': 'Bearer $token', }; + static void _log(String message) { + if (enableLogs) logger.i(message); // <-- Use logger + } + + static dynamic _parseResponse(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['data']; + } + _log("API Error [$label]: ${json['message'] ?? 'Unknown error'}"); + } catch (e) { + _log("Response parsing error [$label]: $e"); + } + return null; + } + static Future _getRequest(String endpoint, - {Map? queryParams}) async { - final token = await _getToken(); + {Map? queryParams, bool hasRetried = false}) async { + String? token = await _getToken(); if (token == null) return null; - final uri = - Uri.parse("$baseUrl$endpoint").replace(queryParameters: queryParams); - print('GET request: $uri'); + Uri uri = Uri.parse("$baseUrl$endpoint").replace(queryParameters: queryParams); + _log('GET request: $uri'); try { - return await http.get(uri, headers: _headers(token)); + http.Response 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); + } + } + _log("Refresh failed."); + } + return response; } catch (e) { - print("HTTP GET Exception: $e"); + _log("HTTP GET Exception: $e"); return null; } } - static dynamic _parseResponse(http.Response response, {String label = ''}) { - print("$label Response: ${response.body}"); - if (response.statusCode == 200) { - final json = jsonDecode(response.body); - if (json['success'] == true) return json['data']; - print("API Error: ${json['message']}"); - } else { - print("HTTP Error [$label]: ${response.statusCode}"); + static Future _postRequest(String endpoint, dynamic body) async { + String? token = await _getToken(); + if (token == null) return null; + + final uri = Uri.parse("$baseUrl$endpoint"); + _log("POST request to $uri with body: $body"); + + try { + final response = await http + .post(uri, headers: _headers(token), body: jsonEncode(body)) + .timeout(timeout); + return response; + } catch (e) { + _log("HTTP POST Exception: $e"); + return null; } - return null; } // ===== API Calls ===== static Future?> getProjects() async { final response = await _getRequest("/project/list"); - return response != null - ? _parseResponse(response, label: 'Projects') - : null; + return response != null ? _parseResponse(response, label: 'Projects') : null; } static Future?> getEmployeesByProject(String projectId) async { final response = await _getRequest("/attendance/project/team", - queryParams: {"projectId": "$projectId"}); - return response != null - ? _parseResponse(response, label: 'Employees') - : null; + queryParams: {"projectId": projectId}); + return response != null ? _parseResponse(response, label: 'Employees') : null; } - static Future?> getAttendanceLogs( - String projectId, { - DateTime? dateFrom, - DateTime? dateTo, - }) async { + static Future?> getAttendanceLogs(String projectId, + {DateTime? dateFrom, DateTime? dateTo}) async { final query = { - "projectId": "$projectId", - if (dateFrom != null) - "dateFrom": DateFormat('yyyy-MM-dd').format(dateFrom), + "projectId": projectId, + if (dateFrom != null) "dateFrom": DateFormat('yyyy-MM-dd').format(dateFrom), if (dateTo != null) "dateTo": DateFormat('yyyy-MM-dd').format(dateTo), }; - final response = - await _getRequest("/attendance/project/log", queryParams: query); - return response != null - ? _parseResponse(response, label: 'Attendance Logs') - : null; + final response = await _getRequest("/attendance/project/log", queryParams: query); + return response != null ? _parseResponse(response, label: 'Attendance Logs') : null; } static Future?> getAttendanceLogView(String id) async { final response = await _getRequest("/attendance/log/attendance/$id"); - return response != null - ? _parseResponse(response, label: 'Attendance Log Details') - : null; + return response != null ? _parseResponse(response, label: 'Log Details') : null; } static Future?> getRegularizationLogs(String projectId) async { final response = await _getRequest("/attendance/regularize", - queryParams: {"projectId": "$projectId"}); - return response != null - ? _parseResponse(response, label: 'Regularization') - : null; + queryParams: {"projectId": projectId}); + return response != null ? _parseResponse(response, label: 'Regularization Logs') : null; } - // ===== Upload Image ===== - - static Future uploadAttendanceImage( - String id, - String employeeId, - XFile? imageFile, - double latitude, - double longitude, { - required String imageName, - required String projectId, - String comment = "", - required int action, - bool imageCapture = true, // <- add this flag -}) async { - final token = await _getToken(); - if (token == null) return false; - - try { - Map? imageObject; - if (imageCapture && imageFile != null) { - final bytes = await imageFile.readAsBytes(); - final base64Image = base64Encode(bytes); - final fileSize = await imageFile.length(); - final contentType = "image/${imageFile.path.split('.').last}"; - - imageObject = { - "fileName": '$imageName', - "contentType": '$contentType', - "fileSize": fileSize, - "description": "Employee attendance photo", - "base64Data": '$base64Image', - }; - } + // ===== Upload Attendance Image ===== + static Future uploadAttendanceImage( + String id, + String employeeId, + XFile? imageFile, + double latitude, + double longitude, { + required String imageName, + required String projectId, + String comment = "", + required int action, + bool imageCapture = true, + }) async { final now = DateTime.now(); - final body = { "id": id, "employeeId": employeeId, @@ -147,40 +151,44 @@ class ApiService { "comment": comment, "action": action, "date": DateFormat('yyyy-MM-dd').format(now), + if (imageCapture) "latitude": '$latitude', + if (imageCapture) "longitude": '$longitude', }; - // Only include latitude and longitude if imageCapture is true - if (imageCapture) { - body["latitude"] = '$latitude'; - body["longitude"] = '$longitude'; + 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, + }; + } catch (e) { + _log("Image encoding error: $e"); + return false; + } } - // Only add imageObject if it's not null - if (imageObject != null) { - body["image"] = imageObject; - } + final response = await _postRequest("/attendance/record-image", body); + if (response == null) return false; - print("Attendance Image Upload Body: $body"); - final response = await http.post( - Uri.parse("$baseUrl/attendance/record-image"), - headers: _headers(token), - body: jsonEncode(body), - ); - print("Attendance Image Upload Response: ${response.body}"); final json = jsonDecode(response.body); if (response.statusCode == 200 && json['success'] == true) { return true; } else { - print("Failed to upload image. API Error: ${json['message']}"); + _log("Failed to upload image: ${json['message'] ?? 'Unknown error'}"); } - } catch (e) { - print("Exception during image upload: $e"); + + return false; } - return false; -} - - // ===== Utilities ===== + // ===== Utilities ===== static String generateImageName(String employeeId, int count) { final now = DateTime.now(); diff --git a/lib/helpers/services/auth_service.dart b/lib/helpers/services/auth_service.dart index b48e3c1..15ee720 100644 --- a/lib/helpers/services/auth_service.dart +++ b/lib/helpers/services/auth_service.dart @@ -1,52 +1,104 @@ 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:get/get.dart'; +import 'package:logger/logger.dart'; // <-- Make sure this import is present + +final Logger logger = Logger(); class AuthService { + static const String _baseUrl = "https://stageapi.marcoaiot.com/api"; + static const Map _headers = { + 'Content-Type': 'application/json', + }; static bool isLoggedIn = false; - static Future?> loginUser(Map data) async { + /// Logs in the user and stores tokens if successful. + static Future?> loginUser( + Map data) async { try { final response = await http.post( - Uri.parse('https://stageapi.marcoaiot.com/api/auth/login'), - headers: {'Content-Type': 'application/json'}, + Uri.parse("$_baseUrl/auth/login"), + headers: _headers, body: jsonEncode(data), ); - if (response.statusCode == 200) { + final responseData = jsonDecode(response.body); + if (response.statusCode == 200 && responseData['data'] != null) { isLoggedIn = true; - // Parse the response to get the JWT and refresh tokens - final responseData = jsonDecode(response.body); - - // Adjusted for the actual response structure - final jwtToken = responseData['data']['token']; // Ensure this matches your actual response - - // Save the JWT token in local storage - await LocalStorage.setJwtToken(jwtToken); - print("JWT Token: $jwtToken"); - - // Optionally save refresh token if available + final jwtToken = responseData['data']['token']; final refreshToken = responseData['data']['refreshToken']; + + await LocalStorage.setJwtToken(jwtToken); + await LocalStorage.setLoggedInUser(true); + if (refreshToken != null) { await LocalStorage.setRefreshToken(refreshToken); - print("Refresh Token: $refreshToken"); } - // Save the login state in local storage - await LocalStorage.setLoggedInUser(true); Get.put(PermissionController()); - // Return null to indicate success - return null; + + logger.i("JWT Token: $jwtToken"); + if (refreshToken != null) logger.i("Refresh Token: $refreshToken"); + + return null; // Success } else if (response.statusCode == 401) { return {"password": "Invalid email or password"}; } else { - return {"error": "Something went wrong. Please try again."}; + return { + "error": responseData['message'] ?? "Unexpected error occurred" + }; } } catch (e) { + logger.e("Login error: $e"); return {"error": "Network error. Please check your connection."}; } } + + /// Refreshes the JWT token using the refresh token. + static Future refreshToken() async { + final refreshToken = await LocalStorage.getRefreshToken(); + if (refreshToken == null || refreshToken.isEmpty) { + logger.w("No refresh token available."); + return false; + } + + try { + final response = await http.post( + Uri.parse("$_baseUrl/auth/refresh-token"), + headers: {'Content-Type': 'application/json'}, + body: jsonEncode({"refreshToken": refreshToken}), + ); + + final data = jsonDecode(response.body); + if (response.statusCode == 200 && data['success'] == true) { + final newAccessToken = data['data']['accessToken']; + final newRefreshToken = data['data']['refreshToken']; + + // Check if the tokens are valid before saving them + 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']}"); + await LocalStorage.removeToken('jwt_token'); + await LocalStorage.removeToken('refresh_token'); + await LocalStorage.setLoggedInUser(false); + return false; + } + } catch (e) { + logger.e("Exception during token refresh: $e"); + return false; + } + } } diff --git a/lib/helpers/services/permission_service.dart b/lib/helpers/services/permission_service.dart index 545e597..a83798f 100644 --- a/lib/helpers/services/permission_service.dart +++ b/lib/helpers/services/permission_service.dart @@ -1,22 +1,25 @@ -import 'package:http/http.dart' as http; import 'dart:convert'; +import 'package:http/http.dart' as http; +import 'package:get/get.dart'; +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'; +import 'package:marco/helpers/services/storage/local_storage.dart'; +import 'package:marco/helpers/services/auth_service.dart'; + +final Logger logger = Logger(); class PermissionService { - // Cache to store the fetched user profile data per token static final Map> _userDataCache = {}; - // Method to fetch the user profile data (permissions, employee info, and projects) - static Future> fetchAllUserData(String token) async { - // Check if the data for this token is already cached + static Future> fetchAllUserData(String token, {bool hasRetried = false}) async { + // Return from cache if available if (_userDataCache.containsKey(token)) { return _userDataCache[token]!; } try { - // Fetch data from the API final response = await http.get( Uri.parse('https://stageapi.marcoaiot.com/api/user/profile'), headers: {'Authorization': 'Bearer $token'}, @@ -25,44 +28,50 @@ class PermissionService { if (response.statusCode == 200) { final data = json.decode(response.body)['data']; - // Parse and extract relevant information - final permissions = _parsePermissions(data['featurePermissions']); - final employeeInfo = _parseEmployeeInfo(data['employeeInfo']); - final projectsInfo = _parseProjectsInfo(data['projects']); - - // Cache the processed data for later use - final allUserData = { - 'permissions': permissions, - 'employeeInfo': employeeInfo, - 'projects': projectsInfo, + final result = { + 'permissions': _parsePermissions(data['featurePermissions']), + 'employeeInfo': _parseEmployeeInfo(data['employeeInfo']), + 'projects': _parseProjectsInfo(data['projects']), }; - _userDataCache[token] = allUserData; - return allUserData; - } else { - final errorData = json.decode(response.body); - throw Exception('Failed to load data: ${errorData['message']}'); + _userDataCache[token] = result; + return result; } + + if (response.statusCode == 401 && !hasRetried) { + final refreshed = await AuthService.refreshToken(); + if (refreshed) { + final newToken = await LocalStorage.getJwtToken(); + if (newToken != null) { + return fetchAllUserData(newToken, hasRetried: true); + } + } + + await _handleUnauthorized(); + throw Exception('Unauthorized. Token refresh failed.'); + } + + final errorMessage = json.decode(response.body)['message'] ?? 'Unknown error'; + throw Exception('Failed to load data: $errorMessage'); } catch (e) { - print('Error fetching user data: $e'); - throw Exception('Error fetching user data: $e'); + logger.e('Error fetching user data: $e'); // <-- Use logger here + rethrow; } } - // Helper method to parse permissions from raw data - static List _parsePermissions(List featurePermissions) { - return featurePermissions - .map((id) => UserPermission.fromJson({'id': id})) - .toList(); + static Future _handleUnauthorized() async { + await LocalStorage.removeToken('jwt_token'); + await LocalStorage.removeToken('refresh_token'); + await LocalStorage.setLoggedInUser(false); + Get.offAllNamed('/auth/login'); } - // Helper method to parse employee info from raw data - static EmployeeInfo _parseEmployeeInfo(Map employeeData) { - return EmployeeInfo.fromJson(employeeData); - } + static List _parsePermissions(List featurePermissions) => + featurePermissions.map((id) => UserPermission.fromJson({'id': id})).toList(); - // Helper method to parse projects from raw data - static List _parseProjectsInfo(List projectIds) { - return projectIds.map((id) => ProjectInfo.fromJson(id)).toList(); - } + static EmployeeInfo _parseEmployeeInfo(Map employeeData) => + EmployeeInfo.fromJson(employeeData); + + static List _parseProjectsInfo(List projects) => + projects.map((proj) => ProjectInfo.fromJson(proj)).toList(); } diff --git a/lib/main.dart b/lib/main.dart index 99a9be4..9a40f15 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -11,17 +11,23 @@ import 'package:marco/helpers/theme/theme_customizer.dart'; import 'package:marco/routes.dart'; import 'package:provider/provider.dart'; import 'package:url_strategy/url_strategy.dart'; + Future main() async { WidgetsFlutterBinding.ensureInitialized(); setPathUrlStrategy(); - await LocalStorage.init(); - AppStyle.init(); - await ThemeCustomizer.init(); + try { + await LocalStorage.init(); + await ThemeCustomizer.init(); + AppStyle.init(); + } catch (e) { + print('Error during app initialization: $e'); + return; + } + runApp(ChangeNotifierProvider( create: (context) => AppNotifier(), child: MyApp(), - )); } diff --git a/pubspec.yaml b/pubspec.yaml index 663e05d..0386f63 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -63,7 +63,7 @@ dependencies: permission_handler: ^11.3.0 image: ^4.0.17 image_picker: ^1.0.7 - + logger: ^2.0.2 dev_dependencies: flutter_test: sdk: flutter