aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorLeonardo Bishop <me@leonardobishop.net>2025-08-10 19:15:47 +0100
committerLeonardo Bishop <me@leonardobishop.net>2025-08-11 12:26:44 +0100
commit1bae9e493756e585ee9cdbc5998359b34b2a1d59 (patch)
treebc6cbda8dba2f7fac001a9ed32ca403bebd28e8c
parent399a3ef4329ce3d22fcd39402faeefbae18bb58b (diff)
Add option to skip digests if there is nothing new
-rw-r--r--walrss/internal/db/20250810191816_skipifempty.up.sql1
-rw-r--r--walrss/internal/db/db.go6
-rw-r--r--walrss/internal/http/edit.go37
-rw-r--r--walrss/internal/http/http.go8
-rw-r--r--walrss/internal/http/mainpage.go1
-rw-r--r--walrss/internal/http/views/main.qtpl.html22
-rw-r--r--walrss/internal/http/views/main.qtpl.html.go32
-rw-r--r--walrss/internal/rss/processor.go37
-rw-r--r--walrss/internal/urls/urls.go1
9 files changed, 128 insertions, 17 deletions
diff --git a/walrss/internal/db/20250810191816_skipifempty.up.sql b/walrss/internal/db/20250810191816_skipifempty.up.sql
new file mode 100644
index 0000000..e008132
--- /dev/null
+++ b/walrss/internal/db/20250810191816_skipifempty.up.sql
@@ -0,0 +1 @@
+ALTER TABLE users ADD COLUMN skip_if_empty BOOLEAN DEFAULT FALSE NOT NULL
diff --git a/walrss/internal/db/db.go b/walrss/internal/db/db.go
index 37e093a..7fed362 100644
--- a/walrss/internal/db/db.go
+++ b/walrss/internal/db/db.go
@@ -3,12 +3,13 @@ package db
import (
"database/sql"
"fmt"
+ "strings"
+ "time"
+
_ "github.com/mattn/go-sqlite3"
"github.com/rs/zerolog/log"
"github.com/uptrace/bun"
"github.com/uptrace/bun/dialect/sqlitedialect"
- "strings"
- "time"
)
func New(filename string) (*bun.DB, error) {
@@ -39,6 +40,7 @@ type User struct {
Active bool `bun:"active,notnull"`
ScheduleDay SendDay `bun:"schedule_day"`
ScheduleHour int `bun:"schedule_hour"`
+ SkipIfEmpty bool `bun:"skip_if_empty"`
}
type Feed struct {
diff --git a/walrss/internal/http/edit.go b/walrss/internal/http/edit.go
index f126710..eb1b7d5 100644
--- a/walrss/internal/http/edit.go
+++ b/walrss/internal/http/edit.go
@@ -1,13 +1,14 @@
package http
import (
+ "strconv"
+ "strings"
+
"github.com/codemicro/walrss/walrss/internal/core"
"github.com/codemicro/walrss/walrss/internal/db"
"github.com/codemicro/walrss/walrss/internal/http/views"
"github.com/codemicro/walrss/walrss/internal/urls"
"github.com/gofiber/fiber/v2"
- "strconv"
- "strings"
)
func (s *Server) editEnabledState(ctx *fiber.Ctx) error {
@@ -34,6 +35,37 @@ func (s *Server) editEnabledState(ctx *fiber.Ctx) error {
fragmentEmitSuccess(ctx)
return ctx.SendString((&views.MainPage{
EnableDigests: user.Active,
+ SkipIfEmpty: user.SkipIfEmpty,
+ SelectedDay: user.ScheduleDay,
+ SelectedTime: user.ScheduleHour,
+ }).RenderScheduleCard())
+}
+
+func (s *Server) editSkipEmptyState(ctx *fiber.Ctx) error {
+ currentUserID := getCurrentUserID(ctx)
+ if currentUserID == "" {
+ return requestFragmentSignIn(ctx, urls.Index)
+ }
+
+ user, err := core.GetUserByID(s.state, currentUserID)
+ if err != nil {
+ return err
+ }
+
+ if strings.ToLower(ctx.FormValue("skipIfEmpty", "off")) == "on" {
+ user.SkipIfEmpty = true
+ } else {
+ user.SkipIfEmpty = false
+ }
+
+ if err := core.UpdateUser(s.state, user); err != nil {
+ return err
+ }
+
+ fragmentEmitSuccess(ctx)
+ return ctx.SendString((&views.MainPage{
+ EnableDigests: user.Active,
+ SkipIfEmpty: user.SkipIfEmpty,
SelectedDay: user.ScheduleDay,
SelectedTime: user.ScheduleHour,
}).RenderScheduleCard())
@@ -77,6 +109,7 @@ func (s *Server) editTimings(ctx *fiber.Ctx) error {
fragmentEmitSuccess(ctx)
return ctx.SendString((&views.MainPage{
EnableDigests: user.Active,
+ SkipIfEmpty: user.SkipIfEmpty,
SelectedDay: user.ScheduleDay,
SelectedTime: user.ScheduleHour,
}).RenderScheduleCard())
diff --git a/walrss/internal/http/http.go b/walrss/internal/http/http.go
index f76170c..12a5da9 100644
--- a/walrss/internal/http/http.go
+++ b/walrss/internal/http/http.go
@@ -3,6 +3,10 @@ package http
import (
"context"
"errors"
+ "net/url"
+ "strings"
+ "time"
+
"github.com/codemicro/walrss/walrss/internal/core"
"github.com/codemicro/walrss/walrss/internal/http/views"
"github.com/codemicro/walrss/walrss/internal/state"
@@ -13,9 +17,6 @@ import (
"github.com/rs/zerolog/log"
"github.com/stevelacy/daz"
"golang.org/x/oauth2"
- "net/url"
- "strings"
- "time"
)
const (
@@ -112,6 +113,7 @@ func (s *Server) registerHandlers() {
s.app.Get(urls.AuthOIDCCallback, s.authOIDCCallback)
s.app.Put(urls.EditEnabledState, s.editEnabledState)
+ s.app.Put(urls.EditSkipEmptyState, s.editSkipEmptyState)
s.app.Put(urls.EditTimings, s.editTimings)
s.app.Get(urls.EditFeedItem, s.editFeedItem)
diff --git a/walrss/internal/http/mainpage.go b/walrss/internal/http/mainpage.go
index 99b26b8..b3530f1 100644
--- a/walrss/internal/http/mainpage.go
+++ b/walrss/internal/http/mainpage.go
@@ -24,6 +24,7 @@ func (s *Server) mainPage(ctx *fiber.Ctx) error {
return views.SendPage(ctx, &views.MainPage{
EnableDigests: user.Active,
+ SkipIfEmpty: user.SkipIfEmpty,
SelectedDay: user.ScheduleDay,
SelectedTime: user.ScheduleHour,
Feeds: feeds,
diff --git a/walrss/internal/http/views/main.qtpl.html b/walrss/internal/http/views/main.qtpl.html
index 4619ee6..2e593e0 100644
--- a/walrss/internal/http/views/main.qtpl.html
+++ b/walrss/internal/http/views/main.qtpl.html
@@ -9,6 +9,7 @@
EnableDigests bool
SelectedDay db.SendDay
SelectedTime int
+ SkipIfEmpty bool
Feeds db.FeedSlice
} %}
@@ -268,6 +269,27 @@
</div>
</div>
+ <div class="mb-2 row row-cols-lg-auto align-items-center">
+ <div class="col-12">
+ <input
+ type="checkbox"
+ id="skipIfEmptyCheckbox"
+ name="skipIfEmpty"
+ {% if !p.EnableDigests %}disabled{% endif %}
+ {% if p.SkipIfEmpty %}checked{% endif %}
+ hx-put="{%s= urls.EditSkipEmptyState %}"
+ hx-indicator="#skipIfEmptyCheckboxIndicator"
+ >
+ <label for="skipIfEmptyCheckbox">Skip digest if there are no updates</label>
+ </div>
+
+ <div class="col-12">
+ <div class="spinner-border spinner-border-sm request-indicator align-middle" style="width: 1rem; height: 1rem;" role="status" id="skipIfEmptyCheckboxIndicator">
+ <span class="visually-hidden">Loading...</span>
+ </div>
+ </div>
+ </div>
+
<form
class="row row-cols-lg-auto g-3 align-items-center"
hx-put="{%s urls.EditTimings %}"
diff --git a/walrss/internal/http/views/main.qtpl.html.go b/walrss/internal/http/views/main.qtpl.html.go
index a85f33e..644e0f6 100644
--- a/walrss/internal/http/views/main.qtpl.html.go
+++ b/walrss/internal/http/views/main.qtpl.html.go
@@ -29,6 +29,7 @@ type MainPage struct {
EnableDigests bool
SelectedDay db.SendDay
SelectedTime int
+ SkipIfEmpty bool
Feeds db.FeedSlice
}
@@ -449,6 +450,37 @@ func (p *MainPage) StreamRenderScheduleCard(qw422016 *qt422016.Writer) {
</div>
</div>
+ <div class="mb-2 row row-cols-lg-auto align-items-center">
+ <div class="col-12">
+ <input
+ type="checkbox"
+ id="skipIfEmptyCheckbox"
+ name="skipIfEmpty"
+ `)
+ if !p.EnableDigests {
+ qw422016.N().S(`disabled`)
+ }
+ qw422016.N().S(`
+ `)
+ if p.SkipIfEmpty {
+ qw422016.N().S(`checked`)
+ }
+ qw422016.N().S(`
+ hx-put="`)
+ qw422016.N().S(urls.EditSkipEmptyState)
+ qw422016.N().S(`"
+ hx-indicator="#skipIfEmptyCheckboxIndicator"
+ >
+ <label for="skipIfEmptyCheckbox">Skip digest if there are no updates</label>
+ </div>
+
+ <div class="col-12">
+ <div class="spinner-border spinner-border-sm request-indicator align-middle" style="width: 1rem; height: 1rem;" role="status" id="skipIfEmptyCheckboxIndicator">
+ <span class="visually-hidden">Loading...</span>
+ </div>
+ </div>
+ </div>
+
<form
class="row row-cols-lg-auto g-3 align-items-center"
hx-put="`)
diff --git a/walrss/internal/rss/processor.go b/walrss/internal/rss/processor.go
index 8a9885d..b989e3c 100644
--- a/walrss/internal/rss/processor.go
+++ b/walrss/internal/rss/processor.go
@@ -5,6 +5,14 @@ import (
"context"
"crypto/tls"
"fmt"
+ "net/http"
+ "net/smtp"
+ "net/textproto"
+ "sort"
+ "strings"
+ "sync"
+ "time"
+
"github.com/carlmjohnson/requests"
"github.com/codemicro/walrss/walrss/internal/core"
"github.com/codemicro/walrss/walrss/internal/db"
@@ -14,13 +22,6 @@ import (
"github.com/matcornic/hermes"
"github.com/mmcdole/gofeed"
"github.com/rs/zerolog/log"
- "net/http"
- "net/smtp"
- "net/textproto"
- "sort"
- "strings"
- "sync"
- "time"
)
const (
@@ -105,6 +106,7 @@ func ProcessUserFeed(st *state.State, user *db.User, progressChannel chan string
}
var processedFeeds []*processedFeed
+ var newContent bool
startTime := time.Now().UTC()
@@ -131,6 +133,9 @@ func ProcessUserFeed(st *state.State, user *db.User, progressChannel chan string
}
pf.Items = ffcRes.filtered
+ if len(pf.Items) > 0 {
+ newContent = true
+ }
// add new items to DB cache when we're not testing. if we're testing, adding known items to the cache will
// stop them from appearing in any scheduled emails later on.
@@ -150,7 +155,16 @@ func ProcessUserFeed(st *state.State, user *db.User, progressChannel chan string
processedFeeds = append(processedFeeds, pf)
}
- reportProgress(progressChannel, "Finished fetching feed content\nGenerating email")
+ reportProgress(progressChannel, "Finished fetching feed content")
+
+ if user.SkipIfEmpty && !newContent {
+ reportProgress(progressChannel, "There is no new content to send")
+ if !isTest {
+ return nil
+ }
+ }
+
+ reportProgress(progressChannel, "Generating email")
plainContent, htmlContent, err := generateEmail(st, processedFeeds, interval, time.Now().UTC().Sub(startTime))
if err != nil {
@@ -166,8 +180,11 @@ func ProcessUserFeed(st *state.State, user *db.User, progressChannel chan string
user.Email,
"RSS digest for "+time.Now().UTC().Format(dateFormat),
)
-
- reportProgress(progressChannel, "Done!")
+ if err != nil {
+ reportProgress(progressChannel, "Failed to send email")
+ } else {
+ reportProgress(progressChannel, "Done!")
+ }
return err
}
diff --git a/walrss/internal/urls/urls.go b/walrss/internal/urls/urls.go
index b0b154d..99943f8 100644
--- a/walrss/internal/urls/urls.go
+++ b/walrss/internal/urls/urls.go
@@ -17,6 +17,7 @@ const (
Edit = "/edit"
EditEnabledState = Edit + "/enabled"
+ EditSkipEmptyState = Edit + "/skipempty"
EditTimings = Edit + "/timings"
EditFeedItem = Edit + "/feed/:id"
CancelEditFeedItem = Edit + "/feed/:id/cancel"