@greatapps/common 1.1.754 → 1.1.756
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/infra/api/client.mjs +36 -0
- package/dist/infra/api/client.mjs.map +1 -1
- package/dist/modules/plans/services/plans.service.mjs +12 -3
- package/dist/modules/plans/services/plans.service.mjs.map +1 -1
- package/package.json +1 -1
- package/src/infra/api/client.ts +309 -263
- package/src/modules/plans/services/plans.service.ts +10 -2
|
@@ -162,6 +162,42 @@ class ApiClient {
|
|
|
162
162
|
async delete(endpoint, config) {
|
|
163
163
|
return this.request(endpoint, { ...config, method: "DELETE" });
|
|
164
164
|
}
|
|
165
|
+
/**
|
|
166
|
+
* Requisição cuja resposta é consumida em stream: devolve a `Response` crua, sem ler o
|
|
167
|
+
* corpo e sem timeout.
|
|
168
|
+
*
|
|
169
|
+
* `request()` não serve para isso por dois motivos. Ele faz `await response.json()`, o
|
|
170
|
+
* que espera o corpo inteiro e mata o propósito do stream; e aborta em 30s, que é o
|
|
171
|
+
* tempo que uma operação longa leva justamente por ser longa — gerar o link de uma
|
|
172
|
+
* página com 132 imagens copia 100 MB e passa disso (ID-5112).
|
|
173
|
+
*
|
|
174
|
+
* O corpo é do chamador: quem recebe decide quando ler e quando cancelar.
|
|
175
|
+
*/
|
|
176
|
+
async stream(endpoint, config = {}) {
|
|
177
|
+
const { timeout: _timeout, responseType: _responseType, whiteLabelId: overrideWlId, authToken: overrideToken, ...fetchConfig } = config;
|
|
178
|
+
const resolvido = overrideWlId != null && overrideToken != null ? { id: overrideWlId, token: overrideToken } : await findWhitelabel();
|
|
179
|
+
const [url, defaultHeaders] = await Promise.all([
|
|
180
|
+
this.buildUrl(endpoint, overrideWlId ?? resolvido.id),
|
|
181
|
+
this.getDefaultHeaders(overrideToken ?? resolvido.token)
|
|
182
|
+
]);
|
|
183
|
+
const response = await fetch(url, {
|
|
184
|
+
...fetchConfig,
|
|
185
|
+
headers: { ...defaultHeaders, ...fetchConfig.headers },
|
|
186
|
+
// Sem isto o fetch do Next segura o corpo inteiro antes de devolver, e o stream chega
|
|
187
|
+
// completo de uma vez no fim: medido em 32s até o primeiro evento, contra 0,4s com
|
|
188
|
+
// no-store. O progresso existiria no protocolo e não na tela.
|
|
189
|
+
cache: "no-store"
|
|
190
|
+
});
|
|
191
|
+
if (!response.ok || !response.body) {
|
|
192
|
+
const detalhe = await response.text().catch(() => "");
|
|
193
|
+
throw new ApiError(
|
|
194
|
+
detalhe.trim() || `HTTP error ${response.status} ${response.statusText}`,
|
|
195
|
+
"HTTP_ERROR",
|
|
196
|
+
response.status
|
|
197
|
+
);
|
|
198
|
+
}
|
|
199
|
+
return response;
|
|
200
|
+
}
|
|
165
201
|
}
|
|
166
202
|
const api = {
|
|
167
203
|
apps: new ApiClient(
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/infra/api/client.ts"],"sourcesContent":["import { ApiError } from \"./types\";\r\nimport { findWhitelabel } from \"../../modules/whitelabel/actions/find-whitelabel.action\";\r\n\r\nexport interface ApiClientOptions {\r\n /**\r\n * Quando true, monta `{base}/{locale}/{wlId}{endpoint}` (ex.: r2-clone).\r\n * Padrão false: `{base}/v1/{locale}/{wlId}{endpoint}`.\r\n */\r\n omitApiVersion?: boolean;\r\n\r\n /** Habilita logs detalhados das requisições (útil para desenvolvimento) */\r\n emitLogs?: boolean;\r\n}\r\n\r\nexport interface RequestConfig extends RequestInit {\r\n timeout?: number;\r\n /** Padrão `json`. Use `text` quando a API devolve corpo não-JSON. */\r\n responseType?: \"json\" | \"text\";\r\n /** Força um whiteLabelId específico na URL, ignorando o findWhitelabel(). */\r\n whiteLabelId?: number;\r\n /** Força um token específico no header Authorization, ignorando o findWhitelabel(). */\r\n authToken?: string;\r\n}\r\n\r\nclass ApiClient {\r\n private readonly baseUrl: string;\r\n private readonly apiVersion = \"v1\";\r\n private readonly apiLocale = \"pt-br\";\r\n private readonly omitApiVersion: boolean;\r\n private readonly emitLogs: boolean = false;\r\n\r\n constructor(baseUrl: string, options?: ApiClientOptions) {\r\n this.baseUrl = baseUrl.replace(/\\/$/, \"\");\r\n this.omitApiVersion = options?.omitApiVersion ?? false;\r\n this.emitLogs = options?.emitLogs ?? false;\r\n }\r\n\r\n private async buildUrl(\r\n endpoint: string,\r\n whiteLabelId: number,\r\n ): Promise<string> {\r\n const wlPath = this.omitApiVersion\r\n ? `/${this.apiLocale}/${whiteLabelId}`\r\n : `/${this.apiVersion}/${this.apiLocale}/${whiteLabelId}`;\r\n const path = endpoint.startsWith(\"/\") ? endpoint : `/${endpoint}`;\r\n const url = `${this.baseUrl}${wlPath}${path}`;\r\n if (this.emitLogs) {\r\n console.log(\"[ApiClient] buildUrl\", {\r\n baseURL: this.baseUrl,\r\n whiteLabelId,\r\n endpoint,\r\n fullUrl: url,\r\n });\r\n }\r\n return url;\r\n }\r\n\r\n private async getDefaultHeaders(\r\n token: string,\r\n isFormData = false,\r\n ): Promise<HeadersInit> {\r\n if (this.emitLogs) {\r\n console.log(\"[ApiClient] getDefaultHeaders\", {\r\n token: token.substring(0, 20) + \"...\",\r\n });\r\n }\r\n\r\n const headers: HeadersInit = {\r\n authorization: token,\r\n };\r\n\r\n if (!isFormData) {\r\n headers[\"Content-Type\"] = \"application/json\";\r\n }\r\n\r\n return headers;\r\n }\r\n\r\n async request<T>(endpoint: string, config: RequestConfig = {}): Promise<T> {\r\n if (this.emitLogs) {\r\n console.log(\"[ApiClient] request called\", {\r\n endpoint,\r\n method: config.method,\r\n });\r\n }\r\n\r\n const { timeout = 30000, responseType = \"json\", whiteLabelId: overrideWlId, authToken: overrideToken, ...fetchConfig } = config;\r\n\r\n const controller = new AbortController();\r\n const timeoutId = setTimeout(() => controller.abort(), timeout);\r\n\r\n try {\r\n if (this.emitLogs) {\r\n console.log(\"[ApiClient] Building URL and headers...\");\r\n }\r\n\r\n const { id, token } = await findWhitelabel();\r\n\r\n const [url, defaultHeaders] = await Promise.all([\r\n this.buildUrl(endpoint, overrideWlId ?? id),\r\n this.getDefaultHeaders(overrideToken ?? token, fetchConfig.body instanceof FormData),\r\n ]);\r\n\r\n if (this.emitLogs)\r\n console.log(\"[ApiClient] Fetching\", {\r\n url,\r\n method: fetchConfig.method,\r\n headers: defaultHeaders,\r\n });\r\n\r\n const response = await fetch(url, {\r\n ...fetchConfig,\r\n headers: {\r\n ...defaultHeaders,\r\n ...fetchConfig.headers,\r\n },\r\n signal: controller.signal,\r\n });\r\n\r\n if (this.emitLogs)\r\n console.log(\"[ApiClient] Response received\", {\r\n status: response.status,\r\n ok: response.ok,\r\n });\r\n\r\n if (!response.ok) {\r\n const errBody = await response.text();\r\n let message = `HTTP error ${response.status} ${response.statusText} ${errBody}`;\r\n let code = \"HTTP_ERROR\";\r\n try {\r\n const errorData = JSON.parse(errBody) as Record<string, unknown>;\r\n // @ts-ignore -- msg pode ser string ou objeto com .message\r\n message = errorData.message?.message ?? errorData.message ?? message;\r\n if (typeof errorData.code === \"string\") code = errorData.code;\r\n } catch {\r\n if (errBody.trim()) message = errBody.trim();\r\n }\r\n const method = fetchConfig.method ?? \"GET\";\r\n let bodyPreview: unknown;\r\n if (method !== \"GET\" && fetchConfig.body != null) {\r\n if (typeof fetchConfig.body === \"string\") {\r\n try {\r\n bodyPreview = JSON.parse(fetchConfig.body);\r\n } catch {\r\n bodyPreview = fetchConfig.body;\r\n }\r\n } else if (fetchConfig.body instanceof FormData) {\r\n bodyPreview = \"[FormData]\";\r\n } else {\r\n bodyPreview = fetchConfig.body;\r\n }\r\n }\r\n console.error(\"[ApiClient] Request failed\", {\r\n from: url,\r\n method,\r\n ...(bodyPreview !== undefined ? { body: bodyPreview } : {}),\r\n status: response.status,\r\n message,\r\n });\r\n throw new ApiError(message, code, response.status);\r\n }\r\n\r\n if (responseType === \"text\") {\r\n return (await response.text()) as T;\r\n }\r\n\r\n const data = (await response.json()) as T;\r\n if (this.emitLogs)\r\n console.log(\"[ApiClient] Request successful\", { hasData: !!data });\r\n return data;\r\n } catch (error) {\r\n if (this.emitLogs) console.error(\"[ApiClient] Request error\", { error });\r\n\r\n if (error instanceof ApiError) {\r\n throw error;\r\n }\r\n\r\n if (error instanceof Error) {\r\n if (error.name === \"AbortError\") {\r\n throw new ApiError(\"Tempo de requisição excedido\", \"TIMEOUT\", 408);\r\n }\r\n throw new ApiError(error.message, \"NETWORK_ERROR\", 0);\r\n }\r\n\r\n throw new ApiError(\"Erro desconhecido\", \"UNKNOWN_ERROR\", 500);\r\n } finally {\r\n clearTimeout(timeoutId);\r\n }\r\n }\r\n\r\n async get<T>(endpoint: string, config?: RequestConfig): Promise<T> {\r\n return this.request<T>(endpoint, { ...config, method: \"GET\" });\r\n }\r\n\r\n async post<T>(\r\n endpoint: string,\r\n data?: unknown,\r\n config?: RequestConfig,\r\n ): Promise<T> {\r\n return this.request<T>(endpoint, {\r\n ...config,\r\n method: \"POST\",\r\n body:\r\n data instanceof FormData\r\n ? data\r\n : data\r\n ? JSON.stringify(data)\r\n : undefined,\r\n });\r\n }\r\n\r\n async put<T>(\r\n endpoint: string,\r\n data?: unknown,\r\n config?: RequestConfig,\r\n ): Promise<T> {\r\n return this.request<T>(endpoint, {\r\n ...config,\r\n method: \"PUT\",\r\n body:\r\n data instanceof FormData\r\n ? data\r\n : data\r\n ? JSON.stringify(data)\r\n : undefined,\r\n });\r\n }\r\n\r\n async patch<T>(\r\n endpoint: string,\r\n data?: unknown,\r\n config?: RequestConfig,\r\n ): Promise<T> {\r\n return this.request<T>(endpoint, {\r\n ...config,\r\n method: \"PATCH\",\r\n body:\r\n data instanceof FormData\r\n ? data\r\n : data\r\n ? JSON.stringify(data)\r\n : undefined,\r\n });\r\n }\r\n\r\n async delete<T>(endpoint: string, config?: RequestConfig): Promise<T> {\r\n return this.request<T>(endpoint, { ...config, method: \"DELETE\" });\r\n }\r\n}\r\n\r\nconst api = {\r\n apps: new ApiClient(\r\n process.env.GAPPS_R3_API_URL || \"https://r3-api.greatapps.dev.br\",\r\n ),\r\n pages: new ApiClient(\r\n process.env.GPAGES_R3_API_URL || \"https://r3-api.greatpages.dev.br\",\r\n ),\r\n};\r\n\r\n/** @deprecated use api.apps */\r\nexport const apiClient = api.apps;\r\n\r\nexport { api, ApiClient };\r\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../../src/infra/api/client.ts"],"sourcesContent":["import { ApiError } from \"./types\";\nimport { findWhitelabel } from \"../../modules/whitelabel/actions/find-whitelabel.action\";\n\nexport interface ApiClientOptions {\n /**\n * Quando true, monta `{base}/{locale}/{wlId}{endpoint}` (ex.: r2-clone).\n * Padrão false: `{base}/v1/{locale}/{wlId}{endpoint}`.\n */\n omitApiVersion?: boolean;\n\n /** Habilita logs detalhados das requisições (útil para desenvolvimento) */\n emitLogs?: boolean;\n}\n\nexport interface RequestConfig extends RequestInit {\n timeout?: number;\n /** Padrão `json`. Use `text` quando a API devolve corpo não-JSON. */\n responseType?: \"json\" | \"text\";\n /** Força um whiteLabelId específico na URL, ignorando o findWhitelabel(). */\n whiteLabelId?: number;\n /** Força um token específico no header Authorization, ignorando o findWhitelabel(). */\n authToken?: string;\n}\n\nclass ApiClient {\n private readonly baseUrl: string;\n private readonly apiVersion = \"v1\";\n private readonly apiLocale = \"pt-br\";\n private readonly omitApiVersion: boolean;\n private readonly emitLogs: boolean = false;\n\n constructor(baseUrl: string, options?: ApiClientOptions) {\n this.baseUrl = baseUrl.replace(/\\/$/, \"\");\n this.omitApiVersion = options?.omitApiVersion ?? false;\n this.emitLogs = options?.emitLogs ?? false;\n }\n\n private async buildUrl(\n endpoint: string,\n whiteLabelId: number,\n ): Promise<string> {\n const wlPath = this.omitApiVersion\n ? `/${this.apiLocale}/${whiteLabelId}`\n : `/${this.apiVersion}/${this.apiLocale}/${whiteLabelId}`;\n const path = endpoint.startsWith(\"/\") ? endpoint : `/${endpoint}`;\n const url = `${this.baseUrl}${wlPath}${path}`;\n if (this.emitLogs) {\n console.log(\"[ApiClient] buildUrl\", {\n baseURL: this.baseUrl,\n whiteLabelId,\n endpoint,\n fullUrl: url,\n });\n }\n return url;\n }\n\n private async getDefaultHeaders(\n token: string,\n isFormData = false,\n ): Promise<HeadersInit> {\n if (this.emitLogs) {\n console.log(\"[ApiClient] getDefaultHeaders\", {\n token: token.substring(0, 20) + \"...\",\n });\n }\n\n const headers: HeadersInit = {\n authorization: token,\n };\n\n if (!isFormData) {\n headers[\"Content-Type\"] = \"application/json\";\n }\n\n return headers;\n }\n\n async request<T>(endpoint: string, config: RequestConfig = {}): Promise<T> {\n if (this.emitLogs) {\n console.log(\"[ApiClient] request called\", {\n endpoint,\n method: config.method,\n });\n }\n\n const { timeout = 30000, responseType = \"json\", whiteLabelId: overrideWlId, authToken: overrideToken, ...fetchConfig } = config;\n\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), timeout);\n\n try {\n if (this.emitLogs) {\n console.log(\"[ApiClient] Building URL and headers...\");\n }\n\n const { id, token } = await findWhitelabel();\n\n const [url, defaultHeaders] = await Promise.all([\n this.buildUrl(endpoint, overrideWlId ?? id),\n this.getDefaultHeaders(overrideToken ?? token, fetchConfig.body instanceof FormData),\n ]);\n\n if (this.emitLogs)\n console.log(\"[ApiClient] Fetching\", {\n url,\n method: fetchConfig.method,\n headers: defaultHeaders,\n });\n\n const response = await fetch(url, {\n ...fetchConfig,\n headers: {\n ...defaultHeaders,\n ...fetchConfig.headers,\n },\n signal: controller.signal,\n });\n\n if (this.emitLogs)\n console.log(\"[ApiClient] Response received\", {\n status: response.status,\n ok: response.ok,\n });\n\n if (!response.ok) {\n const errBody = await response.text();\n let message = `HTTP error ${response.status} ${response.statusText} ${errBody}`;\n let code = \"HTTP_ERROR\";\n try {\n const errorData = JSON.parse(errBody) as Record<string, unknown>;\n // @ts-ignore -- msg pode ser string ou objeto com .message\n message = errorData.message?.message ?? errorData.message ?? message;\n if (typeof errorData.code === \"string\") code = errorData.code;\n } catch {\n if (errBody.trim()) message = errBody.trim();\n }\n const method = fetchConfig.method ?? \"GET\";\n let bodyPreview: unknown;\n if (method !== \"GET\" && fetchConfig.body != null) {\n if (typeof fetchConfig.body === \"string\") {\n try {\n bodyPreview = JSON.parse(fetchConfig.body);\n } catch {\n bodyPreview = fetchConfig.body;\n }\n } else if (fetchConfig.body instanceof FormData) {\n bodyPreview = \"[FormData]\";\n } else {\n bodyPreview = fetchConfig.body;\n }\n }\n console.error(\"[ApiClient] Request failed\", {\n from: url,\n method,\n ...(bodyPreview !== undefined ? { body: bodyPreview } : {}),\n status: response.status,\n message,\n });\n throw new ApiError(message, code, response.status);\n }\n\n if (responseType === \"text\") {\n return (await response.text()) as T;\n }\n\n const data = (await response.json()) as T;\n if (this.emitLogs)\n console.log(\"[ApiClient] Request successful\", { hasData: !!data });\n return data;\n } catch (error) {\n if (this.emitLogs) console.error(\"[ApiClient] Request error\", { error });\n\n if (error instanceof ApiError) {\n throw error;\n }\n\n if (error instanceof Error) {\n if (error.name === \"AbortError\") {\n throw new ApiError(\"Tempo de requisição excedido\", \"TIMEOUT\", 408);\n }\n throw new ApiError(error.message, \"NETWORK_ERROR\", 0);\n }\n\n throw new ApiError(\"Erro desconhecido\", \"UNKNOWN_ERROR\", 500);\n } finally {\n clearTimeout(timeoutId);\n }\n }\n\n async get<T>(endpoint: string, config?: RequestConfig): Promise<T> {\n return this.request<T>(endpoint, { ...config, method: \"GET\" });\n }\n\n async post<T>(\n endpoint: string,\n data?: unknown,\n config?: RequestConfig,\n ): Promise<T> {\n return this.request<T>(endpoint, {\n ...config,\n method: \"POST\",\n body:\n data instanceof FormData\n ? data\n : data\n ? JSON.stringify(data)\n : undefined,\n });\n }\n\n async put<T>(\n endpoint: string,\n data?: unknown,\n config?: RequestConfig,\n ): Promise<T> {\n return this.request<T>(endpoint, {\n ...config,\n method: \"PUT\",\n body:\n data instanceof FormData\n ? data\n : data\n ? JSON.stringify(data)\n : undefined,\n });\n }\n\n async patch<T>(\n endpoint: string,\n data?: unknown,\n config?: RequestConfig,\n ): Promise<T> {\n return this.request<T>(endpoint, {\n ...config,\n method: \"PATCH\",\n body:\n data instanceof FormData\n ? data\n : data\n ? JSON.stringify(data)\n : undefined,\n });\n }\n\n async delete<T>(endpoint: string, config?: RequestConfig): Promise<T> {\n return this.request<T>(endpoint, { ...config, method: \"DELETE\" });\n }\n\n /**\n * Requisição cuja resposta é consumida em stream: devolve a `Response` crua, sem ler o\n * corpo e sem timeout.\n *\n * `request()` não serve para isso por dois motivos. Ele faz `await response.json()`, o\n * que espera o corpo inteiro e mata o propósito do stream; e aborta em 30s, que é o\n * tempo que uma operação longa leva justamente por ser longa — gerar o link de uma\n * página com 132 imagens copia 100 MB e passa disso (ID-5112).\n *\n * O corpo é do chamador: quem recebe decide quando ler e quando cancelar.\n */\n async stream(endpoint: string, config: RequestConfig = {}): Promise<Response> {\n const { timeout: _timeout, responseType: _responseType, whiteLabelId: overrideWlId, authToken: overrideToken, ...fetchConfig } = config;\n\n // Só resolve o whitelabel quando falta algum dos dois: com ambos vindos do chamador, a\n // consulta seria uma ida à rede para descartar o resultado.\n const resolvido = overrideWlId != null && overrideToken != null\n ? { id: overrideWlId, token: overrideToken }\n : await findWhitelabel();\n\n const [url, defaultHeaders] = await Promise.all([\n this.buildUrl(endpoint, overrideWlId ?? resolvido.id),\n this.getDefaultHeaders(overrideToken ?? resolvido.token),\n ]);\n\n const response = await fetch(url, {\n ...fetchConfig,\n headers: { ...defaultHeaders, ...fetchConfig.headers },\n // Sem isto o fetch do Next segura o corpo inteiro antes de devolver, e o stream chega\n // completo de uma vez no fim: medido em 32s até o primeiro evento, contra 0,4s com\n // no-store. O progresso existiria no protocolo e não na tela.\n cache: \"no-store\",\n });\n\n if (!response.ok || !response.body) {\n const detalhe = await response.text().catch(() => \"\");\n throw new ApiError(\n detalhe.trim() || `HTTP error ${response.status} ${response.statusText}`,\n \"HTTP_ERROR\",\n response.status,\n );\n }\n\n return response;\n }\n}\n\nconst api = {\n apps: new ApiClient(\n process.env.GAPPS_R3_API_URL || \"https://r3-api.greatapps.dev.br\",\n ),\n pages: new ApiClient(\n process.env.GPAGES_R3_API_URL || \"https://r3-api.greatpages.dev.br\",\n ),\n};\n\n/** @deprecated use api.apps */\nexport const apiClient = api.apps;\n\nexport { api, ApiClient };\n"],"mappings":"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","names":[]}
|
|
@@ -2,6 +2,7 @@ import "server-only";
|
|
|
2
2
|
import { api, getUserContext } from "@greatapps/common/server";
|
|
3
3
|
import { ApiError, buildQueryParams, PlanSchema } from "@greatapps/common";
|
|
4
4
|
import greatCache from "@greatapps/cache";
|
|
5
|
+
import { accountService } from "../../accounts/services/account.service";
|
|
5
6
|
const PLANS_CACHE_TTL = 21600;
|
|
6
7
|
class PlansService {
|
|
7
8
|
cache = new greatCache({
|
|
@@ -13,10 +14,12 @@ class PlansService {
|
|
|
13
14
|
buildCacheKey(key, params) {
|
|
14
15
|
const idWl = params?.id_wl ?? "";
|
|
15
16
|
const idAccount = params?.id_account ?? "";
|
|
17
|
+
const gateway = params?.gateway ?? "";
|
|
18
|
+
const country = params?.country ?? "";
|
|
16
19
|
const sort = params?.sort ?? "id:ASC";
|
|
17
20
|
const active = params?.active ?? "";
|
|
18
21
|
const search = params?.search ?? "";
|
|
19
|
-
return `${key}-${idWl}-${idAccount}-${sort}-${active}-${search}-
|
|
22
|
+
return `${key}-${idWl}-${idAccount}-${gateway}-${country}-${sort}-${active}-${search}-v8`;
|
|
20
23
|
}
|
|
21
24
|
/**
|
|
22
25
|
* Lista planos do whitelabel.
|
|
@@ -25,10 +28,13 @@ class PlansService {
|
|
|
25
28
|
*/
|
|
26
29
|
async listPlans(params) {
|
|
27
30
|
const { id_wl, id_account } = await getUserContext();
|
|
31
|
+
const { gateway, country } = await accountService.findCurrentAccount();
|
|
28
32
|
const cacheKey = this.buildCacheKey("plans", {
|
|
29
33
|
...params,
|
|
30
34
|
id_wl,
|
|
31
|
-
id_account
|
|
35
|
+
id_account,
|
|
36
|
+
gateway,
|
|
37
|
+
country
|
|
32
38
|
});
|
|
33
39
|
const cachedData = await this.cache.select(cacheKey);
|
|
34
40
|
if (cachedData.status == 1 && "data" in cachedData && cachedData.data) {
|
|
@@ -61,9 +67,12 @@ class PlansService {
|
|
|
61
67
|
}
|
|
62
68
|
async findById(idPlan) {
|
|
63
69
|
const { id_wl, id_account } = await getUserContext();
|
|
70
|
+
const { gateway, country } = await accountService.findCurrentAccount();
|
|
64
71
|
const cacheKey = this.buildCacheKey(`plan-${idPlan}-v3`, {
|
|
65
72
|
id_wl,
|
|
66
|
-
id_account: String(id_account)
|
|
73
|
+
id_account: String(id_account),
|
|
74
|
+
gateway,
|
|
75
|
+
country
|
|
67
76
|
});
|
|
68
77
|
const cachedData = await this.cache.select(cacheKey);
|
|
69
78
|
if (cachedData.status == 1 && "data" in cachedData && cachedData.data) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../src/modules/plans/services/plans.service.ts"],"sourcesContent":["import \"server-only\";\n\nimport { api, getUserContext } from \"@greatapps/common/server\";\nimport { ApiError, buildQueryParams, PlanSchema } from \"@greatapps/common\";\nimport type {\n ApiPaginatedActionResult,\n PaginatedSuccessResult,\n Plan,\n SuccessResult,\n} from \"@greatapps/common\";\nimport greatCache from \"@greatapps/cache\";\n\nexport type ListPlansParams = {\n sort?: string;\n active?: boolean;\n search?: string;\n};\n\nconst PLANS_CACHE_TTL = 21600;\n\nclass PlansService {\n private cache = new greatCache({\n service: \"plans\",\n version: \"1.0\",\n domain: \"whitelabel-cache.greatapps.com.br\",\n ambient: process.env.NODE_ENV || \"development\",\n });\n\n private buildCacheKey(key: string, params?: Record<string, unknown>): string {\n const idWl = params?.id_wl ?? \"\";\n const idAccount = params?.id_account ?? \"\";\n const
|
|
1
|
+
{"version":3,"sources":["../../../../src/modules/plans/services/plans.service.ts"],"sourcesContent":["import \"server-only\";\n\nimport { api, getUserContext } from \"@greatapps/common/server\";\nimport { ApiError, buildQueryParams, PlanSchema } from \"@greatapps/common\";\nimport type {\n ApiPaginatedActionResult,\n PaginatedSuccessResult,\n Plan,\n SuccessResult,\n} from \"@greatapps/common\";\nimport greatCache from \"@greatapps/cache\";\nimport { accountService } from \"../../accounts/services/account.service\";\n\nexport type ListPlansParams = {\n sort?: string;\n active?: boolean;\n search?: string;\n};\n\nconst PLANS_CACHE_TTL = 21600;\n\nclass PlansService {\n private cache = new greatCache({\n service: \"plans\",\n version: \"1.0\",\n domain: \"whitelabel-cache.greatapps.com.br\",\n ambient: process.env.NODE_ENV || \"development\",\n });\n\n private buildCacheKey(key: string, params?: Record<string, unknown>): string {\n const idWl = params?.id_wl ?? \"\";\n const idAccount = params?.id_account ?? \"\";\n const gateway = params?.gateway ?? \"\";\n const country = params?.country ?? \"\";\n const sort = params?.sort ?? \"id:ASC\";\n const active = params?.active ?? \"\";\n const search = params?.search ?? \"\";\n return `${key}-${idWl}-${idAccount}-${gateway}-${country}-${sort}-${active}-${search}-v8`;\n }\n\n /**\n * Lista planos do whitelabel.\n * Exemplo:\n * GET /{id_wl}/plans?active=true&search=client&sort=id:ASC\n */\n async listPlans(\n params?: ListPlansParams,\n ): Promise<PaginatedSuccessResult<Plan>> {\n const { id_wl, id_account } = await getUserContext();\n const { gateway, country } = await accountService.findCurrentAccount();\n const cacheKey = this.buildCacheKey(\"plans\", {\n ...params,\n id_wl,\n id_account,\n gateway,\n country,\n });\n\n const cachedData = await this.cache.select(cacheKey);\n\n if (cachedData.status == 1 && \"data\" in cachedData && cachedData.data) {\n const data = JSON.parse(cachedData.data) as Plan[];\n return { data, total: data.length, success: true };\n }\n\n const query = buildQueryParams({\n sort: params?.sort ?? \"id:ASC\",\n active: params?.active,\n search: params?.search,\n id_account: id_account,\n });\n const url = `/plans${query ? `?${query}` : \"\"}`;\n\n const response = await api.apps.get<ApiPaginatedActionResult<Plan[]>>(url);\n\n if (response.status === 0) {\n throw new ApiError(\n (response as { message?: string }).message || \"Erro ao listar planos\",\n \"LIST_PLANS_FAILED\",\n 400,\n );\n }\n\n const rawData = (response as { data?: unknown }).data;\n const data = Array.isArray(rawData)\n ? rawData.map((item) => PlanSchema.parse(item))\n : [];\n\n await this.cache.insert(cacheKey, JSON.stringify(data), PLANS_CACHE_TTL);\n\n return {\n data,\n total: response.total,\n success: true,\n } satisfies PaginatedSuccessResult<Plan>;\n }\n\n async findById(idPlan: number | string): Promise<SuccessResult<Plan>> {\n const { id_wl, id_account } = await getUserContext();\n const { gateway, country } = await accountService.findCurrentAccount();\n const cacheKey = this.buildCacheKey(`plan-${idPlan}-v3`, {\n id_wl,\n id_account: String(id_account),\n gateway,\n country,\n });\n\n const cachedData = await this.cache.select(cacheKey);\n\n if (cachedData.status == 1 && \"data\" in cachedData && cachedData.data) {\n const data = JSON.parse(cachedData.data) as Plan;\n return { data, success: true };\n }\n\n const query = buildQueryParams({ type: \"client\", id_account });\n\n const response = await api.apps.get<ApiPaginatedActionResult<Plan>>(\n `/plans/${idPlan}?${query}`,\n );\n\n if (response.status === 0) {\n throw new ApiError(\n response.message || \"Erro ao buscar plano\",\n \"FIND_PLAN_FAILED\",\n 400,\n );\n }\n\n if (!response.data?.length) {\n throw new ApiError(\"Plano não encontrado\", \"PLAN_NOT_FOUND\", 404);\n }\n\n const plan = PlanSchema.parse(response.data[0]);\n\n await this.cache.insert(cacheKey, JSON.stringify(plan), PLANS_CACHE_TTL);\n\n return {\n success: true,\n data: plan,\n };\n }\n}\n\nexport const plansService = new PlansService();\n"],"mappings":"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","names":["data"]}
|
package/package.json
CHANGED
package/src/infra/api/client.ts
CHANGED
|
@@ -1,263 +1,309 @@
|
|
|
1
|
-
import { ApiError } from "./types";
|
|
2
|
-
import { findWhitelabel } from "../../modules/whitelabel/actions/find-whitelabel.action";
|
|
3
|
-
|
|
4
|
-
export interface ApiClientOptions {
|
|
5
|
-
/**
|
|
6
|
-
* Quando true, monta `{base}/{locale}/{wlId}{endpoint}` (ex.: r2-clone).
|
|
7
|
-
* Padrão false: `{base}/v1/{locale}/{wlId}{endpoint}`.
|
|
8
|
-
*/
|
|
9
|
-
omitApiVersion?: boolean;
|
|
10
|
-
|
|
11
|
-
/** Habilita logs detalhados das requisições (útil para desenvolvimento) */
|
|
12
|
-
emitLogs?: boolean;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
export interface RequestConfig extends RequestInit {
|
|
16
|
-
timeout?: number;
|
|
17
|
-
/** Padrão `json`. Use `text` quando a API devolve corpo não-JSON. */
|
|
18
|
-
responseType?: "json" | "text";
|
|
19
|
-
/** Força um whiteLabelId específico na URL, ignorando o findWhitelabel(). */
|
|
20
|
-
whiteLabelId?: number;
|
|
21
|
-
/** Força um token específico no header Authorization, ignorando o findWhitelabel(). */
|
|
22
|
-
authToken?: string;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
class ApiClient {
|
|
26
|
-
private readonly baseUrl: string;
|
|
27
|
-
private readonly apiVersion = "v1";
|
|
28
|
-
private readonly apiLocale = "pt-br";
|
|
29
|
-
private readonly omitApiVersion: boolean;
|
|
30
|
-
private readonly emitLogs: boolean = false;
|
|
31
|
-
|
|
32
|
-
constructor(baseUrl: string, options?: ApiClientOptions) {
|
|
33
|
-
this.baseUrl = baseUrl.replace(/\/$/, "");
|
|
34
|
-
this.omitApiVersion = options?.omitApiVersion ?? false;
|
|
35
|
-
this.emitLogs = options?.emitLogs ?? false;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
private async buildUrl(
|
|
39
|
-
endpoint: string,
|
|
40
|
-
whiteLabelId: number,
|
|
41
|
-
): Promise<string> {
|
|
42
|
-
const wlPath = this.omitApiVersion
|
|
43
|
-
? `/${this.apiLocale}/${whiteLabelId}`
|
|
44
|
-
: `/${this.apiVersion}/${this.apiLocale}/${whiteLabelId}`;
|
|
45
|
-
const path = endpoint.startsWith("/") ? endpoint : `/${endpoint}`;
|
|
46
|
-
const url = `${this.baseUrl}${wlPath}${path}`;
|
|
47
|
-
if (this.emitLogs) {
|
|
48
|
-
console.log("[ApiClient] buildUrl", {
|
|
49
|
-
baseURL: this.baseUrl,
|
|
50
|
-
whiteLabelId,
|
|
51
|
-
endpoint,
|
|
52
|
-
fullUrl: url,
|
|
53
|
-
});
|
|
54
|
-
}
|
|
55
|
-
return url;
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
private async getDefaultHeaders(
|
|
59
|
-
token: string,
|
|
60
|
-
isFormData = false,
|
|
61
|
-
): Promise<HeadersInit> {
|
|
62
|
-
if (this.emitLogs) {
|
|
63
|
-
console.log("[ApiClient] getDefaultHeaders", {
|
|
64
|
-
token: token.substring(0, 20) + "...",
|
|
65
|
-
});
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
const headers: HeadersInit = {
|
|
69
|
-
authorization: token,
|
|
70
|
-
};
|
|
71
|
-
|
|
72
|
-
if (!isFormData) {
|
|
73
|
-
headers["Content-Type"] = "application/json";
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
return headers;
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
async request<T>(endpoint: string, config: RequestConfig = {}): Promise<T> {
|
|
80
|
-
if (this.emitLogs) {
|
|
81
|
-
console.log("[ApiClient] request called", {
|
|
82
|
-
endpoint,
|
|
83
|
-
method: config.method,
|
|
84
|
-
});
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
const { timeout = 30000, responseType = "json", whiteLabelId: overrideWlId, authToken: overrideToken, ...fetchConfig } = config;
|
|
88
|
-
|
|
89
|
-
const controller = new AbortController();
|
|
90
|
-
const timeoutId = setTimeout(() => controller.abort(), timeout);
|
|
91
|
-
|
|
92
|
-
try {
|
|
93
|
-
if (this.emitLogs) {
|
|
94
|
-
console.log("[ApiClient] Building URL and headers...");
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
const { id, token } = await findWhitelabel();
|
|
98
|
-
|
|
99
|
-
const [url, defaultHeaders] = await Promise.all([
|
|
100
|
-
this.buildUrl(endpoint, overrideWlId ?? id),
|
|
101
|
-
this.getDefaultHeaders(overrideToken ?? token, fetchConfig.body instanceof FormData),
|
|
102
|
-
]);
|
|
103
|
-
|
|
104
|
-
if (this.emitLogs)
|
|
105
|
-
console.log("[ApiClient] Fetching", {
|
|
106
|
-
url,
|
|
107
|
-
method: fetchConfig.method,
|
|
108
|
-
headers: defaultHeaders,
|
|
109
|
-
});
|
|
110
|
-
|
|
111
|
-
const response = await fetch(url, {
|
|
112
|
-
...fetchConfig,
|
|
113
|
-
headers: {
|
|
114
|
-
...defaultHeaders,
|
|
115
|
-
...fetchConfig.headers,
|
|
116
|
-
},
|
|
117
|
-
signal: controller.signal,
|
|
118
|
-
});
|
|
119
|
-
|
|
120
|
-
if (this.emitLogs)
|
|
121
|
-
console.log("[ApiClient] Response received", {
|
|
122
|
-
status: response.status,
|
|
123
|
-
ok: response.ok,
|
|
124
|
-
});
|
|
125
|
-
|
|
126
|
-
if (!response.ok) {
|
|
127
|
-
const errBody = await response.text();
|
|
128
|
-
let message = `HTTP error ${response.status} ${response.statusText} ${errBody}`;
|
|
129
|
-
let code = "HTTP_ERROR";
|
|
130
|
-
try {
|
|
131
|
-
const errorData = JSON.parse(errBody) as Record<string, unknown>;
|
|
132
|
-
// @ts-ignore -- msg pode ser string ou objeto com .message
|
|
133
|
-
message = errorData.message?.message ?? errorData.message ?? message;
|
|
134
|
-
if (typeof errorData.code === "string") code = errorData.code;
|
|
135
|
-
} catch {
|
|
136
|
-
if (errBody.trim()) message = errBody.trim();
|
|
137
|
-
}
|
|
138
|
-
const method = fetchConfig.method ?? "GET";
|
|
139
|
-
let bodyPreview: unknown;
|
|
140
|
-
if (method !== "GET" && fetchConfig.body != null) {
|
|
141
|
-
if (typeof fetchConfig.body === "string") {
|
|
142
|
-
try {
|
|
143
|
-
bodyPreview = JSON.parse(fetchConfig.body);
|
|
144
|
-
} catch {
|
|
145
|
-
bodyPreview = fetchConfig.body;
|
|
146
|
-
}
|
|
147
|
-
} else if (fetchConfig.body instanceof FormData) {
|
|
148
|
-
bodyPreview = "[FormData]";
|
|
149
|
-
} else {
|
|
150
|
-
bodyPreview = fetchConfig.body;
|
|
151
|
-
}
|
|
152
|
-
}
|
|
153
|
-
console.error("[ApiClient] Request failed", {
|
|
154
|
-
from: url,
|
|
155
|
-
method,
|
|
156
|
-
...(bodyPreview !== undefined ? { body: bodyPreview } : {}),
|
|
157
|
-
status: response.status,
|
|
158
|
-
message,
|
|
159
|
-
});
|
|
160
|
-
throw new ApiError(message, code, response.status);
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
if (responseType === "text") {
|
|
164
|
-
return (await response.text()) as T;
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
const data = (await response.json()) as T;
|
|
168
|
-
if (this.emitLogs)
|
|
169
|
-
console.log("[ApiClient] Request successful", { hasData: !!data });
|
|
170
|
-
return data;
|
|
171
|
-
} catch (error) {
|
|
172
|
-
if (this.emitLogs) console.error("[ApiClient] Request error", { error });
|
|
173
|
-
|
|
174
|
-
if (error instanceof ApiError) {
|
|
175
|
-
throw error;
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
if (error instanceof Error) {
|
|
179
|
-
if (error.name === "AbortError") {
|
|
180
|
-
throw new ApiError("Tempo de requisição excedido", "TIMEOUT", 408);
|
|
181
|
-
}
|
|
182
|
-
throw new ApiError(error.message, "NETWORK_ERROR", 0);
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
throw new ApiError("Erro desconhecido", "UNKNOWN_ERROR", 500);
|
|
186
|
-
} finally {
|
|
187
|
-
clearTimeout(timeoutId);
|
|
188
|
-
}
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
async get<T>(endpoint: string, config?: RequestConfig): Promise<T> {
|
|
192
|
-
return this.request<T>(endpoint, { ...config, method: "GET" });
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
async post<T>(
|
|
196
|
-
endpoint: string,
|
|
197
|
-
data?: unknown,
|
|
198
|
-
config?: RequestConfig,
|
|
199
|
-
): Promise<T> {
|
|
200
|
-
return this.request<T>(endpoint, {
|
|
201
|
-
...config,
|
|
202
|
-
method: "POST",
|
|
203
|
-
body:
|
|
204
|
-
data instanceof FormData
|
|
205
|
-
? data
|
|
206
|
-
: data
|
|
207
|
-
? JSON.stringify(data)
|
|
208
|
-
: undefined,
|
|
209
|
-
});
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
async put<T>(
|
|
213
|
-
endpoint: string,
|
|
214
|
-
data?: unknown,
|
|
215
|
-
config?: RequestConfig,
|
|
216
|
-
): Promise<T> {
|
|
217
|
-
return this.request<T>(endpoint, {
|
|
218
|
-
...config,
|
|
219
|
-
method: "PUT",
|
|
220
|
-
body:
|
|
221
|
-
data instanceof FormData
|
|
222
|
-
? data
|
|
223
|
-
: data
|
|
224
|
-
? JSON.stringify(data)
|
|
225
|
-
: undefined,
|
|
226
|
-
});
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
async patch<T>(
|
|
230
|
-
endpoint: string,
|
|
231
|
-
data?: unknown,
|
|
232
|
-
config?: RequestConfig,
|
|
233
|
-
): Promise<T> {
|
|
234
|
-
return this.request<T>(endpoint, {
|
|
235
|
-
...config,
|
|
236
|
-
method: "PATCH",
|
|
237
|
-
body:
|
|
238
|
-
data instanceof FormData
|
|
239
|
-
? data
|
|
240
|
-
: data
|
|
241
|
-
? JSON.stringify(data)
|
|
242
|
-
: undefined,
|
|
243
|
-
});
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
async delete<T>(endpoint: string, config?: RequestConfig): Promise<T> {
|
|
247
|
-
return this.request<T>(endpoint, { ...config, method: "DELETE" });
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
1
|
+
import { ApiError } from "./types";
|
|
2
|
+
import { findWhitelabel } from "../../modules/whitelabel/actions/find-whitelabel.action";
|
|
3
|
+
|
|
4
|
+
export interface ApiClientOptions {
|
|
5
|
+
/**
|
|
6
|
+
* Quando true, monta `{base}/{locale}/{wlId}{endpoint}` (ex.: r2-clone).
|
|
7
|
+
* Padrão false: `{base}/v1/{locale}/{wlId}{endpoint}`.
|
|
8
|
+
*/
|
|
9
|
+
omitApiVersion?: boolean;
|
|
10
|
+
|
|
11
|
+
/** Habilita logs detalhados das requisições (útil para desenvolvimento) */
|
|
12
|
+
emitLogs?: boolean;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface RequestConfig extends RequestInit {
|
|
16
|
+
timeout?: number;
|
|
17
|
+
/** Padrão `json`. Use `text` quando a API devolve corpo não-JSON. */
|
|
18
|
+
responseType?: "json" | "text";
|
|
19
|
+
/** Força um whiteLabelId específico na URL, ignorando o findWhitelabel(). */
|
|
20
|
+
whiteLabelId?: number;
|
|
21
|
+
/** Força um token específico no header Authorization, ignorando o findWhitelabel(). */
|
|
22
|
+
authToken?: string;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
class ApiClient {
|
|
26
|
+
private readonly baseUrl: string;
|
|
27
|
+
private readonly apiVersion = "v1";
|
|
28
|
+
private readonly apiLocale = "pt-br";
|
|
29
|
+
private readonly omitApiVersion: boolean;
|
|
30
|
+
private readonly emitLogs: boolean = false;
|
|
31
|
+
|
|
32
|
+
constructor(baseUrl: string, options?: ApiClientOptions) {
|
|
33
|
+
this.baseUrl = baseUrl.replace(/\/$/, "");
|
|
34
|
+
this.omitApiVersion = options?.omitApiVersion ?? false;
|
|
35
|
+
this.emitLogs = options?.emitLogs ?? false;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
private async buildUrl(
|
|
39
|
+
endpoint: string,
|
|
40
|
+
whiteLabelId: number,
|
|
41
|
+
): Promise<string> {
|
|
42
|
+
const wlPath = this.omitApiVersion
|
|
43
|
+
? `/${this.apiLocale}/${whiteLabelId}`
|
|
44
|
+
: `/${this.apiVersion}/${this.apiLocale}/${whiteLabelId}`;
|
|
45
|
+
const path = endpoint.startsWith("/") ? endpoint : `/${endpoint}`;
|
|
46
|
+
const url = `${this.baseUrl}${wlPath}${path}`;
|
|
47
|
+
if (this.emitLogs) {
|
|
48
|
+
console.log("[ApiClient] buildUrl", {
|
|
49
|
+
baseURL: this.baseUrl,
|
|
50
|
+
whiteLabelId,
|
|
51
|
+
endpoint,
|
|
52
|
+
fullUrl: url,
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
return url;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
private async getDefaultHeaders(
|
|
59
|
+
token: string,
|
|
60
|
+
isFormData = false,
|
|
61
|
+
): Promise<HeadersInit> {
|
|
62
|
+
if (this.emitLogs) {
|
|
63
|
+
console.log("[ApiClient] getDefaultHeaders", {
|
|
64
|
+
token: token.substring(0, 20) + "...",
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const headers: HeadersInit = {
|
|
69
|
+
authorization: token,
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
if (!isFormData) {
|
|
73
|
+
headers["Content-Type"] = "application/json";
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
return headers;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
async request<T>(endpoint: string, config: RequestConfig = {}): Promise<T> {
|
|
80
|
+
if (this.emitLogs) {
|
|
81
|
+
console.log("[ApiClient] request called", {
|
|
82
|
+
endpoint,
|
|
83
|
+
method: config.method,
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const { timeout = 30000, responseType = "json", whiteLabelId: overrideWlId, authToken: overrideToken, ...fetchConfig } = config;
|
|
88
|
+
|
|
89
|
+
const controller = new AbortController();
|
|
90
|
+
const timeoutId = setTimeout(() => controller.abort(), timeout);
|
|
91
|
+
|
|
92
|
+
try {
|
|
93
|
+
if (this.emitLogs) {
|
|
94
|
+
console.log("[ApiClient] Building URL and headers...");
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const { id, token } = await findWhitelabel();
|
|
98
|
+
|
|
99
|
+
const [url, defaultHeaders] = await Promise.all([
|
|
100
|
+
this.buildUrl(endpoint, overrideWlId ?? id),
|
|
101
|
+
this.getDefaultHeaders(overrideToken ?? token, fetchConfig.body instanceof FormData),
|
|
102
|
+
]);
|
|
103
|
+
|
|
104
|
+
if (this.emitLogs)
|
|
105
|
+
console.log("[ApiClient] Fetching", {
|
|
106
|
+
url,
|
|
107
|
+
method: fetchConfig.method,
|
|
108
|
+
headers: defaultHeaders,
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
const response = await fetch(url, {
|
|
112
|
+
...fetchConfig,
|
|
113
|
+
headers: {
|
|
114
|
+
...defaultHeaders,
|
|
115
|
+
...fetchConfig.headers,
|
|
116
|
+
},
|
|
117
|
+
signal: controller.signal,
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
if (this.emitLogs)
|
|
121
|
+
console.log("[ApiClient] Response received", {
|
|
122
|
+
status: response.status,
|
|
123
|
+
ok: response.ok,
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
if (!response.ok) {
|
|
127
|
+
const errBody = await response.text();
|
|
128
|
+
let message = `HTTP error ${response.status} ${response.statusText} ${errBody}`;
|
|
129
|
+
let code = "HTTP_ERROR";
|
|
130
|
+
try {
|
|
131
|
+
const errorData = JSON.parse(errBody) as Record<string, unknown>;
|
|
132
|
+
// @ts-ignore -- msg pode ser string ou objeto com .message
|
|
133
|
+
message = errorData.message?.message ?? errorData.message ?? message;
|
|
134
|
+
if (typeof errorData.code === "string") code = errorData.code;
|
|
135
|
+
} catch {
|
|
136
|
+
if (errBody.trim()) message = errBody.trim();
|
|
137
|
+
}
|
|
138
|
+
const method = fetchConfig.method ?? "GET";
|
|
139
|
+
let bodyPreview: unknown;
|
|
140
|
+
if (method !== "GET" && fetchConfig.body != null) {
|
|
141
|
+
if (typeof fetchConfig.body === "string") {
|
|
142
|
+
try {
|
|
143
|
+
bodyPreview = JSON.parse(fetchConfig.body);
|
|
144
|
+
} catch {
|
|
145
|
+
bodyPreview = fetchConfig.body;
|
|
146
|
+
}
|
|
147
|
+
} else if (fetchConfig.body instanceof FormData) {
|
|
148
|
+
bodyPreview = "[FormData]";
|
|
149
|
+
} else {
|
|
150
|
+
bodyPreview = fetchConfig.body;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
console.error("[ApiClient] Request failed", {
|
|
154
|
+
from: url,
|
|
155
|
+
method,
|
|
156
|
+
...(bodyPreview !== undefined ? { body: bodyPreview } : {}),
|
|
157
|
+
status: response.status,
|
|
158
|
+
message,
|
|
159
|
+
});
|
|
160
|
+
throw new ApiError(message, code, response.status);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
if (responseType === "text") {
|
|
164
|
+
return (await response.text()) as T;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
const data = (await response.json()) as T;
|
|
168
|
+
if (this.emitLogs)
|
|
169
|
+
console.log("[ApiClient] Request successful", { hasData: !!data });
|
|
170
|
+
return data;
|
|
171
|
+
} catch (error) {
|
|
172
|
+
if (this.emitLogs) console.error("[ApiClient] Request error", { error });
|
|
173
|
+
|
|
174
|
+
if (error instanceof ApiError) {
|
|
175
|
+
throw error;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
if (error instanceof Error) {
|
|
179
|
+
if (error.name === "AbortError") {
|
|
180
|
+
throw new ApiError("Tempo de requisição excedido", "TIMEOUT", 408);
|
|
181
|
+
}
|
|
182
|
+
throw new ApiError(error.message, "NETWORK_ERROR", 0);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
throw new ApiError("Erro desconhecido", "UNKNOWN_ERROR", 500);
|
|
186
|
+
} finally {
|
|
187
|
+
clearTimeout(timeoutId);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
async get<T>(endpoint: string, config?: RequestConfig): Promise<T> {
|
|
192
|
+
return this.request<T>(endpoint, { ...config, method: "GET" });
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
async post<T>(
|
|
196
|
+
endpoint: string,
|
|
197
|
+
data?: unknown,
|
|
198
|
+
config?: RequestConfig,
|
|
199
|
+
): Promise<T> {
|
|
200
|
+
return this.request<T>(endpoint, {
|
|
201
|
+
...config,
|
|
202
|
+
method: "POST",
|
|
203
|
+
body:
|
|
204
|
+
data instanceof FormData
|
|
205
|
+
? data
|
|
206
|
+
: data
|
|
207
|
+
? JSON.stringify(data)
|
|
208
|
+
: undefined,
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
async put<T>(
|
|
213
|
+
endpoint: string,
|
|
214
|
+
data?: unknown,
|
|
215
|
+
config?: RequestConfig,
|
|
216
|
+
): Promise<T> {
|
|
217
|
+
return this.request<T>(endpoint, {
|
|
218
|
+
...config,
|
|
219
|
+
method: "PUT",
|
|
220
|
+
body:
|
|
221
|
+
data instanceof FormData
|
|
222
|
+
? data
|
|
223
|
+
: data
|
|
224
|
+
? JSON.stringify(data)
|
|
225
|
+
: undefined,
|
|
226
|
+
});
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
async patch<T>(
|
|
230
|
+
endpoint: string,
|
|
231
|
+
data?: unknown,
|
|
232
|
+
config?: RequestConfig,
|
|
233
|
+
): Promise<T> {
|
|
234
|
+
return this.request<T>(endpoint, {
|
|
235
|
+
...config,
|
|
236
|
+
method: "PATCH",
|
|
237
|
+
body:
|
|
238
|
+
data instanceof FormData
|
|
239
|
+
? data
|
|
240
|
+
: data
|
|
241
|
+
? JSON.stringify(data)
|
|
242
|
+
: undefined,
|
|
243
|
+
});
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
async delete<T>(endpoint: string, config?: RequestConfig): Promise<T> {
|
|
247
|
+
return this.request<T>(endpoint, { ...config, method: "DELETE" });
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
/**
|
|
251
|
+
* Requisição cuja resposta é consumida em stream: devolve a `Response` crua, sem ler o
|
|
252
|
+
* corpo e sem timeout.
|
|
253
|
+
*
|
|
254
|
+
* `request()` não serve para isso por dois motivos. Ele faz `await response.json()`, o
|
|
255
|
+
* que espera o corpo inteiro e mata o propósito do stream; e aborta em 30s, que é o
|
|
256
|
+
* tempo que uma operação longa leva justamente por ser longa — gerar o link de uma
|
|
257
|
+
* página com 132 imagens copia 100 MB e passa disso (ID-5112).
|
|
258
|
+
*
|
|
259
|
+
* O corpo é do chamador: quem recebe decide quando ler e quando cancelar.
|
|
260
|
+
*/
|
|
261
|
+
async stream(endpoint: string, config: RequestConfig = {}): Promise<Response> {
|
|
262
|
+
const { timeout: _timeout, responseType: _responseType, whiteLabelId: overrideWlId, authToken: overrideToken, ...fetchConfig } = config;
|
|
263
|
+
|
|
264
|
+
// Só resolve o whitelabel quando falta algum dos dois: com ambos vindos do chamador, a
|
|
265
|
+
// consulta seria uma ida à rede para descartar o resultado.
|
|
266
|
+
const resolvido = overrideWlId != null && overrideToken != null
|
|
267
|
+
? { id: overrideWlId, token: overrideToken }
|
|
268
|
+
: await findWhitelabel();
|
|
269
|
+
|
|
270
|
+
const [url, defaultHeaders] = await Promise.all([
|
|
271
|
+
this.buildUrl(endpoint, overrideWlId ?? resolvido.id),
|
|
272
|
+
this.getDefaultHeaders(overrideToken ?? resolvido.token),
|
|
273
|
+
]);
|
|
274
|
+
|
|
275
|
+
const response = await fetch(url, {
|
|
276
|
+
...fetchConfig,
|
|
277
|
+
headers: { ...defaultHeaders, ...fetchConfig.headers },
|
|
278
|
+
// Sem isto o fetch do Next segura o corpo inteiro antes de devolver, e o stream chega
|
|
279
|
+
// completo de uma vez no fim: medido em 32s até o primeiro evento, contra 0,4s com
|
|
280
|
+
// no-store. O progresso existiria no protocolo e não na tela.
|
|
281
|
+
cache: "no-store",
|
|
282
|
+
});
|
|
283
|
+
|
|
284
|
+
if (!response.ok || !response.body) {
|
|
285
|
+
const detalhe = await response.text().catch(() => "");
|
|
286
|
+
throw new ApiError(
|
|
287
|
+
detalhe.trim() || `HTTP error ${response.status} ${response.statusText}`,
|
|
288
|
+
"HTTP_ERROR",
|
|
289
|
+
response.status,
|
|
290
|
+
);
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
return response;
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
const api = {
|
|
298
|
+
apps: new ApiClient(
|
|
299
|
+
process.env.GAPPS_R3_API_URL || "https://r3-api.greatapps.dev.br",
|
|
300
|
+
),
|
|
301
|
+
pages: new ApiClient(
|
|
302
|
+
process.env.GPAGES_R3_API_URL || "https://r3-api.greatpages.dev.br",
|
|
303
|
+
),
|
|
304
|
+
};
|
|
305
|
+
|
|
306
|
+
/** @deprecated use api.apps */
|
|
307
|
+
export const apiClient = api.apps;
|
|
308
|
+
|
|
309
|
+
export { api, ApiClient };
|
|
@@ -9,6 +9,7 @@ import type {
|
|
|
9
9
|
SuccessResult,
|
|
10
10
|
} from "@greatapps/common";
|
|
11
11
|
import greatCache from "@greatapps/cache";
|
|
12
|
+
import { accountService } from "../../accounts/services/account.service";
|
|
12
13
|
|
|
13
14
|
export type ListPlansParams = {
|
|
14
15
|
sort?: string;
|
|
@@ -29,11 +30,12 @@ class PlansService {
|
|
|
29
30
|
private buildCacheKey(key: string, params?: Record<string, unknown>): string {
|
|
30
31
|
const idWl = params?.id_wl ?? "";
|
|
31
32
|
const idAccount = params?.id_account ?? "";
|
|
33
|
+
const gateway = params?.gateway ?? "";
|
|
34
|
+
const country = params?.country ?? "";
|
|
32
35
|
const sort = params?.sort ?? "id:ASC";
|
|
33
36
|
const active = params?.active ?? "";
|
|
34
37
|
const search = params?.search ?? "";
|
|
35
|
-
|
|
36
|
-
return `${key}-${idWl}-${idAccount}-${sort}-${active}-${search}-v7`;
|
|
38
|
+
return `${key}-${idWl}-${idAccount}-${gateway}-${country}-${sort}-${active}-${search}-v8`;
|
|
37
39
|
}
|
|
38
40
|
|
|
39
41
|
/**
|
|
@@ -45,10 +47,13 @@ class PlansService {
|
|
|
45
47
|
params?: ListPlansParams,
|
|
46
48
|
): Promise<PaginatedSuccessResult<Plan>> {
|
|
47
49
|
const { id_wl, id_account } = await getUserContext();
|
|
50
|
+
const { gateway, country } = await accountService.findCurrentAccount();
|
|
48
51
|
const cacheKey = this.buildCacheKey("plans", {
|
|
49
52
|
...params,
|
|
50
53
|
id_wl,
|
|
51
54
|
id_account,
|
|
55
|
+
gateway,
|
|
56
|
+
country,
|
|
52
57
|
});
|
|
53
58
|
|
|
54
59
|
const cachedData = await this.cache.select(cacheKey);
|
|
@@ -92,9 +97,12 @@ class PlansService {
|
|
|
92
97
|
|
|
93
98
|
async findById(idPlan: number | string): Promise<SuccessResult<Plan>> {
|
|
94
99
|
const { id_wl, id_account } = await getUserContext();
|
|
100
|
+
const { gateway, country } = await accountService.findCurrentAccount();
|
|
95
101
|
const cacheKey = this.buildCacheKey(`plan-${idPlan}-v3`, {
|
|
96
102
|
id_wl,
|
|
97
103
|
id_account: String(id_account),
|
|
104
|
+
gateway,
|
|
105
|
+
country,
|
|
98
106
|
});
|
|
99
107
|
|
|
100
108
|
const cachedData = await this.cache.select(cacheKey);
|