added loggers

This commit is contained in:
Vaibhav Surve 2025-05-05 16:56:58 +05:30
parent f5afed0d8b
commit 2a91ccc323
7 changed files with 358 additions and 246 deletions

View File

@ -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<AttendanceModel> attendances = [];
List<ProjectModel> projects = [];
@ -24,7 +29,7 @@ class AttendanceController extends GetxController {
List<RegularizationLogModel> regularizationLogs = [];
List<AttendanceLogViewModel> 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<void> 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<void> 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<void> 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<bool> 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<String, List<AttendanceLogModel>> groupLogsByCheckInDate() {
final groupedLogs = <String, List<AttendanceLogModel>>{};
for (var log in attendanceLogs) {
final checkInDate = log.checkIn != null
? DateFormat('dd MMM yyyy').format(log.checkIn!)
: 'Unknown';
Map<String, List<AttendanceLogModel>> groupLogsByCheckInDate() {
final groupedLogs = <String, List<AttendanceLogModel>>{};
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<void> fetchRegularizationLogs(
String? projectId, {
@ -207,34 +224,35 @@ Map<String, List<AttendanceLogModel>> 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<void> 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");
}
}
}

View File

@ -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 = <UserPermission>[].obs;
var employeeInfo = Rxn<EmployeeInfo>();
@ -20,84 +24,99 @@ class PermissionController extends GetxController {
_startAutoRefresh();
}
// Store all data at once to reduce redundant operations
Future<void> _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<void> _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<void> 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<String, dynamic> 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<String?> _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();
}
}

View File

@ -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<String?> _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<http.Response?> _getRequest(String endpoint,
{Map<String, String>? queryParams}) async {
final token = await _getToken();
{Map<String, String>? 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<http.Response?> _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<List<dynamic>?> 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<List<dynamic>?> 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<List<dynamic>?> getAttendanceLogs(
String projectId, {
DateTime? dateFrom,
DateTime? dateTo,
}) async {
static Future<List<dynamic>?> 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<List<dynamic>?> 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<List<dynamic>?> 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<bool> 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<String, dynamic>? 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<bool> 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();

View File

@ -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<String, String> _headers = {
'Content-Type': 'application/json',
};
static bool isLoggedIn = false;
static Future<Map<String, String>?> loginUser(Map<String, dynamic> data) async {
/// Logs in the user and stores tokens if successful.
static Future<Map<String, String>?> loginUser(
Map<String, dynamic> 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<bool> 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;
}
}
}

View File

@ -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<String, Map<String, dynamic>> _userDataCache = {};
// Method to fetch the user profile data (permissions, employee info, and projects)
static Future<Map<String, dynamic>> fetchAllUserData(String token) async {
// Check if the data for this token is already cached
static Future<Map<String, dynamic>> 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<UserPermission> _parsePermissions(List<dynamic> featurePermissions) {
return featurePermissions
.map<UserPermission>((id) => UserPermission.fromJson({'id': id}))
.toList();
static Future<void> _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<String, dynamic> employeeData) {
return EmployeeInfo.fromJson(employeeData);
}
static List<UserPermission> _parsePermissions(List<dynamic> featurePermissions) =>
featurePermissions.map((id) => UserPermission.fromJson({'id': id})).toList();
// Helper method to parse projects from raw data
static List<ProjectInfo> _parseProjectsInfo(List<dynamic> projectIds) {
return projectIds.map<ProjectInfo>((id) => ProjectInfo.fromJson(id)).toList();
}
static EmployeeInfo _parseEmployeeInfo(Map<String, dynamic> employeeData) =>
EmployeeInfo.fromJson(employeeData);
static List<ProjectInfo> _parseProjectsInfo(List<dynamic> projects) =>
projects.map((proj) => ProjectInfo.fromJson(proj)).toList();
}

View File

@ -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<void> 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<AppNotifier>(
create: (context) => AppNotifier(),
child: MyApp(),
));
}

View File

@ -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