This commit is contained in:
672
src/api/gzapi.js
Normal file
672
src/api/gzapi.js
Normal file
@@ -0,0 +1,672 @@
|
||||
import router from "../router";
|
||||
|
||||
function stringifyPrimitive(v) {
|
||||
switch (typeof v) {
|
||||
case "string":
|
||||
return v;
|
||||
|
||||
case "boolean":
|
||||
return v ? "true" : "false";
|
||||
|
||||
case "number":
|
||||
return isFinite(v) ? v : "";
|
||||
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
////////////////////////////////////////////
|
||||
// Try to handle an api error
|
||||
// return true if handled or false if not
|
||||
//
|
||||
function handleError(action, error, route) {
|
||||
const errorMessage =
|
||||
"API error: " + action + " route =" + route + ", message =" + error.message;
|
||||
window.$gz.store.commit("logItem", errorMessage);
|
||||
|
||||
//Handle 403 not authorized
|
||||
//popup not authorized, log, then go to HOME
|
||||
//was going to go back one page, but realized most of the time a not authorized is in
|
||||
//reaction to directly entered or opened link, not application logic driving it, so home is safest choice
|
||||
//
|
||||
if (error.message && error.message.includes("NotAuthorized")) {
|
||||
window.$gz.eventBus.$emit(
|
||||
"notify-warning",
|
||||
window.$gz.translation.get("ErrorUserNotAuthorized")
|
||||
);
|
||||
router.push(window.$gz.store.state.homePage);
|
||||
|
||||
throw new Error("LT:ErrorUserNotAuthorized");
|
||||
}
|
||||
|
||||
//Handle 401 not authenticated
|
||||
if (error.message && error.message.includes("NotAuthenticated")) {
|
||||
window.$gz.eventBus.$emit(
|
||||
"notify-error",
|
||||
window.$gz.translation.get("ErrorUserNotAuthenticated")
|
||||
);
|
||||
|
||||
router.push("/login");
|
||||
|
||||
throw new Error("LT:ErrorUserNotAuthenticated");
|
||||
}
|
||||
|
||||
//is it a network error?
|
||||
//https://medium.com/@vinhlh/how-to-handle-networkerror-when-using-fetch-ff2663220435
|
||||
if (error instanceof TypeError) {
|
||||
if (
|
||||
error.message.includes("Failed to fetch") ||
|
||||
error.message.includes("NetworkError") ||
|
||||
error.message.includes("Network request failed")
|
||||
) {
|
||||
let msg = "";
|
||||
|
||||
if (window.$gz.store.state.authenticated) {
|
||||
msg = window.$gz.translation.get("ErrorServerUnresponsive");
|
||||
} else {
|
||||
msg = "Could not connect to Sockeye server ";
|
||||
}
|
||||
|
||||
msg += window.$gz.api.APIUrl("") + "\r\nError: " + error.message;
|
||||
|
||||
window.$gz.eventBus.$emit("notify-error", msg);
|
||||
//note: using translation key in square brackets
|
||||
|
||||
throw new Error(msg);
|
||||
}
|
||||
}
|
||||
|
||||
//Ideally this should never get called because any issue should be addressed above
|
||||
window.$gz.errorHandler.handleFormError(error);
|
||||
}
|
||||
|
||||
export default {
|
||||
status(response) {
|
||||
//Handle expected api errors
|
||||
if (response.status == 401) {
|
||||
throw new Error("LT:ErrorUserNotAuthenticated");
|
||||
}
|
||||
|
||||
if (response.status == 403) {
|
||||
throw new Error("LT:ErrorUserNotAuthorized");
|
||||
}
|
||||
|
||||
//404 not found is an expected status not worth logging allow to bubble up
|
||||
//for client code to deal with
|
||||
if (response.status == 404) {
|
||||
return Promise.resolve(response);
|
||||
}
|
||||
|
||||
if (response.status == 405) {
|
||||
//Probably a development error
|
||||
|
||||
throw new Error("Method Not Allowed (route issue?) " + response.url);
|
||||
}
|
||||
|
||||
if (response.status >= 200 && response.status < 300) {
|
||||
return Promise.resolve(response);
|
||||
} else {
|
||||
//log unhandled api error
|
||||
window.$gz.store.commit(
|
||||
"logItem",
|
||||
"API error: status=" +
|
||||
response.status +
|
||||
", statusText=" +
|
||||
response.statusText +
|
||||
", url=" +
|
||||
response.url
|
||||
);
|
||||
//let it float up for dealing with by caller(s)
|
||||
return Promise.resolve(response);
|
||||
}
|
||||
},
|
||||
statusEx(response) {
|
||||
//Handle expected api errors
|
||||
if (response.status == 401) {
|
||||
throw new Error("LT:ErrorUserNotAuthenticated");
|
||||
}
|
||||
|
||||
if (response.status == 403) {
|
||||
throw new Error("LT:ErrorUserNotAuthorized");
|
||||
}
|
||||
|
||||
//404 not found is an expected status not worth logging allow to bubble up
|
||||
//for client code to deal with
|
||||
if (response.status == 404) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (response.status == 405) {
|
||||
//Probably a development error
|
||||
|
||||
throw new Error("Method Not Allowed (route issue?) " + response.url);
|
||||
}
|
||||
|
||||
if (response.status >= 200 && response.status < 300) {
|
||||
return;
|
||||
} else {
|
||||
//log unhandled api error
|
||||
|
||||
window.$gz.store.commit(
|
||||
"logItem",
|
||||
"API error: status=" +
|
||||
response.status +
|
||||
", statusText=" +
|
||||
response.statusText +
|
||||
", url=" +
|
||||
response.url
|
||||
);
|
||||
}
|
||||
},
|
||||
async extractBodyEx(response) {
|
||||
if (response.status == 204) {
|
||||
//no content, nothing to process
|
||||
return response;
|
||||
}
|
||||
const contentType = response.headers.get("content-type");
|
||||
|
||||
if (!contentType) {
|
||||
return response;
|
||||
}
|
||||
if (contentType.includes("json")) {
|
||||
return await response.json();
|
||||
}
|
||||
if (contentType.includes("text/plain")) {
|
||||
return await response.text();
|
||||
}
|
||||
|
||||
if (contentType.includes("application/pdf")) {
|
||||
return await response.blob();
|
||||
}
|
||||
return response;
|
||||
},
|
||||
extractBody(response) {
|
||||
if (response.status == 204) {
|
||||
//no content, nothing to process
|
||||
return response;
|
||||
}
|
||||
const contentType = response.headers.get("content-type");
|
||||
if (!contentType) {
|
||||
return response;
|
||||
}
|
||||
if (contentType.includes("json")) {
|
||||
return response.json();
|
||||
}
|
||||
if (contentType.includes("text/plain")) {
|
||||
return response.text();
|
||||
}
|
||||
return response;
|
||||
},
|
||||
apiErrorToHumanString(apiError) {
|
||||
//empty error object?
|
||||
if (!apiError) {
|
||||
return "(E18) - apiErrorToHumanString():: Empty API eror, unknown";
|
||||
}
|
||||
//convert to readable string
|
||||
return "(E18) - " + JSON.stringify(apiError);
|
||||
},
|
||||
patchAuthorizedHeaders() {
|
||||
return {
|
||||
Accept: "application/json",
|
||||
"Content-Type": "application/json-patch+json",
|
||||
Authorization: "Bearer " + window.$gz.store.state.apiToken
|
||||
};
|
||||
},
|
||||
postAuthorizedHeaders() {
|
||||
return {
|
||||
Accept: "application/json",
|
||||
"Content-Type": "application/json",
|
||||
Authorization: "Bearer " + window.$gz.store.state.apiToken
|
||||
//this maybe useful in future like batch ops etc so keeping as a reminder
|
||||
//,"X-AY-Import-Mode": true
|
||||
};
|
||||
},
|
||||
postUnAuthorizedHeaders() {
|
||||
return {
|
||||
Accept: "application/json",
|
||||
"Content-Type": "application/json"
|
||||
};
|
||||
},
|
||||
fetchPostNoAuthOptions(data) {
|
||||
return {
|
||||
method: "post",
|
||||
mode: "cors",
|
||||
headers: this.postUnAuthorizedHeaders(),
|
||||
body: JSON.stringify(data)
|
||||
};
|
||||
},
|
||||
fetchPostOptions(data) {
|
||||
return {
|
||||
method: "post",
|
||||
mode: "cors",
|
||||
headers: this.postAuthorizedHeaders(),
|
||||
body: JSON.stringify(data)
|
||||
};
|
||||
},
|
||||
fetchPutOptions(data) {
|
||||
return {
|
||||
method: "put",
|
||||
mode: "cors",
|
||||
headers: this.postAuthorizedHeaders(),
|
||||
body: JSON.stringify(data)
|
||||
};
|
||||
},
|
||||
fetchGetOptions() {
|
||||
/* GET WITH AUTH */
|
||||
return {
|
||||
method: "get",
|
||||
mode: "cors",
|
||||
headers: this.postAuthorizedHeaders()
|
||||
};
|
||||
},
|
||||
fetchRemoveOptions() {
|
||||
/* REMOVE WITH AUTH */
|
||||
return {
|
||||
method: "delete",
|
||||
mode: "cors",
|
||||
headers: this.postAuthorizedHeaders()
|
||||
};
|
||||
},
|
||||
APIUrl(apiPath) {
|
||||
if (
|
||||
window.$gz.dev &&
|
||||
window.location.hostname == "localhost" &&
|
||||
window.location.port == "8080"
|
||||
) {
|
||||
return "http://localhost:7676/api/v8.0/" + apiPath;
|
||||
}
|
||||
|
||||
return (
|
||||
window.location.protocol +
|
||||
"//" +
|
||||
window.location.host +
|
||||
"/api/v8.0/" +
|
||||
apiPath
|
||||
);
|
||||
},
|
||||
helpUrl() {
|
||||
if (
|
||||
window.$gz.dev &&
|
||||
window.location.hostname == "localhost" &&
|
||||
window.location.port == "8080"
|
||||
) {
|
||||
return "http://localhost:7676/docs/";
|
||||
}
|
||||
return window.location.protocol + "//" + window.location.host + "/docs/";
|
||||
},
|
||||
helpUrlCustomer() {
|
||||
if (
|
||||
window.$gz.dev &&
|
||||
window.location.hostname == "localhost" &&
|
||||
window.location.port == "8080"
|
||||
) {
|
||||
return "http://localhost:7676/cust/";
|
||||
}
|
||||
return window.location.protocol + "//" + window.location.host + "/cust/";
|
||||
},
|
||||
/////////////////////////////
|
||||
// Just the server itself
|
||||
// used by profiler etc
|
||||
//
|
||||
ServerBaseUrl() {
|
||||
return this.helpUrl().replace("/docs/", "/");
|
||||
},
|
||||
/////////////////////////////
|
||||
// generic routed download URL
|
||||
//
|
||||
genericDownloadUrl(route) {
|
||||
//http://localhost:7676/api/v8/backup/download/100?t=sssss
|
||||
return this.APIUrl(route + "?t=" + window.$gz.store.state.downloadToken);
|
||||
},
|
||||
/////////////////////////////
|
||||
// report file download URL
|
||||
//
|
||||
reportDownloadUrl(fileName) {
|
||||
//http://localhost:7676/api/v8/report/download/filename.pdf?t=sssss
|
||||
|
||||
return this.APIUrl(
|
||||
"report/download/" +
|
||||
fileName +
|
||||
"?t=" +
|
||||
window.$gz.store.state.downloadToken
|
||||
);
|
||||
},
|
||||
/////////////////////////////
|
||||
// backup file download URL
|
||||
//
|
||||
backupDownloadUrl(fileName) {
|
||||
//http://localhost:7676/api/v8/backup/download/100?t=sssss
|
||||
|
||||
return this.APIUrl(
|
||||
"backup/download/" +
|
||||
fileName +
|
||||
"?t=" +
|
||||
window.$gz.store.state.downloadToken
|
||||
);
|
||||
},
|
||||
/////////////////////////////
|
||||
// attachment download URL
|
||||
//
|
||||
attachmentDownloadUrl(fileId, ctype) {
|
||||
//http://localhost:7676/api/v8/attachment/download/100?t=sssss
|
||||
//Ctype is optional and is the MIME content type, used to detect image urls at client for drag and drop ops
|
||||
//in wiki but ignored by server
|
||||
|
||||
let url =
|
||||
"attachment/download/" +
|
||||
fileId +
|
||||
"?t=" +
|
||||
window.$gz.store.state.downloadToken;
|
||||
|
||||
if (ctype && ctype.includes("image")) {
|
||||
url += "&i=1";
|
||||
}
|
||||
|
||||
return this.APIUrl(url);
|
||||
},
|
||||
/////////////////////////////
|
||||
// logo download URL
|
||||
// (size= 'small', 'medium', 'large')
|
||||
logoUrl(size) {
|
||||
//http://localhost:7676/api/v8/logo/small
|
||||
return this.APIUrl("logo/" + size);
|
||||
},
|
||||
/////////////////////////////
|
||||
// REPLACE END OF URL
|
||||
// (used to change ID in url)
|
||||
replaceAfterLastSlash(theUrl, theReplacement) {
|
||||
return theUrl.substr(0, theUrl.lastIndexOf("\\") + 1) + theReplacement;
|
||||
},
|
||||
/////////////////////////////
|
||||
// ENCODE QUERY STRING
|
||||
//
|
||||
buildQuery(obj, sep, eq, name) {
|
||||
sep = sep || "&";
|
||||
eq = eq || "=";
|
||||
if (obj === null) {
|
||||
obj = undefined;
|
||||
}
|
||||
|
||||
if (typeof obj === "object") {
|
||||
return Object.keys(obj)
|
||||
.map(function(k) {
|
||||
const ks = encodeURIComponent(stringifyPrimitive(k)) + eq;
|
||||
if (Array.isArray(obj[k])) {
|
||||
return obj[k]
|
||||
.map(function(v) {
|
||||
return ks + encodeURIComponent(stringifyPrimitive(v));
|
||||
})
|
||||
.join(sep);
|
||||
} else {
|
||||
return ks + encodeURIComponent(stringifyPrimitive(obj[k]));
|
||||
}
|
||||
})
|
||||
.filter(Boolean)
|
||||
.join(sep);
|
||||
}
|
||||
|
||||
if (!name) return "";
|
||||
return (
|
||||
encodeURIComponent(stringifyPrimitive(name)) +
|
||||
eq +
|
||||
encodeURIComponent(stringifyPrimitive(obj))
|
||||
);
|
||||
},
|
||||
///////////////////////////////////
|
||||
// GET DATA FROM API SERVER
|
||||
//
|
||||
async get(route) {
|
||||
try {
|
||||
const that = this;
|
||||
|
||||
let r = await fetch(that.APIUrl(route), that.fetchGetOptions());
|
||||
that.statusEx(r);
|
||||
r = await that.extractBodyEx(r);
|
||||
return r;
|
||||
} catch (error) {
|
||||
//fundamental error, can't proceed with this call
|
||||
handleError("GET", error, route);
|
||||
}
|
||||
},
|
||||
|
||||
//////////////////////////////////////
|
||||
// Test delay for troubleshooting
|
||||
//
|
||||
doDelayAsync: () => {
|
||||
// eslint-disable-next-line
|
||||
return new Promise(resolve => {
|
||||
setTimeout(() => resolve("I did something"), 10000);
|
||||
});
|
||||
},
|
||||
///////////////////////////////////
|
||||
// POST / PUT DATA TO API SERVER
|
||||
//
|
||||
async upsert(route, data, isLogin = false) {
|
||||
try {
|
||||
const that = this;
|
||||
//determine if this is a new or existing record
|
||||
let fetchOptions = undefined;
|
||||
//put?
|
||||
if (data && data.concurrency) {
|
||||
fetchOptions = that.fetchPutOptions(data);
|
||||
} else {
|
||||
//post
|
||||
//ensure the route doesn't end in /0 which will happen if it's a new record
|
||||
//since the edit forms just send the url here with the ID regardless
|
||||
if (route.endsWith("/0")) {
|
||||
route = route.slice(0, -2);
|
||||
}
|
||||
if (isLogin == false) {
|
||||
fetchOptions = that.fetchPostOptions(data);
|
||||
} else {
|
||||
fetchOptions = that.fetchPostNoAuthOptions(data);
|
||||
}
|
||||
}
|
||||
|
||||
let r = await fetch(that.APIUrl(route), fetchOptions);
|
||||
that.statusEx(r);
|
||||
r = await that.extractBodyEx(r);
|
||||
return r;
|
||||
} catch (error) {
|
||||
if (isLogin == false) {
|
||||
handleError("UPSERT", error, route);
|
||||
} else {
|
||||
//specifically this is for the login page
|
||||
console.log("upser error is: ", error);
|
||||
throw new Error(window.$gz.errorHandler.errorToString(error));
|
||||
}
|
||||
}
|
||||
},
|
||||
///////////////////////////////////
|
||||
// DELETE DATA FROM API SERVER
|
||||
//
|
||||
async remove(route) {
|
||||
const that = this;
|
||||
try {
|
||||
let r = await fetch(that.APIUrl(route), that.fetchRemoveOptions());
|
||||
that.statusEx(r);
|
||||
//delete will return a body if there is an error of some kind with the request
|
||||
r = await that.extractBodyEx(r);
|
||||
return r;
|
||||
} catch (error) {
|
||||
//fundamental error, can't proceed with this call
|
||||
handleError("DELETE", error, route);
|
||||
}
|
||||
},
|
||||
///////////////////////////////////
|
||||
// PUT DATA TO API SERVER
|
||||
// (used for puts that can't have a concurrency token like above)
|
||||
async put(route, data) {
|
||||
try {
|
||||
const that = this;
|
||||
let r = await fetch(that.APIUrl(route), that.fetchPutOptions(data));
|
||||
that.statusEx(r);
|
||||
r = await that.extractBodyEx(r);
|
||||
return r;
|
||||
} catch (error) {
|
||||
handleError("PUT", error, route);
|
||||
}
|
||||
},
|
||||
///////////////////////////////////
|
||||
// POST DATA TO API SERVER
|
||||
// (used for post only routes not needing upserts)
|
||||
async post(route, data) {
|
||||
try {
|
||||
const that = this;
|
||||
let r = await fetch(that.APIUrl(route), that.fetchPostOptions(data));
|
||||
that.statusEx(r);
|
||||
r = await that.extractBodyEx(r);
|
||||
return r;
|
||||
} catch (error) {
|
||||
handleError("POST", error, route);
|
||||
}
|
||||
},
|
||||
///////////////////////////////////
|
||||
// POST FILE ATTACHMENTS
|
||||
// @param {sockId:objectid, sockType:aType, files:[array of files]}
|
||||
//
|
||||
async uploadAttachment(at) {
|
||||
const that = this;
|
||||
try {
|
||||
var files = at.files;
|
||||
var data = new FormData();
|
||||
for (var i = 0; i < files.length; i++) {
|
||||
data.append(files[i].name, files[i]);
|
||||
}
|
||||
|
||||
data.append("AttachToAType", at.sockType);
|
||||
data.append("AttachToObjectId", at.sockId);
|
||||
data.append("Notes", at.notes);
|
||||
data.append("FileData", at.fileData);
|
||||
|
||||
//-----------------
|
||||
|
||||
const fetchOptions = {
|
||||
method: "post",
|
||||
mode: "cors",
|
||||
headers: {
|
||||
Authorization: "Bearer " + window.$gz.store.state.apiToken
|
||||
},
|
||||
body: data
|
||||
};
|
||||
|
||||
let r = await fetch(that.APIUrl("attachment"), fetchOptions);
|
||||
that.statusEx(r);
|
||||
r = await that.extractBodyEx(r);
|
||||
return r;
|
||||
} catch (error) {
|
||||
handleError("POSTATTACHMENT", error, "uploadAttachmentRoute");
|
||||
}
|
||||
},
|
||||
//////////////////////////////////////////////
|
||||
// POST (UPLOAD) FILE TO ARBITRARY ROUTE
|
||||
// for various things that require an upload
|
||||
// e.g. translation import etc
|
||||
//
|
||||
//
|
||||
async upload(route, at) {
|
||||
const that = this;
|
||||
try {
|
||||
var files = at.files;
|
||||
var data = new FormData();
|
||||
for (var i = 0; i < files.length; i++) {
|
||||
data.append(files[i].name, files[i]);
|
||||
}
|
||||
if (at.sockType) {
|
||||
data.append("SockType", at.sockType);
|
||||
}
|
||||
if (at.sockId) {
|
||||
data.append("ObjectId", at.sockId);
|
||||
}
|
||||
if (at.notes) {
|
||||
data.append("Notes", at.notes);
|
||||
}
|
||||
data.append("FileData", at.fileData);
|
||||
|
||||
//-----------------
|
||||
|
||||
const fetchOptions = {
|
||||
method: "post",
|
||||
mode: "cors",
|
||||
headers: {
|
||||
Authorization: "Bearer " + window.$gz.store.state.apiToken
|
||||
},
|
||||
body: data
|
||||
};
|
||||
|
||||
let r = await fetch(that.APIUrl(route), fetchOptions);
|
||||
that.statusEx(r);
|
||||
r = await that.extractBodyEx(r);
|
||||
return r;
|
||||
} catch (error) {
|
||||
handleError("POSTATTACHMENT", error, route);
|
||||
}
|
||||
},
|
||||
|
||||
///////////////////////////////////
|
||||
// POST LOGO
|
||||
//
|
||||
//
|
||||
async uploadLogo(fileData, size) {
|
||||
const that = this;
|
||||
try {
|
||||
const data = new FormData();
|
||||
data.append(fileData.name, fileData);
|
||||
|
||||
//-----------------
|
||||
|
||||
const fetchOptions = {
|
||||
method: "post",
|
||||
mode: "cors",
|
||||
headers: {
|
||||
Authorization: "Bearer " + window.$gz.store.state.apiToken
|
||||
},
|
||||
body: data
|
||||
};
|
||||
|
||||
let r = await fetch(that.APIUrl("logo/" + size), fetchOptions);
|
||||
that.statusEx(r);
|
||||
r = await that.extractBodyEx(r);
|
||||
return r;
|
||||
} catch (error) {
|
||||
handleError("uploadLogo", error, "postLogoRoute");
|
||||
}
|
||||
},
|
||||
///////////////////////////////////
|
||||
// REPORT CLIENT META DATA
|
||||
//
|
||||
//
|
||||
reportClientMetaData() {
|
||||
const nowUtc = window.$gz.locale.nowUTC8601String();
|
||||
return {
|
||||
UserName: window.$gz.store.state.userName,
|
||||
UserId: window.$gz.store.state.userId,
|
||||
Authorization: "Bearer " + window.$gz.store.state.apiToken, //api token for using api methods as current user viewing report
|
||||
DownloadToken: window.$gz.store.state.downloadToken,
|
||||
TimeZoneName: window.$gz.locale.getResolvedTimeZoneName(),
|
||||
LanguageName: window.$gz.locale.getResolvedLanguage(),
|
||||
Hour12: window.$gz.locale.getHour12(),
|
||||
CurrencyName: window.$gz.locale.getCurrencyName(),
|
||||
DefaultLocale: window.$gz.locale.getResolvedLanguage().split("-", 1)[0], //kind of suspect, maybe it can be removed
|
||||
PDFDate: window.$gz.locale.utcDateToShortDateLocalized(nowUtc),
|
||||
PDFTime: window.$gz.locale.utcDateToShortTimeLocalized(nowUtc)
|
||||
};
|
||||
},
|
||||
///////////////////////////////////
|
||||
// FETCH BIZ OBJECT NAME
|
||||
//
|
||||
//
|
||||
async fetchBizObjectName(sockType, objectId) {
|
||||
const res = await this.get(`name/${sockType}/${objectId}`);
|
||||
//We never expect there to be no data here
|
||||
if (!Object.prototype.hasOwnProperty.call(res, "data")) {
|
||||
return Promise.reject(res);
|
||||
} else {
|
||||
return res.data;
|
||||
}
|
||||
}
|
||||
//---------------
|
||||
|
||||
//new functions above here
|
||||
};
|
||||
Reference in New Issue
Block a user