feat(settings): surface push.ntfyUrl in /public/settings for the app
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:
@@ -55,3 +55,51 @@ test('admin site_title / contact_email override the brand defaults', async () =>
|
||||
assert.equal(pub.brand.contactEmail, 'hi@shard.tld') // contact_email overrides
|
||||
assert.equal(pub.brand.accent, brand.accent) // colors still from config
|
||||
})
|
||||
|
||||
// The push relay block the app's embedded distributor discovers its ntfy base
|
||||
// URL from (M7 Part 2). Null when nothing is configured; NTFY_PUBLIC_URL wins,
|
||||
// else the first NTFY_ALLOWED_ORIGINS entry; NTFY_BASE_URL is never surfaced.
|
||||
test('push.ntfyUrl is null when no ntfy env is configured', async () => {
|
||||
const saved = { pub: process.env.NTFY_PUBLIC_URL, allow: process.env.NTFY_ALLOWED_ORIGINS, base: process.env.NTFY_BASE_URL }
|
||||
delete process.env.NTFY_PUBLIC_URL
|
||||
delete process.env.NTFY_ALLOWED_ORIGINS
|
||||
process.env.NTFY_BASE_URL = 'http://ntfy:80' // internal-only, must NOT leak
|
||||
try {
|
||||
const pub = await settings.getPublic()
|
||||
assert.ok(pub.push, 'push block present')
|
||||
assert.equal(pub.push.ntfyUrl, null)
|
||||
} finally {
|
||||
restoreNtfyEnv(saved)
|
||||
}
|
||||
})
|
||||
|
||||
test('push.ntfyUrl prefers NTFY_PUBLIC_URL and trims a trailing slash', async () => {
|
||||
const saved = { pub: process.env.NTFY_PUBLIC_URL, allow: process.env.NTFY_ALLOWED_ORIGINS, base: process.env.NTFY_BASE_URL }
|
||||
process.env.NTFY_PUBLIC_URL = 'https://ntfy.shard.tld/'
|
||||
process.env.NTFY_ALLOWED_ORIGINS = 'https://other.tld'
|
||||
try {
|
||||
const pub = await settings.getPublic()
|
||||
assert.equal(pub.push.ntfyUrl, 'https://ntfy.shard.tld')
|
||||
} finally {
|
||||
restoreNtfyEnv(saved)
|
||||
}
|
||||
})
|
||||
|
||||
test('push.ntfyUrl falls back to the first NTFY_ALLOWED_ORIGINS entry', async () => {
|
||||
const saved = { pub: process.env.NTFY_PUBLIC_URL, allow: process.env.NTFY_ALLOWED_ORIGINS, base: process.env.NTFY_BASE_URL }
|
||||
delete process.env.NTFY_PUBLIC_URL
|
||||
process.env.NTFY_ALLOWED_ORIGINS = 'https://ntfy.shard.tld, https://second.tld'
|
||||
try {
|
||||
const pub = await settings.getPublic()
|
||||
assert.equal(pub.push.ntfyUrl, 'https://ntfy.shard.tld')
|
||||
} finally {
|
||||
restoreNtfyEnv(saved)
|
||||
}
|
||||
})
|
||||
|
||||
function restoreNtfyEnv(saved) {
|
||||
for (const [name, val] of [['NTFY_PUBLIC_URL', saved.pub], ['NTFY_ALLOWED_ORIGINS', saved.allow], ['NTFY_BASE_URL', saved.base]]) {
|
||||
if (val === undefined) delete process.env[name]
|
||||
else process.env[name] = val
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user