fix(config): preprocess empty env strings to undefined for .url() fields

PCAT_SOURCE_DB_URL and EMEX_SOURCE_DB_URL are declared as
`z.string().url().optional()` (and `.optional()` resp). docker-compose's
`${VAR:-}` substitution ALWAYS sets the env var, even to "", so when the
Coolify env is unset the container receives PCAT_SOURCE_DB_URL="".
zod's `.optional()` only accepts undefined, so `.url()` then rejects ""
and the api crashes on boot with "Invalid url".

This is exactly what took prod down on commit 939e4dc — the dev→main
merge brought in the catalog-source env schema without the empty-string
preprocess. Hotfixed by setting the env to a dummy URL via Coolify DB;
this patch makes the schema resilient permanently so future env edits
that clear the value won't recrash boot.

Apply a preprocess that maps empty/whitespace strings to undefined
before the URL check fires. Mirror the same treatment on EMEX so it can
also be unset without surprises.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
2026-06-01 19:05:34 +03:00
parent 3613caa072
commit d6602a3e51

View File

@@ -118,8 +118,17 @@ export const envSchema = z.object({
.string()
.transform((v) => v === "true")
.default("false"),
PCAT_SOURCE_DB_URL: z.string().url().optional(),
EMEX_SOURCE_DB_URL: z.string().optional(), // mysql://... — not a strict URL per WHATWG
// docker-compose's `${VAR:-}` substitution always sets the env, even if to
// an empty string. zod's `.optional()` only accepts undefined, so a chained
// `.url()` would reject "" and crash boot — preprocess "" → undefined first.
PCAT_SOURCE_DB_URL: z.preprocess(
(v) => (typeof v === "string" && v.trim() === "" ? undefined : v),
z.string().url().optional(),
),
EMEX_SOURCE_DB_URL: z.preprocess(
(v) => (typeof v === "string" && v.trim() === "" ? undefined : v),
z.string().optional(),
), // mysql://... — not a strict URL per WHATWG
// Per-source kill switches under the master CATALOG_SOURCE_DB_ENABLED.
// EMEX_SOURCE_DB_ENABLED keeps the connection pool alive but, per the
// 2026-06-01 safety audit, fetchCategoryParts ALWAYS returns null unless the