feat: Update application ID in build.gradle and refactor imports and methods in service files for improved clarity and organization

This commit is contained in:
Vaibhav Surve 2025-06-13 10:51:23 +05:30
parent 658f3f26e0
commit 0ad8847b94
4 changed files with 217 additions and 458 deletions

View File

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

View File

@ -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<String?> _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<http.Response?> _getRequest(String endpoint,
{Map<String, String>? queryParams, bool hasRetried = false}) async {
static Future<http.Response?> _getRequest(
String endpoint, {
Map<String, String>? 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<List<dynamic>?> getProjects() async {
final response = await _getRequest(ApiEndpoints.getProjects);
return response != null
? _parseResponse(response, label: 'Projects')
: null;
}
static Future<List<dynamic>?> getGlobalProjects() async {
final response = await _getRequest(ApiEndpoints.getProjects);
return response != null
? _parseResponse(response, label: 'Global Projects')
: null;
}
static Future<List<dynamic>?> getEmployeesByProject(String projectId) async {
final response = await _getRequest(ApiEndpoints.getEmployeesByProject,
queryParams: {"projectId": projectId});
return response != null
? _parseResponse(response, label: 'Employees')
: null;
}
static Future<List<dynamic>?> getProjects() async =>
_getRequest(ApiEndpoints.getProjects).then((res) => res != null ? _parseResponse(res, label: 'Projects') : null);
static Future<List<dynamic>?> getAttendanceLogs(String projectId,
{DateTime? dateFrom, DateTime? dateTo}) async {
static Future<List<dynamic>?> getGlobalProjects() async =>
_getRequest(ApiEndpoints.getProjects).then((res) => res != null ? _parseResponse(res, label: 'Global Projects') : null);
static Future<List<dynamic>?> getEmployeesByProject(String projectId) async =>
_getRequest(ApiEndpoints.getEmployeesByProject, queryParams: {"projectId": projectId})
.then((res) => res != null ? _parseResponse(res, label: 'Employees') : null);
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),
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<List<dynamic>?> getAttendanceLogView(String id) async {
final response =
await _getRequest("${ApiEndpoints.getAttendanceLogView}/$id");
return response != null
? _parseResponse(response, label: 'Log Details')
: null;
}
static Future<List<dynamic>?> getAttendanceLogView(String id) async =>
_getRequest("${ApiEndpoints.getAttendanceLogView}/$id")
.then((res) => res != null ? _parseResponse(res, label: 'Log Details') : null);
static Future<List<dynamic>?> 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<List<dynamic>?> getRegularizationLogs(String projectId) async =>
_getRequest(ApiEndpoints.getRegularizationLogs, queryParams: {"projectId": projectId})
.then((res) => res != null ? _parseResponse(res, label: 'Regularization Logs') : null);
static Future<bool> 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<List<dynamic>?> getAllEmployeesByProject(
String projectId) async {
if (projectId.isEmpty) {
throw ArgumentError('projectId must not be empty');
// === Employee APIs ===
static Future<List<dynamic>?> 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);
}
final String endpoint =
"${ApiEndpoints.getAllEmployeesByProject}/$projectId";
final response = await _getRequest(endpoint);
static Future<List<dynamic>?> getAllEmployees() async =>
_getRequest(ApiEndpoints.getAllEmployees).then((res) => res != null ? _parseResponse(res, label: 'All Employees') : null);
return response != null
? _parseResponse(response, label: 'Employees by Project')
: null;
}
static Future<List<dynamic>?> getAllEmployees() async {
final response = await _getRequest(ApiEndpoints.getAllEmployees);
return response != null
? _parseResponse(response, label: 'All Employees')
: null;
}
static Future<List<dynamic>?> getRoles() async {
final response = await _getRequest(ApiEndpoints.getRoles);
return response != null
? _parseResponse(response, label: 'All Employees')
: null;
}
static Future<List<dynamic>?> getRoles() async =>
_getRequest(ApiEndpoints.getRoles).then((res) => res != null ? _parseResponse(res, label: 'Roles') : null);
static Future<bool> 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<Map<String, dynamic>?> getEmployeeDetails(
String employeeId) async {
static Future<Map<String, dynamic>?> 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<String, dynamic>) {
return data;
}
return null;
final data = response != null ? _parseResponse(response, label: 'Employee Details') : null;
return data is Map<String, dynamic> ? data : null;
}
// ===== Daily Tasks API Calls =====
static Future<List<dynamic>?> getDailyTasks(String projectId,
{DateTime? dateFrom, DateTime? dateTo}) async {
// === Daily Task APIs ===
static Future<List<dynamic>?> 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<bool> 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 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;
} else {
}
_log("Failed to report task: ${json['message'] ?? 'Unknown error'}");
}
} catch (e) {
_log("HTTP POST Exception (reportTask): $e");
}
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 response = await _postRequest(ApiEndpoints.commentTask, body);
if (response == null) return false;
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 response.statusCode == 200 && json['success'] == true;
}
return false;
}
// Daily Task Planing //
static Future<Map<String, dynamic>?> getDailyTasksDetails(
String projectId) async {
static Future<Map<String, dynamic>?> 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<String, dynamic>?
? _parseResponseForAllData(response, label: 'Daily Task Details') as Map<String, dynamic>?
: 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'}");
}
_log("Failed to assign daily task: ${json['message'] ?? 'Unknown error'}");
return false;
}
}
}

