aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--backend/cmd/serve.go62
-rw-r--r--backend/feed/feed.go75
-rw-r--r--backend/graphql/resolver/schema.resolvers.go33
3 files changed, 84 insertions, 86 deletions
diff --git a/backend/cmd/serve.go b/backend/cmd/serve.go
index 30d0702..e2d2df5 100644
--- a/backend/cmd/serve.go
+++ b/backend/cmd/serve.go
@@ -4,7 +4,6 @@ import (
"context"
"database/sql"
"embed"
- "fmt"
"log"
"net/http"
"os"
@@ -20,78 +19,23 @@ import (
"github.com/labstack/echo-contrib/session"
"github.com/labstack/echo/v4"
"github.com/labstack/echo/v4/middleware"
- "github.com/mmcdole/gofeed"
"github.com/vektah/gqlparser/v2/ast"
"undef.ninja/x/feedaka/auth"
"undef.ninja/x/feedaka/config"
"undef.ninja/x/feedaka/db"
+ "undef.ninja/x/feedaka/feed"
"undef.ninja/x/feedaka/graphql"
"undef.ninja/x/feedaka/graphql/resolver"
)
func fetchOneFeed(feedID int64, url string, ctx context.Context, queries *db.Queries) error {
log.Printf("Fetching %s...\n", url)
- fp := gofeed.NewParser()
- ctx, cancel := context.WithTimeout(ctx, 10*time.Second)
- defer cancel()
- feed, err := fp.ParseURLWithContext(url, ctx)
- if err != nil {
- return fmt.Errorf("Failed to fetch %s: %v\n", url, err)
- }
- err = queries.UpdateFeedMetadata(ctx, db.UpdateFeedMetadataParams{
- Title: feed.Title,
- FetchedAt: time.Now().UTC().Format(time.RFC3339),
- ID: feedID,
- })
- if err != nil {
- return err
- }
- // Get GUIDs for this feed (for updating existing articles)
- guids, err := queries.GetArticleGUIDsByFeed(ctx, feedID)
+ f, err := feed.Fetch(ctx, url)
if err != nil {
return err
}
- existingFeedGUIDs := make(map[string]bool)
- for _, guid := range guids {
- existingFeedGUIDs[guid] = true
- }
- for _, item := range feed.Items {
- if existingFeedGUIDs[item.GUID] {
- // Article exists in this feed, update it
- err := queries.UpdateArticle(ctx, db.UpdateArticleParams{
- Title: item.Title,
- Url: item.Link,
- FeedID: feedID,
- Guid: item.GUID,
- })
- if err != nil {
- return err
- }
- } else {
- // Check if article with same GUID exists globally (in any feed)
- exists, err := queries.CheckArticleExistsByGUID(ctx, item.GUID)
- if err != nil {
- return err
- }
- if exists == 1 {
- // Article already exists in another feed, skip
- continue
- }
- // Create new article
- _, err = queries.CreateArticle(ctx, db.CreateArticleParams{
- FeedID: feedID,
- Guid: item.GUID,
- Title: item.Title,
- Url: item.Link,
- IsRead: 0,
- })
- if err != nil {
- return err
- }
- }
- }
- return nil
+ return feed.Sync(ctx, queries, feedID, f)
}
func listFeedsToBeFetched(ctx context.Context, queries *db.Queries) (map[int64]string, error) {
diff --git a/backend/feed/feed.go b/backend/feed/feed.go
new file mode 100644
index 0000000..4349d1e
--- /dev/null
+++ b/backend/feed/feed.go
@@ -0,0 +1,75 @@
+package feed
+
+import (
+ "context"
+ "fmt"
+ "time"
+
+ "github.com/mmcdole/gofeed"
+
+ "undef.ninja/x/feedaka/db"
+)
+
+func Fetch(ctx context.Context, url string) (*gofeed.Feed, error) {
+ fp := gofeed.NewParser()
+ ctx, cancel := context.WithTimeout(ctx, 10*time.Second)
+ defer cancel()
+ feed, err := fp.ParseURLWithContext(url, ctx)
+ if err != nil {
+ return nil, fmt.Errorf("failed to fetch %s: %w", url, err)
+ }
+ return feed, nil
+}
+
+func Sync(ctx context.Context, queries *db.Queries, feedID int64, f *gofeed.Feed) error {
+ err := queries.UpdateFeedMetadata(ctx, db.UpdateFeedMetadataParams{
+ Title: f.Title,
+ FetchedAt: time.Now().UTC().Format(time.RFC3339),
+ ID: feedID,
+ })
+ if err != nil {
+ return err
+ }
+
+ guids, err := queries.GetArticleGUIDsByFeed(ctx, feedID)
+ if err != nil {
+ return err
+ }
+ existingFeedGUIDs := make(map[string]bool, len(guids))
+ for _, guid := range guids {
+ existingFeedGUIDs[guid] = true
+ }
+
+ for _, item := range f.Items {
+ if existingFeedGUIDs[item.GUID] {
+ err := queries.UpdateArticle(ctx, db.UpdateArticleParams{
+ Title: item.Title,
+ Url: item.Link,
+ FeedID: feedID,
+ Guid: item.GUID,
+ })
+ if err != nil {
+ return err
+ }
+ } else {
+ exists, err := queries.CheckArticleExistsByGUID(ctx, item.GUID)
+ if err != nil {
+ return err
+ }
+ if exists == 1 {
+ continue
+ }
+ _, err = queries.CreateArticle(ctx, db.CreateArticleParams{
+ FeedID: feedID,
+ Guid: item.GUID,
+ Title: item.Title,
+ Url: item.Link,
+ IsRead: 0,
+ })
+ if err != nil {
+ return err
+ }
+ }
+ }
+ return nil
+}
diff --git a/backend/graphql/resolver/schema.resolvers.go b/backend/graphql/resolver/schema.resolvers.go
index c3f6f0a..10f892f 100644
--- a/backend/graphql/resolver/schema.resolvers.go
+++ b/backend/graphql/resolver/schema.resolvers.go
@@ -11,9 +11,9 @@ import (
"strconv"
"time"
- "github.com/mmcdole/gofeed"
"undef.ninja/x/feedaka/auth"
"undef.ninja/x/feedaka/db"
+ "undef.ninja/x/feedaka/feed"
gql "undef.ninja/x/feedaka/graphql"
"undef.ninja/x/feedaka/graphql/model"
)
@@ -26,8 +26,7 @@ func (r *mutationResolver) AddFeed(ctx context.Context, url string) (*model.Feed
}
// Fetch the feed to get its title
- fp := gofeed.NewParser()
- feed, err := fp.ParseURL(url)
+ f, err := feed.Fetch(ctx, url)
if err != nil {
return nil, fmt.Errorf("failed to parse feed: %w", err)
}
@@ -35,7 +34,7 @@ func (r *mutationResolver) AddFeed(ctx context.Context, url string) (*model.Feed
// Insert the feed into the database
dbFeed, err := r.Queries.CreateFeed(ctx, db.CreateFeedParams{
Url: url,
- Title: feed.Title,
+ Title: f.Title,
FetchedAt: time.Now().UTC().Format(time.RFC3339),
UserID: userID,
})
@@ -43,29 +42,9 @@ func (r *mutationResolver) AddFeed(ctx context.Context, url string) (*model.Feed
return nil, fmt.Errorf("failed to insert feed: %w", err)
}
- // Insert articles from the feed (skip duplicates by guid)
- for _, item := range feed.Items {
- // Check if article with same GUID already exists globally
- exists, err := r.Queries.CheckArticleExistsByGUID(ctx, item.GUID)
- if err != nil {
- fmt.Printf("Failed to check article existence: %v\n", err)
- continue
- }
- if exists == 1 {
- // Article already exists, skip
- continue
- }
- _, err = r.Queries.CreateArticle(ctx, db.CreateArticleParams{
- FeedID: dbFeed.ID,
- Guid: item.GUID,
- Title: item.Title,
- Url: item.Link,
- IsRead: 0,
- })
- if err != nil {
- // Log but don't fail on individual article errors
- fmt.Printf("Failed to insert article: %v\n", err)
- }
+ // Sync articles from the feed
+ if err := feed.Sync(ctx, r.Queries, dbFeed.ID, f); err != nil {
+ return nil, fmt.Errorf("failed to sync articles: %w", err)
}
return &model.Feed{