summaryrefslogtreecommitdiff
path: root/services/worker/internal/scheduler/refresh.go
diff options
context:
space:
mode:
authorFuwn <[email protected]>2026-02-07 01:42:57 -0800
committerFuwn <[email protected]>2026-02-07 01:42:57 -0800
commit5c5b1993edd890a80870ee05607ac5f088191d4e (patch)
treea721b76bcd49ba10826c53efc87302c7a689512f /services/worker/internal/scheduler/refresh.go
downloadasa.news-5c5b1993edd890a80870ee05607ac5f088191d4e.tar.xz
asa.news-5c5b1993edd890a80870ee05607ac5f088191d4e.zip
feat: asa.news RSS reader with developer tier, REST API, and webhooks
Full-stack RSS reader SaaS: Supabase + Next.js + Go worker. Includes three subscription tiers (free/pro/developer), API key auth, read-only REST API, webhook push notifications, Stripe billing with proration, and PWA support.
Diffstat (limited to 'services/worker/internal/scheduler/refresh.go')
-rw-r--r--services/worker/internal/scheduler/refresh.go196
1 files changed, 196 insertions, 0 deletions
diff --git a/services/worker/internal/scheduler/refresh.go b/services/worker/internal/scheduler/refresh.go
new file mode 100644
index 0000000..8271fa0
--- /dev/null
+++ b/services/worker/internal/scheduler/refresh.go
@@ -0,0 +1,196 @@
+package scheduler
+
+import (
+ "context"
+ "github.com/Fuwn/asa-news/internal/fetcher"
+ "github.com/Fuwn/asa-news/internal/model"
+ "github.com/Fuwn/asa-news/internal/parser"
+ "github.com/Fuwn/asa-news/internal/webhook"
+ "github.com/Fuwn/asa-news/internal/writer"
+ "log/slog"
+)
+
+func ProcessRefreshRequest(
+ refreshContext context.Context,
+ feed model.Feed,
+ feedFetcher *fetcher.Fetcher,
+ feedParser *parser.Parser,
+ feedWriter *writer.Writer,
+ webhookDispatcher *webhook.Dispatcher,
+ logger *slog.Logger,
+) {
+ logger.Info(
+ "processing feed refresh",
+ "feed_identifier", feed.Identifier,
+ "feed_url", feed.URL,
+ )
+
+ var ownerIdentifier *string
+
+ if feed.Visibility == "authenticated" {
+ logger.Warn(
+ "authenticated feed refresh not yet implemented",
+ "feed_identifier", feed.Identifier,
+ )
+
+ return
+ }
+
+ authenticationConfig := fetcher.AuthenticationConfiguration{}
+
+ entityTag := ""
+
+ if feed.EntityTag != nil {
+ entityTag = *feed.EntityTag
+ }
+
+ lastModified := ""
+
+ if feed.LastModified != nil {
+ lastModified = *feed.LastModified
+ }
+
+ fetchResult, fetchError := feedFetcher.Fetch(
+ refreshContext,
+ feed.URL,
+ entityTag,
+ lastModified,
+ authenticationConfig,
+ )
+
+ if fetchError != nil {
+ logger.Warn(
+ "feed fetch failed",
+ "feed_identifier", feed.Identifier,
+ "error", fetchError,
+ )
+
+ recordError := feedWriter.RecordFeedError(refreshContext, feed.Identifier, fetchError.Error())
+
+ if recordError != nil {
+ logger.Error(
+ "failed to record feed error",
+ "feed_identifier", feed.Identifier,
+ "error", recordError,
+ )
+ }
+
+ return
+ }
+
+ if fetchResult.NotModified {
+ logger.Info(
+ "feed not modified",
+ "feed_identifier", feed.Identifier,
+ )
+
+ metadataError := feedWriter.UpdateFeedMetadata(
+ refreshContext,
+ feed.Identifier,
+ fetchResult.EntityTag,
+ fetchResult.LastModifiedHeader,
+ "",
+ "",
+ )
+
+ if metadataError != nil {
+ logger.Error(
+ "failed to update feed metadata after not-modified",
+ "feed_identifier", feed.Identifier,
+ "error", metadataError,
+ )
+ }
+
+ return
+ }
+
+ parseResult, parseError := feedParser.Parse(feed.Identifier, ownerIdentifier, fetchResult.Body)
+
+ if parseError != nil {
+ logger.Warn(
+ "feed parse failed",
+ "feed_identifier", feed.Identifier,
+ "error", parseError,
+ )
+
+ recordError := feedWriter.RecordFeedError(refreshContext, feed.Identifier, parseError.Error())
+
+ if recordError != nil {
+ logger.Error(
+ "failed to record parse error",
+ "feed_identifier", feed.Identifier,
+ "error", recordError,
+ )
+ }
+
+ return
+ }
+
+ rowsAffected, writeError := feedWriter.WriteEntries(refreshContext, parseResult.Entries)
+
+ if writeError != nil {
+ logger.Error(
+ "failed to write feed entries",
+ "feed_identifier", feed.Identifier,
+ "error", writeError,
+ )
+
+ recordError := feedWriter.RecordFeedError(refreshContext, feed.Identifier, writeError.Error())
+
+ if recordError != nil {
+ logger.Error(
+ "failed to record write error",
+ "feed_identifier", feed.Identifier,
+ "error", recordError,
+ )
+ }
+
+ return
+ }
+
+ if rowsAffected > 0 && webhookDispatcher != nil {
+ webhookDispatcher.DispatchForFeed(refreshContext, feed.Identifier, parseResult.Entries)
+ }
+
+ metadataError := feedWriter.UpdateFeedMetadata(
+ refreshContext,
+ feed.Identifier,
+ fetchResult.EntityTag,
+ fetchResult.LastModifiedHeader,
+ parseResult.FeedTitle,
+ parseResult.SiteURL,
+ )
+
+ if metadataError != nil {
+ logger.Error(
+ "failed to update feed metadata",
+ "feed_identifier", feed.Identifier,
+ "error", metadataError,
+ )
+ }
+
+ if parseResult.AudioEnclosureRatio > 0.5 {
+ if feedTypeError := feedWriter.UpdateFeedType(refreshContext, feed.Identifier, "podcast"); feedTypeError != nil {
+ logger.Error(
+ "failed to update feed type to podcast",
+ "feed_identifier", feed.Identifier,
+ "error", feedTypeError,
+ )
+ }
+ } else if feed.FeedType != nil && *feed.FeedType == "podcast" {
+ if feedTypeError := feedWriter.UpdateFeedType(refreshContext, feed.Identifier, "rss"); feedTypeError != nil {
+ logger.Error(
+ "failed to clear podcast feed type",
+ "feed_identifier", feed.Identifier,
+ "error", feedTypeError,
+ )
+ }
+ }
+
+ logger.Info(
+ "feed refresh completed",
+ "feed_identifier", feed.Identifier,
+ "entries_parsed", len(parseResult.Entries),
+ "rows_affected", rowsAffected,
+ )
+}