mirror of
https://github.com/inventree/inventree-app.git
synced 2025-06-16 12:15:31 +00:00
Format code
This commit is contained in:
442
lib/api.dart
442
lib/api.dart
@ -27,13 +27,16 @@ import "package:inventree/user_profile.dart";
|
||||
import "package:inventree/widget/dialogs.dart";
|
||||
import "package:inventree/widget/snacks.dart";
|
||||
|
||||
|
||||
/*
|
||||
* Class representing an API response from the server
|
||||
*/
|
||||
class APIResponse {
|
||||
|
||||
APIResponse({this.url = "", this.method = "", this.statusCode = -1, this.error = "", this.data = const {}});
|
||||
APIResponse(
|
||||
{this.url = "",
|
||||
this.method = "",
|
||||
this.statusCode = -1,
|
||||
this.error = "",
|
||||
this.data = const {}});
|
||||
|
||||
int statusCode = -1;
|
||||
|
||||
@ -88,7 +91,6 @@ class APIResponse {
|
||||
* Handles case where the response is paginated, or a complete set of results
|
||||
*/
|
||||
List<dynamic> resultsList() {
|
||||
|
||||
if (isList()) {
|
||||
return asList();
|
||||
} else if (isMap()) {
|
||||
@ -104,14 +106,12 @@ class APIResponse {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Custom FileService for caching network images
|
||||
* Requires a custom badCertificateCallback,
|
||||
* so we can accept "dodgy" (e.g. self-signed) certificates
|
||||
*/
|
||||
class InvenTreeFileService extends FileService {
|
||||
|
||||
InvenTreeFileService({HttpClient? client, bool strictHttps = false}) {
|
||||
_client = client ?? HttpClient();
|
||||
|
||||
@ -141,8 +141,10 @@ class InvenTreeFileService extends FileService {
|
||||
final HttpClientResponse httpResponse = await req.close();
|
||||
|
||||
final http.StreamedResponse _response = http.StreamedResponse(
|
||||
httpResponse.timeout(Duration(seconds: 60)), httpResponse.statusCode,
|
||||
contentLength: httpResponse.contentLength < 0 ? 0 : httpResponse.contentLength,
|
||||
httpResponse.timeout(Duration(seconds: 60)),
|
||||
httpResponse.statusCode,
|
||||
contentLength:
|
||||
httpResponse.contentLength < 0 ? 0 : httpResponse.contentLength,
|
||||
reasonPhrase: httpResponse.reasonPhrase,
|
||||
isRedirect: httpResponse.isRedirect,
|
||||
);
|
||||
@ -158,12 +160,10 @@ class InvenTreeFileService extends FileService {
|
||||
* initialised using a username:password combination.
|
||||
*/
|
||||
|
||||
|
||||
/*
|
||||
* API class which manages all communication with the InvenTree server
|
||||
*/
|
||||
class InvenTreeAPI {
|
||||
|
||||
factory InvenTreeAPI() {
|
||||
return _api;
|
||||
}
|
||||
@ -209,7 +209,6 @@ class InvenTreeAPI {
|
||||
}
|
||||
|
||||
String _makeUrl(String url) {
|
||||
|
||||
// Strip leading slash
|
||||
if (url.startsWith("/")) {
|
||||
url = url.substring(1, url.length);
|
||||
@ -257,15 +256,10 @@ class InvenTreeAPI {
|
||||
* Useful as a precursor check before performing operations.
|
||||
*/
|
||||
bool checkConnection() {
|
||||
|
||||
// Is the server connected?
|
||||
if (!isConnected()) {
|
||||
|
||||
showSnackIcon(
|
||||
L10().notConnected,
|
||||
success: false,
|
||||
icon: TablerIcons.server
|
||||
);
|
||||
showSnackIcon(L10().notConnected,
|
||||
success: false, icon: TablerIcons.server);
|
||||
|
||||
return false;
|
||||
}
|
||||
@ -388,7 +382,6 @@ class InvenTreeAPI {
|
||||
return !isConnected() && _connecting;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Perform the required login steps, in sequence.
|
||||
* Internal function, called by connectToServer()
|
||||
@ -403,7 +396,6 @@ class InvenTreeAPI {
|
||||
* 5. Request information on available plugins
|
||||
*/
|
||||
Future<bool> _connectToServer() async {
|
||||
|
||||
if (!await _checkServer()) {
|
||||
return false;
|
||||
}
|
||||
@ -413,7 +405,8 @@ class InvenTreeAPI {
|
||||
}
|
||||
|
||||
if (!await _checkAuth()) {
|
||||
showServerError(_URL_ME, L10().serverNotConnected, L10().serverAuthenticationError);
|
||||
showServerError(
|
||||
_URL_ME, L10().serverNotConnected, L10().serverAuthenticationError);
|
||||
|
||||
// Invalidate the token
|
||||
if (profile != null) {
|
||||
@ -436,21 +429,16 @@ class InvenTreeAPI {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Check that the remote server is available.
|
||||
* Ping the api/ endpoint, which does not require user authentication
|
||||
*/
|
||||
Future<bool> _checkServer() async {
|
||||
|
||||
String address = profile?.server ?? "";
|
||||
|
||||
if (address.isEmpty) {
|
||||
showSnackIcon(
|
||||
L10().incompleteDetails,
|
||||
icon: TablerIcons.exclamation_circle,
|
||||
success: false
|
||||
);
|
||||
showSnackIcon(L10().incompleteDetails,
|
||||
icon: TablerIcons.exclamation_circle, success: false);
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -459,7 +447,8 @@ class InvenTreeAPI {
|
||||
}
|
||||
|
||||
// Cache the "strictHttps" setting, so we can use it later without async requirement
|
||||
_strictHttps = await InvenTreeSettingsManager().getValue(INV_STRICT_HTTPS, false) as bool;
|
||||
_strictHttps = await InvenTreeSettingsManager()
|
||||
.getValue(INV_STRICT_HTTPS, false) as bool;
|
||||
|
||||
debug("Connecting to ${apiUrl}");
|
||||
|
||||
@ -467,7 +456,8 @@ class InvenTreeAPI {
|
||||
|
||||
if (!response.successful()) {
|
||||
debug("Server returned invalid response: ${response.statusCode}");
|
||||
showStatusCodeError(apiUrl, response.statusCode, details: response.data.toString());
|
||||
showStatusCodeError(apiUrl, response.statusCode,
|
||||
details: response.data.toString());
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -486,7 +476,6 @@ class InvenTreeAPI {
|
||||
}
|
||||
|
||||
if (apiVersion < _minApiVersion) {
|
||||
|
||||
String message = L10().serverApiVersion + ": ${apiVersion}";
|
||||
|
||||
message += "\n";
|
||||
@ -509,7 +498,6 @@ class InvenTreeAPI {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Check that the user is authenticated
|
||||
* Fetch the user information
|
||||
@ -525,7 +513,8 @@ class InvenTreeAPI {
|
||||
userInfo = response.asMap();
|
||||
return true;
|
||||
} else {
|
||||
debug("Auth request failed: Server returned status ${response.statusCode}");
|
||||
debug(
|
||||
"Auth request failed: Server returned status ${response.statusCode}");
|
||||
if (response.data != null) {
|
||||
debug("Server response: ${response.data.toString()}");
|
||||
}
|
||||
@ -538,8 +527,8 @@ class InvenTreeAPI {
|
||||
* Fetch a token from the server,
|
||||
* with a temporary authentication header
|
||||
*/
|
||||
Future<APIResponse> fetchToken(UserProfile userProfile, String username, String password) async {
|
||||
|
||||
Future<APIResponse> fetchToken(
|
||||
UserProfile userProfile, String username, String password) async {
|
||||
debug("Fetching user token from ${userProfile.server}");
|
||||
|
||||
profile = userProfile;
|
||||
@ -574,14 +563,13 @@ class InvenTreeAPI {
|
||||
}
|
||||
|
||||
// Construct auth header from username and password
|
||||
String authHeader = "Basic " + base64Encode(utf8.encode("${username}:${password}"));
|
||||
String authHeader =
|
||||
"Basic " + base64Encode(utf8.encode("${username}:${password}"));
|
||||
|
||||
// Perform request to get a token
|
||||
final response = await get(
|
||||
_URL_TOKEN,
|
||||
params: { "name": platform_name},
|
||||
headers: { HttpHeaders.authorizationHeader: authHeader}
|
||||
);
|
||||
final response = await get(_URL_TOKEN,
|
||||
params: {"name": platform_name},
|
||||
headers: {HttpHeaders.authorizationHeader: authHeader});
|
||||
|
||||
// Invalid response
|
||||
if (!response.successful()) {
|
||||
@ -643,22 +631,17 @@ class InvenTreeAPI {
|
||||
_connectionStatusChanged();
|
||||
}
|
||||
|
||||
|
||||
/* Public facing connection function.
|
||||
*/
|
||||
Future<bool> connectToServer(UserProfile prf) async {
|
||||
|
||||
// Ensure server is first disconnected
|
||||
disconnectFromServer();
|
||||
|
||||
profile = prf;
|
||||
|
||||
if (profile == null) {
|
||||
showSnackIcon(
|
||||
L10().profileSelect,
|
||||
success: false,
|
||||
icon: TablerIcons.exclamation_circle
|
||||
);
|
||||
showSnackIcon(L10().profileSelect,
|
||||
success: false, icon: TablerIcons.exclamation_circle);
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -681,11 +664,9 @@ class InvenTreeAPI {
|
||||
|
||||
if (_notification_timer == null) {
|
||||
debug("starting notification timer");
|
||||
_notification_timer = Timer.periodic(
|
||||
Duration(seconds: 5),
|
||||
(timer) {
|
||||
_refreshNotifications();
|
||||
});
|
||||
_notification_timer = Timer.periodic(Duration(seconds: 5), (timer) {
|
||||
_refreshNotifications();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@ -700,7 +681,6 @@ class InvenTreeAPI {
|
||||
* Request the user roles (permissions) from the InvenTree server
|
||||
*/
|
||||
Future<bool> _fetchRoles() async {
|
||||
|
||||
roles.clear();
|
||||
|
||||
debug("API: Requesting user role data");
|
||||
@ -714,7 +694,6 @@ class InvenTreeAPI {
|
||||
var data = response.asMap();
|
||||
|
||||
if (!data.containsKey("roles")) {
|
||||
|
||||
roles = {};
|
||||
permissions = {};
|
||||
|
||||
@ -739,7 +718,6 @@ class InvenTreeAPI {
|
||||
|
||||
// Request plugin information from the server
|
||||
Future<bool> _fetchPlugins() async {
|
||||
|
||||
_plugins.clear();
|
||||
|
||||
debug("API: getPluginInformation()");
|
||||
@ -764,7 +742,6 @@ class InvenTreeAPI {
|
||||
* e.g. "sales_order", "change"
|
||||
*/
|
||||
bool checkRole(String role, String permission) {
|
||||
|
||||
if (!_connected) {
|
||||
return false;
|
||||
}
|
||||
@ -793,15 +770,11 @@ class InvenTreeAPI {
|
||||
// Ignore TypeError
|
||||
} else {
|
||||
// Unknown error - report it!
|
||||
sentryReportError(
|
||||
"api.checkRole",
|
||||
error, stackTrace,
|
||||
context: {
|
||||
"role": role,
|
||||
"permission": permission,
|
||||
"error": error.toString(),
|
||||
}
|
||||
);
|
||||
sentryReportError("api.checkRole", error, stackTrace, context: {
|
||||
"role": role,
|
||||
"permission": permission,
|
||||
"error": error.toString(),
|
||||
});
|
||||
}
|
||||
|
||||
// Unable to determine permission - assume true?
|
||||
@ -841,15 +814,11 @@ class InvenTreeAPI {
|
||||
// Ignore TypeError
|
||||
} else {
|
||||
// Unknown error - report it!
|
||||
sentryReportError(
|
||||
"api.checkPermission",
|
||||
error, stackTrace,
|
||||
context: {
|
||||
"model": model,
|
||||
"permission": permission,
|
||||
"error": error.toString(),
|
||||
}
|
||||
);
|
||||
sentryReportError("api.checkPermission", error, stackTrace, context: {
|
||||
"model": model,
|
||||
"permission": permission,
|
||||
"error": error.toString(),
|
||||
});
|
||||
}
|
||||
|
||||
// Unable to determine permission - assume true?
|
||||
@ -857,10 +826,9 @@ class InvenTreeAPI {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Perform a PATCH request
|
||||
Future<APIResponse> patch(String url, {Map<String, dynamic> body = const {}, int? expectedStatusCode}) async {
|
||||
|
||||
Future<APIResponse> patch(String url,
|
||||
{Map<String, dynamic> body = const {}, int? expectedStatusCode}) async {
|
||||
Map<String, dynamic> _body = body;
|
||||
|
||||
HttpClientRequest? request = await apiRequest(url, "PATCH");
|
||||
@ -868,24 +836,17 @@ class InvenTreeAPI {
|
||||
if (request == null) {
|
||||
// Return an "invalid" APIResponse
|
||||
return APIResponse(
|
||||
url: url,
|
||||
method: "PATCH",
|
||||
error: "HttpClientRequest is null"
|
||||
);
|
||||
url: url, method: "PATCH", error: "HttpClientRequest is null");
|
||||
}
|
||||
|
||||
return completeRequest(
|
||||
request,
|
||||
data: json.encode(_body),
|
||||
statusCode: expectedStatusCode
|
||||
);
|
||||
return completeRequest(request,
|
||||
data: json.encode(_body), statusCode: expectedStatusCode);
|
||||
}
|
||||
|
||||
/*
|
||||
* Download a file from the given URL
|
||||
*/
|
||||
Future<void> downloadFile(String url, {bool openOnDownload = true}) async {
|
||||
|
||||
if (url.isEmpty) {
|
||||
// No URL provided for download
|
||||
return;
|
||||
@ -912,19 +873,20 @@ class InvenTreeAPI {
|
||||
|
||||
HttpClientRequest? _request;
|
||||
|
||||
final bool strictHttps = await InvenTreeSettingsManager().getValue(INV_STRICT_HTTPS, false) as bool;
|
||||
final bool strictHttps = await InvenTreeSettingsManager()
|
||||
.getValue(INV_STRICT_HTTPS, false) as bool;
|
||||
|
||||
var client = createClient(url, strictHttps: strictHttps);
|
||||
|
||||
// Attempt to open a connection to the server
|
||||
try {
|
||||
_request = await client.openUrl("GET", _uri).timeout(Duration(seconds: 10));
|
||||
_request =
|
||||
await client.openUrl("GET", _uri).timeout(Duration(seconds: 10));
|
||||
|
||||
// Set headers
|
||||
defaultHeaders().forEach((key, value) {
|
||||
_request?.headers.set(key, value);
|
||||
});
|
||||
|
||||
} on SocketException catch (error) {
|
||||
debug("SocketException at ${url}: ${error.toString()}");
|
||||
showServerError(url, L10().connectionRefused, error.toString());
|
||||
@ -943,7 +905,8 @@ class InvenTreeAPI {
|
||||
showServerError(url, L10().serverError, error.toString());
|
||||
sentryReportError(
|
||||
"api.downloadFile : client.openUrl",
|
||||
error, stackTrace,
|
||||
error,
|
||||
stackTrace,
|
||||
);
|
||||
return;
|
||||
}
|
||||
@ -974,7 +937,8 @@ class InvenTreeAPI {
|
||||
showServerError(url, L10().downloadError, error.toString());
|
||||
sentryReportError(
|
||||
"api.downloadFile : client.closeRequest",
|
||||
error, stackTrace,
|
||||
error,
|
||||
stackTrace,
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -983,7 +947,9 @@ class InvenTreeAPI {
|
||||
* Upload a file to the given URL
|
||||
*/
|
||||
Future<APIResponse> uploadFile(String url, File f,
|
||||
{String name = "attachment", String method="POST", Map<String, dynamic>? fields}) async {
|
||||
{String name = "attachment",
|
||||
String method = "POST",
|
||||
Map<String, dynamic>? fields}) async {
|
||||
var _url = makeApiUrl(url);
|
||||
|
||||
var request = http.MultipartRequest(method, Uri.parse(_url));
|
||||
@ -992,7 +958,6 @@ class InvenTreeAPI {
|
||||
|
||||
if (fields != null) {
|
||||
fields.forEach((String key, dynamic value) {
|
||||
|
||||
if (value == null) {
|
||||
request.fields[key] = "";
|
||||
} else {
|
||||
@ -1023,48 +988,35 @@ class InvenTreeAPI {
|
||||
|
||||
// Report a server-side error
|
||||
if (response.statusCode == 500) {
|
||||
sentryReportMessage(
|
||||
"Server error in uploadFile()",
|
||||
context: {
|
||||
"url": url,
|
||||
"method": request.method,
|
||||
"name": name,
|
||||
"statusCode": response.statusCode.toString(),
|
||||
"requestHeaders": request.headers.toString(),
|
||||
"responseHeaders": httpResponse.headers.toString(),
|
||||
}
|
||||
);
|
||||
sentryReportMessage("Server error in uploadFile()", context: {
|
||||
"url": url,
|
||||
"method": request.method,
|
||||
"name": name,
|
||||
"statusCode": response.statusCode.toString(),
|
||||
"requestHeaders": request.headers.toString(),
|
||||
"responseHeaders": httpResponse.headers.toString(),
|
||||
});
|
||||
}
|
||||
} on SocketException catch (error) {
|
||||
showServerError(url, L10().connectionRefused, error.toString());
|
||||
response.error = "SocketException";
|
||||
response.errorDetail = error.toString();
|
||||
} on FormatException {
|
||||
showServerError(
|
||||
url,
|
||||
L10().formatException,
|
||||
L10().formatExceptionJson + ":\n${jsondata}"
|
||||
);
|
||||
|
||||
sentryReportMessage(
|
||||
"Error decoding JSON response from server",
|
||||
context: {
|
||||
"method": "uploadFile",
|
||||
"url": url,
|
||||
"statusCode": response.statusCode.toString(),
|
||||
"data": jsondata,
|
||||
}
|
||||
);
|
||||
showServerError(url, L10().formatException,
|
||||
L10().formatExceptionJson + ":\n${jsondata}");
|
||||
|
||||
sentryReportMessage("Error decoding JSON response from server", context: {
|
||||
"method": "uploadFile",
|
||||
"url": url,
|
||||
"statusCode": response.statusCode.toString(),
|
||||
"data": jsondata,
|
||||
});
|
||||
} on TimeoutException {
|
||||
showTimeoutError(url);
|
||||
response.error = "TimeoutException";
|
||||
} catch (error, stackTrace) {
|
||||
showServerError(url, L10().serverError, error.toString());
|
||||
sentryReportError(
|
||||
"api.uploadFile",
|
||||
error, stackTrace
|
||||
);
|
||||
sentryReportError("api.uploadFile", error, stackTrace);
|
||||
response.error = "UnknownError";
|
||||
response.errorDetail = error.toString();
|
||||
}
|
||||
@ -1079,15 +1031,11 @@ class InvenTreeAPI {
|
||||
* so that (hopefully) the field messages are correctly translated
|
||||
*/
|
||||
Future<APIResponse> options(String url) async {
|
||||
|
||||
HttpClientRequest? request = await apiRequest(url, "OPTIONS");
|
||||
|
||||
if (request == null) {
|
||||
// Return an "invalid" APIResponse
|
||||
return APIResponse(
|
||||
url: url,
|
||||
method: "OPTIONS"
|
||||
);
|
||||
return APIResponse(url: url, method: "OPTIONS");
|
||||
}
|
||||
|
||||
return completeRequest(request);
|
||||
@ -1097,51 +1045,40 @@ class InvenTreeAPI {
|
||||
* Perform a HTTP POST request
|
||||
* Returns a json object (or null if unsuccessful)
|
||||
*/
|
||||
Future<APIResponse> post(String url, {Map<String, dynamic> body = const {}, int? expectedStatusCode=201}) async {
|
||||
|
||||
Future<APIResponse> post(String url,
|
||||
{Map<String, dynamic> body = const {},
|
||||
int? expectedStatusCode = 201}) async {
|
||||
HttpClientRequest? request = await apiRequest(url, "POST");
|
||||
|
||||
if (request == null) {
|
||||
// Return an "invalid" APIResponse
|
||||
return APIResponse(
|
||||
url: url,
|
||||
method: "POST"
|
||||
);
|
||||
return APIResponse(url: url, method: "POST");
|
||||
}
|
||||
|
||||
return completeRequest(
|
||||
request,
|
||||
data: json.encode(body),
|
||||
statusCode: expectedStatusCode
|
||||
);
|
||||
return completeRequest(request,
|
||||
data: json.encode(body), statusCode: expectedStatusCode);
|
||||
}
|
||||
|
||||
/*
|
||||
* Perform a request to link a custom barcode to a particular item
|
||||
*/
|
||||
Future<bool> linkBarcode(Map<String, String> body) async {
|
||||
HttpClientRequest? request = await apiRequest("/barcode/link/", "POST");
|
||||
|
||||
HttpClientRequest? request = await apiRequest("/barcode/link/", "POST");
|
||||
if (request == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (request == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final response = await completeRequest(
|
||||
request,
|
||||
data: json.encode(body),
|
||||
statusCode: 200
|
||||
);
|
||||
|
||||
return response.isValid() && response.statusCode == 200;
|
||||
final response = await completeRequest(request,
|
||||
data: json.encode(body), statusCode: 200);
|
||||
|
||||
return response.isValid() && response.statusCode == 200;
|
||||
}
|
||||
|
||||
/*
|
||||
* Perform a request to unlink a custom barcode from a particular item
|
||||
*/
|
||||
Future<bool> unlinkBarcode(Map<String, dynamic> body) async {
|
||||
|
||||
HttpClientRequest? request = await apiRequest("/barcode/unlink/", "POST");
|
||||
|
||||
if (request == null) {
|
||||
@ -1149,21 +1086,19 @@ class InvenTreeAPI {
|
||||
}
|
||||
|
||||
final response = await completeRequest(
|
||||
request,
|
||||
data: json.encode(body),
|
||||
statusCode: 200,
|
||||
request,
|
||||
data: json.encode(body),
|
||||
statusCode: 200,
|
||||
);
|
||||
|
||||
return response.isValid() && response.statusCode == 200;
|
||||
}
|
||||
|
||||
|
||||
HttpClient createClient(String url, {bool strictHttps = false}) {
|
||||
|
||||
var client = HttpClient();
|
||||
|
||||
client.badCertificateCallback = (X509Certificate cert, String host, int port) {
|
||||
|
||||
client.badCertificateCallback =
|
||||
(X509Certificate cert, String host, int port) {
|
||||
if (strictHttps) {
|
||||
showServerError(
|
||||
url,
|
||||
@ -1191,22 +1126,15 @@ class InvenTreeAPI {
|
||||
* @param params is the request parameters
|
||||
*/
|
||||
Future<HttpClientRequest?> apiRequest(
|
||||
String url,
|
||||
String method,
|
||||
{
|
||||
Map<String, String> urlParams = const {},
|
||||
Map<String, String> headers = const {},
|
||||
}
|
||||
) async {
|
||||
|
||||
String url,
|
||||
String method, {
|
||||
Map<String, String> urlParams = const {},
|
||||
Map<String, String> headers = const {},
|
||||
}) async {
|
||||
var _url = makeApiUrl(url);
|
||||
|
||||
if (_url.isEmpty) {
|
||||
showServerError(
|
||||
url,
|
||||
L10().invalidHost,
|
||||
L10().invalidHostDetails
|
||||
);
|
||||
showServerError(url, L10().invalidHost, L10().invalidHostDetails);
|
||||
return null;
|
||||
}
|
||||
|
||||
@ -1227,23 +1155,21 @@ class InvenTreeAPI {
|
||||
Uri? _uri = Uri.tryParse(_url);
|
||||
|
||||
if (_uri == null || _uri.host.isEmpty) {
|
||||
showServerError(
|
||||
_url,
|
||||
L10().invalidHost,
|
||||
L10().invalidHostDetails
|
||||
);
|
||||
showServerError(_url, L10().invalidHost, L10().invalidHostDetails);
|
||||
return null;
|
||||
}
|
||||
|
||||
HttpClientRequest? _request;
|
||||
|
||||
final bool strictHttps = await InvenTreeSettingsManager().getValue(INV_STRICT_HTTPS, false) as bool;
|
||||
final bool strictHttps = await InvenTreeSettingsManager()
|
||||
.getValue(INV_STRICT_HTTPS, false) as bool;
|
||||
|
||||
var client = createClient(url, strictHttps: strictHttps);
|
||||
|
||||
// Attempt to open a connection to the server
|
||||
try {
|
||||
_request = await client.openUrl(method, _uri).timeout(Duration(seconds: 10));
|
||||
_request =
|
||||
await client.openUrl(method, _uri).timeout(Duration(seconds: 10));
|
||||
|
||||
// Default headers
|
||||
defaultHeaders().forEach((key, value) {
|
||||
@ -1281,42 +1207,37 @@ class InvenTreeAPI {
|
||||
} catch (error, stackTrace) {
|
||||
debug("Server error at ${url}: ${error.toString()}");
|
||||
showServerError(url, L10().serverError, error.toString());
|
||||
sentryReportError(
|
||||
"api.apiRequest : openUrl",
|
||||
error, stackTrace,
|
||||
context: {
|
||||
"url": url,
|
||||
"method": method,
|
||||
}
|
||||
);
|
||||
sentryReportError("api.apiRequest : openUrl", error, stackTrace,
|
||||
context: {
|
||||
"url": url,
|
||||
"method": method,
|
||||
});
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Complete an API request, and return an APIResponse object
|
||||
*/
|
||||
Future<APIResponse> completeRequest(HttpClientRequest request, {String? data, int? statusCode, bool ignoreResponse = false}) async {
|
||||
|
||||
Future<APIResponse> completeRequest(HttpClientRequest request,
|
||||
{String? data, int? statusCode, bool ignoreResponse = false}) async {
|
||||
if (data != null && data.isNotEmpty) {
|
||||
|
||||
var encoded_data = utf8.encode(data);
|
||||
|
||||
request.headers.set(HttpHeaders.contentLengthHeader, encoded_data.length.toString());
|
||||
request.headers
|
||||
.set(HttpHeaders.contentLengthHeader, encoded_data.length.toString());
|
||||
request.add(encoded_data);
|
||||
}
|
||||
|
||||
APIResponse response = APIResponse(
|
||||
method: request.method,
|
||||
url: request.uri.toString()
|
||||
);
|
||||
APIResponse response =
|
||||
APIResponse(method: request.method, url: request.uri.toString());
|
||||
|
||||
String url = request.uri.toString();
|
||||
|
||||
try {
|
||||
HttpClientResponse? _response = await request.close().timeout(Duration(seconds: 10));
|
||||
HttpClientResponse? _response =
|
||||
await request.close().timeout(Duration(seconds: 10));
|
||||
|
||||
response.statusCode = _response.statusCode;
|
||||
|
||||
@ -1326,31 +1247,29 @@ class InvenTreeAPI {
|
||||
|
||||
// Some server errors are not ones for us to worry about!
|
||||
switch (_response.statusCode) {
|
||||
case 502: // Bad gateway
|
||||
case 503: // Service unavailable
|
||||
case 504: // Gateway timeout
|
||||
case 502: // Bad gateway
|
||||
case 503: // Service unavailable
|
||||
case 504: // Gateway timeout
|
||||
break;
|
||||
default: // Any other error code
|
||||
sentryReportMessage(
|
||||
"Server error",
|
||||
context: {
|
||||
"url": request.uri.toString(),
|
||||
"method": request.method,
|
||||
"statusCode": _response.statusCode.toString(),
|
||||
"requestHeaders": request.headers.toString(),
|
||||
"responseHeaders": _response.headers.toString(),
|
||||
"responseData": response.data.toString(),
|
||||
}
|
||||
);
|
||||
default: // Any other error code
|
||||
sentryReportMessage("Server error", context: {
|
||||
"url": request.uri.toString(),
|
||||
"method": request.method,
|
||||
"statusCode": _response.statusCode.toString(),
|
||||
"requestHeaders": request.headers.toString(),
|
||||
"responseHeaders": _response.headers.toString(),
|
||||
"responseData": response.data.toString(),
|
||||
});
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
|
||||
response.data = ignoreResponse ? {} : await responseToJson(url, _response) ?? {};
|
||||
response.data =
|
||||
ignoreResponse ? {} : await responseToJson(url, _response) ?? {};
|
||||
|
||||
// First check that the returned status code is what we expected
|
||||
if (statusCode != null && statusCode != _response.statusCode) {
|
||||
showStatusCodeError(url, _response.statusCode, details: response.data.toString());
|
||||
showStatusCodeError(url, _response.statusCode,
|
||||
details: response.data.toString());
|
||||
}
|
||||
}
|
||||
} on HttpException catch (error) {
|
||||
@ -1376,14 +1295,12 @@ class InvenTreeAPI {
|
||||
}
|
||||
|
||||
return response;
|
||||
|
||||
}
|
||||
|
||||
/*
|
||||
* Convert a HttpClientResponse response object to JSON
|
||||
*/
|
||||
dynamic responseToJson(String url, HttpClientResponse response) async {
|
||||
|
||||
String body = await response.transform(utf8.decoder).join();
|
||||
|
||||
try {
|
||||
@ -1391,7 +1308,6 @@ class InvenTreeAPI {
|
||||
|
||||
return data ?? {};
|
||||
} on FormatException {
|
||||
|
||||
switch (response.statusCode) {
|
||||
case 400:
|
||||
case 401:
|
||||
@ -1405,36 +1321,32 @@ class InvenTreeAPI {
|
||||
// Ignore for server errors
|
||||
break;
|
||||
default:
|
||||
sentryReportMessage(
|
||||
"Error decoding JSON response from server",
|
||||
sentryReportMessage("Error decoding JSON response from server",
|
||||
context: {
|
||||
"headers": response.headers.toString(),
|
||||
"statusCode": response.statusCode.toString(),
|
||||
"data": body.toString(),
|
||||
"endpoint": url,
|
||||
}
|
||||
);
|
||||
});
|
||||
break;
|
||||
}
|
||||
|
||||
showServerError(
|
||||
url,
|
||||
L10().formatException,
|
||||
L10().formatExceptionJson + ":\n${body}"
|
||||
);
|
||||
url, L10().formatException, L10().formatExceptionJson + ":\n${body}");
|
||||
|
||||
// Return an empty map
|
||||
return {};
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/*
|
||||
* Perform a HTTP GET request
|
||||
* Returns a json object (or null if did not complete)
|
||||
*/
|
||||
Future<APIResponse> get(String url, {Map<String, String> params = const {}, Map<String, String> headers = const {}, int? expectedStatusCode=200}) async {
|
||||
|
||||
Future<APIResponse> get(String url,
|
||||
{Map<String, String> params = const {},
|
||||
Map<String, String> headers = const {},
|
||||
int? expectedStatusCode = 200}) async {
|
||||
HttpClientRequest? request = await apiRequest(
|
||||
url,
|
||||
"GET",
|
||||
@ -1442,7 +1354,6 @@ class InvenTreeAPI {
|
||||
headers: headers,
|
||||
);
|
||||
|
||||
|
||||
if (request == null) {
|
||||
// Return an "invalid" APIResponse
|
||||
return APIResponse(
|
||||
@ -1459,7 +1370,6 @@ class InvenTreeAPI {
|
||||
* Perform a HTTP DELETE request
|
||||
*/
|
||||
Future<APIResponse> delete(String url) async {
|
||||
|
||||
HttpClientRequest? request = await apiRequest(
|
||||
url,
|
||||
"DELETE",
|
||||
@ -1482,15 +1392,12 @@ class InvenTreeAPI {
|
||||
|
||||
// Find the current locale code for the running app
|
||||
String get currentLocale {
|
||||
|
||||
if (hasContext()) {
|
||||
// Try to get app context
|
||||
BuildContext? context = OneContext().context;
|
||||
|
||||
if (context != null) {
|
||||
Locale? locale = InvenTreeApp
|
||||
.of(context)
|
||||
?.locale;
|
||||
Locale? locale = InvenTreeApp.of(context)?.locale;
|
||||
|
||||
if (locale != null) {
|
||||
return locale.languageCode; //.toString();
|
||||
@ -1530,8 +1437,8 @@ class InvenTreeAPI {
|
||||
|
||||
static String get staticThumb => "/static/img/blank_image.thumbnail.png";
|
||||
|
||||
CachedNetworkImage? getThumbnail(String imageUrl, {double size = 40, bool hideIfNull = false}) {
|
||||
|
||||
CachedNetworkImage? getThumbnail(String imageUrl,
|
||||
{double size = 40, bool hideIfNull = false}) {
|
||||
if (hideIfNull) {
|
||||
if (imageUrl.isEmpty) {
|
||||
return null;
|
||||
@ -1539,11 +1446,7 @@ class InvenTreeAPI {
|
||||
}
|
||||
|
||||
try {
|
||||
return getImage(
|
||||
imageUrl,
|
||||
width: size,
|
||||
height: size
|
||||
);
|
||||
return getImage(imageUrl, width: size, height: size);
|
||||
} catch (error, stackTrace) {
|
||||
sentryReportError("_getThumbnail", error, stackTrace);
|
||||
return null;
|
||||
@ -1554,7 +1457,8 @@ class InvenTreeAPI {
|
||||
* Load image from the InvenTree server,
|
||||
* or from local cache (if it has been cached!)
|
||||
*/
|
||||
CachedNetworkImage getImage(String imageUrl, {double? height, double? width}) {
|
||||
CachedNetworkImage getImage(String imageUrl,
|
||||
{double? height, double? width}) {
|
||||
if (imageUrl.isEmpty) {
|
||||
imageUrl = staticImage;
|
||||
}
|
||||
@ -1563,19 +1467,18 @@ class InvenTreeAPI {
|
||||
|
||||
const key = "inventree_network_image";
|
||||
|
||||
CacheManager manager = CacheManager(
|
||||
Config(
|
||||
key,
|
||||
fileService: InvenTreeFileService(
|
||||
strictHttps: _strictHttps,
|
||||
),
|
||||
)
|
||||
);
|
||||
CacheManager manager = CacheManager(Config(
|
||||
key,
|
||||
fileService: InvenTreeFileService(
|
||||
strictHttps: _strictHttps,
|
||||
),
|
||||
));
|
||||
|
||||
return CachedNetworkImage(
|
||||
imageUrl: url,
|
||||
placeholder: (context, url) => CircularProgressIndicator(),
|
||||
errorWidget: (context, url, error) => Icon(TablerIcons.circle_x, color: COLOR_DANGER),
|
||||
errorWidget: (context, url, error) =>
|
||||
Icon(TablerIcons.circle_x, color: COLOR_DANGER),
|
||||
httpHeaders: defaultHeaders(),
|
||||
height: height,
|
||||
width: width,
|
||||
@ -1588,7 +1491,6 @@ class InvenTreeAPI {
|
||||
Map<String, InvenTreeUserSetting> _userSettings = {};
|
||||
|
||||
Future<String> getGlobalSetting(String key) async {
|
||||
|
||||
InvenTreeGlobalSetting? setting = _globalSettings[key];
|
||||
|
||||
if ((setting != null) && setting.reloadedWithin(Duration(minutes: 5))) {
|
||||
@ -1607,7 +1509,8 @@ class InvenTreeAPI {
|
||||
}
|
||||
|
||||
// Return a boolean global setting value
|
||||
Future<bool> getGlobalBooleanSetting(String key, { bool backup = false }) async {
|
||||
Future<bool> getGlobalBooleanSetting(String key,
|
||||
{bool backup = false}) async {
|
||||
String value = await getGlobalSetting(key);
|
||||
|
||||
if (value.isEmpty) {
|
||||
@ -1618,7 +1521,6 @@ class InvenTreeAPI {
|
||||
}
|
||||
|
||||
Future<String> getUserSetting(String key) async {
|
||||
|
||||
InvenTreeUserSetting? setting = _userSettings[key];
|
||||
|
||||
if ((setting != null) && setting.reloadedWithin(Duration(minutes: 5))) {
|
||||
@ -1645,8 +1547,8 @@ class InvenTreeAPI {
|
||||
/*
|
||||
* Send a request to the server to locate / identify either a StockItem or StockLocation
|
||||
*/
|
||||
Future<void> locateItemOrLocation(BuildContext context, {int? item, int? location}) async {
|
||||
|
||||
Future<void> locateItemOrLocation(BuildContext context,
|
||||
{int? item, int? location}) async {
|
||||
var plugins = getPlugins(mixin: "locate");
|
||||
|
||||
if (plugins.isEmpty) {
|
||||
@ -1679,16 +1581,11 @@ class InvenTreeAPI {
|
||||
}
|
||||
};
|
||||
|
||||
await launchApiForm(
|
||||
context,
|
||||
L10().locateLocation,
|
||||
"",
|
||||
fields,
|
||||
await launchApiForm(context, L10().locateLocation, "", fields,
|
||||
icon: TablerIcons.location_search,
|
||||
onSuccess: (Map<String, dynamic> data) async {
|
||||
plugin_name = (data["plugin"] ?? "") as String;
|
||||
}
|
||||
);
|
||||
plugin_name = (data["plugin"] ?? "") as String;
|
||||
});
|
||||
}
|
||||
|
||||
Map<String, dynamic> body = {
|
||||
@ -1730,10 +1627,13 @@ class InvenTreeAPI {
|
||||
}
|
||||
|
||||
// Accessors methods for various status code classes
|
||||
InvenTreeStatusCode get StockHistoryStatus => _get_status_class("stock/track/status/");
|
||||
InvenTreeStatusCode get StockHistoryStatus =>
|
||||
_get_status_class("stock/track/status/");
|
||||
InvenTreeStatusCode get StockStatus => _get_status_class("stock/status/");
|
||||
InvenTreeStatusCode get PurchaseOrderStatus => _get_status_class("order/po/status/");
|
||||
InvenTreeStatusCode get SalesOrderStatus => _get_status_class("order/so/status/");
|
||||
InvenTreeStatusCode get PurchaseOrderStatus =>
|
||||
_get_status_class("order/po/status/");
|
||||
InvenTreeStatusCode get SalesOrderStatus =>
|
||||
_get_status_class("order/so/status/");
|
||||
|
||||
void clearStatusCodeData() {
|
||||
StockHistoryStatus.data.clear();
|
||||
@ -1766,5 +1666,3 @@ class InvenTreeAPI {
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
Reference in New Issue
Block a user