feat(settings): surface push.ntfyUrl in /public/settings for the app
All checks were successful
PR Checks / server-tests (pull_request) Successful in 9m39s
PR Checks / client-build (pull_request) Successful in 9m24s
PR Checks / bot-install (pull_request) Successful in 9m17s

The Android app's embedded push distributor (M7 Part 2) needs the shard's
client-facing ntfy relay URL to build its device topic endpoint, but the M7
Part 1 backend only used the NTFY_* vars server-side and never surfaced them.

Add a `push: { ntfyUrl }` block to settings.getPublic(), sourced from
NTFY_PUBLIC_URL or the first NTFY_ALLOWED_ORIGINS entry (never the possibly
internal NTFY_BASE_URL); null when unconfigured, so the app shows push as
unavailable for that shard. Additive, non-sensitive, forward-compatible.

- Extend the PublicSettings swagger schema; regenerate swagger-output.json.
- publicBrand.test.js: cover null / NTFY_PUBLIC_URL / NTFY_ALLOWED_ORIGINS.
- Document NTFY_PUBLIC_URL in .env.example and (docs PR) BACKEND_DESIGN.md.

Full server suite green (250 pass).

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2026-07-20 15:25:40 -05:00
parent 4fa73d3ccf
commit a789ee3ac9
5 changed files with 123 additions and 5 deletions

View File

@@ -105,9 +105,28 @@ async function getPublic() {
hero: brand.hero,
favicon: brand.favicon,
}
// Push-notification relay (M7). The client-facing ntfy base URL the app's
// embedded distributor registers its device topic against; null when push is
// not configured for this shard, in which case the app simply shows push as
// unavailable. The publisher's own NTFY_BASE_URL may be an internal
// compose-network address, so a distinct NTFY_PUBLIC_URL is preferred; failing
// that we use the first NTFY_ALLOWED_ORIGINS entry (a device endpoint must sit
// on an allowed origin anyway). Never NTFY_BASE_URL — it may be internal-only.
out.push = { ntfyUrl: publicNtfyUrl() }
return out
}
// The client-facing ntfy base URL (no trailing slash), or null when unset.
function publicNtfyUrl() {
const explicit = (process.env.NTFY_PUBLIC_URL || '').trim()
if (explicit) return explicit.replace(/\/+$/, '')
const firstOrigin = (process.env.NTFY_ALLOWED_ORIGINS || '')
.split(',')
.map((s) => s.trim())
.filter(Boolean)[0]
return firstOrigin ? firstOrigin.replace(/\/+$/, '') : null
}
module.exports = {
get,
set,