hexabot/frontend/src/services/api.class.ts
2024-11-18 15:32:34 +01:00

406 lines
10 KiB
TypeScript

/*
* Copyright © 2024 Hexastack. All rights reserved.
*
* Licensed under the GNU Affero General Public License v3.0 (AGPLv3) with the following additional terms:
* 1. The name "Hexabot" is a trademark of Hexastack. You may not use this name in derivative works without express written permission.
* 2. All derivative works must include clear attribution to the original creator and software, Hexastack and Hexabot, in a prominent location (e.g., in the software's "About" section, documentation, and README file).
*/
import { AxiosInstance, AxiosResponse } from "axios";
import { ILoginAttributes } from "@/types/auth/login.types";
import { IUserPermissions } from "@/types/auth/permission.types";
import { StatsType } from "@/types/bot-stat.types";
import { ICsrf } from "@/types/csrf.types";
import { IInvitation, IInvitationAttributes } from "@/types/invitation.types";
import { INlpDatasetSampleAttributes } from "@/types/nlp-sample.types";
import { IResetPayload, IResetRequest } from "@/types/reset.types";
import { IUser, IUserAttributes, IUserStub } from "@/types/user.types";
import { EntityType, Format, TCount, TypeByFormat } from "./types";
export const ROUTES = {
// Misc
ACCEPT_INVITE: "/auth/accept-invite",
CONFIRM_ACCOUNT: "/user/confirm",
LOGIN: "/auth/local",
ME: "/auth/me",
LOGOUT: "/auth/logout",
PROFILE: "/user/edit",
INVITE: "/user/invite",
USER_PERMISSIONS: "/user/permissions",
CSRF: "/csrftoken",
BOTSTATS: "/botstats",
REFRESH_TRANSLATIONS: "/translation/refresh",
FETCH_REMOTE_I18N: "/i18n",
RESET: "/user/reset",
NLP_SAMPLE_IMPORT: "/nlpsample/import",
NLP_SAMPLE_PREDICT: "/nlpsample/message",
CONTENT_IMPORT: "/content/import",
// Entities
[EntityType.SUBSCRIBER]: "/subscriber",
[EntityType.LABEL]: "/label",
[EntityType.ROLE]: "/role",
[EntityType.USER]: "/user",
[EntityType.PERMISSION]: "/permission",
[EntityType.MODEL]: "/model",
[EntityType.CATEGORY]: "/category",
[EntityType.CONTEXT_VAR]: "/contextVar",
[EntityType.MENU]: "/menu",
[EntityType.MENUTREE]: "/menu/tree",
[EntityType.CONTENT]: "/content",
[EntityType.CONTENT_TYPE]: "/contenttype",
[EntityType.SETTING]: "/setting",
[EntityType.BOTSTATS]: "/botstats",
[EntityType.BLOCK]: "/block",
[EntityType.CUSTOM_BLOCK]: "/block/customBlocks",
[EntityType.CUSTOM_BLOCK_SETTINGS]: "/block/customBlocks/settings",
[EntityType.NLP_SAMPLE]: "/nlpsample",
[EntityType.NLP_ENTITY]: "/nlpentity",
[EntityType.NLP_VALUE]: "/nlpvalue",
[EntityType.NLP_SAMPLE_ENTITY]: "",
[EntityType.MESSAGE]: "/message",
[EntityType.LANGUAGE]: "/language",
[EntityType.TRANSLATION]: "/translation",
[EntityType.ATTACHMENT]: "/attachment",
[EntityType.CHANNEL]: "/channel",
[EntityType.HELPER]: "/helper",
[EntityType.NLU_HELPER]: "/helper/nlu",
[EntityType.LLM_HELPER]: "/helper/llm",
} as const;
export class ApiClient {
constructor(protected readonly request: AxiosInstance) {}
async getCsrf() {
const { data } = await this.request.get<ICsrf>(ROUTES.CSRF);
return data;
}
async login(payload: ILoginAttributes) {
const { data } = await this.request.post<
IUser,
AxiosResponse<IUser>,
ILoginAttributes
>(ROUTES.LOGIN, payload);
return data;
}
async logout() {
const { data } = await this.request.post<{ status: "ok" }>(ROUTES.LOGOUT);
return data;
}
async getCurrentSession() {
const { data } = await this.request.get<IUser>(ROUTES.ME);
return data;
}
async updateProfile(id: string, payload: Partial<IUserAttributes>) {
const { _csrf } = await this.getCsrf();
const { data } = await this.request.patch<
IUserStub,
AxiosResponse<IUserStub>,
Partial<IUserAttributes> & ICsrf
>(`${ROUTES.PROFILE}/${id}`, {
...payload,
_csrf,
});
return data;
}
async invite(payload: IInvitationAttributes) {
const { _csrf } = await this.getCsrf();
const { data } = await this.request.post<
IInvitation,
AxiosResponse<IInvitation>,
IInvitationAttributes & ICsrf
>(ROUTES.INVITE, {
...payload,
_csrf,
});
return data;
}
async acceptInvite({
token,
...rest
}: Partial<IUserAttributes> & { token: string }) {
const { _csrf } = await this.getCsrf();
const { data } = await this.request.post<
IInvitation,
AxiosResponse<{
success: boolean;
}>,
ICsrf
>(`${ROUTES.ACCEPT_INVITE}/${token}`, {
...rest,
_csrf,
});
return data;
}
async confirmAccount(payload: { token: string }) {
const { _csrf } = await this.getCsrf();
const { data } = await this.request.post<
IInvitation,
AxiosResponse<never>,
ICsrf
>(`${ROUTES.CONFIRM_ACCOUNT}`, {
...payload,
_csrf,
});
return data;
}
async getUserPermissions(id: string) {
const { data } = await this.request.get<IUserPermissions>(
`${ROUTES.USER_PERMISSIONS}/${id}`,
);
return data;
}
async requestReset(payload: IResetRequest) {
const { data } = await this.request.post<
IResetRequest,
AxiosResponse<void>,
IResetRequest
>(ROUTES.RESET, payload);
return data;
}
async getBotStats<T>(type: StatsType) {
const { data } = await this.request.get<T[]>(`${ROUTES.BOTSTATS}/${type}`);
return data;
}
async refreshTranslations() {
const { _csrf } = await this.getCsrf();
const { data } = await this.request.post<{
acknowledged: boolean;
deletedCount: number;
}>(ROUTES.REFRESH_TRANSLATIONS, { _csrf });
return data;
}
async fetchRemoteI18n() {
const { data } = await this.request.get(ROUTES.FETCH_REMOTE_I18N);
return data;
}
async reset(token: string, payload: IResetPayload) {
const { data } = await this.request.post<
IResetPayload,
AxiosResponse<void>,
IResetPayload
>(`${ROUTES.RESET}/${token}`, payload);
return data;
}
async importNlpSamples(attachmentId: string) {
const { _csrf } = await this.getCsrf();
const { data } = await this.request.post(
`${ROUTES.NLP_SAMPLE_IMPORT}/${attachmentId}`,
{ _csrf },
);
return data;
}
async importContent(contentTypeId: string, attachmentId: string) {
const { data } = await this.request.get(
`${ROUTES.CONTENT_IMPORT}/${contentTypeId}/${attachmentId}`,
);
return data;
}
async predictNlp(text: string) {
const { data } = await this.request.get<INlpDatasetSampleAttributes>(
`${ROUTES.NLP_SAMPLE_PREDICT}`,
{ params: { text } },
);
return data;
}
getRequest() {
return this.request;
}
buildEntityClient<TAttr, TStub, TFull = never>(type: EntityType) {
return EntityApiClient.getInstance<TAttr, TStub, TFull>(this.request, type);
}
}
export class EntityApiClient<TAttr, TBasic, TFull> extends ApiClient {
constructor(request: AxiosInstance, private readonly type: EntityType) {
super(request);
}
static getInstance<TA, TS, TF>(request: AxiosInstance, type: EntityType) {
return new EntityApiClient<TA, TS, TF>(request, type);
}
/**
* Create an entry to the given entity type.
*/
async create(payload: TAttr) {
const { _csrf } = await this.getCsrf();
const { data } = await this.request.post<
TBasic,
AxiosResponse<TBasic>,
TAttr
>(ROUTES[this.type], { ...payload, _csrf });
return data;
}
async upload(file: File) {
const { _csrf } = await this.getCsrf();
const formData = new FormData();
formData.append("file", file);
const { data } = await this.request.post<
TBasic[],
AxiosResponse<TBasic[]>,
FormData
>(`${ROUTES[this.type]}/upload?_csrf=${_csrf}`, formData, {
headers: {
"Content-Type": "multipart/form-data",
},
});
return data[0];
}
private serializePopulate<P = string[] | undefined>(populate: P) {
return ((populate || []) as string[]).join(",");
}
async get<
P = string[] | undefined,
F extends Format = P extends undefined ? Format.BASIC : Format.FULL,
T = TypeByFormat<F, TBasic, TFull>,
>(id?: string, populate?: P) {
const { data } = await this.request.get<T>(
`${ROUTES[this.type]}${id ? `/${id}` : ""}`,
{
params: {
...(Array.isArray(populate) &&
populate.length && { populate: this.serializePopulate(populate) }),
},
},
);
return data;
}
async find<
P = string[] | undefined,
F extends Format = P extends undefined ? Format.BASIC : Format.FULL,
T = TypeByFormat<F, TBasic, TFull>,
>(params: any, populate: P) {
const { data } = await this.request.get<T[]>(ROUTES[this.type], {
params: {
...params,
...(Array.isArray(populate) &&
populate.length && { populate: this.serializePopulate(populate) }),
},
});
return data;
}
/**
* Update an entry in a entity type.
*/
async update(id: string, payload: Partial<TAttr>) {
const { _csrf } = await this.getCsrf();
const { data } = await this.request.patch<TBasic>(
`${ROUTES[this.type]}/${id}`,
{
...payload,
_csrf,
},
);
return data;
}
/**
* Bulk Update entries.
*/
async updateMany(ids: string[], payload: Partial<TAttr>) {
const { _csrf } = await this.getCsrf();
const { data } = await this.request.patch<string>(
`${ROUTES[this.type]}/bulk`,
{
_csrf,
ids,
payload,
},
);
return data;
}
/**
* Delete an entry.
*/
async delete(id: string) {
const { _csrf } = await this.getCsrf();
const { data } = await this.request.delete<string>(
`${ROUTES[this.type]}/${id}`,
{
data: { _csrf },
},
);
return data;
}
/**
* Bulk Delete entries.
*/
async deleteMany(ids: string[]) {
const { _csrf } = await this.getCsrf();
const { data } = await this.request.delete<string>(`${ROUTES[this.type]}`, {
data: {
_csrf,
ids,
},
});
return data;
}
/**
* Count elements.
*/
async count(params?: any) {
const { data } = await this.request.get<TCount>(
`${ROUTES[this.type]}/count`,
{
params,
},
);
return { count: data.count };
}
}