Fix push-digest sentinel filter, watermark, and cleanups from review
- push digest queries now exclude _duplicate channel like every other visibility query (bookmarks list/count and NewClassifiedSince) - advance push watermark to newest scanned story, not pass-start now, so stories arriving during a long send pass aren't re-counted next pass - replace hand-rolled escapeHTMLText with stdlib html.EscapeString - drop em-dashes from user-facing copy; bump PWA CACHE_VERSION so clients pick up the changed shell assets
This commit is contained in:
@@ -96,7 +96,7 @@ func NewClassifiedSince(sinceUnix int64, limit int) ([]Story, error) {
|
||||
rows, err := Get().Query(
|
||||
`SELECT id, headline, source, channel, seen_at
|
||||
FROM stories
|
||||
WHERE classified = 1 AND channel <> '_discarded' AND seen_at > ?
|
||||
WHERE classified = 1 AND channel NOT IN ('_discarded', '_duplicate') AND seen_at > ?
|
||||
ORDER BY seen_at DESC
|
||||
LIMIT ?`, sinceUnix, limit)
|
||||
if err != nil {
|
||||
|
||||
@@ -104,6 +104,7 @@ func ListBookmarks(sub string, limit, offset int) ([]Story, error) {
|
||||
WHERE u.user_sub = ?
|
||||
AND u.bookmarked_at IS NOT NULL
|
||||
AND s.classified = 1
|
||||
AND s.channel NOT IN ('_discarded', '_duplicate')
|
||||
ORDER BY u.bookmarked_at DESC, u.story_id DESC
|
||||
LIMIT ? OFFSET ?`, sub, limit, offset)
|
||||
if err != nil {
|
||||
@@ -129,7 +130,8 @@ func CountBookmarks(sub string) (int, error) {
|
||||
err := Get().QueryRow(
|
||||
`SELECT COUNT(*) FROM user_story_state u
|
||||
JOIN stories s ON s.id = u.story_id
|
||||
WHERE u.user_sub = ? AND u.bookmarked_at IS NOT NULL AND s.classified = 1`,
|
||||
WHERE u.user_sub = ? AND u.bookmarked_at IS NOT NULL AND s.classified = 1
|
||||
AND s.channel NOT IN ('_discarded', '_duplicate')`,
|
||||
sub).Scan(&n)
|
||||
return n, err
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ package web
|
||||
import (
|
||||
"encoding/json"
|
||||
"encoding/xml"
|
||||
"html"
|
||||
"log/slog"
|
||||
"net/http"
|
||||
"strings"
|
||||
@@ -284,20 +285,8 @@ func contentToHTML(text string) string {
|
||||
continue
|
||||
}
|
||||
b.WriteString("<p>")
|
||||
b.WriteString(strings.ReplaceAll(escapeHTMLText(para), "\n", "<br>"))
|
||||
b.WriteString(strings.ReplaceAll(html.EscapeString(para), "\n", "<br>"))
|
||||
b.WriteString("</p>")
|
||||
}
|
||||
return b.String()
|
||||
}
|
||||
|
||||
// escapeHTMLText escapes the five characters that matter inside HTML text so the
|
||||
// article body can't inject markup into content:encoded.
|
||||
func escapeHTMLText(s string) string {
|
||||
return strings.NewReplacer(
|
||||
"&", "&",
|
||||
"<", "<",
|
||||
">", ">",
|
||||
`"`, """,
|
||||
"'", "'",
|
||||
).Replace(s)
|
||||
}
|
||||
|
||||
@@ -56,7 +56,6 @@ func (s *Server) sendDigests() {
|
||||
if len(subs) == 0 {
|
||||
return
|
||||
}
|
||||
now := time.Now().Unix()
|
||||
// Disabled-source sets are per user; cache within a pass so a user with
|
||||
// several devices only parses prefs once.
|
||||
disabledByUser := make(map[string]map[string]bool)
|
||||
@@ -116,7 +115,11 @@ func (s *Server) sendDigests() {
|
||||
slog.Warn("push: send failed", "sub", sub.UserSub, "err", err)
|
||||
continue
|
||||
}
|
||||
if derr := storage.TouchPushSubscription(sub.Endpoint, now); derr != nil {
|
||||
// Advance to the newest story this digest actually accounted for, not a
|
||||
// pass-start "now": the loop can run for minutes (one slow endpoint per
|
||||
// send), so stories arriving mid-pass would otherwise be re-counted next
|
||||
// pass. stories[0] is the newest in the scanned window (seen_at DESC).
|
||||
if derr := storage.TouchPushSubscription(sub.Endpoint, stories[0].SeenAt); derr != nil {
|
||||
slog.Error("push: advance watermark failed", "err", derr)
|
||||
}
|
||||
sent++
|
||||
@@ -133,7 +136,7 @@ func buildDigestPayload(count int, top string) []byte {
|
||||
body = "1 new story"
|
||||
}
|
||||
if top != "" {
|
||||
body += " — " + top
|
||||
body += ": " + top
|
||||
}
|
||||
b, _ := json.Marshal(map[string]string{
|
||||
"title": "Pete has fresh news",
|
||||
|
||||
@@ -101,7 +101,7 @@
|
||||
if (sub) return unsubscribe().then(function () { paint(false, "Notifications off."); });
|
||||
return Notification.requestPermission().then(function (perm) {
|
||||
if (perm !== "granted") { paint(false, "Permission denied."); return; }
|
||||
return subscribe().then(function () { paint(true, "You're all set — new stories will nudge you."); });
|
||||
return subscribe().then(function () { paint(true, "You're all set. New stories will nudge you."); });
|
||||
});
|
||||
}).catch(function () {
|
||||
paint(false, "Something went wrong. Try again.");
|
||||
|
||||
@@ -242,7 +242,7 @@
|
||||
: "";
|
||||
var href = safeURL(it.url);
|
||||
var note = '<p class="pete-reader-note">' +
|
||||
(it.paywalled ? "This source is paywalled — the text above may be partial. " : "") +
|
||||
(it.paywalled ? "This source is paywalled, so the text above may be partial. " : "") +
|
||||
(href ? 'Read it at the source: <a href="' + escapeHTML(href) + '" target="_blank" rel="noopener noreferrer">' +
|
||||
escapeHTML(it.source || "original article") + " ↗</a>." : "") +
|
||||
"</p>";
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
//
|
||||
// Bump CACHE_VERSION whenever the precached shell assets change; activate()
|
||||
// drops every cache that doesn't match the current version.
|
||||
var CACHE_VERSION = "v2";
|
||||
var CACHE_VERSION = "v3";
|
||||
var SHELL_CACHE = "pete-shell-" + CACHE_VERSION;
|
||||
var RUNTIME_CACHE = "pete-runtime-" + CACHE_VERSION;
|
||||
|
||||
@@ -77,7 +77,7 @@ function offlineFallback() {
|
||||
"<div style=\"font-family:system-ui,sans-serif;max-width:32rem;margin:20vh auto;padding:0 1.5rem;text-align:center;color:#3a2f1a\">" +
|
||||
"<div style=font-size:3rem>🦆</div>" +
|
||||
"<h1 style=font-size:1.4rem>You're offline</h1>" +
|
||||
"<p style=opacity:.7>Pete can't reach the news right now. Articles you've already opened are still readable — head back and try one of those.</p>" +
|
||||
"<p style=opacity:.7>Pete can't reach the news right now. Articles you've already opened are still readable, so head back and try one of those.</p>" +
|
||||
"</div>",
|
||||
{ headers: { "Content-Type": "text/html; charset=utf-8" }, status: 503 }
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user