aboutsummaryrefslogtreecommitdiffhomepage
path: root/backend
diff options
context:
space:
mode:
authornsfisis <nsfisis@gmail.com>2025-07-12 23:58:57 +0900
committernsfisis <nsfisis@gmail.com>2025-07-12 23:58:57 +0900
commit756b66b31fd02215fc2d8a30ae263a3bf08a90a6 (patch)
tree245cc37a1d81728260246ae5241eeb8225ec0ddc /backend
parentfbe4bff7e8b6a5239c490601436fb3638dc8e13b (diff)
downloadfeedaka-756b66b31fd02215fc2d8a30ae263a3bf08a90a6.tar.gz
feedaka-756b66b31fd02215fc2d8a30ae263a3bf08a90a6.tar.zst
feedaka-756b66b31fd02215fc2d8a30ae263a3bf08a90a6.zip
feat(backend,frontend): add feature to unsubscribe feed
Diffstat (limited to 'backend')
-rw-r--r--backend/db/articles.sql.go70
-rw-r--r--backend/db/feeds.sql.go25
-rw-r--r--backend/db/models.go9
-rw-r--r--backend/db/queries/articles.sql10
-rw-r--r--backend/db/queries/feeds.sql15
-rw-r--r--backend/db/schema.sql9
-rw-r--r--backend/graphql/generated.go120
-rw-r--r--backend/graphql/model/generated.go2
-rw-r--r--backend/graphql/resolver/schema.resolvers.go73
9 files changed, 211 insertions, 122 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
}