import 'dart:convert'; import 'package:http/http.dart' as http; import 'package:http_parser/http_parser.dart'; import 'package:pshared/api/responses/file_uploaded.dart'; import 'package:pshared/api/responses/message.dart'; import 'package:pshared/api/responses/error/connectivity.dart'; import 'package:pshared/api/responses/error/server.dart'; import 'package:pshared/config/constants.dart'; Uri _uri(String service, String url) { // Ensure the base URL ends with a slash final normalizedBaseUrl = Constants.apiUrl.endsWith('/') ? Constants.apiUrl : '${Constants.apiUrl}/'; // Remove leading slash from service, if any final normalizedService = service.startsWith('/') ? service.substring(1) : service; // Remove leading slash from url, if any final normalizedUrl = url.startsWith('/') ? url.substring(1) : url; // Only append a trailing slash to the service // if the URL is non-empty final serviceWithOptionalSlash = normalizedUrl.isNotEmpty ? '$normalizedService/' : normalizedService; return Uri.parse(normalizedBaseUrl).resolve(serviceWithOptionalSlash).resolve(normalizedUrl); } Map _authHeader(Map headers, String? authToken) { if (authToken != null && authToken.isNotEmpty) { headers['Authorization'] = 'Bearer $authToken'; } return headers; } Map _headers({String? authToken}) { final headers = {'Content-Type': 'application/json'}; return _authHeader(headers, authToken); } Future postRequest(String service, String url, Map body, {String? authToken}) async { final response = await http.post(_uri(service, url), headers: _headers(authToken: authToken), body: json.encode(body), ); return response; } Future getRequest(String service, String url, {String? authToken}) async { final response = await http.get(_uri(service, url), headers: _headers(authToken: authToken), ); return response; } Future putRequest(String service, String url, Map body, {String? authToken}) async { final response = await http.put(_uri(service, url), headers: _headers(authToken: authToken), body: json.encode(body), ); return response; } Future patchRequest(String service, String url, Map body, {String? authToken}) async { final response = await http.patch(_uri(service, url), headers: _headers(authToken: authToken), body: json.encode(body), ); return response; } Future deleteRequest(String service, String url, Map body, {String? authToken}) async { final response = await http.delete(_uri(service, url), headers: _headers(authToken: authToken), body: json.encode(body), ); return response; } Future _fileUploadRequest(String service, String url, String fileName, String fileType, String mediaType, List bytes, {String? authToken}) async { var request = http.MultipartRequest('POST', _uri(service, url)); var multipartFile = http.MultipartFile.fromBytes( fileType, bytes, contentType: MediaType.parse(mediaType), filename: fileName, ); request.files.add(multipartFile); if (authToken != null && authToken.isNotEmpty) { request.headers['Authorization'] = 'Bearer $authToken'; } return request.send(); } void _throwConnectivityError(http.Response response, Object e) { throw ConnectivityError( code: response.statusCode, message: e is FormatException ? 'Invalid response format. error: ${e.toString()}' : 'Unknown error occurred, error: ${e.toString()}', ); } Future> _handleResponse(Future r) async { late http.Response response; try { response = await r; } catch(e) { throw ConnectivityError(message: e.toString()); } late HTTPMessage message; try { message = HTTPMessage.fromJson(json.decode(response.body)); } catch(e) { _throwConnectivityError(response, e); } if (response.statusCode < 200 || response.statusCode >= 300) { late ErrorResponse error; try { error = ErrorResponse.fromJson(message.data); } catch(e) { _throwConnectivityError(response, e); } throw error; } return message.data; } Future> getPOSTResponse(String service, String url, Map body, {String? authToken}) async { return _handleResponse(postRequest(service, url, body, authToken: authToken)); } Future> getGETResponse(String service, String url, {String? authToken}) async { return _handleResponse(getRequest(service, url, authToken: authToken)); } Future> getPUTResponse(String service, String url, Map body, {String? authToken}) async { return _handleResponse(putRequest(service, url, body, authToken: authToken)); } Future> getPATCHResponse(String service, String url, Map body, {String? authToken}) async { return _handleResponse(patchRequest(service, url, body, authToken: authToken)); } Future> getDELETEResponse(String service, String url, Map body, {String? authToken}) async { return _handleResponse(deleteRequest(service, url, body, authToken: authToken)); } Future getFileUploadResponse(String service, String url, String fileName, String fileType, String mediaType, List bytes, {String? authToken}) async { final streamedResponse = await _fileUploadRequest(service, url, fileName, fileType, mediaType, bytes, authToken: authToken); return FileUploaded.fromJson(await _handleResponse(http.Response.fromStream(streamedResponse))); }