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 { defaultConfig {
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). // 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. // You can update the following values to match your application needs.
// For more information, see: https://flutter.dev/to/review-gradle-config. // For more information, see: https://flutter.dev/to/review-gradle-config.
minSdk = flutter.minSdkVersion minSdk = flutter.minSdkVersion

View File

@ -1,12 +1,12 @@
import 'dart:convert'; import 'dart:convert';
import 'package:get/get.dart';
import 'package:http/http.dart' as http; import 'package:http/http.dart' as http;
import 'package:image_picker/image_picker.dart'; import 'package:image_picker/image_picker.dart';
import 'package:intl/intl.dart'; import 'package:intl/intl.dart';
import 'package:logger/logger.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/auth_service.dart';
import 'package:marco/helpers/services/api_endpoints.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(); final Logger logger = Logger();
@ -14,13 +14,11 @@ class ApiService {
static const Duration timeout = Duration(seconds: 10); static const Duration timeout = Duration(seconds: 10);
static const bool enableLogs = true; static const bool enableLogs = true;
// ===== Helpers ===== // === Helpers ===
static Future<String?> _getToken() async { static Future<String?> _getToken() async {
final token = await LocalStorage.getJwtToken(); final token = await LocalStorage.getJwtToken();
if (token == null && enableLogs) { if (token == null && enableLogs) logger.w("No JWT token found.");
logger.w("No JWT token found. Please log in.");
}
return token; return token;
} }
@ -47,13 +45,12 @@ class ApiService {
return null; return null;
} }
static dynamic _parseResponseForAllData(http.Response response, static dynamic _parseResponseForAllData(http.Response response, {String label = ''}) {
{String label = ''}) {
_log("$label Response: ${response.body}"); _log("$label Response: ${response.body}");
try { try {
final json = jsonDecode(response.body); final json = jsonDecode(response.body);
if (response.statusCode == 200 && json['success'] == true) { 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'}"); _log("API Error [$label]: ${json['message'] ?? 'Unknown error'}");
} catch (e) { } catch (e) {
@ -62,28 +59,23 @@ class ApiService {
return null; return null;
} }
static Future<http.Response?> _getRequest(String endpoint, static Future<http.Response?> _getRequest(
{Map<String, String>? queryParams, bool hasRetried = false}) async { String endpoint, {
Map<String, String>? queryParams,
bool hasRetried = false,
}) async {
String? token = await _getToken(); String? token = await _getToken();
if (token == null) return null; if (token == null) return null;
Uri uri = Uri.parse("${ApiEndpoints.baseUrl}$endpoint") final uri = Uri.parse("${ApiEndpoints.baseUrl}$endpoint").replace(queryParameters: queryParams);
.replace(queryParameters: queryParams);
_log("GET $uri"); _log("GET $uri");
try { try {
http.Response response = final response = await http.get(uri, headers: _headers(token)).timeout(timeout);
await http.get(uri, headers: _headers(token)).timeout(timeout);
if (response.statusCode == 401 && !hasRetried) { if (response.statusCode == 401 && !hasRetried) {
_log("Unauthorized. Attempting token refresh..."); _log("Unauthorized. Attempting token refresh...");
bool refreshed = await AuthService.refreshToken(); if (await AuthService.refreshToken()) {
if (refreshed) { return await _getRequest(endpoint, queryParams: queryParams, hasRetried: true);
token = await _getToken();
if (token != null) {
return await _getRequest(endpoint,
queryParams: queryParams, hasRetried: true);
}
} }
_log("Token refresh failed."); _log("Token refresh failed.");
} }
@ -98,22 +90,25 @@ class ApiService {
String endpoint, String endpoint,
dynamic body, { dynamic body, {
Duration customTimeout = timeout, Duration customTimeout = timeout,
bool hasRetried = false,
}) async { }) async {
String? token = await _getToken(); String? token = await _getToken();
if (token == null) return null; if (token == null) return null;
final uri = Uri.parse("${ApiEndpoints.baseUrl}$endpoint"); final uri = Uri.parse("${ApiEndpoints.baseUrl}$endpoint");
_log("POST $uri\nHeaders: ${_headers(token)}\nBody: $body");
_log("POST $uri");
_log("Headers: ${_headers(token)}");
_log("Body: $body");
try { try {
final response = await http final response = await http
.post(uri, headers: _headers(token), body: jsonEncode(body)) .post(uri, headers: _headers(token), body: jsonEncode(body))
.timeout(customTimeout); .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; return response;
} catch (e) { } catch (e) {
_log("HTTP POST Exception: $e"); _log("HTTP POST Exception: $e");
@ -121,61 +116,39 @@ class ApiService {
} }
} }
// ===== Attendence Screen API Calls ===== // === Attendance APIs ===
static Future<List<dynamic>?> getProjects() async { static Future<List<dynamic>?> getProjects() async =>
final response = await _getRequest(ApiEndpoints.getProjects); _getRequest(ApiEndpoints.getProjects).then((res) => res != null ? _parseResponse(res, label: 'Projects') : null);
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>?> getAttendanceLogs(String projectId, static Future<List<dynamic>?> getGlobalProjects() async =>
{DateTime? dateFrom, DateTime? dateTo}) 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 = { final query = {
"projectId": projectId, "projectId": projectId,
if (dateFrom != null) if (dateFrom != null) "dateFrom": DateFormat('yyyy-MM-dd').format(dateFrom),
"dateFrom": DateFormat('yyyy-MM-dd').format(dateFrom),
if (dateTo != null) "dateTo": DateFormat('yyyy-MM-dd').format(dateTo), if (dateTo != null) "dateTo": DateFormat('yyyy-MM-dd').format(dateTo),
}; };
return _getRequest(ApiEndpoints.getAttendanceLogs, queryParams: query)
final response = .then((res) => res != null ? _parseResponse(res, label: 'Attendance Logs') : null);
await _getRequest(ApiEndpoints.getAttendanceLogs, queryParams: query);
return response != null
? _parseResponse(response, label: 'Attendance Logs')
: null;
} }
static Future<List<dynamic>?> getAttendanceLogView(String id) async { static Future<List<dynamic>?> getAttendanceLogView(String id) async =>
final response = _getRequest("${ApiEndpoints.getAttendanceLogView}/$id")
await _getRequest("${ApiEndpoints.getAttendanceLogView}/$id"); .then((res) => res != null ? _parseResponse(res, label: 'Log Details') : null);
return response != null
? _parseResponse(response, label: 'Log Details')
: null;
}
static Future<List<dynamic>?> getRegularizationLogs(String projectId) async { static Future<List<dynamic>?> getRegularizationLogs(String projectId) async =>
final response = await _getRequest(ApiEndpoints.getRegularizationLogs, _getRequest(ApiEndpoints.getRegularizationLogs, queryParams: {"projectId": projectId})
queryParams: {"projectId": projectId}); .then((res) => res != null ? _parseResponse(res, label: 'Regularization Logs') : null);
return response != null
? _parseResponse(response, label: 'Regularization Logs')
: null;
}
// ===== Upload Attendance Image =====
static Future<bool> uploadAttendanceImage( static Future<bool> uploadAttendanceImage(
String id, String id,
@ -188,7 +161,7 @@ class ApiService {
String comment = "", String comment = "",
required int action, required int action,
bool imageCapture = true, bool imageCapture = true,
String? markTime, // <-- Optional markTime parameter String? markTime,
}) async { }) async {
final now = DateTime.now(); final now = DateTime.now();
final body = { final body = {
@ -206,16 +179,14 @@ class ApiService {
if (imageCapture && imageFile != null) { if (imageCapture && imageFile != null) {
try { try {
final bytes = await imageFile.readAsBytes(); final bytes = await imageFile.readAsBytes();
final base64Image = base64Encode(bytes);
final fileSize = await imageFile.length(); final fileSize = await imageFile.length();
final contentType = "image/${imageFile.path.split('.').last}"; final contentType = "image/${imageFile.path.split('.').last}";
body["image"] = { body["image"] = {
"fileName": imageName, "fileName": imageName,
"contentType": contentType, "contentType": contentType,
"fileSize": fileSize, "fileSize": fileSize,
"description": "Employee attendance photo", "description": "Employee attendance photo",
"base64Data": base64Image, "base64Data": base64Encode(bytes),
}; };
} catch (e) { } catch (e) {
_log("Image encoding error: $e"); _log("Image encoding error: $e");
@ -223,22 +194,16 @@ class ApiService {
} }
} }
final response = final response = await _postRequest(ApiEndpoints.uploadAttendanceImage, body);
await _postRequest(ApiEndpoints.uploadAttendanceImage, body);
if (response == null) return false; if (response == null) return false;
final json = jsonDecode(response.body); final json = jsonDecode(response.body);
if (response.statusCode == 200 && json['success'] == true) { if (response.statusCode == 200 && json['success'] == true) return true;
return true;
} else {
_log("Failed to upload image: ${json['message'] ?? 'Unknown error'}");
}
_log("Failed to upload image: ${json['message'] ?? 'Unknown error'}");
return false; return false;
} }
// ===== Utilities =====
static String generateImageName(String employeeId, int count) { static String generateImageName(String employeeId, int count) {
final now = DateTime.now(); final now = DateTime.now();
final dateStr = DateFormat('yyyyMMdd_HHmmss').format(now); final dateStr = DateFormat('yyyyMMdd_HHmmss').format(now);
@ -246,35 +211,19 @@ class ApiService {
return "${employeeId}_${dateStr}_$imageNumber.jpg"; return "${employeeId}_${dateStr}_$imageNumber.jpg";
} }
// ===== Employee Screen API Calls ===== // === Employee APIs ===
static Future<List<dynamic>?> getAllEmployeesByProject(
String projectId) async {
if (projectId.isEmpty) {
throw ArgumentError('projectId must not be empty');
}
final String endpoint = static Future<List<dynamic>?> getAllEmployeesByProject(String projectId) async {
"${ApiEndpoints.getAllEmployeesByProject}/$projectId"; if (projectId.isEmpty) throw ArgumentError('projectId must not be empty');
final response = await _getRequest(endpoint); final endpoint = "${ApiEndpoints.getAllEmployeesByProject}/$projectId";
return _getRequest(endpoint).then((res) => res != null ? _parseResponse(res, label: 'Employees by Project') : null);
return response != null
? _parseResponse(response, label: 'Employees by Project')
: null;
} }
static Future<List<dynamic>?> getAllEmployees() async { static Future<List<dynamic>?> getAllEmployees() async =>
final response = await _getRequest(ApiEndpoints.getAllEmployees); _getRequest(ApiEndpoints.getAllEmployees).then((res) => res != null ? _parseResponse(res, label: 'All Employees') : null);
return response != null
? _parseResponse(response, label: 'All Employees')
: null;
}
static Future<List<dynamic>?> getRoles() async { static Future<List<dynamic>?> getRoles() async =>
final response = await _getRequest(ApiEndpoints.getRoles); _getRequest(ApiEndpoints.getRoles).then((res) => res != null ? _parseResponse(res, label: 'Roles') : null);
return response != null
? _parseResponse(response, label: 'All Employees')
: null;
}
static Future<bool> createEmployee({ static Future<bool> createEmployee({
required String firstName, required String firstName,
@ -290,61 +239,33 @@ class ApiService {
"gender": gender, "gender": gender,
"jobRoleId": jobRoleId, "jobRoleId": jobRoleId,
}; };
// Make the API request
final response = await _postRequest(ApiEndpoints.createEmployee, body); final response = await _postRequest(ApiEndpoints.createEmployee, body);
if (response == null) return false;
if (response == null) {
_log("Error: No response from server.");
return false;
}
final json = jsonDecode(response.body); final json = jsonDecode(response.body);
return response.statusCode == 200 && json['success'] == true;
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;
}
} }
static Future<Map<String, dynamic>?> getEmployeeDetails( static Future<Map<String, dynamic>?> getEmployeeDetails(String employeeId) async {
String employeeId) async {
final url = "${ApiEndpoints.getEmployeeInfo}/$employeeId"; final url = "${ApiEndpoints.getEmployeeInfo}/$employeeId";
final response = await _getRequest(url); final response = await _getRequest(url);
final data = response != null final data = response != null ? _parseResponse(response, label: 'Employee Details') : null;
? _parseResponse(response, label: 'Employee Details') return data is Map<String, dynamic> ? data : null;
: null;
if (data is Map<String, dynamic>) {
return data;
}
return null;
} }
// ===== Daily Tasks API Calls ===== // === Daily Task APIs ===
static Future<List<dynamic>?> getDailyTasks(String projectId,
{DateTime? dateFrom, DateTime? dateTo}) async { static Future<List<dynamic>?> getDailyTasks(
String projectId, {
DateTime? dateFrom,
DateTime? dateTo,
}) async {
final query = { final query = {
"projectId": projectId, "projectId": projectId,
if (dateFrom != null) if (dateFrom != null) "dateFrom": DateFormat('yyyy-MM-dd').format(dateFrom),
"dateFrom": DateFormat('yyyy-MM-dd').format(dateFrom),
if (dateTo != null) "dateTo": DateFormat('yyyy-MM-dd').format(dateTo), if (dateTo != null) "dateTo": DateFormat('yyyy-MM-dd').format(dateTo),
}; };
return _getRequest(ApiEndpoints.getDailyTask, queryParams: query)
final response = .then((res) => res != null ? _parseResponse(res, label: 'Daily Tasks') : null);
await _getRequest(ApiEndpoints.getDailyTask, queryParams: query);
return response != null
? _parseResponse(response, label: 'Daily Tasks')
: null;
} }
static Future<bool> reportTask({ static Future<bool> reportTask({
@ -363,34 +284,14 @@ class ApiService {
if (images != null && images.isNotEmpty) "images": images, if (images != null && images.isNotEmpty) "images": images,
}; };
String? token = await _getToken(); final response = await _postRequest(ApiEndpoints.reportTask, body);
if (token == null) return false; if (response == null) return false;
final json = jsonDecode(response.body);
final uri = Uri.parse("${ApiEndpoints.baseUrl}${ApiEndpoints.reportTask}"); if (response.statusCode == 200 && json['success'] == true) {
Get.back();
_log("POST $uri"); return true;
_log("Headers: ${_headers(token)}");
_log("Body: $body");
try {
final response = await http
.post(uri, headers: _headers(token), body: jsonEncode(body))
.timeout(const Duration(seconds: 30));
_log("Response Status: ${response.statusCode}");
final json = jsonDecode(response.body);
if (response.statusCode == 200 && json['success'] == true) {
Get.back();
return true;
} else {
_log("Failed to report task: ${json['message'] ?? 'Unknown error'}");
}
} catch (e) {
_log("HTTP POST Exception (reportTask): $e");
} }
_log("Failed to report task: ${json['message'] ?? 'Unknown error'}");
return false; return false;
} }
@ -406,45 +307,17 @@ class ApiService {
if (images != null && images.isNotEmpty) "images": images, if (images != null && images.isNotEmpty) "images": images,
}; };
String? token = await _getToken(); final response = await _postRequest(ApiEndpoints.commentTask, body);
if (token == null) return false; if (response == null) return false;
final json = jsonDecode(response.body);
final uri = Uri.parse("${ApiEndpoints.baseUrl}${ApiEndpoints.commentTask}"); return response.statusCode == 200 && json['success'] == true;
_log("POST $uri");
_log("Headers: ${_headers(token)}");
_log("Body: $body");
try {
final response = await http
.post(uri, headers: _headers(token), body: jsonEncode(body))
.timeout(const Duration(seconds: 30));
_log("Response Status: ${response.statusCode}");
final json = jsonDecode(response.body);
if (response.statusCode == 200 && json['success'] == true) {
return true;
} else {
_log("Failed to comment task: ${json['message'] ?? 'Unknown error'}");
}
} catch (e) {
_log("HTTP POST Exception (commentTask): $e");
}
return false;
} }
// 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 url = "${ApiEndpoints.dailyTaskDetails}/$projectId";
final response = await _getRequest(url); final response = await _getRequest(url);
return response != null return response != null
? _parseResponseForAllData(response, label: 'Daily Task Details') ? _parseResponseForAllData(response, label: 'Daily Task Details') as Map<String, dynamic>?
as Map<String, dynamic>?
: null; : null;
} }
@ -460,26 +333,16 @@ class ApiService {
"plannedTask": plannedTask, "plannedTask": plannedTask,
"description": description, "description": description,
"taskTeam": taskTeam, "taskTeam": taskTeam,
"assignmentDate": "assignmentDate": (assignmentDate ?? DateTime.now()).toUtc().toIso8601String(),
(assignmentDate ?? DateTime.now()).toUtc().toIso8601String(),
}; };
final response = await _postRequest(ApiEndpoints.assignDailyTask, body); final response = await _postRequest(ApiEndpoints.assignDailyTask, body);
if (response == null) return false;
if (response == null) {
_log("Error: No response from server.");
return false;
}
final json = jsonDecode(response.body); final json = jsonDecode(response.body);
if (response.statusCode == 200 && json['success'] == true) { if (response.statusCode == 200 && json['success'] == true) {
Get.back(); Get.back();
return true; return true;
} else {
_log(
"Failed to assign daily task: ${json['message'] ?? 'Unknown error'}");
return false;
} }
_log("Failed to assign daily task: ${json['message'] ?? 'Unknown error'}");
return false;
} }
} }

View File

@ -1,11 +1,11 @@
import 'dart:convert'; import 'dart:convert';
import 'package:http/http.dart' as http;
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:marco/helpers/services/storage/local_storage.dart'; import 'package:http/http.dart' as http;
import 'package:marco/controller/permission_controller.dart';
import 'package:logger/logger.dart'; 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/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(); final Logger logger = Logger();
@ -14,9 +14,11 @@ class AuthService {
static const Map<String, String> _headers = { static const Map<String, String> _headers = {
'Content-Type': 'application/json', 'Content-Type': 'application/json',
}; };
static bool isLoggedIn = false; 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 { try {
final response = await http.post( final response = await http.post(
Uri.parse("$_baseUrl/auth/login-mobile"), Uri.parse("$_baseUrl/auth/login-mobile"),
@ -31,9 +33,7 @@ class AuthService {
} else if (response.statusCode == 401) { } else if (response.statusCode == 401) {
return {"password": "Invalid email or password"}; return {"password": "Invalid email or password"};
} else { } else {
return { return {"error": responseData['message'] ?? "Unexpected error occurred"};
"error": responseData['message'] ?? "Unexpected error occurred"
};
} }
} catch (e) { } catch (e) {
logger.e("Login error: $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 { static Future<bool> refreshToken() async {
final accessToken = await LocalStorage.getJwtToken(); final accessToken = await LocalStorage.getJwtToken();
final refreshToken = await LocalStorage.getRefreshToken(); final refreshToken = await LocalStorage.getRefreshToken();
if (accessToken == null || if (accessToken == null || refreshToken == null || accessToken.isEmpty || refreshToken.isEmpty) {
refreshToken == null || logger.w("Missing access/refresh token.");
accessToken.isEmpty ||
refreshToken.isEmpty) {
logger.w("Missing token or refresh token for refresh.");
return false; return false;
} }
@ -59,82 +56,50 @@ class AuthService {
"refreshToken": refreshToken, "refreshToken": refreshToken,
}; };
logger.i("Sending refresh token request with body: $requestBody");
try { try {
final response = await http.post( final response = await http.post(
Uri.parse("$_baseUrl/auth/refresh-token"), 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, headers: _headers,
body: jsonEncode(requestBody), body: jsonEncode(requestBody),
); );
logger.i( final data = jsonDecode(response.body);
"Forgot password API response (${response.statusCode}): ${response.body}"); if (response.statusCode == 200 && data['success'] == true) {
await LocalStorage.setJwtToken(data['data']['token']);
final responseData = jsonDecode(response.body); await LocalStorage.setRefreshToken(data['data']['refreshToken']);
await LocalStorage.setLoggedInUser(true);
if (response.statusCode == 200 && responseData['success'] == true) { logger.i("Token refreshed.");
logger.i("Forgot password request successful."); return true;
return null;
} else { } else {
return { logger.w("Refresh token failed: ${data['message']}");
"error": return false;
responseData['message'] ?? "Failed to send password reset link."
};
} }
} catch (e) { } 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."}; return {"error": "Network error. Please check your connection."};
} }
} }
// Request demo API /// Request demo
static Future<Map<String, String>?> requestDemo( static Future<Map<String, String>?> requestDemo(Map<String, dynamic> demoData) async {
Map<String, dynamic> demoData) async {
try { try {
final response = await http.post( final response = await http.post(
Uri.parse("$_baseUrl/market/inquiry"), Uri.parse("$_baseUrl/market/inquiry"),
@ -142,22 +107,16 @@ class AuthService {
body: jsonEncode(demoData), body: jsonEncode(demoData),
); );
final responseData = jsonDecode(response.body); final data = jsonDecode(response.body);
if (response.statusCode == 200 && data['success'] == true) return null;
if (response.statusCode == 200 && responseData['success'] == true) { return {"error": data['message'] ?? "Failed to submit demo request."};
logger.i("Request Demo submitted successfully.");
return null;
} else {
return {
"error": responseData['message'] ?? "Failed to submit demo request."
};
}
} catch (e) { } catch (e) {
logger.e("Exception during request demo: $e"); logger.e("Request demo error: $e");
return {"error": "Network error. Please check your connection."}; return {"error": "Network error. Please check your connection."};
} }
} }
/// Get list of industries
static Future<List<Map<String, dynamic>>?> getIndustries() async { static Future<List<Map<String, dynamic>>?> getIndustries() async {
try { try {
final response = await http.get( final response = await http.get(
@ -165,201 +124,129 @@ class AuthService {
headers: _headers, headers: _headers,
); );
logger.i( final data = jsonDecode(response.body);
"Get Industries API response (${response.statusCode}): ${response.body}"); if (response.statusCode == 200 && data['success'] == true) {
return List<Map<String, dynamic>>.from(data['data']);
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;
} }
return null;
} catch (e) { } catch (e) {
logger.e("Exception during getIndustries: $e"); logger.e("Get industries error: $e");
return null; return null;
} }
} }
/// Generates a new MPIN for the user. /// Generate MPIN
static Future<Map<String, String>?> generateMpin({ static Future<Map<String, String>?> generateMpin({
required String employeeId, required String employeeId,
required String mpin, required String mpin,
}) async { }) async {
final jwtToken = await LocalStorage.getJwtToken(); final token = await LocalStorage.getJwtToken();
final requestBody = {
"employeeId": employeeId,
"mpin": mpin,
};
logger.i("Sending MPIN generation request: $requestBody");
try { try {
final response = await http.post( final response = await http.post(
Uri.parse("$_baseUrl/auth/generate-mpin"), Uri.parse("$_baseUrl/auth/generate-mpin"),
headers: { headers: {
'Content-Type': 'application/json', ..._headers,
if (jwtToken != null && jwtToken.isNotEmpty) if (token != null && token.isNotEmpty) 'Authorization': 'Bearer $token',
'Authorization': 'Bearer $jwtToken',
}, },
body: jsonEncode(requestBody), body: jsonEncode({"employeeId": employeeId, "mpin": mpin}),
); );
logger.i( final data = jsonDecode(response.body);
"Generate MPIN API response (${response.statusCode}): ${response.body}"); if (response.statusCode == 200 && data['success'] == true) return null;
return {"error": data['message'] ?? "Failed to generate MPIN."};
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."};
}
} catch (e) { } catch (e) {
logger.e("Exception during generate MPIN: $e"); logger.e("Generate MPIN error: $e");
return {"error": "Network error. Please check your connection."}; return {"error": "Network error. Please check your connection."};
} }
} }
/// Verify MPIN
static Future<Map<String, String>?> verifyMpin({ static Future<Map<String, String>?> verifyMpin({
required String mpin, required String mpin,
required String mpinToken, required String mpinToken,
}) async { }) async {
// Get employee info from local storage
final employeeInfo = LocalStorage.getEmployeeInfo(); final employeeInfo = LocalStorage.getEmployeeInfo();
if (employeeInfo == null) return {"error": "Employee info not found."};
if (employeeInfo == null) { final token = await LocalStorage.getJwtToken();
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");
try { try {
final response = await http.post( final response = await http.post(
Uri.parse("$_baseUrl/auth/login-mpin"), Uri.parse("$_baseUrl/auth/login-mpin"),
headers: { headers: {
'Content-Type': 'application/json', ..._headers,
if (jwtToken != null && jwtToken.isNotEmpty) if (token != null && token.isNotEmpty) 'Authorization': 'Bearer $token',
'Authorization': 'Bearer $jwtToken',
}, },
body: jsonEncode(requestBody), body: jsonEncode({
"employeeId": employeeInfo.id,
"mpin": mpin,
"mpinToken": mpinToken,
}),
); );
logger.i( final data = jsonDecode(response.body);
"Verify MPIN API response (${response.statusCode}): ${response.body}"); if (response.statusCode == 200 && data['success'] == true) return null;
return {"error": data['message'] ?? "MPIN verification failed."};
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."};
}
} catch (e) { } catch (e) {
logger.e("Exception during verify MPIN: $e"); logger.e("Verify MPIN error: $e");
return {"error": "Network error. Please check your connection."}; return {"error": "Network error. Please check your connection."};
} }
} }
// Generate OTP API /// Generate OTP
static Future<Map<String, String>?> generateOtp(String email) async { static Future<Map<String, String>?> generateOtp(String email) async {
final requestBody = {"email": email};
logger.i("Sending generate OTP request: $requestBody");
try { try {
final response = await http.post( final response = await http.post(
Uri.parse("$_baseUrl/auth/send-otp"), Uri.parse("$_baseUrl/auth/send-otp"),
headers: _headers, headers: _headers,
body: jsonEncode(requestBody), body: jsonEncode({"email": email}),
); );
logger.i( final data = jsonDecode(response.body);
"Generate OTP API response (${response.statusCode}): ${response.body}"); if (response.statusCode == 200 && data['success'] == true) return null;
return {"error": data['message'] ?? "Failed to generate OTP."};
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."};
}
} catch (e) { } catch (e) {
logger.e("Exception during generate OTP: $e"); logger.e("Generate OTP error: $e");
return {"error": "Network error. Please check your connection."}; return {"error": "Network error. Please check your connection."};
} }
} }
// Verify OTP API /// Verify OTP and login
static Future<Map<String, String>?> verifyOtp({ static Future<Map<String, String>?> verifyOtp({
required String email, required String email,
required String otp, required String otp,
}) async { }) async {
final requestBody = {
"email": email,
"otp": otp,
};
logger.i("Sending verify OTP request: $requestBody");
try { try {
final response = await http.post( final response = await http.post(
Uri.parse("$_baseUrl/auth/login-otp"), Uri.parse("$_baseUrl/auth/login-otp"),
headers: _headers, headers: _headers,
body: jsonEncode(requestBody), body: jsonEncode({"email": email, "otp": otp}),
); );
logger.i( final data = jsonDecode(response.body);
"Verify OTP API response (${response.statusCode}): ${response.body}"); if (response.statusCode == 200 && data['data'] != null) {
await _handleLoginSuccess(data['data']);
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.");
return null; return null;
} else {
return {"error": responseData['message'] ?? "Failed to verify OTP."};
} }
return {"error": data['message'] ?? "OTP verification failed."};
} catch (e) { } catch (e) {
logger.e("Exception during verify OTP: $e"); logger.e("Verify OTP error: $e");
return {"error": "Network error. Please check your connection."}; return {"error": "Network error. Please check your connection."};
} }
} }
/// Handle login success flow
static Future<void> _handleLoginSuccess(Map<String, dynamic> data) async { static Future<void> _handleLoginSuccess(Map<String, dynamic> data) async {
final jwtToken = data['token']; final jwtToken = data['token'];
final refreshToken = data['refreshToken']; final refreshToken = data['refreshToken'];
final mpinToken = data['mpinToken']; 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.setJwtToken(jwtToken);
await LocalStorage.setLoggedInUser(true); await LocalStorage.setLoggedInUser(true);
if (refreshToken != null) { if (refreshToken != null) await LocalStorage.setRefreshToken(refreshToken);
await LocalStorage.setRefreshToken(refreshToken);
}
if (mpinToken != null && mpinToken.isNotEmpty) { if (mpinToken != null && mpinToken.isNotEmpty) {
await LocalStorage.setMpinToken(mpinToken); await LocalStorage.setMpinToken(mpinToken);
await LocalStorage.setIsMpin(true); await LocalStorage.setIsMpin(true);
@ -368,11 +255,11 @@ class AuthService {
await LocalStorage.removeMpinToken(); await LocalStorage.removeMpinToken();
} }
// Put and load PermissionController
final permissionController = Get.put(PermissionController()); final permissionController = Get.put(PermissionController());
await permissionController.loadData(jwtToken); await permissionController.loadData(jwtToken);
await Get.find<ProjectController>().fetchProjects(); await Get.find<ProjectController>().fetchProjects();
isLoggedIn = true; isLoggedIn = true;
logger.i("Login success initialized.");
} }
} }

View File

@ -1,7 +1,8 @@
import 'dart:convert'; import 'dart:convert';
import 'package:http/http.dart' as http;
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:http/http.dart' as http;
import 'package:logger/logger.dart'; import 'package:logger/logger.dart';
import 'package:marco/model/user_permission.dart'; import 'package:marco/model/user_permission.dart';
import 'package:marco/model/employee_info.dart'; import 'package:marco/model/employee_info.dart';
import 'package:marco/model/projects_model.dart'; import 'package:marco/model/projects_model.dart';
@ -13,19 +14,21 @@ final Logger logger = Logger();
class PermissionService { class PermissionService {
static final Map<String, Map<String, dynamic>> _userDataCache = {}; static final Map<String, Map<String, dynamic>> _userDataCache = {};
static Future<Map<String, dynamic>> fetchAllUserData(String token, /// Fetches all user-related data (permissions, employee info, projects)
{bool hasRetried = false}) async { static Future<Map<String, dynamic>> fetchAllUserData(
// Return from cache if available String token, {
bool hasRetried = false,
}) async {
// Return cached data if already available
if (_userDataCache.containsKey(token)) { if (_userDataCache.containsKey(token)) {
return _userDataCache[token]!; return _userDataCache[token]!;
} }
final uri = Uri.parse('https://stageapi.marcoaiot.com/api/user/profile');
final headers = {'Authorization': 'Bearer $token'};
try { try {
final response = await http.get( final response = await http.get(uri, headers: headers);
Uri.parse('https://stageapi.marcoaiot.com/api/user/profile'),
// Uri.parse('https://api.marcoaiot.com/api/user/profile'),
headers: {'Authorization': 'Bearer $token'},
);
if (response.statusCode == 200) { if (response.statusCode == 200) {
final data = json.decode(response.body)['data']; final data = json.decode(response.body)['data'];
@ -40,11 +43,12 @@ class PermissionService {
return result; return result;
} }
// Handle 401 by attempting a single retry with refreshed token
if (response.statusCode == 401 && !hasRetried) { if (response.statusCode == 401 && !hasRetried) {
final refreshed = await AuthService.refreshToken(); final refreshed = await AuthService.refreshToken();
if (refreshed) { if (refreshed) {
final newToken = await LocalStorage.getJwtToken(); final newToken = await LocalStorage.getJwtToken();
if (newToken != null) { if (newToken != null && newToken.isNotEmpty) {
return fetchAllUserData(newToken, hasRetried: true); return fetchAllUserData(newToken, hasRetried: true);
} }
} }
@ -53,15 +57,15 @@ class PermissionService {
throw Exception('Unauthorized. Token refresh failed.'); throw Exception('Unauthorized. Token refresh failed.');
} }
final errorMessage = final error = json.decode(response.body)['message'] ?? 'Unknown error';
json.decode(response.body)['message'] ?? 'Unknown error'; throw Exception('Failed to fetch user data: $error');
throw Exception('Failed to load data: $errorMessage');
} catch (e) { } catch (e) {
logger.e('Error fetching user data: $e'); logger.e('Error fetching user data: $e');
rethrow; rethrow;
} }
} }
/// Clears auth data and redirects to login
static Future<void> _handleUnauthorized() async { static Future<void> _handleUnauthorized() async {
await LocalStorage.removeToken('jwt_token'); await LocalStorage.removeToken('jwt_token');
await LocalStorage.removeToken('refresh_token'); await LocalStorage.removeToken('refresh_token');
@ -69,15 +73,20 @@ class PermissionService {
Get.offAllNamed('/auth/login-option'); Get.offAllNamed('/auth/login-option');
} }
static List<UserPermission> _parsePermissions( /// Converts raw permission data into list of `UserPermission`
List<dynamic> featurePermissions) => static List<UserPermission> _parsePermissions(List<dynamic> permissions) {
featurePermissions return permissions
.map((id) => UserPermission.fromJson({'id': id})) .map((id) => UserPermission.fromJson({'id': id}))
.toList(); .toList();
}
static EmployeeInfo _parseEmployeeInfo(Map<String, dynamic> employeeData) => /// Converts raw employee JSON into `EmployeeInfo`
EmployeeInfo.fromJson(employeeData); static EmployeeInfo _parseEmployeeInfo(Map<String, dynamic> data) {
return EmployeeInfo.fromJson(data);
}
static List<ProjectInfo> _parseProjectsInfo(List<dynamic> projects) => /// Converts raw projects JSON into list of `ProjectInfo`
projects.map((proj) => ProjectInfo.fromJson(proj)).toList(); static List<ProjectInfo> _parseProjectsInfo(List<dynamic> projects) {
return projects.map((proj) => ProjectInfo.fromJson(proj)).toList();
}
} }