Skip to content

fix: serve real home page instead of redirecting, add templated pages#409

Open
CKodidela wants to merge 7 commits intocameri:mainfrom
CKodidela:fix/home-redirect-302
Open

fix: serve real home page instead of redirecting, add templated pages#409
CKodidela wants to merge 7 commits intocameri:mainfrom
CKodidela:fix/home-redirect-302

Conversation

@CKodidela
Copy link
Copy Markdown
Contributor

Previously, visiting the relay's home page resulted in a 301 permanent redirect to /invoices when payments were
enabled, or a bare text response when payments were disabled. Neither gave relay operators a real home page, and the
301 caused browsers to cache the redirect permanently making it impossible to change home page behaviour later
without users clearing cache.

Changes:

  • / now always serves index.html, a proper home page that works with and without payments. When admission fees are
    enabled, a payment card and link to /invoices is shown; otherwise an "open relay" card is shown. Template variables:
    {{name}}, {{description}}, {{relay_url}}, {{amount}}, {{payments_section_class}}, {{no_payments_section_class}},
    {{nonce}}.
  • The old index.html (admission fee form) is renamed to get-invoice.html, served by GET /invoices. The ToS link now
    points to /terms instead of an embedded modal.
  • The old invoices.html (payment result page) is renamed to post-invoice.html, served after POST /invoices. The rename
    reflects what the page actually is.
  • privacy.html template added and served at GET /privacy.
  • Old index.html and invoices.html remain in resources/ for reference.

Description

Replaces the broken home page behaviour with a proper templated home page. All five templates (index.html,
get-invoice.html, post-invoice.html, terms.html, privacy.html) are user-modifiable files in resources/. The
home page conditionally shows or hides the admission fee section using CSS class template variables, so it works
correctly whether payments are enabled or not.

Related Issue

Closes #269

Motivation and Context

The 301 redirect was a bug browsers cache it permanently, so any future change to the home page would be invisible
to returning visitors until they manually cleared their cache. Beyond the redirect fix, relay operators (especially
those running private/whitelist-only relays with no payments) had no home page at all, and there was no privacy policy
page despite many jurisdictions requiring one.

How Has This Been Tested?

Manually verified both payment-enabled and payment-disabled paths render the correct home page content. Verified GET /invoices serves get-invoice.html, POST /invoices serves post-invoice.html, GET /terms and GET /privacy
both render correctly. Confirmed application/nostr+json requests to / still return the relay information document
unchanged.

Types of changes

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)

Checklist

  • My code follows the code style of this project.
  • I have read the CONTRIBUTING document.
  • I have added tests to cover my code changes.
  • All new and existing tests passed.

Previously, visiting the relay's home page resulted in a 301 permanent
redirect to /invoices when payments were enabled, or a bare text response
when payments were disabled. Neither gave relay operators a real home page,
and the 301 caused browsers to cache the redirect permanently — making it
impossible to change home page behaviour later without users clearing cache.

Changes:
- / now always serves index.html, a proper home page that works with and
  without payments. When admission fees are enabled, a payment card and
  link to /invoices is shown; otherwise an "open relay" card is shown.
  Template variables: {{name}}, {{description}}, {{relay_url}}, {{amount}},
  {{payments_section_class}}, {{no_payments_section_class}}, {{nonce}}.
- The old index.html (admission fee form) is renamed to get-invoice.html,
  served by GET /invoices. The ToS link now points to /terms instead of
  an embedded modal.
- The old invoices.html (payment result page) is renamed to post-invoice.html,
  served after POST /invoices. The rename reflects what the page actually is.
- privacy.html template added and served at GET /privacy.
- Old index.html and invoices.html remain in resources/ for reference.
@CKodidela
Copy link
Copy Markdown
Contributor Author

@cameri this PR up for review

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR replaces the relay’s previous root-path behavior (301 redirect to /invoices or plain text) with a real, user-modifiable, templated home page, and adds additional user-modifiable legal/payment templates served from resources/.

Changes:

  • Serve a templated resources/index.html at GET /, with conditional sections for payments-enabled vs open-relay mode.
  • Rename/split invoice templates into get-invoice.html (GET /invoices) and post-invoice.html (after POST /invoices), updating controllers accordingly.
  • Add resources/privacy.html and serve it at GET /privacy.

