import 'dart:convert'; import 'package:Intaleq/constant/box_name.dart'; import 'package:Intaleq/constant/links.dart'; import 'package:Intaleq/controller/auth/login_controller.dart'; import 'package:Intaleq/main.dart'; import 'package:get/get.dart'; import 'package:http/http.dart' as http; import 'package:Intaleq/env/env.dart'; import '../../constant/api_key.dart'; import '../../views/widgets/elevated_btn.dart'; import '../../views/widgets/error_snakbar.dart'; import 'encrypt_decrypt.dart'; import 'upload_image.dart'; import 'dart:io'; import 'package:jwt_decoder/jwt_decoder.dart'; import 'network/connection_check.dart'; import 'network/net_guard.dart'; class CRUD { final NetGuard _netGuard = NetGuard(); /// Stores the signature of the last logged error to prevent duplicates. static String _lastErrorSignature = ''; /// Stores the timestamp of the last logged error. static DateTime _lastErrorTimestamp = DateTime(2000); /// The minimum time that must pass before logging the same error again. static const Duration _errorLogDebounceDuration = Duration(minutes: 1); /// Asynchronously logs an error to the server with debouncing to prevent log flooding. static Future addError( String error, String details, String where) async { try { final currentErrorSignature = '$where-$error'; final now = DateTime.now(); if (currentErrorSignature == _lastErrorSignature && now.difference(_lastErrorTimestamp) < _errorLogDebounceDuration) { print("Debounced a duplicate error: $error"); return; } _lastErrorSignature = currentErrorSignature; _lastErrorTimestamp = now; final userId = box.read(BoxName.driverID) ?? box.read(BoxName.passengerID); final userType = box.read(BoxName.driverID) != null ? 'Driver' : 'Passenger'; final phone = box.read(BoxName.phone) ?? box.read(BoxName.phoneDriver); // Fire-and-forget call to prevent infinite loops if the logger itself fails. CRUD().post( link: AppLink.addError, payload: { 'error': error.toString(), 'userId': userId.toString(), 'userType': userType, 'phone': phone.toString(), 'device': where, 'details': details, }, ); } catch (e) { print("CRITICAL: Failed to log error to server: $e"); } } /// Centralized private method to handle all API requests. /// Includes retry logic, network checking, and standardized error handling. Future _makeRequest({ required String link, Map? payload, required Map headers, }) async { try { var response = await HttpRetry.sendWithRetry( () { var url = Uri.parse(link); return http.post( url, body: payload, headers: headers, ); }, maxRetries: 3, timeout: const Duration(seconds: 15), ); if (response.statusCode == 200) { try { var jsonData = jsonDecode(response.body); if (jsonData['status'] == 'success') { return jsonData; } else { // Log API logical errors (e.g., "Customer not found") if (jsonData['status'] == 'failure') { // return 'failure'; } else { addError( 'API Logic Error: ${jsonData['status']}', 'Response: ${response.body}', 'CRUD._makeRequest - $link', ); } return jsonData['status']; } } catch (e, stackTrace) { addError( 'JSON Decode Error: $e', 'Response Body: ${response.body}\nStack Trace: $stackTrace', 'CRUD._makeRequest - $link', ); return 'failure'; } } else if (response.statusCode == 401) { var jsonData = jsonDecode(response.body); if (jsonData['error'] == 'Token expired') { await Get.put(LoginController()).getJWT(); // mySnackbarSuccess('please order now'.tr); return 'token_expired'; } else { addError( 'Unauthorized Error: ${jsonData['error']}', 'Status Code: 401', 'CRUD._makeRequest - $link', ); return 'failure'; } } else { addError( 'HTTP Error', 'Status Code: ${response.statusCode}\nResponse Body: ${response.body}', 'CRUD._makeRequest - $link', ); return 'failure'; } } on SocketException { _netGuard.notifyOnce((title, msg) { mySnackeBarError(msg); }); return 'no_internet'; } catch (e, stackTrace) { addError( 'HTTP Request Exception: $e', 'Stack Trace: $stackTrace', 'CRUD._makeRequest - $link', ); return 'failure'; } } /// Performs a standard authenticated POST request. /// Automatically handles token renewal. Future post({ required String link, Map? payload, }) async { String token = r(box.read(BoxName.jwt)).toString().split(Env.addd)[0]; // if (JwtDecoder.isExpired(token)) { // await Get.put(LoginController()).getJWT(); // token = r(box.read(BoxName.jwt)).toString().split(Env.addd)[0]; // } final headers = { "Content-Type": "application/x-www-form-urlencoded", 'Authorization': 'Bearer $token' }; return await _makeRequest( link: link, payload: payload, headers: headers, ); } /// Performs a standard authenticated GET request (using POST method as per original code). /// Automatically handles token renewal. Future get({ required String link, Map? payload, }) async { String token = r(box.read(BoxName.jwt)).toString().split(Env.addd)[0]; // if (JwtDecoder.isExpired(token)) { // await Get.put(LoginController()).getJWT(); // token = r(box.read(BoxName.jwt)).toString().split(Env.addd)[0]; // } final headers = { "Content-Type": "application/x-www-form-urlencoded", 'Authorization': 'Bearer $token' }; var result = await _makeRequest( link: link, payload: payload, headers: headers, ); // The original 'get' method returned the raw body on success, maintaining that behavior. if (result is Map && result['status'] == 'success') { return jsonEncode(result); } return result; } /// Performs an authenticated POST request to wallet endpoints. Future postWallet({ required String link, Map? payload, }) async { var jwt = await LoginController().getJwtWallet(); final hmac = box.read(BoxName.hmac); final headers = { "Content-Type": "application/x-www-form-urlencoded", 'Authorization': 'Bearer $jwt', 'X-HMAC-Auth': hmac.toString(), }; return await _makeRequest( link: link, payload: payload, headers: headers, ); } /// Performs an authenticated GET request to wallet endpoints (using POST). Future getWallet({ required String link, Map? payload, }) async { var jwt = await LoginController().getJwtWallet(); final hmac = box.read(BoxName.hmac); final headers = { "Content-Type": "application/x-www-form-urlencoded", 'Authorization': 'Bearer $jwt', 'X-HMAC-Auth': hmac.toString(), }; var result = await _makeRequest( link: link, payload: payload, headers: headers, ); if (result is Map && result['status'] == 'success') { return jsonEncode(result); } return result; } // ======================================================================= // All other specialized methods remain below. // They are kept separate because they interact with external third-party APIs // and have unique authentication, body structures, or error handling logic // that doesn't fit the standardized `_makeRequest` helper. // ======================================================================= Future postWalletMtn( {required String link, Map? payload}) async { // This method has a very custom response-wrapping logic, so it's kept separate. final s = await LoginController().getJwtWallet(); final hmac = box.read(BoxName.hmac); final url = Uri.parse(link); try { final response = await http.post( url, body: payload, headers: { "Content-Type": "application/x-www-form-urlencoded", "Authorization": "Bearer $s", "X-HMAC-Auth": hmac.toString(), }, ); print('req: ${response.request}'); print('status: ${response.statusCode}'); print('body: ${response.body}'); print('payload: $payload'); Map wrap(String status, {Object? message, int? code}) { return { 'status': status, 'message': message, 'code': code ?? response.statusCode, }; } if (response.statusCode == 200) { try { return jsonDecode(response.body); } catch (e) { return wrap('failure', message: 'JSON decode error', code: response.statusCode); } } else if (response.statusCode == 401) { try { final jsonData = jsonDecode(response.body); if (jsonData is Map && jsonData['error'] == 'Token expired') { await Get.put(LoginController()).getJWT(); return { 'status': 'failure', 'message': 'token_expired', 'code': 401 }; } return wrap('failure', message: jsonData); } catch (_) { return wrap('failure', message: response.body); } } else { try { final jsonData = jsonDecode(response.body); return wrap('failure', message: jsonData); } catch (_) { return wrap('failure', message: response.body); } } } catch (e) { return { 'status': 'failure', 'message': 'HTTP request error: $e', 'code': -1 }; } } Future getTokenParent({ required String link, Map? payload, }) async { // Uses Basic Auth, so it's a separate implementation. var url = Uri.parse( link, ); var response = await http.post( url, body: payload, headers: { "Content-Type": "application/x-www-form-urlencoded", 'Authorization': 'Basic ${base64Encode(utf8.encode(AK.basicAuthCredentials.toString()))}', }, ); if (response.statusCode == 200) { return jsonDecode(response.body); } // Consider adding error handling here. return null; } Future sendWhatsAppAuth(String to, String token) async { var res = await CRUD() .get(link: AppLink.getApiKey, payload: {'keyName': 'whatsapp_key'}); var accesstoken = jsonDecode(res)['message']['whatsapp_key']; var headers = { 'Authorization': 'Bearer $accesstoken', 'Content-Type': 'application/json' }; var url = 'https://graph.facebook.com/v20.0/${Env.whatappID}/messages'; var request = http.Request('POST', Uri.parse(url)); var body = json.encode({ "messaging_product": "whatsapp", "to": to, "type": "template", "template": { "name": "sefer1", "language": {"code": "en"}, "components": [ { "type": "body", "parameters": [ { "type": "text", "text": token, } ] } ] } }); request.body = body; request.headers.addAll(headers); try { http.StreamedResponse response = await request.send(); if (response.statusCode == 200) { String responseBody = await response.stream.bytesToString(); Get.defaultDialog( title: 'You will receive a code in WhatsApp Messenger'.tr, middleText: 'wait 1 minute to recive message'.tr, confirm: MyElevatedButton( title: 'OK'.tr, onPressed: () { Get.back(); }, ), ); } else { String errorBody = await response.stream.bytesToString(); } } catch (e) {} } Future getAgoraToken({ required String channelName, required String uid, }) async { var uid = box.read(BoxName.phone) ?? box.read(BoxName.phoneDriver); var res = await http.get(Uri.parse( // 'https://repulsive-pig-rugby-shirt.cyclic.app/token?channelName=$channelName'), 'https://orca-app-b2i85.ondigitalocean.app/token?channelName=$channelName'), headers: {'Authorization': 'Bearer ${AK.agoraAppCertificate}'}); if (res.statusCode == 200) { var response = jsonDecode(res.body); return response['token']; } else {} } Future getLlama({ required String link, required String payload, required String prompt, }) async { var url = Uri.parse( link, ); var headers = { 'Content-Type': 'application/json', 'Authorization': 'Bearer LL-X5lJ0Px9CzKK0HTuVZ3u2u4v3tGWkImLTG7okGRk4t25zrsLqJ0qNoUzZ2x4ciPy' // 'Authorization': 'Bearer ${Env.llamaKey}' }; var data = json.encode({ "model": "Llama-3-70b-Inst-FW", // "model": "llama-13b-chat", "messages": [ { "role": "user", "content": "Extract the desired information from the following passage as json decoded like $prompt just in this:\n\n$payload" } ], "temperature": 0.9 }); var response = await http.post( url, body: data, headers: headers, ); if (response.statusCode == 200) { return response.body; } return response.statusCode; } Future allMethodForAI(String prompt, linkPHP, imagePath) async { await ImageController().choosImage(linkPHP, imagePath); Future.delayed(const Duration(seconds: 2)); String extracted = await arabicTextExtractByVisionAndAI(imagePath: imagePath); // await AI().geminiAiExtraction(prompt, extracted); } Future arabicTextExtractByVisionAndAI({ required String imagePath, }) async { var headers = { 'Content-Type': 'application/json', 'Ocp-Apim-Subscription-Key': '21010e54b50f41a4904708c526e102df' }; var url = Uri.parse( 'https://ocrhamza.cognitiveservices.azure.com/vision/v2.1/ocr?language=ar', ); String imagePathFull = '${AppLink.server}card_image/$imagePath-${box.read(BoxName.driverID) ?? box.read(BoxName.passengerID)}.jpg'; var requestBody = {"url": imagePathFull}; var response = await http.post( url, body: jsonEncode(requestBody), // Encode the JSON object to a string headers: headers, ); if (response.statusCode == 200) { var responseBody = jsonDecode(response.body); return responseBody.toString(); } return response.statusCode; } Future getChatGPT({ required String link, required String payload, }) async { var url = Uri.parse( link, ); var headers = { 'Content-Type': 'application/json', 'Authorization': 'Bearer ${Env.chatGPTkeySeferNew}' }; var data = json.encode({ "model": "gpt-3.5-turbo", "messages": [ { "role": "user", "content": "Extract the desired information from the following passage as json decoded like vin,make,made,year,expiration_date,color,owner,registration_date just in this:\n\n$payload" } ], "temperature": 0.9 }); var response = await http.post( url, body: data, headers: headers, ); if (response.statusCode == 200) { return response.body; } return response.statusCode; } Future postStripe({ required String link, Map? payload, }) async { // String? secretKey = await storage.read(key: BoxName.secretKey); var url = Uri.parse( link, ); var response = await http.post( url, body: payload, headers: { "Content-Type": "application/x-www-form-urlencoded", 'Authorization': 'Bearer ${AK.secretKey}', }, ); if (response.statusCode == 200) { return response.body; } else {} } // Future post({ // required String link, // Map? payload, // }) async { // // String? basicAuthCredentials = // // await storage.read(key: BoxName.basicAuthCredentials); // var url = Uri.parse( // link, // ); // var response = await http.post( // url, // body: payload, // headers: { // "Content-Type": "application/x-www-form-urlencoded", // 'Authorization': // 'Basic ${base64Encode(utf8.encode(AK.basicAuthCredentials))}', // }, // ); // var jsonData = jsonDecode(response.body); // if (response.statusCode == 200) { // if (jsonData['status'] == 'success') { // return response.body; // } else { // return (jsonData['status']); // } // } else { // return response.statusCode; // } // } Future postPayMob({ required String link, Map? payload, }) async { // String? basicAuthCredentials = // await storage.read(key: BoxName.basicAuthCredentials); var url = Uri.parse( link, ); var response = await http.post(url, body: payload, headers: {'Content-Type': 'application/json'}); var jsonData = jsonDecode(response.body); if (response.statusCode == 200) { if (jsonData['status'] == 'success') { return response.body; } else { return (jsonData['status']); } } else { return response.statusCode; } } sendEmail( String link, Map? payload, ) async { var headers = { "Content-Type": "application/x-www-form-urlencoded", 'Authorization': 'Basic ${base64Encode(utf8.encode(AK.basicAuthCredentials))}', }; var request = http.Request('POST', Uri.parse(link)); request.bodyFields = payload!; request.headers.addAll(headers); http.StreamedResponse response = await request.send(); if (response.statusCode == 200) { } else {} } Future postFromDialogue({ required String link, Map? payload, }) async { // String? basicAuthCredentials = // await storage.read(key: BoxName.basicAuthCredentials); var url = Uri.parse( link, ); var response = await http.post( url, body: payload, headers: { "Content-Type": "application/x-www-form-urlencoded", 'Authorization': 'Basic ${base64Encode(utf8.encode(AK.basicAuthCredentials))}', }, ); if (response.body.isNotEmpty) { var jsonData = jsonDecode(response.body); if (response.statusCode == 200) { if (jsonData['status'] == 'success') { Get.back(); // Get.snackbar( // jsonData['status'], // jsonData['message'], // ); return response.body; } } return (jsonData['status']); } } Future sendVerificationRequest(String phoneNumber) async { final accountSid = AK.accountSIDTwillo; final authToken = AK.authTokenTwillo; final verifySid = AK.twilloRecoveryCode; final Uri verificationUri = Uri.parse( 'https://verify.twilio.com/v2/Services/$verifySid/Verifications'); // Send the verification request final response = await http.post( verificationUri, headers: { 'Authorization': 'Basic ' + base64Encode(utf8.encode('$accountSid:$authToken')), 'Content-Type': 'application/x-www-form-urlencoded', }, body: { 'To': phoneNumber, 'Channel': 'sms', }, ); if (response.statusCode == 201) { } else {} // Prompt the user to enter the OTP final otpCode = "123456"; // Replace with user input // Check the verification code final checkUri = Uri.parse( 'https://verify.twilio.com/v2/Services/$verifySid/VerificationCheck'); final checkResponse = await http.post( checkUri, headers: { 'Authorization': 'Basic ' + base64Encode(utf8.encode('$accountSid:$authToken')), 'Content-Type': 'application/x-www-form-urlencoded', }, body: { 'To': phoneNumber, 'Code': otpCode, }, ); if (checkResponse.statusCode == 201) { } else {} } Future getGoogleApi({ required String link, Map? payload, }) async { var url = Uri.parse( link, ); var response = await http.post( url, body: payload, ); // Log.print('req: ${response.request}'); // Log.print('response: ${response.body}'); // Log.print('payload: ${payload}'); var jsonData = jsonDecode(response.body); if (jsonData['status'] == 'OK') { return jsonData; } return (jsonData['status']); } Future getHereMap({ required String link, }) async { var url = Uri.parse(link); try { var response = await http.get(url); if (response.statusCode == 200) { // Ensure the response body is decoded as UTF-8 var decodedBody = utf8.decode(response.bodyBytes); var data = jsonDecode(decodedBody); return data; } else { return null; } } catch (e) { return null; } } // Future update({ // required String endpoint, // required Map data, // required String id, // }) async { // // String? basicAuthCredentials = // // await storage.read(key: BoxName.basicAuthCredentials); // var url = Uri.parse('$endpoint/$id'); // var response = await http.put( // url, // body: json.encode(data), // headers: { // 'Authorization': // 'Basic ${base64Encode(utf8.encode(AK.basicAuthCredentials))}', // }, // ); // return json.decode(response.body); // } Future delete({ required String endpoint, required String id, }) async { // String? basicAuthCredentials = // await storage.read(key: BoxName.basicAuthCredentials); var url = Uri.parse('$endpoint/$id'); var response = await http.delete( url, headers: { 'Authorization': 'Basic ${base64Encode(utf8.encode(AK.basicAuthCredentials))}', }, ); return json.decode(response.body); } // ... [Other methods like sendWhatsAppAuth, getAgoraToken, getLlama, etc., would remain here as they are] ... // For brevity, I am omitting the rest of the third-party API methods as they would not change. }