base.ts 4.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156
  1. import { QueryClient } from "@tanstack/react-query";
  2. import { camelizeKeys, decamelize, decamelizeKeys } from "humps";
  3. import queryString, { type StringifiableRecord } from "query-string";
  4. import AuthStore from "src/modules/AuthStore";
  5. const queryClient = new QueryClient();
  6. const contentTypeHeader = "Content-Type";
  7. interface BuildUrlArgs {
  8. url: string;
  9. params?: StringifiableRecord;
  10. }
  11. function decamelizeParams(params?: StringifiableRecord): StringifiableRecord | undefined {
  12. if (!params) {
  13. return undefined;
  14. }
  15. const result: StringifiableRecord = {};
  16. for (const [key, value] of Object.entries(params)) {
  17. result[decamelize(key)] = value;
  18. }
  19. return result;
  20. }
  21. function buildUrl({ url, params }: BuildUrlArgs) {
  22. const endpoint = url.replace(/^\/|\/$/g, "");
  23. const baseUrl = `/api/${endpoint}`;
  24. const apiUrl = queryString.stringifyUrl({
  25. url: baseUrl,
  26. query: decamelizeParams(params),
  27. });
  28. return apiUrl;
  29. }
  30. function buildAuthHeader(): Record<string, string> | undefined {
  31. if (AuthStore.token) {
  32. return { Authorization: `Bearer ${AuthStore.token.token}` };
  33. }
  34. return {};
  35. }
  36. function buildBody(data?: Record<string, any>): string | undefined {
  37. if (data) {
  38. return JSON.stringify(decamelizeKeys(data));
  39. }
  40. }
  41. async function processResponse(response: Response) {
  42. const payload = await response.json();
  43. if (!response.ok) {
  44. if (response.status === 401) {
  45. // Force logout user and reload the page if Unauthorized
  46. AuthStore.clear();
  47. queryClient.clear();
  48. window.location.reload();
  49. }
  50. throw new Error(
  51. typeof payload.error.messageI18n !== "undefined" ? payload.error.messageI18n : payload.error.message,
  52. );
  53. }
  54. return camelizeKeys(payload) as any;
  55. }
  56. interface GetArgs {
  57. url: string;
  58. params?: queryString.StringifiableRecord;
  59. }
  60. async function baseGet({ url, params }: GetArgs, abortController?: AbortController) {
  61. const apiUrl = buildUrl({ url, params });
  62. const method = "GET";
  63. const headers = buildAuthHeader();
  64. const signal = abortController?.signal;
  65. const response = await fetch(apiUrl, { method, headers, signal });
  66. return response;
  67. }
  68. export async function get(args: GetArgs, abortController?: AbortController) {
  69. return processResponse(await baseGet(args, abortController));
  70. }
  71. export async function download(args: GetArgs, abortController?: AbortController) {
  72. return (await baseGet(args, abortController)).text();
  73. }
  74. interface PostArgs {
  75. url: string;
  76. params?: queryString.StringifiableRecord;
  77. data?: any;
  78. noAuth?: boolean;
  79. }
  80. export async function post({ url, params, data, noAuth }: PostArgs, abortController?: AbortController) {
  81. const apiUrl = buildUrl({ url, params });
  82. const method = "POST";
  83. let headers: Record<string, string> = {};
  84. if (!noAuth) {
  85. headers = {
  86. ...buildAuthHeader(),
  87. };
  88. }
  89. let body: string | FormData | undefined;
  90. // Check if the data is an instance of FormData
  91. // If data is FormData, let the browser set the Content-Type header
  92. if (data instanceof FormData) {
  93. body = data;
  94. } else {
  95. // If data is JSON, set the Content-Type header to 'application/json'
  96. headers = {
  97. ...headers,
  98. [contentTypeHeader]: "application/json",
  99. };
  100. body = buildBody(data);
  101. }
  102. const signal = abortController?.signal;
  103. const response = await fetch(apiUrl, { method, headers, body, signal });
  104. return processResponse(response);
  105. }
  106. interface PutArgs {
  107. url: string;
  108. params?: queryString.StringifiableRecord;
  109. data?: Record<string, unknown>;
  110. }
  111. export async function put({ url, params, data }: PutArgs, abortController?: AbortController) {
  112. const apiUrl = buildUrl({ url, params });
  113. const method = "PUT";
  114. const headers = {
  115. ...buildAuthHeader(),
  116. [contentTypeHeader]: "application/json",
  117. };
  118. const signal = abortController?.signal;
  119. const body = buildBody(data);
  120. const response = await fetch(apiUrl, { method, headers, body, signal });
  121. return processResponse(response);
  122. }
  123. interface DeleteArgs {
  124. url: string;
  125. params?: queryString.StringifiableRecord;
  126. }
  127. export async function del({ url, params }: DeleteArgs, abortController?: AbortController) {
  128. const apiUrl = buildUrl({ url, params });
  129. const method = "DELETE";
  130. const headers = {
  131. ...buildAuthHeader(),
  132. [contentTypeHeader]: "application/json",
  133. };
  134. const signal = abortController?.signal;
  135. const response = await fetch(apiUrl, { method, headers, signal });
  136. return processResponse(response);
  137. }