View File

@ -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<String, String> _headers = {
'Content-Type': 'application/json',
};
static bool isLoggedIn = false;
static Future<Map<String, String>?> loginUser(
Map<String, dynamic> data) async {
/// Login with email and password
static Future<Map<String, String>?> loginUser(Map<String, dynamic> 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<bool> 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<Map<String, String>?> 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<Map<String, String>?> 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<Map<String, String>?> requestDemo(
Map<String, dynamic> demoData) async {
/// Request demo
static Future<Map<String, String>?> requestDemo(Map<String, dynamic> 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<List<Map<String, dynamic>>?> 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<Map<String, dynamic>>
final List<dynamic> industriesData = responseData['data'];
return industriesData.cast<Map<String, dynamic>>();
} 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<Map<String, dynamic>>.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<Map<String, String>?> 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<Map<String, String>?> 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<Map<String, String>?> 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<Map<String, String>?> 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<void> _handleLoginSuccess(Map<String, dynamic> 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<ProjectController>().fetchProjects();
isLoggedIn = true;
logger.i("Login success initialized.");
}
}

View File

@ -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<String, Map<String, dynamic>> _userDataCache = {};
static Future<Map<String, dynamic>> fetchAllUserData(String token,
{bool hasRetried = false}) async {
// Return from cache if available
/// Fetches all user-related data (permissions, employee info, projects)
static Future<Map<String, dynamic>> 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<void> _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<UserPermission> _parsePermissions(
List<dynamic> featurePermissions) =>
featurePermissions
/// Converts raw permission data into list of `UserPermission`
static List<UserPermission> _parsePermissions(List<dynamic> permissions) {
return permissions
.map((id) => UserPermission.fromJson({'id': id}))
.toList();
}
static EmployeeInfo _parseEmployeeInfo(Map<String, dynamic> employeeData) =>
EmployeeInfo.fromJson(employeeData);
/// Converts raw employee JSON into `EmployeeInfo`
static EmployeeInfo _parseEmployeeInfo(Map<String, dynamic> data) {
return EmployeeInfo.fromJson(data);
}
static List<ProjectInfo> _parseProjectsInfo(List<dynamic> projects) =>
projects.map((proj) => ProjectInfo.fromJson(proj)).toList();
/// Converts raw projects JSON into list of `ProjectInfo`
static List<ProjectInfo> _parseProjectsInfo(List<dynamic> projects) {
return projects.map((proj) => ProjectInfo.fromJson(proj)).toList();
}
}