#173: render friendly 'Paste ID not found' UI instead of bare 404
Missing/expired/burned paste IDs and unknown routes now render the main UI (topbar + centered result card) with a 'Paste ID not found' message, returning HTTP 404 status for correctness.
This commit is contained in:
@@ -129,8 +129,11 @@ func (a *apiServer) routes() http.Handler {
|
|||||||
r.Get("/{id}", a.handlePasteView)
|
r.Get("/{id}", a.handlePasteView)
|
||||||
r.Post("/{id}", a.handlePasteView)
|
r.Post("/{id}", a.handlePasteView)
|
||||||
|
|
||||||
|
// #173: missing paste URLs render the main UI with a friendly not-found
|
||||||
|
// message instead of a bare JSON 404. Known routes (above) handle real pages;
|
||||||
|
// anything else is a nonexistent paste ID or typo.
|
||||||
r.NotFound(func(w http.ResponseWriter, r *http.Request) {
|
r.NotFound(func(w http.ResponseWriter, r *http.Request) {
|
||||||
writeErr(w, 404, "not found")
|
a.webHandlers().HandleNotFoundPage(w, r)
|
||||||
})
|
})
|
||||||
return r
|
return r
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,13 @@
|
|||||||
|
{{template "head" .}}
|
||||||
|
{{template "topbar" .}}
|
||||||
|
<div class="center">
|
||||||
|
<div class="float notfound-card">
|
||||||
|
<div class="inner">
|
||||||
|
<div class="lockring"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="12" cy="12" r="10"/><line x1="12" y1="8" x2="12" y2="12"/><line x1="12" y1="16" x2="12.01" y2="16"/></svg></div>
|
||||||
|
<h1>Paste ID not found</h1>
|
||||||
|
<p class="sub">The paste <span class="slug">/{{.ID}}</span> does not exist, has expired, or was burned.</p>
|
||||||
|
<a class="btn" href="/new">Create a new paste</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{{template "foot" .}}
|
||||||
+38
-4
@@ -104,6 +104,13 @@ func (h *Handlers) renderPage(w http.ResponseWriter, name string, data any) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// RenderNotFoundPage is the exported not-found renderer used by the api
|
||||||
|
// package (#173): the router's NotFound handler renders the main UI with a
|
||||||
|
// friendly "Paste ID not found" message, still with HTTP 404.
|
||||||
|
func (h *Handlers) RenderNotFoundPage(w http.ResponseWriter, r *http.Request) {
|
||||||
|
h.renderNotFound(w, r, r.URL.Path)
|
||||||
|
}
|
||||||
|
|
||||||
// RenderPage is the exported wrapper used by the api package (#4 can pages).
|
// RenderPage is the exported wrapper used by the api package (#4 can pages).
|
||||||
func (h *Handlers) RenderPage(w http.ResponseWriter, name string, data any) {
|
func (h *Handlers) RenderPage(w http.ResponseWriter, name string, data any) {
|
||||||
h.renderPage(w, name, data)
|
h.renderPage(w, name, data)
|
||||||
@@ -200,6 +207,30 @@ func (h *Handlers) writeRateLimited(w http.ResponseWriter, retryAfterSecs int) {
|
|||||||
w.Write([]byte(`{"error":"rate limit exceeded"}`))
|
w.Write([]byte(`{"error":"rate limit exceeded"}`))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// renderNotFound serves the friendly not-found page (#173): the main UI
|
||||||
|
// chrome (topbar, centered card) with a "Paste ID not found" message in the
|
||||||
|
// result card, instead of a bare text 404. Still returns HTTP 404 so
|
||||||
|
// crawlers/validators see the correct status.
|
||||||
|
func (h *Handlers) renderNotFound(w http.ResponseWriter, r *http.Request, id string) {
|
||||||
|
h.renderPageStatus(w, "notfound.html", http.StatusNotFound, map[string]any{"Page": "notfound", "ID": id})
|
||||||
|
}
|
||||||
|
|
||||||
|
// HandleNotFoundPage serves the friendly not-found page for unknown routes
|
||||||
|
// (#173): main UI chrome with a "Paste ID not found" message. Called from the
|
||||||
|
// chi NotFound handler in the api package.
|
||||||
|
func (h *Handlers) HandleNotFoundPage(w http.ResponseWriter, r *http.Request) {
|
||||||
|
h.renderNotFound(w, r, r.URL.Path)
|
||||||
|
}
|
||||||
|
|
||||||
|
// renderPageStatus renders a template with an explicit HTTP status code.
|
||||||
|
func (h *Handlers) renderPageStatus(w http.ResponseWriter, name string, status int, data any) {
|
||||||
|
w.Header().Set("Content-Type", "text/html; charset=utf-8")
|
||||||
|
w.WriteHeader(status)
|
||||||
|
if err := h.UI.tmpl.ExecuteTemplate(w, name, data); err != nil {
|
||||||
|
http.Error(w, "template error: "+err.Error(), 500)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (h *Handlers) renderPaste(w http.ResponseWriter, row *store.PasteRow, justCreated bool, deletionToken string, readsRemaining *int) {
|
func (h *Handlers) renderPaste(w http.ResponseWriter, row *store.PasteRow, justCreated bool, deletionToken string, readsRemaining *int) {
|
||||||
lines := strings.Count(row.Content, "\n") + 1
|
lines := strings.Count(row.Content, "\n") + 1
|
||||||
gutter := ""
|
gutter := ""
|
||||||
@@ -262,11 +293,14 @@ func (h *Handlers) HandlePasteView(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
if row == nil {
|
if row == nil {
|
||||||
http.NotFound(w, r)
|
// #173: a missing paste ID gets the main UI with a friendly message,
|
||||||
|
// not a bare text 404 page.
|
||||||
|
h.renderNotFound(w, r, id)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if row.ExpiresAt.Valid && row.ExpiresAt.Int64 < time.Now().Unix() {
|
if row.ExpiresAt.Valid && row.ExpiresAt.Int64 < time.Now().Unix() {
|
||||||
http.Error(w, "paste expired", 404)
|
// #173: expired pastes render the same friendly not-found UI.
|
||||||
|
h.renderNotFound(w, r, id)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if row.PasswordHash.Valid {
|
if row.PasswordHash.Valid {
|
||||||
@@ -333,8 +367,8 @@ func (h *Handlers) HandlePasteView(w http.ResponseWriter, r *http.Request) {
|
|||||||
// Just-created first render does not count as a read for the creator.
|
// Just-created first render does not count as a read for the creator.
|
||||||
if !justCreated {
|
if !justCreated {
|
||||||
rem, admitted := h.Store.RegisterRead(row, h.ViewerID(r), h.BurnWindowMin())
|
rem, admitted := h.Store.RegisterRead(row, h.ViewerID(r), h.BurnWindowMin())
|
||||||
if !admitted { // #58: lost the burn claim; do not render content
|
if !admitted { // #58: lost the burn claim; #173: friendly not-found UI
|
||||||
http.NotFound(w, r)
|
h.renderNotFound(w, r, row.ID)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
h.renderPaste(w, row, false, "", rem)
|
h.renderPaste(w, row, false, "", rem)
|
||||||
|
|||||||
Reference in New Issue
Block a user