mirror of
https://github.com/melgarafael/DeskcommCRM.git
synced 2026-10-02 01:28:34 +08:00
Extraído do PR #465 de @prevprocesso-maker. O achado, o arquivo e a forma da validação são dele; o que mudou aqui foi onde ele é ligado e o que o vigia. ─── O defeito ────────────────────────────────────────────────────────────────── `UPSTASH_REDIS_REST_URL` e `_TOKEN` são `required()` puro em `lib/env.ts` — sem `.url()`, sem forma. Qualquer texto passa pelo Zod. E a forma errada mais comum do self-host não é digitação: é o valor saindo do `.env` com o que estava em volta dele. UPSTASH_REDIS_REST_URL="https://srh:80" → as aspas entram UPSTASH_REDIS_REST_TOKEN=UPSTASH_REDIS_REST_TOKEN=abc → a linha inteira UPSTASH_REDIS_REST_TOKEN=abc\n → a quebra do heredoc Nenhum era rejeitado. Todos chegavam ao `fetch` e falhavam — com o MESMO `reason` de alcance que sai quando o contêiner do Redis está parado de verdade. As duas leituras mandam quem opera para lugares opostos: reiniciar um serviço saudável, ou abrir o editor. É o modo de falha já catalogado nesta casa como ".env sem aspas", e ele custou uma sessão de QA de instalação em VPS. ─── Os três consumidores ─────────────────────────────────────────────────────── app/api/v1/health/route.ts `reason: "configuracao_invalida"`, e sem ida à rede — um endereço que não é endereço não tem o que ser tentado lib/ai/dispatcher/rate-limit.ts o contador de login para de construir um cliente condenado a falhar em toda chamada lib/ai/rag/debounce.ts idem, e sem pagar os 2s do `Promise.race` por evento para chegar ao mesmo fallback O texto do check de saúde continua carregando o endereço de propósito: `semAlvo()` o redige para quem não tem o segredo interno, e quem tem precisa ver QUAL valor está malformado. As três guardas de `route.test.ts` — a que já vinha deste mesmo PR — seguem verdes, inclusive a que prova que o modo verboso NÃO redige. ─── O que NÃO veio do #465 junto ─────────────────────────────────────────────── O autor também trouxe `isOptionalEndpointUnconfigured()`, que trata `localhost`/`127.0.0.1` como "não configurado" e rebaixa o check de `down` para `degraded`. Isso resolve o deploy dele na Vercel, onde o endereço é um placeholder — e cobra da VPS: numa instalação de nó único, onde WAHA e Redis moram em `127.0.0.1` de verdade, uma queda REAL passaria a reportar "não configurado / degradado", e o `/health` deixaria de virar 503. Monitoramento que mente sobre uma queda é pior que monitoramento nenhum. Fica de fora. Sabotagem, previsão escrita antes de rodar e conferida caso a caso: tirar o ramo configuracao_invalida do checkRedis -> 1 vermelho (previsto 1) validador devolvendo sempre `ok` -> 14 vermelhos (previsto 14) tirar a checagem SÓ do debounce (o irmão) -> 1 vermelho (previsto 1) A terceira existe porque os dois `getRedis()` são irmãos que não se parecem por fora: consertar um e deixar o outro é o desfecho natural, e sem esse caso ele ficaria verde. Co-Authored-By: prevprocesso-maker <prevprocesso-maker@users.noreply.github.com> Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
88 lines
3.7 KiB
TypeScript
88 lines
3.7 KiB
TypeScript
/**
|
|
* A CONFIGURAÇÃO DO REDIS É CONFERIDA ANTES DE VIRAR CLIENTE.
|
|
*
|
|
* ─── O erro que isto nomeia ─────────────────────────────────────────────────
|
|
*
|
|
* `UPSTASH_REDIS_REST_URL` e `UPSTASH_REDIS_REST_TOKEN` são `required()` puro em
|
|
* `lib/env.ts` — sem `.url()`, sem forma. Qualquer texto passa pelo Zod. E a
|
|
* forma errada mais comum não é digitação: é o `.env` do self-host levando junto
|
|
* o que estava em volta do valor —
|
|
*
|
|
* UPSTASH_REDIS_REST_URL="https://srh:80" → as aspas entram no valor
|
|
* UPSTASH_REDIS_REST_TOKEN=UPSTASH_REDIS_REST_TOKEN=abc → a linha inteira
|
|
* UPSTASH_REDIS_REST_TOKEN=abc\n → a quebra vinda do heredoc
|
|
*
|
|
* Nenhum desses três é rejeitado por ninguém hoje. Os dois primeiros viram
|
|
* header HTTP inválido; o terceiro, dependendo do runtime, levanta na hora de
|
|
* montar o `fetch`. O sintoma é sempre o MESMO — o Redis "não responde" —, e ele
|
|
* aponta para o contêiner, que está de pé.
|
|
*
|
|
* ─── Por que a checagem vale mais que o retry ───────────────────────────────
|
|
*
|
|
* Sem ela, cada chamada paga uma ida à rede que não tinha como dar certo, e o
|
|
* aviso que sobra no log fala do Redis, não do arquivo que precisa ser editado.
|
|
* A diferença entre "o Redis caiu" e "o seu `.env` está malformado" é a
|
|
* diferença entre reiniciar um contêiner saudável e abrir o editor — e a casa já
|
|
* pagou por essa confusão uma vez, na QA de instalação em VPS.
|
|
*
|
|
* ─── Escopo, dito em voz alta ───────────────────────────────────────────────
|
|
*
|
|
* Isto valida a FORMA do valor, nunca se ele funciona. URL bem formada com host
|
|
* inexistente e token bem formado mas revogado passam por aqui e falham na rede,
|
|
* que é onde essas duas coisas se descobrem. O que se ganha é separar "não dá
|
|
* nem para tentar" de "tentei e não deu".
|
|
*
|
|
* Achado de @prevprocesso-maker no PR #465.
|
|
*/
|
|
|
|
export type RedisConfigReason = "ok" | "nao_configurado" | "configuracao_invalida";
|
|
|
|
export type RedisConfigStatus = {
|
|
ok: boolean;
|
|
reason: RedisConfigReason;
|
|
};
|
|
|
|
/**
|
|
* O valor tem de ser o valor PURO da variável — sem o que envolvia ele no
|
|
* arquivo. Espaço nas pontas, quebra de linha, tabulação e aspas sobrando são
|
|
* todos sinais de que o recorte pegou mais do que o valor.
|
|
*/
|
|
function valorSemFormatacaoExtra(value: string): boolean {
|
|
return (
|
|
value === value.trim() &&
|
|
!/[\r\n\t]/.test(value) &&
|
|
!value.startsWith('"') &&
|
|
!value.endsWith('"') &&
|
|
!value.startsWith("'") &&
|
|
!value.endsWith("'")
|
|
);
|
|
}
|
|
|
|
export function validarConfigRedisRest(
|
|
url: string | undefined,
|
|
token: string | undefined,
|
|
): RedisConfigStatus {
|
|
if (!url || !token) return { ok: false, reason: "nao_configurado" };
|
|
|
|
if (!valorSemFormatacaoExtra(url) || !valorSemFormatacaoExtra(token)) {
|
|
return { ok: false, reason: "configuracao_invalida" };
|
|
}
|
|
|
|
// O `.env` copiado com o nome da variável dentro do valor. Sem esta linha ele
|
|
// passaria como token opaco — que é exatamente o que um token parece.
|
|
if (token.startsWith("UPSTASH_REDIS_REST_TOKEN=") || url.startsWith("UPSTASH_REDIS_REST_URL=")) {
|
|
return { ok: false, reason: "configuracao_invalida" };
|
|
}
|
|
|
|
try {
|
|
const parsed = new URL(url);
|
|
if (!parsed.hostname || !["http:", "https:"].includes(parsed.protocol)) {
|
|
return { ok: false, reason: "configuracao_invalida" };
|
|
}
|
|
} catch {
|
|
return { ok: false, reason: "configuracao_invalida" };
|
|
}
|
|
|
|
return { ok: true, reason: "ok" };
|
|
}
|