diff options
| -rw-r--r-- | backend/db/articles.sql.go | 70 | ||||
| -rw-r--r-- | backend/db/feeds.sql.go | 25 | ||||
| -rw-r--r-- | backend/db/models.go | 9 | ||||
| -rw-r--r-- | backend/db/queries/articles.sql | 10 | ||||
| -rw-r--r-- | backend/db/queries/feeds.sql | 15 | ||||
| -rw-r--r-- | backend/db/schema.sql | 9 | ||||
| -rw-r--r-- | backend/graphql/generated.go | 120 | ||||
| -rw-r--r-- | backend/graphql/model/generated.go | 2 | ||||
| -rw-r--r-- | backend/graphql/resolver/schema.resolvers.go | 73 | ||||
| -rw-r--r-- | common/graphql/schema.graphql | 9 | ||||
| -rw-r--r-- | frontend/src/components/AddFeedForm.tsx | 8 | ||||
| -rw-r--r-- | frontend/src/components/FeedList.tsx | 20 | ||||
| -rw-r--r-- | frontend/src/graphql/generated/gql.ts | 12 | ||||
| -rw-r--r-- | frontend/src/graphql/generated/graphql.ts | 34 | ||||
| -rw-r--r-- | frontend/src/graphql/mutations.graphql | 4 | ||||
| -rw-r--r-- | frontend/src/graphql/queries.graphql | 5 | ||||
| -rw-r--r-- | frontend/src/pages/Settings.tsx | 24 | ||||
| -rw-r--r-- | justfile | 2 |
18 files changed, 277 insertions, 174 deletions
diff --git a/backend/db/articles.sql.go b/backend/db/articles.sql.go index 9e60cb4..7492598 100644 --- a/backend/db/articles.sql.go +++ b/backend/db/articles.sql.go @@ -75,22 +75,23 @@ func (q *Queries) DeleteArticlesByFeed(ctx context.Context, feedID int64) error const getArticle = `-- name: GetArticle :one SELECT a.id, a.feed_id, a.guid, a.title, a.url, a.is_read, - f.id as feed_id_2, f.url as feed_url, f.title as feed_title + f.id as feed_id_2, f.url as feed_url, f.title as feed_title, f.is_subscribed as feed_is_subscribed FROM articles AS a INNER JOIN feeds AS f ON a.feed_id = f.id WHERE a.id = ? ` type GetArticleRow struct { - ID int64 - FeedID int64 - Guid string - Title string - Url string - IsRead int64 - FeedID2 int64 - FeedUrl string - FeedTitle string + ID int64 + FeedID int64 + Guid string + Title string + Url string + IsRead int64 + FeedID2 int64 + FeedUrl string + FeedTitle string + FeedIsSubscribed int64 } func (q *Queries) GetArticle(ctx context.Context, id int64) (GetArticleRow, error) { @@ -106,6 +107,7 @@ func (q *Queries) GetArticle(ctx context.Context, id int64) (GetArticleRow, erro &i.FeedID2, &i.FeedUrl, &i.FeedTitle, + &i.FeedIsSubscribed, ) return i, err } @@ -179,24 +181,25 @@ func (q *Queries) GetArticlesByFeed(ctx context.Context, feedID int64) ([]Articl const getReadArticles = `-- name: GetReadArticles :many SELECT a.id, a.feed_id, a.guid, a.title, a.url, a.is_read, - f.id as feed_id_2, f.url as feed_url, f.title as feed_title + f.id as feed_id_2, f.url as feed_url, f.title as feed_title, f.is_subscribed as feed_is_subscribed FROM articles AS a INNER JOIN feeds AS f ON a.feed_id = f.id -WHERE a.is_read = 1 +WHERE a.is_read = 1 AND f.is_subscribed = 1 ORDER BY a.id DESC LIMIT 100 ` type GetReadArticlesRow struct { - ID int64 - FeedID int64 - Guid string - Title string - Url string - IsRead int64 - FeedID2 int64 - FeedUrl string - FeedTitle string + ID int64 + FeedID int64 + Guid string + Title string + Url string + IsRead int64 + FeedID2 int64 + FeedUrl string + FeedTitle string + FeedIsSubscribed int64 } func (q *Queries) GetReadArticles(ctx context.Context) ([]GetReadArticlesRow, error) { @@ -218,6 +221,7 @@ func (q *Queries) GetReadArticles(ctx context.Context) ([]GetReadArticlesRow, er &i.FeedID2, &i.FeedUrl, &i.FeedTitle, + &i.FeedIsSubscribed, ); err != nil { return nil, err } @@ -235,24 +239,25 @@ func (q *Queries) GetReadArticles(ctx context.Context) ([]GetReadArticlesRow, er const getUnreadArticles = `-- name: GetUnreadArticles :many SELECT a.id, a.feed_id, a.guid, a.title, a.url, a.is_read, - f.id as feed_id_2, f.url as feed_url, f.title as feed_title + f.id as feed_id_2, f.url as feed_url, f.title as feed_title, f.is_subscribed as feed_is_subscribed FROM articles AS a INNER JOIN feeds AS f ON a.feed_id = f.id -WHERE a.is_read = 0 +WHERE a.is_read = 0 AND f.is_subscribed = 1 ORDER BY a.id DESC LIMIT 100 ` type GetUnreadArticlesRow struct { - ID int64 - FeedID int64 - Guid string - Title string - Url string - IsRead int64 - FeedID2 int64 - FeedUrl string - FeedTitle string + ID int64 + FeedID int64 + Guid string + Title string + Url string + IsRead int64 + FeedID2 int64 + FeedUrl string + FeedTitle string + FeedIsSubscribed int64 } func (q *Queries) GetUnreadArticles(ctx context.Context) ([]GetUnreadArticlesRow, error) { @@ -274,6 +279,7 @@ func (q *Queries) GetUnreadArticles(ctx context.Context) ([]GetUnreadArticlesRow &i.FeedID2, &i.FeedUrl, &i.FeedTitle, + &i.FeedIsSubscribed, ); err != nil { return nil, err } diff --git a/backend/db/feeds.sql.go b/backend/db/feeds.sql.go index 4db84af..29b26ca 100644 --- a/backend/db/feeds.sql.go +++ b/backend/db/feeds.sql.go @@ -12,7 +12,7 @@ import ( const createFeed = `-- name: CreateFeed :one INSERT INTO feeds (url, title, fetched_at) VALUES (?, ?, ?) -RETURNING id, url, title, fetched_at +RETURNING id, url, title, fetched_at, is_subscribed ` type CreateFeedParams struct { @@ -29,6 +29,7 @@ func (q *Queries) CreateFeed(ctx context.Context, arg CreateFeedParams) (Feed, e &i.Url, &i.Title, &i.FetchedAt, + &i.IsSubscribed, ) return i, err } @@ -44,7 +45,7 @@ func (q *Queries) DeleteFeed(ctx context.Context, id int64) error { } const getFeed = `-- name: GetFeed :one -SELECT id, url, title, fetched_at +SELECT id, url, title, fetched_at, is_subscribed FROM feeds WHERE id = ? ` @@ -57,12 +58,13 @@ func (q *Queries) GetFeed(ctx context.Context, id int64) (Feed, error) { &i.Url, &i.Title, &i.FetchedAt, + &i.IsSubscribed, ) return i, err } const getFeedByURL = `-- name: GetFeedByURL :one -SELECT id, url, title, fetched_at +SELECT id, url, title, fetched_at, is_subscribed FROM feeds WHERE url = ? ` @@ -75,13 +77,15 @@ func (q *Queries) GetFeedByURL(ctx context.Context, url string) (Feed, error) { &i.Url, &i.Title, &i.FetchedAt, + &i.IsSubscribed, ) return i, err } const getFeeds = `-- name: GetFeeds :many -SELECT id, url, title, fetched_at +SELECT id, url, title, fetched_at, is_subscribed FROM feeds +WHERE is_subscribed = 1 ORDER BY id ` @@ -99,6 +103,7 @@ func (q *Queries) GetFeeds(ctx context.Context) ([]Feed, error) { &i.Url, &i.Title, &i.FetchedAt, + &i.IsSubscribed, ); err != nil { return nil, err } @@ -116,6 +121,7 @@ func (q *Queries) GetFeeds(ctx context.Context) ([]Feed, error) { const getFeedsToFetch = `-- name: GetFeedsToFetch :many SELECT id, url, fetched_at FROM feeds +WHERE is_subscribed = 1 ` type GetFeedsToFetchRow struct { @@ -147,6 +153,17 @@ func (q *Queries) GetFeedsToFetch(ctx context.Context) ([]GetFeedsToFetchRow, er return items, nil } +const unsubscribeFeed = `-- name: UnsubscribeFeed :exec +UPDATE feeds +SET is_subscribed = 0 +WHERE id = ? +` + +func (q *Queries) UnsubscribeFeed(ctx context.Context, id int64) error { + _, err := q.db.ExecContext(ctx, unsubscribeFeed, id) + return err +} + const updateFeedMetadata = `-- name: UpdateFeedMetadata :exec UPDATE feeds SET title = ?, fetched_at = ? diff --git a/backend/db/models.go b/backend/db/models.go index 2f36cb4..94ab9c0 100644 --- a/backend/db/models.go +++ b/backend/db/models.go @@ -14,8 +14,9 @@ type Article struct { } type Feed struct { - ID int64 - Url string - Title string - FetchedAt string + ID int64 + Url string + Title string + FetchedAt string + IsSubscribed int64 } diff --git a/backend/db/queries/articles.sql b/backend/db/queries/articles.sql index 3f1590a..c1feaae 100644 --- a/backend/db/queries/articles.sql +++ b/backend/db/queries/articles.sql @@ -1,7 +1,7 @@ -- name: GetArticle :one SELECT a.id, a.feed_id, a.guid, a.title, a.url, a.is_read, - f.id as feed_id_2, f.url as feed_url, f.title as feed_title + f.id as feed_id_2, f.url as feed_url, f.title as feed_title, f.is_subscribed as feed_is_subscribed FROM articles AS a INNER JOIN feeds AS f ON a.feed_id = f.id WHERE a.id = ?; @@ -9,20 +9,20 @@ WHERE a.id = ?; -- name: GetUnreadArticles :many SELECT a.id, a.feed_id, a.guid, a.title, a.url, a.is_read, - f.id as feed_id_2, f.url as feed_url, f.title as feed_title + f.id as feed_id_2, f.url as feed_url, f.title as feed_title, f.is_subscribed as feed_is_subscribed FROM articles AS a INNER JOIN feeds AS f ON a.feed_id = f.id -WHERE a.is_read = 0 +WHERE a.is_read = 0 AND f.is_subscribed = 1 ORDER BY a.id DESC LIMIT 100; -- name: GetReadArticles :many SELECT a.id, a.feed_id, a.guid, a.title, a.url, a.is_read, - f.id as feed_id_2, f.url as feed_url, f.title as feed_title + f.id as feed_id_2, f.url as feed_url, f.title as feed_title, f.is_subscribed as feed_is_subscribed FROM articles AS a INNER JOIN feeds AS f ON a.feed_id = f.id -WHERE a.is_read = 1 +WHERE a.is_read = 1 AND f.is_subscribed = 1 ORDER BY a.id DESC LIMIT 100; diff --git a/backend/db/queries/feeds.sql b/backend/db/queries/feeds.sql index 6d4d172..8445532 100644 --- a/backend/db/queries/feeds.sql +++ b/backend/db/queries/feeds.sql @@ -1,11 +1,12 @@ -- name: GetFeed :one -SELECT id, url, title, fetched_at +SELECT id, url, title, fetched_at, is_subscribed FROM feeds WHERE id = ?; -- name: GetFeeds :many -SELECT id, url, title, fetched_at +SELECT id, url, title, fetched_at, is_subscribed FROM feeds +WHERE is_subscribed = 1 ORDER BY id; -- name: CreateFeed :one @@ -23,10 +24,16 @@ DELETE FROM feeds WHERE id = ?; -- name: GetFeedByURL :one -SELECT id, url, title, fetched_at +SELECT id, url, title, fetched_at, is_subscribed FROM feeds WHERE url = ?; -- name: GetFeedsToFetch :many SELECT id, url, fetched_at -FROM feeds; +FROM feeds +WHERE is_subscribed = 1; + +-- name: UnsubscribeFeed :exec +UPDATE feeds +SET is_subscribed = 0 +WHERE id = ?; diff --git a/backend/db/schema.sql b/backend/db/schema.sql index 5c2bf48..eb40dea 100644 --- a/backend/db/schema.sql +++ b/backend/db/schema.sql @@ -1,9 +1,10 @@ -- Feeds CREATE TABLE IF NOT EXISTS feeds ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - url TEXT NOT NULL, - title TEXT NOT NULL, - fetched_at TEXT NOT NULL + id INTEGER PRIMARY KEY AUTOINCREMENT, + url TEXT NOT NULL, + title TEXT NOT NULL, + fetched_at TEXT NOT NULL, + is_subscribed INTEGER NOT NULL DEFAULT 1 ); -- Articles diff --git a/backend/graphql/generated.go b/backend/graphql/generated.go index eec1a8d..b09d4d8 100644 --- a/backend/graphql/generated.go +++ b/backend/graphql/generated.go @@ -57,11 +57,12 @@ type ComplexityRoot struct { } Feed struct { - Articles func(childComplexity int) int - FetchedAt func(childComplexity int) int - ID func(childComplexity int) int - Title func(childComplexity int) int - URL func(childComplexity int) int + Articles func(childComplexity int) int + FetchedAt func(childComplexity int) int + ID func(childComplexity int) int + IsSubscribed func(childComplexity int) int + Title func(childComplexity int) int + URL func(childComplexity int) int } Mutation struct { @@ -70,7 +71,7 @@ type ComplexityRoot struct { MarkArticleUnread func(childComplexity int, id string) int MarkFeedRead func(childComplexity int, id string) int MarkFeedUnread func(childComplexity int, id string) int - RemoveFeed func(childComplexity int, id string) int + UnsubscribeFeed func(childComplexity int, id string) int } Query struct { @@ -84,7 +85,7 @@ type ComplexityRoot struct { type MutationResolver interface { AddFeed(ctx context.Context, url string) (*model.Feed, error) - RemoveFeed(ctx context.Context, id string) (bool, error) + UnsubscribeFeed(ctx context.Context, id string) (bool, error) MarkArticleRead(ctx context.Context, id string) (*model.Article, error) MarkArticleUnread(ctx context.Context, id string) (*model.Article, error) MarkFeedRead(ctx context.Context, id string) (*model.Feed, error) @@ -187,6 +188,13 @@ func (e *executableSchema) Complexity(ctx context.Context, typeName, field strin return e.complexity.Feed.ID(childComplexity), true + case "Feed.isSubscribed": + if e.complexity.Feed.IsSubscribed == nil { + break + } + + return e.complexity.Feed.IsSubscribed(childComplexity), true + case "Feed.title": if e.complexity.Feed.Title == nil { break @@ -261,17 +269,17 @@ func (e *executableSchema) Complexity(ctx context.Context, typeName, field strin return e.complexity.Mutation.MarkFeedUnread(childComplexity, args["id"].(string)), true - case "Mutation.removeFeed": - if e.complexity.Mutation.RemoveFeed == nil { + case "Mutation.unsubscribeFeed": + if e.complexity.Mutation.UnsubscribeFeed == nil { break } - args, err := ec.field_Mutation_removeFeed_args(ctx, rawArgs) + args, err := ec.field_Mutation_unsubscribeFeed_args(ctx, rawArgs) if err != nil { return 0, false } - return e.complexity.Mutation.RemoveFeed(childComplexity, args["id"].(string)), true + return e.complexity.Mutation.UnsubscribeFeed(childComplexity, args["id"].(string)), true case "Query.article": if e.complexity.Query.Article == nil { @@ -449,6 +457,11 @@ type Feed { fetchedAt: DateTime! """ + Whether the user is currently subscribed to this feed + """ + isSubscribed: Boolean! + + """ Articles belonging to this feed """ articles: [Article!]! @@ -534,9 +547,9 @@ type Mutation { addFeed(url: String!): Feed! """ - Remove a feed subscription and all its articles + Unsubscribe from a feed (preserves feed and article data) """ - removeFeed(id: ID!): Boolean! + unsubscribeFeed(id: ID!): Boolean! """ Mark an article as read @@ -706,17 +719,17 @@ func (ec *executionContext) field_Mutation_markFeedUnread_argsID( return zeroVal, nil } -func (ec *executionContext) field_Mutation_removeFeed_args(ctx context.Context, rawArgs map[string]any) (map[string]any, error) { +func (ec *executionContext) field_Mutation_unsubscribeFeed_args(ctx context.Context, rawArgs map[string]any) (map[string]any, error) { var err error args := map[string]any{} - arg0, err := ec.field_Mutation_removeFeed_argsID(ctx, rawArgs) + arg0, err := ec.field_Mutation_unsubscribeFeed_argsID(ctx, rawArgs) if err != nil { return nil, err } args["id"] = arg0 return args, nil } -func (ec *executionContext) field_Mutation_removeFeed_argsID( +func (ec *executionContext) field_Mutation_unsubscribeFeed_argsID( ctx context.Context, rawArgs map[string]any, ) (string, error) { @@ -1249,6 +1262,8 @@ func (ec *executionContext) fieldContext_Article_feed(_ context.Context, field g return ec.fieldContext_Feed_title(ctx, field) case "fetchedAt": return ec.fieldContext_Feed_fetchedAt(ctx, field) + case "isSubscribed": + return ec.fieldContext_Feed_isSubscribed(ctx, field) case "articles": return ec.fieldContext_Feed_articles(ctx, field) } @@ -1434,6 +1449,50 @@ func (ec *executionContext) fieldContext_Feed_fetchedAt(_ context.Context, field return fc, nil } +func (ec *executionContext) _Feed_isSubscribed(ctx context.Context, field graphql.CollectedField, obj *model.Feed) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_Feed_isSubscribed(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (any, error) { + ctx = rctx // use context from middleware stack in children + return obj.IsSubscribed, nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.(bool) + fc.Result = res + return ec.marshalNBoolean2bool(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext_Feed_isSubscribed(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "Feed", + Field: field, + IsMethod: false, + IsResolver: false, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return nil, errors.New("field of type Boolean does not have child fields") + }, + } + return fc, nil +} + func (ec *executionContext) _Feed_articles(ctx context.Context, field graphql.CollectedField, obj *model.Feed) (ret graphql.Marshaler) { fc, err := ec.fieldContext_Feed_articles(ctx, field) if err != nil { @@ -1541,6 +1600,8 @@ func (ec *executionContext) fieldContext_Mutation_addFeed(ctx context.Context, f return ec.fieldContext_Feed_title(ctx, field) case "fetchedAt": return ec.fieldContext_Feed_fetchedAt(ctx, field) + case "isSubscribed": + return ec.fieldContext_Feed_isSubscribed(ctx, field) case "articles": return ec.fieldContext_Feed_articles(ctx, field) } @@ -1561,8 +1622,8 @@ func (ec *executionContext) fieldContext_Mutation_addFeed(ctx context.Context, f return fc, nil } -func (ec *executionContext) _Mutation_removeFeed(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { - fc, err := ec.fieldContext_Mutation_removeFeed(ctx, field) +func (ec *executionContext) _Mutation_unsubscribeFeed(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_Mutation_unsubscribeFeed(ctx, field) if err != nil { return graphql.Null } @@ -1575,7 +1636,7 @@ func (ec *executionContext) _Mutation_removeFeed(ctx context.Context, field grap }() resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (any, error) { ctx = rctx // use context from middleware stack in children - return ec.resolvers.Mutation().RemoveFeed(rctx, fc.Args["id"].(string)) + return ec.resolvers.Mutation().UnsubscribeFeed(rctx, fc.Args["id"].(string)) }) if err != nil { ec.Error(ctx, err) @@ -1592,7 +1653,7 @@ func (ec *executionContext) _Mutation_removeFeed(ctx context.Context, field grap return ec.marshalNBoolean2bool(ctx, field.Selections, res) } -func (ec *executionContext) fieldContext_Mutation_removeFeed(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { +func (ec *executionContext) fieldContext_Mutation_unsubscribeFeed(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { fc = &graphql.FieldContext{ Object: "Mutation", Field: field, @@ -1609,7 +1670,7 @@ func (ec *executionContext) fieldContext_Mutation_removeFeed(ctx context.Context } }() ctx = graphql.WithFieldContext(ctx, fc) - if fc.Args, err = ec.field_Mutation_removeFeed_args(ctx, field.ArgumentMap(ec.Variables)); err != nil { + if fc.Args, err = ec.field_Mutation_unsubscribeFeed_args(ctx, field.ArgumentMap(ec.Variables)); err != nil { ec.Error(ctx, err) return fc, err } @@ -1805,6 +1866,8 @@ func (ec *executionContext) fieldContext_Mutation_markFeedRead(ctx context.Conte return ec.fieldContext_Feed_title(ctx, field) case "fetchedAt": return ec.fieldContext_Feed_fetchedAt(ctx, field) + case "isSubscribed": + return ec.fieldContext_Feed_isSubscribed(ctx, field) case "articles": return ec.fieldContext_Feed_articles(ctx, field) } @@ -1872,6 +1935,8 @@ func (ec *executionContext) fieldContext_Mutation_markFeedUnread(ctx context.Con return ec.fieldContext_Feed_title(ctx, field) case "fetchedAt": return ec.fieldContext_Feed_fetchedAt(ctx, field) + case "isSubscribed": + return ec.fieldContext_Feed_isSubscribed(ctx, field) case "articles": return ec.fieldContext_Feed_articles(ctx, field) } @@ -1939,6 +2004,8 @@ func (ec *executionContext) fieldContext_Query_feeds(_ context.Context, field gr return ec.fieldContext_Feed_title(ctx, field) case "fetchedAt": return ec.fieldContext_Feed_fetchedAt(ctx, field) + case "isSubscribed": + return ec.fieldContext_Feed_isSubscribed(ctx, field) case "articles": return ec.fieldContext_Feed_articles(ctx, field) } @@ -2112,6 +2179,8 @@ func (ec *executionContext) fieldContext_Query_feed(ctx context.Context, field g return ec.fieldContext_Feed_title(ctx, field) case "fetchedAt": return ec.fieldContext_Feed_fetchedAt(ctx, field) + case "isSubscribed": + return ec.fieldContext_Feed_isSubscribed(ctx, field) case "articles": return ec.fieldContext_Feed_articles(ctx, field) } @@ -4390,6 +4459,11 @@ func (ec *executionContext) _Feed(ctx context.Context, sel ast.SelectionSet, obj if out.Values[i] == graphql.Null { out.Invalids++ } + case "isSubscribed": + out.Values[i] = ec._Feed_isSubscribed(ctx, field, obj) + if out.Values[i] == graphql.Null { + out.Invalids++ + } case "articles": out.Values[i] = ec._Feed_articles(ctx, field, obj) if out.Values[i] == graphql.Null { @@ -4444,9 +4518,9 @@ func (ec *executionContext) _Mutation(ctx context.Context, sel ast.SelectionSet) if out.Values[i] == graphql.Null { out.Invalids++ } - case "removeFeed": + case "unsubscribeFeed": out.Values[i] = ec.OperationContext.RootResolverMiddleware(innerCtx, func(ctx context.Context) (res graphql.Marshaler) { - return ec._Mutation_removeFeed(ctx, field) + return ec._Mutation_unsubscribeFeed(ctx, field) }) if out.Values[i] == graphql.Null { out.Invalids++ diff --git a/backend/graphql/model/generated.go b/backend/graphql/model/generated.go index bd5dcca..25ed8d8 100644 --- a/backend/graphql/model/generated.go +++ b/backend/graphql/model/generated.go @@ -30,6 +30,8 @@ type Feed struct { Title string `json:"title"` // Timestamp when the feed was last fetched FetchedAt string `json:"fetchedAt"` + // Whether the user is currently subscribed to this feed + IsSubscribed bool `json:"isSubscribed"` // Articles belonging to this feed Articles []*Article `json:"articles"` } diff --git a/backend/graphql/resolver/schema.resolvers.go b/backend/graphql/resolver/schema.resolvers.go index 0ee771b..cadcd33 100644 --- a/backend/graphql/resolver/schema.resolvers.go +++ b/backend/graphql/resolver/schema.resolvers.go @@ -52,47 +52,24 @@ func (r *mutationResolver) AddFeed(ctx context.Context, url string) (*model.Feed } return &model.Feed{ - ID: strconv.FormatInt(dbFeed.ID, 10), - URL: dbFeed.Url, - Title: dbFeed.Title, - FetchedAt: dbFeed.FetchedAt, + ID: strconv.FormatInt(dbFeed.ID, 10), + URL: dbFeed.Url, + Title: dbFeed.Title, + FetchedAt: dbFeed.FetchedAt, + IsSubscribed: dbFeed.IsSubscribed == 1, }, nil } -// RemoveFeed is the resolver for the removeFeed field. -func (r *mutationResolver) RemoveFeed(ctx context.Context, id string) (bool, error) { +// UnsubscribeFeed is the resolver for the unsubscribeFeed field. +func (r *mutationResolver) UnsubscribeFeed(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() + err = r.Queries.UnsubscribeFeed(ctx, feedID) 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 false, fmt.Errorf("failed to unsubscribe from feed: %w", err) } return true, nil @@ -182,10 +159,11 @@ func (r *queryResolver) Feeds(ctx context.Context) ([]*model.Feed, error) { 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, + ID: strconv.FormatInt(dbFeed.ID, 10), + URL: dbFeed.Url, + Title: dbFeed.Title, + FetchedAt: dbFeed.FetchedAt, + IsSubscribed: dbFeed.IsSubscribed == 1, }) } @@ -209,9 +187,10 @@ func (r *queryResolver) UnreadArticles(ctx context.Context) ([]*model.Article, e URL: row.Url, IsRead: row.IsRead == 1, Feed: &model.Feed{ - ID: strconv.FormatInt(row.FeedID2, 10), - URL: row.FeedUrl, - Title: row.FeedTitle, + ID: strconv.FormatInt(row.FeedID2, 10), + URL: row.FeedUrl, + Title: row.FeedTitle, + IsSubscribed: row.FeedIsSubscribed == 1, }, }) } @@ -236,9 +215,10 @@ func (r *queryResolver) ReadArticles(ctx context.Context) ([]*model.Article, err URL: row.Url, IsRead: row.IsRead == 1, Feed: &model.Feed{ - ID: strconv.FormatInt(row.FeedID2, 10), - URL: row.FeedUrl, - Title: row.FeedTitle, + ID: strconv.FormatInt(row.FeedID2, 10), + URL: row.FeedUrl, + Title: row.FeedTitle, + IsSubscribed: row.FeedIsSubscribed == 1, }, }) } @@ -262,10 +242,11 @@ func (r *queryResolver) Feed(ctx context.Context, id string) (*model.Feed, error } return &model.Feed{ - ID: strconv.FormatInt(dbFeed.ID, 10), - URL: dbFeed.Url, - Title: dbFeed.Title, - FetchedAt: dbFeed.FetchedAt, + ID: strconv.FormatInt(dbFeed.ID, 10), + URL: dbFeed.Url, + Title: dbFeed.Title, + FetchedAt: dbFeed.FetchedAt, + IsSubscribed: dbFeed.IsSubscribed == 1, }, nil } diff --git a/common/graphql/schema.graphql b/common/graphql/schema.graphql index 2ee1365..75d4d77 100644 --- a/common/graphql/schema.graphql +++ b/common/graphql/schema.graphql @@ -25,6 +25,11 @@ type Feed { fetchedAt: DateTime! """ + Whether the user is currently subscribed to this feed + """ + isSubscribed: Boolean! + + """ Articles belonging to this feed """ articles: [Article!]! @@ -110,9 +115,9 @@ type Mutation { addFeed(url: String!): Feed! """ - Remove a feed subscription and all its articles + Unsubscribe from a feed (preserves feed and article data) """ - removeFeed(id: ID!): Boolean! + unsubscribeFeed(id: ID!): Boolean! """ Mark an article as read diff --git a/frontend/src/components/AddFeedForm.tsx b/frontend/src/components/AddFeedForm.tsx index 79e26e3..519f9e3 100644 --- a/frontend/src/components/AddFeedForm.tsx +++ b/frontend/src/components/AddFeedForm.tsx @@ -28,7 +28,9 @@ export function AddFeedForm({ onFeedAdded }: Props) { onFeedAdded?.(); } } catch (error) { - setError(error instanceof Error ? error.message : "Failed to add feed"); + setError( + error instanceof Error ? error.message : "Failed to subscribe to feed", + ); } }; @@ -47,7 +49,7 @@ export function AddFeedForm({ onFeedAdded }: Props) { <form onSubmit={handleSubmit} className="space-y-4 p-4"> <div className="rounded-lg border border-gray-200 bg-white p-4 shadow-sm"> <h3 className="text-lg font-semibold text-gray-900 mb-4"> - Add New Feed + Subscribe to New Feed </h3> <div className="flex gap-2"> <div className="flex-1"> @@ -80,7 +82,7 @@ export function AddFeedForm({ onFeedAdded }: Props) { ) : ( <FontAwesomeIcon icon={faPlus} className="mr-2" /> )} - Add Feed + Subscribe </button> </div> </div> diff --git a/frontend/src/components/FeedList.tsx b/frontend/src/components/FeedList.tsx index 7e46e78..e5b6751 100644 --- a/frontend/src/components/FeedList.tsx +++ b/frontend/src/components/FeedList.tsx @@ -9,17 +9,17 @@ import { GetFeedsDocument, MarkFeedReadDocument, MarkFeedUnreadDocument, - RemoveFeedDocument, + UnsubscribeFeedDocument, } from "../graphql/generated/graphql"; interface Props { - onFeedDeleted?: () => void; + onFeedUnsubscribed?: () => void; selectedFeeds?: Set<string>; onSelectFeed?: (feedId: string, selected: boolean) => void; } export function FeedList({ - onFeedDeleted, + onFeedUnsubscribed, selectedFeeds, onSelectFeed, }: Props) { @@ -29,7 +29,7 @@ export function FeedList({ const [, markFeedRead] = useMutation(MarkFeedReadDocument); const [, markFeedUnread] = useMutation(MarkFeedUnreadDocument); - const [, removeFeed] = useMutation(RemoveFeedDocument); + const [, unsubscribeFeed] = useMutation(UnsubscribeFeedDocument); const handleMarkAllRead = async (feedId: string) => { await markFeedRead({ id: feedId }); @@ -39,13 +39,13 @@ export function FeedList({ await markFeedUnread({ id: feedId }); }; - const handleDeleteFeed = async (feedId: string) => { + const handleUnsubscribeFeed = async (feedId: string) => { const confirmed = window.confirm( - "Are you sure you want to delete this feed?", + "Are you sure you want to unsubscribe from this feed?", ); if (confirmed) { - await removeFeed({ id: feedId }); - onFeedDeleted?.(); + await unsubscribeFeed({ id: feedId }); + onFeedUnsubscribed?.(); } }; @@ -134,9 +134,9 @@ export function FeedList({ </button> <button type="button" - onClick={() => handleDeleteFeed(feed.id)} + onClick={() => handleUnsubscribeFeed(feed.id)} className="rounded p-2 text-red-600 hover:bg-red-50 hover:text-red-700" - title="Delete feed" + title="Unsubscribe from feed" > <FontAwesomeIcon icon={faTrash} /> </button> diff --git a/frontend/src/graphql/generated/gql.ts b/frontend/src/graphql/generated/gql.ts index ae8c1e1..b0b965d 100644 --- a/frontend/src/graphql/generated/gql.ts +++ b/frontend/src/graphql/generated/gql.ts @@ -14,12 +14,12 @@ import type { TypedDocumentNode as DocumentNode } from '@graphql-typed-document- * Learn more about it here: https://the-guild.dev/graphql/codegen/plugins/presets/preset-client#reducing-bundle-size */ type Documents = { - "mutation AddFeed($url: String!) {\n addFeed(url: $url) {\n id\n url\n title\n fetchedAt\n }\n}\n\nmutation RemoveFeed($id: ID!) {\n removeFeed(id: $id)\n}\n\nmutation MarkArticleRead($id: ID!) {\n markArticleRead(id: $id) {\n id\n feedId\n guid\n title\n url\n isRead\n }\n}\n\nmutation MarkArticleUnread($id: ID!) {\n markArticleUnread(id: $id) {\n id\n feedId\n guid\n title\n url\n isRead\n }\n}\n\nmutation MarkFeedRead($id: ID!) {\n markFeedRead(id: $id) {\n id\n url\n title\n fetchedAt\n }\n}\n\nmutation MarkFeedUnread($id: ID!) {\n markFeedUnread(id: $id) {\n id\n url\n title\n fetchedAt\n }\n}": typeof types.AddFeedDocument, - "query GetFeeds {\n feeds {\n id\n url\n title\n fetchedAt\n articles {\n id\n isRead\n }\n }\n}\n\nquery GetUnreadArticles {\n unreadArticles {\n id\n feedId\n guid\n title\n url\n isRead\n feed {\n id\n title\n }\n }\n}\n\nquery GetReadArticles {\n readArticles {\n id\n feedId\n guid\n title\n url\n isRead\n feed {\n id\n title\n }\n }\n}\n\nquery GetFeed($id: ID!) {\n feed(id: $id) {\n id\n url\n title\n fetchedAt\n articles {\n id\n guid\n title\n url\n isRead\n }\n }\n}\n\nquery GetArticle($id: ID!) {\n article(id: $id) {\n id\n feedId\n guid\n title\n url\n isRead\n feed {\n id\n title\n }\n }\n}": typeof types.GetFeedsDocument, + "mutation AddFeed($url: String!) {\n addFeed(url: $url) {\n id\n url\n title\n fetchedAt\n }\n}\n\nmutation UnsubscribeFeed($id: ID!) {\n unsubscribeFeed(id: $id)\n}\n\nmutation MarkArticleRead($id: ID!) {\n markArticleRead(id: $id) {\n id\n feedId\n guid\n title\n url\n isRead\n }\n}\n\nmutation MarkArticleUnread($id: ID!) {\n markArticleUnread(id: $id) {\n id\n feedId\n guid\n title\n url\n isRead\n }\n}\n\nmutation MarkFeedRead($id: ID!) {\n markFeedRead(id: $id) {\n id\n url\n title\n fetchedAt\n }\n}\n\nmutation MarkFeedUnread($id: ID!) {\n markFeedUnread(id: $id) {\n id\n url\n title\n fetchedAt\n }\n}": typeof types.AddFeedDocument, + "query GetFeeds {\n feeds {\n id\n url\n title\n fetchedAt\n isSubscribed\n articles {\n id\n isRead\n }\n }\n}\n\nquery GetUnreadArticles {\n unreadArticles {\n id\n feedId\n guid\n title\n url\n isRead\n feed {\n id\n title\n isSubscribed\n }\n }\n}\n\nquery GetReadArticles {\n readArticles {\n id\n feedId\n guid\n title\n url\n isRead\n feed {\n id\n title\n isSubscribed\n }\n }\n}\n\nquery GetFeed($id: ID!) {\n feed(id: $id) {\n id\n url\n title\n fetchedAt\n isSubscribed\n articles {\n id\n guid\n title\n url\n isRead\n }\n }\n}\n\nquery GetArticle($id: ID!) {\n article(id: $id) {\n id\n feedId\n guid\n title\n url\n isRead\n feed {\n id\n title\n isSubscribed\n }\n }\n}": typeof types.GetFeedsDocument, }; const documents: Documents = { - "mutation AddFeed($url: String!) {\n addFeed(url: $url) {\n id\n url\n title\n fetchedAt\n }\n}\n\nmutation RemoveFeed($id: ID!) {\n removeFeed(id: $id)\n}\n\nmutation MarkArticleRead($id: ID!) {\n markArticleRead(id: $id) {\n id\n feedId\n guid\n title\n url\n isRead\n }\n}\n\nmutation MarkArticleUnread($id: ID!) {\n markArticleUnread(id: $id) {\n id\n feedId\n guid\n title\n url\n isRead\n }\n}\n\nmutation MarkFeedRead($id: ID!) {\n markFeedRead(id: $id) {\n id\n url\n title\n fetchedAt\n }\n}\n\nmutation MarkFeedUnread($id: ID!) {\n markFeedUnread(id: $id) {\n id\n url\n title\n fetchedAt\n }\n}": types.AddFeedDocument, - "query GetFeeds {\n feeds {\n id\n url\n title\n fetchedAt\n articles {\n id\n isRead\n }\n }\n}\n\nquery GetUnreadArticles {\n unreadArticles {\n id\n feedId\n guid\n title\n url\n isRead\n feed {\n id\n title\n }\n }\n}\n\nquery GetReadArticles {\n readArticles {\n id\n feedId\n guid\n title\n url\n isRead\n feed {\n id\n title\n }\n }\n}\n\nquery GetFeed($id: ID!) {\n feed(id: $id) {\n id\n url\n title\n fetchedAt\n articles {\n id\n guid\n title\n url\n isRead\n }\n }\n}\n\nquery GetArticle($id: ID!) {\n article(id: $id) {\n id\n feedId\n guid\n title\n url\n isRead\n feed {\n id\n title\n }\n }\n}": types.GetFeedsDocument, + "mutation AddFeed($url: String!) {\n addFeed(url: $url) {\n id\n url\n title\n fetchedAt\n }\n}\n\nmutation UnsubscribeFeed($id: ID!) {\n unsubscribeFeed(id: $id)\n}\n\nmutation MarkArticleRead($id: ID!) {\n markArticleRead(id: $id) {\n id\n feedId\n guid\n title\n url\n isRead\n }\n}\n\nmutation MarkArticleUnread($id: ID!) {\n markArticleUnread(id: $id) {\n id\n feedId\n guid\n title\n url\n isRead\n }\n}\n\nmutation MarkFeedRead($id: ID!) {\n markFeedRead(id: $id) {\n id\n url\n title\n fetchedAt\n }\n}\n\nmutation MarkFeedUnread($id: ID!) {\n markFeedUnread(id: $id) {\n id\n url\n title\n fetchedAt\n }\n}": types.AddFeedDocument, + "query GetFeeds {\n feeds {\n id\n url\n title\n fetchedAt\n isSubscribed\n articles {\n id\n isRead\n }\n }\n}\n\nquery GetUnreadArticles {\n unreadArticles {\n id\n feedId\n guid\n title\n url\n isRead\n feed {\n id\n title\n isSubscribed\n }\n }\n}\n\nquery GetReadArticles {\n readArticles {\n id\n feedId\n guid\n title\n url\n isRead\n feed {\n id\n title\n isSubscribed\n }\n }\n}\n\nquery GetFeed($id: ID!) {\n feed(id: $id) {\n id\n url\n title\n fetchedAt\n isSubscribed\n articles {\n id\n guid\n title\n url\n isRead\n }\n }\n}\n\nquery GetArticle($id: ID!) {\n article(id: $id) {\n id\n feedId\n guid\n title\n url\n isRead\n feed {\n id\n title\n isSubscribed\n }\n }\n}": types.GetFeedsDocument, }; /** @@ -39,11 +39,11 @@ export function graphql(source: string): unknown; /** * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. */ -export function graphql(source: "mutation AddFeed($url: String!) {\n addFeed(url: $url) {\n id\n url\n title\n fetchedAt\n }\n}\n\nmutation RemoveFeed($id: ID!) {\n removeFeed(id: $id)\n}\n\nmutation MarkArticleRead($id: ID!) {\n markArticleRead(id: $id) {\n id\n feedId\n guid\n title\n url\n isRead\n }\n}\n\nmutation MarkArticleUnread($id: ID!) {\n markArticleUnread(id: $id) {\n id\n feedId\n guid\n title\n url\n isRead\n }\n}\n\nmutation MarkFeedRead($id: ID!) {\n markFeedRead(id: $id) {\n id\n url\n title\n fetchedAt\n }\n}\n\nmutation MarkFeedUnread($id: ID!) {\n markFeedUnread(id: $id) {\n id\n url\n title\n fetchedAt\n }\n}"): (typeof documents)["mutation AddFeed($url: String!) {\n addFeed(url: $url) {\n id\n url\n title\n fetchedAt\n }\n}\n\nmutation RemoveFeed($id: ID!) {\n removeFeed(id: $id)\n}\n\nmutation MarkArticleRead($id: ID!) {\n markArticleRead(id: $id) {\n id\n feedId\n guid\n title\n url\n isRead\n }\n}\n\nmutation MarkArticleUnread($id: ID!) {\n markArticleUnread(id: $id) {\n id\n feedId\n guid\n title\n url\n isRead\n }\n}\n\nmutation MarkFeedRead($id: ID!) {\n markFeedRead(id: $id) {\n id\n url\n title\n fetchedAt\n }\n}\n\nmutation MarkFeedUnread($id: ID!) {\n markFeedUnread(id: $id) {\n id\n url\n title\n fetchedAt\n }\n}"]; +export function graphql(source: "mutation AddFeed($url: String!) {\n addFeed(url: $url) {\n id\n url\n title\n fetchedAt\n }\n}\n\nmutation UnsubscribeFeed($id: ID!) {\n unsubscribeFeed(id: $id)\n}\n\nmutation MarkArticleRead($id: ID!) {\n markArticleRead(id: $id) {\n id\n feedId\n guid\n title\n url\n isRead\n }\n}\n\nmutation MarkArticleUnread($id: ID!) {\n markArticleUnread(id: $id) {\n id\n feedId\n guid\n title\n url\n isRead\n }\n}\n\nmutation MarkFeedRead($id: ID!) {\n markFeedRead(id: $id) {\n id\n url\n title\n fetchedAt\n }\n}\n\nmutation MarkFeedUnread($id: ID!) {\n markFeedUnread(id: $id) {\n id\n url\n title\n fetchedAt\n }\n}"): (typeof documents)["mutation AddFeed($url: String!) {\n addFeed(url: $url) {\n id\n url\n title\n fetchedAt\n }\n}\n\nmutation UnsubscribeFeed($id: ID!) {\n unsubscribeFeed(id: $id)\n}\n\nmutation MarkArticleRead($id: ID!) {\n markArticleRead(id: $id) {\n id\n feedId\n guid\n title\n url\n isRead\n }\n}\n\nmutation MarkArticleUnread($id: ID!) {\n markArticleUnread(id: $id) {\n id\n feedId\n guid\n title\n url\n isRead\n }\n}\n\nmutation MarkFeedRead($id: ID!) {\n markFeedRead(id: $id) {\n id\n url\n title\n fetchedAt\n }\n}\n\nmutation MarkFeedUnread($id: ID!) {\n markFeedUnread(id: $id) {\n id\n url\n title\n fetchedAt\n }\n}"]; /** * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. */ -export function graphql(source: "query GetFeeds {\n feeds {\n id\n url\n title\n fetchedAt\n articles {\n id\n isRead\n }\n }\n}\n\nquery GetUnreadArticles {\n unreadArticles {\n id\n feedId\n guid\n title\n url\n isRead\n feed {\n id\n title\n }\n }\n}\n\nquery GetReadArticles {\n readArticles {\n id\n feedId\n guid\n title\n url\n isRead\n feed {\n id\n title\n }\n }\n}\n\nquery GetFeed($id: ID!) {\n feed(id: $id) {\n id\n url\n title\n fetchedAt\n articles {\n id\n guid\n title\n url\n isRead\n }\n }\n}\n\nquery GetArticle($id: ID!) {\n article(id: $id) {\n id\n feedId\n guid\n title\n url\n isRead\n feed {\n id\n title\n }\n }\n}"): (typeof documents)["query GetFeeds {\n feeds {\n id\n url\n title\n fetchedAt\n articles {\n id\n isRead\n }\n }\n}\n\nquery GetUnreadArticles {\n unreadArticles {\n id\n feedId\n guid\n title\n url\n isRead\n feed {\n id\n title\n }\n }\n}\n\nquery GetReadArticles {\n readArticles {\n id\n feedId\n guid\n title\n url\n isRead\n feed {\n id\n title\n }\n }\n}\n\nquery GetFeed($id: ID!) {\n feed(id: $id) {\n id\n url\n title\n fetchedAt\n articles {\n id\n guid\n title\n url\n isRead\n }\n }\n}\n\nquery GetArticle($id: ID!) {\n article(id: $id) {\n id\n feedId\n guid\n title\n url\n isRead\n feed {\n id\n title\n }\n }\n}"]; +export function graphql(source: "query GetFeeds {\n feeds {\n id\n url\n title\n fetchedAt\n isSubscribed\n articles {\n id\n isRead\n }\n }\n}\n\nquery GetUnreadArticles {\n unreadArticles {\n id\n feedId\n guid\n title\n url\n isRead\n feed {\n id\n title\n isSubscribed\n }\n }\n}\n\nquery GetReadArticles {\n readArticles {\n id\n feedId\n guid\n title\n url\n isRead\n feed {\n id\n title\n isSubscribed\n }\n }\n}\n\nquery GetFeed($id: ID!) {\n feed(id: $id) {\n id\n url\n title\n fetchedAt\n isSubscribed\n articles {\n id\n guid\n title\n url\n isRead\n }\n }\n}\n\nquery GetArticle($id: ID!) {\n article(id: $id) {\n id\n feedId\n guid\n title\n url\n isRead\n feed {\n id\n title\n isSubscribed\n }\n }\n}"): (typeof documents)["query GetFeeds {\n feeds {\n id\n url\n title\n fetchedAt\n isSubscribed\n articles {\n id\n isRead\n }\n }\n}\n\nquery GetUnreadArticles {\n unreadArticles {\n id\n feedId\n guid\n title\n url\n isRead\n feed {\n id\n title\n isSubscribed\n }\n }\n}\n\nquery GetReadArticles {\n readArticles {\n id\n feedId\n guid\n title\n url\n isRead\n feed {\n id\n title\n isSubscribed\n }\n }\n}\n\nquery GetFeed($id: ID!) {\n feed(id: $id) {\n id\n url\n title\n fetchedAt\n isSubscribed\n articles {\n id\n guid\n title\n url\n isRead\n }\n }\n}\n\nquery GetArticle($id: ID!) {\n article(id: $id) {\n id\n feedId\n guid\n title\n url\n isRead\n feed {\n id\n title\n isSubscribed\n }\n }\n}"]; export function graphql(source: string) { return (documents as any)[source] ?? {}; diff --git a/frontend/src/graphql/generated/graphql.ts b/frontend/src/graphql/generated/graphql.ts index a54f1b6..22b34c5 100644 --- a/frontend/src/graphql/generated/graphql.ts +++ b/frontend/src/graphql/generated/graphql.ts @@ -43,6 +43,8 @@ export type Feed = { fetchedAt: Scalars['DateTime']['output']; /** Unique identifier for the feed */ id: Scalars['ID']['output']; + /** Whether the user is currently subscribed to this feed */ + isSubscribed: Scalars['Boolean']['output']; /** Title of the feed (extracted from feed metadata) */ title: Scalars['String']['output']; /** URL of the RSS/Atom feed */ @@ -61,8 +63,8 @@ export type Mutation = { markFeedRead: Feed; /** Mark all articles in a feed as unread */ markFeedUnread: Feed; - /** Remove a feed subscription and all its articles */ - removeFeed: Scalars['Boolean']['output']; + /** Unsubscribe from a feed (preserves feed and article data) */ + unsubscribeFeed: Scalars['Boolean']['output']; }; @@ -97,7 +99,7 @@ export type MutationMarkFeedUnreadArgs = { /** Root mutation type for modifying data */ -export type MutationRemoveFeedArgs = { +export type MutationUnsubscribeFeedArgs = { id: Scalars['ID']['input']; }; @@ -134,12 +136,12 @@ export type AddFeedMutationVariables = Exact<{ export type AddFeedMutation = { addFeed: { id: string, url: string, title: string, fetchedAt: string } }; -export type RemoveFeedMutationVariables = Exact<{ +export type UnsubscribeFeedMutationVariables = Exact<{ id: Scalars['ID']['input']; }>; -export type RemoveFeedMutation = { removeFeed: boolean }; +export type UnsubscribeFeedMutation = { unsubscribeFeed: boolean }; export type MarkArticleReadMutationVariables = Exact<{ id: Scalars['ID']['input']; @@ -172,41 +174,41 @@ export type MarkFeedUnreadMutation = { markFeedUnread: { id: string, url: string export type GetFeedsQueryVariables = Exact<{ [key: string]: never; }>; -export type GetFeedsQuery = { feeds: Array<{ id: string, url: string, title: string, fetchedAt: string, articles: Array<{ id: string, isRead: boolean }> }> }; +export type GetFeedsQuery = { feeds: Array<{ id: string, url: string, title: string, fetchedAt: string, isSubscribed: boolean, articles: Array<{ id: string, isRead: boolean }> }> }; export type GetUnreadArticlesQueryVariables = Exact<{ [key: string]: never; }>; -export type GetUnreadArticlesQuery = { unreadArticles: Array<{ id: string, feedId: string, guid: string, title: string, url: string, isRead: boolean, feed: { id: string, title: string } }> }; +export type GetUnreadArticlesQuery = { unreadArticles: Array<{ id: string, feedId: string, guid: string, title: string, url: string, isRead: boolean, feed: { id: string, title: string, isSubscribed: boolean } }> }; export type GetReadArticlesQueryVariables = Exact<{ [key: string]: never; }>; -export type GetReadArticlesQuery = { readArticles: Array<{ id: string, feedId: string, guid: string, title: string, url: string, isRead: boolean, feed: { id: string, title: string } }> }; +export type GetReadArticlesQuery = { readArticles: Array<{ id: string, feedId: string, guid: string, title: string, url: string, isRead: boolean, feed: { id: string, title: string, isSubscribed: boolean } }> }; export type GetFeedQueryVariables = Exact<{ id: Scalars['ID']['input']; }>; -export type GetFeedQuery = { feed?: { id: string, url: string, title: string, fetchedAt: string, articles: Array<{ id: string, guid: string, title: string, url: string, isRead: boolean }> } | null }; +export type GetFeedQuery = { feed?: { id: string, url: string, title: string, fetchedAt: string, isSubscribed: boolean, articles: Array<{ id: string, guid: string, title: string, url: string, isRead: boolean }> } | null }; export type GetArticleQueryVariables = Exact<{ id: Scalars['ID']['input']; }>; -export type GetArticleQuery = { article?: { id: string, feedId: string, guid: string, title: string, url: string, isRead: boolean, feed: { id: string, title: string } } | null }; +export type GetArticleQuery = { article?: { id: string, feedId: string, guid: string, title: string, url: string, isRead: boolean, feed: { id: string, title: string, isSubscribed: boolean } } | null }; export const AddFeedDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"AddFeed"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"url"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"addFeed"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"url"},"value":{"kind":"Variable","name":{"kind":"Name","value":"url"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"url"}},{"kind":"Field","name":{"kind":"Name","value":"title"}},{"kind":"Field","name":{"kind":"Name","value":"fetchedAt"}}]}}]}}]} as unknown as DocumentNode<AddFeedMutation, AddFeedMutationVariables>; -export const RemoveFeedDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"RemoveFeed"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"id"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"ID"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"removeFeed"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"id"},"value":{"kind":"Variable","name":{"kind":"Name","value":"id"}}}]}]}}]} as unknown as DocumentNode<RemoveFeedMutation, RemoveFeedMutationVariables>; +export const UnsubscribeFeedDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"UnsubscribeFeed"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"id"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"ID"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"unsubscribeFeed"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"id"},"value":{"kind":"Variable","name":{"kind":"Name","value":"id"}}}]}]}}]} as unknown as DocumentNode<UnsubscribeFeedMutation, UnsubscribeFeedMutationVariables>; export const MarkArticleReadDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"MarkArticleRead"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"id"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"ID"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"markArticleRead"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"id"},"value":{"kind":"Variable","name":{"kind":"Name","value":"id"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"feedId"}},{"kind":"Field","name":{"kind":"Name","value":"guid"}},{"kind":"Field","name":{"kind":"Name","value":"title"}},{"kind":"Field","name":{"kind":"Name","value":"url"}},{"kind":"Field","name":{"kind":"Name","value":"isRead"}}]}}]}}]} as unknown as DocumentNode<MarkArticleReadMutation, MarkArticleReadMutationVariables>; export const MarkArticleUnreadDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"MarkArticleUnread"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"id"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"ID"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"markArticleUnread"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"id"},"value":{"kind":"Variable","name":{"kind":"Name","value":"id"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"feedId"}},{"kind":"Field","name":{"kind":"Name","value":"guid"}},{"kind":"Field","name":{"kind":"Name","value":"title"}},{"kind":"Field","name":{"kind":"Name","value":"url"}},{"kind":"Field","name":{"kind":"Name","value":"isRead"}}]}}]}}]} as unknown as DocumentNode<MarkArticleUnreadMutation, MarkArticleUnreadMutationVariables>; export const MarkFeedReadDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"MarkFeedRead"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"id"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"ID"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"markFeedRead"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"id"},"value":{"kind":"Variable","name":{"kind":"Name","value":"id"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"url"}},{"kind":"Field","name":{"kind":"Name","value":"title"}},{"kind":"Field","name":{"kind":"Name","value":"fetchedAt"}}]}}]}}]} as unknown as DocumentNode<MarkFeedReadMutation, MarkFeedReadMutationVariables>; export const MarkFeedUnreadDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"MarkFeedUnread"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"id"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"ID"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"markFeedUnread"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"id"},"value":{"kind":"Variable","name":{"kind":"Name","value":"id"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"url"}},{"kind":"Field","name":{"kind":"Name","value":"title"}},{"kind":"Field","name":{"kind":"Name","value":"fetchedAt"}}]}}]}}]} as unknown as DocumentNode<MarkFeedUnreadMutation, MarkFeedUnreadMutationVariables>; -export const GetFeedsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"GetFeeds"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"feeds"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"url"}},{"kind":"Field","name":{"kind":"Name","value":"title"}},{"kind":"Field","name":{"kind":"Name","value":"fetchedAt"}},{"kind":"Field","name":{"kind":"Name","value":"articles"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"isRead"}}]}}]}}]}}]} as unknown as DocumentNode<GetFeedsQuery, GetFeedsQueryVariables>; -export const GetUnreadArticlesDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"GetUnreadArticles"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"unreadArticles"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"feedId"}},{"kind":"Field","name":{"kind":"Name","value":"guid"}},{"kind":"Field","name":{"kind":"Name","value":"title"}},{"kind":"Field","name":{"kind":"Name","value":"url"}},{"kind":"Field","name":{"kind":"Name","value":"isRead"}},{"kind":"Field","name":{"kind":"Name","value":"feed"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"title"}}]}}]}}]}}]} as unknown as DocumentNode<GetUnreadArticlesQuery, GetUnreadArticlesQueryVariables>; -export const GetReadArticlesDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"GetReadArticles"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"readArticles"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"feedId"}},{"kind":"Field","name":{"kind":"Name","value":"guid"}},{"kind":"Field","name":{"kind":"Name","value":"title"}},{"kind":"Field","name":{"kind":"Name","value":"url"}},{"kind":"Field","name":{"kind":"Name","value":"isRead"}},{"kind":"Field","name":{"kind":"Name","value":"feed"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"title"}}]}}]}}]}}]} as unknown as DocumentNode<GetReadArticlesQuery, GetReadArticlesQueryVariables>; -export const GetFeedDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"GetFeed"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"id"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"ID"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"feed"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"id"},"value":{"kind":"Variable","name":{"kind":"Name","value":"id"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"url"}},{"kind":"Field","name":{"kind":"Name","value":"title"}},{"kind":"Field","name":{"kind":"Name","value":"fetchedAt"}},{"kind":"Field","name":{"kind":"Name","value":"articles"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"guid"}},{"kind":"Field","name":{"kind":"Name","value":"title"}},{"kind":"Field","name":{"kind":"Name","value":"url"}},{"kind":"Field","name":{"kind":"Name","value":"isRead"}}]}}]}}]}}]} as unknown as DocumentNode<GetFeedQuery, GetFeedQueryVariables>; -export const GetArticleDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"GetArticle"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"id"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"ID"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"article"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"id"},"value":{"kind":"Variable","name":{"kind":"Name","value":"id"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"feedId"}},{"kind":"Field","name":{"kind":"Name","value":"guid"}},{"kind":"Field","name":{"kind":"Name","value":"title"}},{"kind":"Field","name":{"kind":"Name","value":"url"}},{"kind":"Field","name":{"kind":"Name","value":"isRead"}},{"kind":"Field","name":{"kind":"Name","value":"feed"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"title"}}]}}]}}]}}]} as unknown as DocumentNode<GetArticleQuery, GetArticleQueryVariables>;
\ No newline at end of file +export const GetFeedsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"GetFeeds"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"feeds"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"url"}},{"kind":"Field","name":{"kind":"Name","value":"title"}},{"kind":"Field","name":{"kind":"Name","value":"fetchedAt"}},{"kind":"Field","name":{"kind":"Name","value":"isSubscribed"}},{"kind":"Field","name":{"kind":"Name","value":"articles"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"isRead"}}]}}]}}]}}]} as unknown as DocumentNode<GetFeedsQuery, GetFeedsQueryVariables>; +export const GetUnreadArticlesDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"GetUnreadArticles"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"unreadArticles"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"feedId"}},{"kind":"Field","name":{"kind":"Name","value":"guid"}},{"kind":"Field","name":{"kind":"Name","value":"title"}},{"kind":"Field","name":{"kind":"Name","value":"url"}},{"kind":"Field","name":{"kind":"Name","value":"isRead"}},{"kind":"Field","name":{"kind":"Name","value":"feed"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"title"}},{"kind":"Field","name":{"kind":"Name","value":"isSubscribed"}}]}}]}}]}}]} as unknown as DocumentNode<GetUnreadArticlesQuery, GetUnreadArticlesQueryVariables>; +export const GetReadArticlesDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"GetReadArticles"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"readArticles"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"feedId"}},{"kind":"Field","name":{"kind":"Name","value":"guid"}},{"kind":"Field","name":{"kind":"Name","value":"title"}},{"kind":"Field","name":{"kind":"Name","value":"url"}},{"kind":"Field","name":{"kind":"Name","value":"isRead"}},{"kind":"Field","name":{"kind":"Name","value":"feed"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"title"}},{"kind":"Field","name":{"kind":"Name","value":"isSubscribed"}}]}}]}}]}}]} as unknown as DocumentNode<GetReadArticlesQuery, GetReadArticlesQueryVariables>; +export const GetFeedDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"GetFeed"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"id"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"ID"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"feed"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"id"},"value":{"kind":"Variable","name":{"kind":"Name","value":"id"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"url"}},{"kind":"Field","name":{"kind":"Name","value":"title"}},{"kind":"Field","name":{"kind":"Name","value":"fetchedAt"}},{"kind":"Field","name":{"kind":"Name","value":"isSubscribed"}},{"kind":"Field","name":{"kind":"Name","value":"articles"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"guid"}},{"kind":"Field","name":{"kind":"Name","value":"title"}},{"kind":"Field","name":{"kind":"Name","value":"url"}},{"kind":"Field","name":{"kind":"Name","value":"isRead"}}]}}]}}]}}]} as unknown as DocumentNode<GetFeedQuery, GetFeedQueryVariables>; +export const GetArticleDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"GetArticle"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"id"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"ID"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"article"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"id"},"value":{"kind":"Variable","name":{"kind":"Name","value":"id"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"feedId"}},{"kind":"Field","name":{"kind":"Name","value":"guid"}},{"kind":"Field","name":{"kind":"Name","value":"title"}},{"kind":"Field","name":{"kind":"Name","value":"url"}},{"kind":"Field","name":{"kind":"Name","value":"isRead"}},{"kind":"Field","name":{"kind":"Name","value":"feed"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"title"}},{"kind":"Field","name":{"kind":"Name","value":"isSubscribed"}}]}}]}}]}}]} as unknown as DocumentNode<GetArticleQuery, GetArticleQueryVariables>;
\ No newline at end of file diff --git a/frontend/src/graphql/mutations.graphql b/frontend/src/graphql/mutations.graphql index 8bcbcec..9070118 100644 --- a/frontend/src/graphql/mutations.graphql +++ b/frontend/src/graphql/mutations.graphql @@ -7,8 +7,8 @@ mutation AddFeed($url: String!) { } } -mutation RemoveFeed($id: ID!) { - removeFeed(id: $id) +mutation UnsubscribeFeed($id: ID!) { + unsubscribeFeed(id: $id) } mutation MarkArticleRead($id: ID!) { diff --git a/frontend/src/graphql/queries.graphql b/frontend/src/graphql/queries.graphql index af4ac01..0e96851 100644 --- a/frontend/src/graphql/queries.graphql +++ b/frontend/src/graphql/queries.graphql @@ -4,6 +4,7 @@ query GetFeeds { url title fetchedAt + isSubscribed articles { id isRead @@ -22,6 +23,7 @@ query GetUnreadArticles { feed { id title + isSubscribed } } } @@ -37,6 +39,7 @@ query GetReadArticles { feed { id title + isSubscribed } } } @@ -47,6 +50,7 @@ query GetFeed($id: ID!) { url title fetchedAt + isSubscribed articles { id guid @@ -68,6 +72,7 @@ query GetArticle($id: ID!) { feed { id title + isSubscribed } } } diff --git a/frontend/src/pages/Settings.tsx b/frontend/src/pages/Settings.tsx index 78fc306..81c90e0 100644 --- a/frontend/src/pages/Settings.tsx +++ b/frontend/src/pages/Settings.tsx @@ -5,7 +5,7 @@ import { GetFeedsDocument, MarkFeedReadDocument, MarkFeedUnreadDocument, - RemoveFeedDocument, + UnsubscribeFeedDocument, } from "../graphql/generated/graphql"; export function Settings() { @@ -14,7 +14,7 @@ export function Settings() { }); const [, markFeedRead] = useMutation(MarkFeedReadDocument); const [, markFeedUnread] = useMutation(MarkFeedUnreadDocument); - const [, removeFeed] = useMutation(RemoveFeedDocument); + const [, unsubscribeFeed] = useMutation(UnsubscribeFeedDocument); const [selectedFeeds, setSelectedFeeds] = useState<Set<string>>(new Set()); @@ -22,7 +22,7 @@ export function Settings() { refetchFeeds(); }; - const handleFeedDeleted = () => { + const handleFeedUnsubscribed = () => { refetchFeeds(); setSelectedFeeds(new Set()); }; @@ -62,17 +62,17 @@ export function Settings() { refetchFeeds(); }; - const handleBulkDelete = async () => { + const handleBulkUnsubscribe = async () => { const confirmed = window.confirm( - `Are you sure you want to delete ${selectedFeeds.size} selected feeds?`, + `Are you sure you want to unsubscribe from ${selectedFeeds.size} selected feeds?`, ); if (!confirmed) return; const promises = Array.from(selectedFeeds).map((feedId) => - removeFeed({ id: feedId }), + unsubscribeFeed({ id: feedId }), ); await Promise.all(promises); - handleFeedDeleted(); + handleFeedUnsubscribed(); }; const hasFeeds = feedsData?.feeds && feedsData.feeds.length > 0; @@ -82,10 +82,10 @@ export function Settings() { <div className="mx-auto max-w-4xl"> <h1 className="mb-6 text-2xl font-bold text-gray-900">Feed Settings</h1> - {/* Add New Feed Section */} + {/* Subscribe to New Feed Section */} <div className="mb-8"> <h2 className="mb-4 text-xl font-semibold text-gray-800"> - Add New Feed + Subscribe to New Feed </h2> <AddFeedForm onFeedAdded={handleFeedAdded} /> </div> @@ -134,10 +134,10 @@ export function Settings() { </button> <button type="button" - onClick={handleBulkDelete} + onClick={handleBulkUnsubscribe} className="rounded px-3 py-1 text-sm font-medium text-red-700 hover:bg-red-100" > - Delete Selected + Unsubscribe Selected </button> </div> </div> @@ -145,7 +145,7 @@ export function Settings() { )} <FeedList - onFeedDeleted={handleFeedDeleted} + onFeedUnsubscribed={handleFeedUnsubscribed} selectedFeeds={selectedFeeds} onSelectFeed={handleSelectFeed} /> @@ -1,7 +1,7 @@ list: @just -l -serve: build +serve: FEEDAKA_BASE_PATH="" FEEDAKA_PORT=8080 ./backend/feedaka build: |