Reviewed changes

Copilot reviewed 9 out of 9 changed files in this pull request and generated 8 comments.

Show a summary per file
File Description
src/routes/index.ts Adds GET /privacy route wiring.
src/handlers/request-handlers/root-request-handler.ts Stops redirect/text behavior; renders templated home page.
src/handlers/request-handlers/get-privacy-request-handler.ts New handler that renders privacy.html.
src/controllers/invoices/get-invoice-controller.ts Serves get-invoice.html instead of index.html.
src/controllers/invoices/post-invoice-controller.ts Serves post-invoice.html instead of invoices.html.
resources/index.html New home page template with conditional payments/open-relay sections.
resources/get-invoice.html New “get invoice” page template (formerly home page admission form).
resources/post-invoice.html New “post invoice” page template (formerly invoices.html).
resources/privacy.html New privacy policy template.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@cameri
Copy link
Copy Markdown
Owner

cameri commented Apr 10, 2026

@CKodidela Please address reviews by Copilot.

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 11 out of 11 changed files in this pull request and generated 4 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 13 out of 13 changed files in this pull request and generated 3 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@CKodidela
Copy link
Copy Markdown
Contributor Author

@cameri @phoenix-server I’ve addressed the issues raised by Copilot, please take a look when you get a chance.

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 13 out of 13 changed files in this pull request and generated 4 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +1 to +24
const HTML_ESCAPES: Record<string, string> = {
'&': '&amp;',
'<': '&lt;',
'>': '&gt;',
'"': '&quot;',
"'": '&#39;',
}

/**
* Escape a string for safe interpolation into HTML text or attribute values.
*/
export const escapeHtml = (value: string): string =>
value.replace(/[&<>"']/g, (ch) => HTML_ESCAPES[ch])

/**
* Serialize a value for safe embedding inside an inline <script> block.
*
* JSON.stringify alone is NOT sufficient: it leaves `<` unescaped, so a value
* containing `</script>` would terminate the script block and allow injection.
* After serializing, replace every `<` with the Unicode escape `\u003C`, which
* is valid JSON and prevents the browser from treating the character as markup.
*/
export const safeJsonForScript = (value: unknown): string =>
JSON.stringify(value).replace(/</g, '\\u003C')
Copy link

Copilot AI Apr 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

New escaping utilities (escapeHtml / safeJsonForScript) are security-critical and currently have no unit tests. Adding tests for common and edge cases (quotes/ampersands, '<' in JSON, undefined handling, etc.) would help prevent regressions.

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@CKodidela We should add unit tests

Comment on lines +1 to +28
import { readFileSync } from 'fs'

const cache = new Map<string, string>()
const isProd = process.env.NODE_ENV === 'production'

/**
* Return the raw content of a template file.
*
* In production (NODE_ENV=production) the file is read from disk once and
* cached for the lifetime of the process — no per-request I/O. Operators who
* edit files under resources/ must restart the process for changes to take
* effect.
*
* Outside of production the cache is bypassed so template edits are reflected
* immediately without a restart.
*/
export const getTemplate = (path: string): string => {
if (isProd) {
let template = cache.get(path)
if (template === undefined) {
template = readFileSync(path, 'utf8')
cache.set(path, template)
}
return template
}

return readFileSync(path, 'utf8')
}
Copy link

Copilot AI Apr 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

getTemplate introduces environment-dependent caching behavior but currently has no unit tests. Adding tests for production vs non-production behavior (cache hit/miss, bypass in dev) would help ensure templates render consistently and avoid regressions.

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed with Copilot here: let's add unit tests.

@CKodidela
Copy link
Copy Markdown
Contributor Author

@cameri should i add test coverage?

@cameri
Copy link
Copy Markdown
Owner

cameri commented Apr 10, 2026

@CKodidela Yes please, unit tests are key against regressions, code correctness, and overall quality.
I don't think we need integration tests for this PR though.

  cache, request handlers, and invoice controller
@CKodidela
Copy link
Copy Markdown
Contributor Author

I'm still working on some issues will tag when its ready

@CKodidela
Copy link
Copy Markdown
Contributor Author

Up for review @cameri @phoenix-server

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

User-modifiable templates need work

3 participants