fix(api): allow nest build of program-settings and cleaner replica --check

Cast stored JSON through asSettingsArray so tsc accepts the snapshot payload, and strip Prisma ?schema= from DATABASE_URL before psql.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-08-17 16:01:55 +03:30
co-authored by Cursor
parent f2cf8c9035
commit 25c0a4e941
3 changed files with 27 additions and 5 deletions
+3 -1
View File
@@ -1,6 +1,8 @@
# Copy to deploy/replicas.env on the publisher host (gitignored — holds passwords). # Copy to deploy/replicas.env on the publisher host (gitignored — holds passwords).
# #
# Every logical-replication subscriber of superadmin_pub, space/comma/newline separated. # One DSN per line (or comma). Do not put spaces between URLs.
# Prefer URI form. Password must not contain @ : / # ? — or URL-encode it.
# Keyword form (host=...) is OK only because the script splits on newlines, not spaces.
# The user must be able to run ALTER TABLE (table owner or superuser). # The user must be able to run ALTER TABLE (table owner or superuser).
REPLICA_DSNS="postgresql://USER:PASSWORD@SLAVE_1_IP:5433/superadmin REPLICA_DSNS="postgresql://USER:PASSWORD@SLAVE_1_IP:5433/superadmin
postgresql://USER:PASSWORD@SLAVE_2_IP:5432/superadmin" postgresql://USER:PASSWORD@SLAVE_2_IP:5432/superadmin"
+21 -3
View File
@@ -47,7 +47,10 @@ if [[ -z "${DATABASE_URL:-}" && -f "$API_DIR/.env" ]]; then
set -a && source "$API_DIR/.env" && set +a set -a && source "$API_DIR/.env" && set +a
fi fi
read -r -a REPLICAS <<< "$(tr ',\n' ' ' <<< "$REPLICA_DSNS")" # One DSN per line or comma (NOT spaces — keyword conninfo has spaces).
mapfile -t REPLICAS < <(
printf '%s\n' "$REPLICA_DSNS" | tr ',' '\n' | sed 's/^[[:space:]]*//;s/[[:space:]]*$//' | grep -v '^$'
)
# psql would otherwise swallow the loop input it is called from # psql would otherwise swallow the loop input it is called from
run_psql() { run_psql() {
@@ -56,7 +59,16 @@ run_psql() {
# host:port/db only — DSNs carry passwords # host:port/db only — DSNs carry passwords
label() { label() {
sed -E 's#^[a-z]+://([^@]*@)?##; s#\?.*$##' <<< "$1" local dsn="$1"
if [[ "$dsn" == *"://"* ]]; then
sed -E 's#^[a-z]+://([^@]*@)?##; s#\?.*$##' <<< "$dsn"
return
fi
local host port db
host="$(sed -nE 's/.*host=([^ ]+).*/\1/p' <<< "$dsn")"
port="$(sed -nE 's/.*port=([^ ]+).*/\1/p' <<< "$dsn")"
db="$(sed -nE 's/.*dbname=([^ ]+).*/\1/p' <<< "$dsn")"
echo "${host:-?}:${port:-5432}/${db:-?}"
} }
# every list feeding comm is sorted with the same collation # every list feeding comm is sorted with the same collation
@@ -97,13 +109,19 @@ apply_migration() {
} }
column_fingerprint() { column_fingerprint() {
run_psql "$1" -v ON_ERROR_STOP=1 -At -c " run_psql "$(libpq_dsn "$1")" -v ON_ERROR_STOP=1 -At -c "
SELECT table_name || '.' || column_name SELECT table_name || '.' || column_name
FROM information_schema.columns FROM information_schema.columns
WHERE table_schema = 'public' WHERE table_schema = 'public'
AND table_name NOT IN ('_replica_ddl_applied')" | LC_ALL=C sort AND table_name NOT IN ('_replica_ddl_applied')" | LC_ALL=C sort
} }
# Prisma DATABASE_URL uses ?schema=public; libpq rejects that query param.
libpq_dsn() {
local u="$1"
echo "${u%%\?*}"
}
if [[ "$MODE" == "--check" ]]; then if [[ "$MODE" == "--check" ]]; then
for dsn in "${REPLICAS[@]}"; do for dsn in "${REPLICAS[@]}"; do
echo "== $(label "$dsn")" echo "== $(label "$dsn")"
@@ -144,7 +144,9 @@ export class ProgramSettingsService {
importedAt: '', importedAt: '',
source: 'khanegi_admin', source: 'khanegi_admin',
apiBase: '', apiBase: '',
items: raw.filter((row) => this.isSystemSetting(row as ProgramSettingRow)), items: this.asSettingsArray(raw).filter((row) =>
this.isSystemSetting(row),
),
}; };
} }
return null; return null;