2022-11-30 03:05:07 -03:00

724 lines
23 KiB
TypeScript

import axios, { AxiosInstance, AxiosResponse } from 'axios';
import Client, {
ErrorInfo,
MapInfo,
BasicMapInfo,
NewUser,
Label,
ChangeHistory,
AccountInfo,
ImportMapInfo,
Permission,
Oauth2CallbackResult,
} from '..';
import { getCsrfToken } from '../../../utils';
import { LocaleCode, localeFromStr } from '../../app-i18n';
export default class RestClient implements Client {
private baseUrl: string;
private axios: AxiosInstance;
private checkResponseForSessionExpired = <T>(error: {
response?: AxiosResponse<T>;
}): Promise<{ response?: AxiosResponse<T> }> => {
// TODO: Improve session timeout response and response handling
if (error.response && error.response.status === 405) {
this.sessionExpired();
}
return Promise.reject(error);
};
constructor(baseUrl: string) {
this.baseUrl = baseUrl;
this.axios = axios.create({ maxRedirects: 0 });
const csrfToken = getCsrfToken();
if (csrfToken) {
this.axios.defaults.headers['X-CSRF-TOKEN'] = csrfToken;
} else {
console.warn('csrf token not found in html head');
}
this.axios.interceptors.response.use(
(r) => r,
(r) => this.checkResponseForSessionExpired(r),
);
}
private _onSessionExpired: () => void;
onSessionExpired(callback?: () => void): () => void {
if (callback) {
this._onSessionExpired = callback;
}
return this._onSessionExpired;
}
private sessionExpired() {
if (this._onSessionExpired) {
this._onSessionExpired();
}
}
deleteMapPermission(id: number, email: string): Promise<void> {
const handler = (success: () => void, reject: (error: ErrorInfo) => void) => {
this.axios
.delete(`${this.baseUrl}/c/restful/maps/${id}/collabs?email=${encodeURIComponent(email)}`, {
headers: { 'Content-Type': 'text/plain' },
})
.then(() => {
success();
})
.catch((error) => {
const errorInfo = this.parseResponseOnError(error.response);
reject(errorInfo);
});
};
return new Promise(handler);
}
fetchStarred(id: number): Promise<boolean> {
const handler = (success: (starred: boolean) => void, reject: (error: ErrorInfo) => void) => {
this.axios
.get(`${this.baseUrl}/c/restful/maps/${id}/starred`, {
headers: { 'Content-Type': 'text/plain' },
})
.then((response) => {
const data = response.data;
success(data);
})
.catch((error) => {
const errorInfo = this.parseResponseOnError(error.response);
reject(errorInfo);
});
};
return new Promise(handler);
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
addMapPermissions(id: number, message: string, permissions: Permission[]): Promise<void> {
const handler = (success: () => void, reject: (error: ErrorInfo) => void) => {
this.axios
.put(
`${this.baseUrl}/c/restful/maps/${id}/collabs/`,
{
message: message,
collaborations: permissions,
},
{ headers: { 'Content-Type': 'application/json' } },
)
.then(() => {
// All was ok, let's sent to success page ...;
success();
})
.catch((error) => {
const errorInfo = this.parseResponseOnError(error.response);
reject(errorInfo);
});
};
return new Promise(handler);
}
fetchMapPermissions(id: number): Promise<Permission[]> {
const handler = (
success: (labels: Permission[]) => void,
reject: (error: ErrorInfo) => void,
) => {
this.axios
.get(`${this.baseUrl}/c/restful/maps/${id}/collabs`, {
headers: { 'Content-Type': 'text/plain' },
})
.then((response) => {
const data = response.data;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const perms: Permission[] = (data.collaborations as any[]).map((p) => {
return {
id: p.id,
email: p.email,
name: p.name,
role: p.role,
};
});
success(perms);
})
.catch((error) => {
const errorInfo = this.parseResponseOnError(error.response);
reject(errorInfo);
});
};
return new Promise(handler);
}
deleteAccount(): Promise<void> {
const handler = (success: () => void, reject: (error: ErrorInfo) => void) => {
this.axios
.delete(`${this.baseUrl}/c/restful/account`, {
headers: { 'Content-Type': 'text/plain' },
})
.then(() => {
success();
})
.catch((error) => {
const errorInfo = this.parseResponseOnError(error.response);
reject(errorInfo);
});
};
return new Promise(handler);
}
updateAccountInfo(firstname: string, lastname: string): Promise<void> {
const handler = (success: () => void, reject: (error: ErrorInfo) => void) => {
this.axios
.put(`${this.baseUrl}/c/restful/account/firstname`, firstname, {
headers: { 'Content-Type': 'text/plain' },
})
.then(() => {
return this.axios.put(`${this.baseUrl}/c/restful/account/lastname`, lastname, {
headers: { 'Content-Type': 'text/plain' },
});
})
.then(() => {
// All was ok, let's sent to success page ...;
success();
})
.catch((error) => {
const errorInfo = this.parseResponseOnError(error.response);
reject(errorInfo);
});
};
return new Promise(handler);
}
updateAccountPassword(pasword: string): Promise<void> {
const handler = (success: () => void, reject: (error: ErrorInfo) => void) => {
this.axios
.put(`${this.baseUrl}/c/restful/account/password`, pasword, {
headers: { 'Content-Type': 'text/plain' },
})
.then(() => {
success();
})
.catch((error) => {
const errorInfo = this.parseResponseOnError(error.response);
reject(errorInfo);
});
};
return new Promise(handler);
}
updateAccountLanguage(locale: LocaleCode): Promise<void> {
const handler = (success: () => void, reject: (error: ErrorInfo) => void) => {
this.axios
.put(`${this.baseUrl}/c/restful/account/locale`, locale, {
headers: { 'Content-Type': 'text/plain' },
})
.then(() => {
// All was ok, let's sent to success page ...;
success();
})
.catch((error) => {
const errorInfo = this.parseResponseOnError(error.response);
// Enrich with language ...
errorInfo.msg = `${errorInfo.msg} - Language: ${locale}`;
reject(errorInfo);
});
};
return new Promise(handler);
}
importMap(model: ImportMapInfo): Promise<number> {
const handler = (success: (mapId: number) => void, reject: (error: ErrorInfo) => void) => {
this.axios
.post(
`${this.baseUrl}/c/restful/maps?title=${encodeURIComponent(
model.title,
)}&description=${encodeURIComponent(model.description ? model.description : '')}`,
model.content,
{ headers: { 'Content-Type': 'application/xml' } },
)
.then((response) => {
const mapId = Number.parseInt(response.headers.resourceid, 10);
success(mapId);
})
.catch((error) => {
const errorInfo = this.parseResponseOnError(error.response);
reject(errorInfo);
});
};
return new Promise(handler);
}
fetchAccountInfo(): Promise<AccountInfo> {
const handler = (
success: (account: AccountInfo) => void,
reject: (error: ErrorInfo) => void,
) => {
this.axios
.get(`${this.baseUrl}/c/restful/account`, {
headers: { 'Content-Type': 'application/json' },
})
.then((response) => {
const account = response.data;
const locale: LocaleCode | null = account.locale;
success({
lastname: account.lastname ? account.lastname : '',
firstname: account.firstname ? account.firstname : '',
email: account.email,
locale: locale ? localeFromStr(locale) : undefined,
});
})
.catch((error) => {
const errorInfo = this.parseResponseOnError(error.response);
reject(errorInfo);
});
};
return new Promise(handler);
}
deleteMaps(ids: number[]): Promise<void> {
const handler = (success: () => void, reject: (error: ErrorInfo) => void) => {
this.axios
.delete(`${this.baseUrl}/c/restful/maps/batch?ids=${ids.join()}`, {
headers: { 'Content-Type': 'text/plain' },
})
.then(() => {
success();
})
.catch((error) => {
const response = error.response;
const errorInfo = this.parseResponseOnError(response);
reject(errorInfo);
});
};
return new Promise(handler);
}
updateMapToPublic(id: number, isPublic: boolean): Promise<void> {
const handler = (success: () => void, reject: (error: ErrorInfo) => void) => {
this.axios
.put(`${this.baseUrl}/c/restful/maps/${id}/publish`, isPublic.toString(), {
headers: { 'Content-Type': 'text/plain' },
})
.then(() => {
success();
})
.catch((error) => {
const errorInfo = this.parseResponseOnError(error.response);
reject(errorInfo);
});
};
return new Promise(handler);
}
revertHistory(id: number, hid: number): Promise<void> {
const handler = (success: () => void, reject: (error: ErrorInfo) => void) => {
this.axios
.post(`${this.baseUrl}/c/restful/maps/${id}/history/${hid}`, null, {
headers: { 'Content-Type': 'text/pain' },
})
.then(() => {
success();
})
.catch((error) => {
const errorInfo = this.parseResponseOnError(error.response);
reject(errorInfo);
});
};
return new Promise(handler);
}
fetchHistory(id: number): Promise<ChangeHistory[]> {
const handler = (
success: (historyList: ChangeHistory[]) => void,
reject: (error: ErrorInfo) => void,
) => {
this.axios
.get(`${this.baseUrl}/c/restful/maps/${id}/history/`, {
headers: { 'Content-Type': 'application/json' },
})
.then((response) => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const historyList: ChangeHistory[] = (response.data.changes as any[]).map((h) => {
return {
id: h.id,
lastModificationBy: h.creator,
lastModificationTime: h.creationTime,
};
});
success(historyList);
})
.catch((error) => {
const errorInfo = this.parseResponseOnError(error.response);
reject(errorInfo);
});
};
return new Promise(handler);
}
renameMap(id: number, basicInfo: BasicMapInfo): Promise<void> {
const handler = (success: () => void, reject: (error: ErrorInfo) => void) => {
this.axios
.put(`${this.baseUrl}/c/restful/maps/${id}/title`, basicInfo.title, {
headers: { 'Content-Type': 'text/plain' },
})
.then(() => {
return this.axios.put(
`${this.baseUrl}/c/restful/maps/${id}/description`,
basicInfo.description || ' ',
{ headers: { 'Content-Type': 'text/plain' } },
);
})
.then(() => {
// All was ok, let's sent to success page ...;
success();
})
.catch((error) => {
const response = error.response;
const errorInfo = this.parseResponseOnError(response);
reject(errorInfo);
});
};
return new Promise(handler);
}
createMap(model: BasicMapInfo): Promise<number> {
const handler = (success: (mapId: number) => void, reject: (error: ErrorInfo) => void) => {
this.axios
.post(
`${this.baseUrl}/c/restful/maps?title=${encodeURIComponent(
model.title,
)}&description=${encodeURIComponent(model.description ? model.description : '')}`,
null,
{ headers: { 'Content-Type': 'application/json' } },
)
.then((response) => {
const mapId = Number.parseInt(response.headers.resourceid, 10);
success(mapId);
})
.catch((error) => {
const errorInfo = this.parseResponseOnError(error.response);
reject(errorInfo);
});
};
return new Promise(handler);
}
fetchAllMaps(): Promise<MapInfo[]> {
const handler = (
success: (mapsInfo: MapInfo[]) => void,
reject: (error: ErrorInfo) => void,
) => {
this.axios
.get(`${this.baseUrl}/c/restful/maps/`, {
headers: { 'Content-Type': 'application/json' },
})
.then((response) => {
const data = response.data;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const maps: MapInfo[] = (data.mindmapsInfo as any[]).map((m) => {
return {
id: m.id,
starred: m.starred,
title: m.title,
labels: m.labels,
createdBy: m.creator,
creationTime: m.creationTime,
lastModificationBy: m.lastModifierUser,
lastModificationTime: m.lastModificationTime,
description: m.description,
isPublic: m['public'],
role: m.role,
};
});
success(maps);
})
.catch((error) => {
const errorInfo = this.parseResponseOnError(error.response);
reject(errorInfo);
});
};
return new Promise(handler);
}
registerNewUser(user: NewUser): Promise<void> {
const handler = (success: () => void, reject: (error: ErrorInfo) => void) => {
this.axios
.post(`${this.baseUrl}/service/users/`, JSON.stringify(user), {
headers: { 'Content-Type': 'application/json' },
})
.then(() => {
// All was ok, let's sent to success page ...;
success();
})
.catch((error) => {
const errorInfo = this.parseResponseOnError(error.response);
reject(errorInfo);
});
};
return new Promise(handler);
}
deleteMap(id: number): Promise<void> {
const handler = (success: () => void, reject: (error: ErrorInfo) => void) => {
this.axios
.delete(`${this.baseUrl}/c/restful/maps/${id}`, {
headers: { 'Content-Type': 'application/json' },
})
.then(() => {
success();
})
.catch((error) => {
const errorInfo = this.parseResponseOnError(error.response);
reject(errorInfo);
});
};
return new Promise(handler);
}
resetPassword(email: string): Promise<void> {
const handler = (success: () => void, reject: (error: ErrorInfo) => void) => {
this.axios
.put(
`${this.baseUrl}/service/users/resetPassword?email=${encodeURIComponent(email)}`,
null,
{
headers: { 'Content-Type': 'application/json' },
},
)
.then(() => {
// All was ok, let's sent to success page ...;
success();
})
.catch((error) => {
const response = error.response;
const errorInfo = this.parseResponseOnError(response);
reject(errorInfo);
});
};
return new Promise(handler);
}
duplicateMap(id: number, basicInfo: BasicMapInfo): Promise<number> {
const handler = (success: (mapId: number) => void, reject: (error: ErrorInfo) => void) => {
this.axios
.post(`${this.baseUrl}/c/restful/maps/${id}`, JSON.stringify(basicInfo), {
headers: { 'Content-Type': 'application/json' },
})
.then((response) => {
const mapId = Number.parseInt(response.headers.resourceid, 10);
success(mapId);
})
.catch((error) => {
const response = error.response;
const errorInfo = this.parseResponseOnError(response);
reject(errorInfo);
});
};
return new Promise(handler);
}
updateStarred(id: number, starred: boolean): Promise<void> {
const handler = (success: () => void, reject: (error: ErrorInfo) => void) => {
this.axios
.put(`${this.baseUrl}/c/restful/maps/${id}/starred`, starred.toString(), {
headers: { 'Content-Type': 'text/plain' },
})
.then(() => {
success();
})
.catch((error) => {
const response = error.response;
const errorInfo = this.parseResponseOnError(response);
reject(errorInfo);
});
};
return new Promise(handler);
}
fetchLabels(): Promise<Label[]> {
const handler = (success: (labels: Label[]) => void, reject: (error: ErrorInfo) => void) => {
this.axios
.get(`${this.baseUrl}/c/restful/labels/`, {
headers: { 'Content-Type': 'application/json' },
})
.then((response) => {
const data = response.data;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const maps: Label[] = (data.labels as any[]).map((l) => {
return {
id: l.id,
color: l.color,
title: l.title,
iconName: l.iconName,
};
});
success(maps);
})
.catch((error) => {
const errorInfo = this.parseResponseOnError(error.response);
reject(errorInfo);
});
};
return new Promise(handler);
}
createLabel(title: string, color: string): Promise<number> {
const handler = (success: (labelId: number) => void, reject: (error: ErrorInfo) => void) => {
this.axios
.post(
`${this.baseUrl}/c/restful/labels`,
JSON.stringify({ title, color, iconName: 'smile' }),
{
headers: { 'Content-Type': 'application/json' },
},
)
.then((response) => {
const mapId = Number.parseInt(response.headers.resourceid, 10);
success(mapId);
})
.catch((error) => {
const errorInfo = this.parseResponseOnError(error.response);
reject(errorInfo);
});
};
return new Promise(handler);
}
deleteLabel(id: number): Promise<void> {
const handler = (success: () => void, reject: (error: ErrorInfo) => void) => {
this.axios
.delete(`${this.baseUrl}/c/restful/labels/${id}`)
.then(() => {
success();
})
.catch((error) => {
const errorInfo = this.parseResponseOnError(error.response);
reject(errorInfo);
});
};
return new Promise(handler);
}
addLabelToMap(labelId: number, mapId: number): Promise<void> {
const handler = (success: () => void, reject: (error: ErrorInfo) => void) => {
this.axios
.post(`${this.baseUrl}/c/restful/maps/${mapId}/labels`, JSON.stringify(labelId), {
headers: { 'Content-Type': 'application/json' },
})
.then(() => {
success();
})
.catch((error) => {
const errorInfo = this.parseResponseOnError(error.response);
reject(errorInfo);
});
};
return new Promise(handler);
}
deleteLabelFromMap(labelId: number, mapId: number): Promise<void> {
const handler = (success: () => void, reject: (error: ErrorInfo) => void) => {
this.axios
.delete(`${this.baseUrl}/c/restful/maps/${mapId}/labels/${labelId}`)
.then(() => {
success();
})
.catch((error) => {
const errorInfo = this.parseResponseOnError(error.response);
reject(errorInfo);
});
};
return new Promise(handler);
}
processGoogleCallback(code: string): Promise<Oauth2CallbackResult> {
const handler = (
success: (result: Oauth2CallbackResult) => void,
reject: (error: ErrorInfo) => void,
) => {
this.axios
.post(`${this.baseUrl}/service/users/googleCallback?code=${code}`, {
headers: { 'Content-Type': 'application/json' },
})
.then((response) => {
success({
email: response.data.email,
googleSync: response.data.googleSync,
syncCode: response.data.syncCode,
});
})
.catch((error) => {
const errorInfo = this.parseResponseOnError(error.response);
reject(errorInfo);
});
};
return new Promise(handler);
}
confirmAccountSync(email: string, code: string): Promise<void> {
const handler = (success: () => void, reject: (error: ErrorInfo) => void) => {
this.axios
.put(`${this.baseUrl}/service/users/confirmAccountSync?email=${email}&code=${code}`, {
headers: { 'Content-Type': 'application/json' },
})
.then((response) => {
success();
})
.catch((error) => {
const errorInfo = this.parseResponseOnError(error.response);
reject(errorInfo);
});
};
return new Promise(handler);
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
private parseResponseOnError = (response: any): ErrorInfo => {
console.error(`Performing backend action error: ${JSON.stringify(response)}`);
let result: ErrorInfo | undefined;
if (response) {
const status: number = response.status;
const data = response.data;
console.error(`Status Code: ${status}`);
console.error(`Status Data: ${response.data}`);
console.error(`Status Message: ${response.message}`);
switch (status) {
case 401:
case 302:
this.sessionExpired();
result = {
msg: 'Your current session has expired. Please, sign in and try again.',
};
break;
default:
if (data) {
// Set global errors ...
result = {};
const globalErrors = data.globalErrors;
if (globalErrors && globalErrors.length > 0) {
result.msg = globalErrors[0];
}
// Set field errors ...
if (data.fieldErrors && Object.keys(data.fieldErrors).length > 0) {
result.fields = data.fieldErrors;
if (!result.msg) {
const key = Object.keys(data.fieldErrors)[0];
result.msg = data.fieldErrors[key];
}
}
} else {
result = { msg: response.statusText };
}
}
}
// Network related problem ...
if (!result) {
result = { msg: 'Unexpected error. Please, try latter' };
}
return result;
};
}