repos / pico

pico services - prose.sh, pastes.sh, imgs.sh, feeds.sh, pgs.sh
git clone https://github.com/picosh/pico.git

commit
c59051f
parent
7372600
author
Eric Bower
date
2024-12-13 04:39:25 +0000 UTC
refactor(feeds): remove feeds after 3 failed attempts

We have a bunch of feeds in our system that are failing in a loop.
Previously we did not notify the user when there was a failure, we just
logged it internally.

With this change, we will prepend errors to the email message body.
Further, if the feeds errors and doesn't return any feed items, we
increment a counter.  3 failed attempts and we remove the post and
notify the user.
2 files changed,  +74, -11
M db/db.go
+1, -0
1@@ -32,6 +32,7 @@ type User struct {
2 type PostData struct {
3 	ImgPath    string     `json:"img_path"`
4 	LastDigest *time.Time `json:"last_digest"`
5+	Attempts   int        `json:"attempts"`
6 }
7 
8 // Make the Attrs struct implement the driver.Valuer interface. This method
M feeds/cron.go
+73, -11
  1@@ -175,25 +175,77 @@ func (f *Fetcher) RunPost(logger *slog.Logger, user *db.User, post *db.Post) err
  2 		urls = append(urls, url)
  3 	}
  4 
  5-	msgBody, err := f.FetchAll(logger, urls, parsed.InlineContent, user.Name, post)
  6+	now := time.Now().UTC()
  7+	if post.ExpiresAt == nil {
  8+		expiresAt := time.Now().AddDate(0, 6, 0)
  9+		post.ExpiresAt = &expiresAt
 10+	}
 11+	post.Data.LastDigest = &now
 12+	_, err = f.db.UpdatePost(post)
 13 	if err != nil {
 14 		return err
 15 	}
 16 
 17 	subject := fmt.Sprintf("%s feed digest", post.Title)
 18-	err = f.SendEmail(logger, user.Name, parsed.Email, subject, msgBody)
 19+
 20+	msgBody, err := f.FetchAll(logger, urls, parsed.InlineContent, user.Name, post)
 21 	if err != nil {
 22-		return err
 23+		errForUser := err
 24+
 25+		// we don't want to increment in this case
 26+		if errors.Is(errForUser, ErrNoRecentArticles) {
 27+			return nil
 28+		}
 29+
 30+		post.Data.Attempts += 1
 31+		logger.Error("could not fetch urls", "err", err, "attempts", post.Data.Attempts)
 32+
 33+		errBody := fmt.Sprintf(`There was an error attempting to fetch your feeds (%d) times.  After (3) attempts we remove the file from our system.  Please check all the URLs and re-upload.
 34+Also, we have centralized logs in our pico.sh TUI that will display realtime feed errors so you can debug.
 35+
 36+
 37+%s
 38+
 39+
 40+%s`, post.Data.Attempts, errForUser.Error(), post.Text)
 41+		err = f.SendEmail(
 42+			logger, user.Name,
 43+			parsed.Email,
 44+			subject,
 45+			&MsgBody{Html: strings.ReplaceAll(errBody, "\n", "<br />"), Text: errBody},
 46+		)
 47+		if err != nil {
 48+			return err
 49+		}
 50+
 51+		if post.Data.Attempts >= 3 {
 52+			err = f.db.RemovePosts([]string{post.ID})
 53+			if err != nil {
 54+				return err
 55+			}
 56+		} else {
 57+			_, err = f.db.UpdatePost(post)
 58+			if err != nil {
 59+				return err
 60+			}
 61+		}
 62+		return errForUser
 63+	} else {
 64+		post.Data.Attempts = 0
 65+		_, err := f.db.UpdatePost(post)
 66+		if err != nil {
 67+			return err
 68+		}
 69 	}
 70 
 71-	now := time.Now().UTC()
 72-	if post.ExpiresAt == nil {
 73-		expiresAt := time.Now().AddDate(0, 6, 0)
 74-		post.ExpiresAt = &expiresAt
 75+	if msgBody != nil {
 76+		err = f.SendEmail(logger, user.Name, parsed.Email, subject, msgBody)
 77+		if err != nil {
 78+			return err
 79+		}
 80 	}
 81-	post.Data.LastDigest = &now
 82-	_, err = f.db.UpdatePost(post)
 83-	return err
 84+
 85+	return nil
 86 }
 87 
 88 func (f *Fetcher) RunUser(user *db.User) error {
 89@@ -353,12 +405,14 @@ func (f *Fetcher) FetchAll(logger *slog.Logger, urls []string, inlineContent boo
 90 		return nil, err
 91 	}
 92 
 93+	var allErrors error
 94 	for _, url := range urls {
 95 		feedTmpl, err := f.Fetch(logger, fp, url, username, feedItems)
 96 		if err != nil {
 97 			if errors.Is(err, ErrNoRecentArticles) {
 98 				logger.Info("no recent articles", "err", err)
 99 			} else {
100+				allErrors = errors.Join(allErrors, fmt.Errorf("%s: %w", url, err))
101 				logger.Error("fetch error", "err", err)
102 			}
103 			continue
104@@ -367,7 +421,10 @@ func (f *Fetcher) FetchAll(logger *slog.Logger, urls []string, inlineContent boo
105 	}
106 
107 	if len(feeds.Feeds) == 0 {
108-		return nil, fmt.Errorf("(%s) %w, skipping email", username, ErrNoRecentArticles)
109+		if allErrors != nil {
110+			return nil, allErrors
111+		}
112+		return nil, fmt.Errorf("%w, skipping email", ErrNoRecentArticles)
113 	}
114 
115 	fdi := []*db.FeedItem{}
116@@ -401,6 +458,11 @@ func (f *Fetcher) FetchAll(logger *slog.Logger, urls []string, inlineContent boo
117 		return nil, err
118 	}
119 
120+	if allErrors != nil {
121+		text = fmt.Sprintf("> %s\n\n%s", allErrors, text)
122+		html = fmt.Sprintf("<blockquote>%s</blockquote><br /><br/>%s", allErrors, html)
123+	}
124+
125 	return &MsgBody{
126 		Text: text,
127 		Html: html,