package resolver // This file will be automatically regenerated based on the schema, any resolver implementations // will be copied through when generating and any unknown code will be moved to the end. // Code generated by github.com/99designs/gqlgen version v0.17.76 import ( "context" "database/sql" "fmt" "strconv" "time" "github.com/mmcdole/gofeed" "undef.ninja/x/feedaka/db" gql "undef.ninja/x/feedaka/graphql" "undef.ninja/x/feedaka/graphql/model" ) // AddFeed is the resolver for the addFeed field. func (r *mutationResolver) AddFeed(ctx context.Context, url string) (*model.Feed, error) { // Fetch the feed to get its title fp := gofeed.NewParser() feed, err := fp.ParseURL(url) if err != nil { return nil, fmt.Errorf("failed to parse feed: %w", err) } // Insert the feed into the database dbFeed, err := r.Queries.CreateFeed(ctx, db.CreateFeedParams{ Url: url, Title: feed.Title, FetchedAt: time.Now().UTC().Format(time.RFC3339), }) if err != nil { return nil, fmt.Errorf("failed to insert feed: %w", err) } // Insert articles from the feed for _, item := range feed.Items { _, 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) } } return &model.Feed{ ID: strconv.FormatInt(dbFeed.ID, 10), URL: dbFeed.Url, Title: dbFeed.Title, FetchedAt: dbFeed.FetchedAt, }, nil } // RemoveFeed is the resolver for the removeFeed field. func (r *mutationResolver) RemoveFeed(ctx context.Context, id string) (bool, error) { feedID, err := strconv.ParseInt(id, 10, 64) if err != nil { return false, fmt.Errorf("invalid feed ID: %w", err) } // Start a transaction tx, err := r.DB.Begin() if err != nil { return false, fmt.Errorf("failed to begin transaction: %w", err) } defer tx.Rollback() qtx := r.Queries.WithTx(tx) // Delete articles first (foreign key constraint) err = qtx.DeleteArticlesByFeed(ctx, feedID) if err != nil { return false, fmt.Errorf("failed to delete articles: %w", err) } // Delete the feed err = qtx.DeleteFeed(ctx, feedID) if err != nil { if err == sql.ErrNoRows { return false, fmt.Errorf("feed not found") } return false, fmt.Errorf("failed to delete feed: %w", err) } err = tx.Commit() if err != nil { return false, fmt.Errorf("failed to commit transaction: %w", err) } return true, nil } // MarkArticleRead is the resolver for the markArticleRead field. func (r *mutationResolver) MarkArticleRead(ctx context.Context, id string) (*model.Article, error) { articleID, err := strconv.ParseInt(id, 10, 64) if err != nil { return nil, fmt.Errorf("invalid article ID: %w", err) } // Update the article's read status err = r.Queries.UpdateArticleReadStatus(ctx, db.UpdateArticleReadStatusParams{ IsRead: 1, ID: articleID, }) if err != nil { return nil, fmt.Errorf("failed to mark article as read: %w", err) } // Fetch the updated article return r.Query().Article(ctx, id) } // MarkArticleUnread is the resolver for the markArticleUnread field. func (r *mutationResolver) MarkArticleUnread(ctx context.Context, id string) (*model.Article, error) { articleID, err := strconv.ParseInt(id, 10, 64) if err != nil { return nil, fmt.Errorf("invalid article ID: %w", err) } // Update the article's read status err = r.Queries.UpdateArticleReadStatus(ctx, db.UpdateArticleReadStatusParams{ IsRead: 0, ID: articleID, }) if err != nil { return nil, fmt.Errorf("failed to mark article as unread: %w", err) } // Fetch the updated article return r.Query().Article(ctx, id) } // MarkFeedRead is the resolver for the markFeedRead field. func (r *mutationResolver) MarkFeedRead(ctx context.Context, id string) (*model.Feed, error) { feedID, err := strconv.ParseInt(id, 10, 64) if err != nil { return nil, fmt.Errorf("invalid feed ID: %w", err) } // Update all articles in the feed to be read err = r.Queries.MarkFeedArticlesRead(ctx, feedID) if err != nil { return nil, fmt.Errorf("failed to mark feed as read: %w", err) } // Fetch the updated feed return r.Query().Feed(ctx, id) } // MarkFeedUnread is the resolver for the markFeedUnread field. func (r *mutationResolver) MarkFeedUnread(ctx context.Context, id string) (*model.Feed, error) { feedID, err := strconv.ParseInt(id, 10, 64) if err != nil { return nil, fmt.Errorf("invalid feed ID: %w", err) } // Update all articles in the feed to be unread err = r.Queries.MarkFeedArticlesUnread(ctx, feedID) if err != nil { return nil, fmt.Errorf("failed to mark feed as unread: %w", err) } // Fetch the updated feed return r.Query().Feed(ctx, id) } // Feeds is the resolver for the feeds field. func (r *queryResolver) Feeds(ctx context.Context) ([]*model.Feed, error) { dbFeeds, err := r.Queries.GetFeeds(ctx) if err != nil { return nil, fmt.Errorf("failed to query feeds: %w", err) } var feeds []*model.Feed for _, dbFeed := range dbFeeds { feeds = append(feeds, &model.Feed{ ID: strconv.FormatInt(dbFeed.ID, 10), URL: dbFeed.Url, Title: dbFeed.Title, FetchedAt: dbFeed.FetchedAt, }) } return feeds, nil } // UnreadArticles is the resolver for the unreadArticles field. func (r *queryResolver) UnreadArticles(ctx context.Context) ([]*model.Article, error) { rows, err := r.Queries.GetUnreadArticles(ctx) if err != nil { return nil, fmt.Errorf("failed to query unread articles: %w", err) } var articles []*model.Article for _, row := range rows { articles = append(articles, &model.Article{ ID: strconv.FormatInt(row.ID, 10), FeedID: strconv.FormatInt(row.FeedID, 10), GUID: row.Guid, Title: row.Title, URL: row.Url, IsRead: row.IsRead == 1, Feed: &model.Feed{ ID: strconv.FormatInt(row.FeedID2, 10), URL: row.FeedUrl, Title: row.FeedTitle, }, }) } return articles, nil } // ReadArticles is the resolver for the readArticles field. func (r *queryResolver) ReadArticles(ctx context.Context) ([]*model.Article, error) { rows, err := r.Queries.GetReadArticles(ctx) if err != nil { return nil, fmt.Errorf("failed to query read articles: %w", err) } var articles []*model.Article for _, row := range rows { articles = append(articles, &model.Article{ ID: strconv.FormatInt(row.ID, 10), FeedID: strconv.FormatInt(row.FeedID, 10), GUID: row.Guid, Title: row.Title, URL: row.Url, IsRead: row.IsRead == 1, Feed: &model.Feed{ ID: strconv.FormatInt(row.FeedID2, 10), URL: row.FeedUrl, Title: row.FeedTitle, }, }) } return articles, nil } // Feed is the resolver for the feed field. func (r *queryResolver) Feed(ctx context.Context, id string) (*model.Feed, error) { feedID, err := strconv.ParseInt(id, 10, 64) if err != nil { return nil, fmt.Errorf("invalid feed ID: %w", err) } dbFeed, err := r.Queries.GetFeed(ctx, feedID) if err != nil { if err == sql.ErrNoRows { return nil, fmt.Errorf("feed not found") } return nil, fmt.Errorf("failed to query feed: %w", err) } return &model.Feed{ ID: strconv.FormatInt(dbFeed.ID, 10), URL: dbFeed.Url, Title: dbFeed.Title, FetchedAt: dbFeed.FetchedAt, }, nil } // Article is the resolver for the article field. func (r *queryResolver) Article(ctx context.Context, id string) (*model.Article, error) { articleID, err := strconv.ParseInt(id, 10, 64) if err != nil { return nil, fmt.Errorf("invalid article ID: %w", err) } row, err := r.Queries.GetArticle(ctx, articleID) if err != nil { if err == sql.ErrNoRows { return nil, fmt.Errorf("article not found") } return nil, fmt.Errorf("failed to query article: %w", err) } return &model.Article{ ID: strconv.FormatInt(row.ID, 10), FeedID: strconv.FormatInt(row.FeedID, 10), GUID: row.Guid, Title: row.Title, URL: row.Url, IsRead: row.IsRead == 1, Feed: &model.Feed{ ID: strconv.FormatInt(row.FeedID2, 10), URL: row.FeedUrl, Title: row.FeedTitle, }, }, nil } // Mutation returns gql.MutationResolver implementation. func (r *Resolver) Mutation() gql.MutationResolver { return &mutationResolver{r} } // Query returns gql.QueryResolver implementation. func (r *Resolver) Query() gql.QueryResolver { return &queryResolver{r} } type mutationResolver struct{ *Resolver } type queryResolver struct{ *Resolver }