aboutsummaryrefslogtreecommitdiffhomepage
path: root/backend/graphql
diff options
context:
space:
mode:
Diffstat (limited to 'backend/graphql')
-rw-r--r--backend/graphql/resolver/resolver.go15
-rw-r--r--backend/graphql/resolver/schema.resolvers.go (renamed from backend/graphql/resolvers.go)236
2 files changed, 117 insertions, 134 deletions
diff --git a/backend/graphql/resolver/resolver.go b/backend/graphql/resolver/resolver.go
new file mode 100644
index 0000000..7a9c389
--- /dev/null
+++ b/backend/graphql/resolver/resolver.go
@@ -0,0 +1,15 @@
+package resolver
+
+import (
+ "database/sql"
+ "undef.ninja/x/feedaka/db"
+)
+
+// This file will not be regenerated automatically.
+//
+// It serves as dependency injection for your app, add any dependencies you require here.
+
+type Resolver struct {
+ DB *sql.DB
+ Queries *db.Queries
+}
diff --git a/backend/graphql/resolvers.go b/backend/graphql/resolver/schema.resolvers.go
index 3e73162..0ee771b 100644
--- a/backend/graphql/resolvers.go
+++ b/backend/graphql/resolver/schema.resolvers.go
@@ -1,6 +1,8 @@
-package graphql
+package resolver
-// THIS CODE WILL BE UPDATED WITH SCHEMA CHANGES. PREVIOUS IMPLEMENTATION FOR SCHEMA CHANGES WILL BE KEPT IN THE COMMENT SECTION. IMPLEMENTATION FOR UNCHANGED SCHEMA WILL BE KEPT.
+// 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"
@@ -10,14 +12,11 @@ import (
"time"
"github.com/mmcdole/gofeed"
-
+ "undef.ninja/x/feedaka/db"
+ gql "undef.ninja/x/feedaka/graphql"
"undef.ninja/x/feedaka/graphql/model"
)
-type Resolver struct {
- DB *sql.DB
-}
-
// 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
@@ -28,25 +27,24 @@ func (r *mutationResolver) AddFeed(ctx context.Context, url string) (*model.Feed
}
// Insert the feed into the database
- result, err := r.DB.Exec(
- "INSERT INTO feeds (url, title, fetched_at) VALUES (?, ?, ?)",
- url, feed.Title, time.Now().UTC().Format(time.RFC3339),
- )
+ 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)
}
- id, err := result.LastInsertId()
- if err != nil {
- return nil, fmt.Errorf("failed to get last insert id: %w", err)
- }
-
// Insert articles from the feed
for _, item := range feed.Items {
- _, err = r.DB.Exec(
- "INSERT INTO articles (feed_id, guid, title, url, is_read) VALUES (?, ?, ?, ?, ?)",
- id, item.GUID, item.Title, item.Link, 0,
- )
+ _, 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)
@@ -54,10 +52,10 @@ func (r *mutationResolver) AddFeed(ctx context.Context, url string) (*model.Feed
}
return &model.Feed{
- ID: strconv.FormatInt(id, 10),
- URL: url,
- Title: feed.Title,
- FetchedAt: time.Now().Format(time.RFC3339),
+ ID: strconv.FormatInt(dbFeed.ID, 10),
+ URL: dbFeed.Url,
+ Title: dbFeed.Title,
+ FetchedAt: dbFeed.FetchedAt,
}, nil
}
@@ -75,27 +73,23 @@ func (r *mutationResolver) RemoveFeed(ctx context.Context, id string) (bool, err
}
defer tx.Rollback()
+ qtx := r.Queries.WithTx(tx)
+
// Delete articles first (foreign key constraint)
- _, err = tx.Exec("DELETE FROM articles WHERE feed_id = ?", feedID)
+ err = qtx.DeleteArticlesByFeed(ctx, feedID)
if err != nil {
return false, fmt.Errorf("failed to delete articles: %w", err)
}
// Delete the feed
- result, err := tx.Exec("DELETE FROM feeds WHERE id = ?", feedID)
+ 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)
}
- rowsAffected, err := result.RowsAffected()
- if err != nil {
- return false, fmt.Errorf("failed to get rows affected: %w", err)
- }
-
- if rowsAffected == 0 {
- return false, fmt.Errorf("feed not found")
- }
-
err = tx.Commit()
if err != nil {
return false, fmt.Errorf("failed to commit transaction: %w", err)
@@ -112,7 +106,10 @@ func (r *mutationResolver) MarkArticleRead(ctx context.Context, id string) (*mod
}
// Update the article's read status
- _, err = r.DB.Exec("UPDATE articles SET is_read = 1 WHERE id = ?", articleID)
+ 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)
}
@@ -129,7 +126,10 @@ func (r *mutationResolver) MarkArticleUnread(ctx context.Context, id string) (*m
}
// Update the article's read status
- _, err = r.DB.Exec("UPDATE articles SET is_read = 0 WHERE id = ?", articleID)
+ 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)
}
@@ -146,7 +146,7 @@ func (r *mutationResolver) MarkFeedRead(ctx context.Context, id string) (*model.
}
// Update all articles in the feed to be read
- _, err = r.DB.Exec("UPDATE articles SET is_read = 1 WHERE feed_id = ?", feedID)
+ err = r.Queries.MarkFeedArticlesRead(ctx, feedID)
if err != nil {
return nil, fmt.Errorf("failed to mark feed as read: %w", err)
}
@@ -163,7 +163,7 @@ func (r *mutationResolver) MarkFeedUnread(ctx context.Context, id string) (*mode
}
// Update all articles in the feed to be unread
- _, err = r.DB.Exec("UPDATE articles SET is_read = 0 WHERE feed_id = ?", feedID)
+ err = r.Queries.MarkFeedArticlesUnread(ctx, feedID)
if err != nil {
return nil, fmt.Errorf("failed to mark feed as unread: %w", err)
}
@@ -174,24 +174,19 @@ func (r *mutationResolver) MarkFeedUnread(ctx context.Context, id string) (*mode
// Feeds is the resolver for the feeds field.
func (r *queryResolver) Feeds(ctx context.Context) ([]*model.Feed, error) {
- rows, err := r.DB.Query("SELECT id, url, title, fetched_at FROM feeds")
+ dbFeeds, err := r.Queries.GetFeeds(ctx)
if err != nil {
return nil, fmt.Errorf("failed to query feeds: %w", err)
}
- defer rows.Close()
var feeds []*model.Feed
- for rows.Next() {
- var feed model.Feed
- err := rows.Scan(&feed.ID, &feed.URL, &feed.Title, &feed.FetchedAt)
- if err != nil {
- return nil, fmt.Errorf("failed to scan feed: %w", err)
- }
- feeds = append(feeds, &feed)
- }
-
- if err = rows.Err(); err != nil {
- return nil, fmt.Errorf("error iterating over feeds: %w", err)
+ 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
@@ -199,39 +194,26 @@ func (r *queryResolver) Feeds(ctx context.Context) ([]*model.Feed, error) {
// UnreadArticles is the resolver for the unreadArticles field.
func (r *queryResolver) UnreadArticles(ctx context.Context) ([]*model.Article, error) {
- rows, err := r.DB.Query(`
- SELECT a.id, a.feed_id, a.guid, a.title, a.url, a.is_read,
- f.id, f.url, f.title
- FROM articles AS a
- INNER JOIN feeds AS f ON a.feed_id = f.id
- WHERE a.is_read = 0
- ORDER BY a.id DESC
- LIMIT 100
- `)
+ rows, err := r.Queries.GetUnreadArticles(ctx)
if err != nil {
return nil, fmt.Errorf("failed to query unread articles: %w", err)
}
- defer rows.Close()
var articles []*model.Article
- for rows.Next() {
- var article model.Article
- var feed model.Feed
- var isRead int
- err := rows.Scan(
- &article.ID, &article.FeedID, &article.GUID, &article.Title, &article.URL, &isRead,
- &feed.ID, &feed.URL, &feed.Title,
- )
- if err != nil {
- return nil, fmt.Errorf("failed to scan article: %w", err)
- }
- article.IsRead = isRead == 1
- article.Feed = &feed
- articles = append(articles, &article)
- }
-
- if err = rows.Err(); err != nil {
- return nil, fmt.Errorf("error iterating over articles: %w", err)
+ 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
@@ -239,39 +221,26 @@ func (r *queryResolver) UnreadArticles(ctx context.Context) ([]*model.Article, e
// ReadArticles is the resolver for the readArticles field.
func (r *queryResolver) ReadArticles(ctx context.Context) ([]*model.Article, error) {
- rows, err := r.DB.Query(`
- SELECT a.id, a.feed_id, a.guid, a.title, a.url, a.is_read,
- f.id, f.url, f.title
- FROM articles AS a
- INNER JOIN feeds AS f ON a.feed_id = f.id
- WHERE a.is_read = 1
- ORDER BY a.id DESC
- LIMIT 100
- `)
+ rows, err := r.Queries.GetReadArticles(ctx)
if err != nil {
return nil, fmt.Errorf("failed to query read articles: %w", err)
}
- defer rows.Close()
var articles []*model.Article
- for rows.Next() {
- var article model.Article
- var feed model.Feed
- var isRead int
- err := rows.Scan(
- &article.ID, &article.FeedID, &article.GUID, &article.Title, &article.URL, &isRead,
- &feed.ID, &feed.URL, &feed.Title,
- )
- if err != nil {
- return nil, fmt.Errorf("failed to scan article: %w", err)
- }
- article.IsRead = isRead == 1
- article.Feed = &feed
- articles = append(articles, &article)
- }
-
- if err = rows.Err(); err != nil {
- return nil, fmt.Errorf("error iterating over articles: %w", err)
+ 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
@@ -284,11 +253,7 @@ func (r *queryResolver) Feed(ctx context.Context, id string) (*model.Feed, error
return nil, fmt.Errorf("invalid feed ID: %w", err)
}
- var feed model.Feed
- err = r.DB.QueryRow(
- "SELECT id, url, title, fetched_at FROM feeds WHERE id = ?",
- feedID,
- ).Scan(&feed.ID, &feed.URL, &feed.Title, &feed.FetchedAt)
+ dbFeed, err := r.Queries.GetFeed(ctx, feedID)
if err != nil {
if err == sql.ErrNoRows {
return nil, fmt.Errorf("feed not found")
@@ -296,7 +261,12 @@ func (r *queryResolver) Feed(ctx context.Context, id string) (*model.Feed, error
return nil, fmt.Errorf("failed to query feed: %w", err)
}
- return &feed, nil
+ 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.
@@ -306,36 +276,34 @@ func (r *queryResolver) Article(ctx context.Context, id string) (*model.Article,
return nil, fmt.Errorf("invalid article ID: %w", err)
}
- var article model.Article
- var feed model.Feed
- var isRead int
- err = r.DB.QueryRow(`
- SELECT a.id, a.feed_id, a.guid, a.title, a.url, a.is_read,
- f.id, f.url, f.title
- FROM articles AS a
- INNER JOIN feeds AS f ON a.feed_id = f.id
- WHERE a.id = ?
- `, articleID).Scan(
- &article.ID, &article.FeedID, &article.GUID, &article.Title, &article.URL, &isRead,
- &feed.ID, &feed.URL, &feed.Title,
- )
+ 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)
}
- article.IsRead = isRead == 1
- article.Feed = &feed
- return &article, nil
+ 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 MutationResolver implementation.
-func (r *Resolver) Mutation() MutationResolver { return &mutationResolver{r} }
+// Mutation returns gql.MutationResolver implementation.
+func (r *Resolver) Mutation() gql.MutationResolver { return &mutationResolver{r} }
-// Query returns QueryResolver implementation.
-func (r *Resolver) Query() QueryResolver { return &queryResolver{r} }
+// Query returns gql.QueryResolver implementation.
+func (r *Resolver) Query() gql.QueryResolver { return &queryResolver{r} }
type mutationResolver struct{ *Resolver }
type queryResolver struct{ *Resolver }